chocola 2.0.0-next.2 → 2.0.0-next.4

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.
@@ -1,17 +1,6 @@
1
1
  import path from "path";
2
- import chalk from "chalk";
3
2
  import { getChocolaConfig } from "../utils.js";
4
3
 
5
- /**
6
- * Loads and merges Chocola configuration with defaults
7
- * @param {import("fs").PathLike} rootDir
8
- * @returns {Promise<{
9
- * srcDir: string,
10
- * outDir: string,
11
- * libDir: string,
12
- * emptyOutDir: boolean
13
- * }>}
14
- */
15
4
  export async function loadConfig(rootDir) {
16
5
  const config = await getChocolaConfig(rootDir);
17
6
  const bundleConfig = config.bundle || {};
@@ -20,52 +9,10 @@ export async function loadConfig(rootDir) {
20
9
  const outDir = bundleConfig.outDir || "dist";
21
10
  const libDir = bundleConfig.libDir || "lib";
22
11
  const emptyOutDir = bundleConfig.emptyOutDir !== false;
23
- const assetImport = bundleConfig.assetImport || "legacy";
24
12
 
25
- logConfigWarnings(bundleConfig, emptyOutDir, assetImport);
26
-
27
- return { srcDir, outDir, libDir, emptyOutDir, assetImport };
28
- }
29
-
30
- function logConfigWarnings(bundleConfig, emptyOutDir, assetImport) {
31
- if (!bundleConfig.srcDir) {
32
- console.warn(
33
- chalk.bold.yellow("WARNING!"),
34
- 'srcDir not defined in chocola.config.json file: using default "src" directory.'
35
- );
36
- }
37
-
38
- if (!bundleConfig.outDir) {
39
- console.warn(
40
- chalk.bold.yellow("WARNING!"),
41
- 'outDir not defined in chocola.config.json file: using default "dist" directory.'
42
- );
43
- }
44
-
45
- if (!bundleConfig.libDir) {
46
- console.warn(
47
- chalk.bold.yellow("WARNING!"),
48
- 'libDir not defined in chocola.config.json file: using default "lib" directory.'
49
- );
50
- }
51
-
52
- console.log(`> using emptyOutDir = ${emptyOutDir}`);
53
-
54
- if (assetImport !== "static") {
55
- console.warn(
56
- chalk.bold.yellow("DEPRECATION WARNING!"),
57
- 'The legacy asset import system ("bundle.assetImport") will be removed in Chocola 2. ' +
58
- 'Set "bundle.assetImport": "static" in chocola.config.json and migrate your assets to src/static/.'
59
- );
60
- }
13
+ return { srcDir, outDir, libDir, emptyOutDir };
61
14
  }
62
15
 
63
- /**
64
- * Resolves all path directories based on configuration
65
- * @param {import("fs").PathLike} rootDir
66
- * @param {object} config
67
- * @returns {object}
68
- */
69
16
  export function resolvePaths(rootDir, config) {
70
17
  return {
71
18
  outDir: path.join(rootDir, config.outDir),
@@ -4,20 +4,10 @@ import path from "path";
4
4
  import { throwError, protectCurlyBraces, restoreCurlyBraces, compileExpression } from "./utils.js";
5
5
  import { readMyFile } from "./fs.js";
6
6
 
7
- /**
8
- * Creates a JSDOM instance from source index file
9
- * @param {string} srcIndexContent
10
- * @returns {JSDOM}
11
- */
12
7
  export function createDOM(srcIndexContent) {
13
8
  return new JSDOM(protectCurlyBraces(srcIndexContent));
14
9
  }
15
10
 
16
- /**
17
- * Validates that the index file has an <app> root element
18
- * @param {Document} doc
19
- * @throws {Error} if <app> element not found
20
- */
21
11
  export function validateAppContainer(doc) {
22
12
  const appContainer = doc.querySelector("app");
23
13
  if (!appContainer) {
@@ -26,20 +16,10 @@ export function validateAppContainer(doc) {
26
16
  return appContainer;
27
17
  }
28
18
 
29
- /**
30
- * Extracts all child elements from app container
31
- * @param {Element} appContainer
32
- * @returns {Element[]}
33
- */
34
19
  export function getAppElements(appContainer) {
35
20
  return Array.from(appContainer.querySelectorAll("*"));
36
21
  }
37
22
 
38
- /**
39
- * Extracts context attributes from element (ctx.* attributes)
40
- * @param {Element} element
41
- * @returns {object}
42
- */
43
23
  export function extractContextFromElement(element) {
44
24
  const ctx = {};
45
25
  for (const attr of element.attributes) {
@@ -58,31 +38,16 @@ export function extractContextFromElement(element) {
58
38
  return ctx;
59
39
  }
60
40
 
61
- /**
62
- * Serializes and formats DOM to pretty HTML
63
- * @param {JSDOM} dom
64
- * @returns {string}
65
- */
66
41
  export async function serializeDOM(dom) {
67
42
  const beautify = (await import("js-beautify")).default;
68
43
  const finalHtml = restoreCurlyBraces(dom.serialize());
69
44
  return beautify.html(finalHtml, { indent_size: 2 });
70
45
  }
71
46
 
72
- /**
73
- * Writes the final HTML to output directory
74
- * @param {string} html
75
- * @param {import("fs").PathLike} outDirPath
76
- */
77
47
  export async function writeHTMLOutput(html, outDirPath) {
78
48
  await fs.writeFile(path.join(outDirPath, "index.html"), html);
79
49
  }
80
50
 
81
- /**
82
- * Gets all stylesheet and icon links from document
83
- * @param {Document} doc
84
- * @returns {{stylesheets: HTMLLinkElement[], icons: HTMLLinkElement[]}}
85
- */
86
51
  export function getAssetLinks(doc) {
87
52
  const docLinks = Array.from(doc.querySelectorAll("link"));
88
53
  const stylesheets = docLinks.filter(link => link.rel === "stylesheet");
@@ -90,21 +55,10 @@ export function getAssetLinks(doc) {
90
55
  return { stylesheets, icons };
91
56
  }
92
57
 
93
- /**
94
- * Writes CSS to output directory
95
- * @param {string} css
96
- * @param {import("fs").PathLike} outDirPath
97
- * @param {string} filename
98
- */
99
58
  export async function writeCSSOutput(css, outDirPath, filename = "scopes.css") {
100
59
  await fs.writeFile(path.join(outDirPath, filename), css);
101
60
  }
102
61
 
103
- /**
104
- * Appends a stylesheet link element to document head
105
- * @param {Document} doc
106
- * @param {string} filename
107
- */
108
62
  export function appendStylesheetLink(doc, filename) {
109
63
  const linkEl = doc.createElement("link");
110
64
  linkEl.rel = "stylesheet";
@@ -112,11 +66,6 @@ export function appendStylesheetLink(doc, filename) {
112
66
  doc.head.appendChild(linkEl);
113
67
  }
114
68
 
115
- /**
116
- * Appends a script element to document body
117
- * @param {Document} doc
118
- * @param {string} filename
119
- */
120
69
  export function appendRuntimeScript(doc, filename) {
121
70
  const runtimeScriptEl = doc.createElement("script");
122
71
  runtimeScriptEl.type = "module";
package/compiler/fs.js CHANGED
@@ -1,12 +1,6 @@
1
1
  import { promises as fs } from "fs";
2
2
  import { throwError } from "./utils.js";
3
3
 
4
- /**
5
- * Reads a file and returns its contents as a UTF-8 string
6
- * @param {import("fs").PathLike} filePath - Path to the file
7
- * @returns {Promise<string>} - File contents
8
- * @throws {Error} - If file cannot be read
9
- */
10
4
  export async function readMyFile(filePath) {
11
5
  try {
12
6
  const data = await fs.readFile(filePath, "utf-8");
@@ -16,11 +10,6 @@ export async function readMyFile(filePath) {
16
10
  }
17
11
  }
18
12
 
19
- /**
20
- * Checks if a file exists
21
- * @param {import("fs").PathLike} filePath - Path to check
22
- * @returns {Promise<boolean>} - True if file exists, false otherwise
23
- */
24
13
  export async function checkFile(filePath) {
25
14
  try {
26
15
  await fs.access(filePath);
package/compiler/index.js CHANGED
@@ -16,7 +16,6 @@ import { processAllComponents } from "./component-processor.js";
16
16
  import { generateRuntimeScript } from "./runtime-generator.js";
17
17
  import { genRandomId, throwError, compileExpression } from "./utils.js";
18
18
  import {
19
- copyResources,
20
19
  copyStaticDir,
21
20
  getComponents,
22
21
  getSrcIndex,
@@ -76,15 +75,15 @@ async function setupOutputDirectory(outDirPath, emptyOutDir) {
76
75
 
77
76
  async function loadAndDisplayComponents(srcComponentsPath) {
78
77
  const foundComponents = await getComponents(srcComponentsPath);
79
- const { loadedComponents, notDefComps, componentsLib } = foundComponents;
78
+ const { loadedComponents, notDefComps: emptyComps, componentsLib } = foundComponents;
80
79
 
81
80
  console.log(` LOADING COMPONENTS`);
82
81
  console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(srcComponentsPath) + ":");
83
82
  console.log(" ", componentsLib, "\n");
84
83
 
85
- if (notDefComps.length > 0) {
86
- console.warn(chalk.bold.yellow("WARNING!"), "The following components don't include a default export:");
87
- console.log(" ", notDefComps);
84
+ if (emptyComps?.length > 0) {
85
+ console.warn(chalk.bold.yellow("WARNING!"), "The following component files are empty:");
86
+ console.log(" ", emptyComps);
88
87
  }
89
88
 
90
89
  return loadedComponents;
@@ -106,15 +105,7 @@ async function processAssets(doc, rootDir, srcDir, outDirPath) {
106
105
  return cssContents
107
106
  }
108
107
 
109
- /**
110
- * Compiles a static build of your Chocola project from the directory provided.
111
- * @param {import("fs").PathLike} rootDir
112
- * @param {Object<{
113
- * isHotReload?: boolean
114
- * }
115
- * >} config
116
- */
117
- export default async function runtime(rootDir, buildConfig) {
108
+ export default async function compile(rootDir, buildConfig) {
118
109
  const isHotReload = buildConfig?.isHotReload || null;
119
110
  !isHotReload && logBanner();
120
111
 
@@ -138,20 +129,13 @@ export default async function runtime(rootDir, buildConfig) {
138
129
  const doc = dom.window.document;
139
130
  const appContainer = validateAppContainer(doc);
140
131
 
141
- const warnedDeprecated = new Set();
142
- function hasDelIfAttr(el, sourceLoc) {
143
- if (el.hasAttribute("del-if") && !el.hasAttribute("del:if") && !warnedDeprecated.has(sourceLoc)) {
144
- warnedDeprecated.add(sourceLoc);
145
- const name = sourceLoc ? sourceLoc.split(/[\\/]/).pop() : "unknown";
146
- console.warn(chalk.yellow(`[deprecation] "del-if" is deprecated in ${chalk.cyan(name)} and will be removed in Chocola 2. Use "del:if" instead.`));
147
- }
148
- return el.hasAttribute("del-if") || el.hasAttribute("del:if");
132
+ function hasDelIfAttr(el) {
133
+ return el.hasAttribute("del:if");
149
134
  }
150
135
  function getDelIfAttr(el) {
151
- return el.getAttribute("del:if") ?? el.getAttribute("del-if");
136
+ return el.getAttribute("del:if");
152
137
  }
153
138
  function removeDelIfAttr(el) {
154
- el.removeAttribute("del-if");
155
139
  el.removeAttribute("del:if");
156
140
  }
157
141
 
@@ -162,7 +146,7 @@ export default async function runtime(rootDir, buildConfig) {
162
146
 
163
147
  for (const child of children) {
164
148
  const hasIf = child.hasAttribute("if");
165
- const hasDelIf = hasDelIfAttr(child, pageSourcePath);
149
+ const hasDelIf = hasDelIfAttr(child);
166
150
  const hasElif = child.hasAttribute("elif");
167
151
  const hasElse = child.hasAttribute("else");
168
152
 
@@ -227,7 +211,7 @@ export default async function runtime(rootDir, buildConfig) {
227
211
  const appElements = getAppElements(appContainer);
228
212
  const { runtimeScript, scopesCss } = processAllComponents(appElements, loadedComponents, pageSourcePath, srcIndexContent);
229
213
  const runtimeFilename = await generateRuntimeScript(runtimeScript, paths.outDir);
230
- const globalCss = (await processAssets(doc, rootDir, config.srcDir, paths.outDir)).join("\n");
214
+ await processAssets(doc, rootDir, config.srcDir, paths.outDir);
231
215
 
232
216
  if (scopesCss) {
233
217
  const fileName = "sc-" + genRandomId(null, 6) + ".css";
@@ -240,11 +224,7 @@ export default async function runtime(rootDir, buildConfig) {
240
224
  await writeHTMLOutput(html, paths.outDir);
241
225
 
242
226
  try {
243
- if (config.assetImport === "static") {
244
- await copyStaticDir(paths.src, paths.outDir);
245
- } else {
246
- await copyResources(rootDir, scopesCss, globalCss, config.srcDir, paths.outDir);
247
- }
227
+ await copyStaticDir(paths.src, paths.outDir);
248
228
  } catch (err) {
249
229
  throwError(err.message || err);
250
230
  }
@@ -273,5 +253,5 @@ export const app = {
273
253
  * @example
274
254
  * @param {PathLike} __rootdir the directory where your Chocola App is
275
255
  */
276
- async build(__rootdir) { return runtime(__rootdir) }
256
+ async build(__rootdir) { return compile(__rootdir) }
277
257
  };
@@ -1,26 +1,13 @@
1
1
  import { promises as fs } from "fs";
2
- import { loadWithAssets, throwError, genRandomId, isWebLink } from "./utils.js";
2
+ import { throwError, genRandomId } from "./utils.js";
3
3
  import { readMyFile, checkFile } from "./fs.js";
4
- import { JSDOM } from "jsdom";
5
4
  import path from "path";
6
5
 
7
- /**
8
- * Discovers and loads all components from a library directory.
9
- * Components are JavaScript files that start with an uppercase letter.
10
- * They must have a default export that is a function
11
- * @param {import("node:fs").PathLike} libDir - Directory containing component files
12
- * @returns {Promise<{
13
- * componentsLib: string[],
14
- * loadedComponents: Map<string, object>,
15
- * notDefComps: string[]
16
- * }>}
17
- * @throws {Error} if libDir cannot be found
18
- */
19
6
  export async function getComponents(libDir) {
20
7
  try {
21
8
  let componentsLib = [];
22
9
  let loadedComponents = new Map();
23
- let notDefComps = [];
10
+ let emptyComps = [];
24
11
 
25
12
  const components = await fs.readdir(libDir);
26
13
 
@@ -29,22 +16,15 @@ export async function getComponents(libDir) {
29
16
  }
30
17
 
31
18
  for (const comp of components) {
32
- if (!comp.endsWith(".js") || comp[0] !== comp[0].toUpperCase()) continue;
19
+ if (!comp.endsWith(".html")) continue;
33
20
 
34
21
  componentsLib.push(comp);
35
22
 
36
- let module;
37
23
  try {
38
- module = await loadWithAssets(path.join(libDir, comp));
39
-
40
- if (typeof module.default !== "function") {
41
- notDefComps.push(comp);
42
- continue;
43
- }
44
-
45
- const instance = module.default();
46
24
  const compPath = path.join(libDir, comp);
47
- instance.__sourceFile = compPath;
25
+ const instance = await fs.readFile(compPath, "utf-8");
26
+
27
+ if (instance === "" || instance.trim().length === 0) emptyComps.push(comp);
48
28
 
49
29
  loadedComponents.set(comp.toLowerCase(), instance);
50
30
  } catch (err) {
@@ -52,22 +32,12 @@ export async function getComponents(libDir) {
52
32
  }
53
33
  }
54
34
 
55
- return { componentsLib, loadedComponents, notDefComps };
35
+ return { componentsLib, loadedComponents, emptyComps };
56
36
  } catch (err) {
57
37
  throwError(`Failed to load components from ${libDir}: ${err.message}`);
58
38
  }
59
39
  }
60
40
 
61
- /**
62
- * Loads the project index file (HTML or .choco)
63
- * If both HTML and .choco files exist, throws an error
64
- * @param {import("fs").PathLike} srcPath - Source directory
65
- * @returns {Promise<{
66
- * srcHtmlFile: string | null,
67
- * srcChocoFile: string | null
68
- * }>}
69
- * @throws {Error} if both index files exist or .choco is used (not yet supported)
70
- */
71
41
  export async function getSrcIndex(srcPath) {
72
42
  const srcHtmlPath = path.join(srcPath, "index.html");
73
43
 
@@ -85,15 +55,6 @@ export async function getSrcIndex(srcPath) {
85
55
  }
86
56
  }
87
57
 
88
- /**
89
- * Processes stylesheet links: copies CSS files to output and updates link href
90
- * @param {HTMLLinkElement} link - The link element to process
91
- * @param {import("fs").PathLike} rootDir - Root project directory
92
- * @param {string} srcDir - Source directory name
93
- * @param {import("fs").PathLike} outDirPath - Output directory path
94
- * @param {Array} fileIds - Array to track used file IDs
95
- * @throws {Error} if stylesheet cannot be read or written
96
- */
97
58
  export async function processStylesheet(link, rootDir, srcDir, outDirPath, fileIds) {
98
59
  try {
99
60
  const href = link.href;
@@ -111,14 +72,6 @@ export async function processStylesheet(link, rootDir, srcDir, outDirPath, fileI
111
72
  }
112
73
  }
113
74
 
114
- /**
115
- * Processes icon links: copies icon files to output directory
116
- * @param {HTMLLinkElement} link - The link element to process
117
- * @param {import("fs").PathLike} rootDir - Root project directory
118
- * @param {string} srcDir - Source directory name
119
- * @param {import("fs").PathLike} outDirPath - Output directory path
120
- * @throws {Error} if icon cannot be copied
121
- */
122
75
  export async function processIcons(link, rootDir, srcDir, outDirPath) {
123
76
  try {
124
77
  const href = link.href;
@@ -130,139 +83,6 @@ export async function processIcons(link, rootDir, srcDir, outDirPath) {
130
83
  }
131
84
  }
132
85
 
133
- export function getCssAssets(css) {
134
- const results = new Set();
135
- const urlRegex = /url\(([^)]+)\)/gi;
136
- const importStringRegex = /@import\s+["']([^"']+)["']/gi;
137
- const importUrlRegex = /@import\s+url\(([^)]+)\)/gi;
138
-
139
- const clean = (raw) => {
140
- let p = raw.trim();
141
- if (
142
- (p.startsWith('"') && p.endsWith('"')) ||
143
- (p.startsWith("'") && p.endsWith("'"))
144
- ) {
145
- p = p.slice(1, -1);
146
- }
147
- if (p.startsWith("data:")) return null;
148
- return p;
149
- };
150
-
151
- let match;
152
- while ((match = urlRegex.exec(css))) {
153
- const p = clean(match[1]);
154
- if (p) results.add(p);
155
- }
156
- while ((match = importStringRegex.exec(css))) {
157
- const p = clean(match[1]);
158
- if (p) results.add(p);
159
- }
160
- while ((match = importUrlRegex.exec(css))) {
161
- const p = clean(match[1]);
162
- if (p) results.add(p);
163
- }
164
- return [...results];
165
- }
166
-
167
- function getComponentSource(el) {
168
- let cur = el;
169
- while (cur) {
170
- const src = cur.getAttribute?.("data-ch-source");
171
- if (src) return src;
172
- cur = cur.parentElement;
173
- }
174
- return null;
175
- }
176
-
177
- /**
178
- * Copies all local resources (images, fonts, etc.) to output directory.
179
- * Excludes web links and script/link tags.
180
- * @param {import("fs").PathLike} rootDir - Root project directory
181
- * @param {string} scopesCss - Scoped CSS content
182
- * @param {string} globalCss - Global CSS content
183
- * @param {string} srcDir - Source directory name
184
- * @param {import("fs").PathLike} outDirPath - Output directory path
185
- */
186
- export async function copyResources(rootDir, scopesCss, globalCss, srcDir, outDirPath) {
187
- try {
188
- const newIndex = await fs.readFile(path.join(outDirPath, "index.html"), "utf8");
189
- const newDoc = new JSDOM(newIndex);
190
- const newElements = Array.from(newDoc.window.document.querySelectorAll("*"));
191
- const inlinePathsRegex = /url\((.*?)\)/gi;
192
-
193
- for (const el of newElements) {
194
- if (el.tagName === "LINK" || el.tagName === "SCRIPT") continue;
195
-
196
- const attrName = el.getAttribute("src") ? "src" : "href";
197
- const src = el.getAttribute(attrName);
198
- if (src && !isWebLink(src) && !src.startsWith("#") && !src.startsWith("/")) {
199
- const srcPath = path.join(rootDir, srcDir, src);
200
- const destPath = path.join(outDirPath, src);
201
- try {
202
- await fs.mkdir(path.dirname(destPath), { recursive: true });
203
- await fs.copyFile(srcPath, destPath);
204
- } catch (err) {
205
- throw new Error(
206
- `Failed to copy resource referenced by <${el.tagName.toLowerCase()} ${attrName}="${src}">` +
207
- (getComponentSource(el) ? ` in component "${getComponentSource(el)}"` : "") +
208
- `: ${err.message || err}`
209
- );
210
- }
211
- }
212
-
213
- const styles = el.getAttribute("style");
214
- if (styles) {
215
- let stylesMatch;
216
- while ((stylesMatch = inlinePathsRegex.exec(styles)) !== null) {
217
- let filePath = stylesMatch[1].trim();
218
- if (
219
- (filePath.startsWith('"') && filePath.endsWith('"')) ||
220
- (filePath.startsWith("'") && filePath.endsWith("'"))
221
- ) {
222
- filePath = filePath.slice(1, -1);
223
- }
224
- const srcPath = path.join(rootDir, srcDir, filePath);
225
- const destPath = path.join(outDirPath, filePath);
226
- try {
227
- await fs.mkdir(path.dirname(destPath), { recursive: true });
228
- await fs.copyFile(srcPath, destPath);
229
- } catch (err) {
230
- throw new Error(
231
- `Failed to copy resource from inline style on <${el.tagName.toLowerCase()}> url("${filePath}")` +
232
- (getComponentSource(el) ? ` in component "${getComponentSource(el)}"` : "") +
233
- `: ${err.message || err}`
234
- );
235
- }
236
- }
237
- }
238
- }
239
-
240
- const cssAssets = [...getCssAssets(scopesCss || ""), ...getCssAssets(globalCss || "")];
241
- for (const assetPath of cssAssets) {
242
- try {
243
- if (assetPath.startsWith('http:') || assetPath.startsWith('https:')) continue;
244
- const srcPath = path.join(rootDir, srcDir, assetPath);
245
- const destPath = path.join(outDirPath, assetPath);
246
- await fs.mkdir(path.dirname(destPath), { recursive: true });
247
- await fs.copyFile(srcPath, destPath);
248
- } catch (err) {
249
- throw new Error(
250
- `Failed to copy CSS asset "${assetPath}" referenced from stylesheets: ${err.message || err}`
251
- );
252
- }
253
- }
254
- } catch (err) {
255
- throw err;
256
- }
257
- }
258
-
259
- /**
260
- * Copies the static/ directory from source to output.
261
- * The entire static/ directory tree is copied recursively.
262
- * Silently skips if static/ does not exist.
263
- * @param {import("fs").PathLike} srcPath - Source directory path
264
- * @param {import("fs").PathLike} outDirPath - Output directory path
265
- */
266
86
  export async function copyStaticDir(srcPath, outDirPath) {
267
87
  const staticSrc = path.join(srcPath, "static");
268
88
  const staticDest = path.join(outDirPath, "static");
@@ -270,6 +90,5 @@ export async function copyStaticDir(srcPath, outDirPath) {
270
90
  await fs.access(staticSrc);
271
91
  await fs.cp(staticSrc, staticDest, { recursive: true, force: true });
272
92
  } catch {
273
- // static/ directory doesn't exist — nothing to copy
274
93
  }
275
94
  }
@@ -2,12 +2,6 @@ import { promises as fs } from "fs";
2
2
  import path from "path";
3
3
  import { genRandomId } from "./utils.js";
4
4
 
5
- /**
6
- * Generates a runtime script file and returns its filename
7
- * @param {string} runtimeScript - The JavaScript code for runtime
8
- * @param {import("fs").PathLike} outDirPath
9
- * @returns {Promise<string>} - filename of the generated runtime script
10
- */
11
5
  export async function generateRuntimeScript(runtimeScript, outDirPath) {
12
6
  const fileIds = [];
13
7
  const runtimeFilename = "run-" + genRandomId(fileIds, 6) + ".js";