acrylblends 1.3.2 → 1.5.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 +5 -2
- package/dist/index.d.ts +8 -5
- package/dist/index.js +97 -38
- package/dist/index.js.map +1 -1
- package/legacy/README.md +1 -1
- package/legacy/agent/46c6eadf9230/src/app.ts +293 -0
- package/legacy/agent/46c6eadf9230/src/routes.ts +163 -0
- package/legacy/layouts.json +26 -0
- package/lib/{client-modules-Bkdr83jO.js → client-modules-C5qiVroz.js} +1 -1
- package/lib/cordis/door.d.ts +27 -6
- package/lib/cordis/door.js +41 -10
- package/lib/node-server/register.js +6 -1
- package/lib/node-server.js +1 -1
- package/lib/{transpile-QUK9b3eG.js → transpile-CRRaixyS.js} +19 -4
- package/package.json +1 -1
- package/plugins/acryl-door/src/cli/acryl.ts +13 -4
- package/plugins/acryl-door/src/host/door.ts +41 -3
- package/plugins/acryl-door/src/host/verbs.ts +3 -3
- package/plugins/acryl-node-server/src/register.ts +10 -1
- package/plugins/acryl-node-server/src/transpile.ts +25 -6
- package/plugins/acryl-page/src/favicon.ts +19 -0
- package/plugins/acryl-page/src/index.ts +18 -13
- package/plugins/agent-panel/client/main.ts +9 -0
- package/plugins/agent-panel/headless.ts +2 -2
- package/plugins/desktop/app-page.ts +151 -0
- package/plugins/desktop/assets/acrylblends-logo.png +0 -0
- package/plugins/desktop/brand.ts +164 -0
- package/plugins/desktop/frame.ts +88 -7
- package/plugins/desktop/git.ts +44 -0
- package/plugins/desktop/host-supervisor.ts +63 -0
- package/plugins/desktop/launch.ts +17 -1
- package/plugins/desktop/main.ts +268 -17
- package/plugins/desktop/preload.cjs +8 -0
- package/plugins/desktop/product.ts +117 -0
- package/plugins/desktop/window.ts +4 -0
- package/plugins/desktop-web/main.ts +3 -2
- package/plugins/desktop-web/plugin.json +1 -1
- package/plugins/extensions/index.ts +64 -13
- package/plugins/instance/archive.ts +395 -0
- package/plugins/instance/bundle-overlay.ts +10 -0
- package/plugins/instance/bundle.ts +201 -0
- package/plugins/instance/home.ts +386 -0
- package/plugins/instance/index.ts +132 -24
- package/plugins/instance/storage.ts +413 -0
- package/plugins/plugin-share/index.ts +99 -1
- package/plugins/plugin-share/publish.ts +17 -3
- package/plugins/plugin-share/pull-check.ts +2 -0
- package/plugins/plugin-share/pull-list.ts +71 -0
- package/plugins/showcase/cards.ts +162 -0
- package/plugins/showcase/index.ts +116 -0
- package/plugins/showcase/plugin.json +11 -0
- package/shared/skills/acryl-remove-plugin/SKILL.md +1 -1
- package/templates/agent/AGENTS.md +4 -0
- package/templates/agent/README.md +1 -1
- package/templates/agent/blend.yaml +3 -0
- package/templates/agent/gitignore +2 -0
- package/templates/agent/src/app.ts +2 -0
- package/templates/agent/src/routes.ts +23 -5
- package/templates/agent/template.json +1 -0
- package/templates/agent/test/app.test.ts +26 -0
- package/templates/blank/README.md +1 -1
- package/templates/blank/gitignore +2 -0
- package/templates/blank/src/routes.ts +9 -5
- package/templates/chat/gitignore +2 -0
- package/templates/chat/src/chat-app.ts +7 -3
- package/templates/chat/src/llm.ts +7 -1
- package/templates/chat/test/chat.test.ts +16 -1
- package/templates/nodejs/gitignore +2 -0
- package/templates/notes/gitignore +2 -0
- package/templates/todo/README.md +1 -1
- package/templates/todo/gitignore +2 -0
package/README.md
CHANGED
|
@@ -397,10 +397,13 @@ plugins installed while the app runs, an agent that builds plugins, durable task
|
|
|
397
397
|
secrets):
|
|
398
398
|
|
|
399
399
|
```sh
|
|
400
|
-
npx acrylblends init my-app
|
|
401
|
-
cd my-app && npm install && npm run dev
|
|
400
|
+
npx acrylblends@latest init my-app -y
|
|
401
|
+
cd my-app && npm install && npm run dev # the app in the ACRYL frame: chat, agents, branches, versions, Settings
|
|
402
|
+
npm run desktop # the same in its own window
|
|
402
403
|
```
|
|
403
404
|
|
|
405
|
+
`--bare` makes the plain app without the frame.
|
|
406
|
+
|
|
404
407
|
See the [ACRYLBLENDS documentation](https://github.com/acrylblends/acrylblends/blob/main/docs/README.md) and
|
|
405
408
|
[Acryl in a Cordis app](https://github.com/acrylblends/acrylblends/blob/main/docs/framework/cordis/acryl-in-a-cordis-app.md).
|
|
406
409
|
|
package/dist/index.d.ts
CHANGED
|
@@ -53,7 +53,10 @@ interface FrameUrlRecord {
|
|
|
53
53
|
readonly pid: number;
|
|
54
54
|
readonly writtenAt: string;
|
|
55
55
|
}
|
|
56
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* The app home the frame uses, as the app's own family chooses it (`plugins/instance`, which its launcher asks too): the CLI never keeps a second copy
|
|
58
|
+
* of that rule. A folder without the family is not a frame app.
|
|
59
|
+
*/
|
|
57
60
|
export declare function frameHome(appRoot: string, env?: Record<string, string | undefined>): string;
|
|
58
61
|
export declare function frameUrlPath(home: string): string;
|
|
59
62
|
/** The token a frame address carries, if it carries one. */
|
|
@@ -116,13 +119,13 @@ interface InitOptions {
|
|
|
116
119
|
name: string | undefined;
|
|
117
120
|
template: Template | undefined;
|
|
118
121
|
install: boolean | undefined;
|
|
119
|
-
/**
|
|
122
|
+
/** In the ACRYL frame with the desktop shell (`add desktop`): the default; `--bare` makes a plain app. */
|
|
120
123
|
desktop: boolean;
|
|
121
124
|
/** Print no "Next:" block: `create-acrylblends` runs `init` and prints its own, for the package manager it knows. */
|
|
122
125
|
quietNext: boolean;
|
|
123
126
|
yes: boolean;
|
|
124
127
|
}
|
|
125
|
-
/** Parse `init [dir] [--template t] [--agent|--no-agent] [--name n] [--install] [-y]`. `--agent` means `--template agent`, `--no-agent` means `--template blank`. */
|
|
128
|
+
/** Parse `init [dir] [--template t] [--agent|--no-agent] [--name n] [--install] [--bare] [-y]`. `--agent` means `--template agent`, `--no-agent` means `--template blank`. */
|
|
126
129
|
export declare function parseInitOptions(argv: readonly string[]): InitOptions;
|
|
127
130
|
/** Scaffold a Cordis app. Returns the exit code: 0 done, 1 refused or failed. Never overwrites anything. */
|
|
128
131
|
export declare function init(options: InitOptions, io: Io): Promise<number>;
|
|
@@ -163,7 +166,7 @@ export declare function libraryRoot(): string;
|
|
|
163
166
|
/** Every plugin in the library, by name. */
|
|
164
167
|
export declare function readLibrary(root?: string): Map<string, LibraryPlugin>;
|
|
165
168
|
/** The plugins to copy for `names`: each one after everything it requires, no duplicates. */
|
|
166
|
-
export declare function closure(names: readonly string[], library:
|
|
169
|
+
export declare function closure(names: readonly string[], library: ReadonlyMap<string, LibraryPlugin>): LibraryPlugin[];
|
|
167
170
|
/** Copy one library plugin's source into `<app>/plugins/<name>/`. Throws when that folder already exists. */
|
|
168
171
|
export declare function copyPlugin(plugin: LibraryPlugin, appDir: string, root?: string): string;
|
|
169
172
|
//#endregion
|
|
@@ -221,7 +224,7 @@ export declare function parseUpgradeOptions(argv: readonly string[]): UpgradeOpt
|
|
|
221
224
|
/**
|
|
222
225
|
* `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
|
|
223
226
|
* 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
|
|
224
|
-
* under `.
|
|
227
|
+
* under `.acryl/upgrade-backup/` first. Returns the exit code: 0 done or nothing to do, 1 refused or failed.
|
|
225
228
|
*/
|
|
226
229
|
export declare function upgrade(options: UpgradeOptions, io: Io): Promise<number>;
|
|
227
230
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ var packages = {
|
|
|
18
18
|
},
|
|
19
19
|
"dsh-client-ui-brand-acryl": {
|
|
20
20
|
"name": "brand-client",
|
|
21
|
-
"version": "0.1.
|
|
21
|
+
"version": "0.1.1"
|
|
22
22
|
},
|
|
23
23
|
"acryl-app-shell": {
|
|
24
24
|
"name": "app-shell",
|
|
@@ -70,11 +70,11 @@ var packages = {
|
|
|
70
70
|
},
|
|
71
71
|
"acryl-versions": {
|
|
72
72
|
"name": "versions",
|
|
73
|
-
"version": "0.1.
|
|
73
|
+
"version": "0.1.1"
|
|
74
74
|
},
|
|
75
75
|
"acryl-blends-frame": {
|
|
76
76
|
"name": "blends-frame",
|
|
77
|
-
"version": "0.1.
|
|
77
|
+
"version": "0.1.2"
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
80
|
//#endregion
|
|
@@ -88,19 +88,17 @@ var packages = {
|
|
|
88
88
|
*/
|
|
89
89
|
/** Where an app keeps its data, for the plugins that inject `appInstance` (the framework's settings service does): the slice 0 row, kept in the app. */
|
|
90
90
|
const APP_INSTANCE_ROW = `// Generated by acrylblends: the app's own folder family, for the plugins that inject appInstance.
|
|
91
|
-
import {
|
|
91
|
+
import { fileURLToPath } from 'node:url'
|
|
92
|
+
import { selectInstance } from '../plugins/instance/index.ts'
|
|
92
93
|
|
|
93
94
|
export const name = 'app-instance'
|
|
94
95
|
|
|
95
96
|
export function apply(ctx) {
|
|
96
97
|
ctx.effect(() => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// The engine home ACRYL's plugins read (support serves \`dshHome/logs\`); in this host the engine and the app share one home.
|
|
102
|
-
dshHome: home,
|
|
103
|
-
})
|
|
98
|
+
// Chosen by the app's own family, as the host chose it: this folder, or the source a desktop product names. The engine home ACRYL's plugins read
|
|
99
|
+
// (support serves \`dshHome/logs\`) is the app's home.
|
|
100
|
+
const chosen = selectInstance({ projectRoot: fileURLToPath(new URL('..', import.meta.url)), env: process.env })
|
|
101
|
+
const instance = ctx.reflect.provide('appInstance', chosen)
|
|
104
102
|
return () => {
|
|
105
103
|
void instance()
|
|
106
104
|
}
|
|
@@ -525,9 +523,22 @@ function hostScriptOf(profile, frameModule) {
|
|
|
525
523
|
"import { DEFAULT_SCHEMA, dump as dumpYaml, Type, load as parseYaml } from 'js-yaml'",
|
|
526
524
|
"import { Acryl } from 'acrylblends'",
|
|
527
525
|
"",
|
|
528
|
-
"
|
|
526
|
+
"// The framework's files (this launcher, the composition it reads) sit next to it; the app is chosen by the app's own family: this folder, or the",
|
|
527
|
+
"// source a desktop product names when it runs the framework from its bundle. Its home and its port come with it, decided once.",
|
|
528
|
+
"const frameworkRoot = join(dirname(fileURLToPath(import.meta.url)), '..')",
|
|
529
|
+
"const { selectInstance } = await import(new URL('../plugins/instance/index.ts', import.meta.url).href)",
|
|
530
|
+
"const chosen = selectInstance({ projectRoot: frameworkRoot, env: process.env })",
|
|
531
|
+
"const appRoot = chosen.root",
|
|
532
|
+
"const home = chosen.home",
|
|
529
533
|
`const profileName = '${profile}'`,
|
|
530
|
-
"
|
|
534
|
+
"// The page and its tab are named for the app: the package's name, as the frame's header says it, else the folder's.",
|
|
535
|
+
"const appTitle = (() => {",
|
|
536
|
+
" try {",
|
|
537
|
+
" const name = JSON.parse(readFileSync(join(appRoot, 'package.json'), 'utf8')).name",
|
|
538
|
+
" if (typeof name === 'string' && name.trim() !== '') return name.trim()",
|
|
539
|
+
" } catch {}",
|
|
540
|
+
" return chosen.name",
|
|
541
|
+
"})()",
|
|
531
542
|
"// DSH's rows keep their files in this app's own home: set before any driver row is imported.",
|
|
532
543
|
"process.env.DSH_HOME = home",
|
|
533
544
|
"// Development only: the app's code can be loaded again in this host (a version gone back to, a file written by the chat, an agent or an editor). Every file",
|
|
@@ -543,8 +554,7 @@ function hostScriptOf(profile, frameModule) {
|
|
|
543
554
|
"const { boot } = await import('@deepseek-ai/dsh-app-boot')",
|
|
544
555
|
"// The frame listens where the app's own server would: the folder's stable port (or the home's, when ACRYLBLENDS_HOME pins one). A restart then",
|
|
545
556
|
"// keeps the page's origin, its auth cookie and its tabs; ACRYLBLENDS_PORT still decides, and the gates pass 0 for any free port.",
|
|
546
|
-
"const
|
|
547
|
-
"const port = String(selectInstance({ projectRoot: appRoot, env: process.env }).port.start)",
|
|
557
|
+
"const port = String(chosen.port.start)",
|
|
548
558
|
"const check = process.argv.includes('--check')",
|
|
549
559
|
"const require = createRequire(import.meta.url)",
|
|
550
560
|
"/** The `!!js` tag the loader evaluates: js-yaml needs the same type the Cordis include plugin declares. */",
|
|
@@ -593,7 +603,8 @@ function hostScriptOf(profile, frameModule) {
|
|
|
593
603
|
" return kept",
|
|
594
604
|
"}",
|
|
595
605
|
"",
|
|
596
|
-
"
|
|
606
|
+
"// The frame layer is the framework's file; its rows are anchored at the app, so the composition DSH keeps names the app's files, never the bundle's.",
|
|
607
|
+
"const frameLayer = anchorRows(load(readFileSync(join(frameworkRoot, 'cordis.patch.yml'), 'utf8')) ?? [], appRoot)",
|
|
597
608
|
"/** Our host rows first: the framework's node server, then the webServer port the driver rows register on (they are the app's, not DSH's). */",
|
|
598
609
|
`const clientRows = ${JSON.stringify(CLIENT_ROWS, null, 2).replaceAll("\\n", "\\n")}`,
|
|
599
610
|
`const agentRows = ${JSON.stringify(AGENT_PRESET_ROWS, null, 2)}`,
|
|
@@ -613,7 +624,7 @@ function hostScriptOf(profile, frameModule) {
|
|
|
613
624
|
" insert: [",
|
|
614
625
|
" { id: 'node-server', name: './plugins/acryl-node-server/src/index.ts', config: { hostname: '127.0.0.1', port: Number(port), scan: Number(port) === 0 ? 0 : 20 } },",
|
|
615
626
|
" { id: 'web-server', name: './plugins/acryl-web-server/src/index.ts' },",
|
|
616
|
-
" { id: 'page', name: './plugins/acryl-page/src/index.ts' },",
|
|
627
|
+
" { id: 'page', name: './plugins/acryl-page/src/index.ts', config: { title: appTitle } },",
|
|
617
628
|
" ...clientRows,",
|
|
618
629
|
" ...agentRows,",
|
|
619
630
|
" ],",
|
|
@@ -664,7 +675,7 @@ function hostScriptOf(profile, frameModule) {
|
|
|
664
675
|
"const layers = [compositionLayer, settingsLayer]",
|
|
665
676
|
"let ctx",
|
|
666
677
|
"try {",
|
|
667
|
-
" ctx = await boot('" + profile + "', join(
|
|
678
|
+
" ctx = await boot('" + profile + "', join(frameworkRoot, 'cordis.yml'), layers.flat(), (hostContext) => {",
|
|
668
679
|
" hostContext.provide('profileContext', profileContext)",
|
|
669
680
|
" }, pathToFileURL(join(appRoot, 'package.json')).href)",
|
|
670
681
|
"} catch (error) {",
|
|
@@ -837,9 +848,10 @@ function overlayOf(packages, door = false, appRows = []) {
|
|
|
837
848
|
for (const entry of rows) {
|
|
838
849
|
lines.push(` - id: ${entry.row ?? entry.name}`, ` name: ${yamlName(entry.name)}`);
|
|
839
850
|
if ((entry.row ?? entry.name) === "acryl-blends-frame") lines.push(" config:", ` appPage: ${FRAME_PAGE_PREFIX}`);
|
|
851
|
+
if ((entry.row ?? entry.name) === "acryl-router") lines.push(" config:", " trailingSlash: ignore");
|
|
840
852
|
}
|
|
841
853
|
lines.push("- id: connection", " inject: []", " config:", " trustedHosts: ['127.0.0.1']");
|
|
842
|
-
lines.push("- id: workspace-controller", " config:", " documentsDirectory: !!js process.env.
|
|
854
|
+
lines.push("- id: workspace-controller", " config:", " documentsDirectory: !!js process.env.DSH_HOME");
|
|
843
855
|
for (const id of [...disabled].sort()) lines.push(`- id: ${id}`, " disabled: true");
|
|
844
856
|
return `${lines.join("\n")}\n`;
|
|
845
857
|
}
|
|
@@ -1890,7 +1902,7 @@ function startRepository(directory) {
|
|
|
1890
1902
|
"Initial commit from acrylblends"
|
|
1891
1903
|
]);
|
|
1892
1904
|
}
|
|
1893
|
-
/** Parse `init [dir] [--template t] [--agent|--no-agent] [--name n] [--install] [-y]`. `--agent` means `--template agent`, `--no-agent` means `--template blank`. */
|
|
1905
|
+
/** Parse `init [dir] [--template t] [--agent|--no-agent] [--name n] [--install] [--bare] [-y]`. `--agent` means `--template agent`, `--no-agent` means `--template blank`. */
|
|
1894
1906
|
function parseInitOptions(argv) {
|
|
1895
1907
|
let parsed;
|
|
1896
1908
|
try {
|
|
@@ -1900,6 +1912,7 @@ function parseInitOptions(argv) {
|
|
|
1900
1912
|
}
|
|
1901
1913
|
const { values, positionals } = parsed;
|
|
1902
1914
|
if (positionals.length > 1) throw new UsageError("init takes at most one directory");
|
|
1915
|
+
if (values.bare === true && values.desktop === true) throw new UsageError("use --bare or --desktop, not both");
|
|
1903
1916
|
if (values.agent && values["no-agent"]) throw new UsageError("use --agent or --no-agent, not both");
|
|
1904
1917
|
const named = values.template === void 0 ? void 0 : resolveTemplate(values.template);
|
|
1905
1918
|
if (values.template !== void 0 && named === void 0) throw new UsageError(`unknown template "${values.template}" (${TEMPLATES.join(", ")})`);
|
|
@@ -1910,7 +1923,7 @@ function parseInitOptions(argv) {
|
|
|
1910
1923
|
name: values.name,
|
|
1911
1924
|
template: named ?? alias,
|
|
1912
1925
|
install: values.install,
|
|
1913
|
-
desktop: values.
|
|
1926
|
+
desktop: values.bare !== true,
|
|
1914
1927
|
quietNext: values["no-next"] === true,
|
|
1915
1928
|
yes: values.yes === true
|
|
1916
1929
|
};
|
|
@@ -1926,6 +1939,7 @@ const parse$3 = (argv) => parseArgs({
|
|
|
1926
1939
|
"no-agent": { type: "boolean" },
|
|
1927
1940
|
install: { type: "boolean" },
|
|
1928
1941
|
desktop: { type: "boolean" },
|
|
1942
|
+
bare: { type: "boolean" },
|
|
1929
1943
|
"no-next": { type: "boolean" },
|
|
1930
1944
|
yes: {
|
|
1931
1945
|
type: "boolean",
|
|
@@ -2027,6 +2041,12 @@ const MOUNTED = [
|
|
|
2027
2041
|
name: "feedback",
|
|
2028
2042
|
local: "feedback",
|
|
2029
2043
|
needs: ["acryl-router"]
|
|
2044
|
+
},
|
|
2045
|
+
{
|
|
2046
|
+
name: "showcase",
|
|
2047
|
+
local: "showcase",
|
|
2048
|
+
needs: ["plugin-share"],
|
|
2049
|
+
after: "plugin-share"
|
|
2030
2050
|
}
|
|
2031
2051
|
];
|
|
2032
2052
|
/** A row, in either line ending: a file edited on Windows (or checked out with core.autocrlf) is still a Blend. */
|
|
@@ -2135,7 +2155,8 @@ async function add(options, io) {
|
|
|
2135
2155
|
const code = addExternal(resolve(io.cwd, folder), appDir, io);
|
|
2136
2156
|
if (code !== 0) return code;
|
|
2137
2157
|
}
|
|
2138
|
-
const
|
|
2158
|
+
const folder = pluginsFolder(appDir).dir;
|
|
2159
|
+
const plain = !existsSync(join(appDir, "blend.yaml")) && (!existsSync(join(folder, "instance")) || existsSync(join(folder, "desktop-web")));
|
|
2139
2160
|
if (plain && options.names.includes("db")) {
|
|
2140
2161
|
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");
|
|
2141
2162
|
return 1;
|
|
@@ -2491,7 +2512,7 @@ function checkDeps(options, io) {
|
|
|
2491
2512
|
* The running frame's address, kept where the app's own data lives.
|
|
2492
2513
|
*
|
|
2493
2514
|
* The generated launcher prints the frame's URL with the launch token in it, and that token is the only way into a frame whose browser session ended.
|
|
2494
|
-
* So the address is written where the app keeps its data -
|
|
2515
|
+
* So the address is written where the app keeps its data - its folder under `~/.acryl`, outside the repository - at mint time,
|
|
2495
2516
|
* owner-only (0600 here; a restricted ACL on Windows), and atomically (a temp file in the same folder renamed over it, so a reader never sees half a
|
|
2496
2517
|
* record). On every later boot the launcher reuses the stored token instead of minting a new one, so the address - and an open page - survives a
|
|
2497
2518
|
* restart; `acrylblends open` prints it, and `acrylblends open --new` rotates it (the running host replaces the token in place through
|
|
@@ -2506,10 +2527,20 @@ function checkDeps(options, io) {
|
|
|
2506
2527
|
const FRAME_URL_FILE = "frame-url.json";
|
|
2507
2528
|
/** Where a running frame rotates its token: a route the launcher mounts on the frame's own router. */
|
|
2508
2529
|
const FRAME_TOKEN_PATH = "/api/acryl-frame/token/new";
|
|
2509
|
-
/**
|
|
2530
|
+
/** Where an app keeps its own family: the instance plugin its launcher imports. */
|
|
2531
|
+
const familyOf = (appRoot) => join(resolve(appRoot), "plugins", "instance", "index.ts");
|
|
2532
|
+
/**
|
|
2533
|
+
* The app home the frame uses, as the app's own family chooses it (`plugins/instance`, which its launcher asks too): the CLI never keeps a second copy
|
|
2534
|
+
* of that rule. A folder without the family is not a frame app.
|
|
2535
|
+
*/
|
|
2510
2536
|
function frameHome(appRoot, env = process.env) {
|
|
2511
|
-
const
|
|
2512
|
-
|
|
2537
|
+
const family = familyOf(appRoot);
|
|
2538
|
+
if (!existsSync(family)) throw new Error(`${appRoot} is not a frame app: it has no plugins/instance/index.ts`);
|
|
2539
|
+
const { selectInstance } = createRequire(import.meta.url)(family);
|
|
2540
|
+
return selectInstance({
|
|
2541
|
+
projectRoot: resolve(appRoot),
|
|
2542
|
+
env
|
|
2543
|
+
}).home;
|
|
2513
2544
|
}
|
|
2514
2545
|
function frameUrlPath(home) {
|
|
2515
2546
|
return join(home, FRAME_URL_FILE);
|
|
@@ -2668,6 +2699,10 @@ async function openFrame(args, io) {
|
|
|
2668
2699
|
return 2;
|
|
2669
2700
|
}
|
|
2670
2701
|
const directory = resolve(io.cwd, rest[0] ?? ".");
|
|
2702
|
+
if (!existsSync(familyOf(directory))) {
|
|
2703
|
+
io.stderr.write(`no frame has run in ${directory}: it is not a frame app (it has no plugins/instance)\n`);
|
|
2704
|
+
return 1;
|
|
2705
|
+
}
|
|
2671
2706
|
const found = await lookUpFrameUrl(directory);
|
|
2672
2707
|
if (found.kind === "none") {
|
|
2673
2708
|
io.stderr.write(`no frame has run in ${directory} yet; start it with npm run dev\n`);
|
|
@@ -3489,6 +3524,8 @@ function carriedCopies(dir) {
|
|
|
3489
3524
|
}
|
|
3490
3525
|
/** The plugins the frame's host layer mounts by path: any framed app carries them, and an app made before the frame gets them here. */
|
|
3491
3526
|
const HOST_PLUGINS = ["acryl-web-server", "acryl-page"];
|
|
3527
|
+
/** A library plugin a scaffold file imports: `from '../plugins/<name>/...'`. */
|
|
3528
|
+
const PLUGIN_IMPORT = /from '\.\.\/plugins\/([^/']+)\//gu;
|
|
3492
3529
|
/** The profile script the DSH-profile layout used; the host launcher replaced it, so an app upgraded from that layout drops it. */
|
|
3493
3530
|
const OLD_PROFILE_SCRIPT = "scripts/ide-profile.mjs";
|
|
3494
3531
|
/**
|
|
@@ -3518,7 +3555,15 @@ function planFrameStep(appDir, library, templateDirFor = templateDir) {
|
|
|
3518
3555
|
file: broken
|
|
3519
3556
|
};
|
|
3520
3557
|
const template = identifyTemplate(appDir, name);
|
|
3521
|
-
if (template === void 0)
|
|
3558
|
+
if (template === void 0) {
|
|
3559
|
+
if (!ownsFrameRow(appDir)) return { kind: "unidentified" };
|
|
3560
|
+
const specs = frameProfileForApp(appDir);
|
|
3561
|
+
if (specs === void 0) return {
|
|
3562
|
+
kind: "unavailable",
|
|
3563
|
+
template: void 0
|
|
3564
|
+
};
|
|
3565
|
+
return planFrameWrites(appDir, library, name, specs, void 0, [], [], void 0);
|
|
3566
|
+
}
|
|
3522
3567
|
if (!carriesFrame(template, templateDirFor(template))) return {
|
|
3523
3568
|
kind: "no-frame-template",
|
|
3524
3569
|
template
|
|
@@ -3559,6 +3604,18 @@ function planFrameStep(appDir, library, templateDirFor = templateDir) {
|
|
|
3559
3604
|
else conflictLines = { "src/app.ts": ownerChangeLines(current, target) };
|
|
3560
3605
|
}
|
|
3561
3606
|
}
|
|
3607
|
+
return planFrameWrites(appDir, library, name, specs, template, files, kept, conflictLines);
|
|
3608
|
+
}
|
|
3609
|
+
/**
|
|
3610
|
+
* Whether an app built on its own carries what the frame mounts: its routes row (`src/frame.ts`), a `src/routes.ts` that says where the frame shows it, and
|
|
3611
|
+
* the composition the generated row calls (`composeInto` in `src/app.ts`). A registry blueprint made in the frame has all three.
|
|
3612
|
+
*/
|
|
3613
|
+
function ownsFrameRow(appDir) {
|
|
3614
|
+
const at = (file) => join(appDir, "src", file);
|
|
3615
|
+
return existsSync(at("frame.ts")) && existsSync(at("routes.ts")) && FRAME_PREFIX_EXPORT.test(readText(at("routes.ts"))) && existsSync(at("app.ts")) && carriesComposeInto(readText(at("app.ts")));
|
|
3616
|
+
}
|
|
3617
|
+
/** The part of the frame plan every framed app shares: the generated composition row, the host plugins, the launcher and overlay, and what conflicts. */
|
|
3618
|
+
function planFrameWrites(appDir, library, name, specs, template, files, kept, conflictLines) {
|
|
3562
3619
|
const appTs = files.find((file) => file.file === "src/app.ts")?.text ?? (existsSync(join(appDir, "src", "app.ts")) ? readText(join(appDir, "src", "app.ts")) : void 0);
|
|
3563
3620
|
const rows = appFrameRows(appDir, {
|
|
3564
3621
|
...appTs === void 0 ? {} : { appTs },
|
|
@@ -3572,13 +3629,14 @@ function planFrameStep(appDir, library, templateDirFor = templateDir) {
|
|
|
3572
3629
|
state: generatedState(existsSync(path) ? readText(path) : void 0, rows.appPlugins)
|
|
3573
3630
|
});
|
|
3574
3631
|
}
|
|
3575
|
-
const
|
|
3632
|
+
const imported = files.filter((file) => file.file.endsWith(".ts")).flatMap((file) => [...file.text.matchAll(PLUGIN_IMPORT)].map((match) => match[1]));
|
|
3633
|
+
const addedPlugins = closure([.../* @__PURE__ */ new Set([...HOST_PLUGINS, ...imported])].filter((plugin) => library.has(plugin)), library).map((plugin) => plugin.name).filter((plugin) => !existsSync(join(appDir, "plugins", plugin)));
|
|
3576
3634
|
const oldScript = join(appDir, OLD_PROFILE_SCRIPT);
|
|
3577
3635
|
const removeProfileScript = existsSync(oldScript) && GENERATED_MARKER.test(readText(oldScript).split("\n", 1)[0] ?? "");
|
|
3578
3636
|
const input = frameProfileInput(specs, name, rows.rows);
|
|
3579
3637
|
const profile = planIdeProfileWrite(appDir, input);
|
|
3580
3638
|
const conflicts = [...files.filter((file) => file.state === "conflict").map((file) => file.file), ...profile.files.filter((file) => file.state === "conflict").map((file) => file.file)];
|
|
3581
|
-
const changed = files.some((file) => file.state !== "same") || profile.manifestChanged || profile.files.some((file) => file.state !== "same") ||
|
|
3639
|
+
const changed = files.some((file) => file.state !== "same") || profile.manifestChanged || profile.files.some((file) => file.state !== "same") || addedPlugins.length > 0 || removeProfileScript;
|
|
3582
3640
|
return {
|
|
3583
3641
|
kind: "planned",
|
|
3584
3642
|
template,
|
|
@@ -3586,7 +3644,7 @@ function planFrameStep(appDir, library, templateDirFor = templateDir) {
|
|
|
3586
3644
|
profile,
|
|
3587
3645
|
files,
|
|
3588
3646
|
kept,
|
|
3589
|
-
|
|
3647
|
+
addedPlugins,
|
|
3590
3648
|
removeProfileScript,
|
|
3591
3649
|
conflicts,
|
|
3592
3650
|
...conflictLines === void 0 ? {} : { conflictLines },
|
|
@@ -3603,7 +3661,7 @@ function frameReport(frame) {
|
|
|
3603
3661
|
for (const file of frame.files) if (file.state !== "same") parts.push(`${file.file} ${file.state === "update" ? "updated" : "added"}`);
|
|
3604
3662
|
for (const file of frame.profile.files) if (file.state !== "same") parts.push(`${file.file} ${file.state === "update" ? "updated" : "added"}`);
|
|
3605
3663
|
if (frame.profile.clientRowsAdded.length > 0) parts.push(`scripts/ide-host.mjs client rows added (${frame.profile.clientRowsAdded.join(", ")})`);
|
|
3606
|
-
for (const plugin of frame.
|
|
3664
|
+
for (const plugin of frame.addedPlugins) parts.push(`plugins/${plugin} added`);
|
|
3607
3665
|
if (frame.removeProfileScript) parts.push(`${OLD_PROFILE_SCRIPT} removed`);
|
|
3608
3666
|
if (frame.profile.manifestChanged) parts.push("package.json merged (dev runs scripts/ide-host.mjs)");
|
|
3609
3667
|
for (const { file, mountable } of frame.kept) if (!mountable) parts.push(`${file} kept as the app's (edited): it does not export FRAME_MOUNT_PREFIX, so the frame cannot show the app until it does (a fresh app's ${file} shows how)`);
|
|
@@ -3612,7 +3670,7 @@ function frameReport(frame) {
|
|
|
3612
3670
|
/**
|
|
3613
3671
|
* `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
|
|
3614
3672
|
* 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
|
|
3615
|
-
* under `.
|
|
3673
|
+
* under `.acryl/upgrade-backup/` first. Returns the exit code: 0 done or nothing to do, 1 refused or failed.
|
|
3616
3674
|
*/
|
|
3617
3675
|
async function upgrade(options, io) {
|
|
3618
3676
|
const appDir = resolve(io.cwd, options.directory ?? ".");
|
|
@@ -3637,7 +3695,7 @@ async function upgrade(options, io) {
|
|
|
3637
3695
|
if (frame.kind === "planned" && frame.conflicts.length > 0 && !options.force) {
|
|
3638
3696
|
const own = frame.conflictLines?.["src/app.ts"] ?? [];
|
|
3639
3697
|
const detail = own.length === 0 ? "" : `\n src/app.ts keeps the framework's plugin registrations; the app's own lines are ${own.map((line) => `line ${String(line.line)} (${JSON.stringify(line.text)})`).join(", ")}`;
|
|
3640
|
-
io.stderr.write(`error: ${frame.conflicts.join(", ")} in ${appDir} ${frame.conflicts.length === 1 ? "is" : "are"} not the previous release's scaffold and acrylblends did not write ${frame.conflicts.length === 1 ? "it" : "them"}; nothing was changed.${detail} Move the app's version aside (or merge it into the template's) and run again, or run again with --force (the app's versions are saved under .
|
|
3698
|
+
io.stderr.write(`error: ${frame.conflicts.join(", ")} in ${appDir} ${frame.conflicts.length === 1 ? "is" : "are"} not the previous release's scaffold and acrylblends did not write ${frame.conflicts.length === 1 ? "it" : "them"}; nothing was changed.${detail} Move the app's version aside (or merge it into the template's) and run again, or run again with --force (the app's versions are saved under .acryl/upgrade-backup/ first)\n`);
|
|
3641
3699
|
return 1;
|
|
3642
3700
|
}
|
|
3643
3701
|
const frameChanged = frame.kind === "planned" && frame.changed;
|
|
@@ -3645,7 +3703,7 @@ async function upgrade(options, io) {
|
|
|
3645
3703
|
io.stdout.write(frame.kind === "planned" ? "nothing to upgrade: the library plugins and the frame are up to date\n" : `nothing to upgrade: the app carries no library plugins\n ${"frame".padEnd(22)} ${frameReport(frame)}\n`);
|
|
3646
3704
|
return 0;
|
|
3647
3705
|
}
|
|
3648
|
-
const scratch = join(appDir, ".
|
|
3706
|
+
const scratch = join(appDir, ".acryl", `upgrade-${process.pid}-${Date.now()}`);
|
|
3649
3707
|
const lock = readLock(folder.dir);
|
|
3650
3708
|
const history = readHistory();
|
|
3651
3709
|
const plans = [];
|
|
@@ -3723,7 +3781,7 @@ async function upgrade(options, io) {
|
|
|
3723
3781
|
io.stdout.write(`\n${state === "conflict" ? "EDITED HERE, the upgrade would replace it:\n" : ""}${unifiedDiff(name, before, after)}`);
|
|
3724
3782
|
}
|
|
3725
3783
|
if (conflicts > 0 && !options.force) {
|
|
3726
|
-
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 .
|
|
3784
|
+
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 .acryl/upgrade-backup/)\n`);
|
|
3727
3785
|
return 1;
|
|
3728
3786
|
}
|
|
3729
3787
|
if (options.dryRun) {
|
|
@@ -3734,7 +3792,7 @@ async function upgrade(options, io) {
|
|
|
3734
3792
|
io.stdout.write("\neverything is up to date\n");
|
|
3735
3793
|
return 0;
|
|
3736
3794
|
}
|
|
3737
|
-
const backup = join(appDir, ".
|
|
3795
|
+
const backup = join(appDir, ".acryl", "upgrade-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/gu, "-"));
|
|
3738
3796
|
const addedDependencies = [];
|
|
3739
3797
|
for (const plan of plans) {
|
|
3740
3798
|
const here = join(folder.dir, plan.plugin.name);
|
|
@@ -3777,7 +3835,7 @@ async function upgrade(options, io) {
|
|
|
3777
3835
|
cpSync(join(appDir, file.file), join(backup, file.file));
|
|
3778
3836
|
frameBackups += 1;
|
|
3779
3837
|
}
|
|
3780
|
-
for (const plugin of frame.
|
|
3838
|
+
for (const plugin of frame.addedPlugins) {
|
|
3781
3839
|
const known = library.get(plugin);
|
|
3782
3840
|
if (known === void 0) continue;
|
|
3783
3841
|
copyPlugin(known, appDir);
|
|
@@ -3834,7 +3892,8 @@ Options for init:
|
|
|
3834
3892
|
--template <name> one of the templates below (asked when not given); --agent means --template agent,
|
|
3835
3893
|
--no-agent means --template blank
|
|
3836
3894
|
--name <name> the project name (default: the directory name)
|
|
3837
|
-
--
|
|
3895
|
+
--bare a plain app, without the ACRYL frame (the frame, its chat and agents and npm run desktop, are the default)
|
|
3896
|
+
--desktop the frame and the desktop shell (the default; kept for older scripts)
|
|
3838
3897
|
--install run npm install after copying
|
|
3839
3898
|
-y, --yes ask nothing; use defaults (the blank template, no install)
|
|
3840
3899
|
|