acrylblends 0.11.0 → 1.0.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/README.md +131 -53
- package/dist/index.d.ts +29 -2
- package/dist/index.js +450 -69
- package/dist/index.js.map +1 -1
- package/docs/framework/README.md +5 -3
- package/docs/framework/agents/index.md +1 -1
- package/docs/framework/api/acryl.md +5 -17
- package/docs/framework/api/context.md +12 -61
- package/docs/framework/api/presets.md +1 -1
- package/docs/framework/api/request.md +3 -3
- package/docs/framework/api/summary.md +1 -1
- package/docs/framework/concepts/web-standard.md +6 -16
- package/docs/framework/cordis/acryl-in-a-cordis-app.md +13 -8
- package/docs/framework/cordis/migration-from-0-4.md +1 -1
- package/docs/framework/cordis/migration-to-1-0.md +45 -0
- package/docs/framework/cordis/node-server.md +2 -2
- package/docs/framework/getting-started/basic.md +15 -10
- package/docs/framework/getting-started/desktop.md +1 -1
- package/docs/framework/getting-started/other-runtimes.md +10 -14
- package/docs/framework/guides/create-acrylblends.md +4 -2
- package/docs/framework/guides/data.md +1 -1
- package/docs/framework/guides/faq.md +1 -1
- package/docs/framework/guides/jsx-dom.md +1 -1
- package/docs/framework/guides/production.md +43 -0
- package/docs/framework/guides/rpc.md +3 -11
- package/docs/framework/helpers/adapter.md +13 -43
- package/docs/framework/helpers/conninfo.md +1 -36
- package/docs/framework/helpers/streaming.md +1 -1
- package/docs/framework/helpers/websocket.md +4 -11
- package/docs/framework/middleware/builtin/basic-auth.md +1 -1
- package/docs/framework/middleware/builtin/cache.md +2 -22
- package/docs/framework/middleware/builtin/compress.md +1 -1
- package/docs/framework/middleware/builtin/context-storage.md +10 -14
- package/docs/framework/middleware/builtin/cors.md +1 -2
- package/docs/framework/middleware/builtin/ip-restriction.md +1 -1
- package/docs/framework/middleware/builtin/jsx-renderer.md +1 -1
- package/docs/framework/middleware/builtin/logger.md +1 -1
- package/docs/framework/middleware/builtin/loopback-only.md +1 -1
- package/docs/framework/middleware/builtin/mount.md +1 -1
- package/docs/framework/middleware/builtin/request-id.md +13 -20
- package/docs/framework/middleware/builtin/timing.md +1 -2
- package/lib/cordis/door.d.ts +5 -0
- package/lib/cordis/door.js +8 -4
- package/lib/cordis/inspect.js +1 -1
- package/lib/cordis/router-http.js +1 -1
- package/lib/cordis/router.d.ts +1 -2
- package/lib/cordis/router.js +2 -3
- package/lib/cordis/state.js +1 -1
- package/lib/door/host.js +1 -1
- package/lib/drivers.d.ts +66 -0
- package/lib/drivers.js +16 -0
- package/lib/index.d.ts +1 -2
- package/lib/index.js +1 -2
- package/lib/{snapshot-text-yaWlezbd.js → snapshot-text-DmE0YfeZ.js} +2 -2
- package/lib/{src-DNzw0Qsb.js → src-2Z4mJg0x.js} +0 -1
- package/package.json +5 -121
- package/plugins/acryl-door/src/index.ts +8 -0
- package/plugins/acryl-router/src/index.ts +0 -1
- package/plugins/agent-panel/drivers.ts +1 -16
- package/plugins/agent-panel/index.ts +1 -1
- package/plugins/agent-panel/registry.ts +16 -0
- package/plugins/driver-acp/README.md +53 -0
- package/plugins/driver-acp/chat.ts +258 -0
- package/plugins/driver-acp/connection.ts +239 -0
- package/plugins/driver-acp/index.ts +65 -0
- package/plugins/driver-acp/plugin.json +12 -0
- package/plugins/driver-acp/provenance.json +20 -0
- package/plugins/driver-pty/index.ts +1 -0
- package/shared/library-history.json +464 -0
- package/docs/framework/getting-started/aws-lambda.md +0 -115
- package/docs/framework/getting-started/cloudflare-pages.md +0 -53
- package/docs/framework/getting-started/cloudflare-workers.md +0 -67
- package/docs/framework/getting-started/lambda-edge.md +0 -41
- package/docs/framework/getting-started/netlify.md +0 -25
- package/docs/framework/getting-started/service-worker.md +0 -33
- package/docs/framework/getting-started/vercel.md +0 -28
- package/lib/aws-lambda.d.ts +0 -324
- package/lib/aws-lambda.js +0 -357
- package/lib/cloudflare-pages.d.ts +0 -58
- package/lib/cloudflare-pages.js +0 -78
- package/lib/cloudflare-workers.d.ts +0 -21
- package/lib/cloudflare-workers.js +0 -80
- package/lib/deprecated-BP4fPt38.js +0 -10
- package/lib/deprecated-CTy4N7ng.d.ts +0 -12
- package/lib/lambda-edge.d.ts +0 -92
- package/lib/lambda-edge.js +0 -105
- package/lib/netlify.d.ts +0 -29
- package/lib/netlify.js +0 -30
- package/lib/service-worker.d.ts +0 -48
- package/lib/service-worker.js +0 -38
- package/lib/vercel.d.ts +0 -10
- package/lib/vercel.js +0 -9
- package/plugins/acryl-router/src/deprecated.ts +0 -15
package/dist/index.js
CHANGED
|
@@ -1,10 +1,143 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, join, posix, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
6
7
|
import { spawn } from "node:child_process";
|
|
7
8
|
import { createInterface } from "node:readline/promises";
|
|
9
|
+
//#region src/templates.ts
|
|
10
|
+
/** The templates, all Cordis apps: a web app with a page and a client component (nodejs), a notes app and a chat app on the db port (notes, chat), a blank app on the router, a todo list on the router and state, and the blank app with the offline agent. */
|
|
11
|
+
const TEMPLATES = [
|
|
12
|
+
"blank",
|
|
13
|
+
"nodejs",
|
|
14
|
+
"notes",
|
|
15
|
+
"chat",
|
|
16
|
+
"todo",
|
|
17
|
+
"agent"
|
|
18
|
+
];
|
|
19
|
+
/** One line per template: the single list `acrylblends init` and `create-acrylblends` both show (`create-acrylblends` tests that its own copy equals this one). */
|
|
20
|
+
const TEMPLATE_SUMMARY = {
|
|
21
|
+
blank: "the router, a server and one route: the smallest app",
|
|
22
|
+
nodejs: "a web app with a page and a client component",
|
|
23
|
+
notes: "a multi-user notes app on the db port (sign-up, log-in, sessions)",
|
|
24
|
+
chat: "a chat app that streams an answer over server-sent events",
|
|
25
|
+
todo: "a to-do list on the router and state",
|
|
26
|
+
agent: "the blank app with an offline agent that builds plugins into the running app"
|
|
27
|
+
};
|
|
28
|
+
/** Names that were templates once and still work: `create-acrylblends --template acryl-app` made the agent app. */
|
|
29
|
+
const TEMPLATE_ALIASES = { "acryl-app": "agent" };
|
|
30
|
+
function isTemplate(value) {
|
|
31
|
+
return TEMPLATES.includes(value);
|
|
32
|
+
}
|
|
33
|
+
/** The template a name means: itself, or what an old name stands for; undefined for anything else. */
|
|
34
|
+
function resolveTemplate(value) {
|
|
35
|
+
return isTemplate(value) ? value : TEMPLATE_ALIASES[value];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The `templates/` folder of this package. The module sits at different depths in source (`src/`) and in the build
|
|
39
|
+
* (`dist/`), so walk up until a `templates` folder holds the `blank` template.
|
|
40
|
+
*/
|
|
41
|
+
function templatesRoot() {
|
|
42
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
43
|
+
for (;;) {
|
|
44
|
+
const candidate = join(dir, "templates");
|
|
45
|
+
if (existsSync(join(candidate, "blank"))) return candidate;
|
|
46
|
+
const parent = dirname(dir);
|
|
47
|
+
if (parent === dir) throw new Error("acrylblends cannot find its templates folder");
|
|
48
|
+
dir = parent;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Absolute path of a template folder. */
|
|
52
|
+
function templateDir(template) {
|
|
53
|
+
return join(templatesRoot(), template);
|
|
54
|
+
}
|
|
55
|
+
/** The text every generated app's `AGENTS.md` starts with (`shared/`, beside `templates/`). */
|
|
56
|
+
function sharedDir() {
|
|
57
|
+
return join(dirname(templatesRoot()), "shared");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 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
|
|
61
|
+
* different depths in source and in the build, so walk up until a `docs/framework` folder holds the README.
|
|
62
|
+
*/
|
|
63
|
+
function frameworkDocsRoot() {
|
|
64
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
65
|
+
for (;;) {
|
|
66
|
+
const candidate = join(dir, "docs", "framework");
|
|
67
|
+
if (existsSync(join(candidate, "README.md"))) return candidate;
|
|
68
|
+
const parent = dirname(dir);
|
|
69
|
+
if (parent === dir) throw new Error("acrylblends cannot find the framework docs (docs/framework)");
|
|
70
|
+
dir = parent;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/version.ts
|
|
75
|
+
/** The version of this package. */
|
|
76
|
+
const VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/lock.ts
|
|
79
|
+
/**
|
|
80
|
+
* What an app knows about the library plugins it carries: `plugins/library.lock.json` records, for every library plugin copied into the app, the version it came from and a hash of each file
|
|
81
|
+
* as it was copied. `acrylblends upgrade` reads it to tell a file nobody touched (safe to replace) from one the app edited (never replaced without `--force`). An app made before the lock existed
|
|
82
|
+
* has none; for it `upgrade` falls back to the hashes of the released versions (`shared/library-history.json`, made by `scripts/build-library-history.mjs`).
|
|
83
|
+
*/
|
|
84
|
+
const LOCK_FILE = "library.lock.json";
|
|
85
|
+
/** A file's identity: a hash of its text with line endings normalized (a checkout may have changed them), cut to 20 hex digits. */
|
|
86
|
+
function digest(content) {
|
|
87
|
+
const text = typeof content === "string" ? content : content.toString("utf8");
|
|
88
|
+
return createHash("sha256").update(text.replace(/\r\n/gu, "\n")).digest("hex").slice(0, 20);
|
|
89
|
+
}
|
|
90
|
+
/** Every file below `dir`, as `relative/path` (forward slashes) to its {@link digest}. */
|
|
91
|
+
function hashFolder(dir) {
|
|
92
|
+
const files = {};
|
|
93
|
+
const walk = (folder, inside) => {
|
|
94
|
+
for (const entry of readdirSync(folder).sort()) {
|
|
95
|
+
const path = join(folder, entry);
|
|
96
|
+
const name = inside === "" ? entry : `${inside}/${entry}`;
|
|
97
|
+
if (statSync(path).isDirectory()) walk(path, name);
|
|
98
|
+
else files[name] = digest(readFileSync(path));
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
if (existsSync(dir)) walk(dir, "");
|
|
102
|
+
return files;
|
|
103
|
+
}
|
|
104
|
+
function readLock(pluginsDir) {
|
|
105
|
+
const file = join(pluginsDir, LOCK_FILE);
|
|
106
|
+
if (!existsSync(file)) return {
|
|
107
|
+
version: 1,
|
|
108
|
+
plugins: {}
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
return {
|
|
112
|
+
version: 1,
|
|
113
|
+
plugins: JSON.parse(readFileSync(file, "utf8")).plugins ?? {}
|
|
114
|
+
};
|
|
115
|
+
} catch {
|
|
116
|
+
return {
|
|
117
|
+
version: 1,
|
|
118
|
+
plugins: {}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Record that `name` was copied from this version of the library, with the hashes of the files as they now are. */
|
|
123
|
+
function recordLock(pluginsDir, name, files) {
|
|
124
|
+
const lock = readLock(pluginsDir);
|
|
125
|
+
lock.plugins[name] = {
|
|
126
|
+
from: VERSION,
|
|
127
|
+
files
|
|
128
|
+
};
|
|
129
|
+
const sorted = Object.fromEntries(Object.entries(lock.plugins).sort(([a], [b]) => a.localeCompare(b)));
|
|
130
|
+
writeFileSync(join(pluginsDir, LOCK_FILE), `${JSON.stringify({
|
|
131
|
+
version: 1,
|
|
132
|
+
plugins: sorted
|
|
133
|
+
}, null, 2)}\n`);
|
|
134
|
+
}
|
|
135
|
+
function readHistory() {
|
|
136
|
+
const file = join(sharedDir(), "library-history.json");
|
|
137
|
+
if (!existsSync(file)) return {};
|
|
138
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
8
141
|
//#region src/rewrite.ts
|
|
9
142
|
function entryTable(library) {
|
|
10
143
|
const table = /* @__PURE__ */ new Map();
|
|
@@ -154,6 +287,7 @@ function pluginsFolder(appDir) {
|
|
|
154
287
|
}
|
|
155
288
|
/** The warning for an app with the old layout. */
|
|
156
289
|
const LEGACY_WARNING = "warning: this app keeps its plugins in src/plugins/ (and extensions/); plugins now live in one folder, plugins/. Move them with \"git mv src/plugins plugins\", fix the paths in tsconfig.json, package.json and src/, and see docs/framework/agents/index.md. The old layout is read for this release only.";
|
|
290
|
+
/** List `name` among the library copies of a plugins folder (`library.json`). */
|
|
157
291
|
function recordLibraryCopy(folder, name) {
|
|
158
292
|
const file = join(folder, LIBRARY_LIST);
|
|
159
293
|
const names = existsSync(file) ? JSON.parse(readFileSync(file, "utf8")).plugins : [];
|
|
@@ -177,7 +311,10 @@ function copyPlugin(plugin, appDir, root = libraryRoot()) {
|
|
|
177
311
|
}
|
|
178
312
|
});
|
|
179
313
|
if (entries) rewriteTree(join(target, "src"), plugin.name, "src", entries);
|
|
180
|
-
if (!folder.legacy)
|
|
314
|
+
if (!folder.legacy) {
|
|
315
|
+
recordLibraryCopy(folder.dir, plugin.name);
|
|
316
|
+
recordLock(folder.dir, plugin.name, hashFolder(target));
|
|
317
|
+
}
|
|
181
318
|
return target;
|
|
182
319
|
}
|
|
183
320
|
/** Rewrite the `acryl-*` imports of every TypeScript file below `dir` (the plugin's `src/`) to relative paths. */
|
|
@@ -194,10 +331,6 @@ function copyRewritten(from, to, plugin, inside, root = libraryRoot()) {
|
|
|
194
331
|
}
|
|
195
332
|
const relativeName = (appDir, path) => path.slice(appDir.length + 1).split(sep).join("/");
|
|
196
333
|
//#endregion
|
|
197
|
-
//#region src/version.ts
|
|
198
|
-
/** The version of this package. */
|
|
199
|
-
const VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
200
|
-
//#endregion
|
|
201
334
|
//#region src/project.ts
|
|
202
335
|
/** Thrown when the target directory exists and is not empty. Nothing has been written. */
|
|
203
336
|
var TargetNotEmptyError = class extends Error {
|
|
@@ -392,56 +525,6 @@ function copyFrameworkDocs(source, target) {
|
|
|
392
525
|
return written;
|
|
393
526
|
}
|
|
394
527
|
//#endregion
|
|
395
|
-
//#region src/templates.ts
|
|
396
|
-
/** The templates, all Cordis apps: a web app with a page and a client component (nodejs), a notes app and a chat app on the db port (notes, chat), a blank app on the router, a todo list on the router and state, and the blank app with the offline agent. */
|
|
397
|
-
const TEMPLATES = [
|
|
398
|
-
"nodejs",
|
|
399
|
-
"notes",
|
|
400
|
-
"chat",
|
|
401
|
-
"blank",
|
|
402
|
-
"todo",
|
|
403
|
-
"agent"
|
|
404
|
-
];
|
|
405
|
-
function isTemplate(value) {
|
|
406
|
-
return TEMPLATES.includes(value);
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* The `templates/` folder of this package. The module sits at different depths in source (`src/`) and in the build
|
|
410
|
-
* (`dist/`), so walk up until a `templates` folder holds the `blank` template.
|
|
411
|
-
*/
|
|
412
|
-
function templatesRoot() {
|
|
413
|
-
let dir = dirname(fileURLToPath(import.meta.url));
|
|
414
|
-
for (;;) {
|
|
415
|
-
const candidate = join(dir, "templates");
|
|
416
|
-
if (existsSync(join(candidate, "blank"))) return candidate;
|
|
417
|
-
const parent = dirname(dir);
|
|
418
|
-
if (parent === dir) throw new Error("acrylblends cannot find its templates folder");
|
|
419
|
-
dir = parent;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
/** Absolute path of a template folder. */
|
|
423
|
-
function templateDir(template) {
|
|
424
|
-
return join(templatesRoot(), template);
|
|
425
|
-
}
|
|
426
|
-
/** The text every generated app's `AGENTS.md` starts with (`shared/`, beside `templates/`). */
|
|
427
|
-
function sharedDir() {
|
|
428
|
-
return join(dirname(templatesRoot()), "shared");
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* 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
|
|
432
|
-
* different depths in source and in the build, so walk up until a `docs/framework` folder holds the README.
|
|
433
|
-
*/
|
|
434
|
-
function frameworkDocsRoot() {
|
|
435
|
-
let dir = dirname(fileURLToPath(import.meta.url));
|
|
436
|
-
for (;;) {
|
|
437
|
-
const candidate = join(dir, "docs", "framework");
|
|
438
|
-
if (existsSync(join(candidate, "README.md"))) return candidate;
|
|
439
|
-
const parent = dirname(dir);
|
|
440
|
-
if (parent === dir) throw new Error("acrylblends cannot find the framework docs (docs/framework)");
|
|
441
|
-
dir = parent;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
//#endregion
|
|
445
528
|
//#region src/init.ts
|
|
446
529
|
/** A command line mistake. The CLI prints it with the help text and exits 2. */
|
|
447
530
|
var UsageError = class extends Error {
|
|
@@ -451,26 +534,27 @@ var UsageError = class extends Error {
|
|
|
451
534
|
function parseInitOptions(argv) {
|
|
452
535
|
let parsed;
|
|
453
536
|
try {
|
|
454
|
-
parsed = parse$
|
|
537
|
+
parsed = parse$3(argv);
|
|
455
538
|
} catch (error) {
|
|
456
539
|
throw new UsageError(error.message);
|
|
457
540
|
}
|
|
458
541
|
const { values, positionals } = parsed;
|
|
459
542
|
if (positionals.length > 1) throw new UsageError("init takes at most one directory");
|
|
460
543
|
if (values.agent && values["no-agent"]) throw new UsageError("use --agent or --no-agent, not both");
|
|
461
|
-
|
|
544
|
+
const named = values.template === void 0 ? void 0 : resolveTemplate(values.template);
|
|
545
|
+
if (values.template !== void 0 && named === void 0) throw new UsageError(`unknown template "${values.template}" (${TEMPLATES.join(", ")})`);
|
|
462
546
|
const alias = values.agent ? "agent" : values["no-agent"] ? "blank" : void 0;
|
|
463
|
-
if (
|
|
547
|
+
if (named !== void 0 && alias !== void 0 && named !== alias) throw new UsageError(`--template ${values.template} contradicts --${alias === "agent" ? "agent" : "no-agent"}`);
|
|
464
548
|
return {
|
|
465
549
|
directory: positionals[0],
|
|
466
550
|
name: values.name,
|
|
467
|
-
template:
|
|
551
|
+
template: named ?? alias,
|
|
468
552
|
install: values.install,
|
|
469
553
|
desktop: values.desktop === true,
|
|
470
554
|
yes: values.yes === true
|
|
471
555
|
};
|
|
472
556
|
}
|
|
473
|
-
const parse$
|
|
557
|
+
const parse$3 = (argv) => parseArgs({
|
|
474
558
|
args: [...argv],
|
|
475
559
|
allowPositionals: true,
|
|
476
560
|
strict: true,
|
|
@@ -489,9 +573,10 @@ const parse$2 = (argv) => parseArgs({
|
|
|
489
573
|
});
|
|
490
574
|
const yes = (answer) => ["y", "yes"].includes(answer.trim().toLowerCase());
|
|
491
575
|
async function askTemplate(ask) {
|
|
492
|
-
const answer = (await ask(`
|
|
576
|
+
const answer = (await ask(`Templates:\n${TEMPLATES.map((name) => ` ${name.padEnd(7)} ${TEMPLATE_SUMMARY[name]}`).join("\n")}\nTemplate [blank]: `)).trim().toLowerCase();
|
|
493
577
|
if (answer === "") return "blank";
|
|
494
|
-
|
|
578
|
+
const chosen = resolveTemplate(answer);
|
|
579
|
+
if (chosen !== void 0) return chosen;
|
|
495
580
|
throw new UsageError(`unknown template "${answer}" (${TEMPLATES.join(", ")})`);
|
|
496
581
|
}
|
|
497
582
|
/** Scaffold a Cordis app. Returns the exit code: 0 done, 1 refused or failed. Never overwrites anything. */
|
|
@@ -542,7 +627,7 @@ async function init(options, io) {
|
|
|
542
627
|
function parseAddOptions(argv) {
|
|
543
628
|
let parsed;
|
|
544
629
|
try {
|
|
545
|
-
parsed = parse$
|
|
630
|
+
parsed = parse$2(argv);
|
|
546
631
|
} catch (error) {
|
|
547
632
|
throw new UsageError(error.message);
|
|
548
633
|
}
|
|
@@ -553,7 +638,7 @@ function parseAddOptions(argv) {
|
|
|
553
638
|
suite: parsed.values.suite === true
|
|
554
639
|
};
|
|
555
640
|
}
|
|
556
|
-
const parse$
|
|
641
|
+
const parse$2 = (argv) => parseArgs({
|
|
557
642
|
args: [...argv],
|
|
558
643
|
allowPositionals: true,
|
|
559
644
|
strict: true,
|
|
@@ -976,7 +1061,7 @@ function processIo() {
|
|
|
976
1061
|
function parseKvOptions(argv) {
|
|
977
1062
|
let parsed;
|
|
978
1063
|
try {
|
|
979
|
-
parsed = parse(argv);
|
|
1064
|
+
parsed = parse$1(argv);
|
|
980
1065
|
} catch (error) {
|
|
981
1066
|
throw new UsageError(error.message);
|
|
982
1067
|
}
|
|
@@ -991,7 +1076,7 @@ function parseKvOptions(argv) {
|
|
|
991
1076
|
migrations: parsed.values.migrations
|
|
992
1077
|
};
|
|
993
1078
|
}
|
|
994
|
-
const parse = (argv) => parseArgs({
|
|
1079
|
+
const parse$1 = (argv) => parseArgs({
|
|
995
1080
|
args: [...argv],
|
|
996
1081
|
allowPositionals: true,
|
|
997
1082
|
strict: true,
|
|
@@ -1046,6 +1131,295 @@ async function kvImport(options, io) {
|
|
|
1046
1131
|
}
|
|
1047
1132
|
}
|
|
1048
1133
|
//#endregion
|
|
1134
|
+
//#region src/diff.ts
|
|
1135
|
+
function ops(a, b) {
|
|
1136
|
+
if (a.length * b.length > 6e6) return void 0;
|
|
1137
|
+
const width = b.length + 1;
|
|
1138
|
+
const table = new Uint32Array((a.length + 1) * width);
|
|
1139
|
+
for (let i = a.length - 1; i >= 0; i -= 1) for (let j = b.length - 1; j >= 0; j -= 1) table[i * width + j] = a[i] === b[j] ? table[(i + 1) * width + j + 1] + 1 : Math.max(table[(i + 1) * width + j], table[i * width + j + 1]);
|
|
1140
|
+
const out = [];
|
|
1141
|
+
let i = 0;
|
|
1142
|
+
let j = 0;
|
|
1143
|
+
while (i < a.length && j < b.length) if (a[i] === b[j]) {
|
|
1144
|
+
out.push({
|
|
1145
|
+
kind: " ",
|
|
1146
|
+
line: a[i]
|
|
1147
|
+
});
|
|
1148
|
+
i += 1;
|
|
1149
|
+
j += 1;
|
|
1150
|
+
} else if (table[(i + 1) * width + j] >= table[i * width + j + 1]) {
|
|
1151
|
+
out.push({
|
|
1152
|
+
kind: "-",
|
|
1153
|
+
line: a[i]
|
|
1154
|
+
});
|
|
1155
|
+
i += 1;
|
|
1156
|
+
} else {
|
|
1157
|
+
out.push({
|
|
1158
|
+
kind: "+",
|
|
1159
|
+
line: b[j]
|
|
1160
|
+
});
|
|
1161
|
+
j += 1;
|
|
1162
|
+
}
|
|
1163
|
+
for (; i < a.length; i += 1) out.push({
|
|
1164
|
+
kind: "-",
|
|
1165
|
+
line: a[i]
|
|
1166
|
+
});
|
|
1167
|
+
for (; j < b.length; j += 1) out.push({
|
|
1168
|
+
kind: "+",
|
|
1169
|
+
line: b[j]
|
|
1170
|
+
});
|
|
1171
|
+
return out;
|
|
1172
|
+
}
|
|
1173
|
+
const lines = (text) => text === "" ? [] : text.replace(/\r\n/gu, "\n").replace(/\n$/u, "").split("\n");
|
|
1174
|
+
/** How many lines a change adds and removes; undefined counts when the pair is too large to compare. */
|
|
1175
|
+
function diffStat(from, to) {
|
|
1176
|
+
const all = ops(lines(from), lines(to));
|
|
1177
|
+
if (all === void 0) return {
|
|
1178
|
+
added: lines(to).length,
|
|
1179
|
+
removed: lines(from).length
|
|
1180
|
+
};
|
|
1181
|
+
return {
|
|
1182
|
+
added: all.filter((op) => op.kind === "+").length,
|
|
1183
|
+
removed: all.filter((op) => op.kind === "-").length
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
/** `--- a/<name>`, `+++ b/<name>` and the hunks; empty when the texts are equal. */
|
|
1187
|
+
function unifiedDiff(name, from, to, context = 3) {
|
|
1188
|
+
const a = lines(from);
|
|
1189
|
+
const b = lines(to);
|
|
1190
|
+
const all = ops(a, b);
|
|
1191
|
+
if (all === void 0) return `--- a/${name}\n+++ b/${name}\n(too large to compare line by line: ${a.length} lines became ${b.length})\n`;
|
|
1192
|
+
if (!all.some((op) => op.kind !== " ")) return "";
|
|
1193
|
+
const out = [`--- a/${name}`, `+++ b/${name}`];
|
|
1194
|
+
let at = 0;
|
|
1195
|
+
let aLine = 1;
|
|
1196
|
+
let bLine = 1;
|
|
1197
|
+
const positions = all.map((op) => {
|
|
1198
|
+
const here = {
|
|
1199
|
+
a: aLine,
|
|
1200
|
+
b: bLine
|
|
1201
|
+
};
|
|
1202
|
+
if (op.kind !== "+") aLine += 1;
|
|
1203
|
+
if (op.kind !== "-") bLine += 1;
|
|
1204
|
+
return here;
|
|
1205
|
+
});
|
|
1206
|
+
while (at < all.length) {
|
|
1207
|
+
while (at < all.length && all[at]?.kind === " ") at += 1;
|
|
1208
|
+
if (at >= all.length) break;
|
|
1209
|
+
const start = Math.max(0, at - context);
|
|
1210
|
+
let end = at;
|
|
1211
|
+
for (let k = at; k < all.length; k += 1) if (all[k]?.kind !== " ") end = k;
|
|
1212
|
+
else if (k - end > context * 2) break;
|
|
1213
|
+
const stop = Math.min(all.length, end + context + 1);
|
|
1214
|
+
const slice = all.slice(start, stop);
|
|
1215
|
+
const first = positions[start];
|
|
1216
|
+
out.push(`@@ -${first.a},${slice.filter((op) => op.kind !== "+").length} +${first.b},${slice.filter((op) => op.kind !== "-").length} @@`);
|
|
1217
|
+
for (const op of slice) out.push(`${op.kind}${op.line}`);
|
|
1218
|
+
at = stop;
|
|
1219
|
+
}
|
|
1220
|
+
return `${out.join("\n")}\n`;
|
|
1221
|
+
}
|
|
1222
|
+
//#endregion
|
|
1223
|
+
//#region src/upgrade.ts
|
|
1224
|
+
/** Parse `upgrade [plugin...] [--dir <app>] [--dry-run] [--force] [--diff]`. */
|
|
1225
|
+
function parseUpgradeOptions(argv) {
|
|
1226
|
+
let parsed;
|
|
1227
|
+
try {
|
|
1228
|
+
parsed = parse(argv);
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
throw new UsageError(error.message);
|
|
1231
|
+
}
|
|
1232
|
+
return {
|
|
1233
|
+
names: parsed.positionals,
|
|
1234
|
+
directory: parsed.values.dir,
|
|
1235
|
+
dryRun: parsed.values["dry-run"] === true,
|
|
1236
|
+
force: parsed.values.force === true,
|
|
1237
|
+
diff: parsed.values.diff === true
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
const parse = (argv) => parseArgs({
|
|
1241
|
+
args: [...argv],
|
|
1242
|
+
allowPositionals: true,
|
|
1243
|
+
strict: true,
|
|
1244
|
+
options: {
|
|
1245
|
+
dir: { type: "string" },
|
|
1246
|
+
"dry-run": { type: "boolean" },
|
|
1247
|
+
force: { type: "boolean" },
|
|
1248
|
+
diff: { type: "boolean" }
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
const posix$1 = (path) => path.split(sep).join("/");
|
|
1252
|
+
function readText(path) {
|
|
1253
|
+
return readFileSync(path, "utf8");
|
|
1254
|
+
}
|
|
1255
|
+
/** The library copies an app carries: the plugins listed in `plugins/library.json`, or when there is none the folders of `plugins/` that the library knows. */
|
|
1256
|
+
function carriedCopies(dir) {
|
|
1257
|
+
const listFile = join(dir, LIBRARY_LIST);
|
|
1258
|
+
return (existsSync(listFile) ? JSON.parse(readFileSync(listFile, "utf8")).plugins ?? [] : readdirSync(dir).filter((name) => statSync(join(dir, name)).isDirectory())).filter((name) => existsSync(join(dir, name)));
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* `acrylblends upgrade`: bring the library plugins an app carries (the router, the server, the door, `extensions`, ...) up to the version of this package. Each file is compared with what
|
|
1262
|
+
* the app was given: an untouched file is replaced, a file the app edited stops the upgrade (it is shown as a diff and nothing changes) unless `--force`, which saves the edited version
|
|
1263
|
+
* under `.acrylblends/upgrade-backup/` first. Returns the exit code: 0 done or nothing to do, 1 refused or failed.
|
|
1264
|
+
*/
|
|
1265
|
+
async function upgrade(options, io) {
|
|
1266
|
+
const appDir = resolve(io.cwd, options.directory ?? ".");
|
|
1267
|
+
if (!existsSync(join(appDir, "package.json")) || !existsSync(join(appDir, "src"))) {
|
|
1268
|
+
io.stderr.write(`error: ${appDir} is not an app (no package.json and src/); run "acrylblends init" first\n`);
|
|
1269
|
+
return 1;
|
|
1270
|
+
}
|
|
1271
|
+
const folder = pluginsFolder(appDir);
|
|
1272
|
+
if (folder.legacy) {
|
|
1273
|
+
io.stderr.write(`${LEGACY_WARNING}\nerror: upgrade needs the one-folder layout (plugins/); move the plugins first\n`);
|
|
1274
|
+
return 1;
|
|
1275
|
+
}
|
|
1276
|
+
const library = readLibrary();
|
|
1277
|
+
const carried = carriedCopies(folder.dir);
|
|
1278
|
+
const unknown = carried.filter((name) => !library.has(name));
|
|
1279
|
+
for (const name of options.names) if (!carried.includes(name)) {
|
|
1280
|
+
io.stderr.write(`error: the app carries no library plugin "${name}" (it carries: ${carried.filter((n) => library.has(n)).join(", ") || "none"})\n`);
|
|
1281
|
+
return 1;
|
|
1282
|
+
}
|
|
1283
|
+
const selected = (options.names.length > 0 ? [...options.names] : carried).filter((name) => library.has(name));
|
|
1284
|
+
if (selected.length === 0) {
|
|
1285
|
+
io.stdout.write("nothing to upgrade: the app carries no library plugins\n");
|
|
1286
|
+
return 0;
|
|
1287
|
+
}
|
|
1288
|
+
const scratch = join(appDir, ".acrylblends", `upgrade-${process.pid}-${Date.now()}`);
|
|
1289
|
+
const lock = readLock(folder.dir);
|
|
1290
|
+
const history = readHistory();
|
|
1291
|
+
const plans = [];
|
|
1292
|
+
try {
|
|
1293
|
+
mkdirSync(join(scratch, "plugins"), { recursive: true });
|
|
1294
|
+
const wanted = closure(selected, library);
|
|
1295
|
+
for (const plugin of wanted) copyPlugin(plugin, scratch);
|
|
1296
|
+
for (const plugin of wanted) {
|
|
1297
|
+
const here = join(folder.dir, plugin.name);
|
|
1298
|
+
const fresh = hashFolder(join(scratch, "plugins", plugin.name));
|
|
1299
|
+
const files = /* @__PURE__ */ new Map();
|
|
1300
|
+
const isNew = !existsSync(here);
|
|
1301
|
+
const mine = isNew ? {} : hashFolder(here);
|
|
1302
|
+
const recorded = lock.plugins[plugin.name]?.files ?? {};
|
|
1303
|
+
const pristine = (file, hash) => recorded[file] === hash || (history[plugin.name]?.[file]?.includes(hash) ?? false);
|
|
1304
|
+
for (const [file, hash] of Object.entries(fresh)) {
|
|
1305
|
+
const have = mine[file];
|
|
1306
|
+
files.set(file, have === void 0 ? "add" : have === hash ? "same" : pristine(file, have) ? "update" : "conflict");
|
|
1307
|
+
}
|
|
1308
|
+
for (const [file, hash] of Object.entries(mine)) if (!(file in fresh)) files.set(file, pristine(file, hash) ? "remove" : "extra");
|
|
1309
|
+
plans.push({
|
|
1310
|
+
plugin,
|
|
1311
|
+
isNew,
|
|
1312
|
+
files,
|
|
1313
|
+
fresh
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
const count = (plan, state) => [...plan.files].filter(([, s]) => s === state).map(([file]) => file);
|
|
1317
|
+
io.stdout.write(`upgrade: the library plugins of ${appDir} to acrylblends ${VERSION}\n`);
|
|
1318
|
+
let conflicts = 0;
|
|
1319
|
+
let changes = 0;
|
|
1320
|
+
for (const plan of plans) {
|
|
1321
|
+
const [updated, added, removed, edited, extra] = [
|
|
1322
|
+
"update",
|
|
1323
|
+
"add",
|
|
1324
|
+
"remove",
|
|
1325
|
+
"conflict",
|
|
1326
|
+
"extra"
|
|
1327
|
+
].map((state) => count(plan, state));
|
|
1328
|
+
const label = plan.plugin.name.padEnd(22);
|
|
1329
|
+
conflicts += edited.length;
|
|
1330
|
+
if (plan.isNew) {
|
|
1331
|
+
changes += 1;
|
|
1332
|
+
io.stdout.write(` ${label} new (required by a plugin of the app)\n`);
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
if (updated.length + added.length + removed.length + edited.length === 0) {
|
|
1336
|
+
io.stdout.write(` ${label} up to date${extra.length > 0 ? ` (keeps ${extra.length} file(s) of its own)` : ""}\n`);
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
changes += 1;
|
|
1340
|
+
let plus = 0;
|
|
1341
|
+
let minus = 0;
|
|
1342
|
+
for (const file of [...updated, ...edited]) {
|
|
1343
|
+
const stat = diffStat(readText(join(folder.dir, plan.plugin.name, file)), readText(join(scratch, "plugins", plan.plugin.name, file)));
|
|
1344
|
+
plus += stat.added;
|
|
1345
|
+
minus += stat.removed;
|
|
1346
|
+
}
|
|
1347
|
+
const parts = [
|
|
1348
|
+
updated.length > 0 ? `${updated.length} updated (+${plus} -${minus})` : "",
|
|
1349
|
+
added.length > 0 ? `${added.length} added` : "",
|
|
1350
|
+
removed.length > 0 ? `${removed.length} removed` : "",
|
|
1351
|
+
edited.length > 0 ? `${edited.length} EDITED HERE` : "",
|
|
1352
|
+
extra.length > 0 ? `${extra.length} of its own kept` : ""
|
|
1353
|
+
].filter(Boolean);
|
|
1354
|
+
io.stdout.write(` ${label} ${parts.join(", ")}\n`);
|
|
1355
|
+
}
|
|
1356
|
+
for (const name of unknown) io.stdout.write(` ${name.padEnd(22)} not in this library: left alone\n`);
|
|
1357
|
+
for (const plan of plans) for (const [file, state] of plan.files) {
|
|
1358
|
+
if (!(state === "conflict" || options.diff && (state === "update" || state === "add" || state === "remove"))) continue;
|
|
1359
|
+
const name = `plugins/${plan.plugin.name}/${file}`;
|
|
1360
|
+
const before = state === "add" ? "" : readText(join(folder.dir, plan.plugin.name, file));
|
|
1361
|
+
const after = state === "remove" ? "" : readText(join(scratch, "plugins", plan.plugin.name, file));
|
|
1362
|
+
io.stdout.write(`\n${state === "conflict" ? "EDITED HERE, the upgrade would replace it:\n" : ""}${unifiedDiff(name, before, after)}`);
|
|
1363
|
+
}
|
|
1364
|
+
if (conflicts > 0 && !options.force) {
|
|
1365
|
+
io.stderr.write(`\nerror: ${conflicts} file(s) were edited in this app and the upgrade would replace them; nothing was changed. Review the diffs above, move your changes out of the plugin (a plugin of your own in plugins/<name>/), or run again with --force (the edited versions are saved under .acrylblends/upgrade-backup/)\n`);
|
|
1366
|
+
return 1;
|
|
1367
|
+
}
|
|
1368
|
+
if (options.dryRun) {
|
|
1369
|
+
io.stdout.write(`\n${changes === 0 ? "everything is up to date" : "dry run: nothing was changed"}\n`);
|
|
1370
|
+
return 0;
|
|
1371
|
+
}
|
|
1372
|
+
if (changes === 0) {
|
|
1373
|
+
io.stdout.write("\neverything is up to date\n");
|
|
1374
|
+
return 0;
|
|
1375
|
+
}
|
|
1376
|
+
const backup = join(appDir, ".acrylblends", "upgrade-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/gu, "-"));
|
|
1377
|
+
const addedDependencies = [];
|
|
1378
|
+
for (const plan of plans) {
|
|
1379
|
+
const here = join(folder.dir, plan.plugin.name);
|
|
1380
|
+
if (plan.isNew) {
|
|
1381
|
+
mkdirSync(here, { recursive: true });
|
|
1382
|
+
cpSync(join(scratch, "plugins", plan.plugin.name), here, { recursive: true });
|
|
1383
|
+
recordLibraryCopy(folder.dir, plan.plugin.name);
|
|
1384
|
+
} else {
|
|
1385
|
+
for (const [file, state] of plan.files) {
|
|
1386
|
+
const target = join(here, file);
|
|
1387
|
+
if (state === "conflict") {
|
|
1388
|
+
mkdirSync(dirname(join(backup, plan.plugin.name, file)), { recursive: true });
|
|
1389
|
+
cpSync(target, join(backup, plan.plugin.name, file));
|
|
1390
|
+
}
|
|
1391
|
+
if (state === "add" || state === "update" || state === "conflict") {
|
|
1392
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
1393
|
+
cpSync(join(scratch, "plugins", plan.plugin.name, file), target);
|
|
1394
|
+
} else if (state === "remove") rmSync(target, { force: true });
|
|
1395
|
+
}
|
|
1396
|
+
recordLibraryCopy(folder.dir, plan.plugin.name);
|
|
1397
|
+
}
|
|
1398
|
+
recordLock(folder.dir, plan.plugin.name, plan.fresh);
|
|
1399
|
+
const plugin = plan.plugin;
|
|
1400
|
+
if (plugin.imports) mergeImports(appDir, plugin.imports, posix$1(relative(appDir, folder.dir)));
|
|
1401
|
+
addedDependencies.push(...mergeDependencies(appDir, plugin.dependencies));
|
|
1402
|
+
addedDependencies.push(...mergeTooling(appDir, plugin).devDependencies);
|
|
1403
|
+
appendGitignore(appDir, plugin.gitignore);
|
|
1404
|
+
}
|
|
1405
|
+
const manifestFile = join(appDir, "package.json");
|
|
1406
|
+
const manifest = JSON.parse(readFileSync(manifestFile, "utf8"));
|
|
1407
|
+
if (manifest.dependencies?.acrylblends !== void 0 && manifest.dependencies.acrylblends !== `^${VERSION}`) {
|
|
1408
|
+
manifest.dependencies.acrylblends = `^${VERSION}`;
|
|
1409
|
+
writeFileSync(manifestFile, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
1410
|
+
}
|
|
1411
|
+
io.stdout.write(`\nupgraded ${plans.filter((p) => [...p.files.values()].some((s) => s !== "same" && s !== "extra") || p.isNew).map((p) => p.plugin.name).join(", ")}\n`);
|
|
1412
|
+
if (conflicts > 0) io.stdout.write(`the ${conflicts} edited file(s) replaced by --force are saved in ${posix$1(relative(appDir, backup))}/\n`);
|
|
1413
|
+
io.stdout.write(`acrylblends is now ^${VERSION} in package.json${addedDependencies.length > 0 ? `; new dependencies: ${[...new Set(addedDependencies)].join(", ")}` : ""}: run npm install, then npm run check and npm test\n`);
|
|
1414
|
+
return 0;
|
|
1415
|
+
} finally {
|
|
1416
|
+
rmSync(scratch, {
|
|
1417
|
+
recursive: true,
|
|
1418
|
+
force: true
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
//#endregion
|
|
1049
1423
|
//#region src/cli.ts
|
|
1050
1424
|
const HELP = `acrylblends ${VERSION} - scaffold Cordis apps
|
|
1051
1425
|
|
|
@@ -1054,6 +1428,9 @@ Usage:
|
|
|
1054
1428
|
acrylblends add <plugin...> [--dir d] [--suite] Copy library plugins (and what they require) into an app;
|
|
1055
1429
|
a folder path brings an external plugin into its plugins/;
|
|
1056
1430
|
--suite also copies the port's conformance suite and vitest (add kv --suite)
|
|
1431
|
+
acrylblends upgrade [plugin...] [--dir d] [--dry-run] [--force] [--diff]
|
|
1432
|
+
Bring the library plugins an app carries (the router, the server, the door, ...) up to this
|
|
1433
|
+
version: untouched files are replaced, an edited file stops it (diff shown) unless --force
|
|
1057
1434
|
acrylblends plugins List the plugin library
|
|
1058
1435
|
acrylblends build-client [root] [out] Compile the client modules once for production (src/client to dist/client)
|
|
1059
1436
|
acrylblends kv import <kv.json> <db> Move a JSON-file kv store into the kv table of a SQLite database [--overwrite] [--table t] [--migrations dir]
|
|
@@ -1061,7 +1438,7 @@ Usage:
|
|
|
1061
1438
|
package.json "acryl": { "opener": "src/db.ts" } (a port is imported, a provider is not)
|
|
1062
1439
|
|
|
1063
1440
|
Options for init:
|
|
1064
|
-
--template <name>
|
|
1441
|
+
--template <name> one of the templates below (asked when not given); --agent means --template agent,
|
|
1065
1442
|
--no-agent means --template blank
|
|
1066
1443
|
--name <name> the project name (default: the directory name)
|
|
1067
1444
|
--desktop also add the desktop shell: npm run desktop opens a window on the app
|
|
@@ -1070,6 +1447,9 @@ Options for init:
|
|
|
1070
1447
|
|
|
1071
1448
|
-h, --help show this help
|
|
1072
1449
|
-v, --version show the version
|
|
1450
|
+
|
|
1451
|
+
Templates (the same list as npm create acrylblends):
|
|
1452
|
+
${TEMPLATES.map((name) => ` ${name.padEnd(7)} ${TEMPLATE_SUMMARY[name]}`).join("\n")}
|
|
1073
1453
|
`;
|
|
1074
1454
|
/**
|
|
1075
1455
|
* Run the CLI and return the exit code: 0 done, 1 failed or refused, 2 a usage error. Never calls `process.exit`.
|
|
@@ -1087,6 +1467,7 @@ async function runCli(argv, io = processIo()) {
|
|
|
1087
1467
|
}
|
|
1088
1468
|
if (command === "init") return await init(parseInitOptions(args), io);
|
|
1089
1469
|
if (command === "add") return await add(parseAddOptions(args), io);
|
|
1470
|
+
if (command === "upgrade") return await upgrade(parseUpgradeOptions(args), io);
|
|
1090
1471
|
if (command === "build-client") return await buildClient(parseBuildClientOptions(args), io);
|
|
1091
1472
|
if (command === "kv") return await kvImport(parseKv(args), io);
|
|
1092
1473
|
if (command === "check-deps") return checkDeps(parseCheckDeps(args), io);
|
|
@@ -1112,6 +1493,6 @@ function parseKv(args) {
|
|
|
1112
1493
|
return parseKvOptions(args);
|
|
1113
1494
|
}
|
|
1114
1495
|
//#endregion
|
|
1115
|
-
export { HELP, TEMPLATES, TargetNotEmptyError, UsageError, VERSION, add, assertTargetFree, closure, copyPlugin, copyTemplate, init, isOmitted, isTemplate, isValidName, libraryRoot, listPlugins, mergeDependencies, nameFrom, parseAddOptions, parseInitOptions, readLibrary, readTemplatePlugins, runCli, templateDir, templatesRoot };
|
|
1496
|
+
export { HELP, TEMPLATES, TEMPLATE_ALIASES, TEMPLATE_SUMMARY, TargetNotEmptyError, UsageError, VERSION, add, assertTargetFree, closure, copyPlugin, copyTemplate, init, isOmitted, isTemplate, isValidName, libraryRoot, listPlugins, mergeDependencies, nameFrom, parseAddOptions, parseInitOptions, parseUpgradeOptions, readLibrary, readTemplatePlugins, resolveTemplate, runCli, templateDir, templatesRoot, upgrade };
|
|
1116
1497
|
|
|
1117
1498
|
//# sourceMappingURL=index.js.map
|