@monimesl/dot-cli 0.1.0-alpha.11 → 0.1.0-alpha.12

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/README.md CHANGED
@@ -4,7 +4,7 @@ The `dotctl` command. It creates dots, builds them and runs them in the simulato
4
4
  also package, sign and publish them.
5
5
 
6
6
  ```sh
7
- dotctl create [dir] # create a dot (asks for its name and template)
7
+ dotctl create [dir] # create a dot (asks for its name, package name and template)
8
8
  dotctl create page <path> # add a page to the dot you are in
9
9
  dotctl build [root] # build the dot, minified, into dist/ui and dist/logic
10
10
  dotctl dev [root] # run the dot in the browser simulator and reload it on each change
@@ -20,6 +20,7 @@ dotctl lsp # start the .dml language server (editors run this)
20
20
  | `--port <port>` | `dev`, `preview` | Simulator port (default: `3680`, or the next free one) |
21
21
  | `--device` | `dev` | Also serve the dot to the Dot example apps on phones, through a QR code |
22
22
  | `--name <name>` | `create` | The dot's name, instead of asking |
23
+ | `--package <name>` | `create` | The dot's package name, such as `com.mysalone.thing`, instead of asking |
23
24
  | `--template <name>` | `create` | `starter` (one page to build on, the default) or `showcase` (all features) |
24
25
  | `--pm <name>` | `create` | The package manager to install with: npm, pnpm, yarn or bun |
25
26
 
@@ -29,10 +30,13 @@ Creates a self-contained project for a new dot. It asks for:
29
30
 
30
31
  - **The name.** The default is `Dot`. The project goes in `dir`, or a folder named after the dot in
31
32
  kebab case.
33
+ - **The package name.** A reverse domain name you own, such as `com.mysalone.thing`. It can't change
34
+ after the dot is published. The default is `com.example.<the folder's name>`.
32
35
  - **The template.** `starter` is one page to build on. `showcase` has a page for each feature.
33
36
 
34
37
  The dot depends on `@monimesl/dot-runtime` and `@monimesl/dot-cli`. Inside the Dot repository it
35
- uses the workspace packages instead.
38
+ uses the workspace packages instead. Its `dot.json` starts with `"id": "<monime-Internal-Id>"`.
39
+ Replace that with the ID Monime gives the dot when you register it, before you publish.
36
40
 
37
41
  Then it offers to install the dependencies and start the simulator. If more than one package
38
42
  manager is installed (npm, pnpm, yarn or bun), it asks which to use. The default is the one that
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
2
2
  import { spawn, spawnSync } from "node:child_process";
3
3
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
4
4
  import { parseArgs, styleText } from "node:util";
5
- import { CompileError, PAGES_DIR, build, installedRuntime, loadProject } from "@monimesl/dot-compiler";
5
+ import { CompileError, PAGES_DIR, build, installedRuntime, isDotPackage, loadProject } from "@monimesl/dot-compiler";
6
6
  import { DEFAULT_PORT, startSimulator } from "@monimesl/dot-simulator";
7
7
  import { existsSync, readFileSync, readdirSync, watch } from "node:fs";
