@sdeverywhere/create 0.2.43 → 0.2.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,71 +1,55 @@
1
- // src/index.ts
2
- import { existsSync as existsSync5 } from "fs";
3
- import { posix as posix2, relative as relative3, resolve as resolvePath5 } from "path";
4
- import { bgCyan, black, bold as bold8, cyan as cyan5, dim as dim9, green as green9 } from "kleur/colors";
5
- import ora9 from "ora";
6
- import prompts9 from "prompts";
7
- import detectPackageManager from "which-pm-runs";
8
- import yargs from "yargs-parser";
9
-
10
- // src/step-code-format.ts
11
- import { bold, dim, green } from "kleur/colors";
1
+ import { existsSync, mkdtempSync, readdirSync, renameSync, rmSync } from "fs";
2
+ import { dirname, extname, join, parse, posix, relative, resolve, sep } from "path";
3
+ import { bgCyan, black, bold, cyan, dim, green, red, reset, yellow } from "kleur/colors";
12
4
  import ora from "ora";
13
5
  import prompts from "prompts";
14
- var promptMessage = `Would you like your project to use WebAssembly?`;
15
- var noDesc = `* Choose this if you want to get started using SDEverywhere quickly
6
+ import detectPackageManager from "which-pm-runs";
7
+ import yargs from "yargs-parser";
8
+ import { mkdir, readFile, readdir, writeFile } from "fs/promises";
9
+ import { parseAndGenerate } from "@sdeverywhere/compile";
10
+ import { execa, execaCommand } from "execa";
11
+ import { findUp } from "find-up";
12
+ import { copy } from "fs-extra";
13
+ import { tmpdir } from "os";
14
+ import { downloadTemplate } from "giget";
15
+ //#region src/step-code-format.ts
16
+ const promptMessage = `Would you like your project to use WebAssembly?`;
17
+ const FORMATS = [{
18
+ title: "No, generate a JavaScript model",
19
+ description: `\
20
+ * Choose this if you want to get started using SDEverywhere quickly
16
21
  and you don't want to install the Emscripten SDK.
17
22
  * This will generate a model that uses JavaScript code only, which
