acrylblends 0.7.1 → 0.7.2
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 +2 -2
- package/dist/index.js +41 -1
- package/dist/index.js.map +1 -1
- package/docs/framework/README.md +2 -1
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/api/summary.md +6 -3
- package/docs/framework/cordis/node-server.md +3 -0
- package/docs/framework/getting-started/deploy.md +133 -0
- package/docs/framework/getting-started/nodejs.md +37 -16
- package/docs/framework/guides/client-modules.md +175 -0
- package/docs/framework/guides/forms-and-sessions.md +1 -1
- package/docs/framework/guides/jsx-dom.md +6 -1
- package/docs/framework/guides/validation.md +87 -0
- package/docs/framework/helpers/testing.md +17 -0
- package/docs/framework/upstream.json +3 -3
- package/lib/browser/client.js +307 -0
- package/lib/browser/jsx-dom.js +1432 -0
- package/lib/index.js +1 -25
- package/lib/node-server/register.js +7 -49
- package/lib/node-server.d.ts +74 -7
- package/lib/node-server.js +264 -7
- package/lib/schema-validator.d.ts +40 -14
- package/lib/schema-validator.js +2 -2
- package/lib/src-0hI7M7cN.js +27 -0
- package/lib/transpile-CTnWof4z.js +90 -0
- package/package.json +1 -1
- package/plugins/acryl-inspect/upstream.json +1 -1
- package/plugins/acryl-node-server/src/adapter/server.ts +28 -5
- package/plugins/acryl-node-server/src/adapter/types.ts +14 -2
- package/plugins/acryl-node-server/src/client-modules.ts +293 -0
- package/plugins/acryl-node-server/src/register.ts +5 -62
- package/plugins/acryl-node-server/src/transpile.ts +120 -0
- package/plugins/acryl-node-server/upstream.json +4 -3
- package/plugins/acryl-router/src/browser/client.ts +3 -0
- package/plugins/acryl-router/src/browser/jsx-dom.ts +9 -0
- package/plugins/acryl-router/src/helpers/index.ts +1 -1
- package/plugins/acryl-router/src/helpers/schema-validator.ts +39 -12
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/upstream.json +1 -1
- package/plugins/acryl-state/upstream.json +1 -1
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/upstream.json +1 -1
- package/templates/agent/package.json +4 -3
- package/templates/blank/package.json +4 -3
- package/templates/todo/package.json +4 -3
- package/templates/todo/upstream.json +1 -1
package/README.md
CHANGED
|
@@ -276,9 +276,9 @@ See [Node.js](https://github.com/acryldev/acryl/blob/main/docs/framework/getting
|
|
|
276
276
|
| `acrylblends/ssg` | static site generation: `toSSG` writes every route as a file ([SSG helper](https://github.com/acryldev/acryl/blob/main/docs/framework/helpers/ssg.md)) |
|
|
277
277
|
| `acrylblends/proxy`, `acrylblends/dev` | the proxy helper and the dev helpers (`showRoutes`, `inspectRoutes`, `getRouterName`) |
|
|
278
278
|
| `acrylblends/jsx` | JSX on the server: `jsxImportSource: 'acrylblends/jsx'`, `.tsx` files run on Node with `node --import acrylblends/node-server/register`, with `jsx/streaming`, and `acrylblends/jsx-renderer` for a layout behind `c.render` ([JSX guide](https://github.com/acryldev/acryl/blob/main/docs/framework/guides/jsx.md)) |
|
|
279
|
-
| `acrylblends/jsx/dom` | JSX in the browser: hooks and `render` (needs the DOM library) ([client components](https://github.com/acryldev/acryl/blob/main/docs/framework/guides/jsx-dom.md)) |
|
|
279
|
+
| `acrylblends/jsx/dom` | JSX in the browser: hooks and `render` (needs the DOM library) ([client components](https://github.com/acryldev/acryl/blob/main/docs/framework/guides/jsx-dom.md)); on Node, `clientModules` serves them to the browser without a bundler ([guide](https://github.com/acryldev/acryl/blob/main/docs/framework/guides/client-modules.md)) |
|
|
280
280
|
| `acrylblends/css` | CSS in JSX: `css`, `cx`, `keyframes`, `Style` ([CSS helper](https://github.com/acryldev/acryl/blob/main/docs/framework/helpers/css.md)) |
|
|
281
|
-
| `acrylblends/node-server` | `serve`, `createAdaptorServer`, `getRequestListener`; with `node-server/serve-static`, `node-server/conninfo`, and `node-server/register` (compiles `.tsx` with the app's `typescript`) |
|
|
281
|
+
| `acrylblends/node-server` | `serve`, `createAdaptorServer`, `getRequestListener`; with `node-server/serve-static`, `node-server/conninfo`, and `node-server/register` (compiles `.tsx` with the app's `typescript`); `clientModules` and `buildClientModules` (client components without a bundler) |
|
|
282
282
|
|
|
283
283
|
Every subpath is listed in the package's `exports`. The adapters of other runtimes are being ported. The docs mark them "not yet available" until they ship
|
|
284
284
|
([FAQ](https://github.com/acryldev/acryl/blob/main/docs/framework/guides/faq.md)).
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
3
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
@@ -509,6 +509,44 @@ function listPlugins(io) {
|
|
|
509
509
|
return 0;
|
|
510
510
|
}
|
|
511
511
|
//#endregion
|
|
512
|
+
//#region src/build-client.ts
|
|
513
|
+
/** Parse `build-client [root] [outDir]`. */
|
|
514
|
+
function parseBuildClientOptions(argv) {
|
|
515
|
+
const args = argv.filter((a) => !a.startsWith("-"));
|
|
516
|
+
if (args.length !== argv.length) throw new UsageError("build-client takes [root] [outDir] and no options");
|
|
517
|
+
if (args.length > 2) throw new UsageError("build-client takes at most two arguments: [root] [outDir]");
|
|
518
|
+
return {
|
|
519
|
+
root: args[0] ?? "src/client",
|
|
520
|
+
outDir: args[1] ?? "dist/client"
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Compile the app's client modules once (`buildClientModules` of the framework), for a production install that has no compiler at run time.
|
|
525
|
+
* The framework is the one installed in the app (so the compiled files match the server that serves them), else this package's own.
|
|
526
|
+
*/
|
|
527
|
+
async function buildClient(options, io) {
|
|
528
|
+
const root = resolve(io.cwd, options.root);
|
|
529
|
+
if (!existsSync(root)) {
|
|
530
|
+
io.stderr.write(`error: ${root} does not exist; the client modules live in src/client by default\n`);
|
|
531
|
+
return 1;
|
|
532
|
+
}
|
|
533
|
+
const outDir = resolve(io.cwd, options.outDir);
|
|
534
|
+
const here = createRequire(resolve(io.cwd, "package.json"));
|
|
535
|
+
let specifier = "acrylblends/node-server";
|
|
536
|
+
try {
|
|
537
|
+
here.resolve(specifier);
|
|
538
|
+
} catch {
|
|
539
|
+
specifier = new URL("../lib/node-server.js", import.meta.url).href;
|
|
540
|
+
}
|
|
541
|
+
const written = (await (specifier.startsWith("file:") ? import(specifier) : import(here.resolve(specifier)))).buildClientModules({
|
|
542
|
+
root,
|
|
543
|
+
outDir
|
|
544
|
+
});
|
|
545
|
+
for (const file of written) io.stdout.write(`${relative(io.cwd, resolve(outDir, file))}\n`);
|
|
546
|
+
io.stdout.write(`built ${written.length} files in ${relative(io.cwd, outDir) || "."}\n`);
|
|
547
|
+
return 0;
|
|
548
|
+
}
|
|
549
|
+
//#endregion
|
|
512
550
|
//#region src/spawn.ts
|
|
513
551
|
/**
|
|
514
552
|
* How to start a package manager without the shell option (Node warns about passing args to a shell, DEP0190, and a `.cmd` shim cannot be spawned directly).
|
|
@@ -581,6 +619,7 @@ Usage:
|
|
|
581
619
|
acrylblends add <plugin...> [--dir d] Copy library plugins (and what they require) into an app;
|
|
582
620
|
a folder path brings an external plugin into its extensions/
|
|
583
621
|
acrylblends plugins List the plugin library
|
|
622
|
+
acrylblends build-client [root] [out] Compile the client modules once for production (src/client to dist/client)
|
|
584
623
|
|
|
585
624
|
Options for init:
|
|
586
625
|
--template <name> blank or agent (asked when not given); --agent means --template agent,
|
|
@@ -609,6 +648,7 @@ async function runCli(argv, io = processIo()) {
|
|
|
609
648
|
}
|
|
610
649
|
if (command === "init") return await init(parseInitOptions(args), io);
|
|
611
650
|
if (command === "add") return await add(parseAddOptions(args), io);
|
|
651
|
+
if (command === "build-client") return await buildClient(parseBuildClientOptions(args), io);
|
|
612
652
|
if (command === "plugins") return listPlugins(io);
|
|
613
653
|
throw new UsageError(`unknown command "${command}"`);
|
|
614
654
|
} catch (error) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["parse"],"sources":["../src/library.ts","../src/project.ts","../src/templates.ts","../src/init.ts","../src/add.ts","../src/spawn.ts","../src/io.ts","../src/cli.ts"],"sourcesContent":["import { cpSync, existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { dirname, join, sep } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n/** One entry of the plugin library: `plugins/<name>/plugin.json` in this repository. */\nexport interface LibraryPlugin {\n readonly name: string\n readonly description: string\n /** Library plugins this one imports; they are copied with it. */\n readonly requires: readonly string[]\n /** npm packages an app needs at run time for this plugin. */\n readonly dependencies: Readonly<Record<string, string>>\n /** npm packages an app needs only to develop or run this plugin's tooling (for example `electron` for the desktop shell). */\n readonly devDependencies?: Readonly<Record<string, string>>\n /** `package.json` scripts the plugin adds; a script the app already has is kept. */\n readonly scripts?: Readonly<Record<string, string>>\n /** Lines the app's `.gitignore` needs for this plugin (added when missing, if the app has a `.gitignore`). */\n readonly gitignore?: readonly string[]\n /** How to mount it, shown after `acrylblends add`. */\n readonly mount?: string\n /**\n * Paths inside the plugin folder that are not copied into an app (for example a React hook an app without React cannot type-check, or colocated test\n * files). A pattern is an exact path or a glob: `*` matches within one folder name, `**` across folders, `?` one character; `**` followed by a slash also matches no folder.\n */\n readonly omit?: readonly string[]\n}\n\n/** A glob as a regular expression: `*` within a name, `**` across folders, `**` plus a slash may match no folder, `?` one character. */\nfunction globToRegExp(glob: string): RegExp {\n let source = ''\n for (let i = 0; i < glob.length; i += 1) {\n const c = glob[i] as string\n if (c === '*' && glob[i + 1] === '*') {\n if (glob[i + 2] === '/') {\n source += '(?:.*/)?'\n i += 2\n } else {\n source += '.*'\n i += 1\n }\n } else if (c === '*') source += '[^/]*'\n else if (c === '?') source += '[^/]'\n else source += c.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n }\n return new RegExp(`^${source}$`)\n}\n\n/** Whether a path inside a plugin folder (relative, any separator) is named by one of its `omit` patterns. */\nexport function isOmitted(inside: string, patterns: readonly string[] | undefined): boolean {\n if (!patterns || patterns.length === 0) return false\n const path = inside.split(sep).join('/')\n return patterns.some((pattern) =>\n /[*?]/.test(pattern) ? globToRegExp(pattern).test(path) : pattern === path,\n )\n}\n\n/** Files of a library plugin that stay in the repository: its manifest and its tests. */\nconst LIBRARY_ONLY = new Set(['plugin.json', 'test', 'tests'])\n\nexport class LibraryError extends Error {\n override name = 'LibraryError'\n}\n\n/** The library folder (`plugins/` at the repository root): walk up from this module, in source or in the build. */\nexport function libraryRoot(): string {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (;;) {\n const candidate = join(dir, 'plugins')\n if (existsSync(join(candidate, 'instance', 'plugin.json'))) return candidate\n const parent = dirname(dir)\n if (parent === dir)\n throw new LibraryError('acrylblends cannot find its plugin library (plugins/)')\n dir = parent\n }\n}\n\n/** Every plugin in the library, by name. */\nexport function readLibrary(root: string = libraryRoot()): Map<string, LibraryPlugin> {\n const plugins = new Map<string, LibraryPlugin>()\n for (const entry of readdirSync(root, { withFileTypes: true })) {\n const manifest = join(root, entry.name, 'plugin.json')\n if (!entry.isDirectory() || !existsSync(manifest)) continue\n const plugin = JSON.parse(readFileSync(manifest, 'utf8')) as LibraryPlugin\n if (plugin.name !== entry.name)\n throw new LibraryError(`plugins/${entry.name}/plugin.json names \"${plugin.name}\"`)\n plugins.set(plugin.name, {\n ...plugin,\n requires: plugin.requires ?? [],\n dependencies: plugin.dependencies ?? {},\n })\n }\n return plugins\n}\n\n/** The plugins to copy for `names`: each one after everything it requires, no duplicates. */\nexport function closure(\n names: readonly string[],\n library: Map<string, LibraryPlugin>,\n): LibraryPlugin[] {\n const ordered: LibraryPlugin[] = []\n const visiting = new Set<string>()\n const visit = (name: string, trail: string[]) => {\n if (ordered.some((plugin) => plugin.name === name)) return\n const plugin = library.get(name)\n if (!plugin)\n throw new LibraryError(\n `no plugin \"${name}\" in the library${trail.length ? ` (required by ${trail.at(-1)})` : ''}`,\n )\n if (visiting.has(name))\n throw new LibraryError(\n `plugins require each other in a cycle: ${[...trail, name].join(' -> ')}`,\n )\n visiting.add(name)\n for (const required of plugin.requires) visit(required, [...trail, name])\n visiting.delete(name)\n ordered.push(plugin)\n }\n for (const name of names) visit(name, [])\n return ordered\n}\n\n/** Copy one library plugin's source into `<app>/src/plugins/<name>/`. Throws when that folder already exists. */\nexport function copyPlugin(\n plugin: LibraryPlugin,\n appDir: string,\n root: string = libraryRoot(),\n): string {\n const target = join(appDir, 'src', 'plugins', plugin.name)\n if (existsSync(target))\n throw new LibraryError(\n `src/plugins/${plugin.name} already exists; remove it first to copy a fresh one`,\n )\n const source = join(root, plugin.name)\n cpSync(source, target, {\n recursive: true,\n filter: (path) => {\n const inside = path.slice(source.length + 1)\n return (\n !(dirname(path) === source && LIBRARY_ONLY.has(inside)) && !isOmitted(inside, plugin.omit)\n )\n },\n })\n return target\n}\n","import {\n cpSync,\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n statSync,\n writeFileSync,\n} from 'node:fs'\nimport { join } from 'node:path'\n\n/** Thrown when the target directory exists and is not empty. Nothing has been written. */\nexport class TargetNotEmptyError extends Error {\n constructor(readonly target: string) {\n super(\n `refusing to write into \"${target}\": the directory is not empty. Choose an empty or new directory.`,\n )\n this.name = 'TargetNotEmptyError'\n }\n}\n\n/** A project name npm accepts: lower case letters, digits and dashes, starting with a letter. */\nexport function isValidName(name: string): boolean {\n return /^[a-z][a-z0-9-]*$/.test(name) && name.length <= 214\n}\n\n/** A valid default project name derived from a directory path. */\nexport function nameFrom(directory: string): string {\n const base = directory.split(/[\\\\/]/).filter(Boolean).pop() ?? ''\n const cleaned = base\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, '-')\n .replace(/^[^a-z]+/, '')\n .replace(/-+$/, '')\n return cleaned || 'my-app'\n}\n\n/** Refuse a target that exists and is not an empty directory. A missing target is fine: it is created. */\nexport function assertTargetFree(target: string): void {\n if (!existsSync(target)) return\n if (!statSync(target).isDirectory() || readdirSync(target).length > 0)\n throw new TargetNotEmptyError(target)\n}\n\n/**\n * Copy a template into `target`, replacing `__APP_NAME__` with `name`. Checks the target before writing anything.\n *\n * @returns the copied files, relative to `target`\n */\nexport function copyTemplate(source: string, target: string, name: string): string[] {\n assertTargetFree(target)\n mkdirSync(target, { recursive: true })\n const written: string[] = []\n const walk = (from: string, to: string, prefix: string) => {\n for (const entry of readdirSync(from, { withFileTypes: true })) {\n if (\n entry.name === 'node_modules' ||\n (prefix === '' &&\n (entry.name === TEMPLATE_MANIFEST ||\n entry.name === 'upstream.json' ||\n entry.name === 'AGENTS.md'))\n )\n continue\n const sourcePath = join(from, entry.name)\n // npm and git treat a file named .gitignore specially, so templates keep it as `gitignore` (and `dockerignore`).\n const targetName =\n prefix === '' && (entry.name === 'gitignore' || entry.name === 'dockerignore')\n ? `.${entry.name}`\n : entry.name\n const targetPath = join(to, targetName)\n if (entry.isDirectory()) {\n mkdirSync(targetPath, { recursive: true })\n walk(sourcePath, targetPath, `${prefix}${entry.name}/`)\n continue\n }\n const text = readFileSync(sourcePath, 'utf8')\n if (text.includes('__APP_NAME__'))\n writeFileSync(targetPath, text.replaceAll('__APP_NAME__', name))\n else cpSync(sourcePath, targetPath)\n written.push(`${prefix}${targetName}`)\n }\n }\n walk(source, target, '')\n return written\n}\n\n/** A template's own manifest, `template.json`: the library plugins it is built from. Never copied (nor is `upstream.json`, the record of what was synced from ACRYL, nor the template's `AGENTS.md`, which `writeAgentFiles` joins to the shared core). */\nexport const TEMPLATE_MANIFEST = 'template.json'\n\n/** The address a template's desktop window opens on, when it is not the root (`template.json`: `desktopPath`). */\nexport function readTemplateDesktopPath(templateDir: string): string | undefined {\n const file = join(templateDir, TEMPLATE_MANIFEST)\n if (!existsSync(file)) return undefined\n return (JSON.parse(readFileSync(file, 'utf8')) as { desktopPath?: string }).desktopPath\n}\n\n/** Record where the desktop window opens in package.json (`acrylblends.desktopPath`), read by `src/plugins/desktop/main.ts`. */\nexport function writeDesktopPath(appDir: string, path: string): void {\n const file = join(appDir, 'package.json')\n const manifest = JSON.parse(readFileSync(file, 'utf8')) as {\n acrylblends?: Record<string, unknown>\n }\n manifest.acrylblends = { ...manifest.acrylblends, desktopPath: path }\n writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\\n`)\n}\n\nexport function readTemplatePlugins(templateDir: string): string[] {\n const file = join(templateDir, TEMPLATE_MANIFEST)\n if (!existsSync(file)) return []\n const { plugins } = JSON.parse(readFileSync(file, 'utf8')) as { plugins?: string[] }\n return plugins ?? []\n}\n\n/** Add run-time dependencies to an app's package.json, keeping what is there and sorting the keys. Returns the names added. */\nexport function mergeDependencies(\n appDir: string,\n dependencies: Readonly<Record<string, string>>,\n): string[] {\n const file = join(appDir, 'package.json')\n const manifest = JSON.parse(readFileSync(file, 'utf8')) as {\n dependencies?: Record<string, string>\n }\n const current = manifest.dependencies ?? {}\n const added = Object.keys(dependencies).filter((name) => !(name in current))\n const merged = { ...dependencies, ...current }\n manifest.dependencies = Object.fromEntries(\n Object.entries(merged).sort(([a], [b]) => a.localeCompare(b)),\n )\n writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\\n`)\n return added\n}\n\n/**\n * Add a plugin's development dependencies and scripts to an app's package.json: nothing the app already has is replaced. Returns the names of the development dependencies\n * and scripts added.\n */\nexport function mergeTooling(\n appDir: string,\n tooling: {\n devDependencies?: Readonly<Record<string, string>> | undefined\n scripts?: Readonly<Record<string, string>> | undefined\n },\n): { devDependencies: string[]; scripts: string[] } {\n const file = join(appDir, 'package.json')\n const manifest = JSON.parse(readFileSync(file, 'utf8')) as {\n devDependencies?: Record<string, string>\n scripts?: Record<string, string>\n }\n const dev = manifest.devDependencies ?? {}\n const scripts = manifest.scripts ?? {}\n const addedDev = Object.keys(tooling.devDependencies ?? {}).filter((name) => !(name in dev))\n const addedScripts = Object.keys(tooling.scripts ?? {}).filter((name) => !(name in scripts))\n manifest.devDependencies = Object.fromEntries(\n Object.entries({ ...tooling.devDependencies, ...dev }).sort(([a], [b]) => a.localeCompare(b)),\n )\n manifest.scripts = { ...tooling.scripts, ...scripts }\n writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\\n`)\n return { devDependencies: addedDev, scripts: addedScripts }\n}\n\n/** Add the lines a plugin needs to the app's `.gitignore` when it has one and they are missing. Returns the lines added. */\nexport function appendGitignore(appDir: string, lines: readonly string[] | undefined): string[] {\n const file = join(appDir, '.gitignore')\n if (!lines || lines.length === 0 || !existsSync(file)) return []\n const current = readFileSync(file, 'utf8')\n const present = new Set(current.split('\\n').map((line) => line.trim()))\n const missing = lines.filter((line) => !present.has(line))\n if (missing.length > 0)\n writeFileSync(\n file,\n `${current}${current.endsWith('\\n') || current === '' ? '' : '\\n'}${missing.join('\\n')}\\n`,\n )\n return missing\n}\n\n/** The generated app's guide for a coding agent: the shared core, then the template's own part. `CLAUDE.md` is only an import of it. */\nexport function writeAgentFiles(\n sharedDir: string,\n templateDir: string,\n target: string,\n name: string,\n): string[] {\n const core = readFileSync(join(sharedDir, 'AGENTS.core.md'), 'utf8')\n const own = existsSync(join(templateDir, 'AGENTS.md'))\n ? readFileSync(join(templateDir, 'AGENTS.md'), 'utf8')\n : ''\n writeFileSync(join(target, 'AGENTS.md'), `${core}${own}`.replaceAll('__APP_NAME__', name))\n writeFileSync(join(target, 'CLAUDE.md'), '@AGENTS.md\\n')\n return ['AGENTS.md', 'CLAUDE.md']\n}\n\n/**\n * Copy the framework's docs (a generated copy of ACRYL's `docs/framework`, already checked against its digest) into `<app>/docs/framework/`,\n * so a coding agent working in the app reads the API from the folder it is in. The generated-copy notes and the digest record stay behind.\n *\n * @returns the copied files, relative to `target`\n */\nexport function copyFrameworkDocs(source: string, target: string): string[] {\n const written: string[] = []\n const walk = (from: string, to: string, prefix: string) => {\n for (const entry of readdirSync(from, { withFileTypes: true })) {\n if (entry.name === 'upstream.json' || entry.name === 'UPSTREAM.md') continue\n const sourcePath = join(from, entry.name)\n const targetPath = join(to, entry.name)\n if (entry.isDirectory()) {\n mkdirSync(targetPath, { recursive: true })\n walk(sourcePath, targetPath, `${prefix}${entry.name}/`)\n continue\n }\n cpSync(sourcePath, targetPath)\n written.push(`docs/framework/${prefix}${entry.name}`)\n }\n }\n mkdirSync(join(target, 'docs', 'framework'), { recursive: true })\n walk(source, join(target, 'docs', 'framework'), '')\n return written\n}\n","import { existsSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n/** The templates: a blank Cordis app on the router, a todo list built only on the router and state, and the blank app with the offline agent. */\nexport const TEMPLATES = ['blank', 'todo', 'agent'] as const\nexport type Template = (typeof TEMPLATES)[number]\n\nexport function isTemplate(value: string): value is Template {\n return (TEMPLATES as readonly string[]).includes(value)\n}\n\n/**\n * The `templates/` folder of this package. The module sits at different depths in source (`src/`) and in the build\n * (`dist/`), so walk up until a `templates` folder holds the `blank` template.\n */\nexport function templatesRoot(): string {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (;;) {\n const candidate = join(dir, 'templates')\n if (existsSync(join(candidate, 'blank'))) return candidate\n const parent = dirname(dir)\n if (parent === dir) throw new Error('acrylblends cannot find its templates folder')\n dir = parent\n }\n}\n\n/** Absolute path of a template folder. */\nexport function templateDir(template: Template): string {\n return join(templatesRoot(), template)\n}\n\n/** The text every generated app's `AGENTS.md` starts with (`shared/`, beside `templates/`). */\nexport function sharedDir(): string {\n return join(dirname(templatesRoot()), 'shared')\n}\n\n/**\n * The framework's docs, as ACRYL wrote them: `docs/framework` of the repository, or the copy `prepack` puts inside the package. The module sits at\n * different depths in source and in the build, so walk up until a `docs/framework` folder holds the README.\n */\nexport function frameworkDocsRoot(): string {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (;;) {\n const candidate = join(dir, 'docs', 'framework')\n if (existsSync(join(candidate, 'README.md'))) return candidate\n const parent = dirname(dir)\n if (parent === dir)\n throw new Error('acrylblends cannot find the framework docs (docs/framework)')\n dir = parent\n }\n}\n","import { resolve } from 'node:path'\nimport { parseArgs } from 'node:util'\nimport type { Io } from './io'\nimport { closure, copyPlugin, readLibrary } from './library'\nimport {\n copyFrameworkDocs,\n copyTemplate,\n isValidName,\n mergeDependencies,\n mergeTooling,\n nameFrom,\n readTemplateDesktopPath,\n readTemplatePlugins,\n TargetNotEmptyError,\n writeAgentFiles,\n writeDesktopPath,\n} from './project'\nimport {\n frameworkDocsRoot,\n isTemplate,\n sharedDir,\n TEMPLATES,\n type Template,\n templateDir,\n} from './templates'\n\n/** A command line mistake. The CLI prints it with the help text and exits 2. */\nexport class UsageError extends Error {\n override name = 'UsageError'\n}\n\n/** What `init` was asked for. Anything left undefined is asked for on a terminal, or defaulted. */\nexport interface InitOptions {\n directory: string | undefined\n name: string | undefined\n template: Template | undefined\n install: boolean | undefined\n /** Also add the desktop shell (`add desktop`): an Electron window on the app's own server. */\n desktop: boolean\n yes: boolean\n}\n\n/** Parse `init [dir] [--template t] [--agent|--no-agent] [--name n] [--install] [-y]`. `--agent` means `--template agent`, `--no-agent` means `--template blank`. */\nexport function parseInitOptions(argv: readonly string[]): InitOptions {\n let parsed: ReturnType<typeof parse>\n try {\n parsed = parse(argv)\n } catch (error) {\n throw new UsageError((error as Error).message)\n }\n const { values, positionals } = parsed\n if (positionals.length > 1) throw new UsageError('init takes at most one directory')\n if (values.agent && values['no-agent'])\n throw new UsageError('use --agent or --no-agent, not both')\n if (values.template !== undefined && !isTemplate(values.template))\n throw new UsageError(`unknown template \"${values.template}\" (${TEMPLATES.join(', ')})`)\n const alias: Template | undefined = values.agent\n ? 'agent'\n : values['no-agent']\n ? 'blank'\n : undefined\n if (values.template !== undefined && alias !== undefined && values.template !== alias)\n throw new UsageError(\n `--template ${values.template} contradicts --${alias === 'agent' ? 'agent' : 'no-agent'}`,\n )\n return {\n directory: positionals[0],\n name: values.name,\n template: (values.template as Template | undefined) ?? alias,\n install: values.install,\n desktop: values.desktop === true,\n yes: values.yes === true,\n }\n}\n\nconst parse = (argv: readonly string[]) =>\n parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: {\n name: { type: 'string' },\n template: { type: 'string' },\n agent: { type: 'boolean' },\n 'no-agent': { type: 'boolean' },\n install: { type: 'boolean' },\n desktop: { type: 'boolean' },\n yes: { type: 'boolean', short: 'y' },\n },\n })\n\nconst yes = (answer: string) => ['y', 'yes'].includes(answer.trim().toLowerCase())\n\nasync function askTemplate(ask: (question: string) => Promise<string>): Promise<Template> {\n const answer = (await ask(`Template (${TEMPLATES.join(', ')}) [blank]: `)).trim().toLowerCase()\n if (answer === '') return 'blank'\n if (isTemplate(answer)) return answer\n throw new UsageError(`unknown template \"${answer}\" (${TEMPLATES.join(', ')})`)\n}\n\n/** Scaffold a Cordis app. Returns the exit code: 0 done, 1 refused or failed. Never overwrites anything. */\nexport async function init(options: InitOptions, io: Io): Promise<number> {\n const relative = options.directory ?? '.'\n const directory = resolve(io.cwd, relative)\n const ask = options.yes ? undefined : io.ask\n\n let name = options.name ?? nameFrom(directory)\n if (options.name === undefined && ask)\n name = (await ask(`Project name (${name}): `)).trim() || name\n if (!isValidName(name)) {\n throw new UsageError(\n `\"${name}\" is not a valid project name (lower case letters, digits and dashes, starting with a letter)`,\n )\n }\n const template = options.template ?? (ask ? await askTemplate(ask) : 'blank')\n const install =\n options.install ?? (ask ? yes(await ask('Install dependencies with npm now? (y/N): ')) : false)\n\n let files: string[]\n try {\n files = copyTemplate(templateDir(template), directory, name)\n files.push(...writeAgentFiles(sharedDir(), templateDir(template), directory, name))\n files.push(...copyFrameworkDocs(frameworkDocsRoot(), directory))\n } catch (error) {\n if (!(error instanceof TargetNotEmptyError)) throw error\n io.stderr.write(`error: ${error.message}\\n`)\n return 1\n }\n const plugins = closure(\n [...readTemplatePlugins(templateDir(template)), ...(options.desktop ? ['desktop'] : [])],\n readLibrary(),\n )\n for (const plugin of plugins) {\n copyPlugin(plugin, directory)\n mergeDependencies(directory, plugin.dependencies)\n mergeTooling(directory, plugin)\n }\n const desktopPath = options.desktop ? readTemplateDesktopPath(templateDir(template)) : undefined\n if (desktopPath !== undefined) writeDesktopPath(directory, desktopPath)\n io.stdout.write(\n `created ${name} (${template}) in ${directory}: ${files.length} files, plugins ${plugins.map((plugin) => plugin.name).join(', ')}\\n`,\n )\n\n if (install && (await io.install(directory)) !== 0) {\n io.stderr.write('error: npm install failed; run it yourself in the new directory\\n')\n return 1\n }\n const steps = [\n relative === '.' ? '' : ` cd ${relative}\\n`,\n install ? '' : ' npm install\\n',\n ' npm run dev\\n npm test\\n',\n ]\n io.stdout.write(`\\nNext:\\n${steps.join('')}`)\n return 0\n}\n","import { cpSync, existsSync, readFileSync, statSync } from 'node:fs'\nimport { basename, join, resolve } from 'node:path'\nimport { parseArgs } from 'node:util'\nimport { UsageError } from './init'\nimport type { Io } from './io'\nimport { closure, copyPlugin, type LibraryPlugin, readLibrary } from './library'\nimport { appendGitignore, mergeDependencies, mergeTooling } from './project'\n\nexport interface AddOptions {\n readonly names: readonly string[]\n readonly directory: string | undefined\n}\n\n/** Parse `add <plugin...> [--dir <app>]`. */\nexport function parseAddOptions(argv: readonly string[]): AddOptions {\n let parsed: ReturnType<typeof parse>\n try {\n parsed = parse(argv)\n } catch (error) {\n throw new UsageError((error as Error).message)\n }\n if (parsed.positionals.length === 0)\n throw new UsageError('add needs at least one plugin name (see \"acrylblends plugins\")')\n return { names: parsed.positionals, directory: parsed.values.dir }\n}\n\nconst parse = (argv: readonly string[]) =>\n parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: { dir: { type: 'string' } },\n })\n\nconst present = (appDir: string, name: string) => existsSync(join(appDir, 'src', 'plugins', name))\n\n/**\n * Copy library plugins into an existing app, with the plugins they require, and add their npm packages to its package.json.\n * Refuses a plugin the app already has. Returns the exit code.\n */\nexport async function add(options: AddOptions, io: Io): Promise<number> {\n const appDir = resolve(io.cwd, options.directory ?? '.')\n if (!existsSync(join(appDir, 'package.json')) || !existsSync(join(appDir, 'src'))) {\n io.stderr.write(\n `error: ${appDir} is not an app (no package.json and src/); run \"acrylblends init\" first\\n`,\n )\n return 1\n }\n // A path is an external plugin folder; anything else is a library plugin name.\n const external = options.names.filter((name) => /[\\\\/]/.test(name) || name.startsWith('.'))\n for (const folder of external) {\n const code = addExternal(resolve(io.cwd, folder), appDir, io)\n if (code !== 0) return code\n }\n // The desktop shell has two forms: the Blend app (it needs the `instance` plugin) and a plain Acryl app (`export default app`); `desktop` means the one that fits.\n const plain = !existsSync(join(appDir, 'src', 'plugins', 'instance'))\n const names = options.names\n .filter((name) => !external.includes(name))\n .map((name) => (name === 'desktop' && plain ? 'desktop-web' : name))\n if (names.length === 0) return 0\n const already = names.filter((name) => present(appDir, name))\n if (already.length > 0) {\n io.stderr.write(\n `error: the app already has ${already.map((name) => `src/plugins/${name}`).join(', ')}\\n`,\n )\n return 1\n }\n const toCopy: LibraryPlugin[] = closure(names, readLibrary()).filter(\n (plugin) => !present(appDir, plugin.name),\n )\n const addedDependencies: string[] = []\n for (const plugin of toCopy) {\n copyPlugin(plugin, appDir)\n addedDependencies.push(...mergeDependencies(appDir, plugin.dependencies))\n const tooling = mergeTooling(appDir, plugin)\n appendGitignore(appDir, plugin.gitignore)\n addedDependencies.push(...tooling.devDependencies)\n if (tooling.scripts.length > 0)\n io.stdout.write(\n `new scripts: ${tooling.scripts.map((name) => `npm run ${name}`).join(', ')}\\n`,\n )\n }\n io.stdout.write(`added ${toCopy.map((plugin) => plugin.name).join(', ')} to ${appDir}\\n`)\n if (addedDependencies.length > 0)\n io.stdout.write(`new dependencies: ${addedDependencies.join(', ')} (run npm install)\\n`)\n for (const plugin of toCopy.filter((plugin) => names.includes(plugin.name))) {\n io.stdout.write(\n `\\n${plugin.name}: ${plugin.mount ?? `import it from './plugins/${plugin.name}/index.ts' and mount it in src/app.ts`}\\n`,\n )\n }\n return 0\n}\n\nconst PLUGIN_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/\n\n/**\n * Bring an external plugin folder (a package made with the builder's package_plugin, or any folder with an index.ts that exports a\n * Cordis plugin) into the app's `extensions/`, where the extensions plugin installs it like any other: checked, staged, started,\n * verified. Its runtime dependencies are not installed here; they are reported so the user adds them deliberately.\n */\nfunction addExternal(folder: string, appDir: string, io: Io): number {\n if (!existsSync(folder) || !statSync(folder).isDirectory()) {\n io.stderr.write(`error: ${folder} is not a folder\\n`)\n return 1\n }\n if (!['index.ts', 'index.js', 'index.mjs'].some((entry) => existsSync(join(folder, entry)))) {\n io.stderr.write(`error: ${folder} has no index.ts that exports a Cordis plugin\\n`)\n return 1\n }\n const manifestFile = join(folder, 'package.json')\n const manifest = existsSync(manifestFile)\n ? (JSON.parse(readFileSync(manifestFile, 'utf8')) as {\n name?: string\n dependencies?: Record<string, string>\n })\n : {}\n const name = (manifest.name ?? basename(folder)).replace(/^@[^/]+\\//, '')\n if (!PLUGIN_NAME.test(name)) {\n io.stderr.write(\n `error: \"${name}\" is not a plugin name (lowercase letters, digits and dashes)\\n`,\n )\n return 1\n }\n const target = join(appDir, 'extensions', name)\n if (existsSync(target)) {\n io.stderr.write(`error: the app already has extensions/${name}\\n`)\n return 1\n }\n cpSync(folder, target, {\n recursive: true,\n filter: (path) => !/[\\\\/](node_modules|\\.git)([\\\\/]|$)/.test(path.slice(folder.length)),\n })\n io.stdout.write(\n `added external plugin ${name} to ${join(appDir, 'extensions')}; it installs at the next start\\n`,\n )\n const dependencies = Object.keys(manifest.dependencies ?? {})\n if (dependencies.length > 0)\n io.stdout.write(\n `it uses ${dependencies.join(', ')}: add them to the app with npm install first\\n`,\n )\n return 0\n}\n\n/** `acrylblends plugins`: the library, one line per plugin. */\nexport function listPlugins(io: Io): number {\n for (const plugin of readLibrary().values()) {\n const requires = plugin.requires.length > 0 ? ` (requires ${plugin.requires.join(', ')})` : ''\n io.stdout.write(`${plugin.name.padEnd(12)} ${plugin.description}${requires}\\n`)\n }\n return 0\n}\n","import { existsSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nexport interface Invocation {\n file: string\n args: string[]\n}\n\n/**\n * How to start a package manager without the shell option (Node warns about passing args to a shell, DEP0190, and a `.cmd` shim cannot be spawned directly).\n * Off Windows the command runs as it is. On Windows npm runs as `node npm-cli.js` (the one npm is running from, else the one beside `node`); any other\n * manager goes through `cmd.exe /d /s /c <name>.cmd`, whose arguments stay an array.\n */\nexport function packageManagerInvocation(\n command: string,\n args: readonly string[],\n platform: NodeJS.Platform = process.platform,\n env: NodeJS.ProcessEnv = process.env,\n exists: (path: string) => boolean = existsSync,\n execPath: string = process.execPath,\n): Invocation {\n if (platform !== 'win32') return { file: command, args: [...args] }\n if (command === 'npm') {\n const running = env.npm_execpath\n const cli =\n running !== undefined && /npm-cli\\.c?js$/.test(running)\n ? running\n : join(dirname(execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js')\n if (exists(cli)) return { file: execPath, args: [cli, ...args] }\n }\n return { file: env.ComSpec ?? 'cmd.exe', args: ['/d', '/s', '/c', `${command}.cmd`, ...args] }\n}\n","import { spawn } from 'node:child_process'\nimport { createInterface } from 'node:readline/promises'\nimport { packageManagerInvocation } from './spawn'\n\n/** Everything the CLI touches outside the filesystem. Tests pass their own. */\nexport interface Io {\n stdout: { write(text: string): unknown }\n stderr: { write(text: string): unknown }\n cwd: string\n /** Ask one question; `undefined` when there is no terminal to ask on. */\n ask: ((question: string) => Promise<string>) | undefined\n /** Install dependencies in a directory; resolves to the exit code. */\n install(directory: string): Promise<number>\n}\n\n/** The real terminal and `npm install`. */\nexport function processIo(): Io {\n const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY)\n return {\n stdout: process.stdout,\n stderr: process.stderr,\n cwd: process.cwd(),\n ask: interactive\n ? async (question) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n try {\n return await rl.question(question)\n } finally {\n rl.close()\n }\n }\n : undefined,\n install: (directory) =>\n new Promise((done) => {\n const { file, args } = packageManagerInvocation('npm', ['install'])\n const child = spawn(file, args, { cwd: directory, stdio: 'inherit' })\n child.on('error', () => done(1))\n child.on('exit', (code) => done(code ?? 1))\n }),\n }\n}\n","import { createRequire } from 'node:module'\nimport { add, listPlugins, parseAddOptions } from './add'\nimport { init, parseInitOptions, UsageError } from './init'\nimport { type Io, processIo } from './io'\n\n/** The version of this package. */\nexport const VERSION: string = (\n createRequire(import.meta.url)('../package.json') as { version: string }\n).version\n\nexport const HELP = `acrylblends ${VERSION} - scaffold Cordis apps\n\nUsage:\n acrylblends init [dir] [options] Create a Cordis app in an empty or new directory\n acrylblends add <plugin...> [--dir d] Copy library plugins (and what they require) into an app;\n a folder path brings an external plugin into its extensions/\n acrylblends plugins List the plugin library\n\nOptions for init:\n --template <name> blank or agent (asked when not given); --agent means --template agent,\n --no-agent means --template blank\n --name <name> the project name (default: the directory name)\n --desktop also add the desktop shell: npm run desktop opens a window on the app\n --install run npm install after copying\n -y, --yes ask nothing; use defaults (the blank template, no install)\n\n -h, --help show this help\n -v, --version show the version\n`\n\n/**\n * Run the CLI and return the exit code: 0 done, 1 failed or refused, 2 a usage error. Never calls `process.exit`.\n */\nexport async function runCli(argv: readonly string[], io: Io = processIo()): Promise<number> {\n const [command, ...args] = argv\n try {\n if (command === undefined || command === 'help' || command === '-h' || command === '--help') {\n io.stdout.write(HELP)\n return command === undefined ? 2 : 0\n }\n if (command === 'version' || command === '-v' || command === '--version') {\n io.stdout.write(`${VERSION}\\n`)\n return 0\n }\n if (command === 'init') return await init(parseInitOptions(args), io)\n if (command === 'add') return await add(parseAddOptions(args), io)\n if (command === 'plugins') return listPlugins(io)\n throw new UsageError(`unknown command \"${command}\"`)\n } catch (error) {\n if (error instanceof UsageError) {\n io.stderr.write(`error: ${error.message}\\n\\n${HELP}`)\n return 2\n }\n io.stderr.write(`error: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["parse"],"sources":["../src/library.ts","../src/project.ts","../src/templates.ts","../src/init.ts","../src/add.ts","../src/build-client.ts","../src/spawn.ts","../src/io.ts","../src/cli.ts"],"sourcesContent":["import { cpSync, existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { dirname, join, sep } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n/** One entry of the plugin library: `plugins/<name>/plugin.json` in this repository. */\nexport interface LibraryPlugin {\n readonly name: string\n readonly description: string\n /** Library plugins this one imports; they are copied with it. */\n readonly requires: readonly string[]\n /** npm packages an app needs at run time for this plugin. */\n readonly dependencies: Readonly<Record<string, string>>\n /** npm packages an app needs only to develop or run this plugin's tooling (for example `electron` for the desktop shell). */\n readonly devDependencies?: Readonly<Record<string, string>>\n /** `package.json` scripts the plugin adds; a script the app already has is kept. */\n readonly scripts?: Readonly<Record<string, string>>\n /** Lines the app's `.gitignore` needs for this plugin (added when missing, if the app has a `.gitignore`). */\n readonly gitignore?: readonly string[]\n /** How to mount it, shown after `acrylblends add`. */\n readonly mount?: string\n /**\n * Paths inside the plugin folder that are not copied into an app (for example a React hook an app without React cannot type-check, or colocated test\n * files). A pattern is an exact path or a glob: `*` matches within one folder name, `**` across folders, `?` one character; `**` followed by a slash also matches no folder.\n */\n readonly omit?: readonly string[]\n}\n\n/** A glob as a regular expression: `*` within a name, `**` across folders, `**` plus a slash may match no folder, `?` one character. */\nfunction globToRegExp(glob: string): RegExp {\n let source = ''\n for (let i = 0; i < glob.length; i += 1) {\n const c = glob[i] as string\n if (c === '*' && glob[i + 1] === '*') {\n if (glob[i + 2] === '/') {\n source += '(?:.*/)?'\n i += 2\n } else {\n source += '.*'\n i += 1\n }\n } else if (c === '*') source += '[^/]*'\n else if (c === '?') source += '[^/]'\n else source += c.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n }\n return new RegExp(`^${source}$`)\n}\n\n/** Whether a path inside a plugin folder (relative, any separator) is named by one of its `omit` patterns. */\nexport function isOmitted(inside: string, patterns: readonly string[] | undefined): boolean {\n if (!patterns || patterns.length === 0) return false\n const path = inside.split(sep).join('/')\n return patterns.some((pattern) =>\n /[*?]/.test(pattern) ? globToRegExp(pattern).test(path) : pattern === path,\n )\n}\n\n/** Files of a library plugin that stay in the repository: its manifest and its tests. */\nconst LIBRARY_ONLY = new Set(['plugin.json', 'test', 'tests'])\n\nexport class LibraryError extends Error {\n override name = 'LibraryError'\n}\n\n/** The library folder (`plugins/` at the repository root): walk up from this module, in source or in the build. */\nexport function libraryRoot(): string {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (;;) {\n const candidate = join(dir, 'plugins')\n if (existsSync(join(candidate, 'instance', 'plugin.json'))) return candidate\n const parent = dirname(dir)\n if (parent === dir)\n throw new LibraryError('acrylblends cannot find its plugin library (plugins/)')\n dir = parent\n }\n}\n\n/** Every plugin in the library, by name. */\nexport function readLibrary(root: string = libraryRoot()): Map<string, LibraryPlugin> {\n const plugins = new Map<string, LibraryPlugin>()\n for (const entry of readdirSync(root, { withFileTypes: true })) {\n const manifest = join(root, entry.name, 'plugin.json')\n if (!entry.isDirectory() || !existsSync(manifest)) continue\n const plugin = JSON.parse(readFileSync(manifest, 'utf8')) as LibraryPlugin\n if (plugin.name !== entry.name)\n throw new LibraryError(`plugins/${entry.name}/plugin.json names \"${plugin.name}\"`)\n plugins.set(plugin.name, {\n ...plugin,\n requires: plugin.requires ?? [],\n dependencies: plugin.dependencies ?? {},\n })\n }\n return plugins\n}\n\n/** The plugins to copy for `names`: each one after everything it requires, no duplicates. */\nexport function closure(\n names: readonly string[],\n library: Map<string, LibraryPlugin>,\n): LibraryPlugin[] {\n const ordered: LibraryPlugin[] = []\n const visiting = new Set<string>()\n const visit = (name: string, trail: string[]) => {\n if (ordered.some((plugin) => plugin.name === name)) return\n const plugin = library.get(name)\n if (!plugin)\n throw new LibraryError(\n `no plugin \"${name}\" in the library${trail.length ? ` (required by ${trail.at(-1)})` : ''}`,\n )\n if (visiting.has(name))\n throw new LibraryError(\n `plugins require each other in a cycle: ${[...trail, name].join(' -> ')}`,\n )\n visiting.add(name)\n for (const required of plugin.requires) visit(required, [...trail, name])\n visiting.delete(name)\n ordered.push(plugin)\n }\n for (const name of names) visit(name, [])\n return ordered\n}\n\n/** Copy one library plugin's source into `<app>/src/plugins/<name>/`. Throws when that folder already exists. */\nexport function copyPlugin(\n plugin: LibraryPlugin,\n appDir: string,\n root: string = libraryRoot(),\n): string {\n const target = join(appDir, 'src', 'plugins', plugin.name)\n if (existsSync(target))\n throw new LibraryError(\n `src/plugins/${plugin.name} already exists; remove it first to copy a fresh one`,\n )\n const source = join(root, plugin.name)\n cpSync(source, target, {\n recursive: true,\n filter: (path) => {\n const inside = path.slice(source.length + 1)\n return (\n !(dirname(path) === source && LIBRARY_ONLY.has(inside)) && !isOmitted(inside, plugin.omit)\n )\n },\n })\n return target\n}\n","import {\n cpSync,\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n statSync,\n writeFileSync,\n} from 'node:fs'\nimport { join } from 'node:path'\n\n/** Thrown when the target directory exists and is not empty. Nothing has been written. */\nexport class TargetNotEmptyError extends Error {\n constructor(readonly target: string) {\n super(\n `refusing to write into \"${target}\": the directory is not empty. Choose an empty or new directory.`,\n )\n this.name = 'TargetNotEmptyError'\n }\n}\n\n/** A project name npm accepts: lower case letters, digits and dashes, starting with a letter. */\nexport function isValidName(name: string): boolean {\n return /^[a-z][a-z0-9-]*$/.test(name) && name.length <= 214\n}\n\n/** A valid default project name derived from a directory path. */\nexport function nameFrom(directory: string): string {\n const base = directory.split(/[\\\\/]/).filter(Boolean).pop() ?? ''\n const cleaned = base\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, '-')\n .replace(/^[^a-z]+/, '')\n .replace(/-+$/, '')\n return cleaned || 'my-app'\n}\n\n/** Refuse a target that exists and is not an empty directory. A missing target is fine: it is created. */\nexport function assertTargetFree(target: string): void {\n if (!existsSync(target)) return\n if (!statSync(target).isDirectory() || readdirSync(target).length > 0)\n throw new TargetNotEmptyError(target)\n}\n\n/**\n * Copy a template into `target`, replacing `__APP_NAME__` with `name`. Checks the target before writing anything.\n *\n * @returns the copied files, relative to `target`\n */\nexport function copyTemplate(source: string, target: string, name: string): string[] {\n assertTargetFree(target)\n mkdirSync(target, { recursive: true })\n const written: string[] = []\n const walk = (from: string, to: string, prefix: string) => {\n for (const entry of readdirSync(from, { withFileTypes: true })) {\n if (\n entry.name === 'node_modules' ||\n (prefix === '' &&\n (entry.name === TEMPLATE_MANIFEST ||\n entry.name === 'upstream.json' ||\n entry.name === 'AGENTS.md'))\n )\n continue\n const sourcePath = join(from, entry.name)\n // npm and git treat a file named .gitignore specially, so templates keep it as `gitignore` (and `dockerignore`).\n const targetName =\n prefix === '' && (entry.name === 'gitignore' || entry.name === 'dockerignore')\n ? `.${entry.name}`\n : entry.name\n const targetPath = join(to, targetName)\n if (entry.isDirectory()) {\n mkdirSync(targetPath, { recursive: true })\n walk(sourcePath, targetPath, `${prefix}${entry.name}/`)\n continue\n }\n const text = readFileSync(sourcePath, 'utf8')\n if (text.includes('__APP_NAME__'))\n writeFileSync(targetPath, text.replaceAll('__APP_NAME__', name))\n else cpSync(sourcePath, targetPath)\n written.push(`${prefix}${targetName}`)\n }\n }\n walk(source, target, '')\n return written\n}\n\n/** A template's own manifest, `template.json`: the library plugins it is built from. Never copied (nor is `upstream.json`, the record of what was synced from ACRYL, nor the template's `AGENTS.md`, which `writeAgentFiles` joins to the shared core). */\nexport const TEMPLATE_MANIFEST = 'template.json'\n\n/** The address a template's desktop window opens on, when it is not the root (`template.json`: `desktopPath`). */\nexport function readTemplateDesktopPath(templateDir: string): string | undefined {\n const file = join(templateDir, TEMPLATE_MANIFEST)\n if (!existsSync(file)) return undefined\n return (JSON.parse(readFileSync(file, 'utf8')) as { desktopPath?: string }).desktopPath\n}\n\n/** Record where the desktop window opens in package.json (`acrylblends.desktopPath`), read by `src/plugins/desktop/main.ts`. */\nexport function writeDesktopPath(appDir: string, path: string): void {\n const file = join(appDir, 'package.json')\n const manifest = JSON.parse(readFileSync(file, 'utf8')) as {\n acrylblends?: Record<string, unknown>\n }\n manifest.acrylblends = { ...manifest.acrylblends, desktopPath: path }\n writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\\n`)\n}\n\nexport function readTemplatePlugins(templateDir: string): string[] {\n const file = join(templateDir, TEMPLATE_MANIFEST)\n if (!existsSync(file)) return []\n const { plugins } = JSON.parse(readFileSync(file, 'utf8')) as { plugins?: string[] }\n return plugins ?? []\n}\n\n/** Add run-time dependencies to an app's package.json, keeping what is there and sorting the keys. Returns the names added. */\nexport function mergeDependencies(\n appDir: string,\n dependencies: Readonly<Record<string, string>>,\n): string[] {\n const file = join(appDir, 'package.json')\n const manifest = JSON.parse(readFileSync(file, 'utf8')) as {\n dependencies?: Record<string, string>\n }\n const current = manifest.dependencies ?? {}\n const added = Object.keys(dependencies).filter((name) => !(name in current))\n const merged = { ...dependencies, ...current }\n manifest.dependencies = Object.fromEntries(\n Object.entries(merged).sort(([a], [b]) => a.localeCompare(b)),\n )\n writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\\n`)\n return added\n}\n\n/**\n * Add a plugin's development dependencies and scripts to an app's package.json: nothing the app already has is replaced. Returns the names of the development dependencies\n * and scripts added.\n */\nexport function mergeTooling(\n appDir: string,\n tooling: {\n devDependencies?: Readonly<Record<string, string>> | undefined\n scripts?: Readonly<Record<string, string>> | undefined\n },\n): { devDependencies: string[]; scripts: string[] } {\n const file = join(appDir, 'package.json')\n const manifest = JSON.parse(readFileSync(file, 'utf8')) as {\n devDependencies?: Record<string, string>\n scripts?: Record<string, string>\n }\n const dev = manifest.devDependencies ?? {}\n const scripts = manifest.scripts ?? {}\n const addedDev = Object.keys(tooling.devDependencies ?? {}).filter((name) => !(name in dev))\n const addedScripts = Object.keys(tooling.scripts ?? {}).filter((name) => !(name in scripts))\n manifest.devDependencies = Object.fromEntries(\n Object.entries({ ...tooling.devDependencies, ...dev }).sort(([a], [b]) => a.localeCompare(b)),\n )\n manifest.scripts = { ...tooling.scripts, ...scripts }\n writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\\n`)\n return { devDependencies: addedDev, scripts: addedScripts }\n}\n\n/** Add the lines a plugin needs to the app's `.gitignore` when it has one and they are missing. Returns the lines added. */\nexport function appendGitignore(appDir: string, lines: readonly string[] | undefined): string[] {\n const file = join(appDir, '.gitignore')\n if (!lines || lines.length === 0 || !existsSync(file)) return []\n const current = readFileSync(file, 'utf8')\n const present = new Set(current.split('\\n').map((line) => line.trim()))\n const missing = lines.filter((line) => !present.has(line))\n if (missing.length > 0)\n writeFileSync(\n file,\n `${current}${current.endsWith('\\n') || current === '' ? '' : '\\n'}${missing.join('\\n')}\\n`,\n )\n return missing\n}\n\n/** The generated app's guide for a coding agent: the shared core, then the template's own part. `CLAUDE.md` is only an import of it. */\nexport function writeAgentFiles(\n sharedDir: string,\n templateDir: string,\n target: string,\n name: string,\n): string[] {\n const core = readFileSync(join(sharedDir, 'AGENTS.core.md'), 'utf8')\n const own = existsSync(join(templateDir, 'AGENTS.md'))\n ? readFileSync(join(templateDir, 'AGENTS.md'), 'utf8')\n : ''\n writeFileSync(join(target, 'AGENTS.md'), `${core}${own}`.replaceAll('__APP_NAME__', name))\n writeFileSync(join(target, 'CLAUDE.md'), '@AGENTS.md\\n')\n return ['AGENTS.md', 'CLAUDE.md']\n}\n\n/**\n * Copy the framework's docs (a generated copy of ACRYL's `docs/framework`, already checked against its digest) into `<app>/docs/framework/`,\n * so a coding agent working in the app reads the API from the folder it is in. The generated-copy notes and the digest record stay behind.\n *\n * @returns the copied files, relative to `target`\n */\nexport function copyFrameworkDocs(source: string, target: string): string[] {\n const written: string[] = []\n const walk = (from: string, to: string, prefix: string) => {\n for (const entry of readdirSync(from, { withFileTypes: true })) {\n if (entry.name === 'upstream.json' || entry.name === 'UPSTREAM.md') continue\n const sourcePath = join(from, entry.name)\n const targetPath = join(to, entry.name)\n if (entry.isDirectory()) {\n mkdirSync(targetPath, { recursive: true })\n walk(sourcePath, targetPath, `${prefix}${entry.name}/`)\n continue\n }\n cpSync(sourcePath, targetPath)\n written.push(`docs/framework/${prefix}${entry.name}`)\n }\n }\n mkdirSync(join(target, 'docs', 'framework'), { recursive: true })\n walk(source, join(target, 'docs', 'framework'), '')\n return written\n}\n","import { existsSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n/** The templates: a blank Cordis app on the router, a todo list built only on the router and state, and the blank app with the offline agent. */\nexport const TEMPLATES = ['blank', 'todo', 'agent'] as const\nexport type Template = (typeof TEMPLATES)[number]\n\nexport function isTemplate(value: string): value is Template {\n return (TEMPLATES as readonly string[]).includes(value)\n}\n\n/**\n * The `templates/` folder of this package. The module sits at different depths in source (`src/`) and in the build\n * (`dist/`), so walk up until a `templates` folder holds the `blank` template.\n */\nexport function templatesRoot(): string {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (;;) {\n const candidate = join(dir, 'templates')\n if (existsSync(join(candidate, 'blank'))) return candidate\n const parent = dirname(dir)\n if (parent === dir) throw new Error('acrylblends cannot find its templates folder')\n dir = parent\n }\n}\n\n/** Absolute path of a template folder. */\nexport function templateDir(template: Template): string {\n return join(templatesRoot(), template)\n}\n\n/** The text every generated app's `AGENTS.md` starts with (`shared/`, beside `templates/`). */\nexport function sharedDir(): string {\n return join(dirname(templatesRoot()), 'shared')\n}\n\n/**\n * The framework's docs, as ACRYL wrote them: `docs/framework` of the repository, or the copy `prepack` puts inside the package. The module sits at\n * different depths in source and in the build, so walk up until a `docs/framework` folder holds the README.\n */\nexport function frameworkDocsRoot(): string {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (;;) {\n const candidate = join(dir, 'docs', 'framework')\n if (existsSync(join(candidate, 'README.md'))) return candidate\n const parent = dirname(dir)\n if (parent === dir)\n throw new Error('acrylblends cannot find the framework docs (docs/framework)')\n dir = parent\n }\n}\n","import { resolve } from 'node:path'\nimport { parseArgs } from 'node:util'\nimport type { Io } from './io'\nimport { closure, copyPlugin, readLibrary } from './library'\nimport {\n copyFrameworkDocs,\n copyTemplate,\n isValidName,\n mergeDependencies,\n mergeTooling,\n nameFrom,\n readTemplateDesktopPath,\n readTemplatePlugins,\n TargetNotEmptyError,\n writeAgentFiles,\n writeDesktopPath,\n} from './project'\nimport {\n frameworkDocsRoot,\n isTemplate,\n sharedDir,\n TEMPLATES,\n type Template,\n templateDir,\n} from './templates'\n\n/** A command line mistake. The CLI prints it with the help text and exits 2. */\nexport class UsageError extends Error {\n override name = 'UsageError'\n}\n\n/** What `init` was asked for. Anything left undefined is asked for on a terminal, or defaulted. */\nexport interface InitOptions {\n directory: string | undefined\n name: string | undefined\n template: Template | undefined\n install: boolean | undefined\n /** Also add the desktop shell (`add desktop`): an Electron window on the app's own server. */\n desktop: boolean\n yes: boolean\n}\n\n/** Parse `init [dir] [--template t] [--agent|--no-agent] [--name n] [--install] [-y]`. `--agent` means `--template agent`, `--no-agent` means `--template blank`. */\nexport function parseInitOptions(argv: readonly string[]): InitOptions {\n let parsed: ReturnType<typeof parse>\n try {\n parsed = parse(argv)\n } catch (error) {\n throw new UsageError((error as Error).message)\n }\n const { values, positionals } = parsed\n if (positionals.length > 1) throw new UsageError('init takes at most one directory')\n if (values.agent && values['no-agent'])\n throw new UsageError('use --agent or --no-agent, not both')\n if (values.template !== undefined && !isTemplate(values.template))\n throw new UsageError(`unknown template \"${values.template}\" (${TEMPLATES.join(', ')})`)\n const alias: Template | undefined = values.agent\n ? 'agent'\n : values['no-agent']\n ? 'blank'\n : undefined\n if (values.template !== undefined && alias !== undefined && values.template !== alias)\n throw new UsageError(\n `--template ${values.template} contradicts --${alias === 'agent' ? 'agent' : 'no-agent'}`,\n )\n return {\n directory: positionals[0],\n name: values.name,\n template: (values.template as Template | undefined) ?? alias,\n install: values.install,\n desktop: values.desktop === true,\n yes: values.yes === true,\n }\n}\n\nconst parse = (argv: readonly string[]) =>\n parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: {\n name: { type: 'string' },\n template: { type: 'string' },\n agent: { type: 'boolean' },\n 'no-agent': { type: 'boolean' },\n install: { type: 'boolean' },\n desktop: { type: 'boolean' },\n yes: { type: 'boolean', short: 'y' },\n },\n })\n\nconst yes = (answer: string) => ['y', 'yes'].includes(answer.trim().toLowerCase())\n\nasync function askTemplate(ask: (question: string) => Promise<string>): Promise<Template> {\n const answer = (await ask(`Template (${TEMPLATES.join(', ')}) [blank]: `)).trim().toLowerCase()\n if (answer === '') return 'blank'\n if (isTemplate(answer)) return answer\n throw new UsageError(`unknown template \"${answer}\" (${TEMPLATES.join(', ')})`)\n}\n\n/** Scaffold a Cordis app. Returns the exit code: 0 done, 1 refused or failed. Never overwrites anything. */\nexport async function init(options: InitOptions, io: Io): Promise<number> {\n const relative = options.directory ?? '.'\n const directory = resolve(io.cwd, relative)\n const ask = options.yes ? undefined : io.ask\n\n let name = options.name ?? nameFrom(directory)\n if (options.name === undefined && ask)\n name = (await ask(`Project name (${name}): `)).trim() || name\n if (!isValidName(name)) {\n throw new UsageError(\n `\"${name}\" is not a valid project name (lower case letters, digits and dashes, starting with a letter)`,\n )\n }\n const template = options.template ?? (ask ? await askTemplate(ask) : 'blank')\n const install =\n options.install ?? (ask ? yes(await ask('Install dependencies with npm now? (y/N): ')) : false)\n\n let files: string[]\n try {\n files = copyTemplate(templateDir(template), directory, name)\n files.push(...writeAgentFiles(sharedDir(), templateDir(template), directory, name))\n files.push(...copyFrameworkDocs(frameworkDocsRoot(), directory))\n } catch (error) {\n if (!(error instanceof TargetNotEmptyError)) throw error\n io.stderr.write(`error: ${error.message}\\n`)\n return 1\n }\n const plugins = closure(\n [...readTemplatePlugins(templateDir(template)), ...(options.desktop ? ['desktop'] : [])],\n readLibrary(),\n )\n for (const plugin of plugins) {\n copyPlugin(plugin, directory)\n mergeDependencies(directory, plugin.dependencies)\n mergeTooling(directory, plugin)\n }\n const desktopPath = options.desktop ? readTemplateDesktopPath(templateDir(template)) : undefined\n if (desktopPath !== undefined) writeDesktopPath(directory, desktopPath)\n io.stdout.write(\n `created ${name} (${template}) in ${directory}: ${files.length} files, plugins ${plugins.map((plugin) => plugin.name).join(', ')}\\n`,\n )\n\n if (install && (await io.install(directory)) !== 0) {\n io.stderr.write('error: npm install failed; run it yourself in the new directory\\n')\n return 1\n }\n const steps = [\n relative === '.' ? '' : ` cd ${relative}\\n`,\n install ? '' : ' npm install\\n',\n ' npm run dev\\n npm test\\n',\n ]\n io.stdout.write(`\\nNext:\\n${steps.join('')}`)\n return 0\n}\n","import { cpSync, existsSync, readFileSync, statSync } from 'node:fs'\nimport { basename, join, resolve } from 'node:path'\nimport { parseArgs } from 'node:util'\nimport { UsageError } from './init'\nimport type { Io } from './io'\nimport { closure, copyPlugin, type LibraryPlugin, readLibrary } from './library'\nimport { appendGitignore, mergeDependencies, mergeTooling } from './project'\n\nexport interface AddOptions {\n readonly names: readonly string[]\n readonly directory: string | undefined\n}\n\n/** Parse `add <plugin...> [--dir <app>]`. */\nexport function parseAddOptions(argv: readonly string[]): AddOptions {\n let parsed: ReturnType<typeof parse>\n try {\n parsed = parse(argv)\n } catch (error) {\n throw new UsageError((error as Error).message)\n }\n if (parsed.positionals.length === 0)\n throw new UsageError('add needs at least one plugin name (see \"acrylblends plugins\")')\n return { names: parsed.positionals, directory: parsed.values.dir }\n}\n\nconst parse = (argv: readonly string[]) =>\n parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: { dir: { type: 'string' } },\n })\n\nconst present = (appDir: string, name: string) => existsSync(join(appDir, 'src', 'plugins', name))\n\n/**\n * Copy library plugins into an existing app, with the plugins they require, and add their npm packages to its package.json.\n * Refuses a plugin the app already has. Returns the exit code.\n */\nexport async function add(options: AddOptions, io: Io): Promise<number> {\n const appDir = resolve(io.cwd, options.directory ?? '.')\n if (!existsSync(join(appDir, 'package.json')) || !existsSync(join(appDir, 'src'))) {\n io.stderr.write(\n `error: ${appDir} is not an app (no package.json and src/); run \"acrylblends init\" first\\n`,\n )\n return 1\n }\n // A path is an external plugin folder; anything else is a library plugin name.\n const external = options.names.filter((name) => /[\\\\/]/.test(name) || name.startsWith('.'))\n for (const folder of external) {\n const code = addExternal(resolve(io.cwd, folder), appDir, io)\n if (code !== 0) return code\n }\n // The desktop shell has two forms: the Blend app (it needs the `instance` plugin) and a plain Acryl app (`export default app`); `desktop` means the one that fits.\n const plain = !existsSync(join(appDir, 'src', 'plugins', 'instance'))\n const names = options.names\n .filter((name) => !external.includes(name))\n .map((name) => (name === 'desktop' && plain ? 'desktop-web' : name))\n if (names.length === 0) return 0\n const already = names.filter((name) => present(appDir, name))\n if (already.length > 0) {\n io.stderr.write(\n `error: the app already has ${already.map((name) => `src/plugins/${name}`).join(', ')}\\n`,\n )\n return 1\n }\n const toCopy: LibraryPlugin[] = closure(names, readLibrary()).filter(\n (plugin) => !present(appDir, plugin.name),\n )\n const addedDependencies: string[] = []\n for (const plugin of toCopy) {\n copyPlugin(plugin, appDir)\n addedDependencies.push(...mergeDependencies(appDir, plugin.dependencies))\n const tooling = mergeTooling(appDir, plugin)\n appendGitignore(appDir, plugin.gitignore)\n addedDependencies.push(...tooling.devDependencies)\n if (tooling.scripts.length > 0)\n io.stdout.write(\n `new scripts: ${tooling.scripts.map((name) => `npm run ${name}`).join(', ')}\\n`,\n )\n }\n io.stdout.write(`added ${toCopy.map((plugin) => plugin.name).join(', ')} to ${appDir}\\n`)\n if (addedDependencies.length > 0)\n io.stdout.write(`new dependencies: ${addedDependencies.join(', ')} (run npm install)\\n`)\n for (const plugin of toCopy.filter((plugin) => names.includes(plugin.name))) {\n io.stdout.write(\n `\\n${plugin.name}: ${plugin.mount ?? `import it from './plugins/${plugin.name}/index.ts' and mount it in src/app.ts`}\\n`,\n )\n }\n return 0\n}\n\nconst PLUGIN_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/\n\n/**\n * Bring an external plugin folder (a package made with the builder's package_plugin, or any folder with an index.ts that exports a\n * Cordis plugin) into the app's `extensions/`, where the extensions plugin installs it like any other: checked, staged, started,\n * verified. Its runtime dependencies are not installed here; they are reported so the user adds them deliberately.\n */\nfunction addExternal(folder: string, appDir: string, io: Io): number {\n if (!existsSync(folder) || !statSync(folder).isDirectory()) {\n io.stderr.write(`error: ${folder} is not a folder\\n`)\n return 1\n }\n if (!['index.ts', 'index.js', 'index.mjs'].some((entry) => existsSync(join(folder, entry)))) {\n io.stderr.write(`error: ${folder} has no index.ts that exports a Cordis plugin\\n`)\n return 1\n }\n const manifestFile = join(folder, 'package.json')\n const manifest = existsSync(manifestFile)\n ? (JSON.parse(readFileSync(manifestFile, 'utf8')) as {\n name?: string\n dependencies?: Record<string, string>\n })\n : {}\n const name = (manifest.name ?? basename(folder)).replace(/^@[^/]+\\//, '')\n if (!PLUGIN_NAME.test(name)) {\n io.stderr.write(\n `error: \"${name}\" is not a plugin name (lowercase letters, digits and dashes)\\n`,\n )\n return 1\n }\n const target = join(appDir, 'extensions', name)\n if (existsSync(target)) {\n io.stderr.write(`error: the app already has extensions/${name}\\n`)\n return 1\n }\n cpSync(folder, target, {\n recursive: true,\n filter: (path) => !/[\\\\/](node_modules|\\.git)([\\\\/]|$)/.test(path.slice(folder.length)),\n })\n io.stdout.write(\n `added external plugin ${name} to ${join(appDir, 'extensions')}; it installs at the next start\\n`,\n )\n const dependencies = Object.keys(manifest.dependencies ?? {})\n if (dependencies.length > 0)\n io.stdout.write(\n `it uses ${dependencies.join(', ')}: add them to the app with npm install first\\n`,\n )\n return 0\n}\n\n/** `acrylblends plugins`: the library, one line per plugin. */\nexport function listPlugins(io: Io): number {\n for (const plugin of readLibrary().values()) {\n const requires = plugin.requires.length > 0 ? ` (requires ${plugin.requires.join(', ')})` : ''\n io.stdout.write(`${plugin.name.padEnd(12)} ${plugin.description}${requires}\\n`)\n }\n return 0\n}\n","import { existsSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { relative, resolve } from 'node:path'\nimport { UsageError } from './init'\nimport type { Io } from './io'\n\n/** Parse `build-client [root] [outDir]`. */\nexport function parseBuildClientOptions(argv: readonly string[]): { root: string; outDir: string } {\n const args = argv.filter((a) => !a.startsWith('-'))\n if (args.length !== argv.length)\n throw new UsageError('build-client takes [root] [outDir] and no options')\n if (args.length > 2)\n throw new UsageError('build-client takes at most two arguments: [root] [outDir]')\n return { root: args[0] ?? 'src/client', outDir: args[1] ?? 'dist/client' }\n}\n\n/**\n * Compile the app's client modules once (`buildClientModules` of the framework), for a production install that has no compiler at run time.\n * The framework is the one installed in the app (so the compiled files match the server that serves them), else this package's own.\n */\nexport async function buildClient(\n options: { root: string; outDir: string },\n io: Io,\n): Promise<number> {\n const root = resolve(io.cwd, options.root)\n if (!existsSync(root)) {\n io.stderr.write(\n `error: ${root} does not exist; the client modules live in src/client by default\\n`,\n )\n return 1\n }\n const outDir = resolve(io.cwd, options.outDir)\n const here = createRequire(resolve(io.cwd, 'package.json'))\n let specifier = 'acrylblends/node-server'\n try {\n here.resolve(specifier)\n } catch {\n specifier = new URL('../lib/node-server.js', import.meta.url).href\n }\n const mod = (await import(\n specifier.startsWith('file:') ? specifier : here.resolve(specifier)\n )) as typeof import('../lib/node-server')\n const written = mod.buildClientModules({ root, outDir })\n for (const file of written) io.stdout.write(`${relative(io.cwd, resolve(outDir, file))}\\n`)\n io.stdout.write(`built ${written.length} files in ${relative(io.cwd, outDir) || '.'}\\n`)\n return 0\n}\n","import { existsSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nexport interface Invocation {\n file: string\n args: string[]\n}\n\n/**\n * How to start a package manager without the shell option (Node warns about passing args to a shell, DEP0190, and a `.cmd` shim cannot be spawned directly).\n * Off Windows the command runs as it is. On Windows npm runs as `node npm-cli.js` (the one npm is running from, else the one beside `node`); any other\n * manager goes through `cmd.exe /d /s /c <name>.cmd`, whose arguments stay an array.\n */\nexport function packageManagerInvocation(\n command: string,\n args: readonly string[],\n platform: NodeJS.Platform = process.platform,\n env: NodeJS.ProcessEnv = process.env,\n exists: (path: string) => boolean = existsSync,\n execPath: string = process.execPath,\n): Invocation {\n if (platform !== 'win32') return { file: command, args: [...args] }\n if (command === 'npm') {\n const running = env.npm_execpath\n const cli =\n running !== undefined && /npm-cli\\.c?js$/.test(running)\n ? running\n : join(dirname(execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js')\n if (exists(cli)) return { file: execPath, args: [cli, ...args] }\n }\n return { file: env.ComSpec ?? 'cmd.exe', args: ['/d', '/s', '/c', `${command}.cmd`, ...args] }\n}\n","import { spawn } from 'node:child_process'\nimport { createInterface } from 'node:readline/promises'\nimport { packageManagerInvocation } from './spawn'\n\n/** Everything the CLI touches outside the filesystem. Tests pass their own. */\nexport interface Io {\n stdout: { write(text: string): unknown }\n stderr: { write(text: string): unknown }\n cwd: string\n /** Ask one question; `undefined` when there is no terminal to ask on. */\n ask: ((question: string) => Promise<string>) | undefined\n /** Install dependencies in a directory; resolves to the exit code. */\n install(directory: string): Promise<number>\n}\n\n/** The real terminal and `npm install`. */\nexport function processIo(): Io {\n const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY)\n return {\n stdout: process.stdout,\n stderr: process.stderr,\n cwd: process.cwd(),\n ask: interactive\n ? async (question) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n try {\n return await rl.question(question)\n } finally {\n rl.close()\n }\n }\n : undefined,\n install: (directory) =>\n new Promise((done) => {\n const { file, args } = packageManagerInvocation('npm', ['install'])\n const child = spawn(file, args, { cwd: directory, stdio: 'inherit' })\n child.on('error', () => done(1))\n child.on('exit', (code) => done(code ?? 1))\n }),\n }\n}\n","import { createRequire } from 'node:module'\nimport { add, listPlugins, parseAddOptions } from './add'\nimport { buildClient, parseBuildClientOptions } from './build-client'\nimport { init, parseInitOptions, UsageError } from './init'\nimport { type Io, processIo } from './io'\n\n/** The version of this package. */\nexport const VERSION: string = (\n createRequire(import.meta.url)('../package.json') as { version: string }\n).version\n\nexport const HELP = `acrylblends ${VERSION} - scaffold Cordis apps\n\nUsage:\n acrylblends init [dir] [options] Create a Cordis app in an empty or new directory\n acrylblends add <plugin...> [--dir d] Copy library plugins (and what they require) into an app;\n a folder path brings an external plugin into its extensions/\n acrylblends plugins List the plugin library\n acrylblends build-client [root] [out] Compile the client modules once for production (src/client to dist/client)\n\nOptions for init:\n --template <name> blank or agent (asked when not given); --agent means --template agent,\n --no-agent means --template blank\n --name <name> the project name (default: the directory name)\n --desktop also add the desktop shell: npm run desktop opens a window on the app\n --install run npm install after copying\n -y, --yes ask nothing; use defaults (the blank template, no install)\n\n -h, --help show this help\n -v, --version show the version\n`\n\n/**\n * Run the CLI and return the exit code: 0 done, 1 failed or refused, 2 a usage error. Never calls `process.exit`.\n */\nexport async function runCli(argv: readonly string[], io: Io = processIo()): Promise<number> {\n const [command, ...args] = argv\n try {\n if (command === undefined || command === 'help' || command === '-h' || command === '--help') {\n io.stdout.write(HELP)\n return command === undefined ? 2 : 0\n }\n if (command === 'version' || command === '-v' || command === '--version') {\n io.stdout.write(`${VERSION}\\n`)\n return 0\n }\n if (command === 'init') return await init(parseInitOptions(args), io)\n if (command === 'add') return await add(parseAddOptions(args), io)\n if (command === 'build-client') return await buildClient(parseBuildClientOptions(args), io)\n if (command === 'plugins') return listPlugins(io)\n throw new UsageError(`unknown command \"${command}\"`)\n } catch (error) {\n if (error instanceof UsageError) {\n io.stderr.write(`error: ${error.message}\\n\\n${HELP}`)\n return 2\n }\n io.stderr.write(`error: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n}\n"],"mappings":"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"}
|
package/docs/framework/README.md
CHANGED
|
@@ -48,9 +48,10 @@ They are enabled as they are ported.
|
|
|
48
48
|
| --- | --- |
|
|
49
49
|
| Run something in five minutes | [Getting started](getting-started/basic.md) |
|
|
50
50
|
| Run the app as a desktop app | [Desktop](getting-started/desktop.md) |
|
|
51
|
+
| Run it on a server | [Deploy](getting-started/deploy.md) |
|
|
51
52
|
| Look up a call | [API](api/index.md), [Routing](api/routing.md), [Context](api/context.md), [Request](api/request.md), [API summary](api/summary.md) |
|
|
52
53
|
| Understand the design | [Concepts](concepts/motivation.md) |
|
|
53
|
-
| Copy a pattern | [Guides](guides/best-practices.md), [Validation](guides/validation.md), [Testing](guides/testing.md), [Forms, sessions and a database](guides/forms-and-sessions.md) |
|
|
54
|
+
| Copy a pattern | [Guides](guides/best-practices.md), [Validation](guides/validation.md), [Testing](guides/testing.md), [Forms, sessions and a database](guides/forms-and-sessions.md), [Client components without a bundler](guides/client-modules.md) |
|
|
54
55
|
| Use Acryl inside a Cordis app | [Acryl in a Cordis app](cordis/acryl-in-a-cordis-app.md), [Plugin lifecycle](cordis/plugin-lifecycle.md), [Mounting](cordis/mounting.md) |
|
|
55
56
|
| Move from the 0.3 router calls | [Migration from 0.3](cordis/migration-from-0-3.md) |
|
|
56
57
|
| Move from the 0.4 names (`App`) | [Migration from 0.4](cordis/migration-from-0-4.md) |
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
# Generated copy
|
|
2
2
|
|
|
3
|
-
This folder is a copy of `docs/framework` of acryldev/acryl at commit `
|
|
3
|
+
This folder is a copy of `docs/framework` of acryldev/acryl at commit `cdc083e1bbaedaeafa7494fe646bdb202ce86d09`, made by `scripts/sync-framework.mjs`. Do not edit it here: change the text in ACRYL and sync again. `pnpm run verify:framework` fails when a page differs from the recorded digest.
|
|
@@ -16,8 +16,10 @@ export const name = 'orders' // the package name and the Loader
|
|
|
16
16
|
export const inject = ['router', 'state'] // the plugin waits (PENDING) until both exist
|
|
17
17
|
|
|
18
18
|
interface Order { id: string, item: string }
|
|
19
|
-
const NewOrder: StandardSchema<{ item: string }> = {
|
|
19
|
+
const NewOrder: StandardSchema<unknown, { item: string }> = {
|
|
20
20
|
'~standard': {
|
|
21
|
+
version: 1,
|
|
22
|
+
vendor: 'orders',
|
|
21
23
|
validate: value => {
|
|
22
24
|
const item = (value as { item?: unknown } | null)?.item
|
|
23
25
|
return typeof item === 'string' && item !== '' ? { value: { item } } : { issues: [{ message: 'item is required', path: ['item'] }] }
|
|
@@ -98,8 +100,9 @@ import { validator } from 'acrylblends/validator'
|
|
|
98
100
|
app.post('/', schemaValidator('json', Schema), c => c.json(c.req.valid('json')))
|
|
99
101
|
```
|
|
100
102
|
|
|
101
|
-
- Targets: `json`, `form`, `query`, `param`, `header`, `cookie`. `Schema` is any Standard Schema (Schemastery, Zod, Valibot, or an
|
|
102
|
-
object with `'~standard': { validate }`).
|
|
103
|
+
- Targets: `json`, `form`, `query`, `param`, `header`, `cookie`. `Schema` is any Standard Schema v1 (Schemastery, Zod, Valibot, or an
|
|
104
|
+
object with `'~standard': { version: 1, vendor, validate }`; `version` and `vendor` may be left out, as only `validate` is called).
|
|
105
|
+
`StandardSchema<Input, Output>` is the type of such an object.
|
|
103
106
|
- A failure is 400 `{ error: 'invalid <target>', issues: [{ message, path }] }`; `path` is dotted, and `""` when the issue has none.
|
|
104
107
|
- The `json` target reads the body only with a JSON `Content-Type`; otherwise the schema sees `{}`.
|
|
105
108
|
- `validator(target, (value, c) => valueOrResponse)` is the same middleware with a function instead of a schema.
|
|
@@ -29,6 +29,9 @@ root.inject(['nodeServer'], ctx => {
|
|
|
29
29
|
| `port` | `0` | port to listen on; 0 lets the system choose |
|
|
30
30
|
| `scan` | `20` | how many further ports to try when the configured one is taken |
|
|
31
31
|
|
|
32
|
+
Without Cordis, `serve()` from `acrylblends/node-server` binds the same default, `127.0.0.1`; pass `hostname: '0.0.0.0'` to accept other
|
|
33
|
+
machines (see [Node.js](../getting-started/nodejs.md#listen-on-another-interface)).
|
|
34
|
+
|
|
32
35
|
The plugin becomes active once the server listens, so a plugin that injects `nodeServer` can rely on `ctx.nodeServer.url` and
|
|
33
36
|
`ctx.nodeServer.port`. A failure to bind fails the plugin.
|
|
34
37
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Deploy
|
|
2
|
+
|
|
3
|
+
This page runs an app made by `npm create acrylblends@latest` (the `nodejs` and `notes` templates) on a Linux server with Node.js 22.19 or later: install, start, keep it running, put HTTPS in front, and look after the SQLite file.
|
|
4
|
+
The snippets are examples to adapt: check them against your own server before relying on them.
|
|
5
|
+
|
|
6
|
+
## What the template already does
|
|
7
|
+
|
|
8
|
+
- **Port and address.** `src/index.ts` reads `PORT` (default 3000) and `HOST` (default `127.0.0.1`). The default is local on purpose, and `serve()` without a `hostname` binds `127.0.0.1` too (from acrylblends 0.7.2; earlier versions listened on every interface). In a container, set `HOST=0.0.0.0`.
|
|
9
|
+
- **Graceful stop.** On `SIGTERM` and `SIGINT` the server stops taking requests, closes idle connections, lets the requests in flight finish, closes the database (the `notes` template) and exits with 0. A connection that never ends cannot keep it alive past 10 seconds (it then exits with 1).
|
|
10
|
+
- **Errors.** An error answers `500 Internal Server Error` with no stack and no detail; the stack goes to the log (standard error).
|
|
11
|
+
- **Cookies.** The `notes` template marks its session cookie `Secure` when the request is HTTPS, or when `TRUST_PROXY=1` and the proxy sends `X-Forwarded-Proto: https`.
|
|
12
|
+
|
|
13
|
+
## Environment variables
|
|
14
|
+
|
|
15
|
+
| Variable | Default | Meaning |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| `NODE_ENV` | unset | set it to `production` on the server |
|
|
18
|
+
| `PORT` | `3000` | the port to listen on |
|
|
19
|
+
| `HOST` | `127.0.0.1` | the address to bind. `0.0.0.0` exposes the app to the network: do it in a container or when nothing else is in front |
|
|
20
|
+
| `NOTES_DB` | `notes.db` in the working directory | the SQLite file (`notes` template) |
|
|
21
|
+
| `TRUST_PROXY` | unset | `1` when a reverse proxy you run terminates HTTPS in front of the app (`notes` template) |
|
|
22
|
+
| `PUBLIC_ORIGIN` | unset | the address in the browser, for example `https://notes.example.com`. Behind a proxy the app sees `http://127.0.0.1:3000`, so csrf needs to be told which origin is the site (`notes` template) |
|
|
23
|
+
|
|
24
|
+
Keep secrets out of the repository: put them in an environment file the service reads (below), readable only by the service user.
|
|
25
|
+
|
|
26
|
+
## Install and start
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
git clone <your repository> /srv/notes && cd /srv/notes
|
|
30
|
+
npm ci --omit=dev
|
|
31
|
+
NODE_ENV=production PORT=3000 npm start
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`npm ci --omit=dev` installs `acrylblends` and `typescript`. `typescript` is a normal dependency on purpose (it is why a production install is bigger than the framework alone, about 20 MB more): `.tsx` pages are compiled when the app starts, by `acrylblends/node-server/register`, with the app's own `typescript`.
|
|
35
|
+
There is no build step today. When a build step appears (compiling client code, for instance) it belongs in `npm run build` before `npm start`, not in the start command.
|
|
36
|
+
|
|
37
|
+
## Keep it running with systemd
|
|
38
|
+
|
|
39
|
+
`/etc/systemd/system/notes.service`:
|
|
40
|
+
|
|
41
|
+
```ini
|
|
42
|
+
[Unit]
|
|
43
|
+
Description=Notes
|
|
44
|
+
After=network.target
|
|
45
|
+
|
|
46
|
+
[Service]
|
|
47
|
+
User=notes
|
|
48
|
+
WorkingDirectory=/srv/notes
|
|
49
|
+
EnvironmentFile=/etc/notes.env
|
|
50
|
+
ExecStart=/usr/bin/node --import acrylblends/node-server/register src/index.ts
|
|
51
|
+
Restart=on-failure
|
|
52
|
+
TimeoutStopSec=15
|
|
53
|
+
NoNewPrivileges=true
|
|
54
|
+
ProtectSystem=strict
|
|
55
|
+
ReadWritePaths=/var/lib/notes
|
|
56
|
+
|
|
57
|
+
[Install]
|
|
58
|
+
WantedBy=multi-user.target
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`/etc/notes.env` (mode `0640`, owned by root, group `notes`):
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
NODE_ENV=production
|
|
65
|
+
PORT=3000
|
|
66
|
+
HOST=127.0.0.1
|
|
67
|
+
NOTES_DB=/var/lib/notes/notes.db
|
|
68
|
+
TRUST_PROXY=1
|
|
69
|
+
PUBLIC_ORIGIN=https://notes.example.com
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
sudo useradd --system --home /srv/notes notes
|
|
74
|
+
sudo install -d -o notes -g notes /var/lib/notes
|
|
75
|
+
sudo systemctl daemon-reload && sudo systemctl enable --now notes
|
|
76
|
+
sudo systemctl stop notes # sends SIGTERM; the app exits within seconds
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`ExecStart` runs `node` directly (not `npm start`) so the signal reaches the app and not an `npm` wrapper.
|
|
80
|
+
|
|
81
|
+
## Behind a reverse proxy
|
|
82
|
+
|
|
83
|
+
Let the proxy handle HTTPS and keep the app on `127.0.0.1`.
|
|
84
|
+
|
|
85
|
+
Caddy (`Caddyfile`), which also obtains the certificate:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
notes.example.com {
|
|
89
|
+
reverse_proxy 127.0.0.1:3000
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
nginx (certificates from your own tool, for example certbot):
|
|
94
|
+
|
|
95
|
+
```nginx
|
|
96
|
+
server {
|
|
97
|
+
listen 443 ssl;
|
|
98
|
+
server_name notes.example.com;
|
|
99
|
+
# ssl_certificate and ssl_certificate_key here
|
|
100
|
+
|
|
101
|
+
location / {
|
|
102
|
+
proxy_pass http://127.0.0.1:3000;
|
|
103
|
+
proxy_set_header Host $host;
|
|
104
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
105
|
+
proxy_set_header X-Forwarded-For $remote_addr;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Both pass `X-Forwarded-Proto`. Set `TRUST_PROXY=1` only when the app is reachable **only** through that proxy (it listens on `127.0.0.1`): the header is otherwise something any client can send.
|
|
111
|
+
|
|
112
|
+
## The SQLite file and backups
|
|
113
|
+
|
|
114
|
+
The database is one file (plus `-wal` and `-shm` files while it is open). Keep it outside the code folder (`NOTES_DB=/var/lib/notes/notes.db`), owned by the service user, on a disk you back up.
|
|
115
|
+
Do not copy the file while the app writes to it. Take a consistent copy with SQLite's backup, which is safe on a running database:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { DatabaseSync, backup } from 'node:sqlite'
|
|
119
|
+
|
|
120
|
+
const db = new DatabaseSync(process.env.NOTES_DB ?? 'notes.db')
|
|
121
|
+
await backup(db, `/var/backups/notes-${new Date().toISOString().slice(0, 10)}.db`)
|
|
122
|
+
db.close()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
or, with the `sqlite3` command line tool installed: `sqlite3 /var/lib/notes/notes.db ".backup '/var/backups/notes.db'"`. Run it from cron or a systemd timer and copy the result off the machine. To restore, stop the service and put the backup file in place of `notes.db`.
|
|
126
|
+
|
|
127
|
+
## Check it
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
curl -I http://127.0.0.1:3000/ # the app, directly
|
|
131
|
+
curl -I https://notes.example.com/ # through the proxy
|
|
132
|
+
journalctl -u notes -n 50 # the log, including error stacks
|
|
133
|
+
```
|
|
@@ -80,27 +80,29 @@ app.get('/', (c) => c.text('Hello Node.js!'))
|
|
|
80
80
|
serve(app)
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
`serve()` listens on `127.0.0.1` unless you pass `hostname`, so a development server is reachable from this machine only, not from the rest
|
|
84
|
+
of the network. See [Listen on another interface](#listen-on-another-interface).
|
|
85
|
+
|
|
86
|
+
To shut the server down gracefully, stop taking requests, let the ones in flight finish, and exit after a bounded time, so a connection
|
|
87
|
+
that never ends cannot keep the process alive:
|
|
84
88
|
|
|
85
89
|
```ts
|
|
86
90
|
const server = serve(app)
|
|
87
91
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
console.error(err)
|
|
97
|
-
process.exit(1)
|
|
98
|
-
}
|
|
99
|
-
process.exit(0)
|
|
100
|
-
})
|
|
101
|
-
})
|
|
92
|
+
function shutdown(): void {
|
|
93
|
+
server.close(() => process.exit(0))
|
|
94
|
+
// Keep-alive connections with no request in flight would otherwise hold close() open until they time out.
|
|
95
|
+
server.closeIdleConnections()
|
|
96
|
+
setTimeout(() => process.exit(1), 10_000).unref()
|
|
97
|
+
}
|
|
98
|
+
process.once('SIGINT', shutdown)
|
|
99
|
+
process.once('SIGTERM', shutdown)
|
|
102
100
|
```
|
|
103
101
|
|
|
102
|
+
`serve(app)` and `serve({ fetch, ... })` return an `http.Server`, which has `closeIdleConnections()`. With `createServer` from
|
|
103
|
+
`node:https` the result is an `https.Server`, and with `node:http2` an `Http2Server` or `Http2SecureServer`, which has no such method;
|
|
104
|
+
there, `server.close()` waits for the open sessions.
|
|
105
|
+
|
|
104
106
|
> **Note.** On Node.js, `serve()` wraps the `node:http` module and returns the underlying server instance, so closing it is up to you. Bun serves Fetch handlers natively and manages the server itself, which is why its guide doesn't include this step.
|
|
105
107
|
|
|
106
108
|
## 3. Run
|
|
@@ -136,6 +138,22 @@ serve({
|
|
|
136
138
|
})
|
|
137
139
|
```
|
|
138
140
|
|
|
141
|
+
## Listen on another interface
|
|
142
|
+
|
|
143
|
+
`serve()` binds `127.0.0.1` when `hostname` is not given. That is right on a developer machine and behind a reverse proxy on the same
|
|
144
|
+
host. Inside a container, or when other machines should reach the app directly, bind every interface on purpose:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
serve({
|
|
148
|
+
fetch: app.fetch,
|
|
149
|
+
port: 3000,
|
|
150
|
+
hostname: '0.0.0.0', // or '::' for IPv6 and IPv4
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
> **Note.** Many Node servers listen on every interface by default. `serve()` does not, so starting a development server never exposes
|
|
155
|
+
> the app to the local network by accident.
|
|
156
|
+
|
|
139
157
|
## WebSocket
|
|
140
158
|
|
|
141
159
|
WebSocket support is built into `acrylblends/node-server`. Install `ws` and, if you use TypeScript, `@types/ws`. Then create a `WebSocketServer` with `{ noServer: true }` and pass it to `serve()` with the `websocket` option.
|
|
@@ -311,7 +329,9 @@ bun run build
|
|
|
311
329
|
|
|
312
330
|
### Dockerfile
|
|
313
331
|
|
|
314
|
-
Here is an example of a Node.js Dockerfile.
|
|
332
|
+
Here is an example of a Node.js Dockerfile. A container's published port reaches the app only when it listens on every interface, so
|
|
333
|
+
the app reads its hostname from the environment, as the templates do (`hostname: process.env.HOST || '127.0.0.1'`), and the image
|
|
334
|
+
sets `HOST=0.0.0.0`.
|
|
315
335
|
|
|
316
336
|
```Dockerfile
|
|
317
337
|
FROM node:22-alpine AS base
|
|
@@ -338,6 +358,7 @@ COPY --from=builder --chown=acrylblends:nodejs /app/dist /app/dist
|
|
|
338
358
|
COPY --from=builder --chown=acrylblends:nodejs /app/package.json /app/package.json
|
|
339
359
|
|
|
340
360
|
USER acrylblends
|
|
361
|
+
ENV HOST=0.0.0.0
|
|
341
362
|
EXPOSE 3000
|
|
342
363
|
|
|
343
364
|
CMD ["node", "/app/dist/index.js"]
|