@sdeverywhere/create 0.2.42 → 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.d.ts +4 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +648 -861
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/index.cjs +0 -982
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -3
package/dist/index.js
CHANGED
|
@@ -1,71 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
135
|
-
|
|
114
|
+
await generateYaml(projDir, "checks", sampleChecksContent);
|
|
115
|
+
await generateYaml(projDir, "comparisons", sampleComparisonsContent);
|
|
136
116
|
}
|
|
137
117
|
async function chooseGenConfig(projDir, modelPath) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
304
|
+
return s.includes(",") ? `"${s}"` : s;
|
|
391
305
|
}
|
|
392
306
|
async function readModelVars(projDir, modelPath) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
563
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
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
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
-
|
|
677
|
+
//#endregion
|
|
678
|
+
//#region src/index.ts
|
|
876
679
|
async function main() {
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
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
|
-
|
|
946
|
-
|
|
947
|
-
|
|
732
|
+
//#endregion
|
|
733
|
+
export { main };
|
|
734
|
+
|
|
948
735
|
//# sourceMappingURL=index.js.map
|