18
- doesn't require extra build tools, but runs slower than WebAssembly.`;
19
- var yesDesc = `* Choose this if you want this script to install the Emscripten SDK
23
+ doesn't require extra build tools, but runs slower than WebAssembly.`,
24
+ value: "js"
25
+ }, {
26
+ title: "Yes, generate a WebAssembly model",
27
+ description: `\
28
+ * Choose this if you want this script to install the Emscripten SDK
20
29
  for you, or if you already have it installed.
21
30
  * This will generate a model that uses WebAssembly, which requires an
22
- additional build step, but runs faster than pure JavaScript code.`;
23
- var FORMATS = [
24
- {
25
- title: "No, generate a JavaScript model",
26
- description: noDesc,
27
- value: "js"
28
- },
29
- {
30
- title: "Yes, generate a WebAssembly model",
31
- description: yesDesc,
32
- value: "c"
33
- }
34
- ];
31
+ additional build step, but runs faster than pure JavaScript code.`,
32
+ value: "c"
33
+ }];
35
34
  async function chooseCodeFormat() {
36
- const options = await prompts(
37
- [
38
- {
39
- type: "select",
40
- name: "format",
41
- message: promptMessage,
42
- choices: FORMATS
43
- }
44
- ],
45
- {
46
- onCancel: () => {
47
- ora().info(dim("Operation cancelled."));
48
- process.exit(0);
49
- }
50
- }
51
- );
52
- const target = options.format === "c" ? "WebAssembly" : "JavaScript";
53
- const successMessage = green(
54
- `Configuring your project to generate ${target}. See "${bold("sde.config.js")}" for details.`
55
- );
56
- ora(successMessage).succeed();
57
- return options.format;
35
+ const options = await prompts([{
36
+ type: "select",
37
+ name: "format",
38
+ message: promptMessage,
39
+ choices: FORMATS
40
+ }], { onCancel: () => {
41
+ ora().info(dim("Operation cancelled."));
42
+ process.exit(0);
43
+ } });
44
+ const target = options.format === "c" ? "WebAssembly" : "JavaScript";
45
+ const successMessage = green(`Configuring your project to generate ${target}. See "${bold("sde.config.js")}" for details.`);
46
+ ora(successMessage).succeed();
47
+ return options.format;
58
48
  }
59
-
60
- // src/step-config.ts
61
- import { existsSync } from "fs";
62
- import { mkdir, readFile, writeFile } from "fs/promises";
63
- import { dirname, join as joinPath, parse as parsePath, relative, resolve as resolvePath } from "path";
64
- import { bold as bold2, cyan, dim as dim2, green as green2, reset, yellow } from "kleur/colors";
65
- import ora2 from "ora";
66
- import prompts2 from "prompts";
67
- import { parseAndGenerate } from "@sdeverywhere/compile";
68
- var sampleChecksContent = `# yaml-language-server: $schema=SCHEMA_PATH
49
+ //#endregion
50
+ //#region src/step-config.ts
51
+ const sampleChecksContent = `\
52
+ # yaml-language-server: $schema=SCHEMA_PATH
69
53
 
70
54
  #
71
55
  # This file contains "check" tests that exercise your model under different input
@@ -85,7 +69,8 @@ var sampleChecksContent = `# yaml-language-server: $schema=SCHEMA_PATH
85
69
  predicates:
86
70
  - gt: 0
87
71
  `;
88
- var sampleComparisonsContent = `# yaml-language-server: $schema=SCHEMA_PATH
72
+ const sampleComparisonsContent = `\
73
+ # yaml-language-server: $schema=SCHEMA_PATH
89
74
 
90
75
  #
91
76
  # This file contains definitions of custom comparison scenarios, which allow you to see
@@ -107,842 +92,644 @@ var sampleComparisonsContent = `# yaml-language-server: $schema=SCHEMA_PATH
107
92
  at: 20
108
93
  `;
109
94
  async function updateSdeConfig(projDir, modelPath, genFormat) {
110
- const configPath = joinPath(projDir, "sde.config.js");
111
- let configText = await readFile(configPath, "utf8");
112
- configText = configText.replace(`const genFormat = 'js'`, `const genFormat = '${genFormat}'`);
113
- configText = configText.replaceAll("model/MODEL_NAME.mdl", modelPath);
114
- await writeFile(configPath, configText);
95
+ const configPath = join(projDir, "sde.config.js");
96
+ let configText = await readFile(configPath, "utf8");
97
+ configText = configText.replace(`const genFormat = 'js'`, `const genFormat = '${genFormat}'`);
98
+ configText = configText.replaceAll("model/MODEL_NAME.mdl", modelPath);
99
+ await writeFile(configPath, configText);
115
100
  }
116
101
  async function generateYaml(projDir, kind, template) {
117
- const yamlDir = joinPath(projDir, "model", kind);
118
- const yamlPath = joinPath(yamlDir, `${kind}.yaml`);
119
- if (!existsSync(yamlPath)) {
120
- let relProjPath = relative(dirname(yamlPath), projDir);
121
- if (relProjPath.length === 0) {
122
- relProjPath = "./";
123
- }
124
- await mkdir(yamlDir, { recursive: true });
125
- const nodeModulesPart = joinPath(relProjPath, "node_modules");
126
- const schemaName = kind === "checks" ? "check" : "comparison";
127
- const checkCorePart = `@sdeverywhere/check-core/schema/${schemaName}.schema.json`;
128
- const schemaPath = `${nodeModulesPart}/${checkCorePart}`;
129
- const yamlContent = template.replace("SCHEMA_PATH", schemaPath);
130
- await writeFile(yamlPath, yamlContent);
131
- }
102
+ const yamlDir = join(projDir, "model", kind);
103
+ const yamlPath = join(yamlDir, `${kind}.yaml`);
104
+ if (!existsSync(yamlPath)) {
105
+ let relProjPath = relative(dirname(yamlPath), projDir);
106
+ if (relProjPath.length === 0) relProjPath = "./";
107
+ await mkdir(yamlDir, { recursive: true });
108
+ const schemaPath = `${join(relProjPath, "node_modules")}/${`@sdeverywhere/check-core/schema/${kind === "checks" ? "check" : "comparison"}.schema.json`}`;
109
+ const yamlContent = template.replace("SCHEMA_PATH", schemaPath);
110
+ await writeFile(yamlPath, yamlContent);
111
+ }
132
112
  }
133
113
  async function generateSampleYamlFiles(projDir) {
134
- await generateYaml(projDir, "checks", sampleChecksContent);
135
- await generateYaml(projDir, "comparisons", sampleComparisonsContent);
114
+ await generateYaml(projDir, "checks", sampleChecksContent);
115
+ await generateYaml(projDir, "comparisons", sampleComparisonsContent);
136
116
  }
137
117
  async function chooseGenConfig(projDir, modelPath) {
138
- let mdlVars;
139
- try {
140
- mdlVars = await readModelVars(projDir, modelPath);
141
- } catch (e) {
142
- console.log(e);
143
- ora2(
144
- yellow("The model file failed to load. We will continue setting things up, and you can diagnose the issue later.")
145
- ).warn();
146
- return;
147
- }
148
- let initialTime;
149
- let finalTime;
150
- const validVars = [];
151
- for (const v of mdlVars) {
152
- const varName = v.name.toLowerCase();
153
- let skip = false;
154
- switch (varName) {
155
- case "final time":
156
- skip = true;
157
- if (v.kind === "const") {
158
- finalTime = v.value;
159
- }
160
- break;
161
- case "initial time":
162
- skip = true;
163
- if (v.kind === "const") {
164
- initialTime = v.value;
165
- }
166
- break;
167
- case "saveper":
168
- case "time":
169
- case "time step":
170
- skip = true;
171
- break;
172
- default:
173
- break;
174
- }
175
- if (!skip) {
176
- validVars.push(v);
177
- }
178
- }
179
- if (initialTime === void 0) {
180
- initialTime = 0;
181
- }
182
- if (finalTime === void 0) {
183
- finalTime = 100;
184
- }
185
- const datFiles = [];
186
- const datPart = datFiles.join(";");
187
- const modelCsvFile = joinPath(projDir, "config", "model.csv");
188
- const origModelCsvContent = await readFile(modelCsvFile, "utf8");
189
- const modelCsvHeader = origModelCsvContent.split("\n")[0];
190
- const bundleListing = "false";
191
- const customConstants = "false";
192
- const customLookups = "false";
193
- const customOutputs = "false";
194
- const modelCsvLine = `${initialTime},${finalTime},${datPart},${bundleListing},${customConstants},${customLookups},${customOutputs}`;
195
- const newModelCsvContent = `${modelCsvHeader}
196
- ${modelCsvLine}
197
- `;
198
- await writeFile(modelCsvFile, newModelCsvContent);
199
- await chooseGenGraphConfig(projDir, validVars);
200
- console.log();
201
- await chooseGenSliderConfig(projDir, validVars);
118
+ let mdlVars;
119
+ try {
120
+ mdlVars = await readModelVars(projDir, modelPath);
121
+ } catch (e) {
122
+ console.log(e);
123
+ ora(yellow("The model file failed to load. We will continue setting things up, and you can diagnose the issue later.")).warn();
124
+ return;
125
+ }
126
+ let initialTime;
127
+ let finalTime;
128
+ const validVars = [];
129
+ for (const v of mdlVars) {
130
+ const varName = v.name.toLowerCase();
131
+ let skip = false;
132
+ switch (varName) {
133
+ case "final time":
134
+ skip = true;
135
+ if (v.kind === "const") finalTime = v.value;
136
+ break;
137
+ case "initial time":
138
+ skip = true;
139
+ if (v.kind === "const") initialTime = v.value;
140
+ break;
141
+ case "saveper":
142
+ case "time":
143
+ case "time step": skip = true;
144
+ }
145
+ if (!skip) validVars.push(v);
146
+ }
147
+ if (initialTime === void 0) initialTime = 0;
148
+ if (finalTime === void 0) finalTime = 100;
149
+ const datPart = [].join(";");
150
+ const modelCsvFile = join(projDir, "config", "model.csv");
151
+ const newModelCsvContent = `${(await readFile(modelCsvFile, "utf8")).split("\n")[0]}\n${`${initialTime},${finalTime},${datPart},false,false,false,false`}\n`;
152
+ await writeFile(modelCsvFile, newModelCsvContent);
153
+ await chooseGenGraphConfig(projDir, validVars);
154
+ console.log();
155
+ await chooseGenSliderConfig(projDir, validVars);
202
156
  }
203
157
  async function chooseGenGraphConfig(projDir, mdlVars) {
204
- const genResponse = await prompts2(
205
- {
206
- type: "confirm",
207
- name: "genGraph",
208
- message: `Would you like to configure a graph to get you started? ${reset(dim2("(recommended)"))}`,
209
- initial: true
210
- },
211
- {
212
- onCancel: () => {
213
- ora2().info(dim2("Operation cancelled."));
214
- process.exit(0);
215
- }
216
- }
217
- );
218
- if (!genResponse.genGraph) {
219
- ora2().info(dim2(`No problem! You can edit the "${cyan("config/graphs.csv")}" file later.`));
220
- return;
221
- }
222
- const outputVarNames = [];
223
- for (const mdlVar of mdlVars) {
224
- if (mdlVar.kind === "aux" || mdlVar.kind === "level") {
225
- outputVarNames.push(mdlVar.name);
226
- }
227
- }
228
- outputVarNames.sort((a, b) => {
229
- return a.toLowerCase().localeCompare(b.toLowerCase());
230
- });
231
- const choices = outputVarNames.map((f) => {
232
- return {
233
- title: f,
234
- value: f
235
- };
236
- });
237
- const varsResponse = await prompts2(
238
- {
239
- type: "autocompleteMultiselect",
240
- name: "vars",
241
- message: "Choose up to three output variables to display in the graph",
242
- choices,
243
- max: 3
244
- },
245
- {
246
- onCancel: () => {
247
- ora2().info(dim2("Operation cancelled."));
248
- process.exit(0);
249
- }
250
- }
251
- );
252
- const graphsCsvFile = joinPath(projDir, "config", "graphs.csv");
253
- const origGraphsCsvContent = await readFile(graphsCsvFile, "utf8");
254
- const graphsCsvHeader = origGraphsCsvContent.split("\n")[0];
255
- let newGraphsCsvContent = `${graphsCsvHeader}
256
- `;
257
- if (varsResponse.vars.length > 0) {
258
- const csvLine = graphsCsvLine(varsResponse.vars);
259
- newGraphsCsvContent += `${csvLine}
260
- `;
261
- }
262
- await writeFile(graphsCsvFile, newGraphsCsvContent);
263
- if (varsResponse.vars.length === 0) {
264
- ora2().info(dim2(`No variables selected. You can edit the "${cyan("config/graphs.csv")}" file later.`));
265
- return;
266
- }
267
- ora2(
268
- green2(`Added graph to "${bold2("config/graphs.csv")}". ${dim2("You can configure graphs in that file later.")}`)
269
- ).succeed();
158
+ if (!(await prompts({
159
+ type: "confirm",
160
+ name: "genGraph",
161
+ message: `Would you like to configure a graph to get you started? ${reset(dim("(recommended)"))}`,
162
+ initial: true
163
+ }, { onCancel: () => {
164
+ ora().info(dim("Operation cancelled."));
165
+ process.exit(0);
166
+ } })).genGraph) {
167
+ ora().info(dim(`No problem! You can edit the "${cyan("config/graphs.csv")}" file later.`));
168
+ return;
169
+ }
170
+ const outputVarNames = [];
171
+ for (const mdlVar of mdlVars) if (mdlVar.kind === "aux" || mdlVar.kind === "level") outputVarNames.push(mdlVar.name);
172
+ outputVarNames.sort((a, b) => {
173
+ return a.toLowerCase().localeCompare(b.toLowerCase());
174
+ });
175
+ const choices = outputVarNames.map((f) => {
176
+ return {
177
+ title: f,
178
+ value: f
179
+ };
180
+ });
181
+ const varsResponse = await prompts({
182
+ type: "autocompleteMultiselect",
183
+ name: "vars",
184
+ message: "Choose up to three output variables to display in the graph",
185
+ choices,
186
+ max: 3
187
+ }, { onCancel: () => {
188
+ ora().info(dim("Operation cancelled."));
189
+ process.exit(0);
190
+ } });
191
+ const graphsCsvFile = join(projDir, "config", "graphs.csv");
192
+ let newGraphsCsvContent = `${(await readFile(graphsCsvFile, "utf8")).split("\n")[0]}\n`;
193
+ if (varsResponse.vars.length > 0) {
194
+ const csvLine = graphsCsvLine(varsResponse.vars);
195
+ newGraphsCsvContent += `${csvLine}\n`;
196
+ }
197
+ await writeFile(graphsCsvFile, newGraphsCsvContent);
198
+ if (varsResponse.vars.length === 0) {
199
+ ora().info(dim(`No variables selected. You can edit the "${cyan("config/graphs.csv")}" file later.`));
200
+ return;
201
+ }
202
+ ora(green(`Added graph to "${bold("config/graphs.csv")}". ${dim("You can configure graphs in that file later.")}`)).succeed();
270
203
  }
271
204
  function graphsCsvLine(outputVarNames) {
272
- const colors = ["blue", "red", "green"];
273
- const a = Array(131).fill("");
274
- a[0] = "1";
275
- a[2] = "Graphs";
276
- a[3] = "Graph Title";
277
- a[7] = "line";
278
- let index = 26;
279
- let colorIndex = 0;
280
- for (const outputVarName of outputVarNames) {
281
- const escapedName = escapeCsvField(outputVarName);
282
- a[index + 0] = escapedName;
283
- a[index + 2] = "line";
284
- a[index + 3] = escapedName;
285
- a[index + 4] = colors[colorIndex];
286
- index += 7;
287
- colorIndex++;
288
- }
289
- return a.join(",");
205
+ const colors = [
206
+ "blue",
207
+ "red",
208
+ "green"
209
+ ];
210
+ const a = Array(131).fill("");
211
+ a[0] = "1";
212
+ a[2] = "Graphs";
213
+ a[3] = "Graph Title";
214
+ a[7] = "line";
215
+ let index = 26;
216
+ let colorIndex = 0;
217
+ for (const outputVarName of outputVarNames) {
218
+ const escapedName = escapeCsvField(outputVarName);
219
+ a[index + 0] = escapedName;
220
+ a[index + 2] = "line";
221
+ a[index + 3] = escapedName;
222
+ a[index + 4] = colors[colorIndex];
223
+ index += 7;
224
+ colorIndex++;
225
+ }
226
+ return a.join(",");
290
227
  }
291
228
  async function chooseGenSliderConfig(projDir, mdlVars) {
292
- const genResponse = await prompts2(
293
- {
294
- type: "confirm",
295
- name: "genSliders",
296
- message: `Would you like to configure a few sliders to get you started? ${reset(dim2("(recommended)"))}`,
297
- initial: true
298
- },
299
- {
300
- onCancel: () => {
301
- ora2().info(dim2("Operation cancelled."));
302
- process.exit(0);
303
- }
304
- }
305
- );
306
- if (!genResponse.genSliders) {
307
- ora2().info(dim2(`No problem! You can edit the "${cyan("config/inputs.csv")}" file later.`));
308
- return;
309
- }
310
- const inputVarNames = [];
311
- for (const mdlVar of mdlVars) {
312
- if (mdlVar.kind === "const") {
313
- inputVarNames.push(mdlVar.name);
314
- }
315
- }
316
- inputVarNames.sort((a, b) => {
317
- return a.toLowerCase().localeCompare(b.toLowerCase());
318
- });
319
- const choices = inputVarNames.map((f) => {
320
- return {
321
- title: f,
322
- value: f
323
- };
324
- });
325
- const varsResponse = await prompts2(
326
- {
327
- type: "autocompleteMultiselect",
328
- name: "vars",
329
- message: "Choose up to three input variables to control with sliders",
330
- choices,
331
- max: 3
332
- },
333
- {
334
- onCancel: () => {
335
- ora2().info(dim2("Operation cancelled."));
336
- process.exit(0);
337
- }
338
- }
339
- );
340
- const inputsCsvFile = joinPath(projDir, "config", "inputs.csv");
341
- const origInputsCsvContent = await readFile(inputsCsvFile, "utf8");
342
- const inputsCsvHeader = origInputsCsvContent.split("\n")[0];
343
- let newInputsCsvContent = `${inputsCsvHeader}
344
- `;
345
- if (varsResponse.vars.length > 0) {
346
- let idNumber = 1;
347
- for (const inputVarName of varsResponse.vars) {
348
- const inputVar = mdlVars.find((v) => v.name === inputVarName);
349
- if (inputVar && inputVar.kind === "const") {
350
- const defaultValue = inputVar.value;
351
- const csvLine = inputsCsvLine(inputVarName, defaultValue, idNumber.toString());
352
- newInputsCsvContent += `${csvLine}
353
- `;
354
- idNumber++;
355
- }
356
- }
357
- }
358
- await writeFile(inputsCsvFile, newInputsCsvContent);
359
- if (varsResponse.vars.length === 0) {
360
- ora2().info(dim2(`No variables selected. You can edit the "${cyan("config/inputs.csv")}" file later.`));
361
- return;
362
- }
363
- const slidersText = varsResponse.vars.length > 1 ? "sliders" : "sliders";
364
- ora2(
365
- green2(
366
- `Added ${slidersText} to "${bold2("config/inputs.csv")}". ${dim2("You can configure sliders in that file later.")}`
367
- )
368
- ).succeed();
229
+ if (!(await prompts({
230
+ type: "confirm",
231
+ name: "genSliders",
232
+ message: `Would you like to configure a few sliders to get you started? ${reset(dim("(recommended)"))}`,
233
+ initial: true
234
+ }, { onCancel: () => {
235
+ ora().info(dim("Operation cancelled."));
236
+ process.exit(0);
237
+ } })).genSliders) {
238
+ ora().info(dim(`No problem! You can edit the "${cyan("config/inputs.csv")}" file later.`));
239
+ return;
240
+ }
241
+ const inputVarNames = [];
242
+ for (const mdlVar of mdlVars) if (mdlVar.kind === "const") inputVarNames.push(mdlVar.name);
243
+ inputVarNames.sort((a, b) => {
244
+ return a.toLowerCase().localeCompare(b.toLowerCase());
245
+ });
246
+ const choices = inputVarNames.map((f) => {
247
+ return {
248
+ title: f,
249
+ value: f
250
+ };
251
+ });
252
+ const varsResponse = await prompts({
253
+ type: "autocompleteMultiselect",
254
+ name: "vars",
255
+ message: "Choose up to three input variables to control with sliders",
256
+ choices,
257
+ max: 3
258
+ }, { onCancel: () => {
259
+ ora().info(dim("Operation cancelled."));
260
+ process.exit(0);
261
+ } });
262
+ const inputsCsvFile = join(projDir, "config", "inputs.csv");
263
+ let newInputsCsvContent = `${(await readFile(inputsCsvFile, "utf8")).split("\n")[0]}\n`;
264
+ if (varsResponse.vars.length > 0) {
265
+ let idNumber = 1;
266
+ for (const inputVarName of varsResponse.vars) {
267
+ const inputVar = mdlVars.find((v) => v.name === inputVarName);
268
+ if (inputVar && inputVar.kind === "const") {
269
+ const defaultValue = inputVar.value;
270
+ const csvLine = inputsCsvLine(inputVarName, defaultValue, idNumber.toString());
271
+ newInputsCsvContent += `${csvLine}\n`;
272
+ idNumber++;
273
+ }
274
+ }
275
+ }
276
+ await writeFile(inputsCsvFile, newInputsCsvContent);
277
+ if (varsResponse.vars.length === 0) {
278
+ ora().info(dim(`No variables selected. You can edit the "${cyan("config/inputs.csv")}" file later.`));
279
+ return;
280
+ }
281
+ const slidersText = varsResponse.vars.length > 1 ? "sliders" : "sliders";
282
+ ora(green(`Added ${slidersText} to "${bold("config/inputs.csv")}". ${dim("You can configure sliders in that file later.")}`)).succeed();
369
283
  }
370
284
  function inputsCsvLine(inputVarName, defaultValue, id) {
371
- const escapedName = escapeCsvField(inputVarName);
372
- const minValue = defaultValue - 1;
373
- const maxValue = defaultValue + 1;
374
- const step = 0.1;
375
- const a = Array(28).fill("");
376
- a[0] = id;
377
- a[1] = "slider";
378
- a[2] = "view1";
379
- a[3] = escapedName;
380
- a[4] = escapedName;
381
- a[6] = "Sliders";
382
- a[7] = minValue;
383
- a[8] = maxValue;
384
- a[9] = defaultValue;
385
- a[10] = step;
386
- a[11] = "(units)";
387
- return a.join(",");
285
+ const escapedName = escapeCsvField(inputVarName);
286
+ const minValue = defaultValue - 1;
287
+ const maxValue = defaultValue + 1;
288
+ const step = .1;
289
+ const a = Array(28).fill("");
290
+ a[0] = id;
291
+ a[1] = "slider";
292
+ a[2] = "view1";
293
+ a[3] = escapedName;
294
+ a[4] = escapedName;
295
+ a[6] = "Sliders";
296
+ a[7] = minValue;
297
+ a[8] = maxValue;
298
+ a[9] = defaultValue;
299
+ a[10] = step;
300
+ a[11] = "(units)";
301
+ return a.join(",");
388
302
  }
389
303
  function escapeCsvField(s) {
390
- return s.includes(",") ? `"${s}"` : s;
304
+ return s.includes(",") ? `"${s}"` : s;
391
305
  }
392
306
  async function readModelVars(projDir, modelPath) {
393
- const buildDir = resolvePath(projDir, "sde-prep", "build");
394
- await mkdir(buildDir, { recursive: true });
395
- const spec = {};
396
- const modelFile = resolvePath(projDir, modelPath);
397
- const modelContent = await readFile(modelFile, "utf8");
398
- const modelKind = modelContent.includes("<xmile") ? "xmile" : "vensim";
399
- const modelDir = dirname(modelFile);
400
- const modelName = parsePath(modelFile).name;
401
- await parseAndGenerate(modelContent, modelKind, spec, ["printVarList"], modelDir, modelName, buildDir);
402
- const jsonListFile = joinPath(buildDir, `${modelName}.json`);
403
- const jsonListContent = await readFile(jsonListFile, "utf8");
404
- const jsonList = JSON.parse(jsonListContent);
405
- const varObjs = jsonList.variables;
406
- const mdlVars = [];
407
- for (const varObj of varObjs) {
408
- switch (varObj.varType) {
409
- case "const":
410
- mdlVars.push({
411
- kind: "const",
412
- name: varObj.modelLHS,
413
- value: Number.parseFloat(varObj.modelFormula)
414
- });
415
- break;
416
- case "aux":
417
- mdlVars.push({
418
- kind: "aux",
419
- name: varObj.modelLHS
420
- });
421
- break;
422
- case "level":
423
- mdlVars.push({
424
- kind: "level",
425
- name: varObj.modelLHS
426
- });
427
- break;
428
- default:
429
- break;
430
- }
431
- }
432
- return mdlVars;
307
+ const buildDir = resolve(projDir, "sde-prep", "build");
308
+ await mkdir(buildDir, { recursive: true });
309
+ const spec = {};
310
+ const modelFile = resolve(projDir, modelPath);
311
+ const modelContent = await readFile(modelFile, "utf8");
312
+ const modelKind = modelContent.includes("<xmile") ? "xmile" : "vensim";
313
+ const modelDir = dirname(modelFile);
314
+ const modelName = parse(modelFile).name;
315
+ await parseAndGenerate(modelContent, modelKind, spec, ["printVarList"], modelDir, modelName, buildDir);
316
+ const jsonListFile = join(buildDir, `${modelName}.json`);
317
+ const jsonListContent = await readFile(jsonListFile, "utf8");
318
+ const varObjs = JSON.parse(jsonListContent).variables;
319
+ const mdlVars = [];
320
+ for (const varObj of varObjs) switch (varObj.varType) {
321
+ case "const":
322
+ mdlVars.push({
323
+ kind: "const",
324
+ name: varObj.modelLHS,
325
+ value: Number.parseFloat(varObj.modelFormula)
326
+ });
327
+ break;
328
+ case "aux":
329
+ mdlVars.push({
330
+ kind: "aux",
331
+ name: varObj.modelLHS
332
+ });
333
+ break;
334
+ case "level": mdlVars.push({
335
+ kind: "level",
336
+ name: varObj.modelLHS
337
+ });
338
+ }
339
+ return mdlVars;
433
340
  }
434
-
435
- // src/step-deps.ts
436
- import { execa } from "execa";
437
- import { bold as bold3, cyan as cyan2, dim as dim3, green as green3, reset as reset2, yellow as yellow2 } from "kleur/colors";
438
- import ora3 from "ora";
439
- import prompts3 from "prompts";
341
+ //#endregion
342
+ //#region src/step-deps.ts
440
343
  async function chooseInstallDeps(projDir, args, pkgManager) {
441
- const installResponse = await prompts3(
442
- {
443
- type: "confirm",
444
- name: "install",
445
- message: `Would you like to install ${pkgManager} dependencies? ${reset2(dim3("(recommended)"))}`,
446
- initial: true
447
- },
448
- {
449
- onCancel: () => {
450
- ora3().info(
451
- dim3("Operation cancelled. Your project folder has been created, but no dependencies have been installed.")
452
- );
453
- process.exit(0);
454
- }
455
- }
456
- );
457
- if (args.dryRun) {
458
- ora3().info(dim3(`--dry-run enabled, skipping.`));
459
- return;
460
- } else if (!installResponse.install) {
461
- ora3().info(dim3(`No problem! Remember to install dependencies after setup.`));
462
- return;
463
- }
464
- const installExec = execa(pkgManager, ["install"], { cwd: projDir });
465
- const installingPackagesMsg = "Installing packages...";
466
- const installSpinner = ora3(installingPackagesMsg).start();
467
- try {
468
- await new Promise((resolve, reject) => {
469
- installExec.stdout?.on("data", function(data) {
470
- installSpinner.text = `${installingPackagesMsg}
471
- ${bold3(`[${pkgManager}]`)} ${data}`;
472
- });
473
- installExec.stderr?.on("data", function(data) {
474
- installSpinner.text = `${installingPackagesMsg}
475
- ${bold3(`[${pkgManager}]`)} ${data}`;
476
- });
477
- installExec.on("error", (error) => reject(error));
478
- installExec.on("close", (code) => {
479
- if (code !== 0) {
480
- reject(`Install failed (code=${code})`);
481
- } else {
482
- resolve();
483
- }
484
- });
485
- });
486
- installSpinner.text = green3("Packages installed!");
487
- installSpinner.succeed();
488
- } catch {
489
- installSpinner.text = yellow2(
490
- `There was an error installing packages. Try running ${cyan2(
491
- `${pkgManager} install`
492
- )} in your project directory later.`
493
- );
494
- installSpinner.warn();
495
- }
344
+ const installResponse = await prompts({
345
+ type: "confirm",
346
+ name: "install",
347
+ message: `Would you like to install ${pkgManager} dependencies? ${reset(dim("(recommended)"))}`,
348
+ initial: true
349
+ }, { onCancel: () => {
350
+ ora().info(dim("Operation cancelled. Your project folder has been created, but no dependencies have been installed."));
351
+ process.exit(0);
352
+ } });
353
+ if (args.dryRun) {
354
+ ora().info(dim(`--dry-run enabled, skipping.`));
355
+ return;
356
+ } else if (!installResponse.install) {
357
+ ora().info(dim(`No problem! Remember to install dependencies after setup.`));
358
+ return;
359
+ }
360
+ const installExec = execa(pkgManager, ["install"], { cwd: projDir });
361
+ const installingPackagesMsg = "Installing packages...";
362
+ const installSpinner = ora(installingPackagesMsg).start();
363
+ try {
364
+ await new Promise((resolve, reject) => {
365
+ installExec.stdout?.on("data", function(data) {
366
+ installSpinner.text = `${installingPackagesMsg}\n${bold(`[${pkgManager}]`)} ${data}`;
367
+ });
368
+ installExec.stderr?.on("data", function(data) {
369
+ installSpinner.text = `${installingPackagesMsg}\n${bold(`[${pkgManager}]`)} ${data}`;
370
+ });
371
+ installExec.on("error", (error) => reject(error));
372
+ installExec.on("close", (code) => {
373
+ if (code !== 0) reject(`Install failed (code=${code})`);
374
+ else resolve();
375
+ });
376
+ });
377
+ installSpinner.text = green("Packages installed!");
378
+ installSpinner.succeed();
379
+ } catch {
380
+ installSpinner.text = yellow(`There was an error installing packages. Try running ${cyan(`${pkgManager} install`)} in your project directory later.`);
381
+ installSpinner.warn();
382
+ }
496
383
  }
497
-
498
- // src/step-directory.ts
499
- import { existsSync as existsSync2 } from "fs";
500
- import { resolve as resolvePath2 } from "path";
501
- import { bold as bold4, dim as dim4, green as green4, red } from "kleur/colors";
502
- import ora4 from "ora";
503
- import prompts4 from "prompts";
384
+ //#endregion
385
+ //#region src/step-directory.ts
504
386
  async function chooseProjectDir(args) {
505
- function isValidDir(dir) {
506
- const packageJson = resolvePath2(dir, "package.json");
507
- const packagesDir = resolvePath2(dir, "packages");
508
- return !existsSync2(dir) || !existsSync2(packageJson) && !existsSync2(packagesDir);
509
- }
510
- const showValidDirMsg = (dir) => {
511
- ora4(green4(`Using "${bold4(dir)}" as the project directory.`)).succeed();
512
- };
513
- const showInvalidDirMsg = (dir) => {
514
- ora4(red(`"${bold4(dir)}" contains existing 'package.json' and/or 'packages' directory, stopping.`)).fail();
515
- };
516
- let projDir = args["_"][2];
517
- if (projDir) {
518
- if (isValidDir(projDir)) {
519
- showValidDirMsg(projDir);
520
- } else {
521
- showInvalidDirMsg(projDir);
522
- process.exit(0);
523
- }
524
- } else {
525
- const dirResponse = await prompts4(
526
- {
527
- type: "text",
528
- name: "directory",
529
- message: "Where would you like to create your new project?",
530
- initial: "<current directory>"
531
- // validate(value) {
532
- // if (value === '<current directory>') {
533
- // value = process.cwd()
534
- // }
535
- // if (!isValidDir(value)) {
536
- // return notValidMsg(value)
537
- // }
538
- // return true
539
- // }
540
- },
541
- {
542
- onCancel: () => {
543
- ora4().info(dim4("Operation cancelled."));
544
- process.exit(0);
545
- }
546
- }
547
- );
548
- projDir = dirResponse.directory;
549
- if (projDir === "<current directory>") {
550
- projDir = process.cwd();
551
- }
552
- if (isValidDir(projDir)) {
553
- showValidDirMsg(projDir);
554
- } else {
555
- showInvalidDirMsg(projDir);
556
- process.exit(0);
557
- }
558
- }
559
- return projDir;
387
+ /**
388
+ * Return true if the given directory does not exist, or it does not contain important files
389
+ * like `package.json`.
390
+ */
391
+ function isValidDir(dir) {
392
+ const packageJson = resolve(dir, "package.json");
393
+ const packagesDir = resolve(dir, "packages");
394
+ return !existsSync(dir) || !existsSync(packageJson) && !existsSync(packagesDir);
395
+ }
396
+ const showValidDirMsg = (dir) => {
397
+ ora(green(`Using "${bold(dir)}" as the project directory.`)).succeed();
398
+ };
399
+ const showInvalidDirMsg = (dir) => {
400
+ ora(red(`"${bold(dir)}" contains existing 'package.json' and/or 'packages' directory, stopping.`)).fail();
401
+ };
402
+ let projDir = args["_"][2];
403
+ if (projDir) {
404
+ if (isValidDir(projDir)) showValidDirMsg(projDir);
405
+ else {
406
+ showInvalidDirMsg(projDir);
407
+ process.exit(0);
408
+ }
409
+ } else {
410
+ projDir = (await prompts({
411
+ type: "text",
412
+ name: "directory",
413
+ message: "Where would you like to create your new project?",
414
+ initial: "<current directory>"
415
+ }, { onCancel: () => {
416
+ ora().info(dim("Operation cancelled."));
417
+ process.exit(0);
418
+ } })).directory;
419
+ if (projDir === "<current directory>") projDir = process.cwd();
420
+ if (isValidDir(projDir)) showValidDirMsg(projDir);
421
+ else {
422
+ showInvalidDirMsg(projDir);
423
+ process.exit(0);
424
+ }
425
+ }
426
+ return projDir;
560
427
  }
561
-
562
- // src/step-emsdk.ts
563
- import { existsSync as existsSync3, rmSync } from "fs";
564
- import { join as joinPath2, resolve as resolvePath3 } from "path";
565
- import { execa as execa2 } from "execa";
566
- import { findUp } from "find-up";
567
- import { bold as bold5, cyan as cyan3, dim as dim5, green as green5, red as red2 } from "kleur/colors";
568
- import ora5 from "ora";
569
- import prompts5 from "prompts";
570
- var version = "2.0.34";
428
+ //#endregion
429
+ //#region src/step-emsdk.ts
430
+ const version = "2.0.34";
571
431
  async function chooseInstallEmsdk(projDir, args) {
572
- const existingEmsdkDir = await findUp("emsdk", {
573
- cwd: projDir,
574
- type: "directory"
575
- });
576
- if (existingEmsdkDir) {
577
- ora5().succeed("Found existing Emscripten SDK installation.");
578
- ora5().info(dim5(`Your project will use "${cyan3(existingEmsdkDir)}".`));
579
- return;
580
- }
581
- const underParentDir = resolvePath3(projDir, "..", "emsdk");
582
- const underProjDir = joinPath2(projDir, "emsdk");
583
- const installResponse = await prompts5(
584
- {
585
- type: "select",
586
- name: "install",
587
- message: `Would you like to install the Emscripten SDK that is used to generate WebAssembly?`,
588
- choices: [
589
- // ${reset(dim('(recommended)'))
590
- {
591
- title: `Install under parent directory (${bold5(underParentDir)})`,
592
- description: "This is recommended so that it can be shared by multiple projects",
593
- value: "parent"
594
- },
595
- {
596
- title: `Install under project directory (${bold5(underProjDir)})"`,
597
- description: "This is useful for keeping everything under a single project directory",
598
- value: "project"
599
- },
600
- {
601
- title: `Don't install`,
602
- description: `It's OK, you can install it later`,
603
- value: "skip"
604
- }
605
- ]
606
- },
607
- {
608
- onCancel: () => {
609
- ora5().info(
610
- dim5(
611
- "Operation cancelled. Your project folder has been created, but the Emscripten SDK and other dependencies have not been installed."
612
- )
613
- );
614
- process.exit(0);
615
- }
616
- }
617
- );
618
- if (args.dryRun) {
619
- ora5().info(dim5(`--dry-run enabled, skipping.`));
620
- return;
621
- } else if (installResponse.install === "skip") {
622
- ora5().info(
623
- dim5(
624
- `No problem! Be sure to install the Emscripten SDK and configure it in "${cyan3("sde.config.js")}" after setup.`
625
- )
626
- );
627
- return;
628
- }
629
- const installDir = installResponse.install === "parent" ? underParentDir : underProjDir;
630
- try {
631
- await installEmscripten(installDir);
632
- } catch (e) {
633
- ora5(red2(`Failed to install Emscripten SDK: ${e.message}`)).fail();
634
- process.exit(0);
635
- }
636
- ora5(green5(`Installed the Emscripten SDK in "${bold5(installDir)}"`)).succeed();
432
+ const existingEmsdkDir = await findUp("emsdk", {
433
+ cwd: projDir,
434
+ type: "directory"
435
+ });
436
+ if (existingEmsdkDir) {
437
+ ora().succeed("Found existing Emscripten SDK installation.");
438
+ ora().info(dim(`Your project will use "${cyan(existingEmsdkDir)}".`));
439
+ return;
440
+ }
441
+ const underParentDir = resolve(projDir, "..", "emsdk");
442
+ const underProjDir = join(projDir, "emsdk");
443
+ const installResponse = await prompts({
444
+ type: "select",
445
+ name: "install",
446
+ message: `Would you like to install the Emscripten SDK that is used to generate WebAssembly?`,
447
+ choices: [
448
+ {
449
+ title: `Install under parent directory (${bold(underParentDir)})`,
450
+ description: "This is recommended so that it can be shared by multiple projects",
451
+ value: "parent"
452
+ },
453
+ {
454
+ title: `Install under project directory (${bold(underProjDir)})"`,
455
+ description: "This is useful for keeping everything under a single project directory",
456
+ value: "project"
457
+ },
458
+ {
459
+ title: `Don't install`,
460
+ description: `It's OK, you can install it later`,
461
+ value: "skip"
462
+ }
463
+ ]
464
+ }, { onCancel: () => {
465
+ ora().info(dim("Operation cancelled. Your project folder has been created, but the Emscripten SDK and other dependencies have not been installed."));
466
+ process.exit(0);
467
+ } });
468
+ if (args.dryRun) {
469
+ ora().info(dim(`--dry-run enabled, skipping.`));
470
+ return;
471
+ } else if (installResponse.install === "skip") {
472
+ ora().info(dim(`No problem! Be sure to install the Emscripten SDK and configure it in "${cyan("sde.config.js")}" after setup.`));
473
+ return;
474
+ }
475
+ const installDir = installResponse.install === "parent" ? underParentDir : underProjDir;
476
+ try {
477
+ await installEmscripten(installDir);
478
+ } catch (e) {
479
+ ora(red(`Failed to install Emscripten SDK: ${e.message}`)).fail();
480
+ process.exit(0);
481
+ }
482
+ ora(green(`Installed the Emscripten SDK in "${bold(installDir)}"`)).succeed();
637
483
  }