8
8
  import { cp, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
@@ -59,7 +59,7 @@ const TEMPLATE_FILES = [
59
59
  "statics",
60
60
  ".env"
61
61
  ];
62
- const DOT_JSON_SCHEMA = "./node_modules/@monimesl/dot-runtime/dot.schema.json";
62
+ const DOT_JSON_SCHEMA = "https://unpkg.com/@monimesl/dot-runtime/dot.schema.json";
63
63
  const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
64
64
  function projectFiles(options) {
65
65
  return {
@@ -113,11 +113,11 @@ function tsconfig({ workspace }) {
113
113
  });
114
114
  }
115
115
  const UNREGISTERED_DOT_ID = "<monime-Internal-Id>";
116
- function dotJson({ name, packageName, template }) {
116
+ function dotJson({ name, dotPackage, template }) {
117
117
  const dot = {
118
118
  id: UNREGISTERED_DOT_ID,
119
119
  $schema: DOT_JSON_SCHEMA,
120
- package: `com.example.${hostWord(packageName)}`,
120
+ package: dotPackage,
121
121
  name,
122
122
  version: "0.1.0"
123
123
  };
@@ -163,13 +163,19 @@ also lists the page in \`dot.ts\`. The dot opens on the first page in that list.
163
163
 
164
164
  ## dot.json
165
165
 
166
- \`dot.json\` describes the dot to Dash. Before you publish, change its \`package\` from
167
- \`com.example.…\` to a reverse domain name you own. You can't change it after publishing. To
168
- publish, register the dot with Monime first, and replace its \`id\`, \`<monime-Internal-Id>\`, with
169
- the ID Monime gives you.
166
+ \`dot.json\` describes the dot to Dash. Its \`package\` is a reverse domain name you own, such as
167
+ \`com.mysalone.thing\`. You can't change it after publishing. To publish, register the dot with
168
+ Monime first, and replace its \`id\`, \`<monime-Internal-Id>\`, with the ID Monime gives you.
170
169
  \`network.hosts\` lists every host the dot sends requests to with \`dash.network\`. Requests to any
171
170
  other host fail. Your editor can complete the other fields from the schema the file points to.
172
171
 
172
+ ## Which Dash opens it
173
+
174
+ Dash opens the dot unless it was built with a newer major version of \`@monimesl/dot-runtime\` than
175
+ the one Dash has. A newer minor version can add Dash calls that an older Dash doesn't have yet.
176
+ There, such a call fails with a \`HostError\` whose \`code\` is \`unsupported\`, so catch it and carry
177
+ on without the feature.
178
+
173
179
  ## .env
174
180
 
175
181
  \`.env\` holds public configuration. Code in the dot reads each variable from \`env\`, such as
@@ -178,16 +184,19 @@ Never put a secret there. \`.env.development\` and \`.env.production\` override
178
184
  \`build\`.
179
185
  `;
180
186
  }
181
- function hostWord(packageName) {
182
- return packageName.slice(0, 63).replace(/-+$/, "");
187
+ function suggestedDotPackage(packageName) {
188
+ return `com.example.${packageName.slice(0, 63).replace(/-+$/, "")}`;
183
189
  }
184
190
  //#endregion
185
191
  //#region src/create/create.ts
186
- async function create({ name, dir, template = "starter" }) {
192
+ const DOT_PACKAGE_RULE = "Use lowercase words joined by dots, such as com.mysalone.thing.";
193
+ async function create({ name, dir, dotPackage, template = "starter" }) {
187
194
  const root = resolve(dir);
188
195
  if (existsSync(root) && readdirSync(root).length > 0) throw new CommandError(`${dir} already exists and is not empty. Choose another folder`);
189
196
  const packageName = toPackageName(name);
190
197
  if (!packageName) throw new CommandError(`The name "${name}" needs at least one letter or digit`);
198
+ dotPackage ??= suggestedDotPackage(packageName);
199
+ if (!isDotPackage(dotPackage)) throw new CommandError(`"${dotPackage}" isn't a package name. ${DOT_PACKAGE_RULE}`);
191
200
  const workspace = isInDotWorkspace(root);
192
201
  const source = templateDir(template);
193
202
  await mkdir(root, { recursive: true });
@@ -195,6 +204,7 @@ async function create({ name, dir, template = "starter" }) {
195
204
  const generated = projectFiles({
196
205
  name,
197
206
  packageName,
207
+ dotPackage,
198
208
  dotVersion: workspace ? "workspace:*" : `^${cliVersion()}`,
199
209
  workspace,
200
210
  template
@@ -736,15 +746,16 @@ async function confirm(question, fallback, terminal = TERMINAL) {
736
746
  if (!terminal.input.isTTY) return false;
737
747
  return await select(question, [{ value: "Yes" }, { value: "No" }], fallback ? "Yes" : "No", terminal) === "Yes";
738
748
  }
739
- async function ask(question, fallback, { input, output } = TERMINAL) {
749
+ async function ask(question, fallback, hint, { input, output } = TERMINAL) {
740
750
  if (!input.isTTY) return fallback;
741
- const hint = fallback ? styleText("dim", ` (default: ${fallback})`) : "";
751
+ const guide = hint ? styleText("dim", `, ${hint}`) : "";
752
+ const defaultMark = fallback ? styleText("dim", ` (default: ${fallback})`) : "";
742
753
  const readline = createInterface({
743
754
  input,
744
755
  output
745
756
  });
746
757
  try {
747
- return (await readline.question(`${styleText("bold", question)}${hint}: `)).trim() || fallback;
758
+ return (await readline.question(`${styleText("bold", question)}${guide}${defaultMark}: `)).trim() || fallback;
748
759
  } catch (error) {
749
760
  if (error.name === "AbortError") return;
750
761
  throw error;
@@ -755,9 +766,10 @@ async function ask(question, fallback, { input, output } = TERMINAL) {
755
766
  //#endregion
756
767
  //#region src/cli.ts
757
768
  const USAGE = `Usage:
758
- dotctl create [dir] [--name <name>] [--template <starter|showcase>]
759
- [--pm <npm|pnpm|yarn|bun>]
760
- Create a dot. Asks for its name and template:
769
+ dotctl create [dir] [--name <name>] [--package <com.mysalone.thing>]
770
+ [--template <starter|showcase>] [--pm <npm|pnpm|yarn|bun>]
771
+ Create a dot. Asks for its name, its package name
772
+ (a reverse domain name you own) and its template:
761
773
  starter is one page to build on, showcase has a
762
774
  page for each feature
763
775
  dotctl create page <path> Add a page to the dot you are in. Writes
@@ -792,6 +804,7 @@ async function main(argv) {
792
804
  port: { type: "string" },
793
805
  device: { type: "boolean" },
794
806
  name: { type: "string" },
807
+ package: { type: "string" },
795
808
  template: { type: "string" },
796
809
  pm: { type: "string" },
797
810
  help: {
@@ -809,7 +822,7 @@ async function main(argv) {
809
822
  }
810
823
  if (command === "keys" && !values.help) return keys(target);
811
824
  if (command === "create" && target === "page" && !values.help) return addPage(pagePath);
812
- if (command === "create" && !values.help) return createApp(target, values.name, values.template, values.pm);
825
+ if (command === "create" && !values.help) return createApp(target, values.name, values.package, values.template, values.pm);
813
826
  if (values.help || command !== "build" && command !== "dev" && command !== "preview") {
814
827
  console.log(USAGE);
815
828
  return values.help ? 0 : 1;
@@ -879,7 +892,7 @@ async function addPage(path) {
879
892
  else if (listing === "manual") console.log(`\nAdd it to the pages in dot.ts so it gets built: defineDot({ pages: [..., '${path}'] })`);
880
893
  return 0;
881
894
  }
882
- async function createApp(dir, name, templateFlag, packageManagerFlag) {
895
+ async function createApp(dir, name, dotPackageFlag, templateFlag, packageManagerFlag) {
883
896
  if (templateFlag !== void 0 && !isTemplate(templateFlag)) {
884
897
  console.error(`--template must be one of ${TEMPLATES.join(", ")}`);
885
898
  return 1;
@@ -893,10 +906,17 @@ async function createApp(dir, name, templateFlag, packageManagerFlag) {
893
906
  console.error("The dot needs a name. Pass one with: dotctl create [dir] --name \"My Dot\"");
894
907
  return 1;
895
908
  }
909
+ if (dotPackageFlag !== void 0 && !isDotPackage(dotPackageFlag)) {
910
+ console.error(`--package "${dotPackageFlag}" isn't a package name. ${DOT_PACKAGE_RULE}`);
911
+ return 1;
912
+ }
913
+ const dotPackage = dotPackageFlag ?? await askDotPackage(suggestedDotPackage(toPackageName(name)));
914
+ if (!dotPackage) return 1;
896
915
  const template = templateFlag ?? await chooseTemplate();
897
916
  const { root, workspace } = await create({
898
917
  name,
899
918
  dir: dir ?? toPackageName(name),
919
+ dotPackage,
900
920
  template
901
921
  });
902
922
  const path = relative(process.cwd(), root) || ".";
@@ -923,6 +943,13 @@ function run(command, args, cwd) {
923
943
  child.on("exit", (code) => resolvePromise(code ?? 1));
924
944
  });
925
945
  }
946
+ async function askDotPackage(suggested) {
947
+ while (true) {
948
+ const answer = await ask("Package name", suggested, "a reverse domain name you own");
949
+ if (answer === void 0 || isDotPackage(answer)) return answer;
950
+ console.error(`"${answer}" isn't a package name. ${DOT_PACKAGE_RULE}`);
951
+ }
952
+ }
926
953
  function chooseTemplate() {
927
954
  console.log();
928
955
  return select("Template?", TEMPLATES.map((template) => ({
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/report.ts","../src/create/template.ts","../src/create/create.ts","../src/dot-root.ts","../src/create/page.ts","../src/create/package-manager.ts","../src/device/developer-key.ts","../src/device/server.ts","../src/simulator.ts","../src/dev.ts","../src/preview.ts","../src/prompts.ts","../src/cli.ts"],"sourcesContent":["import { DmlParseError } from '@monimesl/dot-dml-parser';\nimport type { BuildResult } from '@monimesl/dot-compiler';\nimport { CompileError } from '@monimesl/dot-compiler';\nimport { ScriptParseError } from '@monimesl/dot-script-parser';\n\n// A mistake in how a command was used. Its message is enough, so no stack trace is shown.\nexport class CommandError extends Error {\n override readonly name = 'CommandError';\n}\n\nexport function describeError(error: unknown): string {\n const known =\n error instanceof CommandError ||\n error instanceof CompileError ||\n error instanceof DmlParseError ||\n error instanceof ScriptParseError;\n if (known) {\n return error.message;\n }\n if ((error as { code?: string }).code?.startsWith('ERR_PARSE_ARGS')) {\n // Keep only the first sentence. Node goes on to explain `--`, which doesn't help here.\n const [firstSentence] = (error as Error).message.split('. ');\n return `${firstSentence}. See dotctl --help.`;\n }\n return error instanceof Error ? (error.stack ?? error.message) : String(error);\n}\n\nexport function printWarnings({ warnings }: BuildResult): void {\n for (const warning of warnings) {\n console.warn(`warning: ${warning.message}`);\n }\n}\n\nexport function printBuildTime(startedAt: number): void {\n console.log(`Built in ${Math.round(performance.now() - startedAt)}ms`);\n}\n","export const TEMPLATES = ['starter', 'showcase'] as const;\n\nexport type Template = (typeof TEMPLATES)[number];\n\n// Shown next to each template when `dotctl create` asks which one to use.\nexport const TEMPLATE_HINTS: Record<Template, string> = {\n starter: 'one page to build on',\n showcase: 'a page for each feature, to learn from',\n};\n\nexport function isTemplate(name: string): name is Template {\n return (TEMPLATES as readonly string[]).includes(name);\n}\n\ninterface TemplateOptions {\n name: string;\n packageName: string;\n dotVersion: string;\n workspace: boolean;\n template: Template;\n}\n\nexport const PAGE_FILES: Readonly<Record<string, string>> = {\n 'view.dml': '<View>\\n\\t<Text>My new page</Text>\\n</View>\\n',\n 'state.ts': 'export default {};\\n',\n 'controller.ts': `import state from './state';\n\nexport default definePage({\n initialState: state,\n});\n`,\n};\n\n// The files `dotctl create` copies from the template's example. It generates the rest.\nexport const TEMPLATE_FILES = ['dot.ts', 'pages', 'statics', '.env'] as const;\n\n// Editors that follow `$schema` can check dot.json and complete its fields.\nconst DOT_JSON_SCHEMA = './node_modules/@monimesl/dot-runtime/dot.schema.json';\n\nconst json = (value: unknown) => `${JSON.stringify(value, null, 2)}\\n`;\n\nexport function projectFiles(options: TemplateOptions): Record<string, string> {\n return {\n 'package.json': packageJson(options),\n 'tsconfig.json': tsconfig(options),\n '.gitignore': 'node_modules/\\ndist/\\n# Types generated by dotctl\\n.dot/\\n',\n 'dot.json': dotJson(options),\n 'README.md': readme(options),\n };\n}\n\nfunction packageJson({ packageName, dotVersion, workspace }: TemplateOptions): string {\n const conditions = workspace ? ' --conditions @monimesl/dot-source' : '';\n return json({\n name: packageName,\n private: true,\n type: 'module',\n scripts: {\n dev: `dotctl dev${conditions}`,\n build: `dotctl build${conditions}`,\n preview: `dotctl preview${conditions}`,\n typecheck: 'tsc --noEmit',\n },\n dependencies: { '@monimesl/dot-runtime': dotVersion },\n devDependencies: { '@monimesl/dot-cli': dotVersion, typescript: '^5.9.3' },\n });\n}\n\nfunction tsconfig({ workspace }: TemplateOptions): string {\n const compilerOptions: Record<string, unknown> = {\n target: 'ES2022',\n module: 'ESNext',\n moduleResolution: 'bundler',\n lib: ['ES2022'],\n types: ['@monimesl/dot-runtime/globals'],\n strict: true,\n noEmit: true,\n skipLibCheck: true,\n verbatimModuleSyntax: true,\n };\n if (workspace) {\n compilerOptions.customConditions = ['@monimesl/dot-source'];\n }\n return json({\n compilerOptions,\n include: ['dot.ts', 'pages', '.dot/pages.d.ts', '.dot/env.d.ts'],\n });\n}\n\n// Stands in for the ID Monime gives the dot when it's registered.\nexport const UNREGISTERED_DOT_ID = '<monime-Internal-Id>';\n\nfunction dotJson({ name, packageName, template }: TemplateOptions): string {\n const dot: Record<string, unknown> = {\n id: UNREGISTERED_DOT_ID,\n $schema: DOT_JSON_SCHEMA,\n package: `com.example.${hostWord(packageName)}`,\n name,\n version: '0.1.0',\n };\n if (template === 'showcase') {\n // The showcase's network page sends requests to these hosts.\n dot.network = { hosts: ['itunes.apple.com', '*.mzstatic.com', 'httpbin.org'] };\n }\n return json(dot);\n}\n\nconst TEMPLATE_READMES: Readonly<Record<Template, string>> = {\n starter: `It starts with one page, \\`index\\`. It shows a greeting from \\`.env\\` and a counter you\ncan tap. Change it, and add pages as the dot grows. To see how each feature works, make a showcase\ndot with \\`npx dotctl create my-tour --template showcase\\`.`,\n showcase: `It starts as a showcase. The \\`index\\` page links to a page for each feature: layout,\ntext, images and inputs, then state, navigation, and Dash's alerts, network and files. Read a page\nto see how it works. Then delete the pages you don't need, both from \\`pages/\\` and from\n\\`dot.ts\\`.`,\n};\n\nfunction readme({ name, template }: TemplateOptions): string {\n return `# ${name}\n\nA dot: a mini app that runs inside Dash.\n\n\\`\\`\\`sh\nnpm install\nnpm run dev # run it in the simulator; it reloads on every change\nnpm run build # build it for Dash\n\\`\\`\\`\n\n${TEMPLATE_READMES[template]}\n\n## Pages\n\nEach page is a folder under \\`pages/\\` with three files:\n\n- \\`view.dml\\`: what the page shows. It has one root \\`<View>\\`, with everything else inside it.\n- \\`state.ts\\`: the data the page starts with.\n- \\`controller.ts\\`: how the data changes.\n\n\\`npx dotctl create page <path>\\` adds a page, such as \\`cart/checkout\\`, with these three files. It\nalso lists the page in \\`dot.ts\\`. The dot opens on the first page in that list.\n\n## dot.json\n\n\\`dot.json\\` describes the dot to Dash. Before you publish, change its \\`package\\` from\n\\`com.example.…\\` to a reverse domain name you own. You can't change it after publishing. To\npublish, register the dot with Monime first, and replace its \\`id\\`, \\`<monime-Internal-Id>\\`, with\nthe ID Monime gives you.\n\\`network.hosts\\` lists every host the dot sends requests to with \\`dash.network\\`. Requests to any\nother host fail. Your editor can complete the other fields from the schema the file points to.\n\n## .env\n\n\\`.env\\` holds public configuration. Code in the dot reads each variable from \\`env\\`, such as\n\\`env.GREETING\\`. Every variable is built into the dot, so anyone who has the dot can read it.\nNever put a secret there. \\`.env.development\\` and \\`.env.production\\` override it for \\`dev\\` and\n\\`build\\`.\n`;\n}\n\n// The npm package name is already lowercase letters, digits and inner hyphens. A part of a host name\n// can be at most 63 characters, so cut it there.\nfunction hostWord(packageName: string): string {\n return packageName.slice(0, 63).replace(/-+$/, '');\n}\n","import { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { cp, mkdir, writeFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { basename, dirname, join, resolve } from 'node:path';\n\nimport { CommandError } from '../report';\nimport type { Template } from './template';\nimport { projectFiles, TEMPLATE_FILES } from './template';\n\ninterface CreateOptions {\n name: string;\n dir: string;\n template?: Template;\n}\n\ninterface CreateResult {\n readonly root: string;\n readonly workspace: boolean;\n}\n\ninterface PackageJson {\n name: string;\n version: string;\n}\n\nexport async function create({\n name,\n dir,\n template = 'starter',\n}: CreateOptions): Promise<CreateResult> {\n const root = resolve(dir);\n if (existsSync(root) && readdirSync(root).length > 0) {\n throw new CommandError(`${dir} already exists and is not empty. Choose another folder`);\n }\n const packageName = toPackageName(name);\n if (!packageName) {\n throw new CommandError(`The name \"${name}\" needs at least one letter or digit`);\n }\n\n const workspace = isInDotWorkspace(root);\n const source = templateDir(template);\n await mkdir(root, { recursive: true });\n for (const path of TEMPLATE_FILES) {\n await cp(join(source, path), join(root, path), { recursive: true });\n }\n const generated = projectFiles({\n name,\n packageName,\n dotVersion: workspace ? 'workspace:*' : `^${cliVersion()}`,\n workspace,\n template,\n });\n for (const [path, content] of Object.entries(generated)) {\n await writeFile(join(root, path), content);\n }\n return { root, workspace };\n}\n\nexport function toPackageName(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\nexport function toDisplayName(dir: string): string {\n return basename(resolve(dir))\n .split(/[-_\\s]+/)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\n// Whether the nearest pnpm workspace above `dir` is the Dot repository.\nexport function isInDotWorkspace(dir: string): boolean {\n let current = dirname(dir);\n while (true) {\n if (existsSync(join(current, 'pnpm-workspace.yaml'))) {\n const runtimeManifest = join(current, 'packages/runtime/package.json');\n if (!existsSync(runtimeManifest)) {\n return false;\n }\n return readPackageJson(runtimeManifest).name === '@monimesl/dot-runtime';\n }\n const parent = dirname(current);\n if (parent === current) {\n return false;\n }\n current = parent;\n }\n}\n\n// Inside the repository, templates come from examples/. The published CLI ships its own copy.\nfunction templateDir(template: Template): string {\n const example = join(cliRoot(), '../../examples', template);\n return existsSync(example) ? example : join(cliRoot(), 'dist/templates', template);\n}\n\nfunction cliVersion(): string {\n return readPackageJson(join(cliRoot(), 'package.json')).version;\n}\n\nfunction readPackageJson(path: string): PackageJson {\n return JSON.parse(readFileSync(path, 'utf8')) as PackageJson;\n}\n\nfunction cliRoot(): string {\n return dirname(createRequire(import.meta.url).resolve('@monimesl/dot-cli/package.json'));\n}\n","import { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\n// The nearest folder with a dot.json, starting at `dir` and walking up.\nexport function findDotRoot(dir: string): string | undefined {\n while (true) {\n if (existsSync(join(dir, 'dot.json'))) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n return undefined;\n }\n dir = parent;\n }\n}\n","import { existsSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\n\nimport { isPageId } from '@monimesl/dot-core';\nimport type { DotModuleIR } from '@monimesl/dot-script-parser';\nimport { parseDotModule } from '@monimesl/dot-script-parser';\n\nimport { findDotRoot } from '../dot-root';\nimport { CommandError } from '../report';\nimport { PAGE_FILES } from './template';\n\ninterface CreatePageResult {\n readonly dir: string;\n readonly files: readonly string[];\n // Whether the page is now in `defineDot({ pages })` in dot.ts. `manual` means dot.ts couldn't be\n // updated, so the developer must add the page by hand.\n readonly listing: 'added' | 'already' | 'manual';\n}\n\n// Adds a page folder under pages/ in the dot that contains `cwd`, and lists it last in dot.ts.\nexport async function createPage(path: string, cwd: string): Promise<CreatePageResult> {\n if (!isPageId(path)) {\n throw new CommandError(\n `\"${path}\" is not a page path. Use letters, digits, \"-\" and \"_\", with \"/\" between folders, such as \"cart/checkout\"`,\n );\n }\n const root = findDotRoot(resolve(cwd));\n if (root === undefined) {\n throw new CommandError(\n 'There is no dot.json in this folder or any folder above it. Run this command inside a dot',\n );\n }\n const dir = join(root, 'pages', path);\n if (existsSync(dir)) {\n throw new CommandError(`pages/${path} already exists. Choose another path`);\n }\n await mkdir(dir, { recursive: true });\n const files: string[] = [];\n for (const [name, content] of Object.entries(PAGE_FILES)) {\n const file = join(dir, name);\n await writeFile(file, content);\n files.push(file);\n }\n return { dir, files, listing: await listPage(join(root, 'dot.ts'), path) };\n}\n\nasync function listPage(dotFile: string, path: string): Promise<CreatePageResult['listing']> {\n if (!existsSync(dotFile)) {\n return 'manual';\n }\n const source = await readFile(dotFile, 'utf8');\n let dot: DotModuleIR;\n try {\n dot = parseDotModule(source, dotFile);\n } catch {\n return 'manual';\n }\n if (dot.pages.includes(path)) {\n return 'already';\n }\n const updated = withPage(source, dot.lastPage, path);\n if (!listsPage(updated, dotFile, path)) {\n return 'manual';\n }\n await writeFile(dotFile, updated);\n return 'added';\n}\n\n// Inserts `path` after the last page, matching the list's style: same quotes, and on its own line\n// if each page has one.\nfunction withPage(source: string, lastPage: DotModuleIR['lastPage'], path: string): string {\n const quote = source[lastPage.start] === '\"' ? '\"' : \"'\";\n const page = `${quote}${path}${quote}`;\n const lineStart = source.lastIndexOf('\\n', lastPage.start - 1) + 1;\n const indent = source.slice(lineStart, lastPage.start);\n if (!/^\\s*$/.test(indent)) {\n return `${source.slice(0, lastPage.end)}, ${page}${source.slice(lastPage.end)}`;\n }\n const trailingComma = /^\\s*,/.exec(source.slice(lastPage.end));\n if (trailingComma) {\n const end = lastPage.end + trailingComma[0].length;\n return `${source.slice(0, end)}\\n${indent}${page},${source.slice(end)}`;\n }\n return `${source.slice(0, lastPage.end)},\\n${indent}${page}${source.slice(lastPage.end)}`;\n}\n\nfunction listsPage(source: string, dotFile: string, path: string): boolean {\n try {\n return parseDotModule(source, dotFile).pages.includes(path);\n } catch {\n return false;\n }\n}\n","import { spawnSync } from 'node:child_process';\n\nexport const PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'] as const;\nexport type PackageManager = (typeof PACKAGE_MANAGERS)[number];\n\nexport function isPackageManager(name: string): name is PackageManager {\n return (PACKAGE_MANAGERS as readonly string[]).includes(name);\n}\n\nexport function launchedBy(userAgent = process.env.npm_config_user_agent ?? ''): PackageManager {\n return PACKAGE_MANAGERS.find((name) => userAgent.startsWith(`${name}/`)) ?? 'npm';\n}\n\nexport function installedManagers(\n isInstalled: (name: PackageManager) => boolean = onPath,\n): PackageManager[] {\n return PACKAGE_MANAGERS.filter(isInstalled);\n}\n\nfunction onPath(name: PackageManager): boolean {\n return (\n spawnSync(name, ['--version'], { stdio: 'ignore', shell: process.platform === 'win32' })\n .status === 0\n );\n}\n","import { createPrivateKey, createPublicKey, sign } from 'node:crypto';\nimport type { KeyObject } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\n// The Ed25519 key this computer signs dotdev links with. The developer adds its public key to their\n// Monime account. Before Dash opens a link, Monime checks its signature.\nexport interface DeveloperKey {\n // The raw 32-byte public key in base64. This is what the developer adds to their account.\n readonly publicKey: string;\n // The first 8 bytes of the public key in lowercase hex. A link names its key by this ID.\n readonly keyId: string;\n // Returns the signature in base64.\n sign(bytes: Buffer): string;\n}\n\nexport function defaultDeveloperKeyPath(): string {\n return process.env.DOT_DEVELOPER_KEY ?? join(homedir(), '.dot', 'developer-key.pem');\n}\n\n// Explains how to make a key. dotctl reads the key but never creates it.\nexport function missingDeveloperKeyHint(path = defaultDeveloperKeyPath()): string {\n return (\n ` There is no developer key at ${path}. Dash only opens signed links.\\n` +\n \" Make one with OpenSSL 3. macOS's built-in openssl can't, so first run brew install openssl:\\n\\n\" +\n ` mkdir -p ${dirname(path)} && openssl genpkey -algorithm ed25519 -out ${path}\\n\\n` +\n ' Then run `dotctl keys show` and add the public key it prints to your Monime account.'\n );\n}\n\n// Returns undefined when there is no key file yet.\nexport async function loadDeveloperKey(\n path = defaultDeveloperKeyPath(),\n): Promise<DeveloperKey | undefined> {\n let pem: string;\n try {\n pem = await readFile(path, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return undefined;\n }\n throw error;\n }\n const privateKey = createPrivateKey(pem);\n if (privateKey.asymmetricKeyType !== 'ed25519') {\n throw new Error(\n `${path} is not an Ed25519 key. Make one with: openssl genpkey -algorithm ed25519 -out ${path}`,\n );\n }\n return toDeveloperKey(privateKey);\n}\n\nfunction toDeveloperKey(privateKey: KeyObject): DeveloperKey {\n const raw = Buffer.from(createPublicKey(privateKey).export({ format: 'jwk' }).x!, 'base64url');\n return {\n publicKey: raw.toString('base64'),\n keyId: raw.subarray(0, 8).toString('hex'),\n sign: (bytes) => sign(null, bytes, privateKey).toString('base64'),\n };\n}\n","import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';\nimport { readdir, readFile } from 'node:fs/promises';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { createServer } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { networkInterfaces } from 'node:os';\nimport { join, relative, sep } from 'node:path';\n\nimport type { DeveloperKey } from './developer-key';\n\ninterface DeviceServerOptions {\n // The address devices reach the server on. Defaults to the computer's local network address.\n address?: string | undefined;\n // Defaults to any free port. The link tells devices which one.\n port?: number | undefined;\n // Called for each line a device sends, such as its dot's console output.\n onLog?: (line: string, from: string) => void;\n onConnect?: (from: string) => void;\n // Signs the link. Hosts such as Dash only open links signed by a known developer.\n developerKey?: DeveloperKey | undefined;\n}\n\n// Serves a dot's builds to the example host apps on phones, which reload on each new build.\nexport interface DeviceServer {\n // The link a device opens: `dotdev://connect?server=…&secret=…`. It is kept short so the QR\n // code stays small. A signed link's signature is served at `GET /dev/link` instead.\n readonly link: string;\n // Serves the build output in `dir` as the newest build. A `hot` build changed only pages, so\n // devices can reload it in place and keep each open page's state.\n publish(dir: string, options?: { hot?: boolean }): Promise<void>;\n // Tells devices the newest build failed. They keep running the last good one.\n fail(message: string): void;\n close(): Promise<void>;\n}\n\ninterface Build {\n readonly files: ReadonlyMap<string, { readonly bytes: Buffer; readonly hash: string }>;\n}\n\nconst DEVICE_SCHEME = 'dotdev';\n\n// A waiting request ends after this and the device asks again, before the network drops the idle\n// connection.\nconst WAIT_MS = 25_000;\nconst MAX_LOG_BYTES = 1024 * 1024;\n// How long a signed link works. A photo of an old QR code soon becomes useless.\nconst LINK_LIFETIME_SECONDS = 12 * 60 * 60;\n\nexport const BUILD_MAC_HEADER = 'dot-build-mac';\n\n// The text a signed link's `sig` signs, one field per line.\nexport function signedLinkMessage(\n server: string,\n secret: string,\n expires: number,\n keyId: string,\n): string {\n return ['dotdev-connect', server, secret, String(expires), keyId].join('\\n');\n}\n\n// The bearer token a device sends. It is derived from the link's secret, so the link doesn't need\n// to carry it. Someone who sees the token on the network learns nothing about the secret.\nexport function deviceToken(secret: string): string {\n return createHmac('sha256', Buffer.from(secret, 'base64url'))\n .update('dotdev-token', 'utf8')\n .digest('base64url');\n}\n\n// A signed link's signature, served at `GET /dev/link`. It isn't secret, so it stays out of the QR\n// code.\nexport interface LinkSignature {\n readonly expires: number;\n readonly keyId: string;\n // Base64\n readonly sig: string;\n}\n\n// The HMAC-SHA256 of a build listing, keyed with the link's secret. A device only accepts listings\n// from someone who has the link, and each file must match its hash in the listing.\nexport function buildMac(secret: string, body: string): string {\n return createHmac('sha256', Buffer.from(secret, 'base64url'))\n .update(`dotdev-build\\n${body}`, 'utf8')\n .digest('base64url');\n}\n\nexport async function startDeviceServer({\n address = localNetworkAddress(),\n port = 0,\n onLog = () => {},\n onConnect = () => {},\n developerKey,\n}: DeviceServerOptions = {}): Promise<DeviceServer> {\n if (address === undefined) {\n throw new Error(\n \"This computer has no local network address, so devices can't reach it. Connect to a network and try again\",\n );\n }\n // The secret never goes over the network. Only the link carries it.\n const secret = randomBytes(16).toString('base64url');\n const token = deviceToken(secret);\n let signature: LinkSignature | null = null;\n let build: Build = { files: new Map() };\n let generation = 0;\n // The oldest generation from which only hot builds lead to the newest. A device on this\n // generation or a later one can hot reload to the newest build.\n let hotSince = 0;\n let error: string | null = null;\n const waiting = new Set<() => void>();\n const connected = new Set<string>();\n\n const announce = () => {\n generation += 1;\n for (const wake of waiting) {\n wake();\n }\n };\n\n const server = createServer((request, response) => {\n const from = request.socket.remoteAddress ?? 'a device';\n // Served without the token check: a device needs the signature to check the link before using\n // it, and the signature isn't secret.\n if (request.method === 'GET' && request.url === '/dev/link') {\n if (signature) {\n sendJson(response, signature);\n } else {\n response.writeHead(404).end();\n }\n return;\n }\n if (!isAuthorized(request, token)) {\n response.writeHead(401).end();\n return;\n }\n const url = new URL(request.url ?? '/', 'http://device');\n const path = url.pathname;\n if (request.method === 'GET' && path === '/dev/build') {\n if (!connected.has(from)) {\n connected.add(from);\n onConnect(from);\n }\n const files = Object.fromEntries([...build.files].map(([name, file]) => [name, file.hash]));\n const body = JSON.stringify({ generation, hotSince, files, error });\n response\n .writeHead(200, {\n 'content-type': 'application/json',\n [BUILD_MAC_HEADER]: buildMac(secret, body),\n })\n .end(body);\n } else if (request.method === 'GET' && path.startsWith('/dev/files/')) {\n if (url.searchParams.get('generation') !== String(generation)) {\n response.writeHead(409).end();\n return;\n }\n const name = path.slice('/dev/files/'.length).split('/').map(decodeURIComponent).join('/');\n const file = build.files.get(name);\n if (!file) {\n response.writeHead(404).end();\n return;\n }\n response.writeHead(200, { 'content-type': 'application/octet-stream' }).end(file.bytes);\n } else if (request.method === 'GET' && path === '/dev/changes') {\n waitForChange(response, Number(url.searchParams.get('after')));\n } else if (request.method === 'POST' && path === '/dev/log') {\n readText(request)\n .then((text) => {\n for (const line of text.split('\\n')) {\n onLog(line, from);\n }\n response.writeHead(204).end();\n })\n .catch(() => response.writeHead(413).end());\n } else {\n response.writeHead(404).end();\n }\n });\n\n const waitForChange = (response: ServerResponse, after: number) => {\n if (generation > after) {\n sendJson(response, { generation });\n return;\n }\n const wake = () => {\n clearTimeout(timer);\n waiting.delete(wake);\n sendJson(response, { generation });\n };\n const timer = setTimeout(() => {\n waiting.delete(wake);\n response.writeHead(204).end();\n }, WAIT_MS);\n waiting.add(wake);\n response.on('close', () => {\n clearTimeout(timer);\n waiting.delete(wake);\n });\n };\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(port, address, () => resolve());\n });\n const { port: listening } = server.address() as AddressInfo;\n const origin = `http://${address}:${listening}`;\n if (developerKey) {\n const expires = Math.floor(Date.now() / 1000) + LINK_LIFETIME_SECONDS;\n const keyId = developerKey.keyId;\n const message = signedLinkMessage(origin, secret, expires, keyId);\n signature = { expires, keyId, sig: developerKey.sign(Buffer.from(message, 'utf8')) };\n }\n\n return {\n link: `${DEVICE_SCHEME}://connect?server=${encodeURIComponent(origin)}&secret=${secret}`,\n async publish(dir, { hot = false } = {}) {\n build = await readBuild(dir);\n error = null;\n announce();\n if (!hot) {\n hotSince = generation;\n }\n },\n fail(message) {\n error = message;\n announce();\n },\n close() {\n for (const wake of waiting) {\n wake();\n }\n return new Promise((resolve) => {\n server.close(() => resolve());\n server.closeAllConnections();\n });\n },\n };\n}\n\n// The first IPv4 address that isn't a loopback address.\nfunction localNetworkAddress(): string | undefined {\n for (const addresses of Object.values(networkInterfaces())) {\n for (const candidate of addresses ?? []) {\n if (candidate.family === 'IPv4' && !candidate.internal) {\n return candidate.address;\n }\n }\n }\n return undefined;\n}\n\n// Reads the build output into memory, so a device downloading it never gets files from two builds.\nasync function readBuild(dir: string): Promise<Build> {\n const files = new Map<string, { bytes: Buffer; hash: string }>();\n const entries = await readdir(dir, { recursive: true, withFileTypes: true });\n for (const entry of entries) {\n // Devices don't need source maps or the archives `dotctl build` writes.\n if (!entry.isFile() || entry.name.endsWith('.map') || entry.name.endsWith('.zip')) {\n continue;\n }\n const path = join(entry.parentPath, entry.name);\n const bytes = await readFile(path);\n const hash = createHash('sha256').update(bytes).digest('base64url');\n files.set(relative(dir, path).split(sep).join('/'), { bytes, hash });\n }\n return { files };\n}\n\nfunction isAuthorized(request: IncomingMessage, token: string): boolean {\n const given = Buffer.from(request.headers.authorization ?? '');\n const expected = Buffer.from(`Bearer ${token}`);\n return given.length === expected.length && timingSafeEqual(given, expected);\n}\n\nfunction sendJson(response: ServerResponse, body: unknown): void {\n response.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(body));\n}\n\nasync function readText(request: IncomingMessage): Promise<string> {\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of request) {\n size += (chunk as Buffer).length;\n if (size > MAX_LOG_BYTES) {\n throw new Error(`A device sent more than ${MAX_LOG_BYTES} bytes of log at once`);\n }\n chunks.push(chunk as Buffer);\n }\n return Buffer.concat(chunks).toString('utf8');\n}\n","import { join } from 'node:path';\n\nimport type { Project } from '@monimesl/dot-compiler';\nimport { installedRuntime } from '@monimesl/dot-compiler';\nimport type { Simulator } from '@monimesl/dot-simulator';\nimport { startSimulator } from '@monimesl/dot-simulator';\n\n// `dev` and `preview` share .dot/files, so a preview sees the files the dot saved during `dev`.\nexport function startDotSimulator(\n project: Project,\n outDir: string,\n port: number | undefined,\n): Promise<Simulator> {\n return startSimulator({\n dotDir: outDir,\n reactHostDir: installedRuntime(project.root).react.dir,\n name: project.dotJson.name,\n port,\n storageDir: join(project.root, '.dot', 'files'),\n });\n}\n","import { watch } from 'node:fs';\nimport { join, relative, resolve, sep } from 'node:path';\n\nimport type { BuildOptions } from '@monimesl/dot-compiler';\nimport { build, loadProject, PAGES_DIR } from '@monimesl/dot-compiler';\nimport { renderUnicodeCompact } from 'uqr';\n\nimport { loadDeveloperKey, missingDeveloperKeyHint } from './device/developer-key';\nimport type { DeviceServer } from './device/server';\nimport { startDeviceServer } from './device/server';\nimport { describeError, printBuildTime, printWarnings } from './report';\nimport { startDotSimulator } from './simulator';\n\ninterface DevOptions extends BuildOptions {\n port?: number | undefined;\n // Also serve the dot to the example host apps on phones, through a link and a QR code.\n device?: boolean | undefined;\n}\n\n// Editors often save a file in several writes. Waiting this long turns them into one rebuild.\nconst CHANGE_DEBOUNCE_MS = 50;\n\nexport async function dev({ port, device, ...options }: DevOptions): Promise<() => Promise<void>> {\n const project = await loadProject(options.root);\n const outDir = resolve(project.root, options.outDir ?? 'dist');\n const buildOptions: BuildOptions = { mode: 'development', ...options, outDir };\n\n // Returns the error to show, with paths relative to the project, or null if the build worked.\n const buildApp = async (): Promise<string | null> => {\n const started = performance.now();\n try {\n printWarnings(await build(buildOptions));\n printBuildTime(started);\n return null;\n } catch (error) {\n const message = describeError(error).replaceAll(project.root + sep, '');\n console.error(`error: ${message}`);\n return message;\n }\n };\n\n const firstError = await buildApp();\n const simulator = await startDotSimulator(project, outDir, port);\n if (firstError) {\n simulator.error(firstError);\n }\n console.log(`\\n Simulator running at: ${simulator.url}\\n`);\n\n let devices: DeviceServer | undefined;\n if (device) {\n const developerKey = await loadDeveloperKey();\n devices = await startDeviceServer({\n developerKey,\n onConnect: (from) => console.log(`A device connected from ${from}`),\n onLog: (line) => console.log(`device ${line}`),\n });\n if (firstError) {\n devices.fail(firstError);\n } else {\n await devices.publish(outDir);\n }\n printDeviceLink(devices.link);\n if (developerKey) {\n console.log(` Signed with developer key ${developerKey.keyId}\\n`);\n } else {\n console.log(`${missingDeveloperKeyHint()}\\n`);\n }\n }\n\n // Files changed since the last good build. They decide whether the next build can hot reload.\n const changed = new Set<string>();\n const pagesDir = join(project.root, PAGES_DIR) + sep;\n const rebuilds = new RebuildQueue(async () => {\n // Files that change during this build stay in `changed` for the next one.\n const building = [...changed];\n const error = await buildApp();\n if (error) {\n simulator.error(error);\n devices?.fail(error);\n return;\n }\n for (const path of building) {\n changed.delete(path);\n }\n // Page changes hot reload in place. Any other change, such as dot.ts, .env or statics/,\n // restarts the dot on the pages it had open.\n const hot = building.every((path) => path.startsWith(pagesDir));\n if (hot) {\n simulator.hotReload();\n } else {\n simulator.reload();\n }\n await devices?.publish(outDir, { hot });\n });\n\n let debounceTimer: NodeJS.Timeout | undefined;\n const watcher = watch(project.root, { recursive: true }, (_event, file) => {\n if (!file || isIgnored(file)) {\n return;\n }\n const path = resolve(project.root, file);\n if (path === outDir || path.startsWith(outDir + sep)) {\n return;\n }\n changed.add(path);\n clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n console.log(`Changed ${relative(project.root, path)}`);\n rebuilds.request();\n }, CHANGE_DEBOUNCE_MS);\n });\n\n return async () => {\n clearTimeout(debounceTimer);\n watcher.close();\n await Promise.all([simulator.close(), devices?.close()]);\n };\n}\n\nfunction printDeviceLink(link: string): void {\n console.log(\n ' To open it in Dash, turn on Enable Dot Debugging in Developer Settings.\\n' +\n ' Then tap Live Test on the Dots tab and scan this code.\\n',\n );\n console.log(renderUnicodeCompact(link, { border: 1 }).replace(/^/gm, ' '));\n console.log(`\\n ${link}\\n`);\n}\n\n// Runs one rebuild at a time. Changes during a build lead to exactly one more build after it.\nclass RebuildQueue {\n readonly #rebuild: () => Promise<void>;\n #building = false;\n #changedWhileBuilding = false;\n\n constructor(rebuild: () => Promise<void>) {\n this.#rebuild = rebuild;\n }\n\n request(): void {\n if (this.#building) {\n this.#changedWhileBuilding = true;\n return;\n }\n void this.#build();\n }\n\n async #build(): Promise<void> {\n this.#building = true;\n this.#changedWhileBuilding = false;\n try {\n await this.#rebuild();\n } finally {\n this.#building = false;\n }\n if (this.#changedWhileBuilding) {\n this.request();\n }\n }\n}\n\n// Skips node_modules and hidden folders such as .git or .dot, but not .env files: the build reads\n// them.\nfunction isIgnored(file: string): boolean {\n return file.split(/[\\\\/]/).some((segment) => {\n if (segment === '.env' || segment.startsWith('.env.')) {\n return false;\n }\n return segment === 'node_modules' || segment.startsWith('.');\n });\n}\n","import { resolve } from 'node:path';\n\nimport type { BuildOptions } from '@monimesl/dot-compiler';\nimport { build, loadProject } from '@monimesl/dot-compiler';\nimport type { Simulator } from '@monimesl/dot-simulator';\n\nimport { printBuildTime, printWarnings } from './report';\nimport { startDotSimulator } from './simulator';\n\ninterface PreviewOptions extends BuildOptions {\n port?: number | undefined;\n}\n\n// Runs the `dotctl build` output in the simulator: minified, with only the components its pages\n// use. It doesn't rebuild on change, because a new component in a page wouldn't be in the build.\nexport async function preview({ port, ...options }: PreviewOptions): Promise<Simulator> {\n const project = await loadProject(options.root);\n const outDir = resolve(project.root, options.outDir ?? 'dist');\n const started = performance.now();\n printWarnings(await build({ ...options, outDir, mode: 'production' }));\n printBuildTime(started);\n const simulator = await startDotSimulator(project, outDir, port);\n console.log(`\\n Simulator running at: ${simulator.url}\\n`);\n return simulator;\n}\n","import { emitKeypressEvents } from 'node:readline';\nimport { createInterface } from 'node:readline/promises';\nimport { styleText } from 'node:util';\n\nexport interface Choice<Value extends string> {\n value: Value;\n // Shown dimmed after the value, such as a short description of a template.\n hint?: string;\n}\n\ninterface Terminal {\n input: NodeJS.ReadStream;\n output: NodeJS.WriteStream;\n}\n\nconst TERMINAL: Terminal = { input: process.stdin, output: process.stdout };\n\nconst ERASE_BELOW = '\\x1b[0J';\nconst HIDE_CURSOR = '\\x1b[?25l';\nconst SHOW_CURSOR = '\\x1b[?25h';\nconst cursorUp = (lines: number) => `\\x1b[${lines}A`;\n\n// Lets the person pick one of `choices` with the arrow keys and Enter. The list starts on\n// `fallback`, marked as the default. Without a terminal, it returns `fallback` without asking.\nexport async function select<Value extends string>(\n question: string,\n choices: readonly Choice<Value>[],\n fallback: Value,\n { input, output }: Terminal = TERMINAL,\n): Promise<Value> {\n if (!input.isTTY) {\n return fallback;\n }\n let current = Math.max(\n 0,\n choices.findIndex((choice) => choice.value === fallback),\n );\n // Pad the values so their hints line up.\n const width = choices.some((choice) => choice.hint)\n ? Math.max(...choices.map((choice) => choice.value.length))\n : 0;\n const row = (choice: Choice<Value>, index: number) => {\n const name = choice.value.padEnd(width);\n const hint = choice.hint ? styleText('dim', ` ${choice.hint}`) : '';\n const defaultMark = choice.value === fallback ? styleText('green', ' (default)') : '';\n if (index === current) {\n return `${styleText('cyan', '❯')} ${styleText(['cyan', 'bold'], name)}${hint}${defaultMark}`;\n }\n return ` ${name}${hint}${defaultMark}`;\n };\n\n // Each draw replaces the one before. First the question and its list, then the question and the\n // answer.\n let drawnLines = 0;\n const draw = (lines: string[]) => {\n output.write(`${drawnLines ? cursorUp(drawnLines) : ''}${ERASE_BELOW}${lines.join('\\n')}\\n`);\n drawnLines = lines.length;\n };\n const drawList = () =>\n draw([\n `${styleText('bold', question)} ${styleText('dim', '↑/↓ to move, Enter to choose')}`,\n ...choices.map(row),\n ]);\n\n emitKeypressEvents(input);\n input.setRawMode(true);\n input.resume();\n output.write(HIDE_CURSOR);\n drawList();\n return new Promise((resolve) => {\n const onKeypress = (_text: string, key: { name?: string; ctrl?: boolean }) => {\n if (key.ctrl && key.name === 'c') {\n output.write(`${SHOW_CURSOR}\\n`);\n process.exit(130);\n }\n if (key.name === 'up' || key.name === 'k') {\n current = (current - 1 + choices.length) % choices.length;\n drawList();\n } else if (key.name === 'down' || key.name === 'j') {\n current = (current + 1) % choices.length;\n drawList();\n } else if (key.name === 'return' || key.name === 'enter') {\n const { value } = choices[current]!;\n input.off('keypress', onKeypress);\n input.setRawMode(false);\n input.pause();\n draw([`${styleText('bold', question)} ${styleText('cyan', value)}`]);\n output.write(SHOW_CURSOR);\n resolve(value);\n }\n };\n input.on('keypress', onKeypress);\n });\n}\n\n// A yes or no question, shown as a list. Without a terminal, the answer is always no.\nexport async function confirm(\n question: string,\n fallback: boolean,\n terminal: Terminal = TERMINAL,\n): Promise<boolean> {\n if (!terminal.input.isTTY) {\n return false;\n }\n const choices = [{ value: 'Yes' }, { value: 'No' }] as const;\n return (await select(question, choices, fallback ? 'Yes' : 'No', terminal)) === 'Yes';\n}\n\n// Asks for a typed answer. An empty answer, or no terminal, gives `fallback`, shown as the default.\nexport async function ask(\n question: string,\n fallback?: string,\n { input, output }: Terminal = TERMINAL,\n): Promise<string | undefined> {\n if (!input.isTTY) {\n return fallback;\n }\n const hint = fallback ? styleText('dim', ` (default: ${fallback})`) : '';\n const readline = createInterface({ input, output });\n try {\n const answer = await readline.question(`${styleText('bold', question)}${hint}: `);\n return answer.trim() || fallback;\n } catch (error) {\n // The person pressed Ctrl+C or Ctrl+D.\n if ((error as Error).name === 'AbortError') {\n return undefined;\n }\n throw error;\n } finally {\n readline.close();\n }\n}\n","import { spawn } from 'node:child_process';\nimport { relative } from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport type { BuildOptions } from '@monimesl/dot-compiler';\nimport { build } from '@monimesl/dot-compiler';\nimport { DEFAULT_PORT } from '@monimesl/dot-simulator';\n\nimport { create, toDisplayName, toPackageName } from './create/create';\nimport { createPage } from './create/page';\nimport type { PackageManager } from './create/package-manager';\nimport type { Template } from './create/template';\nimport { isTemplate, TEMPLATE_HINTS, TEMPLATES } from './create/template';\nimport {\n installedManagers,\n isPackageManager,\n launchedBy,\n PACKAGE_MANAGERS,\n} from './create/package-manager';\nimport { dev } from './dev';\nimport { preview } from './preview';\nimport { ask, confirm, select } from './prompts';\nimport {\n defaultDeveloperKeyPath,\n loadDeveloperKey,\n missingDeveloperKeyHint,\n} from './device/developer-key';\nimport { describeError, printBuildTime, printWarnings } from './report';\n\nconst USAGE = `Usage:\n dotctl create [dir] [--name <name>] [--template <starter|showcase>]\n [--pm <npm|pnpm|yarn|bun>]\n Create a dot. Asks for its name and template:\n starter is one page to build on, showcase has a\n page for each feature\n dotctl create page <path> Add a page to the dot you are in. Writes\n pages/<path>/ with view.dml, state.ts and controller.ts\n dotctl build [root] [options] Build the minified bundles the host app runs\n dotctl dev [root] [options] Run the dot in the browser simulator and rebuild on\n every change\n dotctl preview [root] [options] Build as \\`build\\` does, then run that build in the\n browser simulator, exactly as the host app gets it\n dotctl keys show Show this computer's developer key: its public key\n and key ID. The key signs the links that\n \\`dev --device\\` shows, so Dash will open them\n dotctl lsp [--stdio] Start the .dml language server for editors. It talks\n over stdin and stdout\n\nOptions:\n --out-dir <dir> Output directory (default: <root>/dist)\n --conditions <list> Extra export conditions, separated by commas\n --port <port> Simulator port for \\`dev\\` and \\`preview\\`. If it is taken, the\n next free port is used (default: ${DEFAULT_PORT})\n --device With \\`dev\\`, also serve the dot to the Dot example apps on phones\n on your local network, through a link and QR code. The apps\n reload after each build\n -h, --help Show this message`;\n\nasync function main(argv: string[]): Promise<number> {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n 'out-dir': { type: 'string' },\n conditions: { type: 'string' },\n port: { type: 'string' },\n device: { type: 'boolean' },\n name: { type: 'string' },\n template: { type: 'string' },\n pm: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n // Editors pass --stdio. The server always uses stdin and stdout, so the flag changes nothing.\n stdio: { type: 'boolean' },\n },\n });\n\n const [command, target, pagePath] = positionals;\n if (command === 'lsp' && !values.help) {\n // Imported lazily so the other commands start faster.\n const { serveStdio } = await import('./lsp/server');\n serveStdio();\n // Never resolves: the connection exits the process when the editor asks it to.\n return new Promise<number>(() => {});\n }\n if (command === 'keys' && !values.help) {\n return keys(target);\n }\n if (command === 'create' && target === 'page' && !values.help) {\n return addPage(pagePath);\n }\n if (command === 'create' && !values.help) {\n return createApp(target, values.name, values.template, values.pm);\n }\n if (values.help || (command !== 'build' && command !== 'dev' && command !== 'preview')) {\n console.log(USAGE);\n return values.help ? 0 : 1;\n }\n const port = values.port === undefined ? undefined : Number(values.port);\n if (port !== undefined && !Number.isInteger(port)) {\n console.error(`--port must be a whole number, not \"${values.port}\"`);\n return 1;\n }\n\n const options: BuildOptions = { root: target ?? '.' };\n if (values['out-dir'] !== undefined) {\n options.outDir = values['out-dir'];\n }\n if (values.conditions !== undefined) {\n options.conditions = values.conditions.split(',');\n }\n\n if (command === 'dev') {\n const stop = await dev({ ...options, port, device: values.device });\n await new Promise((resolve) => process.once('SIGINT', resolve));\n await stop();\n return 0;\n }\n\n if (command === 'preview') {\n if (values.device) {\n console.error('--device only works with dotctl dev');\n return 1;\n }\n const simulator = await preview({ ...options, port });\n await new Promise((resolve) => process.once('SIGINT', resolve));\n await simulator.close();\n return 0;\n }\n\n const started = performance.now();\n const result = await build(options);\n for (const file of result.files.filter((file) => !file.endsWith('.map'))) {\n console.log(` ${relative(process.cwd(), file)}`);\n }\n printWarnings(result);\n printBuildTime(started);\n return 0;\n}\n\nasync function keys(action: string | undefined): Promise<number> {\n if (action !== 'show') {\n console.error('Usage: dotctl keys show');\n return 1;\n }\n const path = defaultDeveloperKeyPath();\n const key = await loadDeveloperKey(path);\n if (!key) {\n console.error(missingDeveloperKeyHint(path));\n return 1;\n }\n console.log(`\\n Public key: ${key.publicKey}\\n Key ID: ${key.keyId}\\n`);\n return 0;\n}\n\nasync function addPage(path: string | undefined): Promise<number> {\n if (path === undefined) {\n console.error('Give the page a path, such as: dotctl create page cart/checkout');\n return 1;\n }\n if (path.startsWith('/')) {\n path = path.substring(1);\n }\n if (path.endsWith('/')) {\n path = path.substring(0, path.length - 1);\n }\n const { files, listing } = await createPage(path, process.cwd());\n for (const file of files) {\n console.log(` ${relative(process.cwd(), file)}`);\n }\n if (listing === 'added') {\n console.log(`\\nListed '${path}' in dot.ts.`);\n } else if (listing === 'manual') {\n console.log(\n `\\nAdd it to the pages in dot.ts so it gets built: defineDot({ pages: [..., '${path}'] })`,\n );\n }\n return 0;\n}\n\nasync function createApp(\n dir: string | undefined,\n name: string | undefined,\n templateFlag: string | undefined,\n packageManagerFlag: string | undefined,\n): Promise<number> {\n if (templateFlag !== undefined && !isTemplate(templateFlag)) {\n console.error(`--template must be one of ${TEMPLATES.join(', ')}`);\n return 1;\n }\n if (packageManagerFlag !== undefined && !isPackageManager(packageManagerFlag)) {\n console.error(`--pm must be one of ${PACKAGE_MANAGERS.join(', ')}`);\n return 1;\n }\n name ??= await ask('Dot name', dir ? toDisplayName(dir) : 'Dot');\n if (!name) {\n console.error('The dot needs a name. Pass one with: dotctl create [dir] --name \"My Dot\"');\n return 1;\n }\n const template = templateFlag ?? (await chooseTemplate());\n const { root, workspace } = await create({\n name,\n dir: dir ?? toPackageName(name),\n template,\n });\n const path = relative(process.cwd(), root) || '.';\n console.log(`\\nCreated ${name} in ${path}`);\n if (workspace) {\n console.log('It is inside the Dot repository, so it uses the workspace packages.');\n }\n\n // A command can't change its shell's folder, so it offers to install and start the dot from here.\n let packageManager: PackageManager = workspace ? 'pnpm' : (packageManagerFlag ?? launchedBy());\n console.log();\n if (await confirm('Install and start it now?', true)) {\n if (!workspace && !packageManagerFlag) {\n packageManager = await choosePackageManager(packageManager);\n }\n const installed = await run(packageManager, ['install'], root);\n if (installed === 0) {\n return run(packageManager, ['run', 'dev'], root);\n }\n console.error(\n `\\n${packageManager} install failed. Fix the problem above, then run the steps below.`,\n );\n }\n console.log(\n `\\nNext:\\n\\n cd ${path}\\n ${packageManager} install\\n ${packageManager} run dev\\n`,\n );\n return 0;\n}\n\nfunction run(command: string, args: string[], cwd: string): Promise<number> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(command, args, {\n cwd,\n stdio: 'inherit',\n shell: process.platform === 'win32',\n });\n child.on('error', reject);\n child.on('exit', (code) => resolvePromise(code ?? 1));\n });\n}\n\nfunction chooseTemplate(): Promise<Template> {\n console.log();\n return select(\n 'Template?',\n TEMPLATES.map((template) => ({ value: template, hint: TEMPLATE_HINTS[template] })),\n 'starter',\n );\n}\n\nasync function choosePackageManager(suggested: PackageManager): Promise<PackageManager> {\n const installed = installedManagers();\n if (installed.length < 2) {\n return installed[0] ?? suggested;\n }\n const fallback = installed.includes(suggested) ? suggested : installed[0]!;\n return select(\n 'Package manager?',\n installed.map((manager) => ({ value: manager })),\n fallback,\n );\n}\n\nmain(process.argv.slice(2)).then(\n (code) => process.exit(code),\n (error: unknown) => {\n console.error(`error: ${describeError(error)}`);\n process.exit(1);\n },\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/report.ts","../src/create/template.ts","../src/create/create.ts","../src/dot-root.ts","../src/create/page.ts","../src/create/package-manager.ts","../src/device/developer-key.ts","../src/device/server.ts","../src/simulator.ts","../src/dev.ts","../src/preview.ts","../src/prompts.ts","../src/cli.ts"],"sourcesContent":["import { DmlParseError } from '@monimesl/dot-dml-parser';\nimport type { BuildResult } from '@monimesl/dot-compiler';\nimport { CompileError } from '@monimesl/dot-compiler';\nimport { ScriptParseError } from '@monimesl/dot-script-parser';\n\n// A mistake in how a command was used. Its message is enough, so no stack trace is shown.\nexport class CommandError extends Error {\n override readonly name = 'CommandError';\n}\n\nexport function describeError(error: unknown): string {\n const known =\n error instanceof CommandError ||\n error instanceof CompileError ||\n error instanceof DmlParseError ||\n error instanceof ScriptParseError;\n if (known) {\n return error.message;\n }\n if ((error as { code?: string }).code?.startsWith('ERR_PARSE_ARGS')) {\n // Keep only the first sentence. Node goes on to explain `--`, which doesn't help here.\n const [firstSentence] = (error as Error).message.split('. ');\n return `${firstSentence}. See dotctl --help.`;\n }\n return error instanceof Error ? (error.stack ?? error.message) : String(error);\n}\n\nexport function printWarnings({ warnings }: BuildResult): void {\n for (const warning of warnings) {\n console.warn(`warning: ${warning.message}`);\n }\n}\n\nexport function printBuildTime(startedAt: number): void {\n console.log(`Built in ${Math.round(performance.now() - startedAt)}ms`);\n}\n","export const TEMPLATES = ['starter', 'showcase'] as const;\n\nexport type Template = (typeof TEMPLATES)[number];\n\n// Shown next to each template when `dotctl create` asks which one to use.\nexport const TEMPLATE_HINTS: Record<Template, string> = {\n starter: 'one page to build on',\n showcase: 'a page for each feature, to learn from',\n};\n\nexport function isTemplate(name: string): name is Template {\n return (TEMPLATES as readonly string[]).includes(name);\n}\n\ninterface TemplateOptions {\n name: string;\n // The npm package name, such as `my-dot`.\n packageName: string;\n // The dot's own package name, a reverse domain name such as `com.mysalone.thing`.\n dotPackage: string;\n dotVersion: string;\n workspace: boolean;\n template: Template;\n}\n\nexport const PAGE_FILES: Readonly<Record<string, string>> = {\n 'view.dml': '<View>\\n\\t<Text>My new page</Text>\\n</View>\\n',\n 'state.ts': 'export default {};\\n',\n 'controller.ts': `import state from './state';\n\nexport default definePage({\n initialState: state,\n});\n`,\n};\n\n// The files `dotctl create` copies from the template's example. It generates the rest.\nexport const TEMPLATE_FILES = ['dot.ts', 'pages', 'statics', '.env'] as const;\n\n// Editors that follow `$schema` can check dot.json and complete its fields. unpkg serves the schema\n// from the runtime's latest release on npm, and picks up a new release within minutes.\nconst DOT_JSON_SCHEMA = 'https://unpkg.com/@monimesl/dot-runtime/dot.schema.json';\n\nconst json = (value: unknown) => `${JSON.stringify(value, null, 2)}\\n`;\n\nexport function projectFiles(options: TemplateOptions): Record<string, string> {\n return {\n 'package.json': packageJson(options),\n 'tsconfig.json': tsconfig(options),\n '.gitignore': 'node_modules/\\ndist/\\n# Types generated by dotctl\\n.dot/\\n',\n 'dot.json': dotJson(options),\n 'README.md': readme(options),\n };\n}\n\nfunction packageJson({ packageName, dotVersion, workspace }: TemplateOptions): string {\n const conditions = workspace ? ' --conditions @monimesl/dot-source' : '';\n return json({\n name: packageName,\n private: true,\n type: 'module',\n scripts: {\n dev: `dotctl dev${conditions}`,\n build: `dotctl build${conditions}`,\n preview: `dotctl preview${conditions}`,\n typecheck: 'tsc --noEmit',\n },\n dependencies: { '@monimesl/dot-runtime': dotVersion },\n devDependencies: { '@monimesl/dot-cli': dotVersion, typescript: '^5.9.3' },\n });\n}\n\nfunction tsconfig({ workspace }: TemplateOptions): string {\n const compilerOptions: Record<string, unknown> = {\n target: 'ES2022',\n module: 'ESNext',\n moduleResolution: 'bundler',\n lib: ['ES2022'],\n types: ['@monimesl/dot-runtime/globals'],\n strict: true,\n noEmit: true,\n skipLibCheck: true,\n verbatimModuleSyntax: true,\n };\n if (workspace) {\n compilerOptions.customConditions = ['@monimesl/dot-source'];\n }\n return json({\n compilerOptions,\n include: ['dot.ts', 'pages', '.dot/pages.d.ts', '.dot/env.d.ts'],\n });\n}\n\n// Stands in for the ID Monime gives the dot when it's registered.\nexport const UNREGISTERED_DOT_ID = '<monime-Internal-Id>';\n\nfunction dotJson({ name, dotPackage, template }: TemplateOptions): string {\n const dot: Record<string, unknown> = {\n id: UNREGISTERED_DOT_ID,\n $schema: DOT_JSON_SCHEMA,\n package: dotPackage,\n name,\n version: '0.1.0',\n };\n if (template === 'showcase') {\n // The showcase's network page sends requests to these hosts.\n dot.network = { hosts: ['itunes.apple.com', '*.mzstatic.com', 'httpbin.org'] };\n }\n return json(dot);\n}\n\nconst TEMPLATE_READMES: Readonly<Record<Template, string>> = {\n starter: `It starts with one page, \\`index\\`. It shows a greeting from \\`.env\\` and a counter you\ncan tap. Change it, and add pages as the dot grows. To see how each feature works, make a showcase\ndot with \\`npx dotctl create my-tour --template showcase\\`.`,\n showcase: `It starts as a showcase. The \\`index\\` page links to a page for each feature: layout,\ntext, images and inputs, then state, navigation, and Dash's alerts, network and files. Read a page\nto see how it works. Then delete the pages you don't need, both from \\`pages/\\` and from\n\\`dot.ts\\`.`,\n};\n\nfunction readme({ name, template }: TemplateOptions): string {\n return `# ${name}\n\nA dot: a mini app that runs inside Dash.\n\n\\`\\`\\`sh\nnpm install\nnpm run dev # run it in the simulator; it reloads on every change\nnpm run build # build it for Dash\n\\`\\`\\`\n\n${TEMPLATE_READMES[template]}\n\n## Pages\n\nEach page is a folder under \\`pages/\\` with three files:\n\n- \\`view.dml\\`: what the page shows. It has one root \\`<View>\\`, with everything else inside it.\n- \\`state.ts\\`: the data the page starts with.\n- \\`controller.ts\\`: how the data changes.\n\n\\`npx dotctl create page <path>\\` adds a page, such as \\`cart/checkout\\`, with these three files. It\nalso lists the page in \\`dot.ts\\`. The dot opens on the first page in that list.\n\n## dot.json\n\n\\`dot.json\\` describes the dot to Dash. Its \\`package\\` is a reverse domain name you own, such as\n\\`com.mysalone.thing\\`. You can't change it after publishing. To publish, register the dot with\nMonime first, and replace its \\`id\\`, \\`<monime-Internal-Id>\\`, with the ID Monime gives you.\n\\`network.hosts\\` lists every host the dot sends requests to with \\`dash.network\\`. Requests to any\nother host fail. Your editor can complete the other fields from the schema the file points to.\n\n## Which Dash opens it\n\nDash opens the dot unless it was built with a newer major version of \\`@monimesl/dot-runtime\\` than\nthe one Dash has. A newer minor version can add Dash calls that an older Dash doesn't have yet.\nThere, such a call fails with a \\`HostError\\` whose \\`code\\` is \\`unsupported\\`, so catch it and carry\non without the feature.\n\n## .env\n\n\\`.env\\` holds public configuration. Code in the dot reads each variable from \\`env\\`, such as\n\\`env.GREETING\\`. Every variable is built into the dot, so anyone who has the dot can read it.\nNever put a secret there. \\`.env.development\\` and \\`.env.production\\` override it for \\`dev\\` and\n\\`build\\`.\n`;\n}\n\n// The package name to suggest, such as `com.example.my-dot` for the npm package `my-dot`. The npm\n// name is already lowercase letters, digits and inner hyphens. A part of a host name can be at most\n// 63 characters, so it's cut there.\nexport function suggestedDotPackage(packageName: string): string {\n return `com.example.${packageName.slice(0, 63).replace(/-+$/, '')}`;\n}\n","import { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { cp, mkdir, writeFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { basename, dirname, join, resolve } from 'node:path';\n\nimport { isDotPackage } from '@monimesl/dot-compiler';\n\nimport { CommandError } from '../report';\nimport type { Template } from './template';\nimport { projectFiles, suggestedDotPackage, TEMPLATE_FILES } from './template';\n\ninterface CreateOptions {\n name: string;\n dir: string;\n // A reverse domain name, such as `com.mysalone.thing`. Defaults to `com.example.<npm name>`.\n dotPackage?: string;\n template?: Template;\n}\n\ninterface CreateResult {\n readonly root: string;\n readonly workspace: boolean;\n}\n\ninterface PackageJson {\n name: string;\n version: string;\n}\n\nexport const DOT_PACKAGE_RULE = 'Use lowercase words joined by dots, such as com.mysalone.thing.';\n\nexport async function create({\n name,\n dir,\n dotPackage,\n template = 'starter',\n}: CreateOptions): Promise<CreateResult> {\n const root = resolve(dir);\n if (existsSync(root) && readdirSync(root).length > 0) {\n throw new CommandError(`${dir} already exists and is not empty. Choose another folder`);\n }\n const packageName = toPackageName(name);\n if (!packageName) {\n throw new CommandError(`The name \"${name}\" needs at least one letter or digit`);\n }\n dotPackage ??= suggestedDotPackage(packageName);\n if (!isDotPackage(dotPackage)) {\n throw new CommandError(`\"${dotPackage}\" isn't a package name. ${DOT_PACKAGE_RULE}`);\n }\n\n const workspace = isInDotWorkspace(root);\n const source = templateDir(template);\n await mkdir(root, { recursive: true });\n for (const path of TEMPLATE_FILES) {\n await cp(join(source, path), join(root, path), { recursive: true });\n }\n const generated = projectFiles({\n name,\n packageName,\n dotPackage,\n dotVersion: workspace ? 'workspace:*' : `^${cliVersion()}`,\n workspace,\n template,\n });\n for (const [path, content] of Object.entries(generated)) {\n await writeFile(join(root, path), content);\n }\n return { root, workspace };\n}\n\nexport function toPackageName(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\nexport function toDisplayName(dir: string): string {\n return basename(resolve(dir))\n .split(/[-_\\s]+/)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\n// Whether the nearest pnpm workspace above `dir` is the Dot repository.\nexport function isInDotWorkspace(dir: string): boolean {\n let current = dirname(dir);\n while (true) {\n if (existsSync(join(current, 'pnpm-workspace.yaml'))) {\n const runtimeManifest = join(current, 'packages/runtime/package.json');\n if (!existsSync(runtimeManifest)) {\n return false;\n }\n return readPackageJson(runtimeManifest).name === '@monimesl/dot-runtime';\n }\n const parent = dirname(current);\n if (parent === current) {\n return false;\n }\n current = parent;\n }\n}\n\n// Inside the repository, templates come from examples/. The published CLI ships its own copy.\nfunction templateDir(template: Template): string {\n const example = join(cliRoot(), '../../examples', template);\n return existsSync(example) ? example : join(cliRoot(), 'dist/templates', template);\n}\n\nfunction cliVersion(): string {\n return readPackageJson(join(cliRoot(), 'package.json')).version;\n}\n\nfunction readPackageJson(path: string): PackageJson {\n return JSON.parse(readFileSync(path, 'utf8')) as PackageJson;\n}\n\nfunction cliRoot(): string {\n return dirname(createRequire(import.meta.url).resolve('@monimesl/dot-cli/package.json'));\n}\n","import { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\n// The nearest folder with a dot.json, starting at `dir` and walking up.\nexport function findDotRoot(dir: string): string | undefined {\n while (true) {\n if (existsSync(join(dir, 'dot.json'))) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n return undefined;\n }\n dir = parent;\n }\n}\n","import { existsSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\n\nimport { isPageId } from '@monimesl/dot-core';\nimport type { DotModuleIR } from '@monimesl/dot-script-parser';\nimport { parseDotModule } from '@monimesl/dot-script-parser';\n\nimport { findDotRoot } from '../dot-root';\nimport { CommandError } from '../report';\nimport { PAGE_FILES } from './template';\n\ninterface CreatePageResult {\n readonly dir: string;\n readonly files: readonly string[];\n // Whether the page is now in `defineDot({ pages })` in dot.ts. `manual` means dot.ts couldn't be\n // updated, so the developer must add the page by hand.\n readonly listing: 'added' | 'already' | 'manual';\n}\n\n// Adds a page folder under pages/ in the dot that contains `cwd`, and lists it last in dot.ts.\nexport async function createPage(path: string, cwd: string): Promise<CreatePageResult> {\n if (!isPageId(path)) {\n throw new CommandError(\n `\"${path}\" is not a page path. Use letters, digits, \"-\" and \"_\", with \"/\" between folders, such as \"cart/checkout\"`,\n );\n }\n const root = findDotRoot(resolve(cwd));\n if (root === undefined) {\n throw new CommandError(\n 'There is no dot.json in this folder or any folder above it. Run this command inside a dot',\n );\n }\n const dir = join(root, 'pages', path);\n if (existsSync(dir)) {\n throw new CommandError(`pages/${path} already exists. Choose another path`);\n }\n await mkdir(dir, { recursive: true });\n const files: string[] = [];\n for (const [name, content] of Object.entries(PAGE_FILES)) {\n const file = join(dir, name);\n await writeFile(file, content);\n files.push(file);\n }\n return { dir, files, listing: await listPage(join(root, 'dot.ts'), path) };\n}\n\nasync function listPage(dotFile: string, path: string): Promise<CreatePageResult['listing']> {\n if (!existsSync(dotFile)) {\n return 'manual';\n }\n const source = await readFile(dotFile, 'utf8');\n let dot: DotModuleIR;\n try {\n dot = parseDotModule(source, dotFile);\n } catch {\n return 'manual';\n }\n if (dot.pages.includes(path)) {\n return 'already';\n }\n const updated = withPage(source, dot.lastPage, path);\n if (!listsPage(updated, dotFile, path)) {\n return 'manual';\n }\n await writeFile(dotFile, updated);\n return 'added';\n}\n\n// Inserts `path` after the last page, matching the list's style: same quotes, and on its own line\n// if each page has one.\nfunction withPage(source: string, lastPage: DotModuleIR['lastPage'], path: string): string {\n const quote = source[lastPage.start] === '\"' ? '\"' : \"'\";\n const page = `${quote}${path}${quote}`;\n const lineStart = source.lastIndexOf('\\n', lastPage.start - 1) + 1;\n const indent = source.slice(lineStart, lastPage.start);\n if (!/^\\s*$/.test(indent)) {\n return `${source.slice(0, lastPage.end)}, ${page}${source.slice(lastPage.end)}`;\n }\n const trailingComma = /^\\s*,/.exec(source.slice(lastPage.end));\n if (trailingComma) {\n const end = lastPage.end + trailingComma[0].length;\n return `${source.slice(0, end)}\\n${indent}${page},${source.slice(end)}`;\n }\n return `${source.slice(0, lastPage.end)},\\n${indent}${page}${source.slice(lastPage.end)}`;\n}\n\nfunction listsPage(source: string, dotFile: string, path: string): boolean {\n try {\n return parseDotModule(source, dotFile).pages.includes(path);\n } catch {\n return false;\n }\n}\n","import { spawnSync } from 'node:child_process';\n\nexport const PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'] as const;\nexport type PackageManager = (typeof PACKAGE_MANAGERS)[number];\n\nexport function isPackageManager(name: string): name is PackageManager {\n return (PACKAGE_MANAGERS as readonly string[]).includes(name);\n}\n\nexport function launchedBy(userAgent = process.env.npm_config_user_agent ?? ''): PackageManager {\n return PACKAGE_MANAGERS.find((name) => userAgent.startsWith(`${name}/`)) ?? 'npm';\n}\n\nexport function installedManagers(\n isInstalled: (name: PackageManager) => boolean = onPath,\n): PackageManager[] {\n return PACKAGE_MANAGERS.filter(isInstalled);\n}\n\nfunction onPath(name: PackageManager): boolean {\n return (\n spawnSync(name, ['--version'], { stdio: 'ignore', shell: process.platform === 'win32' })\n .status === 0\n );\n}\n","import { createPrivateKey, createPublicKey, sign } from 'node:crypto';\nimport type { KeyObject } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\n// The Ed25519 key this computer signs dotdev links with. The developer adds its public key to their\n// Monime account. Before Dash opens a link, Monime checks its signature.\nexport interface DeveloperKey {\n // The raw 32-byte public key in base64. This is what the developer adds to their account.\n readonly publicKey: string;\n // The first 8 bytes of the public key in lowercase hex. A link names its key by this ID.\n readonly keyId: string;\n // Returns the signature in base64.\n sign(bytes: Buffer): string;\n}\n\nexport function defaultDeveloperKeyPath(): string {\n return process.env.DOT_DEVELOPER_KEY ?? join(homedir(), '.dot', 'developer-key.pem');\n}\n\n// Explains how to make a key. dotctl reads the key but never creates it.\nexport function missingDeveloperKeyHint(path = defaultDeveloperKeyPath()): string {\n return (\n ` There is no developer key at ${path}. Dash only opens signed links.\\n` +\n \" Make one with OpenSSL 3. macOS's built-in openssl can't, so first run brew install openssl:\\n\\n\" +\n ` mkdir -p ${dirname(path)} && openssl genpkey -algorithm ed25519 -out ${path}\\n\\n` +\n ' Then run `dotctl keys show` and add the public key it prints to your Monime account.'\n );\n}\n\n// Returns undefined when there is no key file yet.\nexport async function loadDeveloperKey(\n path = defaultDeveloperKeyPath(),\n): Promise<DeveloperKey | undefined> {\n let pem: string;\n try {\n pem = await readFile(path, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return undefined;\n }\n throw error;\n }\n const privateKey = createPrivateKey(pem);\n if (privateKey.asymmetricKeyType !== 'ed25519') {\n throw new Error(\n `${path} is not an Ed25519 key. Make one with: openssl genpkey -algorithm ed25519 -out ${path}`,\n );\n }\n return toDeveloperKey(privateKey);\n}\n\nfunction toDeveloperKey(privateKey: KeyObject): DeveloperKey {\n const raw = Buffer.from(createPublicKey(privateKey).export({ format: 'jwk' }).x!, 'base64url');\n return {\n publicKey: raw.toString('base64'),\n keyId: raw.subarray(0, 8).toString('hex'),\n sign: (bytes) => sign(null, bytes, privateKey).toString('base64'),\n };\n}\n","import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';\nimport { readdir, readFile } from 'node:fs/promises';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { createServer } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { networkInterfaces } from 'node:os';\nimport { join, relative, sep } from 'node:path';\n\nimport type { DeveloperKey } from './developer-key';\n\ninterface DeviceServerOptions {\n // The address devices reach the server on. Defaults to the computer's local network address.\n address?: string | undefined;\n // Defaults to any free port. The link tells devices which one.\n port?: number | undefined;\n // Called for each line a device sends, such as its dot's console output.\n onLog?: (line: string, from: string) => void;\n onConnect?: (from: string) => void;\n // Signs the link. Hosts such as Dash only open links signed by a known developer.\n developerKey?: DeveloperKey | undefined;\n}\n\n// Serves a dot's builds to the example host apps on phones, which reload on each new build.\nexport interface DeviceServer {\n // The link a device opens: `dotdev://connect?server=…&secret=…`. It is kept short so the QR\n // code stays small. A signed link's signature is served at `GET /dev/link` instead.\n readonly link: string;\n // Serves the build output in `dir` as the newest build. A `hot` build changed only pages, so\n // devices can reload it in place and keep each open page's state.\n publish(dir: string, options?: { hot?: boolean }): Promise<void>;\n // Tells devices the newest build failed. They keep running the last good one.\n fail(message: string): void;\n close(): Promise<void>;\n}\n\ninterface Build {\n readonly files: ReadonlyMap<string, { readonly bytes: Buffer; readonly hash: string }>;\n}\n\nconst DEVICE_SCHEME = 'dotdev';\n\n// A waiting request ends after this and the device asks again, before the network drops the idle\n// connection.\nconst WAIT_MS = 25_000;\nconst MAX_LOG_BYTES = 1024 * 1024;\n// How long a signed link works. A photo of an old QR code soon becomes useless.\nconst LINK_LIFETIME_SECONDS = 12 * 60 * 60;\n\nexport const BUILD_MAC_HEADER = 'dot-build-mac';\n\n// The text a signed link's `sig` signs, one field per line.\nexport function signedLinkMessage(\n server: string,\n secret: string,\n expires: number,\n keyId: string,\n): string {\n return ['dotdev-connect', server, secret, String(expires), keyId].join('\\n');\n}\n\n// The bearer token a device sends. It is derived from the link's secret, so the link doesn't need\n// to carry it. Someone who sees the token on the network learns nothing about the secret.\nexport function deviceToken(secret: string): string {\n return createHmac('sha256', Buffer.from(secret, 'base64url'))\n .update('dotdev-token', 'utf8')\n .digest('base64url');\n}\n\n// A signed link's signature, served at `GET /dev/link`. It isn't secret, so it stays out of the QR\n// code.\nexport interface LinkSignature {\n readonly expires: number;\n readonly keyId: string;\n // Base64\n readonly sig: string;\n}\n\n// The HMAC-SHA256 of a build listing, keyed with the link's secret. A device only accepts listings\n// from someone who has the link, and each file must match its hash in the listing.\nexport function buildMac(secret: string, body: string): string {\n return createHmac('sha256', Buffer.from(secret, 'base64url'))\n .update(`dotdev-build\\n${body}`, 'utf8')\n .digest('base64url');\n}\n\nexport async function startDeviceServer({\n address = localNetworkAddress(),\n port = 0,\n onLog = () => {},\n onConnect = () => {},\n developerKey,\n}: DeviceServerOptions = {}): Promise<DeviceServer> {\n if (address === undefined) {\n throw new Error(\n \"This computer has no local network address, so devices can't reach it. Connect to a network and try again\",\n );\n }\n // The secret never goes over the network. Only the link carries it.\n const secret = randomBytes(16).toString('base64url');\n const token = deviceToken(secret);\n let signature: LinkSignature | null = null;\n let build: Build = { files: new Map() };\n let generation = 0;\n // The oldest generation from which only hot builds lead to the newest. A device on this\n // generation or a later one can hot reload to the newest build.\n let hotSince = 0;\n let error: string | null = null;\n const waiting = new Set<() => void>();\n const connected = new Set<string>();\n\n const announce = () => {\n generation += 1;\n for (const wake of waiting) {\n wake();\n }\n };\n\n const server = createServer((request, response) => {\n const from = request.socket.remoteAddress ?? 'a device';\n // Served without the token check: a device needs the signature to check the link before using\n // it, and the signature isn't secret.\n if (request.method === 'GET' && request.url === '/dev/link') {\n if (signature) {\n sendJson(response, signature);\n } else {\n response.writeHead(404).end();\n }\n return;\n }\n if (!isAuthorized(request, token)) {\n response.writeHead(401).end();\n return;\n }\n const url = new URL(request.url ?? '/', 'http://device');\n const path = url.pathname;\n if (request.method === 'GET' && path === '/dev/build') {\n if (!connected.has(from)) {\n connected.add(from);\n onConnect(from);\n }\n const files = Object.fromEntries([...build.files].map(([name, file]) => [name, file.hash]));\n const body = JSON.stringify({ generation, hotSince, files, error });\n response\n .writeHead(200, {\n 'content-type': 'application/json',\n [BUILD_MAC_HEADER]: buildMac(secret, body),\n })\n .end(body);\n } else if (request.method === 'GET' && path.startsWith('/dev/files/')) {\n if (url.searchParams.get('generation') !== String(generation)) {\n response.writeHead(409).end();\n return;\n }\n const name = path.slice('/dev/files/'.length).split('/').map(decodeURIComponent).join('/');\n const file = build.files.get(name);\n if (!file) {\n response.writeHead(404).end();\n return;\n }\n response.writeHead(200, { 'content-type': 'application/octet-stream' }).end(file.bytes);\n } else if (request.method === 'GET' && path === '/dev/changes') {\n waitForChange(response, Number(url.searchParams.get('after')));\n } else if (request.method === 'POST' && path === '/dev/log') {\n readText(request)\n .then((text) => {\n for (const line of text.split('\\n')) {\n onLog(line, from);\n }\n response.writeHead(204).end();\n })\n .catch(() => response.writeHead(413).end());\n } else {\n response.writeHead(404).end();\n }\n });\n\n const waitForChange = (response: ServerResponse, after: number) => {\n if (generation > after) {\n sendJson(response, { generation });\n return;\n }\n const wake = () => {\n clearTimeout(timer);\n waiting.delete(wake);\n sendJson(response, { generation });\n };\n const timer = setTimeout(() => {\n waiting.delete(wake);\n response.writeHead(204).end();\n }, WAIT_MS);\n waiting.add(wake);\n response.on('close', () => {\n clearTimeout(timer);\n waiting.delete(wake);\n });\n };\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(port, address, () => resolve());\n });\n const { port: listening } = server.address() as AddressInfo;\n const origin = `http://${address}:${listening}`;\n if (developerKey) {\n const expires = Math.floor(Date.now() / 1000) + LINK_LIFETIME_SECONDS;\n const keyId = developerKey.keyId;\n const message = signedLinkMessage(origin, secret, expires, keyId);\n signature = { expires, keyId, sig: developerKey.sign(Buffer.from(message, 'utf8')) };\n }\n\n return {\n link: `${DEVICE_SCHEME}://connect?server=${encodeURIComponent(origin)}&secret=${secret}`,\n async publish(dir, { hot = false } = {}) {\n build = await readBuild(dir);\n error = null;\n announce();\n if (!hot) {\n hotSince = generation;\n }\n },\n fail(message) {\n error = message;\n announce();\n },\n close() {\n for (const wake of waiting) {\n wake();\n }\n return new Promise((resolve) => {\n server.close(() => resolve());\n server.closeAllConnections();\n });\n },\n };\n}\n\n// The first IPv4 address that isn't a loopback address.\nfunction localNetworkAddress(): string | undefined {\n for (const addresses of Object.values(networkInterfaces())) {\n for (const candidate of addresses ?? []) {\n if (candidate.family === 'IPv4' && !candidate.internal) {\n return candidate.address;\n }\n }\n }\n return undefined;\n}\n\n// Reads the build output into memory, so a device downloading it never gets files from two builds.\nasync function readBuild(dir: string): Promise<Build> {\n const files = new Map<string, { bytes: Buffer; hash: string }>();\n const entries = await readdir(dir, { recursive: true, withFileTypes: true });\n for (const entry of entries) {\n // Devices don't need source maps or the archives `dotctl build` writes.\n if (!entry.isFile() || entry.name.endsWith('.map') || entry.name.endsWith('.zip')) {\n continue;\n }\n const path = join(entry.parentPath, entry.name);\n const bytes = await readFile(path);\n const hash = createHash('sha256').update(bytes).digest('base64url');\n files.set(relative(dir, path).split(sep).join('/'), { bytes, hash });\n }\n return { files };\n}\n\nfunction isAuthorized(request: IncomingMessage, token: string): boolean {\n const given = Buffer.from(request.headers.authorization ?? '');\n const expected = Buffer.from(`Bearer ${token}`);\n return given.length === expected.length && timingSafeEqual(given, expected);\n}\n\nfunction sendJson(response: ServerResponse, body: unknown): void {\n response.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(body));\n}\n\nasync function readText(request: IncomingMessage): Promise<string> {\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of request) {\n size += (chunk as Buffer).length;\n if (size > MAX_LOG_BYTES) {\n throw new Error(`A device sent more than ${MAX_LOG_BYTES} bytes of log at once`);\n }\n chunks.push(chunk as Buffer);\n }\n return Buffer.concat(chunks).toString('utf8');\n}\n","import { join } from 'node:path';\n\nimport type { Project } from '@monimesl/dot-compiler';\nimport { installedRuntime } from '@monimesl/dot-compiler';\nimport type { Simulator } from '@monimesl/dot-simulator';\nimport { startSimulator } from '@monimesl/dot-simulator';\n\n// `dev` and `preview` share .dot/files, so a preview sees the files the dot saved during `dev`.\nexport function startDotSimulator(\n project: Project,\n outDir: string,\n port: number | undefined,\n): Promise<Simulator> {\n return startSimulator({\n dotDir: outDir,\n reactHostDir: installedRuntime(project.root).react.dir,\n name: project.dotJson.name,\n port,\n storageDir: join(project.root, '.dot', 'files'),\n });\n}\n","import { watch } from 'node:fs';\nimport { join, relative, resolve, sep } from 'node:path';\n\nimport type { BuildOptions } from '@monimesl/dot-compiler';\nimport { build, loadProject, PAGES_DIR } from '@monimesl/dot-compiler';\nimport { renderUnicodeCompact } from 'uqr';\n\nimport { loadDeveloperKey, missingDeveloperKeyHint } from './device/developer-key';\nimport type { DeviceServer } from './device/server';\nimport { startDeviceServer } from './device/server';\nimport { describeError, printBuildTime, printWarnings } from './report';\nimport { startDotSimulator } from './simulator';\n\ninterface DevOptions extends BuildOptions {\n port?: number | undefined;\n // Also serve the dot to the example host apps on phones, through a link and a QR code.\n device?: boolean | undefined;\n}\n\n// Editors often save a file in several writes. Waiting this long turns them into one rebuild.\nconst CHANGE_DEBOUNCE_MS = 50;\n\nexport async function dev({ port, device, ...options }: DevOptions): Promise<() => Promise<void>> {\n const project = await loadProject(options.root);\n const outDir = resolve(project.root, options.outDir ?? 'dist');\n const buildOptions: BuildOptions = { mode: 'development', ...options, outDir };\n\n // Returns the error to show, with paths relative to the project, or null if the build worked.\n const buildApp = async (): Promise<string | null> => {\n const started = performance.now();\n try {\n printWarnings(await build(buildOptions));\n printBuildTime(started);\n return null;\n } catch (error) {\n const message = describeError(error).replaceAll(project.root + sep, '');\n console.error(`error: ${message}`);\n return message;\n }\n };\n\n const firstError = await buildApp();\n const simulator = await startDotSimulator(project, outDir, port);\n if (firstError) {\n simulator.error(firstError);\n }\n console.log(`\\n Simulator running at: ${simulator.url}\\n`);\n\n let devices: DeviceServer | undefined;\n if (device) {\n const developerKey = await loadDeveloperKey();\n devices = await startDeviceServer({\n developerKey,\n onConnect: (from) => console.log(`A device connected from ${from}`),\n onLog: (line) => console.log(`device ${line}`),\n });\n if (firstError) {\n devices.fail(firstError);\n } else {\n await devices.publish(outDir);\n }\n printDeviceLink(devices.link);\n if (developerKey) {\n console.log(` Signed with developer key ${developerKey.keyId}\\n`);\n } else {\n console.log(`${missingDeveloperKeyHint()}\\n`);\n }\n }\n\n // Files changed since the last good build. They decide whether the next build can hot reload.\n const changed = new Set<string>();\n const pagesDir = join(project.root, PAGES_DIR) + sep;\n const rebuilds = new RebuildQueue(async () => {\n // Files that change during this build stay in `changed` for the next one.\n const building = [...changed];\n const error = await buildApp();\n if (error) {\n simulator.error(error);\n devices?.fail(error);\n return;\n }\n for (const path of building) {\n changed.delete(path);\n }\n // Page changes hot reload in place. Any other change, such as dot.ts, .env or statics/,\n // restarts the dot on the pages it had open.\n const hot = building.every((path) => path.startsWith(pagesDir));\n if (hot) {\n simulator.hotReload();\n } else {\n simulator.reload();\n }\n await devices?.publish(outDir, { hot });\n });\n\n let debounceTimer: NodeJS.Timeout | undefined;\n const watcher = watch(project.root, { recursive: true }, (_event, file) => {\n if (!file || isIgnored(file)) {\n return;\n }\n const path = resolve(project.root, file);\n if (path === outDir || path.startsWith(outDir + sep)) {\n return;\n }\n changed.add(path);\n clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n console.log(`Changed ${relative(project.root, path)}`);\n rebuilds.request();\n }, CHANGE_DEBOUNCE_MS);\n });\n\n return async () => {\n clearTimeout(debounceTimer);\n watcher.close();\n await Promise.all([simulator.close(), devices?.close()]);\n };\n}\n\nfunction printDeviceLink(link: string): void {\n console.log(\n ' To open it in Dash, turn on Enable Dot Debugging in Developer Settings.\\n' +\n ' Then tap Live Test on the Dots tab and scan this code.\\n',\n );\n console.log(renderUnicodeCompact(link, { border: 1 }).replace(/^/gm, ' '));\n console.log(`\\n ${link}\\n`);\n}\n\n// Runs one rebuild at a time. Changes during a build lead to exactly one more build after it.\nclass RebuildQueue {\n readonly #rebuild: () => Promise<void>;\n #building = false;\n #changedWhileBuilding = false;\n\n constructor(rebuild: () => Promise<void>) {\n this.#rebuild = rebuild;\n }\n\n request(): void {\n if (this.#building) {\n this.#changedWhileBuilding = true;\n return;\n }\n void this.#build();\n }\n\n async #build(): Promise<void> {\n this.#building = true;\n this.#changedWhileBuilding = false;\n try {\n await this.#rebuild();\n } finally {\n this.#building = false;\n }\n if (this.#changedWhileBuilding) {\n this.request();\n }\n }\n}\n\n// Skips node_modules and hidden folders such as .git or .dot, but not .env files: the build reads\n// them.\nfunction isIgnored(file: string): boolean {\n return file.split(/[\\\\/]/).some((segment) => {\n if (segment === '.env' || segment.startsWith('.env.')) {\n return false;\n }\n return segment === 'node_modules' || segment.startsWith('.');\n });\n}\n","import { resolve } from 'node:path';\n\nimport type { BuildOptions } from '@monimesl/dot-compiler';\nimport { build, loadProject } from '@monimesl/dot-compiler';\nimport type { Simulator } from '@monimesl/dot-simulator';\n\nimport { printBuildTime, printWarnings } from './report';\nimport { startDotSimulator } from './simulator';\n\ninterface PreviewOptions extends BuildOptions {\n port?: number | undefined;\n}\n\n// Runs the `dotctl build` output in the simulator: minified, with only the components its pages\n// use. It doesn't rebuild on change, because a new component in a page wouldn't be in the build.\nexport async function preview({ port, ...options }: PreviewOptions): Promise<Simulator> {\n const project = await loadProject(options.root);\n const outDir = resolve(project.root, options.outDir ?? 'dist');\n const started = performance.now();\n printWarnings(await build({ ...options, outDir, mode: 'production' }));\n printBuildTime(started);\n const simulator = await startDotSimulator(project, outDir, port);\n console.log(`\\n Simulator running at: ${simulator.url}\\n`);\n return simulator;\n}\n","import { emitKeypressEvents } from 'node:readline';\nimport { createInterface } from 'node:readline/promises';\nimport { styleText } from 'node:util';\n\nexport interface Choice<Value extends string> {\n value: Value;\n // Shown dimmed after the value, such as a short description of a template.\n hint?: string;\n}\n\ninterface Terminal {\n input: NodeJS.ReadStream;\n output: NodeJS.WriteStream;\n}\n\nconst TERMINAL: Terminal = { input: process.stdin, output: process.stdout };\n\nconst ERASE_BELOW = '\\x1b[0J';\nconst HIDE_CURSOR = '\\x1b[?25l';\nconst SHOW_CURSOR = '\\x1b[?25h';\nconst cursorUp = (lines: number) => `\\x1b[${lines}A`;\n\n// Lets the person pick one of `choices` with the arrow keys and Enter. The list starts on\n// `fallback`, marked as the default. Without a terminal, it returns `fallback` without asking.\nexport async function select<Value extends string>(\n question: string,\n choices: readonly Choice<Value>[],\n fallback: Value,\n { input, output }: Terminal = TERMINAL,\n): Promise<Value> {\n if (!input.isTTY) {\n return fallback;\n }\n let current = Math.max(\n 0,\n choices.findIndex((choice) => choice.value === fallback),\n );\n // Pad the values so their hints line up.\n const width = choices.some((choice) => choice.hint)\n ? Math.max(...choices.map((choice) => choice.value.length))\n : 0;\n const row = (choice: Choice<Value>, index: number) => {\n const name = choice.value.padEnd(width);\n const hint = choice.hint ? styleText('dim', ` ${choice.hint}`) : '';\n const defaultMark = choice.value === fallback ? styleText('green', ' (default)') : '';\n if (index === current) {\n return `${styleText('cyan', '❯')} ${styleText(['cyan', 'bold'], name)}${hint}${defaultMark}`;\n }\n return ` ${name}${hint}${defaultMark}`;\n };\n\n // Each draw replaces the one before. First the question and its list, then the question and the\n // answer.\n let drawnLines = 0;\n const draw = (lines: string[]) => {\n output.write(`${drawnLines ? cursorUp(drawnLines) : ''}${ERASE_BELOW}${lines.join('\\n')}\\n`);\n drawnLines = lines.length;\n };\n const drawList = () =>\n draw([\n `${styleText('bold', question)} ${styleText('dim', '↑/↓ to move, Enter to choose')}`,\n ...choices.map(row),\n ]);\n\n emitKeypressEvents(input);\n input.setRawMode(true);\n input.resume();\n output.write(HIDE_CURSOR);\n drawList();\n return new Promise((resolve) => {\n const onKeypress = (_text: string, key: { name?: string; ctrl?: boolean }) => {\n if (key.ctrl && key.name === 'c') {\n output.write(`${SHOW_CURSOR}\\n`);\n process.exit(130);\n }\n if (key.name === 'up' || key.name === 'k') {\n current = (current - 1 + choices.length) % choices.length;\n drawList();\n } else if (key.name === 'down' || key.name === 'j') {\n current = (current + 1) % choices.length;\n drawList();\n } else if (key.name === 'return' || key.name === 'enter') {\n const { value } = choices[current]!;\n input.off('keypress', onKeypress);\n input.setRawMode(false);\n input.pause();\n draw([`${styleText('bold', question)} ${styleText('cyan', value)}`]);\n output.write(SHOW_CURSOR);\n resolve(value);\n }\n };\n input.on('keypress', onKeypress);\n });\n}\n\n// A yes or no question, shown as a list. Without a terminal, the answer is always no.\nexport async function confirm(\n question: string,\n fallback: boolean,\n terminal: Terminal = TERMINAL,\n): Promise<boolean> {\n if (!terminal.input.isTTY) {\n return false;\n }\n const choices = [{ value: 'Yes' }, { value: 'No' }] as const;\n return (await select(question, choices, fallback ? 'Yes' : 'No', terminal)) === 'Yes';\n}\n\n// Asks for a typed answer, with an optional `hint` shown dimmed after the question. An empty answer,\n// or no terminal, gives `fallback`, shown as the default.\nexport async function ask(\n question: string,\n fallback?: string,\n hint?: string,\n { input, output }: Terminal = TERMINAL,\n): Promise<string | undefined> {\n if (!input.isTTY) {\n return fallback;\n }\n const guide = hint ? styleText('dim', `, ${hint}`) : '';\n const defaultMark = fallback ? styleText('dim', ` (default: ${fallback})`) : '';\n const readline = createInterface({ input, output });\n try {\n const answer = await readline.question(\n `${styleText('bold', question)}${guide}${defaultMark}: `,\n );\n return answer.trim() || fallback;\n } catch (error) {\n // The person pressed Ctrl+C or Ctrl+D.\n if ((error as Error).name === 'AbortError') {\n return undefined;\n }\n throw error;\n } finally {\n readline.close();\n }\n}\n","import { spawn } from 'node:child_process';\nimport { relative } from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport type { BuildOptions } from '@monimesl/dot-compiler';\nimport { build, isDotPackage } from '@monimesl/dot-compiler';\nimport { DEFAULT_PORT } from '@monimesl/dot-simulator';\n\nimport { create, DOT_PACKAGE_RULE, toDisplayName, toPackageName } from './create/create';\nimport { createPage } from './create/page';\nimport type { PackageManager } from './create/package-manager';\nimport type { Template } from './create/template';\nimport { isTemplate, suggestedDotPackage, TEMPLATE_HINTS, TEMPLATES } from './create/template';\nimport {\n installedManagers,\n isPackageManager,\n launchedBy,\n PACKAGE_MANAGERS,\n} from './create/package-manager';\nimport { dev } from './dev';\nimport { preview } from './preview';\nimport { ask, confirm, select } from './prompts';\nimport {\n defaultDeveloperKeyPath,\n loadDeveloperKey,\n missingDeveloperKeyHint,\n} from './device/developer-key';\nimport { describeError, printBuildTime, printWarnings } from './report';\n\nconst USAGE = `Usage:\n dotctl create [dir] [--name <name>] [--package <com.mysalone.thing>]\n [--template <starter|showcase>] [--pm <npm|pnpm|yarn|bun>]\n Create a dot. Asks for its name, its package name\n (a reverse domain name you own) and its template:\n starter is one page to build on, showcase has a\n page for each feature\n dotctl create page <path> Add a page to the dot you are in. Writes\n pages/<path>/ with view.dml, state.ts and controller.ts\n dotctl build [root] [options] Build the minified bundles the host app runs\n dotctl dev [root] [options] Run the dot in the browser simulator and rebuild on\n every change\n dotctl preview [root] [options] Build as \\`build\\` does, then run that build in the\n browser simulator, exactly as the host app gets it\n dotctl keys show Show this computer's developer key: its public key\n and key ID. The key signs the links that\n \\`dev --device\\` shows, so Dash will open them\n dotctl lsp [--stdio] Start the .dml language server for editors. It talks\n over stdin and stdout\n\nOptions:\n --out-dir <dir> Output directory (default: <root>/dist)\n --conditions <list> Extra export conditions, separated by commas\n --port <port> Simulator port for \\`dev\\` and \\`preview\\`. If it is taken, the\n next free port is used (default: ${DEFAULT_PORT})\n --device With \\`dev\\`, also serve the dot to the Dot example apps on phones\n on your local network, through a link and QR code. The apps\n reload after each build\n -h, --help Show this message`;\n\nasync function main(argv: string[]): Promise<number> {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n 'out-dir': { type: 'string' },\n conditions: { type: 'string' },\n port: { type: 'string' },\n device: { type: 'boolean' },\n name: { type: 'string' },\n package: { type: 'string' },\n template: { type: 'string' },\n pm: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n // Editors pass --stdio. The server always uses stdin and stdout, so the flag changes nothing.\n stdio: { type: 'boolean' },\n },\n });\n\n const [command, target, pagePath] = positionals;\n if (command === 'lsp' && !values.help) {\n // Imported lazily so the other commands start faster.\n const { serveStdio } = await import('./lsp/server');\n serveStdio();\n // Never resolves: the connection exits the process when the editor asks it to.\n return new Promise<number>(() => {});\n }\n if (command === 'keys' && !values.help) {\n return keys(target);\n }\n if (command === 'create' && target === 'page' && !values.help) {\n return addPage(pagePath);\n }\n if (command === 'create' && !values.help) {\n return createApp(target, values.name, values.package, values.template, values.pm);\n }\n if (values.help || (command !== 'build' && command !== 'dev' && command !== 'preview')) {\n console.log(USAGE);\n return values.help ? 0 : 1;\n }\n const port = values.port === undefined ? undefined : Number(values.port);\n if (port !== undefined && !Number.isInteger(port)) {\n console.error(`--port must be a whole number, not \"${values.port}\"`);\n return 1;\n }\n\n const options: BuildOptions = { root: target ?? '.' };\n if (values['out-dir'] !== undefined) {\n options.outDir = values['out-dir'];\n }\n if (values.conditions !== undefined) {\n options.conditions = values.conditions.split(',');\n }\n\n if (command === 'dev') {\n const stop = await dev({ ...options, port, device: values.device });\n await new Promise((resolve) => process.once('SIGINT', resolve));\n await stop();\n return 0;\n }\n\n if (command === 'preview') {\n if (values.device) {\n console.error('--device only works with dotctl dev');\n return 1;\n }\n const simulator = await preview({ ...options, port });\n await new Promise((resolve) => process.once('SIGINT', resolve));\n await simulator.close();\n return 0;\n }\n\n const started = performance.now();\n const result = await build(options);\n for (const file of result.files.filter((file) => !file.endsWith('.map'))) {\n console.log(` ${relative(process.cwd(), file)}`);\n }\n printWarnings(result);\n printBuildTime(started);\n return 0;\n}\n\nasync function keys(action: string | undefined): Promise<number> {\n if (action !== 'show') {\n console.error('Usage: dotctl keys show');\n return 1;\n }\n const path = defaultDeveloperKeyPath();\n const key = await loadDeveloperKey(path);\n if (!key) {\n console.error(missingDeveloperKeyHint(path));\n return 1;\n }\n console.log(`\\n Public key: ${key.publicKey}\\n Key ID: ${key.keyId}\\n`);\n return 0;\n}\n\nasync function addPage(path: string | undefined): Promise<number> {\n if (path === undefined) {\n console.error('Give the page a path, such as: dotctl create page cart/checkout');\n return 1;\n }\n if (path.startsWith('/')) {\n path = path.substring(1);\n }\n if (path.endsWith('/')) {\n path = path.substring(0, path.length - 1);\n }\n const { files, listing } = await createPage(path, process.cwd());\n for (const file of files) {\n console.log(` ${relative(process.cwd(), file)}`);\n }\n if (listing === 'added') {\n console.log(`\\nListed '${path}' in dot.ts.`);\n } else if (listing === 'manual') {\n console.log(\n `\\nAdd it to the pages in dot.ts so it gets built: defineDot({ pages: [..., '${path}'] })`,\n );\n }\n return 0;\n}\n\nasync function createApp(\n dir: string | undefined,\n name: string | undefined,\n dotPackageFlag: string | undefined,\n templateFlag: string | undefined,\n packageManagerFlag: string | undefined,\n): Promise<number> {\n if (templateFlag !== undefined && !isTemplate(templateFlag)) {\n console.error(`--template must be one of ${TEMPLATES.join(', ')}`);\n return 1;\n }\n if (packageManagerFlag !== undefined && !isPackageManager(packageManagerFlag)) {\n console.error(`--pm must be one of ${PACKAGE_MANAGERS.join(', ')}`);\n return 1;\n }\n name ??= await ask('Dot name', dir ? toDisplayName(dir) : 'Dot');\n if (!name) {\n console.error('The dot needs a name. Pass one with: dotctl create [dir] --name \"My Dot\"');\n return 1;\n }\n if (dotPackageFlag !== undefined && !isDotPackage(dotPackageFlag)) {\n console.error(`--package \"${dotPackageFlag}\" isn't a package name. ${DOT_PACKAGE_RULE}`);\n return 1;\n }\n const dotPackage =\n dotPackageFlag ?? (await askDotPackage(suggestedDotPackage(toPackageName(name))));\n if (!dotPackage) {\n return 1;\n }\n const template = templateFlag ?? (await chooseTemplate());\n const { root, workspace } = await create({\n name,\n dir: dir ?? toPackageName(name),\n dotPackage,\n template,\n });\n const path = relative(process.cwd(), root) || '.';\n console.log(`\\nCreated ${name} in ${path}`);\n if (workspace) {\n console.log('It is inside the Dot repository, so it uses the workspace packages.');\n }\n\n // A command can't change its shell's folder, so it offers to install and start the dot from here.\n let packageManager: PackageManager = workspace ? 'pnpm' : (packageManagerFlag ?? launchedBy());\n console.log();\n if (await confirm('Install and start it now?', true)) {\n if (!workspace && !packageManagerFlag) {\n packageManager = await choosePackageManager(packageManager);\n }\n const installed = await run(packageManager, ['install'], root);\n if (installed === 0) {\n return run(packageManager, ['run', 'dev'], root);\n }\n console.error(\n `\\n${packageManager} install failed. Fix the problem above, then run the steps below.`,\n );\n }\n console.log(\n `\\nNext:\\n\\n cd ${path}\\n ${packageManager} install\\n ${packageManager} run dev\\n`,\n );\n return 0;\n}\n\nfunction run(command: string, args: string[], cwd: string): Promise<number> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(command, args, {\n cwd,\n stdio: 'inherit',\n shell: process.platform === 'win32',\n });\n child.on('error', reject);\n child.on('exit', (code) => resolvePromise(code ?? 1));\n });\n}\n\n// Asks again until the answer is a package name. Undefined when the person stops with Ctrl+C.\nasync function askDotPackage(suggested: string): Promise<string | undefined> {\n while (true) {\n const answer = await ask('Package name', suggested, 'a reverse domain name you own');\n if (answer === undefined || isDotPackage(answer)) {\n return answer;\n }\n console.error(`\"${answer}\" isn't a package name. ${DOT_PACKAGE_RULE}`);\n }\n}\n\nfunction chooseTemplate(): Promise<Template> {\n console.log();\n return select(\n 'Template?',\n TEMPLATES.map((template) => ({ value: template, hint: TEMPLATE_HINTS[template] })),\n 'starter',\n );\n}\n\nasync function choosePackageManager(suggested: PackageManager): Promise<PackageManager> {\n const installed = installedManagers();\n if (installed.length < 2) {\n return installed[0] ?? suggested;\n }\n const fallback = installed.includes(suggested) ? suggested : installed[0]!;\n return select(\n 'Package manager?',\n installed.map((manager) => ({ value: manager })),\n fallback,\n );\n}\n\nmain(process.argv.slice(2)).then(\n (code) => process.exit(code),\n (error: unknown) => {\n console.error(`error: ${describeError(error)}`);\n process.exit(1);\n },\n);\n"],"mappings":"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"}
@@ -18,28 +18,28 @@ export default {
18
18
  {
19
19
  id: 'drizilik',
20
20
  name: 'Drizilik',
21
- text: 'Kriofusion rapped in Krio. "Lemonade", from Wait and See, made Rolling Stone\'s best Afropop of 2025.',
21
+ text: 'Kriofusion rapped in Krio. "Lemonade" is from his album Wait and See.',
22
22
  release: 1788321371,
23
23
  photo: '',
24
24
  },
25
25
  {
26
26
  id: 'the-therapist',
27
27
  name: 'The Therapist',
28
- text: 'Had two million TikTok followers before his first single, "Nack", in 2022.',
28
+ text: 'Had a big following online before his first single, "Nack", in 2022.',
29
29
  release: 1612124616,
30
30
  photo: '',
31
31
  },
32
32
  {
33
33
  id: 'boii',
34
34
  name: 'Boii',
35
- text: "Spotify's most-streamed Sierra Leonean woman of 2024. Her first EP, Money or Honey, came in 2025.",
35
+ text: 'One of the most-streamed Sierra Leonean women of 2024. Her first EP, Money or Honey, came in 2025.',
36
36
  release: 1776449163,
37
37
  photo: '',
38
38
  },
39
39
  {
40
40
  id: 'apreel',
41
41
  name: 'ApreeL',
42
- text: '"O\'way", and "Control", a Krio love song that made Rolling Stone\'s best Afropop of 2025.',
42
+ text: '"O\'way", and "Control", a Krio love song.',
43
43
  release: 1831185667,
44
44
  photo: '',
45
45
  },
@@ -70,7 +70,7 @@ export default {
70
70
  },
71
71
  {
72
72
  title: 'Gen Z',
73
- text: 'Drizilik, Boii and ApreeL sing in Krio over Afrobeats, even amapiano, and new stars break through on Audiomack and TikTok.',
73
+ text: 'Drizilik, Boii and ApreeL sing in Krio over Afrobeats, and new stars break through online.',
74
74
  },
75
75
  ] as Chapter[],
76
76
  chapter: 0,
@@ -67,7 +67,7 @@ export default {
67
67
  songs: releases.filter((item) => item.kind === 'song').length,
68
68
  genres: ['All', 'Kriofusion', 'Afropop', 'Hip hop', 'R&B', 'Palm-wine', 'Gospel', 'Bubu'],
69
69
  genre: 'All',
70
- // Gen Z's shows, at home in Sierra Leone and abroad.
70
+ // Shows at home in Sierra Leone, and abroad in the Mano River region.
71
71
  where: 'home',
72
72
  home: [
73
73
  {
@@ -84,35 +84,42 @@ export default {
84
84
  date: '28 November 2025',
85
85
  city: 'Freetown',
86
86
  },
87
- ] as Show[],
88
- abroad: [
89
87
  {
90
- id: 'detty-rave',
91
- title: 'Detty Rave, with Mr Eazi',
92
- place: 'Drizilik joins him for "Sabi Road"',
93
- date: 'December 2025',
94
- city: 'Accra',
88
+ id: 'christmas-jam',
89
+ title: 'Christmas Jam',
90
+ place: 'Coronation Field',
91
+ date: '26 December 2025',
92
+ city: 'Bo',
93
+ },
94
+ {
95
+ id: 'gospel-night',
96
+ title: 'Gospel Night',
97
+ place: 'Kenema City Hall',
98
+ date: '15 November 2025',
99
+ city: 'Kenema',
95
100
  },
101
+ ] as Show[],
102
+ abroad: [
96
103
  {
97
- id: 'steel-yard',
98
- title: 'Drizilik Live In London',
99
- place: 'The Steel Yard',
100
- date: '16 November 2025',
101
- city: 'London',
104
+ id: 'monrovia-live',
105
+ title: 'Salone Sounds Live',
106
+ place: 'EJS Ministerial Complex',
107
+ date: '20 December 2025',
108
+ city: 'Monrovia',
102
109
  },
103
110
  {
104
- id: 'howard-theatre',
105
- title: 'Drizilik live',
106
- place: 'Howard Theatre',
107
- date: '6 July 2024',
108
- city: 'Washington',
111
+ id: 'mano-river-festival',
112
+ title: 'Mano River Music Festival',
113
+ place: 'Palais du Peuple',
114
+ date: '8 November 2025',
115
+ city: 'Conakry',
109
116
  },
110
117
  {
111
- id: 'freedom-festival',
112
- title: 'Freedom Festival',
113
- place: "In Hull, Freetown's twin city",
114
- date: '2–3 September 2022',
115
- city: 'Hull',
118
+ id: 'abidjan-night',
119
+ title: 'Salone Night',
120
+ place: 'Palais de la Culture',
121
+ date: '13 September 2025',
122
+ city: 'Abidjan',
116
123
  },
117
124
  ] as Show[],
118
125
  };
@@ -30,7 +30,7 @@
30
30
  </Else>
31
31
 
32
32
  <View gap="small">
33
- <Text variant="caption" tone="muted">Segmented tabs, for a few short choices. Gen Z shows at home and abroad.</Text>
33
+ <Text variant="caption" tone="muted">Segmented tabs, for a few short choices. Shows at home and in the Mano River region.</Text>
34
34
  <Tabs variant="segmented" value="{{ where }}" onChange="chooseWhere">
35
35
  <Tab value="home" label="Sierra Leone" />
36
36
  <Tab value="abroad" label="Abroad" />
@@ -19,7 +19,7 @@
19
19
 
20
20
  <View gap="small">
21
21
  <Text variant="caption" tone="muted">Another currency: US dollars. This one sends dollars, not cents</Text>
22
- <AmountInput label="Pledge for the band's tour" currency="USD" unit="major" value="{{ pledge }}" error="{{ pledgeError }}" hint="Up to USD 1,000" onChange="setPledge" />
22
+ <AmountInput label="Pledge for the band's Mano River tour" currency="USD" unit="major" value="{{ pledge }}" error="{{ pledgeError }}" hint="Up to USD 1,000" onChange="setPledge" />
23
23
  </View>
24
24
 
25
25
  <View gap="small">
@@ -10,7 +10,7 @@
10
10
 
11
11
  <Text variant="caption" tone="muted">Links open a web page or run something on this page</Text>
12
12
  <View variant="card" gap="small">
13
- <Text>Read about{{ ' ' }}<Link href="https://en.wikipedia.org/wiki/Music_of_Sierra_Leone">the music of Sierra Leone</Link>{{ ' ' }}on Wikipedia, in the phone's browser.</Text>
13
+ <Text>Read about{{ ' ' }}<Link href="https://en.wikipedia.org/wiki/Music_of_Sierra_Leone">the music of Sierra Leone</Link>{{ ' ' }}in the phone's browser.</Text>
14
14
  <View direction="row" gap="medium">
15
15
  <Link onTap="addTicket">Add a ticket below</Link>
16
16
  <Link tone="danger" onTap="removeTicket">Remove one</Link>
@@ -33,7 +33,7 @@
33
33
  </View>
34
34
  <Text align="center">In the center</Text>
35
35
  <Text align="end">At the end</Text>
36
- <Text maxLines="2">Sierra Leone's Refugee All Stars came together in refugee camps in Guinea, recorded their first album, Living Like a Refugee, back in Freetown, and went on to play Bonnaroo, open for Aerosmith and appear on Oprah. Two lines are allowed here, so the rest is cut off.</Text>
36
+ <Text maxLines="2">Sierra Leone's Refugee All Stars came together in refugee camps in Guinea, recorded their first album, Living Like a Refugee, back in Freetown, and carried Salone music to stages around the world. Two lines are allowed here, so the rest is cut off.</Text>
37
37
  </View>
38
38
 
39
39
  <Text variant="caption" tone="muted">Bindings show state in the view</Text>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monimesl/dot-cli",
3
- "version": "0.1.0-alpha.11",
3
+ "version": "0.1.0-alpha.12",
4
4
  "description": "The dotctl command: build mini apps and run them in the simulator",
5
5
  "type": "module",
6
6
  "repository": {
@@ -24,11 +24,11 @@
24
24
  "dependencies": {
25
25
  "typescript": "^5.9.3",
26
26
  "uqr": "^0.1.3",
27
- "@monimesl/dot-core": "0.1.0-alpha.11",
28
- "@monimesl/dot-dml-parser": "0.1.0-alpha.11",
29
- "@monimesl/dot-compiler": "0.1.0-alpha.11",
30
- "@monimesl/dot-simulator": "0.1.0-alpha.11",
31
- "@monimesl/dot-script-parser": "0.1.0-alpha.11"
27
+ "@monimesl/dot-compiler": "0.1.0-alpha.12",
28
+ "@monimesl/dot-dml-parser": "0.1.0-alpha.12",
29
+ "@monimesl/dot-core": "0.1.0-alpha.12",
30
+ "@monimesl/dot-script-parser": "0.1.0-alpha.12",
31
+ "@monimesl/dot-simulator": "0.1.0-alpha.12"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^22.20.4",