microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
e966efd30e552f4e84d5ae7224515199738ddd8e

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/compiler/init/init.ts

281lines · modecode

1import { readdir } from "fs/promises";
2import jsyaml from "js-yaml";
3import Mustache from "mustache";
4import prompts from "prompts";
5import { CadlConfigFilename } from "../config/config-loader.js";
6import { logDiagnostics } from "../core/diagnostics.js";
7import { formatCadl } from "../core/formatter.js";
8import { getBaseFileName, joinPaths } from "../core/path-utils.js";
9import { SchemaValidator } from "../core/schema-validator.js";
10import { CompilerHost, SourceFile } from "../core/types.js";
11import { readUrlOrPath, resolveRelativeUrlOrPath } from "../core/util.js";
12import { InitTemplate, InitTemplateDefinitionsSchema, InitTemplateFile } from "./init-template.js";
13
14interface ScaffoldingConfig extends InitTemplate {
15 /**
16 * Path where this template was laoded from.
17 */
18 templateUri: string;
19
20 /**
21 * Directory where the project should be initialized.
22 */
23 directory: string;
24
25 /**
26 * Name of the project.
27 */
28 name: string;
29
30 /**
31 * List of libraries to include
32 */
33 libraries: string[];
34
35 /**
36 * Custom parameters provided in the tempalates.
37 */
38 parameters: Record<string, any>;
39}
40
41export async function initCadlProject(
42 host: CompilerHost,
43 directory: string,
44 templatesUrl?: string
45) {
46 if (!(await confirmDirectoryEmpty(directory))) {
47 return;
48 }
49 const folderName = getBaseFileName(directory);
50
51 const template = await selectTemplate(host, templatesUrl);
52 const { name } = await prompts([
53 {
54 type: "text",
55 name: "name",
56 message: `Project name`,
57 initial: folderName,
58 },
59 ]);
60
61 const libraries = await selectLibraries(template);
62 const parameters = await promptCustomParameters(template);
63 const scaffoldingConfig: ScaffoldingConfig = {
64 ...template,
65 templateUri: templatesUrl ?? ".",
66 libraries,
67 name,
68 directory,
69 parameters,
70 };
71 await scaffoldNewProject(host, scaffoldingConfig);
72}
73
74async function promptCustomParameters(template: InitTemplate): Promise<Record<string, any>> {
75 if (!template.inputs) {
76 return {};
77 }
78
79 const promptList = [...Object.entries(template.inputs)].map(([name, input]) => {
80 return {
81 name,
82 type: input.type,
83 message: input.description,
84 initial: input.initialValue,
85 };
86 });
87 return await prompts(promptList);
88}
89
90async function isDirectoryEmpty(directory: string) {
91 try {
92 const files = await readdir(directory);
93 return files.length === 0;
94 } catch {
95 return true;
96 }
97}
98
99async function confirmDirectoryEmpty(directory: string) {
100 if (await isDirectoryEmpty(directory)) {
101 return true;
102 }
103
104 return confirm(
105 `Folder '${directory}' is not empty. Are you sure you want to initialize a new project here?`
106 );
107}
108
109const builtInTemplates: Record<string, InitTemplate> = {
110 empty: {
111 title: "Empty project",
112 description: "Create an empty project.",
113 libraries: [],
114 },
115 rest: {
116 title: "Generic Rest API",
117 description: "Create a project representing a generic Rest API",
118 libraries: ["@cadl-lang/rest", "@cadl-lang/openapi3"],
119 config: {
120 emitters: {
121 "@cadl-lang/openapi3": true,
122 },
123 },
124 },
125};
126
127async function confirm(message: string): Promise<boolean> {
128 const { confirm } = await prompts({
129 name: "confirm",
130 type: "confirm",
131 message,
132 initial: true,
133 });
134 return confirm;
135}
136
137async function downloadTemplates(
138 host: CompilerHost,
139 templatesUrl: string
140): Promise<Record<string, InitTemplate>> {
141 const file = await readUrlOrPath(host, templatesUrl);
142
143 const json = JSON.parse(file.text);
144 validateTemplateDefinitions(host, json, file);
145 return json;
146}
147
148async function selectTemplate(
149 host: CompilerHost,
150 templatesUrl: string | undefined
151): Promise<InitTemplate> {
152 const templates =
153 templatesUrl === undefined ? builtInTemplates : await downloadTemplates(host, templatesUrl);
154 return promptTemplateSelection(templates);
155}
156
157async function promptTemplateSelection(
158 templates: Record<string, InitTemplate>
159): Promise<InitTemplate> {
160 const { templateName } = await prompts({
161 type: "select",
162 name: "templateName",
163 message: "Please select a template",
164 choices: Object.entries(templates).map(([id, template]) => {
165 return { value: id, description: template.description, title: template.title };
166 }),
167 });
168 const template = templates[templateName];
169 if (!template) {
170 throw new Error(`Unexpected error: Cannot find template ${templateName}`);
171 }
172 return template;
173}
174
175async function selectLibraries(template: InitTemplate): Promise<string[]> {
176 if (template.libraries.length === 0) {
177 return [];
178 }
179
180 const libraryChoices = template.libraries.map((x) => ({ name: x, description: "" }));
181
182 const { libraries } = await prompts({
183 type: "multiselect",
184 name: "libraries",
185 message: "Update the libraries?",
186 choices: libraryChoices.map((x) => {
187 return {
188 title: x.name,
189 description: x.description,
190 value: x.name,
191 selected: true,
192 };
193 }),
194 initial: template.libraries as any,
195 });
196
197 return libraries;
198}
199
200export async function scaffoldNewProject(host: CompilerHost, config: ScaffoldingConfig) {
201 await writePackageJson(host, config);
202 await writeConfig(host, config);
203 await writeMain(host, config);
204 await writeFiles(host, config);
205
206 // eslint-disable-next-line no-console
207 console.log("Cadl init completed. You can run `cadl install` now to install dependencies.");
208}
209
210async function writePackageJson(host: CompilerHost, config: ScaffoldingConfig) {
211 const dependencies: Record<string, string> = {
212 "@cadl-lang/compiler": "latest",
213 };
214
215 for (const library of config.libraries) {
216 dependencies[library] = "latest";
217 }
218
219 const packageJson = {
220 name: config.name,
221 dependencies,
222 private: true,
223 };
224
225 return host.writeFile(
226 joinPaths(config.directory, "package.json"),
227 JSON.stringify(packageJson, null, 2)
228 );
229}
230
231async function writeConfig(host: CompilerHost, config: ScaffoldingConfig) {
232 if (!config.config) {
233 return;
234 }
235 const content = jsyaml.dump(config.config);
236 return host.writeFile(joinPaths(config.directory, CadlConfigFilename), content);
237}
238
239async function writeMain(host: CompilerHost, config: ScaffoldingConfig) {
240 const dependencies: Record<string, string> = {};
241
242 for (const library of config.libraries) {
243 dependencies[library] = "latest";
244 }
245
246 const lines = [...config.libraries.map((x) => `import "${x}";`), ""];
247 const content = lines.join("\n");
248
249 return host.writeFile(joinPaths(config.directory, "main.cadl"), await formatCadl(content));
250}
251
252async function writeFiles(host: CompilerHost, config: ScaffoldingConfig) {
253 if (!config.files) {
254 return;
255 }
256 for (const file of config.files) {
257 await writeFile(host, config, file);
258 }
259}
260
261async function writeFile(host: CompilerHost, config: ScaffoldingConfig, file: InitTemplateFile) {
262 const template = await readUrlOrPath(
263 host,
264 resolveRelativeUrlOrPath(config.templateUri, file.path)
265 );
266 const content = Mustache.render(template.text, config);
267 return host.writeFile(joinPaths(config.directory, file.destination), content);
268}
269
270function validateTemplateDefinitions(
271 host: CompilerHost,
272 templates: unknown,
273 file: SourceFile
274): asserts templates is Record<string, InitTemplate> {
275 const validator = new SchemaValidator(InitTemplateDefinitionsSchema);
276 const diagnostics = validator.validate(templates, file);
277 if (diagnostics.length > 0) {
278 logDiagnostics(diagnostics, host.logSink);
279 throw new Error("Template contained error.");
280 }
281}
282