484
+ /**
485
+ * Install the Emscripten SDK to the `emsdk` directory under the
486
+ * given directory (if `emsdk` is not already present), then
487
+ * activates the requested version (specified with `version`).
488
+ *
489
+ * The implementation is similar to the existing `setup-emsdk` action
490
+ * (https://github.com/mymindstorm/setup-emsdk) except that one has issues
491
+ * with the cache directory on Windows, so having our own script gives us
492
+ * more control over installation and caching behavior.
493
+ */
638
494
  async function installEmscripten(emsdkDir) {
639
- if (!existsSync3(emsdkDir)) {
640
- console.log(`Downloading Emscripten SDK to ${emsdkDir}`);
641
- await execa2("git", ["clone", "https://github.com/emscripten-core/emsdk.git", emsdkDir]);
642
- } else {
643
- console.log(`Found existing Emscripten SDK directory: ${emsdkDir}`);
644
- }
645
- if (process.env.CI) {
646
- console.log("CI detected, removing .git directory...");
647
- const gitDir = joinPath2(emsdkDir, ".git");
648
- rmSync(gitDir, { recursive: true, force: true });
649
- } else {
650
- console.log("Local development detected, performing git pull...");
651
- await execa2("git", ["pull"], { cwd: emsdkDir });
652
- }
653
- const emsdkCmd = async (...args) => {
654
- return execa2("python3", ["emsdk.py", ...args], { cwd: emsdkDir });
655
- };
656
- console.log(`Activating Emscripten SDK ${version}...`);
657
- await emsdkCmd("install", version);
658
- await emsdkCmd("activate", version);
495
+ if (!existsSync(emsdkDir)) {
496
+ console.log(`Downloading Emscripten SDK to ${emsdkDir}`);
497
+ await execa("git", [
498
+ "clone",
499
+ "https://github.com/emscripten-core/emsdk.git",
500
+ emsdkDir
501
+ ]);
502
+ } else console.log(`Found existing Emscripten SDK directory: ${emsdkDir}`);
503
+ if (process.env.CI) {
504
+ console.log("CI detected, removing .git directory...");
505
+ const gitDir = join(emsdkDir, ".git");
506
+ rmSync(gitDir, {
507
+ recursive: true,
508
+ force: true
509
+ });
510
+ } else {
511
+ console.log("Local development detected, performing git pull...");
512
+ await execa("git", ["pull"], { cwd: emsdkDir });
513
+ }
514
+ const emsdkCmd = async (...args) => {
515
+ return execa("python3", ["emsdk.py", ...args], { cwd: emsdkDir });
516
+ };
517
+ console.log(`Activating Emscripten SDK ${version}...`);
518
+ await emsdkCmd("install", version);
519
+ await emsdkCmd("activate", version);
659
520
  }
