chainlesschain 0.156.6 → 0.156.7

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.
Files changed (113) hide show
  1. package/README.md +24 -0
  2. package/package.json +2 -1
  3. package/src/assets/web-panel/assets/{ActionButton-Dme4LGax.js → ActionButton-Cs4QdjYb.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-B3-5BjRm.js → Analytics-Xot0e9TT.js} +1 -1
  5. package/src/assets/web-panel/assets/{AppLayout-DvVLRyPs.js → AppLayout-3qsE1-pz.js} +1 -1
  6. package/src/assets/web-panel/assets/{BaseInput-wLmjCc9u.js → BaseInput-Tg40P4JM.js} +1 -1
  7. package/src/assets/web-panel/assets/{Checkbox-BfbEUJDW.js → Checkbox-CheA2Ety.js} +1 -1
  8. package/src/assets/web-panel/assets/{Col-HJI40OzO.js → Col-Cdfsmnaq.js} +1 -1
  9. package/src/assets/web-panel/assets/{Compact-ADVAwcbQ.js → Compact-D3LSgEpW.js} +1 -1
  10. package/src/assets/web-panel/assets/{Cron-DcNB5TYu.js → Cron-9MV6k-MV.js} +1 -1
  11. package/src/assets/web-panel/assets/{Dashboard-p_Wuj0Un.js → Dashboard-A1TZC5_t.js} +1 -1
  12. package/src/assets/web-panel/assets/{Dropdown-CrXGzreQ.js → Dropdown-DZxUTZvw.js} +1 -1
  13. package/src/assets/web-panel/assets/{FormItemContext-B97Dibo2.js → FormItemContext-C3_-j_SR.js} +1 -1
  14. package/src/assets/web-panel/assets/{Git-90CPsOOr.js → Git-Cw-gW-kh.js} +1 -1
  15. package/src/assets/web-panel/assets/{Memory-B_3zNQNB.js → Memory-CMweTJyn.js} +1 -1
  16. package/src/assets/web-panel/assets/{Notes-DNQz9UXh.js → Notes-B_W3BfZF.js} +1 -1
  17. package/src/assets/web-panel/assets/{Organization-CgXUnp-W.js → Organization-Dz_jGbAM.js} +1 -1
  18. package/src/assets/web-panel/assets/{Overflow-BVsn6SM5.js → Overflow-Dka3nWV9.js} +1 -1
  19. package/src/assets/web-panel/assets/{Permissions-DIFqcnjU.js → Permissions-DvXVIlHX.js} +1 -1
  20. package/src/assets/web-panel/assets/{Providers-mscN7CK5.js → Providers-DUfX_ynl.js} +1 -1
  21. package/src/assets/web-panel/assets/{Row-BFUWxIkx.js → Row-DZhDSo2Q.js} +1 -1
  22. package/src/assets/web-panel/assets/{RssFeed-Dpa4h-q_.js → RssFeed-CHQpUl3h.js} +1 -1
  23. package/src/assets/web-panel/assets/{Security-DR6HKo_S.js → Security-FUSOn89T.js} +1 -1
  24. package/src/assets/web-panel/assets/{Skeleton-VNikEgM4.js → Skeleton-DxmZ7zRw.js} +1 -1
  25. package/src/assets/web-panel/assets/{Templates-Ny_4GO6a.js → Templates-BVbmyn38.js} +1 -1
  26. package/src/assets/web-panel/assets/{Trigger-C7MTh_xj.js → Trigger-xAvohiq9.js} +1 -1
  27. package/src/assets/web-panel/assets/{VideoEditing-BNRFHgJ9.js → VideoEditing-BUWYQv2y.js} +1 -1
  28. package/src/assets/web-panel/assets/{Wallet-BUfg4IAx.js → Wallet-BDYdEwFf.js} +1 -1
  29. package/src/assets/web-panel/assets/{WebAuthn-Cia89OyQ.js → WebAuthn-CvpuagtK.js} +1 -1
  30. package/src/assets/web-panel/assets/{WorkflowEditor-C1OsMtqv.js → WorkflowEditor-BR7W5cjw.js} +1 -1
  31. package/src/assets/web-panel/assets/{colors-C_wDMX2Q.js → colors-C5kDbQCi.js} +1 -1
  32. package/src/assets/web-panel/assets/{compact-item-C1ikzEN-.js → compact-item-Bo_1zDrX.js} +1 -1
  33. package/src/assets/web-panel/assets/{createContext-XExBTk9v.js → createContext-CniPpJsG.js} +1 -1
  34. package/src/assets/web-panel/assets/{hasIn-mXvd_Kdq.js → hasIn-ClDc6Sz8.js} +1 -1
  35. package/src/assets/web-panel/assets/{index-D6Hyy0Bc.js → index--lcO-bOn.js} +1 -1
  36. package/src/assets/web-panel/assets/{index-lPIeHtHE.js → index-6tQekF0Y.js} +1 -1
  37. package/src/assets/web-panel/assets/{index-BfncNR8d.js → index-8qWxPHSb.js} +1 -1
  38. package/src/assets/web-panel/assets/{index-C53dnYiq.js → index-B7nGNm_C.js} +1 -1
  39. package/src/assets/web-panel/assets/{index-CMYADk0v.js → index-B8y0NO-M.js} +1 -1
  40. package/src/assets/web-panel/assets/{index-DMcLOtIo.js → index-BAlSSCbs.js} +1 -1
  41. package/src/assets/web-panel/assets/{index-DJkIheU6.js → index-BCXFoTAw.js} +1 -1
  42. package/src/assets/web-panel/assets/{index-kLUQdSDJ.js → index-BHruTebo.js} +1 -1
  43. package/src/assets/web-panel/assets/{index-1ZqkTPt2.js → index-BJx6C3J8.js} +1 -1
  44. package/src/assets/web-panel/assets/{index-CbpKJ2W0.js → index-BUTCJTbj.js} +1 -1
  45. package/src/assets/web-panel/assets/{index-B6P9mWuk.js → index-BYShDlZ0.js} +1 -1
  46. package/src/assets/web-panel/assets/{index-D9tzxSFs.js → index-Bv2OmZAS.js} +1 -1
  47. package/src/assets/web-panel/assets/{index-BFFb9yPd.js → index-C92K4iDE.js} +1 -1
  48. package/src/assets/web-panel/assets/{index-v4Oi0d0l.js → index-CCdb36il.js} +1 -1
  49. package/src/assets/web-panel/assets/{index-B-TI0cZ2.js → index-CKR2ITFk.js} +1 -1
  50. package/src/assets/web-panel/assets/{index-fLUJs2Sr.js → index-CMcGcbea.js} +1 -1
  51. package/src/assets/web-panel/assets/{index-BirLVqrC.js → index-CTetsi8W.js} +1 -1
  52. package/src/assets/web-panel/assets/{index-LpE6Six-.js → index-CXxLp7Aw.js} +1 -1
  53. package/src/assets/web-panel/assets/{index-qtDQSqTG.js → index-CeSV8f3b.js} +1 -1
  54. package/src/assets/web-panel/assets/{index-DwMlStra.js → index-Ch5mAXeh.js} +1 -1
  55. package/src/assets/web-panel/assets/{index-BOqmUcij.js → index-CwhWEkmA.js} +1 -1
  56. package/src/assets/web-panel/assets/{index-D_oSE2Nk.js → index-D2fe9a6f.js} +1 -1
  57. package/src/assets/web-panel/assets/index-D3UDIt7h.js +1 -0
  58. package/src/assets/web-panel/assets/{index-CxwU-EjS.js → index-D90sLw5Q.js} +1 -1
  59. package/src/assets/web-panel/assets/{index-D1eekAaa.js → index-D9bolkbl.js} +1 -1
  60. package/src/assets/web-panel/assets/{index-BL27IhbN.js → index-DNY0K7iI.js} +1 -1
  61. package/src/assets/web-panel/assets/{index-Du7KGlCP.js → index-DSiHmo4b.js} +1 -1
  62. package/src/assets/web-panel/assets/{index-CttcpCq_.js → index-DTYnvYqB.js} +1 -1
  63. package/src/assets/web-panel/assets/{index-jg5cpQg9.js → index-DaLYbr0E.js} +1 -1
  64. package/src/assets/web-panel/assets/{index-DYLE4bnY.js → index-DkSNIJhM.js} +1 -1
  65. package/src/assets/web-panel/assets/{index-DZjQgmBq.js → index-DnQkqOZj.js} +1 -1
  66. package/src/assets/web-panel/assets/{index-DaMG8ksh.js → index-Dn_OQQaV.js} +3 -3
  67. package/src/assets/web-panel/assets/{index-Dz6RDRcu.js → index-Dtfrhky9.js} +1 -1
  68. package/src/assets/web-panel/assets/{index-CTle6zcb.js → index-JNbd08FN.js} +1 -1
  69. package/src/assets/web-panel/assets/index-PT376OZM.js +1 -0
  70. package/src/assets/web-panel/assets/{index-BBOVB9YK.js → index-cIgCeEqo.js} +1 -1
  71. package/src/assets/web-panel/assets/{index-a0qENb5U.js → index-vBi4x_6g.js} +1 -1
  72. package/src/assets/web-panel/assets/{index-C5Zv4fBx.js → index-xL8gcpmy.js} +1 -1
  73. package/src/assets/web-panel/assets/{initDefaultProps-DOj2K4bh.js → initDefaultProps-DMfJaUzk.js} +1 -1
  74. package/src/assets/web-panel/assets/{motion-joGf7r-l.js → motion-sEbWmOWo.js} +1 -1
  75. package/src/assets/web-panel/assets/{move-Cwb6tumJ.js → move-DIWXVs--.js} +1 -1
  76. package/src/assets/web-panel/assets/{omit-CPycjJ8C.js → omit-D7mkMPhu.js} +1 -1
  77. package/src/assets/web-panel/assets/{pickAttrs-CnibXC3T.js → pickAttrs-B25NUX4k.js} +1 -1
  78. package/src/assets/web-panel/assets/{placementArrow-DWcIO1y4.js → placementArrow-By1Bkq1d.js} +1 -1
  79. package/src/assets/web-panel/assets/{responsiveObserve-C5giLhLf.js → responsiveObserve-B3aCQz5r.js} +1 -1
  80. package/src/assets/web-panel/assets/{slide-zwgmm7vM.js → slide-eR-f56FQ.js} +1 -1
  81. package/src/assets/web-panel/assets/{statusUtils-CK8tJSHq.js → statusUtils-zcNWczhN.js} +1 -1
  82. package/src/assets/web-panel/assets/{styleChecker-BzLSEXyu.js → styleChecker-u9Z0IfRy.js} +1 -1
  83. package/src/assets/web-panel/assets/{transition-D4AbuDdO.js → transition-gRK4XSlW.js} +1 -1
  84. package/src/assets/web-panel/assets/{useConfigInject-ImjEZhXr.js → useConfigInject-ZEunuNHN.js} +1 -1
  85. package/src/assets/web-panel/assets/{useFlexGapSupport-Cd-PoTMl.js → useFlexGapSupport-BpbEJfeh.js} +1 -1
  86. package/src/assets/web-panel/assets/{vnode-DAWimP6X.js → vnode-DVHvXn9F.js} +1 -1
  87. package/src/assets/web-panel/assets/{zoom-u6SXbmzZ.js → zoom-ByzgJIn6.js} +1 -1
  88. package/src/assets/web-panel/index.html +1 -1
  89. package/src/commands/pack.js +463 -0
  90. package/src/commands/ui.js +6 -0
  91. package/src/gateways/ws/ws-server.js +29 -3
  92. package/src/index.js +34 -1
  93. package/src/lib/governance-v2-helpers.js +109 -0
  94. package/src/lib/packer/config-template-builder.js +151 -0
  95. package/src/lib/packer/errors.js +30 -0
  96. package/src/lib/packer/index.js +383 -0
  97. package/src/lib/packer/manifest-writer.js +93 -0
  98. package/src/lib/packer/native-prebuild-collector.js +305 -0
  99. package/src/lib/packer/pack-update-applier.js +203 -0
  100. package/src/lib/packer/pack-update-checker.js +185 -0
  101. package/src/lib/packer/pack-update-downloader.js +162 -0
  102. package/src/lib/packer/pkg-config-generator.js +325 -0
  103. package/src/lib/packer/pkg-runner.js +139 -0
  104. package/src/lib/packer/precheck.js +273 -0
  105. package/src/lib/packer/project-assets-collector.js +0 -0
  106. package/src/lib/packer/smoke-runner.js +339 -0
  107. package/src/lib/packer/web-panel-builder.js +157 -0
  108. package/src/lib/web-ui-server.js +95 -2
  109. package/src/lib/ws-server.js +1 -0
  110. package/src/runtime/agent-runtime.js +1 -0
  111. package/src/runtime/policies/agent-policy.js +1 -0
  112. package/src/assets/web-panel/assets/index-DQgS_8Fd.js +0 -1
  113. package/src/assets/web-panel/assets/index-f4W8Sok0.js +0 -1
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Phase 3: config-template-builder
3
+ *
4
+ * Build the .chainlesschain/ template that the artifact will release into the
5
+ * user's data dir on first run. Optionally merges a --preset-config provided
6
+ * by the packager, after a strict secret scan.
7
+ *
8
+ * Secrets policy: any non-empty string at a path matching SECRET_PATTERNS
9
+ * causes a hard error unless --allow-secrets is passed. Reason: if the
10
+ * packager accidentally bundles their own API key, every downstream user
11
+ * gets it.
12
+ */
13
+
14
+ import fs from "node:fs";
15
+ import path from "node:path";
16
+ import { PackError, EXIT } from "./errors.js";
17
+
18
+ /**
19
+ * Field paths (regex on the joined dotted path) that, if set to a non-empty
20
+ * string, are considered sensitive credentials.
21
+ */
22
+ export const SECRET_PATTERNS = Object.freeze([
23
+ /(^|\.)apiKey$/i,
24
+ /(^|\.)api_key$/i,
25
+ /(^|\.)secret$/i,
26
+ /(^|\.)privateKey$/i,
27
+ /(^|\.)private_key$/i,
28
+ /(^|\.)mnemonic$/i,
29
+ /(^|\.)password$/i,
30
+ /(^|\.)token$/i,
31
+ /(^|\.)access_token$/i,
32
+ /(^|\.)refresh_token$/i,
33
+ ]);
34
+
35
+ /**
36
+ * Walk an object, return [{ path, value }] for every leaf that matches a
37
+ * secret pattern with a non-empty string value. Numbers / booleans / nulls
38
+ * are ignored.
39
+ */
40
+ export function findSecrets(obj, prefix = "") {
41
+ const hits = [];
42
+ if (obj === null || typeof obj !== "object") return hits;
43
+ for (const [key, val] of Object.entries(obj)) {
44
+ const dotted = prefix ? `${prefix}.${key}` : key;
45
+ if (val && typeof val === "object" && !Array.isArray(val)) {
46
+ hits.push(...findSecrets(val, dotted));
47
+ } else if (typeof val === "string" && val.length > 0) {
48
+ if (SECRET_PATTERNS.some((re) => re.test(dotted))) {
49
+ hits.push({ path: dotted, value: val });
50
+ }
51
+ }
52
+ }
53
+ return hits;
54
+ }
55
+
56
+ /**
57
+ * @param {object} ctx
58
+ * @param {string|null} ctx.presetConfigPath
59
+ * @param {boolean} ctx.allowSecrets
60
+ * @param {object} [ctx.logger]
61
+ * @returns {{ template: object, secrets: Array<{path:string}> }}
62
+ */
63
+ export function buildConfigTemplate(ctx) {
64
+ const { presetConfigPath, allowSecrets, logger } = ctx;
65
+ const log = logger?.log || (() => {});
66
+
67
+ const baseTemplate = {
68
+ schema: 1,
69
+ server: {
70
+ bindHost: ctx.bindHost || "127.0.0.1",
71
+ wsPort: ctx.wsPort || 18800,
72
+ uiPort: ctx.uiPort || 18810,
73
+ enableTls: Boolean(ctx.enableTls),
74
+ },
75
+ llm: { providers: {} },
76
+ mcp: { servers: {} },
77
+ note: {},
78
+ };
79
+
80
+ let preset = null;
81
+ if (presetConfigPath) {
82
+ if (!fs.existsSync(presetConfigPath)) {
83
+ throw new PackError(
84
+ `--preset-config file not found: ${presetConfigPath}`,
85
+ EXIT.PRECHECK,
86
+ );
87
+ }
88
+ try {
89
+ preset = JSON.parse(fs.readFileSync(presetConfigPath, "utf-8"));
90
+ } catch (e) {
91
+ throw new PackError(
92
+ `--preset-config is not valid JSON: ${e.message}`,
93
+ EXIT.PRECHECK,
94
+ );
95
+ }
96
+ }
97
+
98
+ if (preset) {
99
+ const secrets = findSecrets(preset);
100
+ if (secrets.length > 0 && !allowSecrets) {
101
+ const list = secrets.map((s) => ` - ${s.path}`).join("\n");
102
+ throw new PackError(
103
+ `Preset config contains ${secrets.length} sensitive field(s):\n${list}\n` +
104
+ " Refusing to bundle credentials into a distributable artifact.\n" +
105
+ " Pass --allow-secrets to override (DANGEROUS), or remove the values from the preset.",
106
+ EXIT.SECRETS,
107
+ );
108
+ }
109
+ if (secrets.length > 0 && allowSecrets) {
110
+ log(
111
+ ` [config-template] WARNING: bundling ${secrets.length} secret value(s) — artifact must NOT be redistributed.`,
112
+ );
113
+ }
114
+ deepMerge(baseTemplate, preset);
115
+ return { template: baseTemplate, secrets };
116
+ }
117
+
118
+ return { template: baseTemplate, secrets: [] };
119
+ }
120
+
121
+ /**
122
+ * In-place deep merge: source overrides target on conflict; arrays replaced wholesale.
123
+ */
124
+ function deepMerge(target, source) {
125
+ for (const [k, v] of Object.entries(source)) {
126
+ if (
127
+ v &&
128
+ typeof v === "object" &&
129
+ !Array.isArray(v) &&
130
+ target[k] &&
131
+ typeof target[k] === "object" &&
132
+ !Array.isArray(target[k])
133
+ ) {
134
+ deepMerge(target[k], v);
135
+ } else {
136
+ target[k] = v;
137
+ }
138
+ }
139
+ return target;
140
+ }
141
+
142
+ /**
143
+ * Write the resolved template to disk inside the build temp dir.
144
+ * Returns the absolute path so pkg-config-generator can include it as an asset.
145
+ */
146
+ export function writeTemplate(template, outDir) {
147
+ fs.mkdirSync(outDir, { recursive: true });
148
+ const file = path.join(outDir, "config.example.json");
149
+ fs.writeFileSync(file, JSON.stringify(template, null, 2), "utf-8");
150
+ return file;
151
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * PackError — typed error for the pack pipeline. Carries an exit code that
3
+ * `cc pack` propagates back to the OS so CI can branch on failure category.
4
+ *
5
+ * Exit code map (mirror docs/design/CC_PACK_打包指令设计文档.md §5.3):
6
+ * 10 — precheck failed
7
+ * 11 — web-panel build failed
8
+ * 12 — native module prebuild missing
9
+ * 13 — pkg build failed
10
+ * 14 — smoke test failed
11
+ * 15 — code-signing failed
12
+ * 16 — preset config contained sensitive credentials
13
+ */
14
+ export class PackError extends Error {
15
+ constructor(message, exitCode = 1) {
16
+ super(message);
17
+ this.name = "PackError";
18
+ this.exitCode = exitCode;
19
+ }
20
+ }
21
+
22
+ export const EXIT = Object.freeze({
23
+ PRECHECK: 10,
24
+ WEB_PANEL: 11,
25
+ NATIVE: 12,
26
+ PKG: 13,
27
+ SMOKE: 14,
28
+ SIGN: 15,
29
+ SECRETS: 16,
30
+ });
@@ -0,0 +1,383 @@
1
+ /**
2
+ * `cc pack` orchestrator. Runs the 7-phase pipeline:
3
+ * 1. precheck — git/node_modules sanity
4
+ * 2. ensureWebPanel — Vue panel built or buildable
5
+ * 3. buildConfigTemplate — config.example.json with secret scan
6
+ * 4. collectPrebuilds — better-sqlite3 .node files per target
7
+ * 5. generatePkgConfig — synthesized package.json + pack-entry.js
8
+ * 6. runPkg — invoke @yao-pkg/pkg
9
+ * 7. writeManifests — sidecar metadata + SHA-256
10
+ *
11
+ * --dry-run stops after phase 5 and reports the build plan without
12
+ * invoking pkg or writing the artifact.
13
+ */
14
+
15
+ import fs from "node:fs";
16
+ import os from "node:os";
17
+ import path from "node:path";
18
+ import chalk from "chalk";
19
+ import { precheck } from "./precheck.js";
20
+ import { ensureWebPanel } from "./web-panel-builder.js";
21
+ import {
22
+ buildConfigTemplate,
23
+ writeTemplate,
24
+ } from "./config-template-builder.js";
25
+ import { collectPrebuilds } from "./native-prebuild-collector.js";
26
+ import { collectProjectAssets } from "./project-assets-collector.js";
27
+ import { generatePkgConfig } from "./pkg-config-generator.js";
28
+ import { runPkg } from "./pkg-runner.js";
29
+ import { writeManifests } from "./manifest-writer.js";
30
+ import { smokeTestExe } from "./smoke-runner.js";
31
+ import { PackError } from "./errors.js";
32
+
33
+ /**
34
+ * Public entry — invoked from packages/cli/src/commands/pack.js.
35
+ *
36
+ * @param {object} cliOpts raw Commander option object
37
+ * @param {object} [deps]
38
+ * @param {object} [deps.logger] logger.log/info/error
39
+ * @returns {Promise<{outputPath?:string, sha256?:string, steps:Array}>}
40
+ */
41
+ export async function runPack(cliOpts, deps = {}) {
42
+ const logger = deps.logger || console;
43
+ const log = (msg) => {
44
+ if (typeof logger.log === "function") logger.log(msg);
45
+ else if (typeof logger.info === "function") logger.info(msg);
46
+ };
47
+
48
+ const projectRoot = path.resolve(cliOpts.cwd || process.cwd());
49
+ const targets = (cliOpts.targets || "node20-win-x64")
50
+ .split(",")
51
+ .map((s) => s.trim())
52
+ .filter(Boolean);
53
+
54
+ log(
55
+ chalk.bold("\n cc pack — bundling project into standalone executable\n"),
56
+ );
57
+ log(` Project root : ${projectRoot}`);
58
+ log(` Targets : ${targets.join(", ")}`);
59
+ log(` Dry-run : ${cliOpts.dryRun ? "YES" : "no"}`);
60
+ log("");
61
+
62
+ const steps = [];
63
+
64
+ // ── Phase 1 ────────────────────────────────────────────────────────────
65
+ log(chalk.cyan(" [1/7] Precheck"));
66
+ const pre = precheck({
67
+ projectRoot,
68
+ allowDirty: Boolean(cliOpts.allowDirty),
69
+ projectMode: cliOpts.project, // tri-state: true / false / undefined
70
+ projectConfigOverride: cliOpts.projectConfigOverride || null,
71
+ });
72
+ steps.push({ phase: "precheck", ok: true, ...pre });
73
+ log(
74
+ ` cliRoot=${pre.cliRoot}\n` +
75
+ ` gitCommit=${pre.gitCommit || "(no git)"} dirty=${pre.dirty}\n` +
76
+ ` projectMode=${pre.projectMode} ` +
77
+ `projectConfig=${pre.projectConfigPath || "(none)"}`,
78
+ );
79
+
80
+ // ── Phase 2 ────────────────────────────────────────────────────────────
81
+ log(chalk.cyan(" [2/7] Ensure web-panel"));
82
+ const wp = ensureWebPanel({
83
+ cliRoot: pre.cliRoot,
84
+ skipBuild: Boolean(cliOpts.skipWebPanelBuild),
85
+ logger,
86
+ });
87
+ steps.push({ phase: "web-panel", ok: true, ...wp });
88
+ log(
89
+ ` distDir=${wp.distDir}\n` +
90
+ ` rebuilt=${wp.rebuilt} assetCount=${wp.assetCount}`,
91
+ );
92
+
93
+ // ── Build temp dir (lives under OS tmp; cleaned on success) ────────────
94
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "cc-pack-"));
95
+ log(chalk.dim(` tempDir=${tempDir}`));
96
+
97
+ // ── Phase 3 ────────────────────────────────────────────────────────────
98
+ log(chalk.cyan(" [3/7] Build config template"));
99
+ const cfg = buildConfigTemplate({
100
+ presetConfigPath: cliOpts.presetConfig || null,
101
+ allowSecrets: Boolean(cliOpts.allowSecrets),
102
+ bindHost: cliOpts.bindHost,
103
+ wsPort: parseInt(cliOpts.wsPort, 10),
104
+ uiPort: parseInt(cliOpts.uiPort, 10),
105
+ enableTls: Boolean(cliOpts.enableTls),
106
+ logger,
107
+ });
108
+ const templatesDir = path.join(tempDir, "templates");
109
+ const templateFile = writeTemplate(cfg.template, templatesDir);
110
+ steps.push({
111
+ phase: "config-template",
112
+ ok: true,
113
+ file: templateFile,
114
+ secretsFound: cfg.secrets.length,
115
+ });
116
+ log(` template=${templateFile}`);
117
+ log(` secretsFound=${cfg.secrets.length}`);
118
+
119
+ // ── Phase 3.5 (project mode only) ─────────────────────────────────────
120
+ // Snapshot projectRoot/.chainlesschain/ into tempDir/project/ so pkg can
121
+ // bundle it as an asset. The runtime entry script materializes this at
122
+ // first launch. See docs/design/CC_PACK_项目模式_设计文档.md §6.
123
+ let project = null;
124
+ if (pre.projectMode) {
125
+ log(chalk.cyan(" [3.5/7] Collect project assets"));
126
+ project = collectProjectAssets({
127
+ projectRoot,
128
+ tempDir,
129
+ allowSecrets: Boolean(cliOpts.allowSecrets),
130
+ forceLargeProject: Boolean(cliOpts.forceLargeProject),
131
+ logger,
132
+ });
133
+ steps.push({
134
+ phase: "project-assets",
135
+ ok: true,
136
+ projectName: project.projectName,
137
+ fileCount: project.fileCount,
138
+ totalBytes: project.totalBytes,
139
+ bundledSkills: project.bundledSkills.map((s) => s.name),
140
+ configSha: project.configSha,
141
+ });
142
+ log(
143
+ ` projectName=${project.projectName}\n` +
144
+ ` files=${project.fileCount} size=${formatMB(project.totalBytes)}\n` +
145
+ ` bundledSkills=[${project.bundledSkills.map((s) => s.name).join(", ")}]\n` +
146
+ ` configSha=${project.configSha.slice(0, 12)}...`,
147
+ );
148
+ }
149
+
150
+ // ── Phase 4 ────────────────────────────────────────────────────────────
151
+ // None of the native drivers are required anymore — the runtime falls
152
+ // back to sql.js (WASM). Missing natives are reported, not fatal.
153
+ log(chalk.cyan(" [4/7] Collect native prebuilds"));
154
+ const native = collectPrebuilds({
155
+ cliRoot: pre.cliRoot,
156
+ targets,
157
+ tempDir,
158
+ });
159
+ steps.push({
160
+ phase: "native-prebuilds",
161
+ ok: true,
162
+ collected: native.collected.length,
163
+ missing: native.missing.length,
164
+ sqlJs: Boolean(native.sqlJs),
165
+ });
166
+ log(
167
+ ` collected=${native.collected.length} missing=${native.missing.length}` +
168
+ ` sqlJs=${native.sqlJs ? "bundled" : "absent"}`,
169
+ );
170
+ if (native.missing.length > 0) {
171
+ for (const m of native.missing) {
172
+ log(
173
+ chalk.yellow(
174
+ ` - missing: ${m.module} (${m.target}) — will fall back to sql.js at runtime`,
175
+ ),
176
+ );
177
+ }
178
+ }
179
+ if (native.collected.length === 0 && !native.sqlJs) {
180
+ log(
181
+ chalk.red(
182
+ " WARN: no native SQLite driver found AND sql.js not installed.\n" +
183
+ " The packed binary will fail at DB init. Install sql.js" +
184
+ " in the workspace before packing.",
185
+ ),
186
+ );
187
+ }
188
+
189
+ // ── Phase 5 ────────────────────────────────────────────────────────────
190
+ log(chalk.cyan(" [5/7] Generate pkg config"));
191
+ // In project mode the artifact is named after the project, not the CLI.
192
+ const artifactBase =
193
+ pre.projectMode && project?.projectName
194
+ ? `${project.projectName}-portable-${targets[0]}`
195
+ : `chainlesschain-portable-${targets[0]}`;
196
+ const outputPath = path.resolve(
197
+ cliOpts.output || path.join(projectRoot, "dist", artifactBase),
198
+ );
199
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
200
+ const pkgCfg = generatePkgConfig({
201
+ cliRoot: pre.cliRoot,
202
+ tempDir,
203
+ distDir: wp.distDir,
204
+ prebuildsDir: native.prebuildsDir,
205
+ templatesDir,
206
+ targets,
207
+ outputPath,
208
+ compress: cliOpts.compress !== false,
209
+ runtime: {
210
+ token: typeof cliOpts.token === "string" ? cliOpts.token : "auto",
211
+ bindHost: cliOpts.bindHost,
212
+ wsPort: parseInt(cliOpts.wsPort, 10),
213
+ uiPort: parseInt(cliOpts.uiPort, 10),
214
+ },
215
+ project,
216
+ projectEntry: cliOpts.entry || null,
217
+ forceRefreshOnLaunch: Boolean(cliOpts.forceRefreshOnLaunch),
218
+ updateManifestUrl: cliOpts.updateManifestUrl || null,
219
+ });
220
+ steps.push({
221
+ phase: "pkg-config",
222
+ ok: true,
223
+ pkgConfigFile: pkgCfg.pkgConfigFile,
224
+ entryScript: pkgCfg.entryScript,
225
+ });
226
+ log(` pkgConfig=${pkgCfg.pkgConfigFile}`);
227
+ log(` entry=${pkgCfg.entryScript}`);
228
+
229
+ if (cliOpts.dryRun) {
230
+ log(chalk.yellow("\n [dry-run] Stopping before pkg invocation."));
231
+ log(chalk.dim(` Plan written to: ${pkgCfg.pkgConfigFile}`));
232
+ return { steps, dryRun: true, tempDir, project };
233
+ }
234
+
235
+ // ── Phase 6 ────────────────────────────────────────────────────────────
236
+ log(chalk.cyan(" [6/7] Run pkg"));
237
+ const built = runPkg({
238
+ cliRoot: pre.cliRoot,
239
+ pkgConfigFile: pkgCfg.pkgConfigFile,
240
+ outputPath,
241
+ targets,
242
+ logger,
243
+ });
244
+ steps.push({ phase: "pkg-run", ok: true, outputs: built.outputs });
245
+ log(` outputs=${built.outputs.length}`);
246
+
247
+ // ── Phase 7 ────────────────────────────────────────────────────────────
248
+ log(chalk.cyan(" [7/7] Write manifest"));
249
+ const manifests = writeManifests({
250
+ outputs: built.outputs,
251
+ cliRoot: pre.cliRoot,
252
+ gitCommit: pre.gitCommit,
253
+ gitDirty: pre.dirty,
254
+ targets,
255
+ ports: {
256
+ ws: parseInt(cliOpts.wsPort, 10),
257
+ ui: parseInt(cliOpts.uiPort, 10),
258
+ },
259
+ includeDb: cliOpts.includeDb !== false,
260
+ includeModels: Boolean(cliOpts.includeModels),
261
+ commands: [],
262
+ });
263
+ steps.push({ phase: "manifest", ok: true, count: manifests.length });
264
+
265
+ // Project manifest sidecar: <artifact>.project.json beside each exe.
266
+ if (project && manifests.length > 0) {
267
+ const projectManifest = {
268
+ schema: 1,
269
+ projectName: project.projectName,
270
+ configSha: project.configSha,
271
+ fileCount: project.fileCount,
272
+ bundledSkills: project.bundledSkills.map((s) => ({
273
+ name: s.name,
274
+ dir: s.dir,
275
+ })),
276
+ };
277
+ for (const { artifact } of manifests) {
278
+ fs.writeFileSync(
279
+ artifact + ".project.json",
280
+ JSON.stringify(projectManifest, null, 2),
281
+ "utf-8",
282
+ );
283
+ }
284
+ }
285
+
286
+ // ── Phase 8 (optional) ────────────────────────────────────────────────
287
+ // The `--no-smoke-test` flag and cross-target builds skip this: pkg can
288
+ // compile a linux-x64 artifact on Windows, but we can't execute it. We
289
+ // also skip when the artifact is for an OS/arch other than the host.
290
+ const hostTargetable = targets.filter((t) => isHostExecutable(t));
291
+ const smokeable = built.outputs.filter((o) =>
292
+ hostTargetable.some((t) => o.target === t),
293
+ );
294
+ if (cliOpts.smokeTest !== false && smokeable.length > 0) {
295
+ log(chalk.cyan(" [8/8] Smoke-test artifact"));
296
+ for (const out of smokeable) {
297
+ log(` probing: ${out.path}`);
298
+ try {
299
+ // Pick ports guaranteed not to clash with a user's running
300
+ // instance on the defaults 18800/18810. These flow into the
301
+ // spawned exe via CC_PACK_{UI,WS}_PORT env.
302
+ const res = await smokeTestExe({
303
+ exePath: out.path,
304
+ uiPort: 18951,
305
+ wsPort: 18950,
306
+ bundledSkillNames: project?.bundledSkills?.map((s) => s.name) ?? null,
307
+ logger,
308
+ });
309
+ steps.push({
310
+ phase: "smoke",
311
+ ok: true,
312
+ target: out.target,
313
+ uiStatus: res.uiStatus,
314
+ wsListening: res.wsListening,
315
+ });
316
+ } catch (e) {
317
+ steps.push({
318
+ phase: "smoke",
319
+ ok: false,
320
+ target: out.target,
321
+ error: e.message,
322
+ });
323
+ throw e;
324
+ }
325
+ }
326
+ } else if (cliOpts.smokeTest === false) {
327
+ log(chalk.dim(" [8/8] Smoke-test skipped (--no-smoke-test)"));
328
+ } else {
329
+ log(
330
+ chalk.dim(
331
+ ` [8/8] Smoke-test skipped — no host-executable target in ${targets.join(",")}`,
332
+ ),
333
+ );
334
+ }
335
+
336
+ // Optional cleanup of temp dir on success
337
+ try {
338
+ fs.rmSync(tempDir, { recursive: true, force: true });
339
+ } catch {
340
+ /* best effort */
341
+ }
342
+
343
+ // For UX, return the first artifact path
344
+ const first = manifests[0] || {};
345
+ return {
346
+ outputPath: first.artifact,
347
+ sha256: first.sha256,
348
+ manifests,
349
+ steps,
350
+ project, // null in CLI-only mode
351
+ };
352
+ }
353
+
354
+ /**
355
+ * Return true if a pkg target can actually run on the current host. We
356
+ * only smoke-test artifacts that match the host platform+arch — pkg can
357
+ * cross-compile to other OS/arch combos, but we have no way to execute
358
+ * them locally. The small shape of the target string (`nodeXX-<os>-<arch>`)
359
+ * makes a purely string comparison safe.
360
+ */
361
+ function isHostExecutable(target) {
362
+ const parts = String(target).split("-");
363
+ if (parts.length < 3) return false;
364
+ const [, os, arch] = parts;
365
+ const hostOs =
366
+ process.platform === "win32"
367
+ ? "win"
368
+ : process.platform === "darwin"
369
+ ? "macos"
370
+ : "linux";
371
+ const hostArch = process.arch; // 'x64' | 'arm64' | ...
372
+ // 'alpine' is linux with musl; still host-executable on a glibc host in
373
+ // practice for smoke tests, but skip to avoid false negatives.
374
+ if (os === "alpine") return false;
375
+ return os === hostOs && arch === hostArch;
376
+ }
377
+
378
+ function formatMB(bytes) {
379
+ return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
380
+ }
381
+
382
+ // Re-export the typed error so callers can introspect exit codes if needed.
383
+ export { PackError };
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Phase 7: manifest-writer
3
+ *
4
+ * Emit a sidecar `<artifact>.pack-manifest.json` next to each produced
5
+ * executable, plus a SHA-256 of the artifact bytes. Schema mirrors §6.3
6
+ * of the design doc.
7
+ */
8
+
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import crypto from "node:crypto";
12
+
13
+ /**
14
+ * @param {object} ctx
15
+ * @param {Array<string|{path:string, target?:string}>} ctx.outputs
16
+ * artifact paths or {path, target}
17
+ * objects (as returned by runPkg).
18
+ * @param {string} ctx.cliRoot
19
+ * @param {string|null} ctx.gitCommit
20
+ * @param {boolean} ctx.gitDirty
21
+ * @param {string[]} ctx.targets
22
+ * @param {object} ctx.ports { ws, ui }
23
+ * @param {boolean} ctx.includeDb
24
+ * @param {boolean} ctx.includeModels
25
+ * @param {string[]} ctx.commands
26
+ * @returns {Array<{artifact:string, manifestPath:string, sha256:string}>}
27
+ */
28
+ export function writeManifests(ctx) {
29
+ const cliPkg = JSON.parse(
30
+ fs.readFileSync(path.join(ctx.cliRoot, "package.json"), "utf-8"),
31
+ );
32
+ const rootPkg = readRootPackage(ctx.cliRoot);
33
+ const productVersion = rootPkg?.productVersion || "vDev";
34
+
35
+ const out = [];
36
+ for (const entry of ctx.outputs) {
37
+ const artifact = typeof entry === "string" ? entry : entry.path;
38
+ const sha256 = sha256File(artifact);
39
+ const manifest = {
40
+ schema: 1,
41
+ productVersion,
42
+ cliVersion: cliPkg.version,
43
+ buildTime: new Date().toISOString(),
44
+ gitCommit: ctx.gitCommit,
45
+ gitDirty: ctx.gitDirty,
46
+ buildHost: process.platform,
47
+ nodeVersion: process.version,
48
+ pkgVersion: lookupPkgVersion(ctx.cliRoot),
49
+ targets: ctx.targets,
50
+ ports: ctx.ports,
51
+ includeDb: ctx.includeDb,
52
+ includeModels: ctx.includeModels,
53
+ commands: ctx.commands || [],
54
+ sha256,
55
+ signed: false,
56
+ };
57
+ const manifestPath = artifact + ".pack-manifest.json";
58
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
59
+ out.push({ artifact, manifestPath, sha256 });
60
+ }
61
+ return out;
62
+ }
63
+
64
+ function sha256File(file) {
65
+ const hash = crypto.createHash("sha256");
66
+ hash.update(fs.readFileSync(file));
67
+ return hash.digest("hex");
68
+ }
69
+
70
+ function readRootPackage(cliRoot) {
71
+ // packages/cli -> ../../package.json
72
+ const rootPkgPath = path.resolve(cliRoot, "..", "..", "package.json");
73
+ try {
74
+ return JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ function lookupPkgVersion(cliRoot) {
81
+ for (const candidate of [
82
+ path.join(cliRoot, "node_modules", "@yao-pkg", "pkg", "package.json"),
83
+ path.join(cliRoot, "node_modules", "pkg", "package.json"),
84
+ ]) {
85
+ try {
86
+ const pkg = JSON.parse(fs.readFileSync(candidate, "utf-8"));
87
+ return `${pkg.name}@${pkg.version}`;
88
+ } catch {
89
+ /* not installed via this path */
90
+ }
91
+ }
92
+ return null;
93
+ }