acrylblends 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11,6 +11,16 @@ import { homedir } from "node:os";
11
11
  //#region ../../frame/packages.json
12
12
  var scope = "@acrylblends";
13
13
  var dshVersion = "0.2.0-rc.2";
14
+ var pnpm = {
15
+ "//": "An app in the frame installed with pnpm: these build scripts run, as they do under npm (packages/cli/src/pnpm-settings.ts). Found by installing a framed app with pnpm 12: dsh-subprocess-local builds its spawn helper, node-pty and koffi their native parts.",
16
+ "allowBuilds": [
17
+ "@deepseek-ai/dsh-subprocess-local",
18
+ "@google/genai",
19
+ "koffi",
20
+ "node-pty",
21
+ "protobufjs"
22
+ ]
23
+ };
14
24
  var packages = {
15
25
  "acryl-ui": {
16
26
  "name": "ui",
@@ -78,6 +88,65 @@ var packages = {
78
88
  }
79
89
  };
80
90
  //#endregion
91
+ //#region src/pnpm-settings.ts
92
+ /**
93
+ * What an app in the ACRYL frame needs from pnpm to install and start as it does with npm, kept in the app's `pnpm-workspace.yaml` (npm never reads it).
94
+ *
95
+ * - `nodeLinker: hoisted`: the frame's host composes about a hundred DSH rows by package name from the app's own folder, which finds them only in a flat
96
+ * `node_modules` (npm's layout). pnpm's isolated layout hides them, and the host stops with "failed to import" on every row.
97
+ * - `allowBuilds`: pnpm 11 and later refuse to finish an install while a dependency's build script is neither allowed nor denied. The frame's packages
98
+ * whose scripts run under npm (frame/packages.json `pnpm.allowBuilds`) are allowed, so pnpm installs what npm installs.
99
+ *
100
+ * The file is the person's too (pnpm itself writes `minimumReleaseAgeExclude` there), so it is edited by lines: a key they set is never changed, and
101
+ * their comments and layout stay.
102
+ */
103
+ /** The frame's dependencies whose build scripts pnpm must run. */
104
+ const FRAME_ALLOW_BUILDS = pnpm.allowBuilds;
105
+ const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
106
+ /** A top-level key line (`name:` at column 0), with or without a value after it. */
107
+ function topKey(line) {
108
+ return /^([A-Za-z][\w-]*):/u.exec(line)?.[1];
109
+ }
110
+ /** A YAML map key, quoted when it is not a plain word (a scoped package name). */
111
+ function yamlKey(name) {
112
+ return /^[\w.-]+$/u.test(name) ? name : `'${name}'`;
113
+ }
114
+ /** The package names a top-level `allowBuilds:` block already decides, allowed or denied. */
115
+ function decided(lines, start) {
116
+ const names = /* @__PURE__ */ new Set();
117
+ let end = start + 1;
118
+ for (let index = start + 1; index < lines.length; index += 1) {
119
+ const line = lines[index] ?? "";
120
+ if (line.trim() === "" || line.trimStart().startsWith("#")) continue;
121
+ if (!/^\s/u.test(line)) break;
122
+ const key = /^\s+(['"]?)(.+?)\1\s*:/u.exec(line)?.[2];
123
+ if (key !== void 0) names.add(key);
124
+ end = index + 1;
125
+ }
126
+ return {
127
+ names,
128
+ end
129
+ };
130
+ }
131
+ /**
132
+ * `pnpm-workspace.yaml` with the frame's settings added: the whole file when there is none, else only what it lacks. `builds` defaults to the frame's
133
+ * list. Returns the text unchanged when nothing is missing.
134
+ */
135
+ function withFramePnpmSettings(text, builds = FRAME_ALLOW_BUILDS) {
136
+ const lines = text === void 0 || text === "" ? [] : text.replace(/\n$/u, "").split("\n");
137
+ if (!new Set(lines.map(topKey).filter((key) => key !== void 0)).has("nodeLinker")) lines.push("# The ACRYL frame finds its packages in a flat node_modules, as npm lays it out (acrylblends writes this; npm ignores the file).", "nodeLinker: hoisted");
138
+ const at = lines.findIndex((line) => topKey(line) === "allowBuilds");
139
+ if (at === -1) {
140
+ if (builds.length > 0) lines.push("# The frame's packages whose install scripts npm runs too; pnpm stops an install while a build is neither allowed nor denied.", "allowBuilds:", ...builds.map((name) => ` ${yamlKey(name)}: true`));
141
+ } else {
142
+ const { names, end } = decided(lines, at);
143
+ const missing = builds.filter((name) => !names.has(name));
144
+ if (missing.length > 0 && /^allowBuilds:\s*(#.*)?$/u.test(lines[at] ?? "")) lines.splice(end, 0, ...missing.map((name) => ` ${yamlKey(name)}: true`));
145
+ }
146
+ const next = lines.length === 0 ? "" : `${lines.join("\n")}\n`;
147
+ return text !== void 0 && next === text ? text : next;
148
+ }
149
+ //#endregion
81
150
  //#region src/ide-profile.ts
82
151
  /**
83
152
  * The host an app runs on inside the ACRYL frame: the app's own node server and loader composition, with DSH riding as the chat/session driver
@@ -622,7 +691,7 @@ function hostScriptOf(profile, frameModule) {
622
691
  `agentRows.find((row) => row.id === 'preset-${AGENT_PRESET_ID}').config.plugins.push(...DSH_STANDARD_PLUGINS.map(dshStandardPlugin))`,
623
692
  "const hostLayer = anchorRows([{",
624
693
  " insert: [",
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 } },",
694
+ " { id: 'node-server', name: './plugins/acryl-node-server/src/index.ts', config: { hostname: chosen.host ?? '127.0.0.1', port: Number(port), scan: Number(port) === 0 ? 0 : 20 } },",
626
695
  " { id: 'web-server', name: './plugins/acryl-web-server/src/index.ts' },",
627
696
  " { id: 'page', name: './plugins/acryl-page/src/index.ts', config: { title: appTitle } },",
628
697
  " ...clientRows,",
@@ -684,10 +753,11 @@ function hostScriptOf(profile, frameModule) {
684
753
  " const entries = Array.isArray(error?.entries) ? error.entries : []",
685
754
  " const failures = entries.filter(({ outcome }) => outcome?.kind === \"failed\")",
686
755
  " console.error(`acrylblends host: startup failed (${String(failures.length)} row(s))`)",
687
- " for (const { entry, outcome } of failures) {",
756
+ " // Each entry is the boot report of one row: { id, module, required, fiberState, outcome }.",
757
+ " for (const { id, module, outcome } of failures) {",
688
758
  " const cause = outcome.error",
689
759
  " const detail = cause instanceof Error ? cause.stack ?? cause.message : String(cause)",
690
- " console.error(`- ${entry.id} (${entry.options?.name ?? \"?\"}): ${detail}`)",
760
+ " console.error(`- ${String(id)} (${String(module)}): ${detail}`)",
691
761
  " }",
692
762
  " const cause = error?.cause",
693
763
  " if (failures.length === 0 && cause !== undefined) console.error(cause instanceof Error ? cause.stack ?? cause.message : String(cause))",
@@ -947,36 +1017,41 @@ function planIdeProfileWrite(root, input) {
947
1017
  const hostPath = join(root, "scripts/ide-host.mjs");
948
1018
  const appHost = existsSync(hostPath) ? readFileSync(hostPath, "utf8") : void 0;
949
1019
  let clientRowsAdded = [];
1020
+ const files = GENERATED_FILES.map(({ file, of }) => {
1021
+ let text = of(plan);
1022
+ if (file === "scripts/ide-host.mjs") {
1023
+ const merged = mergeHostClientRows(text, appHost);
1024
+ text = merged.text;
1025
+ clientRowsAdded = merged.added;
1026
+ }
1027
+ const path = join(root, file);
1028
+ if (!existsSync(path)) return {
1029
+ file,
1030
+ text,
1031
+ state: "add"
1032
+ };
1033
+ const before = readFileSync(path, "utf8");
1034
+ if (before === text) return {
1035
+ file,
1036
+ text,
1037
+ state: "same"
1038
+ };
1039
+ const firstLine = before.split("\n", 1)[0] ?? "";
1040
+ return {
1041
+ file,
1042
+ text,
1043
+ state: GENERATED_MARKER.test(firstLine) ? "update" : "conflict"
1044
+ };
1045
+ });
1046
+ const workspaceFile = join(root, PNPM_WORKSPACE_FILE);
1047
+ const workspaceBefore = existsSync(workspaceFile) ? readFileSync(workspaceFile, "utf8") : void 0;
1048
+ const workspaceAfter = withFramePnpmSettings(workspaceBefore);
950
1049
  return {
951
1050
  plan,
952
- files: GENERATED_FILES.map(({ file, of }) => {
953
- let text = of(plan);
954
- if (file === "scripts/ide-host.mjs") {
955
- const merged = mergeHostClientRows(text, appHost);
956
- text = merged.text;
957
- clientRowsAdded = merged.added;
958
- }
959
- const path = join(root, file);
960
- if (!existsSync(path)) return {
961
- file,
962
- text,
963
- state: "add"
964
- };
965
- const before = readFileSync(path, "utf8");
966
- if (before === text) return {
967
- file,
968
- text,
969
- state: "same"
970
- };
971
- const firstLine = before.split("\n", 1)[0] ?? "";
972
- return {
973
- file,
974
- text,
975
- state: GENERATED_MARKER.test(firstLine) ? "update" : "conflict"
976
- };
977
- }),
1051
+ files,
978
1052
  manifestChanged: existing !== plan.packageJson,
979
- clientRowsAdded
1053
+ clientRowsAdded,
1054
+ pnpmWorkspace: workspaceAfter === workspaceBefore ? void 0 : workspaceAfter
980
1055
  };
981
1056
  }
982
1057
  /** Write a plan made by {@link planIdeProfileWrite}: only the files whose bytes differ, and `package.json` only when the merge changes it. */
@@ -997,6 +1072,10 @@ function writeIdeProfilePlan(root, planned) {
997
1072
  writeFileSync(join(root, "package.json"), planned.plan.packageJson);
998
1073
  written.push("package.json");
999
1074
  } else unchanged.push("package.json");
1075
+ if (planned.pnpmWorkspace !== void 0) {
1076
+ writeFileSync(join(root, PNPM_WORKSPACE_FILE), planned.pnpmWorkspace);
1077
+ written.push(PNPM_WORKSPACE_FILE);
1078
+ } else unchanged.push(PNPM_WORKSPACE_FILE);
1000
1079
  return {
1001
1080
  written,
1002
1081
  unchanged