660
-
661
- // src/step-git.ts
662
- import { execaCommand } from "execa";
663
- import { cyan as cyan4, dim as dim6, green as green6, reset as reset3, yellow as yellow3 } from "kleur/colors";
664
- import ora6 from "ora";
665
- import prompts6 from "prompts";
521
+ //#endregion
522
+ //#region src/step-git.ts
666
523
  async function chooseGitInit(projDir, args) {
667
- const gitResponse = await prompts6(
668
- {
669
- type: "confirm",
670
- name: "git",
671
- message: `Would you like to initialize a new git repository? ${reset3(dim6("(optional)"))}`,
672
- initial: true
673
- },
674
- {
675
- onCancel: () => {
676
- ora6().info(dim6("Operation cancelled. Your project folder has already been created."));
677
- process.exit(0);
678
- }
679
- }
680
- );
681
- if (args.dryRun) {
682
- ora6().info(dim6(`--dry-run enabled, skipping.`));
683
- return;
684
- } else if (!gitResponse.git) {
685
- ora6().info(dim6(`No problem! You can come back and run ${cyan4(`git init`)} later.`));
686
- return;
687
- }
688
- try {
689
- await execaCommand("git init", { cwd: projDir });
690
- ora6().succeed(green6("Git repository initialized!"));
691
- } catch {
692
- ora6().warn(
693
- yellow3(
694
- `There was a problem initializing the Git repository, but no problem, you can run ${cyan4(`git init`)} later.`
695
- )
696
- );
697
- }
524
+ const gitResponse = await prompts({
525
+ type: "confirm",
526
+ name: "git",
527
+ message: `Would you like to initialize a new git repository? ${reset(dim("(optional)"))}`,
528
+ initial: true
529
+ }, { onCancel: () => {
530
+ ora().info(dim("Operation cancelled. Your project folder has already been created."));
531
+ process.exit(0);
532
+ } });
533
+ if (args.dryRun) {
534
+ ora().info(dim(`--dry-run enabled, skipping.`));
535
+ return;
536
+ } else if (!gitResponse.git) {
537
+ ora().info(dim(`No problem! You can come back and run ${cyan(`git init`)} later.`));
538
+ return;
539
+ }
540
+ try {
541
+ await execaCommand("git init", { cwd: projDir });
542
+ ora().succeed(green("Git repository initialized!"));
543
+ } catch {
544
+ ora().warn(yellow(`There was a problem initializing the Git repository, but no problem, you can run ${cyan(`git init`)} later.`));
545
+ }
698
546
  }
