acrylblends 0.0.0-stage → 0.2.0
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/bin/acrylblends.js +4 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.js +434 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -4
- package/plugins/agent/agent.ts +152 -0
- package/plugins/agent/index.ts +20 -0
- package/plugins/agent/memory.ts +24 -0
- package/plugins/agent/model.ts +79 -0
- package/plugins/agent/plugin.json +10 -0
- package/plugins/agent/tools.ts +50 -0
- package/plugins/builder/index.ts +193 -0
- package/plugins/builder/plugin.json +11 -0
- package/plugins/builder/script.ts +162 -0
- package/plugins/events/index.ts +42 -0
- package/plugins/events/plugin.json +9 -0
- package/plugins/extensions/index.ts +738 -0
- package/plugins/extensions/ledger.ts +39 -0
- package/plugins/extensions/plugin.json +12 -0
- package/plugins/extensions/resolve.ts +30 -0
- package/plugins/extensions/stage.ts +44 -0
- package/plugins/http/index.ts +64 -0
- package/plugins/http/plugin.json +10 -0
- package/plugins/instance/index.ts +167 -0
- package/plugins/instance/plugin.json +10 -0
- package/plugins/manifest/index.ts +635 -0
- package/plugins/manifest/lock.ts +94 -0
- package/plugins/manifest/plugin.json +13 -0
- package/plugins/manifest/upgrade-cli.ts +70 -0
- package/plugins/manifest/upgrade.ts +97 -0
- package/plugins/openai/index.ts +159 -0
- package/plugins/openai/plugin.json +11 -0
- package/plugins/openai/prompt.ts +31 -0
- package/plugins/sandbox/index.ts +150 -0
- package/plugins/sandbox/plugin.json +8 -0
- package/plugins/sandbox/runner.mjs +18 -0
- package/plugins/save/cli.ts +19 -0
- package/plugins/save/index.ts +55 -0
- package/plugins/save/plugin.json +12 -0
- package/plugins/server/index.ts +138 -0
- package/plugins/server/plugin.json +10 -0
- package/plugins/settings/index.ts +270 -0
- package/plugins/settings/merge.ts +79 -0
- package/plugins/settings/plugin.json +13 -0
- package/plugins/tasks/index.ts +232 -0
- package/plugins/tasks/plugin.json +11 -0
- package/plugins/tasks/store.ts +86 -0
- package/plugins/tsconfig.json +19 -0
- package/templates/agent/Dockerfile +15 -0
- package/templates/agent/README.md +52 -0
- package/templates/agent/blend.yaml +58 -0
- package/templates/agent/dockerignore +3 -0
- package/templates/agent/gitignore +2 -0
- package/templates/agent/package.json +23 -0
- package/templates/agent/src/app.ts +98 -0
- package/templates/agent/src/main.ts +25 -0
- package/templates/agent/src/routes.ts +62 -0
- package/templates/agent/template.json +16 -0
- package/templates/agent/test/agent.test.ts +45 -0
- package/templates/agent/test/app.test.ts +46 -0
- package/templates/agent/test/builder.test.ts +186 -0
- package/templates/agent/test/model.test.ts +89 -0
- package/templates/agent/tsconfig.json +19 -0
- package/templates/app/README.md +26 -0
- package/templates/app/blend.yaml +23 -0
- package/templates/app/gitignore +2 -0
- package/templates/app/package.json +23 -0
- package/templates/app/src/app.ts +61 -0
- package/templates/app/src/main.ts +20 -0
- package/templates/app/src/routes.ts +14 -0
- package/templates/app/template.json +1 -0
- package/templates/app/test/app.test.ts +56 -0
- package/templates/app/tsconfig.json +19 -0
- package/README.md +0 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
//#region src/io.d.ts
|
|
2
|
+
/** Everything the CLI touches outside the filesystem. Tests pass their own. */
|
|
3
|
+
interface Io {
|
|
4
|
+
stdout: {
|
|
5
|
+
write(text: string): unknown;
|
|
6
|
+
};
|
|
7
|
+
stderr: {
|
|
8
|
+
write(text: string): unknown;
|
|
9
|
+
};
|
|
10
|
+
cwd: string;
|
|
11
|
+
/** Ask one question; `undefined` when there is no terminal to ask on. */
|
|
12
|
+
ask: ((question: string) => Promise<string>) | undefined;
|
|
13
|
+
/** Install dependencies in a directory; resolves to the exit code. */
|
|
14
|
+
install(directory: string): Promise<number>;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/add.d.ts
|
|
18
|
+
interface AddOptions {
|
|
19
|
+
readonly names: readonly string[];
|
|
20
|
+
readonly directory: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Parse `add <plugin...> [--dir <app>]`. */
|
|
23
|
+
export declare function parseAddOptions(argv: readonly string[]): AddOptions;
|
|
24
|
+
/**
|
|
25
|
+
* Copy library plugins into an existing app, with the plugins they require, and add their npm packages to its package.json.
|
|
26
|
+
* Refuses a plugin the app already has. Returns the exit code.
|
|
27
|
+
*/
|
|
28
|
+
export declare function add(options: AddOptions, io: Io): Promise<number>;
|
|
29
|
+
/** `acrylblends plugins`: the library, one line per plugin. */
|
|
30
|
+
export declare function listPlugins(io: Io): number;
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/cli.d.ts
|
|
33
|
+
/** The version of this package. */
|
|
34
|
+
export declare const VERSION: string;
|
|
35
|
+
export declare const HELP: string;
|
|
36
|
+
/**
|
|
37
|
+
* Run the CLI and return the exit code: 0 done, 1 failed or refused, 2 a usage error. Never calls `process.exit`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function runCli(argv: readonly string[], io?: Io): Promise<number>;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/init.d.ts
|
|
42
|
+
/** A command line mistake. The CLI prints it with the help text and exits 2. */
|
|
43
|
+
export declare class UsageError extends Error {
|
|
44
|
+
name: string;
|
|
45
|
+
}
|
|
46
|
+
/** What `init` was asked for. Anything left undefined is asked for on a terminal, or defaulted. */
|
|
47
|
+
interface InitOptions {
|
|
48
|
+
directory: string | undefined;
|
|
49
|
+
name: string | undefined;
|
|
50
|
+
agent: boolean | undefined;
|
|
51
|
+
install: boolean | undefined;
|
|
52
|
+
yes: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** Parse `init [dir] [--agent|--no-agent] [--name n] [--install] [-y]`. */
|
|
55
|
+
export declare function parseInitOptions(argv: readonly string[]): InitOptions;
|
|
56
|
+
/** Scaffold a Cordis app. Returns the exit code: 0 done, 1 refused or failed. Never overwrites anything. */
|
|
57
|
+
export declare function init(options: InitOptions, io: Io): Promise<number>;
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/library.d.ts
|
|
60
|
+
/** One entry of the plugin library: `plugins/<name>/plugin.json` in this repository. */
|
|
61
|
+
interface LibraryPlugin {
|
|
62
|
+
readonly name: string;
|
|
63
|
+
readonly description: string;
|
|
64
|
+
/** Library plugins this one imports; they are copied with it. */
|
|
65
|
+
readonly requires: readonly string[];
|
|
66
|
+
/** npm packages an app needs at run time for this plugin. */
|
|
67
|
+
readonly dependencies: Readonly<Record<string, string>>;
|
|
68
|
+
/** How to mount it, shown after `acrylblends add`. */
|
|
69
|
+
readonly mount?: string;
|
|
70
|
+
}
|
|
71
|
+
/** The library folder (`plugins/` at the repository root): walk up from this module, in source or in the build. */
|
|
72
|
+
export declare function libraryRoot(): string;
|
|
73
|
+
/** Every plugin in the library, by name. */
|
|
74
|
+
export declare function readLibrary(root?: string): Map<string, LibraryPlugin>;
|
|
75
|
+
/** The plugins to copy for `names`: each one after everything it requires, no duplicates. */
|
|
76
|
+
export declare function closure(names: readonly string[], library: Map<string, LibraryPlugin>): LibraryPlugin[];
|
|
77
|
+
/** Copy one library plugin's source into `<app>/src/plugins/<name>/`. Throws when that folder already exists. */
|
|
78
|
+
export declare function copyPlugin(plugin: LibraryPlugin, appDir: string, root?: string): string;
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/project.d.ts
|
|
81
|
+
/** Thrown when the target directory exists and is not empty. Nothing has been written. */
|
|
82
|
+
export declare class TargetNotEmptyError extends Error {
|
|
83
|
+
readonly target: string;
|
|
84
|
+
constructor(target: string);
|
|
85
|
+
}
|
|
86
|
+
/** A project name npm accepts: lower case letters, digits and dashes, starting with a letter. */
|
|
87
|
+
export declare function isValidName(name: string): boolean;
|
|
88
|
+
/** A valid default project name derived from a directory path. */
|
|
89
|
+
export declare function nameFrom(directory: string): string;
|
|
90
|
+
/** Refuse a target that exists and is not an empty directory. A missing target is fine: it is created. */
|
|
91
|
+
export declare function assertTargetFree(target: string): void;
|
|
92
|
+
/**
|
|
93
|
+
* Copy a template into `target`, replacing `__APP_NAME__` with `name`. Checks the target before writing anything.
|
|
94
|
+
*
|
|
95
|
+
* @returns the copied files, relative to `target`
|
|
96
|
+
*/
|
|
97
|
+
export declare function copyTemplate(source: string, target: string, name: string): string[];
|
|
98
|
+
export declare function readTemplatePlugins(templateDir: string): string[];
|
|
99
|
+
/** Add run-time dependencies to an app's package.json, keeping what is there and sorting the keys. Returns the names added. */
|
|
100
|
+
export declare function mergeDependencies(appDir: string, dependencies: Readonly<Record<string, string>>): string[];
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/templates.d.ts
|
|
103
|
+
/** The two templates: a Cordis app, and the same app with the offline agent. Both depend on `cordis` only. */
|
|
104
|
+
export declare const TEMPLATES: readonly ["app", "agent"];
|
|
105
|
+
type Template = (typeof TEMPLATES)[number];
|
|
106
|
+
/** The template for an agent choice. */
|
|
107
|
+
export declare function templateFor(agent: boolean): Template;
|
|
108
|
+
/**
|
|
109
|
+
* The `templates/` folder of this package. The module sits at different depths in source (`src/`) and in the build
|
|
110
|
+
* (`dist/`), so walk up until a `templates` folder holds the `app` template.
|
|
111
|
+
*/
|
|
112
|
+
export declare function templatesRoot(): string;
|
|
113
|
+
/** Absolute path of a template folder. */
|
|
114
|
+
export declare function templateDir(template: Template): string;
|
|
115
|
+
//#endregion
|
|
116
|
+
export type { AddOptions, InitOptions, Io, LibraryPlugin, Template };
|
|
117
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { createInterface } from "node:readline/promises";
|
|
8
|
+
//#region src/library.ts
|
|
9
|
+
/** Files of a library plugin that stay in the repository: its manifest and its tests. */
|
|
10
|
+
const LIBRARY_ONLY = /* @__PURE__ */ new Set(["plugin.json", "test"]);
|
|
11
|
+
var LibraryError = class extends Error {
|
|
12
|
+
name = "LibraryError";
|
|
13
|
+
};
|
|
14
|
+
/** The library folder (`plugins/` at the repository root): walk up from this module, in source or in the build. */
|
|
15
|
+
function libraryRoot() {
|
|
16
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
for (;;) {
|
|
18
|
+
const candidate = join(dir, "plugins");
|
|
19
|
+
if (existsSync(join(candidate, "http", "plugin.json"))) return candidate;
|
|
20
|
+
const parent = dirname(dir);
|
|
21
|
+
if (parent === dir) throw new LibraryError("acrylblends cannot find its plugin library (plugins/)");
|
|
22
|
+
dir = parent;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Every plugin in the library, by name. */
|
|
26
|
+
function readLibrary(root = libraryRoot()) {
|
|
27
|
+
const plugins = /* @__PURE__ */ new Map();
|
|
28
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
29
|
+
const manifest = join(root, entry.name, "plugin.json");
|
|
30
|
+
if (!entry.isDirectory() || !existsSync(manifest)) continue;
|
|
31
|
+
const plugin = JSON.parse(readFileSync(manifest, "utf8"));
|
|
32
|
+
if (plugin.name !== entry.name) throw new LibraryError(`plugins/${entry.name}/plugin.json names "${plugin.name}"`);
|
|
33
|
+
plugins.set(plugin.name, {
|
|
34
|
+
...plugin,
|
|
35
|
+
requires: plugin.requires ?? [],
|
|
36
|
+
dependencies: plugin.dependencies ?? {}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return plugins;
|
|
40
|
+
}
|
|
41
|
+
/** The plugins to copy for `names`: each one after everything it requires, no duplicates. */
|
|
42
|
+
function closure(names, library) {
|
|
43
|
+
const ordered = [];
|
|
44
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
45
|
+
const visit = (name, trail) => {
|
|
46
|
+
if (ordered.some((plugin) => plugin.name === name)) return;
|
|
47
|
+
const plugin = library.get(name);
|
|
48
|
+
if (!plugin) throw new LibraryError(`no plugin "${name}" in the library${trail.length ? ` (required by ${trail.at(-1)})` : ""}`);
|
|
49
|
+
if (visiting.has(name)) throw new LibraryError(`plugins require each other in a cycle: ${[...trail, name].join(" -> ")}`);
|
|
50
|
+
visiting.add(name);
|
|
51
|
+
for (const required of plugin.requires) visit(required, [...trail, name]);
|
|
52
|
+
visiting.delete(name);
|
|
53
|
+
ordered.push(plugin);
|
|
54
|
+
};
|
|
55
|
+
for (const name of names) visit(name, []);
|
|
56
|
+
return ordered;
|
|
57
|
+
}
|
|
58
|
+
/** Copy one library plugin's source into `<app>/src/plugins/<name>/`. Throws when that folder already exists. */
|
|
59
|
+
function copyPlugin(plugin, appDir, root = libraryRoot()) {
|
|
60
|
+
const target = join(appDir, "src", "plugins", plugin.name);
|
|
61
|
+
if (existsSync(target)) throw new LibraryError(`src/plugins/${plugin.name} already exists; remove it first to copy a fresh one`);
|
|
62
|
+
const source = join(root, plugin.name);
|
|
63
|
+
cpSync(source, target, {
|
|
64
|
+
recursive: true,
|
|
65
|
+
filter: (path) => !(dirname(path) === source && LIBRARY_ONLY.has(path.slice(source.length + 1)))
|
|
66
|
+
});
|
|
67
|
+
return target;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/project.ts
|
|
71
|
+
/** Thrown when the target directory exists and is not empty. Nothing has been written. */
|
|
72
|
+
var TargetNotEmptyError = class extends Error {
|
|
73
|
+
target;
|
|
74
|
+
constructor(target) {
|
|
75
|
+
super(`refusing to write into "${target}": the directory is not empty. Choose an empty or new directory.`);
|
|
76
|
+
this.target = target;
|
|
77
|
+
this.name = "TargetNotEmptyError";
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/** A project name npm accepts: lower case letters, digits and dashes, starting with a letter. */
|
|
81
|
+
function isValidName(name) {
|
|
82
|
+
return /^[a-z][a-z0-9-]*$/.test(name) && name.length <= 214;
|
|
83
|
+
}
|
|
84
|
+
/** A valid default project name derived from a directory path. */
|
|
85
|
+
function nameFrom(directory) {
|
|
86
|
+
return (directory.split(/[\\/]/).filter(Boolean).pop() ?? "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/, "") || "my-app";
|
|
87
|
+
}
|
|
88
|
+
/** Refuse a target that exists and is not an empty directory. A missing target is fine: it is created. */
|
|
89
|
+
function assertTargetFree(target) {
|
|
90
|
+
if (!existsSync(target)) return;
|
|
91
|
+
if (!statSync(target).isDirectory() || readdirSync(target).length > 0) throw new TargetNotEmptyError(target);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Copy a template into `target`, replacing `__APP_NAME__` with `name`. Checks the target before writing anything.
|
|
95
|
+
*
|
|
96
|
+
* @returns the copied files, relative to `target`
|
|
97
|
+
*/
|
|
98
|
+
function copyTemplate(source, target, name) {
|
|
99
|
+
assertTargetFree(target);
|
|
100
|
+
mkdirSync(target, { recursive: true });
|
|
101
|
+
const written = [];
|
|
102
|
+
const walk = (from, to, prefix) => {
|
|
103
|
+
for (const entry of readdirSync(from, { withFileTypes: true })) {
|
|
104
|
+
if (entry.name === "node_modules" || prefix === "" && entry.name === "template.json") continue;
|
|
105
|
+
const sourcePath = join(from, entry.name);
|
|
106
|
+
const targetName = prefix === "" && (entry.name === "gitignore" || entry.name === "dockerignore") ? `.${entry.name}` : entry.name;
|
|
107
|
+
const targetPath = join(to, targetName);
|
|
108
|
+
if (entry.isDirectory()) {
|
|
109
|
+
mkdirSync(targetPath, { recursive: true });
|
|
110
|
+
walk(sourcePath, targetPath, `${prefix}${entry.name}/`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const text = readFileSync(sourcePath, "utf8");
|
|
114
|
+
if (text.includes("__APP_NAME__")) writeFileSync(targetPath, text.replaceAll("__APP_NAME__", name));
|
|
115
|
+
else cpSync(sourcePath, targetPath);
|
|
116
|
+
written.push(`${prefix}${targetName}`);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
walk(source, target, "");
|
|
120
|
+
return written;
|
|
121
|
+
}
|
|
122
|
+
/** A template's own manifest, `template.json`: the library plugins it is built from. Never copied. */
|
|
123
|
+
const TEMPLATE_MANIFEST = "template.json";
|
|
124
|
+
function readTemplatePlugins(templateDir) {
|
|
125
|
+
const file = join(templateDir, TEMPLATE_MANIFEST);
|
|
126
|
+
if (!existsSync(file)) return [];
|
|
127
|
+
const { plugins } = JSON.parse(readFileSync(file, "utf8"));
|
|
128
|
+
return plugins ?? [];
|
|
129
|
+
}
|
|
130
|
+
/** Add run-time dependencies to an app's package.json, keeping what is there and sorting the keys. Returns the names added. */
|
|
131
|
+
function mergeDependencies(appDir, dependencies) {
|
|
132
|
+
const file = join(appDir, "package.json");
|
|
133
|
+
const manifest = JSON.parse(readFileSync(file, "utf8"));
|
|
134
|
+
const current = manifest.dependencies ?? {};
|
|
135
|
+
const added = Object.keys(dependencies).filter((name) => !(name in current));
|
|
136
|
+
const merged = {
|
|
137
|
+
...dependencies,
|
|
138
|
+
...current
|
|
139
|
+
};
|
|
140
|
+
manifest.dependencies = Object.fromEntries(Object.entries(merged).sort(([a], [b]) => a.localeCompare(b)));
|
|
141
|
+
writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
142
|
+
return added;
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/templates.ts
|
|
146
|
+
/** The two templates: a Cordis app, and the same app with the offline agent. Both depend on `cordis` only. */
|
|
147
|
+
const TEMPLATES = ["app", "agent"];
|
|
148
|
+
/** The template for an agent choice. */
|
|
149
|
+
function templateFor(agent) {
|
|
150
|
+
return agent ? "agent" : "app";
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The `templates/` folder of this package. The module sits at different depths in source (`src/`) and in the build
|
|
154
|
+
* (`dist/`), so walk up until a `templates` folder holds the `app` template.
|
|
155
|
+
*/
|
|
156
|
+
function templatesRoot() {
|
|
157
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
158
|
+
for (;;) {
|
|
159
|
+
const candidate = join(dir, "templates");
|
|
160
|
+
if (existsSync(join(candidate, "app"))) return candidate;
|
|
161
|
+
const parent = dirname(dir);
|
|
162
|
+
if (parent === dir) throw new Error("acrylblends cannot find its templates folder");
|
|
163
|
+
dir = parent;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Absolute path of a template folder. */
|
|
167
|
+
function templateDir(template) {
|
|
168
|
+
return join(templatesRoot(), template);
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/init.ts
|
|
172
|
+
/** A command line mistake. The CLI prints it with the help text and exits 2. */
|
|
173
|
+
var UsageError = class extends Error {
|
|
174
|
+
name = "UsageError";
|
|
175
|
+
};
|
|
176
|
+
/** Parse `init [dir] [--agent|--no-agent] [--name n] [--install] [-y]`. */
|
|
177
|
+
function parseInitOptions(argv) {
|
|
178
|
+
let parsed;
|
|
179
|
+
try {
|
|
180
|
+
parsed = parse$1(argv);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
throw new UsageError(error.message);
|
|
183
|
+
}
|
|
184
|
+
const { values, positionals } = parsed;
|
|
185
|
+
if (positionals.length > 1) throw new UsageError("init takes at most one directory");
|
|
186
|
+
if (values.agent && values["no-agent"]) throw new UsageError("use --agent or --no-agent, not both");
|
|
187
|
+
return {
|
|
188
|
+
directory: positionals[0],
|
|
189
|
+
name: values.name,
|
|
190
|
+
agent: values.agent ? true : values["no-agent"] ? false : void 0,
|
|
191
|
+
install: values.install,
|
|
192
|
+
yes: values.yes === true
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const parse$1 = (argv) => parseArgs({
|
|
196
|
+
args: [...argv],
|
|
197
|
+
allowPositionals: true,
|
|
198
|
+
strict: true,
|
|
199
|
+
options: {
|
|
200
|
+
name: { type: "string" },
|
|
201
|
+
agent: { type: "boolean" },
|
|
202
|
+
"no-agent": { type: "boolean" },
|
|
203
|
+
install: { type: "boolean" },
|
|
204
|
+
yes: {
|
|
205
|
+
type: "boolean",
|
|
206
|
+
short: "y"
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
const yes = (answer) => ["y", "yes"].includes(answer.trim().toLowerCase());
|
|
211
|
+
/** Scaffold a Cordis app. Returns the exit code: 0 done, 1 refused or failed. Never overwrites anything. */
|
|
212
|
+
async function init(options, io) {
|
|
213
|
+
const relative = options.directory ?? ".";
|
|
214
|
+
const directory = resolve(io.cwd, relative);
|
|
215
|
+
const ask = options.yes ? void 0 : io.ask;
|
|
216
|
+
let name = options.name ?? nameFrom(directory);
|
|
217
|
+
if (options.name === void 0 && ask) name = (await ask(`Project name (${name}): `)).trim() || name;
|
|
218
|
+
if (!isValidName(name)) throw new UsageError(`"${name}" is not a valid project name (lower case letters, digits and dashes, starting with a letter)`);
|
|
219
|
+
const agent = options.agent ?? (ask ? yes(await ask("Include the offline agent? (y/N): ")) : false);
|
|
220
|
+
const install = options.install ?? (ask ? yes(await ask("Install dependencies with npm now? (y/N): ")) : false);
|
|
221
|
+
const template = templateFor(agent);
|
|
222
|
+
let files;
|
|
223
|
+
try {
|
|
224
|
+
files = copyTemplate(templateDir(template), directory, name);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if (!(error instanceof TargetNotEmptyError)) throw error;
|
|
227
|
+
io.stderr.write(`error: ${error.message}\n`);
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
230
|
+
const plugins = closure(readTemplatePlugins(templateDir(template)), readLibrary());
|
|
231
|
+
for (const plugin of plugins) {
|
|
232
|
+
copyPlugin(plugin, directory);
|
|
233
|
+
mergeDependencies(directory, plugin.dependencies);
|
|
234
|
+
}
|
|
235
|
+
io.stdout.write(`created ${name} (${template}) in ${directory}: ${files.length} files, plugins ${plugins.map((plugin) => plugin.name).join(", ")}\n`);
|
|
236
|
+
if (install && await io.install(directory) !== 0) {
|
|
237
|
+
io.stderr.write("error: npm install failed; run it yourself in the new directory\n");
|
|
238
|
+
return 1;
|
|
239
|
+
}
|
|
240
|
+
const steps = [
|
|
241
|
+
relative === "." ? "" : ` cd ${relative}\n`,
|
|
242
|
+
install ? "" : " npm install\n",
|
|
243
|
+
" npm run dev\n npm test\n"
|
|
244
|
+
];
|
|
245
|
+
io.stdout.write(`\nNext:\n${steps.join("")}`);
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/add.ts
|
|
250
|
+
/** Parse `add <plugin...> [--dir <app>]`. */
|
|
251
|
+
function parseAddOptions(argv) {
|
|
252
|
+
let parsed;
|
|
253
|
+
try {
|
|
254
|
+
parsed = parse(argv);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
throw new UsageError(error.message);
|
|
257
|
+
}
|
|
258
|
+
if (parsed.positionals.length === 0) throw new UsageError("add needs at least one plugin name (see \"acrylblends plugins\")");
|
|
259
|
+
return {
|
|
260
|
+
names: parsed.positionals,
|
|
261
|
+
directory: parsed.values.dir
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const parse = (argv) => parseArgs({
|
|
265
|
+
args: [...argv],
|
|
266
|
+
allowPositionals: true,
|
|
267
|
+
strict: true,
|
|
268
|
+
options: { dir: { type: "string" } }
|
|
269
|
+
});
|
|
270
|
+
const present = (appDir, name) => existsSync(join(appDir, "src", "plugins", name));
|
|
271
|
+
/**
|
|
272
|
+
* Copy library plugins into an existing app, with the plugins they require, and add their npm packages to its package.json.
|
|
273
|
+
* Refuses a plugin the app already has. Returns the exit code.
|
|
274
|
+
*/
|
|
275
|
+
async function add(options, io) {
|
|
276
|
+
const appDir = resolve(io.cwd, options.directory ?? ".");
|
|
277
|
+
if (!existsSync(join(appDir, "package.json")) || !existsSync(join(appDir, "src"))) {
|
|
278
|
+
io.stderr.write(`error: ${appDir} is not an app (no package.json and src/); run "acrylblends init" first\n`);
|
|
279
|
+
return 1;
|
|
280
|
+
}
|
|
281
|
+
const external = options.names.filter((name) => /[\\/]/.test(name) || name.startsWith("."));
|
|
282
|
+
for (const folder of external) {
|
|
283
|
+
const code = addExternal(resolve(io.cwd, folder), appDir, io);
|
|
284
|
+
if (code !== 0) return code;
|
|
285
|
+
}
|
|
286
|
+
const names = options.names.filter((name) => !external.includes(name));
|
|
287
|
+
if (names.length === 0) return 0;
|
|
288
|
+
const already = names.filter((name) => present(appDir, name));
|
|
289
|
+
if (already.length > 0) {
|
|
290
|
+
io.stderr.write(`error: the app already has ${already.map((name) => `src/plugins/${name}`).join(", ")}\n`);
|
|
291
|
+
return 1;
|
|
292
|
+
}
|
|
293
|
+
const toCopy = closure(names, readLibrary()).filter((plugin) => !present(appDir, plugin.name));
|
|
294
|
+
const addedDependencies = [];
|
|
295
|
+
for (const plugin of toCopy) {
|
|
296
|
+
copyPlugin(plugin, appDir);
|
|
297
|
+
addedDependencies.push(...mergeDependencies(appDir, plugin.dependencies));
|
|
298
|
+
}
|
|
299
|
+
io.stdout.write(`added ${toCopy.map((plugin) => plugin.name).join(", ")} to ${appDir}\n`);
|
|
300
|
+
if (addedDependencies.length > 0) io.stdout.write(`new dependencies: ${addedDependencies.join(", ")} (run npm install)\n`);
|
|
301
|
+
for (const plugin of toCopy.filter((plugin) => names.includes(plugin.name))) io.stdout.write(`\n${plugin.name}: ${plugin.mount ?? `import it from './plugins/${plugin.name}/index.ts' and mount it in src/app.ts`}\n`);
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
304
|
+
const PLUGIN_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
305
|
+
/**
|
|
306
|
+
* Bring an external plugin folder (a package made with the builder's package_plugin, or any folder with an index.ts that exports a
|
|
307
|
+
* Cordis plugin) into the app's `extensions/`, where the extensions plugin installs it like any other: checked, staged, started,
|
|
308
|
+
* verified. Its runtime dependencies are not installed here; they are reported so the user adds them deliberately.
|
|
309
|
+
*/
|
|
310
|
+
function addExternal(folder, appDir, io) {
|
|
311
|
+
if (!existsSync(folder) || !statSync(folder).isDirectory()) {
|
|
312
|
+
io.stderr.write(`error: ${folder} is not a folder\n`);
|
|
313
|
+
return 1;
|
|
314
|
+
}
|
|
315
|
+
if (![
|
|
316
|
+
"index.ts",
|
|
317
|
+
"index.js",
|
|
318
|
+
"index.mjs"
|
|
319
|
+
].some((entry) => existsSync(join(folder, entry)))) {
|
|
320
|
+
io.stderr.write(`error: ${folder} has no index.ts that exports a Cordis plugin\n`);
|
|
321
|
+
return 1;
|
|
322
|
+
}
|
|
323
|
+
const manifestFile = join(folder, "package.json");
|
|
324
|
+
const manifest = existsSync(manifestFile) ? JSON.parse(readFileSync(manifestFile, "utf8")) : {};
|
|
325
|
+
const name = (manifest.name ?? basename(folder)).replace(/^@[^/]+\//, "");
|
|
326
|
+
if (!PLUGIN_NAME.test(name)) {
|
|
327
|
+
io.stderr.write(`error: "${name}" is not a plugin name (lowercase letters, digits and dashes)\n`);
|
|
328
|
+
return 1;
|
|
329
|
+
}
|
|
330
|
+
const target = join(appDir, "extensions", name);
|
|
331
|
+
if (existsSync(target)) {
|
|
332
|
+
io.stderr.write(`error: the app already has extensions/${name}\n`);
|
|
333
|
+
return 1;
|
|
334
|
+
}
|
|
335
|
+
cpSync(folder, target, {
|
|
336
|
+
recursive: true,
|
|
337
|
+
filter: (path) => !/[\\/](node_modules|\.git)([\\/]|$)/.test(path.slice(folder.length))
|
|
338
|
+
});
|
|
339
|
+
io.stdout.write(`added external plugin ${name} to ${join(appDir, "extensions")}; it installs at the next start\n`);
|
|
340
|
+
const dependencies = Object.keys(manifest.dependencies ?? {});
|
|
341
|
+
if (dependencies.length > 0) io.stdout.write(`it uses ${dependencies.join(", ")}: add them to the app with npm install first\n`);
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
/** `acrylblends plugins`: the library, one line per plugin. */
|
|
345
|
+
function listPlugins(io) {
|
|
346
|
+
for (const plugin of readLibrary().values()) {
|
|
347
|
+
const requires = plugin.requires.length > 0 ? ` (requires ${plugin.requires.join(", ")})` : "";
|
|
348
|
+
io.stdout.write(`${plugin.name.padEnd(12)} ${plugin.description}${requires}\n`);
|
|
349
|
+
}
|
|
350
|
+
return 0;
|
|
351
|
+
}
|
|
352
|
+
//#endregion
|
|
353
|
+
//#region src/io.ts
|
|
354
|
+
/** The real terminal and `npm install`. */
|
|
355
|
+
function processIo() {
|
|
356
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
357
|
+
return {
|
|
358
|
+
stdout: process.stdout,
|
|
359
|
+
stderr: process.stderr,
|
|
360
|
+
cwd: process.cwd(),
|
|
361
|
+
ask: interactive ? async (question) => {
|
|
362
|
+
const rl = createInterface({
|
|
363
|
+
input: process.stdin,
|
|
364
|
+
output: process.stdout
|
|
365
|
+
});
|
|
366
|
+
try {
|
|
367
|
+
return await rl.question(question);
|
|
368
|
+
} finally {
|
|
369
|
+
rl.close();
|
|
370
|
+
}
|
|
371
|
+
} : void 0,
|
|
372
|
+
install: (directory) => new Promise((done) => {
|
|
373
|
+
const child = spawn("npm", ["install"], {
|
|
374
|
+
cwd: directory,
|
|
375
|
+
stdio: "inherit",
|
|
376
|
+
shell: process.platform === "win32"
|
|
377
|
+
});
|
|
378
|
+
child.on("error", () => done(1));
|
|
379
|
+
child.on("exit", (code) => done(code ?? 1));
|
|
380
|
+
})
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/cli.ts
|
|
385
|
+
/** The version of this package. */
|
|
386
|
+
const VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
387
|
+
const HELP = `acrylblends ${VERSION} - scaffold Cordis apps
|
|
388
|
+
|
|
389
|
+
Usage:
|
|
390
|
+
acrylblends init [dir] [options] Create a Cordis app in an empty or new directory
|
|
391
|
+
acrylblends add <plugin...> [--dir d] Copy library plugins (and what they require) into an app;
|
|
392
|
+
a folder path brings an external plugin into its extensions/
|
|
393
|
+
acrylblends plugins List the plugin library
|
|
394
|
+
|
|
395
|
+
Options for init:
|
|
396
|
+
--agent / --no-agent include the offline agent (asked when not given)
|
|
397
|
+
--name <name> the project name (default: the directory name)
|
|
398
|
+
--install run npm install after copying
|
|
399
|
+
-y, --yes ask nothing; use defaults (no agent, no install)
|
|
400
|
+
|
|
401
|
+
-h, --help show this help
|
|
402
|
+
-v, --version show the version
|
|
403
|
+
`;
|
|
404
|
+
/**
|
|
405
|
+
* Run the CLI and return the exit code: 0 done, 1 failed or refused, 2 a usage error. Never calls `process.exit`.
|
|
406
|
+
*/
|
|
407
|
+
async function runCli(argv, io = processIo()) {
|
|
408
|
+
const [command, ...args] = argv;
|
|
409
|
+
try {
|
|
410
|
+
if (command === void 0 || command === "help" || command === "-h" || command === "--help") {
|
|
411
|
+
io.stdout.write(HELP);
|
|
412
|
+
return command === void 0 ? 2 : 0;
|
|
413
|
+
}
|
|
414
|
+
if (command === "version" || command === "-v" || command === "--version") {
|
|
415
|
+
io.stdout.write(`${VERSION}\n`);
|
|
416
|
+
return 0;
|
|
417
|
+
}
|
|
418
|
+
if (command === "init") return await init(parseInitOptions(args), io);
|
|
419
|
+
if (command === "add") return await add(parseAddOptions(args), io);
|
|
420
|
+
if (command === "plugins") return listPlugins(io);
|
|
421
|
+
throw new UsageError(`unknown command "${command}"`);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
if (error instanceof UsageError) {
|
|
424
|
+
io.stderr.write(`error: ${error.message}\n\n${HELP}`);
|
|
425
|
+
return 2;
|
|
426
|
+
}
|
|
427
|
+
io.stderr.write(`error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
428
|
+
return 1;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
//#endregion
|
|
432
|
+
export { HELP, TEMPLATES, TargetNotEmptyError, UsageError, VERSION, add, assertTargetFree, closure, copyPlugin, copyTemplate, init, isValidName, libraryRoot, listPlugins, mergeDependencies, nameFrom, parseAddOptions, parseInitOptions, readLibrary, readTemplatePlugins, runCli, templateDir, templateFor, templatesRoot };
|
|
433
|
+
|
|
434
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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/io.ts","../src/cli.ts"],"sourcesContent":["import { cpSync, existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { dirname, join } 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 /** How to mount it, shown after `acrylblends add`. */\n readonly mount?: string\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'])\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, 'http', '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 !(dirname(path) === source && LIBRARY_ONLY.has(path.slice(source.length + 1))),\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 (entry.name === 'node_modules' || (prefix === '' && entry.name === TEMPLATE_MANIFEST))\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. */\nexport const TEMPLATE_MANIFEST = 'template.json'\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","import { existsSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n/** The two templates: a Cordis app, and the same app with the offline agent. Both depend on `cordis` only. */\nexport const TEMPLATES = ['app', 'agent'] as const\nexport type Template = (typeof TEMPLATES)[number]\n\n/** The template for an agent choice. */\nexport function templateFor(agent: boolean): Template {\n return agent ? 'agent' : 'app'\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 `app` 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, 'app'))) 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","import { resolve } from 'node:path'\nimport { parseArgs } from 'node:util'\nimport type { Io } from './io'\nimport { closure, copyPlugin, readLibrary } from './library'\nimport {\n copyTemplate,\n isValidName,\n mergeDependencies,\n nameFrom,\n readTemplatePlugins,\n TargetNotEmptyError,\n} from './project'\nimport { templateDir, templateFor } 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 agent: boolean | undefined\n install: boolean | undefined\n yes: boolean\n}\n\n/** Parse `init [dir] [--agent|--no-agent] [--name n] [--install] [-y]`. */\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 return {\n directory: positionals[0],\n name: values.name,\n agent: values.agent ? true : values['no-agent'] ? false : undefined,\n install: values.install,\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 agent: { type: 'boolean' },\n 'no-agent': { type: 'boolean' },\n install: { type: 'boolean' },\n yes: { type: 'boolean', short: 'y' },\n },\n })\n\nconst yes = (answer: string) => ['y', 'yes'].includes(answer.trim().toLowerCase())\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 agent =\n options.agent ?? (ask ? yes(await ask('Include the offline agent? (y/N): ')) : false)\n const install =\n options.install ?? (ask ? yes(await ask('Install dependencies with npm now? (y/N): ')) : false)\n\n const template = templateFor(agent)\n let files: string[]\n try {\n files = copyTemplate(templateDir(template), directory, name)\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(readTemplatePlugins(templateDir(template)), readLibrary())\n for (const plugin of plugins) {\n copyPlugin(plugin, directory)\n mergeDependencies(directory, plugin.dependencies)\n }\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 { mergeDependencies } 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 const names = options.names.filter((name) => !external.includes(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 }\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 { spawn } from 'node:child_process'\nimport { createInterface } from 'node:readline/promises'\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 child = spawn('npm', ['install'], {\n cwd: directory,\n stdio: 'inherit',\n shell: process.platform === 'win32',\n })\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 --agent / --no-agent include the offline agent (asked when not given)\n --name <name> the project name (default: the directory name)\n --install run npm install after copying\n -y, --yes ask nothing; use defaults (no agent, 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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"}
|