@m2c2kit/cli 0.3.6 → 0.3.8

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/cli.js DELETED
@@ -1,430 +0,0 @@
1
- #!/usr/bin/env node
2
- import yargs from "yargs";
3
- import { hideBin } from "yargs/helpers";
4
- import fs from "fs";
5
- import path from "path";
6
- import handlebars from "handlebars";
7
- import { dirname } from "path";
8
- import { fileURLToPath } from "url";
9
- import { spawn } from "child_process";
10
- import ora from "ora";
11
- import chalk from "chalk";
12
- import { resolve } from "path";
13
- import { readdir } from "fs/promises";
14
- import { isReservedWord } from "./isReservedWord.js";
15
- // this is the path of our @m2c2kit/cli program
16
- // our templates are stored under here
17
- const packageHomeFolderPath = dirname(fileURLToPath(import.meta.url));
18
- // we store the CLI version in .env. The .env file is created during the
19
- // build process with the write-dotenv.js script
20
- // I couldn't get dotenv working with a node cli, so just read it in with
21
- // readFileSync()
22
- let cliVersion;
23
- try {
24
- cliVersion = fs
25
- .readFileSync(path.join(packageHomeFolderPath, ".env"))
26
- .toString()
27
- .split("=")[1];
28
- }
29
- catch {
30
- cliVersion = "UNKNOWN";
31
- }
32
- async function getFilenamesRecursive(dir) {
33
- const dirents = await readdir(dir, { withFileTypes: true });
34
- const files = await Promise.all(dirents.map((dirent) => {
35
- const res = resolve(dir, dirent.name);
36
- return dirent.isDirectory() ? getFilenamesRecursive(res) : res;
37
- }));
38
- return Array.prototype.concat(...files);
39
- }
40
- async function copyFolderRecursive(args) {
41
- const sourceFiles = await getFilenamesRecursive(args.sourceFolder);
42
- const destFiles = sourceFiles.map((f) => path.join(args.destinationFolder, f.replace(args.sourceFolder, "")));
43
- sourceFiles.forEach((source, index) => {
44
- const sourceContents = fs.readFileSync(source);
45
- const destFile = destFiles[index];
46
- fs.mkdirSync(path.dirname(destFile), { recursive: true });
47
- fs.writeFileSync(destFile, sourceContents);
48
- console.log(`${chalk.green("COPY")} ${destFile} (${sourceContents.length} bytes)`);
49
- });
50
- }
51
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
- function applyValuesToTemplate(templateFilePath, contents) {
53
- const templateContents = fs.readFileSync(templateFilePath).toString();
54
- const template = handlebars.compile(templateContents);
55
- return template(contents);
56
- }
57
- // on the demo server, identify studies with a random 5 digit code
58
- const generateStudyCode = () => {
59
- let result = "";
60
- // omit I, 1, O, and 0 due to potential confusion
61
- const characters = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
62
- const charactersLength = characters.length;
63
- for (let i = 0; i < 5; i++) {
64
- result += characters.charAt(Math.floor(Math.random() * charactersLength));
65
- }
66
- return result;
67
- };
68
- const yarg = yargs(hideBin(process.argv));
69
- await yarg
70
- .scriptName("m2")
71
- .command("new <name>", "create a new m2c2kit app", (yarg) => {
72
- yarg.positional("name", {
73
- describe: "name of new app",
74
- type: "string",
75
- });
76
- },
77
- // WIP: app name should be lower case, not the class-case name
78
- async (argv) => {
79
- const appName = argv["name"];
80
- if (isReservedWord(appName)) {
81
- console.error(`${chalk.red("ERROR")}: the name ${appName} is not valid. The name must be a valid JavaScript identifer (e.g., begin with a letter, $ or _; not contain spaces; not be a reserved word, etc.).`);
82
- yarg.exit(1, new Error("invalid name"));
83
- }
84
- const className = appName.charAt(0).toUpperCase() + appName.slice(1);
85
- const newFolderPath = path.join(path.resolve(), appName);
86
- if (fs.existsSync(newFolderPath)) {
87
- console.error(`${chalk.red("ERROR")}: folder ${newFolderPath} already exists. Please remove this folder and try again.`);
88
- yarg.exit(1, new Error("folder exists"));
89
- }
90
- fs.mkdirSync(newFolderPath);
91
- fs.mkdirSync(path.join(newFolderPath, "src"));
92
- fs.mkdirSync(path.join(newFolderPath, ".vscode"));
93
- const templateFolderPath = path.join(packageHomeFolderPath, "templates");
94
- const templates = [
95
- {
96
- templateFilePath: path.join(templateFolderPath, "package.json.handlebars"),
97
- destinationFilePath: path.join(newFolderPath, "package.json"),
98
- },
99
- {
100
- templateFilePath: path.join(templateFolderPath, "rollup.config.mjs.handlebars"),
101
- destinationFilePath: path.join(newFolderPath, "rollup.config.mjs"),
102
- },
103
- {
104
- templateFilePath: path.join(templateFolderPath, "tsconfig.json.handlebars"),
105
- destinationFilePath: path.join(newFolderPath, "tsconfig.json"),
106
- },
107
- {
108
- templateFilePath: path.join(templateFolderPath, "README.md.handlebars"),
109
- destinationFilePath: path.join(newFolderPath, "README.md"),
110
- },
111
- {
112
- templateFilePath: path.join(templateFolderPath, "index.ts.handlebars"),
113
- destinationFilePath: path.join(newFolderPath, "src", "index.ts"),
114
- },
115
- {
116
- templateFilePath: path.join(templateFolderPath, "index.html.handlebars"),
117
- destinationFilePath: path.join(newFolderPath, "src", "index.html"),
118
- },
119
- {
120
- templateFilePath: path.join(templateFolderPath, "launch.json.handlebars"),
121
- destinationFilePath: path.join(newFolderPath, ".vscode", "launch.json"),
122
- },
123
- {
124
- templateFilePath: path.join(templateFolderPath, "m2c2kit.json.handlebars"),
125
- destinationFilePath: path.join(newFolderPath, "m2c2kit.json"),
126
- },
127
- ];
128
- let errorProcessingTemplates = false;
129
- templates.forEach((t) => {
130
- try {
131
- const contents = applyValuesToTemplate(t.templateFilePath, {
132
- appName: appName,
133
- className: className,
134
- cliVersion: cliVersion,
135
- studyCode: generateStudyCode(),
136
- });
137
- fs.writeFileSync(t.destinationFilePath, contents);
138
- console.log(`${chalk.green("CREATE")} ${t.destinationFilePath} (${contents.length} bytes)`);
139
- }
140
- catch (error) {
141
- errorProcessingTemplates = true;
142
- console.error(`${chalk.red("ERROR")} ${t.destinationFilePath}`);
143
- }
144
- });
145
- if (errorProcessingTemplates) {
146
- console.error(`${chalk.red("ERROR")}: could not generate files`);
147
- yarg.exit(1, new Error("template error"));
148
- }
149
- const foldersToCopy = [
150
- {
151
- sourceFolder: path.join(packageHomeFolderPath, "assets", "cli-starter"),
152
- destinationFolder: path.join(newFolderPath, "src", "assets", appName),
153
- },
154
- {
155
- sourceFolder: path.join(packageHomeFolderPath, "assets", "css"),
156
- destinationFolder: path.join(newFolderPath, "src", "assets", "css"),
157
- },
158
- // {
159
- // sourceFolder: path.join(packageHomeFolderPath, "scripts"),
160
- // destinationFolder: path.join(newFolderPath),
161
- // },
162
- ];
163
- try {
164
- await Promise.all(foldersToCopy.map(async (c) => copyFolderRecursive(c)));
165
- }
166
- catch (error) {
167
- console.error(`${chalk.red("ERROR")}: could not copy files: ${error}`);
168
- yarg.exit(1, new Error("copy files error"));
169
- }
170
- const spinner = ora("Installing packages (npm)...").start();
171
- let npm;
172
- if (process.platform === "win32") {
173
- npm = spawn(`npm`, ["install"], { shell: true, cwd: newFolderPath });
174
- // npm = spawn(`npm`, ["--version"], { shell: true, cwd: newFolderPath });
175
- }
176
- else {
177
- npm = spawn(`npm`, ["install"], { cwd: newFolderPath });
178
- // npm = spawn(`npm`, ["--version"], { cwd: newFolderPath });
179
- }
180
- let npmOut = "";
181
- npm.stdout.on("data", (data) => {
182
- npmOut = npmOut + data.toString();
183
- });
184
- npm.stderr.on("data", (data) => {
185
- npmOut = npmOut + data.toString();
186
- });
187
- // node will wait for the npm process to exit. Thus,
188
- // this on exit listener is the last code we execute before
189
- // the "m2 new" command is done
190
- npm.on("exit", (code) => {
191
- if (code === 0) {
192
- spinner.succeed("Packages installed successfully");
193
- console.log(`Created ${appName} at ${newFolderPath}`);
194
- console.log("Inside that directory, you can run several commands:\n");
195
- console.log(` ${chalk.green("npm run serve")}`);
196
- console.log(" Starts the development server.\n");
197
- console.log(` ${chalk.green("npm run build")}`);
198
- console.log(" Bundles the app for production.\n");
199
- // console.log(` ${chalk.green("m2 upload")}`);
200
- // console.log(" Uploads the app to a test server.\n");
201
- console.log("We suggest you begin by typing:\n");
202
- console.log(` ${chalk.green("cd")} ${appName}`);
203
- console.log(` ${chalk.green("npm run serve")}`);
204
- }
205
- else {
206
- spinner.fail("Error during package installation");
207
- console.log(npmOut);
208
- yarg.exit(1, new Error("npm install error"));
209
- }
210
- });
211
- })
212
- // TODO: move npm script execution into this cli, so there's a single tool
213
- //.command("build", "compiles to output directory named dist/")
214
- //.command("serve", "builds and serves app, rebuilding on files changes")
215
- // .command(
216
- // "upload",
217
- // "upload production build from dist/ to server",
218
- // (yargs) => {
219
- // yargs
220
- // .option("url", {
221
- // alias: "u",
222
- // type: "string",
223
- // description: "server url",
224
- // })
225
- // .option("studyCode", {
226
- // alias: "s",
227
- // type: "string",
228
- // description:
229
- // "study code. This 5-digit random alphanumeric code is automatically generated by the cli during the creation of a new app and stored in m2c2kit.json",
230
- // })
231
- // .option("remove", {
232
- // alias: "r",
233
- // type: "boolean",
234
- // description: "remove saved server credentials from local machine",
235
- // });
236
- // },
237
- // async (argv) => {
238
- // const m2c2kitProjectConfig = new Conf({
239
- // // store this config in the project folder, not user's ~/.config
240
- // cwd: ".",
241
- // configName: "m2c2kit",
242
- // });
243
- // // store server credentials in user folder, not project folder
244
- // // to avoid secrets getting into source control
245
- // const m2c2kitUserConfig = new Conf({
246
- // configName: "m2c2kit-cli-user",
247
- // projectName: "m2c2kit-cli",
248
- // projectSuffix: "",
249
- // accessPropertiesByDotNotation: false,
250
- // });
251
- // if (argv["remove"] as unknown as boolean) {
252
- // m2c2kitUserConfig.clear();
253
- // console.log(
254
- // `all server credentials removed from ${m2c2kitUserConfig.path}`
255
- // );
256
- // return;
257
- // }
258
- // let abort = false;
259
- // let serverConfig: serverConfig;
260
- // try {
261
- // serverConfig = m2c2kitProjectConfig.get("server") as serverConfig;
262
- // } catch {
263
- // serverConfig = {};
264
- // }
265
- // // get url first from command line, then from config file, then assign blank
266
- // let url = (argv["url"] as unknown as string) ?? serverConfig?.url ?? "";
267
- // if (url === "") {
268
- // const response = await prompts({
269
- // type: "text",
270
- // name: "url",
271
- // message: "server url?",
272
- // validate: (text) =>
273
- // (text as unknown as string).length > 0
274
- // ? true
275
- // : "provide a valid server url (e.g., https://www.myserver.com)",
276
- // onState: (state) => (abort = state.aborted === true),
277
- // });
278
- // url = response.url;
279
- // }
280
- // if (abort) {
281
- // yarg.exit(1, new Error("user aborted"));
282
- // }
283
- // // get study code first from command line, then from config file, then generate new one
284
- // const studyCode =
285
- // (argv["studyCode"] as unknown as string) ??
286
- // serverConfig?.studyCode ??
287
- // generateStudyCode();
288
- // // if new url or study code was provided, save these
289
- // if (url !== serverConfig?.url || studyCode !== serverConfig?.studyCode) {
290
- // m2c2kitProjectConfig.set("server", { url, studyCode } as serverConfig);
291
- // }
292
- // const serverCredentials = m2c2kitUserConfig.get(
293
- // url
294
- // ) as m2c2kitServerCredentials;
295
- // let username = serverCredentials?.username ?? "";
296
- // let writeCredentials = false;
297
- // if (username === "") {
298
- // const response = await prompts({
299
- // type: "text",
300
- // name: "username",
301
- // message: `username for server at ${url}`,
302
- // validate: (text) =>
303
- // (text as unknown as string).length > 0
304
- // ? true
305
- // : "username cannot be empty",
306
- // onState: (state) => (abort = state.aborted === true),
307
- // });
308
- // username = response.username;
309
- // writeCredentials = true;
310
- // }
311
- // if (abort) {
312
- // yarg.exit(1, new Error("user aborted"));
313
- // }
314
- // let password = serverCredentials?.password ?? "";
315
- // if (password === "") {
316
- // const response = await prompts({
317
- // type: "password",
318
- // name: "password",
319
- // message: `password for server at ${url}`,
320
- // validate: (text) =>
321
- // (text as unknown as string).length > 0
322
- // ? true
323
- // : "password cannot be empty",
324
- // onState: (state) => (abort = state.aborted === true),
325
- // });
326
- // password = response.password;
327
- // writeCredentials = true;
328
- // }
329
- // if (abort) {
330
- // yarg.exit(1, new Error("user aborted"));
331
- // }
332
- // if (writeCredentials) {
333
- // m2c2kitUserConfig.set(url, {
334
- // username,
335
- // password,
336
- // } as m2c2kitServerCredentials);
337
- // console.log(
338
- // `credentials (username, password) for server ${url} written to ${m2c2kitUserConfig.path}`
339
- // );
340
- // }
341
- // // const getFilenamesRecursive = async (dir: string): Promise<string[]> => {
342
- // // const dirents = await readdir(dir, { withFileTypes: true });
343
- // // const files = await Promise.all(
344
- // // dirents.map((dirent) => {
345
- // // const res = resolve(dir, dirent.name);
346
- // // return dirent.isDirectory() ? getFilenamesRecursive(res) : res;
347
- // // })
348
- // // );
349
- // // return Array.prototype.concat(...files);
350
- // // };
351
- // const uploadFile = async (
352
- // serverUrl: string,
353
- // userPw: string,
354
- // studyCode: string,
355
- // fileContent: Buffer,
356
- // filename: string
357
- // ) => {
358
- // const basename = path.basename(filename);
359
- // let folderName = filename.replace(basename, "");
360
- // folderName = folderName.replace(/\/$/, ""); // no trailing slash
361
- // const form = new FormData();
362
- // form.append("folder", folderName);
363
- // form.append("file", fileContent, filename);
364
- // const userPwBase64 = Buffer.from(userPw, "utf-8").toString("base64");
365
- // const uploadUrl = `${serverUrl}/api/studies/${studyCode}/files`;
366
- // return axios.post(uploadUrl, form, {
367
- // headers: {
368
- // ...form.getHeaders(),
369
- // // this uses basic auth because it's simply a demo server
370
- // // but for production, a more robust auth must be used
371
- // Authorization: `Basic ${userPwBase64}`,
372
- // },
373
- // });
374
- // };
375
- // const distFolder = path.join(process.cwd(), "dist");
376
- // if (!fs.existsSync(distFolder)) {
377
- // console.error(
378
- // `${chalk.red(
379
- // "ERROR"
380
- // )}: distribution folder not found. looked for ${distFolder}`
381
- // );
382
- // yarg.exit(1, new Error("no files"));
383
- // }
384
- // const files = await getFilenamesRecursive(distFolder);
385
- // if (files.length === 0) {
386
- // console.error(
387
- // `${chalk.red("ERROR")}: no files for upload in ${distFolder}`
388
- // );
389
- // yarg.exit(1, new Error("no files"));
390
- // }
391
- // const uploadRequests = files.map((file) => {
392
- // let relativeName = file;
393
- // if (relativeName.startsWith(distFolder)) {
394
- // relativeName = relativeName.slice(distFolder.length);
395
- // // on server, normalize all paths to forward slash
396
- // relativeName = relativeName.replace(/\\/g, "/");
397
- // if (relativeName.startsWith("/")) {
398
- // relativeName = relativeName.slice(1);
399
- // }
400
- // }
401
- // const fileBuffer = fs.readFileSync(file);
402
- // return uploadFile(
403
- // url,
404
- // `${username}:${password}`,
405
- // studyCode,
406
- // fileBuffer,
407
- // relativeName
408
- // );
409
- // });
410
- // const spinner = ora(`Uploading files to ${url}...`).start();
411
- // Promise.all(uploadRequests)
412
- // .then(() => {
413
- // spinner.succeed(`All files uploaded from ${distFolder}`);
414
- // console.log(
415
- // `Study can be viewed with browser at ${url}/studies/${studyCode}`
416
- // );
417
- // })
418
- // .catch((error) => {
419
- // spinner.fail(`Failed uploading files from ${distFolder}`);
420
- // console.error(`${chalk.red("ERROR")}: ${error}`);
421
- // yarg.exit(1, new Error("upload error"));
422
- // });
423
- // }
424
- // )
425
- .demandCommand()
426
- .strict()
427
- // seems to have problems getting version automatically; thus I explicity
428
- // provide
429
- .version(cliVersion)
430
- .showHelpOnFail(true).argv;
@@ -1,98 +0,0 @@
1
- export function isReservedWord(name) {
2
- const reserved = [
3
- "abstract",
4
- "arguments",
5
- "await",
6
- "boolean",
7
- "break",
8
- "byte",
9
- "case",
10
- "catch",
11
- "char",
12
- "class",
13
- "const",
14
- "continue",
15
- "debugger",
16
- "default",
17
- "delete",
18
- "do",
19
- "double",
20
- "else",
21
- "enum",
22
- "eval",
23
- "export",
24
- "extends",
25
- "false",
26
- "final",
27
- "finally",
28
- "float",
29
- "for",
30
- "function",
31
- "goto",
32
- "if",
33
- "implements",
34
- "import",
35
- "in",
36
- "instanceof",
37
- "int",
38
- "interface",
39
- "let",
40
- "long",
41
- "native",
42
- "new",
43
- "null",
44
- "package",
45
- "private",
46
- "protected",
47
- "public",
48
- "return",
49
- "short",
50
- "static",
51
- "super",
52
- "switch",
53
- "synchronized",
54
- "this",
55
- "throw",
56
- "throws",
57
- "transient",
58
- "true",
59
- "try",
60
- "typeof",
61
- "var",
62
- "void",
63
- "volatile",
64
- "while",
65
- "with",
66
- "yield",
67
- ];
68
- const builtin = [
69
- "Array",
70
- "Date",
71
- "eval",
72
- "function",
73
- "hasOwnProperty",
74
- "Infinity",
75
- "isFinite",
76
- "isNaN",
77
- "isPrototypeOf",
78
- "length",
79
- "Math",
80
- "name",
81
- "NaN",
82
- "Number",
83
- "Object",
84
- "prototype",
85
- "String",
86
- "toString",
87
- "undefined",
88
- "valueOf",
89
- ];
90
- if (reserved.includes(name) || builtin.includes(name)) {
91
- return true;
92
- }
93
- const re = /^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*$/;
94
- if (!re.test(name)) {
95
- return true;
96
- }
97
- return false;
98
- }
@@ -1,17 +0,0 @@
1
- # {{appName}}
2
-
3
- This project was generated with the m2c2kit CLI version {{cliVersion}}.
4
-
5
- ## Development server
6
-
7
- Run `npm run serve` from the command line for a development server. Browse to `http://localhost:3000/`. The app will automatically compile and reload when you change source files. If you get an error on the reload, edit `rollup.config.mjs` and increase the delay parameter (unit is milliseconds) in this line:
8
-
9
- livereload({ watch: "build", delay: 250 })
10
-
11
- ## Debugging
12
-
13
- With the file `.vscode/launch.json`, the project has been configured for debugging with Visual Studio Code and Chrome. If the development server is running, debugging in Visual Studio Code is available by pressing `F5` or selecting Run --> Start Debugging
14
-
15
- ## Build
16
-
17
- Run `npm run build` from the command line to build the project. Build artifacts will be stored in the `dist/` directory.
@@ -1,29 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>{{appName}}</title>
5
- <meta charset="UTF-8" />
6
- <meta http-equiv="Cache-Control" content="no-store, max-age=0" />
7
- <meta http-equiv="Pragma" content="no-cache" />
8
- <meta http-equiv="Expires" content="0" />
9
- <meta name="viewport" content="width=device-width, initial-scale=1" />
10
- <link href="assets/css/m2c2kit.css" rel="preload" as="style" />
11
- <link href="assets/css/m2c2kit.css" rel="stylesheet" />
12
- <link href="assets/css/defaultV2.css" rel="stylesheet" />
13
- <link href="assets/css/nouislider.css" rel="stylesheet" />
14
- <link href="assets/css/select2.css" rel="stylesheet" />
15
- <link
16
- href="assets/css/bootstrap-datepicker.standalone.css"
17
- rel="stylesheet"
18
- />
19
- <link href="assets/css/bootstrap-slider.css" rel="stylesheet" />
20
- <script type="module" src="./index.js" defer></script>
21
- </head>
22
-
23
- <body class="m2c2kit-background-color m2c2kit-no-margin">
24
- <div id="m2c2kit-survey-div"></div>
25
- <div class="m2c2kit-full-viewport m2c2kit-flex-container" id="m2c2kit-container-div">
26
- <canvas class="m2c2kit-full-viewport" id="m2c2kit-canvas"></canvas>
27
- </div>
28
- </body>
29
- </html>