699
-
700
- // src/step-model-file.ts
701
- import { readdir } from "fs/promises";
702
- import { relative as relative2, resolve as resolvePath4, sep, posix, extname } from "path";
703
- import { bold as bold6, dim as dim7, green as green7, yellow as yellow4 } from "kleur/colors";
704
- import ora7 from "ora";
705
- import prompts7 from "prompts";
706
- var supportedModelFileExtensions = /* @__PURE__ */ new Set([".mdl", ".xmile", ".stmx", ".itmx"]);
547
+ //#endregion
548
+ //#region src/step-model-file.ts
549
+ const supportedModelFileExtensions = /* @__PURE__ */ new Set([
550
+ ".mdl",
551
+ ".xmile",
552
+ ".stmx",
553
+ ".itmx"
554
+ ]);
707
555
  async function chooseModelFile(projDir) {
708
- async function getFiles(dir) {
709
- const dirents = await readdir(dir, { withFileTypes: true });
710
- const files = await Promise.all(
711
- dirents.map((dirent) => {
712
- const res = resolvePath4(dir, dirent.name);
713
- return dirent.isDirectory() ? getFiles(res) : res;
714
- })
715
- );
716
- return files.flat();
717
- }
718
- const allFiles = await getFiles(projDir);
719
- const modelFiles = allFiles.filter((f) => {
720
- const ext = extname(f).toLowerCase();
721
- return supportedModelFileExtensions.has(ext);
722
- }).map((f) => relative2(projDir, f).replaceAll(sep, posix.sep));
723
- const modelChoices = modelFiles.map((f) => {
724
- return {
725
- title: f,
726
- value: f
727
- };
728
- });
729
- let modelFile;
730
- if (modelFiles.length === 0) {
731
- ora7(
732
- yellow4(`No model files were found in "${projDir}". The model file from the template will be used instead.`)
733
- ).warn();
734
- modelFile = void 0;
735
- } else if (modelFiles.length === 1) {
736
- modelFile = modelFiles[0];
737
- ora7().succeed(`Found "${modelFile}", will configure the project to use that model file.`);
738
- } else {
739
- const options = await prompts7(
740
- [
741
- {
742
- type: "select",
743
- name: "modelFile",
744
- message: "It looks like there are multiple model files. Which one would you like to use?",
745
- choices: modelChoices
746
- }
747
- ],
748
- {
749
- onCancel: () => {
750
- ora7().info(dim7("Operation cancelled."));
751
- process.exit(0);
752
- }
753
- }
754
- );
755
- modelFile = options.modelFile;
756
- ora7(green7(`Using "${bold6(modelFile)}" as the model for the project.`)).succeed();
757
- }
758
- return modelFile;
556
+ async function getFiles(dir) {
557
+ const dirents = await readdir(dir, { withFileTypes: true });
558
+ return (await Promise.all(dirents.map((dirent) => {
559
+ const res = resolve(dir, dirent.name);
560
+ return dirent.isDirectory() ? getFiles(res) : res;
561
+ }))).flat();
562
+ }
563
+ const modelFiles = (await getFiles(projDir)).filter((f) => {
564
+ const ext = extname(f).toLowerCase();
565
+ return supportedModelFileExtensions.has(ext);
566
+ }).map((f) => relative(projDir, f).replaceAll(sep, posix.sep));
567
+ const modelChoices = modelFiles.map((f) => {
568
+ return {
569
+ title: f,
570
+ value: f
571
+ };
572
+ });
573
+ let modelFile;
574
+ if (modelFiles.length === 0) {
575
+ ora(yellow(`No model files were found in "${projDir}". The model file from the template will be used instead.`)).warn();
576
+ modelFile = void 0;
577
+ } else if (modelFiles.length === 1) {
578
+ modelFile = modelFiles[0];
579
+ ora().succeed(`Found "${modelFile}", will configure the project to use that model file.`);
580
+ } else {
581
+ modelFile = (await prompts([{
582
+ type: "select",
583
+ name: "modelFile",
584
+ message: "It looks like there are multiple model files. Which one would you like to use?",
585
+ choices: modelChoices
586
+ }], { onCancel: () => {
587
+ ora().info(dim("Operation cancelled."));
588
+ process.exit(0);
589
+ } })).modelFile;
590
+ ora(green(`Using "${bold(modelFile)}" as the model for the project.`)).succeed();
591
+ }
592
+ return modelFile;
759
593
  }
