acrylblends 0.7.6 → 0.7.8
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/dist/index.d.ts +1 -1
- package/dist/index.js +148 -24
- package/dist/index.js.map +1 -1
- package/docs/framework/README.md +1 -1
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/agents/index.md +18 -3
- package/docs/framework/getting-started/desktop.md +2 -2
- package/docs/framework/guides/client-modules.md +4 -3
- package/docs/framework/guides/data.md +72 -8
- package/docs/framework/helpers/streaming.md +12 -27
- package/docs/framework/upstream.json +2 -2
- package/lib/browser/read-sse.js +84 -0
- package/lib/index-BLTYsyc1.d.ts +93 -0
- package/lib/kv/db.d.ts +12 -0
- package/lib/kv/db.js +57 -0
- package/lib/kv/json.d.ts +11 -0
- package/lib/kv/json.js +224 -0
- package/lib/kv.d.ts +2 -0
- package/lib/kv.js +2 -0
- package/lib/node-server.d.ts +3 -3
- package/lib/node-server.js +13 -8
- package/lib/read-sse.d.ts +33 -0
- package/lib/read-sse.js +84 -0
- package/lib/src-DRFVEQQb.js +90 -0
- package/package.json +17 -1
- package/plugins/acryl-db/plugin.json +1 -1
- package/plugins/acryl-db/upstream.json +2 -2
- package/plugins/acryl-db-sqlite/upstream.json +1 -1
- package/plugins/acryl-inspect/upstream.json +1 -1
- package/plugins/acryl-kv/LICENSE +47 -0
- package/plugins/acryl-kv/plugin.json +12 -0
- package/plugins/acryl-kv/src/check.ts +58 -0
- package/plugins/acryl-kv/src/copy.ts +39 -0
- package/plugins/acryl-kv/src/cordis.ts +13 -0
- package/plugins/acryl-kv/src/index.ts +14 -0
- package/plugins/acryl-kv/src/kv.ts +37 -0
- package/plugins/acryl-kv/upstream.json +18 -0
- package/plugins/acryl-kv-db/LICENSE +47 -0
- package/plugins/acryl-kv-db/plugin.json +13 -0
- package/plugins/acryl-kv-db/src/index.ts +37 -0
- package/plugins/acryl-kv-db/src/open.ts +80 -0
- package/plugins/acryl-kv-db/upstream.json +21 -0
- package/plugins/acryl-kv-json/LICENSE +47 -0
- package/plugins/acryl-kv-json/plugin.json +13 -0
- package/plugins/acryl-kv-json/src/index.ts +71 -0
- package/plugins/acryl-kv-json/src/open.ts +249 -0
- package/plugins/acryl-kv-json/upstream.json +22 -0
- package/plugins/acryl-node-server/src/client-modules.ts +10 -9
- package/plugins/acryl-node-server/src/index.ts +12 -1
- package/plugins/acryl-node-server/upstream.json +2 -2
- package/plugins/acryl-router/src/browser/read-sse.ts +3 -0
- package/plugins/acryl-router/src/helpers/read-sse.ts +111 -0
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/upstream.json +1 -1
- package/plugins/acryl-state/upstream.json +1 -1
- package/plugins/builder/index.ts +2 -2
- package/plugins/builder/plugin.json +1 -1
- package/plugins/desktop/main.ts +3 -3
- package/plugins/desktop/plugin.json +2 -2
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/main.ts +2 -2
- package/plugins/desktop-web/plugin.json +2 -2
- package/plugins/desktop-web/upstream.json +1 -1
- package/plugins/extensions/index.ts +19 -1
- package/plugins/extensions/plugin.json +1 -1
- package/plugins/extensions/resolve.ts +1 -1
- package/plugins/manifest/lock.ts +7 -3
- package/plugins/manifest/upgrade-cli.ts +1 -1
- package/plugins/save/cli.ts +1 -1
- package/plugins/save/plugin.json +1 -1
- package/plugins/tsconfig.json +2 -0
- package/shared/AGENTS.core.md +10 -10
- package/shared/DEFAULTS.md +40 -0
- package/shared/SKILL.md +14 -0
- package/templates/agent/AGENTS.md +1 -1
- package/templates/agent/README.md +6 -6
- package/templates/agent/gitignore +1 -0
- package/templates/agent/package.json +8 -8
- package/templates/agent/src/app.ts +28 -15
- package/templates/agent/src/main.ts +2 -2
- package/templates/agent/src/routes.ts +5 -5
- package/templates/agent/test/agent.test.ts +1 -1
- package/templates/agent/test/app.test.ts +1 -1
- package/templates/agent/test/builder.test.ts +1 -1
- package/templates/agent/test/model.test.ts +1 -1
- package/templates/agent/tsconfig.json +48 -50
- package/templates/blank/AGENTS.md +1 -1
- package/templates/blank/README.md +2 -2
- package/templates/blank/gitignore +1 -0
- package/templates/blank/package.json +8 -8
- package/templates/blank/src/about.tsx +2 -2
- package/templates/blank/src/app.ts +4 -4
- package/templates/blank/src/main.ts +1 -1
- package/templates/blank/src/routes.ts +1 -1
- package/templates/blank/test/app.test.ts +1 -1
- package/templates/blank/tsconfig.json +1 -1
- package/templates/todo/README.md +1 -1
- package/templates/todo/gitignore +1 -0
- package/templates/todo/package.json +8 -8
- package/templates/todo/src/app.ts +5 -5
- package/templates/todo/src/main.ts +1 -1
- package/templates/todo/src/todo.ts +4 -4
- package/templates/todo/test/todo.test.ts +1 -1
- package/templates/todo/tsconfig.json +1 -1
- package/templates/todo/upstream.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -102,7 +102,7 @@ export declare function libraryRoot(): string;
|
|
|
102
102
|
export declare function readLibrary(root?: string): Map<string, LibraryPlugin>;
|
|
103
103
|
/** The plugins to copy for `names`: each one after everything it requires, no duplicates. */
|
|
104
104
|
export declare function closure(names: readonly string[], library: Map<string, LibraryPlugin>): LibraryPlugin[];
|
|
105
|
-
/** Copy one library plugin's source into `<app>/
|
|
105
|
+
/** Copy one library plugin's source into `<app>/plugins/<name>/`. Throws when that folder already exists. */
|
|
106
106
|
export declare function copyPlugin(plugin: LibraryPlugin, appDir: string, root?: string): string;
|
|
107
107
|
//#endregion
|
|
108
108
|
//#region src/project.d.ts
|
package/dist/index.js
CHANGED
|
@@ -84,10 +84,33 @@ function closure(names, library) {
|
|
|
84
84
|
for (const name of names) visit(name, []);
|
|
85
85
|
return ordered;
|
|
86
86
|
}
|
|
87
|
-
/**
|
|
87
|
+
/** The one folder every plugin of an app lives in, library copies and the ones an agent writes. */
|
|
88
|
+
const PLUGINS_DIR = "plugins";
|
|
89
|
+
/** Where apps made before 0.7.8 kept library copies; still understood for one release. */
|
|
90
|
+
const LEGACY_PLUGINS_DIR = join("src", "plugins");
|
|
91
|
+
/** The list of library copies in the plugins folder; the extensions plugin installs every other folder there. */
|
|
92
|
+
const LIBRARY_LIST = "library.json";
|
|
93
|
+
/** The plugins folder of an app: `plugins/`, or `src/plugins/` for an app that still has the old layout (and no `plugins/`). */
|
|
94
|
+
function pluginsFolder(appDir) {
|
|
95
|
+
const legacy = existsSync(join(appDir, LEGACY_PLUGINS_DIR)) && !existsSync(join(appDir, "plugins"));
|
|
96
|
+
return {
|
|
97
|
+
dir: join(appDir, legacy ? LEGACY_PLUGINS_DIR : PLUGINS_DIR),
|
|
98
|
+
legacy
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** The warning for an app with the old layout. */
|
|
102
|
+
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.";
|
|
103
|
+
function recordLibraryCopy(folder, name) {
|
|
104
|
+
const file = join(folder, LIBRARY_LIST);
|
|
105
|
+
const names = existsSync(file) ? JSON.parse(readFileSync(file, "utf8")).plugins : [];
|
|
106
|
+
if (!names.includes(name)) names.push(name);
|
|
107
|
+
writeFileSync(file, `${JSON.stringify({ plugins: names.sort() }, null, 2)}\n`);
|
|
108
|
+
}
|
|
109
|
+
/** Copy one library plugin's source into `<app>/plugins/<name>/`. Throws when that folder already exists. */
|
|
88
110
|
function copyPlugin(plugin, appDir, root = libraryRoot()) {
|
|
89
|
-
const
|
|
90
|
-
|
|
111
|
+
const folder = pluginsFolder(appDir);
|
|
112
|
+
const target = join(folder.dir, plugin.name);
|
|
113
|
+
if (existsSync(target)) throw new LibraryError(`${relativeName(appDir, target)} already exists; remove it first to copy a fresh one`);
|
|
91
114
|
const source = join(root, plugin.name);
|
|
92
115
|
cpSync(source, target, {
|
|
93
116
|
recursive: true,
|
|
@@ -96,8 +119,10 @@ function copyPlugin(plugin, appDir, root = libraryRoot()) {
|
|
|
96
119
|
return !(dirname(path) === source && LIBRARY_ONLY.has(inside)) && !isOmitted(inside, plugin.omit);
|
|
97
120
|
}
|
|
98
121
|
});
|
|
122
|
+
if (!folder.legacy) recordLibraryCopy(folder.dir, plugin.name);
|
|
99
123
|
return target;
|
|
100
124
|
}
|
|
125
|
+
const relativeName = (appDir, path) => path.slice(appDir.length + 1).split(sep).join("/");
|
|
101
126
|
//#endregion
|
|
102
127
|
//#region src/project.ts
|
|
103
128
|
/** Thrown when the target directory exists and is not empty. Nothing has been written. */
|
|
@@ -159,7 +184,7 @@ function readTemplateDesktopPath(templateDir) {
|
|
|
159
184
|
if (!existsSync(file)) return void 0;
|
|
160
185
|
return JSON.parse(readFileSync(file, "utf8")).desktopPath;
|
|
161
186
|
}
|
|
162
|
-
/** Record where the desktop window opens in package.json (`acrylblends.desktopPath`), read by `
|
|
187
|
+
/** Record where the desktop window opens in package.json (`acrylblends.desktopPath`), read by `plugins/desktop/main.ts`. */
|
|
163
188
|
function writeDesktopPath(appDir, path) {
|
|
164
189
|
const file = join(appDir, "package.json");
|
|
165
190
|
const manifest = JSON.parse(readFileSync(file, "utf8"));
|
|
@@ -241,9 +266,17 @@ function appendGitignore(appDir, lines) {
|
|
|
241
266
|
function writeAgentFiles(sharedDir, templateDir, target, name) {
|
|
242
267
|
const core = readFileSync(join(sharedDir, "AGENTS.core.md"), "utf8");
|
|
243
268
|
const own = existsSync(join(templateDir, "AGENTS.md")) ? readFileSync(join(templateDir, "AGENTS.md"), "utf8") : "";
|
|
244
|
-
|
|
245
|
-
writeFileSync(join(target, "
|
|
246
|
-
|
|
269
|
+
const text = `${core}${readFileSync(join(sharedDir, "DEFAULTS.md"), "utf8")}${own}`.replaceAll("__APP_NAME__", name);
|
|
270
|
+
writeFileSync(join(target, "AGENTS.md"), text);
|
|
271
|
+
writeFileSync(join(target, "CLAUDE.md"), text);
|
|
272
|
+
const skill = readFileSync(join(sharedDir, "SKILL.md"), "utf8").replaceAll("__APP_NAME__", name);
|
|
273
|
+
const written = ["AGENTS.md", "CLAUDE.md"];
|
|
274
|
+
for (const folder of [".claude/skills/app-guide", ".agents/skills/app-guide"]) {
|
|
275
|
+
mkdirSync(join(target, folder), { recursive: true });
|
|
276
|
+
writeFileSync(join(target, folder, "SKILL.md"), skill);
|
|
277
|
+
written.push(`${folder}/SKILL.md`);
|
|
278
|
+
}
|
|
279
|
+
return written;
|
|
247
280
|
}
|
|
248
281
|
/**
|
|
249
282
|
* Copy the framework's docs (a generated copy of ACRYL's `docs/framework`, already checked against its digest) into `<app>/docs/framework/`,
|
|
@@ -328,7 +361,7 @@ var UsageError = class extends Error {
|
|
|
328
361
|
function parseInitOptions(argv) {
|
|
329
362
|
let parsed;
|
|
330
363
|
try {
|
|
331
|
-
parsed = parse$
|
|
364
|
+
parsed = parse$2(argv);
|
|
332
365
|
} catch (error) {
|
|
333
366
|
throw new UsageError(error.message);
|
|
334
367
|
}
|
|
@@ -347,7 +380,7 @@ function parseInitOptions(argv) {
|
|
|
347
380
|
yes: values.yes === true
|
|
348
381
|
};
|
|
349
382
|
}
|
|
350
|
-
const parse$
|
|
383
|
+
const parse$2 = (argv) => parseArgs({
|
|
351
384
|
args: [...argv],
|
|
352
385
|
allowPositionals: true,
|
|
353
386
|
strict: true,
|
|
@@ -418,7 +451,7 @@ async function init(options, io) {
|
|
|
418
451
|
function parseAddOptions(argv) {
|
|
419
452
|
let parsed;
|
|
420
453
|
try {
|
|
421
|
-
parsed = parse(argv);
|
|
454
|
+
parsed = parse$1(argv);
|
|
422
455
|
} catch (error) {
|
|
423
456
|
throw new UsageError(error.message);
|
|
424
457
|
}
|
|
@@ -428,13 +461,13 @@ function parseAddOptions(argv) {
|
|
|
428
461
|
directory: parsed.values.dir
|
|
429
462
|
};
|
|
430
463
|
}
|
|
431
|
-
const parse = (argv) => parseArgs({
|
|
464
|
+
const parse$1 = (argv) => parseArgs({
|
|
432
465
|
args: [...argv],
|
|
433
466
|
allowPositionals: true,
|
|
434
467
|
strict: true,
|
|
435
468
|
options: { dir: { type: "string" } }
|
|
436
469
|
});
|
|
437
|
-
const present = (appDir, name) => existsSync(join(appDir,
|
|
470
|
+
const present = (appDir, name) => existsSync(join(pluginsFolder(appDir).dir, name));
|
|
438
471
|
/**
|
|
439
472
|
* Copy library plugins into an existing app, with the plugins they require, and add their npm packages to its package.json.
|
|
440
473
|
* Refuses a plugin the app already has. Returns the exit code.
|
|
@@ -445,21 +478,26 @@ async function add(options, io) {
|
|
|
445
478
|
io.stderr.write(`error: ${appDir} is not an app (no package.json and src/); run "acrylblends init" first\n`);
|
|
446
479
|
return 1;
|
|
447
480
|
}
|
|
481
|
+
if (pluginsFolder(appDir).legacy) io.stderr.write(`${LEGACY_WARNING}\n`);
|
|
448
482
|
const external = options.names.filter((name) => /[\\/]/.test(name) || name.startsWith("."));
|
|
449
483
|
for (const folder of external) {
|
|
450
484
|
const code = addExternal(resolve(io.cwd, folder), appDir, io);
|
|
451
485
|
if (code !== 0) return code;
|
|
452
486
|
}
|
|
453
|
-
const plain = !existsSync(join(appDir, "
|
|
487
|
+
const plain = !existsSync(join(pluginsFolder(appDir).dir, "instance"));
|
|
454
488
|
if (plain && options.names.includes("db")) {
|
|
455
489
|
io.stderr.write("error: \"db\" is the Cordis plugin for a Blend app. A plain Acryl app opens its database with openDb from \"acrylblends/db/sqlite\" (see docs/framework/guides/data.md)\n");
|
|
456
490
|
return 1;
|
|
457
491
|
}
|
|
458
|
-
|
|
492
|
+
if (plain && options.names.includes("kv")) {
|
|
493
|
+
io.stderr.write("error: \"kv\" is the Cordis plugin for a Blend app. A plain Acryl app opens its store with openKv from \"acrylblends/kv/json\" (see docs/framework/guides/data.md#a-tiny-app-kv)\n");
|
|
494
|
+
return 1;
|
|
495
|
+
}
|
|
496
|
+
const names = options.names.filter((name) => !external.includes(name)).map((name) => name === "db" ? "acryl-db-sqlite" : name).map((name) => name === "kv" ? "acryl-kv-json" : name).map((name) => name === "desktop" && plain ? "desktop-web" : name);
|
|
459
497
|
if (names.length === 0) return 0;
|
|
460
498
|
const already = names.filter((name) => present(appDir, name));
|
|
461
499
|
if (already.length > 0) {
|
|
462
|
-
io.stderr.write(`error: the app already has ${already.map((name) =>
|
|
500
|
+
io.stderr.write(`error: the app already has ${already.map((name) => `${relative(appDir, join(pluginsFolder(appDir).dir, name)).split(sep).join("/")}`).join(", ")}\n`);
|
|
463
501
|
return 1;
|
|
464
502
|
}
|
|
465
503
|
const toCopy = closure(names, readLibrary()).filter((plugin) => !present(appDir, plugin.name));
|
|
@@ -475,13 +513,13 @@ async function add(options, io) {
|
|
|
475
513
|
}
|
|
476
514
|
io.stdout.write(`added ${toCopy.map((plugin) => plugin.name).join(", ")} to ${appDir}\n`);
|
|
477
515
|
if (addedDependencies.length > 0) io.stdout.write(`new dependencies: ${addedDependencies.join(", ")} (run npm install)\n`);
|
|
478
|
-
for (const plugin of toCopy.filter((plugin) => names.includes(plugin.name))) io.stdout.write(`\n${plugin.name}: ${plugin.mount ?? `import it from '
|
|
516
|
+
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`);
|
|
479
517
|
return 0;
|
|
480
518
|
}
|
|
481
519
|
const PLUGIN_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
482
520
|
/**
|
|
483
521
|
* Bring an external plugin folder (a package made with the builder's package_plugin, or any folder with an index.ts that exports a
|
|
484
|
-
* Cordis plugin) into the app's `
|
|
522
|
+
* Cordis plugin) into the app's `plugins/`, where the extensions plugin installs it like any other: checked, staged, started,
|
|
485
523
|
* verified. Its runtime dependencies are not installed here; they are reported so the user adds them deliberately.
|
|
486
524
|
*/
|
|
487
525
|
function addExternal(folder, appDir, io) {
|
|
@@ -504,16 +542,16 @@ function addExternal(folder, appDir, io) {
|
|
|
504
542
|
io.stderr.write(`error: "${name}" is not a plugin name (lowercase letters, digits and dashes)\n`);
|
|
505
543
|
return 1;
|
|
506
544
|
}
|
|
507
|
-
const target = join(appDir, "extensions", name);
|
|
545
|
+
const target = join(pluginsFolder(appDir).legacy ? join(appDir, "extensions") : join(appDir, PLUGINS_DIR), name);
|
|
508
546
|
if (existsSync(target)) {
|
|
509
|
-
io.stderr.write(`error: the app already has
|
|
547
|
+
io.stderr.write(`error: the app already has ${relative(appDir, target).split(sep).join("/")}\n`);
|
|
510
548
|
return 1;
|
|
511
549
|
}
|
|
512
550
|
cpSync(folder, target, {
|
|
513
551
|
recursive: true,
|
|
514
552
|
filter: (path) => !/[\\/](node_modules|\.git)([\\/]|$)/.test(path.slice(folder.length))
|
|
515
553
|
});
|
|
516
|
-
io.stdout.write(`added external plugin ${name} to ${
|
|
554
|
+
io.stdout.write(`added external plugin ${name} to ${dirname(target)}; it installs at the next start\n`);
|
|
517
555
|
const dependencies = Object.keys(manifest.dependencies ?? {});
|
|
518
556
|
if (dependencies.length > 0) io.stdout.write(`it uses ${dependencies.join(", ")}: add them to the app with npm install first\n`);
|
|
519
557
|
return 0;
|
|
@@ -592,9 +630,14 @@ const DRIVERS = [
|
|
|
592
630
|
const OPENERS = ["acrylblends/db/sqlite", "acryl-db-sqlite"];
|
|
593
631
|
/** The desktop shell is where Electron lives (`acrylblends add desktop` copies it). */
|
|
594
632
|
const ELECTRON = "electron";
|
|
595
|
-
const ELECTRON_HOMES = [
|
|
633
|
+
const ELECTRON_HOMES = [
|
|
634
|
+
"plugins/desktop/",
|
|
635
|
+
"plugins/desktop-web/",
|
|
636
|
+
"src/plugins/desktop/",
|
|
637
|
+
"src/plugins/desktop-web/"
|
|
638
|
+
];
|
|
596
639
|
/** The provider plugins themselves may import what they wrap. */
|
|
597
|
-
const PROVIDER_HOMES = ["src/plugins/acryl-db-sqlite/"];
|
|
640
|
+
const PROVIDER_HOMES = ["plugins/acryl-db-sqlite/", "src/plugins/acryl-db-sqlite/"];
|
|
598
641
|
const SOURCE = /\.(?:[cm]?[jt]s|[jt]sx)$/u;
|
|
599
642
|
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
600
643
|
"node_modules",
|
|
@@ -607,6 +650,7 @@ const ROOTS = [
|
|
|
607
650
|
"src",
|
|
608
651
|
"test",
|
|
609
652
|
"tests",
|
|
653
|
+
"plugins",
|
|
610
654
|
"extensions"
|
|
611
655
|
];
|
|
612
656
|
const matches = (specifier, name) => specifier === name || specifier.startsWith(`${name}/`);
|
|
@@ -705,7 +749,7 @@ function findViolations(appDir) {
|
|
|
705
749
|
}
|
|
706
750
|
const GUIDE = "docs/framework/guides/data.md#why-the-database-is-opened-in-one-place";
|
|
707
751
|
function advice(violation, openers) {
|
|
708
|
-
if (violation.kind === "electron") return `"${violation.specifier}" is the desktop shell's: only
|
|
752
|
+
if (violation.kind === "electron") return `"${violation.specifier}" is the desktop shell's: only plugins/desktop (added with "acrylblends add desktop") imports electron`;
|
|
709
753
|
const where = openers.length > 0 ? openers.join(", ") : "a file named in package.json \"acryl\": { \"opener\": \"...\" }";
|
|
710
754
|
return violation.kind === "driver" ? `"${violation.specifier}" is a database driver; take a Db (import type { Db } from "acrylblends/db") as an argument and let ${where} open the database` : `"${violation.specifier}" opens a database, and only ${where} may; take a Db (import type { Db } from "acrylblends/db") as an argument`;
|
|
711
755
|
}
|
|
@@ -791,6 +835,81 @@ function processIo() {
|
|
|
791
835
|
};
|
|
792
836
|
}
|
|
793
837
|
//#endregion
|
|
838
|
+
//#region src/kv-import.ts
|
|
839
|
+
/** Parse `kv import <kv.json> <database.sqlite> [--overwrite] [--table name] [--migrations dir]`. */
|
|
840
|
+
function parseKvOptions(argv) {
|
|
841
|
+
let parsed;
|
|
842
|
+
try {
|
|
843
|
+
parsed = parse(argv);
|
|
844
|
+
} catch (error) {
|
|
845
|
+
throw new UsageError(error.message);
|
|
846
|
+
}
|
|
847
|
+
const [action, from, to, ...rest] = parsed.positionals;
|
|
848
|
+
if (action !== "import") throw new UsageError("kv takes one subcommand: kv import <kv.json> <database.sqlite>");
|
|
849
|
+
if (from === void 0 || to === void 0 || rest.length > 0) throw new UsageError("kv import takes two arguments: the JSON file and the SQLite file");
|
|
850
|
+
return {
|
|
851
|
+
from,
|
|
852
|
+
to,
|
|
853
|
+
overwrite: parsed.values.overwrite ?? false,
|
|
854
|
+
table: parsed.values.table,
|
|
855
|
+
migrations: parsed.values.migrations
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
const parse = (argv) => parseArgs({
|
|
859
|
+
args: [...argv],
|
|
860
|
+
allowPositionals: true,
|
|
861
|
+
strict: true,
|
|
862
|
+
options: {
|
|
863
|
+
overwrite: { type: "boolean" },
|
|
864
|
+
table: { type: "string" },
|
|
865
|
+
migrations: { type: "string" }
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
/** The framework as the app has it installed (so the code that reads the file is the code the app runs), else this package's own. */
|
|
869
|
+
async function framework(io, subpath, own) {
|
|
870
|
+
const here = createRequire(resolve(io.cwd, "package.json"));
|
|
871
|
+
try {
|
|
872
|
+
return await import(here.resolve(`acrylblends/${subpath}`));
|
|
873
|
+
} catch {
|
|
874
|
+
return await import(new URL(`../lib/${own}`, import.meta.url).href);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Import a JSON-file kv store into the `kv` table of an app's SQLite database: the move from `acryl-kv-json` to SQL. The file is not changed.
|
|
879
|
+
* The database is opened with the app's `migrations/` folder when it has one (a database whose schema is newer than the files given is refused),
|
|
880
|
+
* and the table is created when missing. Refuses to overwrite a key the table already has unless `--overwrite`.
|
|
881
|
+
*/
|
|
882
|
+
async function kvImport(options, io) {
|
|
883
|
+
const from = resolve(io.cwd, options.from);
|
|
884
|
+
const to = resolve(io.cwd, options.to);
|
|
885
|
+
if (!existsSync(from)) {
|
|
886
|
+
io.stderr.write(`error: ${from} does not exist\n`);
|
|
887
|
+
return 1;
|
|
888
|
+
}
|
|
889
|
+
const kvCore = await framework(io, "kv", "kv.js");
|
|
890
|
+
const json = await framework(io, "kv/json", "kv/json.js");
|
|
891
|
+
const onDb = await framework(io, "kv/db", "kv/db.js");
|
|
892
|
+
const sqlite = await framework(io, "db/sqlite", "db/sqlite.js");
|
|
893
|
+
const migrations = options.migrations !== void 0 ? resolve(io.cwd, options.migrations) : existsSync(resolve(io.cwd, "migrations")) ? resolve(io.cwd, "migrations") : void 0;
|
|
894
|
+
const source = await json.openKv({ path: from });
|
|
895
|
+
try {
|
|
896
|
+
const database = await sqlite.openDb({
|
|
897
|
+
path: to,
|
|
898
|
+
...migrations === void 0 ? {} : { migrations }
|
|
899
|
+
});
|
|
900
|
+
try {
|
|
901
|
+
const target = onDb.kvOnDb(database.db, options.table === void 0 ? {} : { table: options.table });
|
|
902
|
+
const result = await kvCore.copyKv(source.kv, target, { overwrite: options.overwrite });
|
|
903
|
+
io.stdout.write(`imported ${result.copied} ${result.copied === 1 ? "key" : "keys"} from ${options.from} into ${options.to}${options.table === void 0 ? "" : ` (table ${options.table})`}${result.overwritten > 0 ? `, ${result.overwritten} overwritten` : ""}\n`);
|
|
904
|
+
return 0;
|
|
905
|
+
} finally {
|
|
906
|
+
await database.close();
|
|
907
|
+
}
|
|
908
|
+
} finally {
|
|
909
|
+
await source.close();
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
//#endregion
|
|
794
913
|
//#region src/cli.ts
|
|
795
914
|
/** The version of this package. */
|
|
796
915
|
const VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
@@ -799,9 +918,10 @@ const HELP = `acrylblends ${VERSION} - scaffold Cordis apps
|
|
|
799
918
|
Usage:
|
|
800
919
|
acrylblends init [dir] [options] Create a Cordis app in an empty or new directory
|
|
801
920
|
acrylblends add <plugin...> [--dir d] Copy library plugins (and what they require) into an app;
|
|
802
|
-
a folder path brings an external plugin into its
|
|
921
|
+
a folder path brings an external plugin into its plugins/
|
|
803
922
|
acrylblends plugins List the plugin library
|
|
804
923
|
acrylblends build-client [root] [out] Compile the client modules once for production (src/client to dist/client)
|
|
924
|
+
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]
|
|
805
925
|
acrylblends check-deps [dir] Fail when app code imports a database driver or opener outside the file named in
|
|
806
926
|
package.json "acryl": { "opener": "src/db.ts" } (a port is imported, a provider is not)
|
|
807
927
|
|
|
@@ -833,6 +953,7 @@ async function runCli(argv, io = processIo()) {
|
|
|
833
953
|
if (command === "init") return await init(parseInitOptions(args), io);
|
|
834
954
|
if (command === "add") return await add(parseAddOptions(args), io);
|
|
835
955
|
if (command === "build-client") return await buildClient(parseBuildClientOptions(args), io);
|
|
956
|
+
if (command === "kv") return await kvImport(parseKv(args), io);
|
|
836
957
|
if (command === "check-deps") return checkDeps(parseCheckDeps(args), io);
|
|
837
958
|
if (command === "plugins") return listPlugins(io);
|
|
838
959
|
throw new UsageError(`unknown command "${command}"`);
|
|
@@ -852,6 +973,9 @@ function parseCheckDeps(args) {
|
|
|
852
973
|
throw new UsageError(error.message);
|
|
853
974
|
}
|
|
854
975
|
}
|
|
976
|
+
function parseKv(args) {
|
|
977
|
+
return parseKvOptions(args);
|
|
978
|
+
}
|
|
855
979
|
//#endregion
|
|
856
980
|
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 };
|
|
857
981
|
|