760
-
761
- // src/step-template.ts
762
- import { existsSync as existsSync4, mkdtempSync, readdirSync, renameSync, rmSync as rmSync2 } from "fs";
763
- import { writeFile as writeFile2 } from "fs/promises";
764
- import { copy } from "fs-extra";
765
- import { tmpdir } from "os";
766
- import { join as joinPath3 } from "path";
767
- import { downloadTemplate } from "giget";
768
- import { bold as bold7, dim as dim8, green as green8, red as red3, yellow as yellow5 } from "kleur/colors";
769
- import ora8 from "ora";
770
- import prompts8 from "prompts";
771
- var TEMPLATES = [
772
- {
773
- title: "Svelte project (recommended)",
774
- description: "Includes recommended structure with config files, Svelte-based app, core library, model-check, etc",
775
- value: "svelte"
776
- },
777
- {
778
- title: "jQuery project",
779
- description: "Includes recommended structure with config files, jQuery-based app, core library, model-check, etc",
780
- value: "jquery"
781
- },
782
- {
783
- title: "Minimal project",
784
- description: "Includes simple config for model-check",
785
- value: "minimal"
786
- }
594
+ //#endregion
595
+ //#region src/step-template.ts
596
+ const TEMPLATES = [
597
+ {
598
+ title: "Svelte project (recommended)",
599
+ description: "Includes recommended structure with config files, Svelte-based app, core library, model-check, etc",
600
+ value: "svelte"
601
+ },
602
+ {
603
+ title: "jQuery project",
604
+ description: "Includes recommended structure with config files, jQuery-based app, core library, model-check, etc",
605
+ value: "jquery"
606
+ },
607
+ {
608
+ title: "Minimal project",
609
+ description: "Includes simple config for model-check",
610
+ value: "minimal"
611
+ }
787
612
  ];
788
613
  async function chooseTemplate(args) {
789
- let templateName;
790
- if (args.template) {
791
- templateName = args.template;
792
- } else {
793
- const options = await prompts8(
794
- [
795
- {
796
- type: "select",
797
- name: "template",
798
- message: "Which template would you like to use?",
799
- choices: TEMPLATES
800
- }
801
- ],
802
- {
803
- onCancel: () => {
804
- ora8().info(dim8("Operation cancelled."));
805
- process.exit(0);
806
- }
807
- }
808
- );
809
- templateName = options.template;
810
- }
811
- const defaultRev = "main";
812
- const commit = args.commit || defaultRev;
813
- let baseTemplateUri;
814
- if (templateName.includes(":")) {
815
- baseTemplateUri = templateName;
816
- } else if (templateName.includes("/")) {
817
- baseTemplateUri = `github:${templateName}`;
818
- } else {
819
- baseTemplateUri = `github:climateinteractive/SDEverywhere/examples/template-${templateName}`;
820
- }
821
- const templateUri = `${baseTemplateUri}#${commit}`;
822
- ora8(green8(`Using "${bold7(templateUri)}" as the template for the project.`)).succeed();
823
- return {
824
- uri: templateUri,
825
- name: templateName
826
- };
614
+ let templateName;
615
+ if (args.template) templateName = args.template;
616
+ else templateName = (await prompts([{
617
+ type: "select",
618
+ name: "template",
619
+ message: "Which template would you like to use?",
620
+ choices: TEMPLATES
621
+ }], { onCancel: () => {
622
+ ora().info(dim("Operation cancelled."));
623
+ process.exit(0);
624
+ } })).template;
625
+ const commit = args.commit || "main";
626
+ let baseTemplateUri;
627
+ if (templateName.includes(":")) baseTemplateUri = templateName;
628
+ else if (templateName.includes("/")) baseTemplateUri = `github:${templateName}`;
629
+ else baseTemplateUri = `github:climateinteractive/SDEverywhere/examples/template-${templateName}`;
630
+ const templateUri = `${baseTemplateUri}#${commit}`;
631
+ ora(green(`Using "${bold(templateUri)}" as the template for the project.`)).succeed();
632
+ return {
633
+ uri: templateUri,
634
+ name: templateName
635
+ };
827
636
  }
828
637
  async function copyTemplate(template, projDir, pkgManager, configDirExisted, modelExisted) {
829
- const templateSpinner = ora8("Copying template files...").start();
830
- try {
831
- const tmpDir = mkdtempSync(joinPath3(tmpdir(), "sde-create-"));
832
- await downloadTemplate(template.uri, {
833
- force: true,
834
- dir: tmpDir
835
- });
836
- if (!existsSync4(tmpDir) || readdirSync(tmpDir).length === 0) {
837
- throw new Error("Failed to download the requested template: the temporary directory is empty.");
838
- }
839
- if (configDirExisted) {
840
- rmSync2(joinPath3(tmpDir, "config"), { recursive: true, force: true });
841
- }
842
- if (modelExisted) {
843
- rmSync2(joinPath3(tmpDir, "model"), { recursive: true, force: true });
844
- }
845
- await copy(tmpDir, projDir, {
846
- overwrite: false,
847
- errorOnExist: false
848
- });
849
- if (!modelExisted) {
850
- renameSync(joinPath3(projDir, "model", "MODEL_NAME.mdl"), joinPath3(projDir, "model", "sample.mdl"));
851
- }
852
- rmSync2(tmpDir, { recursive: true, force: true });
853
- } catch (e) {
854
- templateSpinner.fail();
855
- console.error(red3(e.message));
856
- console.error(yellow5("\nThere was a problem copying the template."));
857
- console.error(
858
- yellow5(
859
- "Please start a new discussion thread and include the command output so that we can help:\n https://github.com/climateinteractive/SDEverywhere/discussions/categories/q-a\n"
860
- )
861
- );
862
- process.exit(0);
863
- }
864
- if (existsSync4(joinPath3(projDir, "packages")) && pkgManager === "pnpm") {
865
- const workspaceFile = joinPath3(projDir, "pnpm-workspace.yaml");
866
- const workspaceContent = `packages:
867
- - packages/*
868
- `;
869
- await writeFile2(workspaceFile, workspaceContent);
870
- }
871
- templateSpinner.text = green8("Template copied!");
872
- templateSpinner.succeed();
638
+ const templateSpinner = ora("Copying template files...").start();
639
+ try {
640
+ const tmpDir = mkdtempSync(join(tmpdir(), "sde-create-"));
641
+ await downloadTemplate(template.uri, {
642
+ force: true,
643
+ dir: tmpDir
644
+ });
645
+ if (!existsSync(tmpDir) || readdirSync(tmpDir).length === 0) throw new Error("Failed to download the requested template: the temporary directory is empty.");
646
+ if (configDirExisted) rmSync(join(tmpDir, "config"), {
647
+ recursive: true,
648
+ force: true
649
+ });
650
+ if (modelExisted) rmSync(join(tmpDir, "model"), {
651
+ recursive: true,
652
+ force: true
653
+ });
654
+ await copy(tmpDir, projDir, {
655
+ overwrite: false,
656
+ errorOnExist: false
657
+ });
658
+ if (!modelExisted) renameSync(join(projDir, "model", "MODEL_NAME.mdl"), join(projDir, "model", "sample.mdl"));
659
+ rmSync(tmpDir, {
660
+ recursive: true,
661
+ force: true
662
+ });
663
+ } catch (e) {
664
+ templateSpinner.fail();
665
+ console.error(red(e.message));
666
+ console.error(yellow("\nThere was a problem copying the template."));
667
+ console.error(yellow("Please start a new discussion thread and include the command output so that we can help:\n https://github.com/climateinteractive/SDEverywhere/discussions/categories/q-a\n"));
668
+ process.exit(0);
669
+ }
670
+ if (existsSync(join(projDir, "packages")) && pkgManager === "pnpm") {
671
+ const workspaceFile = join(projDir, "pnpm-workspace.yaml");
672
+ await writeFile(workspaceFile, `packages:\n - packages/*\n`);
673
+ }
674
+ templateSpinner.text = green("Template copied!");
675
+ templateSpinner.succeed();
873
676
  }
874
-
875
- // src/index.ts
677
+ //#endregion
678
+ //#region src/index.ts
876
679
  async function main() {
877
- const pkgManager = detectPackageManager()?.name || "npm";
878
- const args = yargs(process.argv);
879
- prompts9.override(args);
880
- if (args.dryRun) {
881
- console.log();
882
- ora9().info(dim9(`--dry-run enabled, no files will be written.`));
883
- }
884
- console.log(`
885
- ${bold8("Welcome to SDEverywhere!")}`);
886
- console.log(`Let's create a new SDEverywhere project for your model.
887
- `);
888
- const projDir = await chooseProjectDir(args);
889
- console.log();
890
- const configDirExisted = existsSync5(resolvePath5(projDir, "config"));
891
- const template = await chooseTemplate(args);
892
- console.log();
893
- let modelPath = await chooseModelFile(projDir);
894
- const modelExisted = modelPath !== void 0;
895
- console.log();
896
- if (!args.dryRun) {
897
- await copyTemplate(template, projDir, pkgManager, configDirExisted, modelExisted);
898
- console.log();
899
- }
900
- if (modelPath === void 0) {
901
- modelPath = `model${posix2.sep}sample.mdl`;
902
- }
903
- const genFormat = await chooseCodeFormat();
904
- if (!args.dryRun) {
905
- await updateSdeConfig(projDir, modelPath, genFormat);
906
- const modelCheckFilesExist = existsSync5(resolvePath5(projDir, "model", "checks")) || existsSync5(resolvePath5(projDir, "model", "comparisons"));
907
- if (!modelCheckFilesExist) {
908
- await generateSampleYamlFiles(projDir);
909
- }
910
- }
911
- console.log();
912
- if (configDirExisted) {
913
- ora9().succeed(`Found existing "${bold8("config")}" directory.`);
914
- ora9().info(
915
- dim9(`You can edit the files in the "${cyan5("config")}" directory later to configure graphs and sliders.`)
916
- );
917
- console.log();
918
- } else {
919
- const configDirExistsNow = existsSync5(resolvePath5(projDir, "config"));
920
- if (configDirExistsNow && modelExisted && !args.dryRun) {
921
- await chooseGenConfig(projDir, modelPath);
922
- console.log();
923
- }
924
- }
925
- if (genFormat === "c") {
926
- await chooseInstallEmsdk(projDir, args);
927
- console.log();
928
- }
929
- await chooseInstallDeps(projDir, args, pkgManager);
930
- console.log();
931
- await chooseGitInit(projDir, args);
932
- console.log();
933
- ora9(green9("Setup complete!")).succeed();
934
- console.log(`
935
- ${bgCyan(black(" Next steps "))}
936
- `);
937
- const relProjDir = relative3(process.cwd(), projDir);
938
- const devCmd = pkgManager === "npm" ? "npm run dev" : `${pkgManager} dev`;
939
- if (relProjDir !== "") {
940
- console.log(`You can now ${bold8(cyan5("cd"))} into the ${bold8(cyan5(relProjDir))} project directory.`);
941
- }
942
- console.log(`Run ${bold8(cyan5(devCmd))} to start the local dev server. ${bold8(cyan5("CTRL-C"))} to close.`);
943
- console.log("");
680
+ const pkgManager = detectPackageManager()?.name || "npm";
681
+ const args = yargs(process.argv);
682
+ prompts.override(args);
683
+ if (args.dryRun) {
684
+ console.log();
685
+ ora().info(dim(`--dry-run enabled, no files will be written.`));
686
+ }
687
+ console.log(`\n${bold("Welcome to SDEverywhere!")}`);
688
+ console.log(`Let's create a new SDEverywhere project for your model.\n`);
689
+ const projDir = await chooseProjectDir(args);
690
+ console.log();
691
+ const configDirExisted = existsSync(resolve(projDir, "config"));
692
+ const template = await chooseTemplate(args);
693
+ console.log();
694
+ let modelPath = await chooseModelFile(projDir);
695
+ const modelExisted = modelPath !== void 0;
696
+ console.log();
697
+ if (!args.dryRun) {
698
+ await copyTemplate(template, projDir, pkgManager, configDirExisted, modelExisted);
699
+ console.log();
700
+ }
701
+ if (modelPath === void 0) modelPath = `model${posix.sep}sample.mdl`;
702
+ const genFormat = await chooseCodeFormat();
703
+ if (!args.dryRun) {
704
+ await updateSdeConfig(projDir, modelPath, genFormat);
705
+ if (!(existsSync(resolve(projDir, "model", "checks")) || existsSync(resolve(projDir, "model", "comparisons")))) await generateSampleYamlFiles(projDir);
706
+ }
707
+ console.log();
708
+ if (configDirExisted) {
709
+ ora().succeed(`Found existing "${bold("config")}" directory.`);
710
+ ora().info(dim(`You can edit the files in the "${cyan("config")}" directory later to configure graphs and sliders.`));
711
+ console.log();
712
+ } else if (existsSync(resolve(projDir, "config")) && modelExisted && !args.dryRun) {
713
+ await chooseGenConfig(projDir, modelPath);
714
+ console.log();
715
+ }
716
+ if (genFormat === "c") {
717
+ await chooseInstallEmsdk(projDir, args);
718
+ console.log();
719
+ }
720
+ await chooseInstallDeps(projDir, args, pkgManager);
721
+ console.log();
722
+ await chooseGitInit(projDir, args);
723
+ console.log();
724
+ ora(green("Setup complete!")).succeed();
725
+ console.log(`\n${bgCyan(black(" Next steps "))}\n`);
726
+ const relProjDir = relative(process.cwd(), projDir);
727
+ const devCmd = pkgManager === "npm" ? "npm run dev" : `${pkgManager} dev`;
728
+ if (relProjDir !== "") console.log(`You can now ${bold(cyan("cd"))} into the ${bold(cyan(relProjDir))} project directory.`);
729
+ console.log(`Run ${bold(cyan(devCmd))} to start the local dev server. ${bold(cyan("CTRL-C"))} to close.`);
730
+ console.log("");
944
731
  }
945
- export {
946
- main
947
- };
732
+ //#endregion
733
+ export { main };
734
+
948
735
  //# sourceMappingURL=index.js.map