chainlesschain 0.156.6 → 0.157.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 +26 -2
- package/bin/chainlesschain.js +13 -0
- package/package.json +3 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Dme4LGax.js → ActionButton-CCj9oE5_.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-B3-5BjRm.js → Analytics-RMqXlyHE.js} +2 -2
- package/src/assets/web-panel/assets/AppLayout-CSmBboZB.css +1 -0
- package/src/assets/web-panel/assets/AppLayout-CV6gWn1r.js +1 -0
- package/src/assets/web-panel/assets/{Backup-Cih5dXcD.js → Backup-bes7wE_k.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-wLmjCc9u.js → BaseInput-BKgAovqI.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CRfTuSl8.js → Chat-Duckao6i.js} +2 -2
- package/src/assets/web-panel/assets/{Checkbox-BfbEUJDW.js → Checkbox-BaBBUZnH.js} +1 -1
- package/src/assets/web-panel/assets/{Col-HJI40OzO.js → Col-B6iQKzFs.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-ADVAwcbQ.js → Compact-Fwt0CbI5.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BLRUoSoO.js → Cowork-DNy50_Cp.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DcNB5TYu.js → Cron-CBREJypB.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-p_Wuj0Un.js → Dashboard-DnJ1aZvj.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CrXGzreQ.js → Dropdown-B3vdFzOi.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-B97Dibo2.js → FormItemContext-RYn2zMn5.js} +1 -1
- package/src/assets/web-panel/assets/{Git-90CPsOOr.js → Git-7TolKe3c.js} +2 -2
- package/src/assets/web-panel/assets/KnowledgeGraph-4b9v3wYV.js +19 -0
- package/src/assets/web-panel/assets/KnowledgeGraph-U8ps3aGJ.css +1 -0
- package/src/assets/web-panel/assets/{Logs-0SXs6Eyx.js → Logs-BZgM3Q4b.js} +2 -2
- package/src/assets/web-panel/assets/{McpTools-VVSCkpV2.js → McpTools-5Wrif1R_.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-B_3zNQNB.js → Memory-D8YRnt51.js} +2 -2
- package/src/assets/web-panel/assets/{Notes-DNQz9UXh.js → Notes-CWTUG8hk.js} +2 -2
- package/src/assets/web-panel/assets/{Organization-CgXUnp-W.js → Organization-DvJzrIES.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BVsn6SM5.js → Overflow-tpvXbZkh.js} +1 -1
- package/src/assets/web-panel/assets/{OverrideContext-7M2Kv4Ru.js → OverrideContext-9ePmgwvW.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DGPIG-9j.js → P2P-C-AoKbTs.js} +2 -2
- package/src/assets/web-panel/assets/{Permissions-DIFqcnjU.js → Permissions-cIclKlQB.js} +3 -3
- package/src/assets/web-panel/assets/Portal-DXIqogG2.js +1 -0
- package/src/assets/web-panel/assets/{Projects-Bzn-dJ59.js → Projects-CDKdsdit.js} +2 -2
- package/src/assets/web-panel/assets/{Providers-mscN7CK5.js → Providers-C33u4Mka.js} +2 -2
- package/src/assets/web-panel/assets/{Row-BFUWxIkx.js → Row-DVscVTtp.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Dpa4h-q_.js → RssFeed-CGC4w7VD.js} +3 -3
- package/src/assets/web-panel/assets/{Security-DR6HKo_S.js → Security-ymR0We-P.js} +3 -3
- package/src/assets/web-panel/assets/{Services-CDh7r75R.js → Services-Gydt4uVC.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-VNikEgM4.js → Skeleton-CfLdvEpu.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-Dk9Cp1NG.js → Skills-C_SrPIFZ.js} +1 -1
- package/src/assets/web-panel/assets/Tasks-BchvT4YD.js +1 -0
- package/src/assets/web-panel/assets/{Templates-Ny_4GO6a.js → Templates-Bj1wDexb.js} +1 -1
- package/src/assets/web-panel/assets/Trigger-CIW_GVYA.js +1 -0
- package/src/assets/web-panel/assets/{VideoEditing-BNRFHgJ9.js → VideoEditing-CQOwjQFu.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BUfg4IAx.js → Wallet-0kSZ-ENs.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Cia89OyQ.js → WebAuthn-CrZlAr6l.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-C1OsMtqv.js → WorkflowEditor-DbdF8700.js} +1 -1
- package/src/assets/web-panel/assets/{chat-B2uGA8wN.js → chat-BNXQJRrX.js} +1 -1
- package/src/assets/web-panel/assets/{collapseMotion-DnZigkzG.js → collapseMotion-CSS8MlIE.js} +1 -1
- package/src/assets/web-panel/assets/{colors-C_wDMX2Q.js → colors-BgoKrIXh.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-C1ikzEN-.js → compact-item-COAuztwB.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-XExBTk9v.js → createContext-BTceykzK.js} +1 -1
- package/src/assets/web-panel/assets/{hasIn-mXvd_Kdq.js → hasIn-BNooGgXx.js} +1 -1
- package/src/assets/web-panel/assets/{icons-CpgFsfkd.js → icons-DzieZiVh.js} +4 -4
- package/src/assets/web-panel/assets/index-4C1c4hkZ.js +36 -0
- package/src/assets/web-panel/assets/{index-Dz6RDRcu.js → index-81NIDNcI.js} +2 -2
- package/src/assets/web-panel/assets/{index-a0qENb5U.js → index-B7gZm05C.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTle6zcb.js → index-BAPulPv1.js} +2 -2
- package/src/assets/web-panel/assets/{index-qtDQSqTG.js → index-BEbmwv5T.js} +2 -2
- package/src/assets/web-panel/assets/{index-D1eekAaa.js → index-BLzkplpf.js} +3 -3
- package/src/assets/web-panel/assets/{index-BBOVB9YK.js → index-BMU9I-F6.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6Hyy0Bc.js → index-BRBeeCRz.js} +2 -2
- package/src/assets/web-panel/assets/index-BbBjevdP.js +1 -0
- package/src/assets/web-panel/assets/{index-kLUQdSDJ.js → index-Bjf02LQY.js} +2 -2
- package/src/assets/web-panel/assets/index-Bn7dXbEh.js +1 -0
- package/src/assets/web-panel/assets/index-BqJS8Rje.js +1 -0
- package/src/assets/web-panel/assets/{index-LpE6Six-.js → index-C1BBkQIj.js} +4 -4
- package/src/assets/web-panel/assets/{index-CxwU-EjS.js → index-C4YPr8e8.js} +1 -1
- package/src/assets/web-panel/assets/{index-lPIeHtHE.js → index-C7uW3zj4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMcLOtIo.js → index-CZ3YetO8.js} +1 -1
- package/src/assets/web-panel/assets/{index-Du7KGlCP.js → index-Cf1n_3VC.js} +1 -1
- package/src/assets/web-panel/assets/{index-DwMlStra.js → index-CjOtB4wg.js} +3 -3
- package/src/assets/web-panel/assets/{index-DYLE4bnY.js → index-CpFrvhnj.js} +1 -1
- package/src/assets/web-panel/assets/{index-v4Oi0d0l.js → index-D1SCFE25.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOqmUcij.js → index-D5wFryiJ.js} +2 -2
- package/src/assets/web-panel/assets/{index-CbpKJ2W0.js → index-D6w1kIHg.js} +1 -1
- package/src/assets/web-panel/assets/{index-CttcpCq_.js → index-DC-nDpH_.js} +2 -2
- package/src/assets/web-panel/assets/index-DDgwb3KP.js +1 -0
- package/src/assets/web-panel/assets/index-DYDvGZbQ.js +1 -0
- package/src/assets/web-panel/assets/index-DbxkO9Uy.js +1 -0
- package/src/assets/web-panel/assets/index-DeN7D3FZ.js +1 -0
- package/src/assets/web-panel/assets/{index-DZjQgmBq.js → index-DnoNK5Gb.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_oSE2Nk.js → index-KEQgDCwj.js} +5 -5
- package/src/assets/web-panel/assets/{index-C53dnYiq.js → index-O-dqdn3q.js} +2 -2
- package/src/assets/web-panel/assets/{index-DJkIheU6.js → index-Z5CuKqcS.js} +1 -1
- package/src/assets/web-panel/assets/{index-fLUJs2Sr.js → index-Z9wFemG0.js} +2 -2
- package/src/assets/web-panel/assets/{index-D9tzxSFs.js → index-cF6gfPY3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BL27IhbN.js → index-eVLTVVvL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMYADk0v.js → index-g7FAcG7B.js} +1 -1
- package/src/assets/web-panel/assets/{index-BirLVqrC.js → index-lrfnKtkl.js} +1 -1
- package/src/assets/web-panel/assets/{index-jg5cpQg9.js → index-nw5SqpgZ.js} +2 -2
- package/src/assets/web-panel/assets/{index-BFFb9yPd.js → index-vHj3UTBK.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DOj2K4bh.js → initDefaultProps-_t-PG0bz.js} +1 -1
- package/src/assets/web-panel/assets/{motion-joGf7r-l.js → motion-DPnqtODq.js} +2 -2
- package/src/assets/web-panel/assets/{move-Cwb6tumJ.js → move-kO9NQRyb.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CPycjJ8C.js → omit-DD9MqVt0.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CnibXC3T.js → pickAttrs-D4u5AYq1.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DWcIO1y4.js → placementArrow-BUkUxlJc.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-C5giLhLf.js → responsiveObserve-DA_o8FHw.js} +1 -1
- package/src/assets/web-panel/assets/{slide-zwgmm7vM.js → slide-DSmAtCrK.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CK8tJSHq.js → statusUtils-Bn00xQ4D.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BzLSEXyu.js → styleChecker-phGTLjuN.js} +1 -1
- package/src/assets/web-panel/assets/{transition-D4AbuDdO.js → transition-exl3w1iN.js} +1 -1
- package/src/assets/web-panel/assets/{useConfigInject-ImjEZhXr.js → useConfigInject-C2E3Qsop.js} +2 -2
- package/src/assets/web-panel/assets/useFlexGapSupport-tlovsMBV.js +1 -0
- package/src/assets/web-panel/assets/{useMergedState-CXfbNKuO.js → useMergedState-DUMpRiCy.js} +1 -1
- package/src/assets/web-panel/assets/{useRefs-DwsdQTxa.js → useRefs-C8A7zAB_.js} +1 -1
- package/src/assets/web-panel/assets/{useState-DRbnp348.js → useState-CSbzOa8O.js} +1 -1
- package/src/assets/web-panel/assets/{vendor-C5RM7MZO.js → vendor-aH7YaPZi.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DAWimP6X.js → vnode-CIbqhcnZ.js} +1 -1
- package/src/assets/web-panel/assets/{ws-D-sl0vsW.js → ws-BTS8ehTm.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-u6SXbmzZ.js → zoom-Ck9WbYsZ.js} +1 -1
- package/src/assets/web-panel/index.html +3 -3
- package/src/commands/mtc.js +786 -0
- package/src/commands/pack.js +463 -0
- package/src/commands/ui.js +6 -0
- package/src/gateways/ws/ws-server.js +49 -5
- package/src/index.js +37 -1
- package/src/lib/governance-v2-helpers.js +109 -0
- package/src/lib/packer/config-template-builder.js +151 -0
- package/src/lib/packer/errors.js +30 -0
- package/src/lib/packer/index.js +383 -0
- package/src/lib/packer/manifest-writer.js +93 -0
- package/src/lib/packer/native-prebuild-collector.js +305 -0
- package/src/lib/packer/pack-update-applier.js +203 -0
- package/src/lib/packer/pack-update-checker.js +185 -0
- package/src/lib/packer/pack-update-downloader.js +162 -0
- package/src/lib/packer/pkg-config-generator.js +325 -0
- package/src/lib/packer/pkg-runner.js +193 -0
- package/src/lib/packer/precheck.js +273 -0
- package/src/lib/packer/project-assets-collector.js +0 -0
- package/src/lib/packer/smoke-runner.js +339 -0
- package/src/lib/packer/web-panel-builder.js +157 -0
- package/src/lib/web-ui-server.js +95 -2
- package/src/lib/ws-server.js +1 -0
- package/src/runtime/agent-runtime.js +1 -0
- package/src/runtime/policies/agent-policy.js +1 -0
- package/src/assets/web-panel/assets/AppLayout-DvVLRyPs.js +0 -1
- package/src/assets/web-panel/assets/AppLayout-nff99EgA.css +0 -1
- package/src/assets/web-panel/assets/Portal-CFB5Y97t.js +0 -1
- package/src/assets/web-panel/assets/Tasks-CfHL1NrP.js +0 -1
- package/src/assets/web-panel/assets/Trigger-C7MTh_xj.js +0 -1
- package/src/assets/web-panel/assets/index-1ZqkTPt2.js +0 -1
- package/src/assets/web-panel/assets/index-B-TI0cZ2.js +0 -1
- package/src/assets/web-panel/assets/index-B6P9mWuk.js +0 -1
- package/src/assets/web-panel/assets/index-BfncNR8d.js +0 -1
- package/src/assets/web-panel/assets/index-C5Zv4fBx.js +0 -1
- package/src/assets/web-panel/assets/index-DQgS_8Fd.js +0 -1
- package/src/assets/web-panel/assets/index-DaMG8ksh.js +0 -36
- package/src/assets/web-panel/assets/index-f4W8Sok0.js +0 -1
- package/src/assets/web-panel/assets/useFlexGapSupport-Cd-PoTMl.js +0 -1
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5a: cc pack check-update — manifest-based version check for packed exes.
|
|
3
|
+
*
|
|
4
|
+
* Unlike `cc update` (which runs `npm install -g chainlesschain@<v>`), a packed
|
|
5
|
+
* exe has no Node/npm to rely on. Instead, we publish a small JSON manifest at
|
|
6
|
+
* a known URL and have the exe fetch + compare + surface the diff. Download +
|
|
7
|
+
* self-replace are the job of Phase 5b/5c; this module only does the check.
|
|
8
|
+
*
|
|
9
|
+
* See docs/design/CC_PACK_打包指令设计文档.md §17.5-17.7.
|
|
10
|
+
*
|
|
11
|
+
* The expected manifest shape is:
|
|
12
|
+
* {
|
|
13
|
+
* schema: 1,
|
|
14
|
+
* channel: "stable" | "beta",
|
|
15
|
+
* latest: {
|
|
16
|
+
* cliVersion: "0.157.0",
|
|
17
|
+
* productVersion: "v5.0.3.0",
|
|
18
|
+
* publishedAt: "2026-04-25T00:00:00Z",
|
|
19
|
+
* releaseNotes: "https://…",
|
|
20
|
+
* artifacts: [{ target, url, sha256 }, …]
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* `target` matches pkg's `node20-<os>-<arch>` convention.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import semver from "semver";
|
|
28
|
+
|
|
29
|
+
const SUPPORTED_SCHEMA = 1;
|
|
30
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Fetch a manifest URL and compare against the current version.
|
|
34
|
+
*
|
|
35
|
+
* @param {object} ctx
|
|
36
|
+
* @param {string} ctx.manifestUrl absolute http(s) URL
|
|
37
|
+
* @param {string} ctx.currentVersion e.g. BAKED.packedCliVersion or VERSION
|
|
38
|
+
* @param {string} [ctx.target] e.g. "node20-win-x64"; if set, the
|
|
39
|
+
* returned artifact matches it
|
|
40
|
+
* @param {number} [ctx.timeoutMs=10000]
|
|
41
|
+
* @param {typeof fetch} [ctx.fetchImpl] injected for tests
|
|
42
|
+
* @returns {Promise<{
|
|
43
|
+
* updateAvailable: boolean,
|
|
44
|
+
* currentVersion: string,
|
|
45
|
+
* latestVersion: string,
|
|
46
|
+
* artifact: {target:string,url:string,sha256:string}|null,
|
|
47
|
+
* releaseNotes: string|null,
|
|
48
|
+
* channel: string,
|
|
49
|
+
* publishedAt: string|null,
|
|
50
|
+
* }>}
|
|
51
|
+
*/
|
|
52
|
+
export async function checkPackUpdate(ctx) {
|
|
53
|
+
const {
|
|
54
|
+
manifestUrl,
|
|
55
|
+
currentVersion,
|
|
56
|
+
target,
|
|
57
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
58
|
+
fetchImpl = fetch,
|
|
59
|
+
} = ctx;
|
|
60
|
+
|
|
61
|
+
if (!manifestUrl || typeof manifestUrl !== "string") {
|
|
62
|
+
throw new PackUpdateError("manifestUrl is required", "NO_MANIFEST_URL");
|
|
63
|
+
}
|
|
64
|
+
if (!currentVersion || typeof currentVersion !== "string") {
|
|
65
|
+
throw new PackUpdateError(
|
|
66
|
+
"currentVersion is required",
|
|
67
|
+
"NO_CURRENT_VERSION",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let body;
|
|
72
|
+
try {
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetchImpl(manifestUrl, {
|
|
77
|
+
headers: { Accept: "application/json" },
|
|
78
|
+
signal: controller.signal,
|
|
79
|
+
});
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new PackUpdateError(
|
|
82
|
+
`manifest fetch failed: HTTP ${response.status}`,
|
|
83
|
+
"FETCH_FAILED",
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
body = await response.text();
|
|
87
|
+
} finally {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
}
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if (err instanceof PackUpdateError) throw err;
|
|
92
|
+
const kind = err?.name === "AbortError" ? "TIMEOUT" : "NETWORK_ERROR";
|
|
93
|
+
throw new PackUpdateError(`manifest fetch failed: ${err.message}`, kind);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let manifest;
|
|
97
|
+
try {
|
|
98
|
+
manifest = JSON.parse(body);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
throw new PackUpdateError(
|
|
101
|
+
`manifest JSON parse failed: ${err.message}`,
|
|
102
|
+
"PARSE_FAILED",
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return parseAndCompare(manifest, { currentVersion, target });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Pure parser — separated so tests can pass a manifest object directly
|
|
111
|
+
* without stubbing fetch.
|
|
112
|
+
*
|
|
113
|
+
* @param {object} manifest
|
|
114
|
+
* @param {object} ctx
|
|
115
|
+
* @param {string} ctx.currentVersion
|
|
116
|
+
* @param {string} [ctx.target]
|
|
117
|
+
*/
|
|
118
|
+
export function parseAndCompare(manifest, ctx) {
|
|
119
|
+
const { currentVersion, target } = ctx;
|
|
120
|
+
|
|
121
|
+
if (!manifest || typeof manifest !== "object") {
|
|
122
|
+
throw new PackUpdateError("manifest must be an object", "SCHEMA_MISMATCH");
|
|
123
|
+
}
|
|
124
|
+
if (manifest.schema !== SUPPORTED_SCHEMA) {
|
|
125
|
+
throw new PackUpdateError(
|
|
126
|
+
`unsupported manifest schema ${manifest.schema} (expected ${SUPPORTED_SCHEMA})`,
|
|
127
|
+
"SCHEMA_MISMATCH",
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const latest = manifest.latest;
|
|
131
|
+
if (!latest || typeof latest.cliVersion !== "string") {
|
|
132
|
+
throw new PackUpdateError(
|
|
133
|
+
"manifest.latest.cliVersion missing",
|
|
134
|
+
"SCHEMA_MISMATCH",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const latestVersion = latest.cliVersion;
|
|
139
|
+
if (!semver.valid(latestVersion)) {
|
|
140
|
+
throw new PackUpdateError(
|
|
141
|
+
`manifest.latest.cliVersion "${latestVersion}" is not a valid semver`,
|
|
142
|
+
"INVALID_VERSION",
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
if (!semver.valid(currentVersion)) {
|
|
146
|
+
throw new PackUpdateError(
|
|
147
|
+
`currentVersion "${currentVersion}" is not a valid semver`,
|
|
148
|
+
"INVALID_VERSION",
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const updateAvailable = semver.gt(latestVersion, currentVersion);
|
|
153
|
+
|
|
154
|
+
let artifact = null;
|
|
155
|
+
if (target && Array.isArray(latest.artifacts)) {
|
|
156
|
+
artifact =
|
|
157
|
+
latest.artifacts.find(
|
|
158
|
+
(a) => a && typeof a.target === "string" && a.target === target,
|
|
159
|
+
) || null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
updateAvailable,
|
|
164
|
+
currentVersion,
|
|
165
|
+
latestVersion,
|
|
166
|
+
artifact,
|
|
167
|
+
releaseNotes:
|
|
168
|
+
typeof latest.releaseNotes === "string" ? latest.releaseNotes : null,
|
|
169
|
+
channel: typeof manifest.channel === "string" ? manifest.channel : "stable",
|
|
170
|
+
publishedAt:
|
|
171
|
+
typeof latest.publishedAt === "string" ? latest.publishedAt : null,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Typed error with a machine-readable `code`. The `cc pack check-update`
|
|
177
|
+
* command turns these into friendly messages and non-zero exit codes.
|
|
178
|
+
*/
|
|
179
|
+
export class PackUpdateError extends Error {
|
|
180
|
+
constructor(message, code) {
|
|
181
|
+
super(message);
|
|
182
|
+
this.name = "PackUpdateError";
|
|
183
|
+
this.code = code;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5b: stream a packed exe to disk and verify its SHA-256 against the
|
|
3
|
+
* manifest. Self-replacement is Phase 5c; this module just writes the new
|
|
4
|
+
* artifact next to the old one (conventionally `<exePath>.new`).
|
|
5
|
+
*
|
|
6
|
+
* Design notes:
|
|
7
|
+
* - Streaming, not buffered: artifacts are ~80-140 MB, so we write chunks
|
|
8
|
+
* straight to disk and hash incrementally. Node's fetch yields a web
|
|
9
|
+
* ReadableStream, which we consume via its async iterator.
|
|
10
|
+
* - Atomicity: we write to `<outputPath>.partial` and rename on success.
|
|
11
|
+
* A SHA mismatch or mid-stream failure deletes the partial so a retry
|
|
12
|
+
* starts from scratch.
|
|
13
|
+
* - No resume: first cut skips Range / If-Range. Large-artifact resume
|
|
14
|
+
* is a Phase 5d concern if it becomes painful.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import crypto from "node:crypto";
|
|
20
|
+
|
|
21
|
+
const SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {object} ctx
|
|
25
|
+
* @param {string} ctx.url absolute http(s) URL to the artifact
|
|
26
|
+
* @param {string} ctx.sha256 lowercase hex SHA-256 from the manifest
|
|
27
|
+
* @param {string} ctx.outputPath where the verified artifact lands
|
|
28
|
+
* @param {typeof fetch} [ctx.fetchImpl] injected for tests
|
|
29
|
+
* @param {(p:{bytes:number,total:number|null})=>void} [ctx.onProgress]
|
|
30
|
+
* fires as chunks arrive; total
|
|
31
|
+
* is null if Content-Length was
|
|
32
|
+
* absent or unparseable
|
|
33
|
+
* @returns {Promise<{outputPath:string,bytes:number,sha256:string}>}
|
|
34
|
+
*/
|
|
35
|
+
export async function downloadAndVerify(ctx) {
|
|
36
|
+
const { url, sha256, outputPath, fetchImpl = fetch, onProgress } = ctx;
|
|
37
|
+
|
|
38
|
+
if (!url || typeof url !== "string") {
|
|
39
|
+
throw new DownloadError("url is required", "NO_URL");
|
|
40
|
+
}
|
|
41
|
+
if (!sha256 || !SHA256_HEX.test(sha256)) {
|
|
42
|
+
throw new DownloadError(
|
|
43
|
+
`sha256 must be a 64-char lowercase hex string (got ${JSON.stringify(sha256)})`,
|
|
44
|
+
"BAD_SHA256",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (!outputPath || typeof outputPath !== "string") {
|
|
48
|
+
throw new DownloadError("outputPath is required", "NO_OUTPUT");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
52
|
+
const partialPath = outputPath + ".partial";
|
|
53
|
+
|
|
54
|
+
// Clean up any stale partial from a prior interrupted attempt. Ignoring
|
|
55
|
+
// ENOENT is fine — we just want to start with a blank slate.
|
|
56
|
+
try {
|
|
57
|
+
fs.unlinkSync(partialPath);
|
|
58
|
+
} catch {
|
|
59
|
+
/* no prior partial */
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let response;
|
|
63
|
+
try {
|
|
64
|
+
response = await fetchImpl(url, { headers: { Accept: "*/*" } });
|
|
65
|
+
} catch (err) {
|
|
66
|
+
throw new DownloadError(
|
|
67
|
+
`fetch failed: ${err.message}`,
|
|
68
|
+
err?.name === "AbortError" ? "TIMEOUT" : "NETWORK_ERROR",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new DownloadError(
|
|
73
|
+
`artifact fetch failed: HTTP ${response.status}`,
|
|
74
|
+
"FETCH_FAILED",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const totalRaw = response.headers?.get?.("content-length");
|
|
79
|
+
const total = totalRaw ? Number(totalRaw) : null;
|
|
80
|
+
const body = response.body;
|
|
81
|
+
if (!body) {
|
|
82
|
+
throw new DownloadError(
|
|
83
|
+
"response has no body stream (fetch impl returned no body)",
|
|
84
|
+
"NO_BODY",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const hasher = crypto.createHash("sha256");
|
|
89
|
+
const out = fs.createWriteStream(partialPath);
|
|
90
|
+
let bytes = 0;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Web ReadableStream exposes an async iterator in Node ≥18. We prefer
|
|
94
|
+
// that over pumping via pipe() because we need to see each chunk for
|
|
95
|
+
// hashing + progress, and we want the final rename deterministic.
|
|
96
|
+
for await (const chunk of body) {
|
|
97
|
+
// `chunk` is a Uint8Array
|
|
98
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
99
|
+
hasher.update(buf);
|
|
100
|
+
bytes += buf.length;
|
|
101
|
+
const ok = out.write(buf);
|
|
102
|
+
if (!ok) {
|
|
103
|
+
await new Promise((r) => out.once("drain", r));
|
|
104
|
+
}
|
|
105
|
+
if (typeof onProgress === "function") {
|
|
106
|
+
try {
|
|
107
|
+
onProgress({ bytes, total: Number.isFinite(total) ? total : null });
|
|
108
|
+
} catch {
|
|
109
|
+
/* progress callback errors must not interrupt the download */
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
await new Promise((resolve, reject) => {
|
|
114
|
+
out.end((err) => (err ? reject(err) : resolve()));
|
|
115
|
+
});
|
|
116
|
+
} catch (err) {
|
|
117
|
+
try {
|
|
118
|
+
out.destroy();
|
|
119
|
+
fs.unlinkSync(partialPath);
|
|
120
|
+
} catch {
|
|
121
|
+
/* best effort */
|
|
122
|
+
}
|
|
123
|
+
throw new DownloadError(`stream aborted: ${err.message}`, "STREAM_ERROR");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const actualSha = hasher.digest("hex");
|
|
127
|
+
if (actualSha !== sha256.toLowerCase()) {
|
|
128
|
+
try {
|
|
129
|
+
fs.unlinkSync(partialPath);
|
|
130
|
+
} catch {
|
|
131
|
+
/* best effort */
|
|
132
|
+
}
|
|
133
|
+
throw new DownloadError(
|
|
134
|
+
`SHA-256 mismatch: expected ${sha256}, got ${actualSha}`,
|
|
135
|
+
"SHA_MISMATCH",
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Atomic rename — on Windows this fails if the destination exists; delete
|
|
140
|
+
// first. On POSIX rename atomically replaces, but the extra unlink is
|
|
141
|
+
// harmless and keeps behavior symmetric across platforms.
|
|
142
|
+
try {
|
|
143
|
+
fs.unlinkSync(outputPath);
|
|
144
|
+
} catch {
|
|
145
|
+
/* no prior artifact */
|
|
146
|
+
}
|
|
147
|
+
fs.renameSync(partialPath, outputPath);
|
|
148
|
+
|
|
149
|
+
return { outputPath, bytes, sha256: actualSha };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Typed error with a machine-readable `code`. The `cc pack check-update
|
|
154
|
+
* --download` command turns these into friendly messages + exit codes.
|
|
155
|
+
*/
|
|
156
|
+
export class DownloadError extends Error {
|
|
157
|
+
constructor(message, code) {
|
|
158
|
+
super(message);
|
|
159
|
+
this.name = "DownloadError";
|
|
160
|
+
this.code = code;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5: pkg-config-generator
|
|
3
|
+
*
|
|
4
|
+
* Produce a transient pkg config (written next to a temp entry script) that
|
|
5
|
+
* tells @yao-pkg/pkg which scripts to compile and which assets to embed.
|
|
6
|
+
*
|
|
7
|
+
* The pkg config used here is the standard "pkg" key shape:
|
|
8
|
+
* {
|
|
9
|
+
* "scripts": ["src/**\/*.js"],
|
|
10
|
+
* "assets": ["src/assets/web-panel/**\/*", "templates/**\/*", "prebuilds/**\/*"],
|
|
11
|
+
* "targets": ["node20-win-x64"],
|
|
12
|
+
* "outputPath": "dist"
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* We do NOT mutate the real packages/cli/package.json — we write a synthesized
|
|
16
|
+
* package.json into the build temp dir that re-exports the bin and adds the
|
|
17
|
+
* "pkg" key. pkg is invoked against that file.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import fs from "node:fs";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {object} ctx
|
|
25
|
+
* @param {string} ctx.cliRoot packages/cli/
|
|
26
|
+
* @param {string} ctx.tempDir build temp dir
|
|
27
|
+
* @param {string} ctx.distDir web-panel dist absolute path
|
|
28
|
+
* @param {string|null} ctx.prebuildsDir prebuilds/ absolute path or null
|
|
29
|
+
* @param {string} ctx.templatesDir templates/ absolute path
|
|
30
|
+
* @param {string[]} ctx.targets
|
|
31
|
+
* @param {string} ctx.outputPath absolute output path (no ext)
|
|
32
|
+
* @param {boolean} ctx.compress
|
|
33
|
+
* @param {object} [ctx.runtime] defaults baked into the entry
|
|
34
|
+
* @param {string} [ctx.runtime.token] 'auto' = regenerate each run,
|
|
35
|
+
* '' = no auth, any other string
|
|
36
|
+
* is hardcoded.
|
|
37
|
+
* @param {string} [ctx.runtime.bindHost] default --host for `ui`
|
|
38
|
+
* @param {number} [ctx.runtime.wsPort] default --ws-port
|
|
39
|
+
* @param {number} [ctx.runtime.uiPort] default --port
|
|
40
|
+
* @param {object|null} [ctx.project] project-mode payload from
|
|
41
|
+
* project-assets-collector. When
|
|
42
|
+
* present, the entry script
|
|
43
|
+
* materializes the bundled
|
|
44
|
+
* .chainlesschain/ to a user-data
|
|
45
|
+
* dir and chdirs into it.
|
|
46
|
+
* @param {string} [ctx.project.projectDir] tempDir/project absolute path
|
|
47
|
+
* @param {string} [ctx.project.projectName]
|
|
48
|
+
* @param {string} [ctx.project.configSha]
|
|
49
|
+
* @param {string} [ctx.projectEntry] default subcommand for project
|
|
50
|
+
* mode (e.g. 'ui', 'chat'). If
|
|
51
|
+
* omitted, reads from the bundled
|
|
52
|
+
* config's `pack.entry` field.
|
|
53
|
+
* @param {boolean} [ctx.forceRefreshOnLaunch] re-materialize every launch
|
|
54
|
+
* @param {string|null} [ctx.updateManifestUrl] Phase 5a OTA manifest URL;
|
|
55
|
+
* baked into BAKED.updateManifestUrl
|
|
56
|
+
* and surfaced via `cc pack check-update`
|
|
57
|
+
* @returns {{ pkgConfigDir: string, pkgConfigFile: string, entryScript: string, projectMeta: object|null }}
|
|
58
|
+
*/
|
|
59
|
+
export function generatePkgConfig(ctx) {
|
|
60
|
+
const {
|
|
61
|
+
cliRoot,
|
|
62
|
+
tempDir,
|
|
63
|
+
distDir,
|
|
64
|
+
prebuildsDir,
|
|
65
|
+
templatesDir,
|
|
66
|
+
targets,
|
|
67
|
+
outputPath,
|
|
68
|
+
compress,
|
|
69
|
+
runtime = {},
|
|
70
|
+
project = null,
|
|
71
|
+
projectEntry,
|
|
72
|
+
forceRefreshOnLaunch = false,
|
|
73
|
+
updateManifestUrl = null,
|
|
74
|
+
} = ctx;
|
|
75
|
+
|
|
76
|
+
const bakedTokenMode =
|
|
77
|
+
typeof runtime.token === "string" ? runtime.token : "auto";
|
|
78
|
+
const bakedHost =
|
|
79
|
+
typeof runtime.bindHost === "string" && runtime.bindHost
|
|
80
|
+
? runtime.bindHost
|
|
81
|
+
: "127.0.0.1";
|
|
82
|
+
const bakedWsPort = Number.isFinite(runtime.wsPort)
|
|
83
|
+
? String(runtime.wsPort)
|
|
84
|
+
: "18800";
|
|
85
|
+
const bakedUiPort = Number.isFinite(runtime.uiPort)
|
|
86
|
+
? String(runtime.uiPort)
|
|
87
|
+
: "18810";
|
|
88
|
+
|
|
89
|
+
// ── Project mode: read pack.* from bundled config ─────────────────────────
|
|
90
|
+
let projectBaked = null;
|
|
91
|
+
if (project) {
|
|
92
|
+
const bundledConfigPath = path.join(
|
|
93
|
+
project.projectDir,
|
|
94
|
+
".chainlesschain",
|
|
95
|
+
"config.json",
|
|
96
|
+
);
|
|
97
|
+
let packConfig = {};
|
|
98
|
+
try {
|
|
99
|
+
const raw = JSON.parse(fs.readFileSync(bundledConfigPath, "utf-8"));
|
|
100
|
+
packConfig = raw.pack || {};
|
|
101
|
+
} catch {
|
|
102
|
+
/* already validated by Phase 3.5 — missing config falls back to defaults */
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const resolvedEntry =
|
|
106
|
+
typeof projectEntry === "string" && projectEntry.trim()
|
|
107
|
+
? projectEntry.trim()
|
|
108
|
+
: typeof packConfig.entry === "string" && packConfig.entry.trim()
|
|
109
|
+
? packConfig.entry.trim()
|
|
110
|
+
: "ui";
|
|
111
|
+
|
|
112
|
+
projectBaked = {
|
|
113
|
+
projectMode: true,
|
|
114
|
+
projectName: project.projectName,
|
|
115
|
+
projectEntry: resolvedEntry,
|
|
116
|
+
projectConfigSha: project.configSha,
|
|
117
|
+
projectAutoPersona:
|
|
118
|
+
typeof packConfig.autoPersona === "string"
|
|
119
|
+
? packConfig.autoPersona
|
|
120
|
+
: null,
|
|
121
|
+
projectAllowedSubcommands: Array.isArray(packConfig.allowedSubcommands)
|
|
122
|
+
? packConfig.allowedSubcommands
|
|
123
|
+
: ["ui", "chat", "agent", "skill"],
|
|
124
|
+
// Absolute path to the bundled .chainlesschain/ inside the pkg snapshot FS.
|
|
125
|
+
// At runtime, pkg surfaces assets at their original absolute paths.
|
|
126
|
+
projectBundledDir: posixify(
|
|
127
|
+
path.join(project.projectDir, ".chainlesschain"),
|
|
128
|
+
),
|
|
129
|
+
forceRefreshOnLaunch: Boolean(forceRefreshOnLaunch),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const pkgConfigDir = path.join(tempDir, "pkg-config");
|
|
134
|
+
fs.mkdirSync(pkgConfigDir, { recursive: true });
|
|
135
|
+
|
|
136
|
+
// Read the real CLI package.json to inherit version/dependencies
|
|
137
|
+
const realPkg = JSON.parse(
|
|
138
|
+
fs.readFileSync(path.join(cliRoot, "package.json"), "utf-8"),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Phase 5a: bake OTA manifest URL + the exact CLI version shipped in this
|
|
142
|
+
// artifact so `cc pack check-update` can compare against the manifest's
|
|
143
|
+
// latest.cliVersion without relying on the unpacked CLI's VERSION constant.
|
|
144
|
+
const bakedObj = {
|
|
145
|
+
tokenMode: bakedTokenMode,
|
|
146
|
+
host: bakedHost,
|
|
147
|
+
wsPort: bakedWsPort,
|
|
148
|
+
uiPort: bakedUiPort,
|
|
149
|
+
packedCliVersion: realPkg.version || null,
|
|
150
|
+
updateManifestUrl:
|
|
151
|
+
typeof updateManifestUrl === "string" && updateManifestUrl
|
|
152
|
+
? updateManifestUrl
|
|
153
|
+
: null,
|
|
154
|
+
...(projectBaked || {}),
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Inline the real bin's logic directly. We can't use `import('...')`
|
|
158
|
+
// because pkg's snapshot bootstrap does not register a dynamic-import
|
|
159
|
+
// callback (ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING). Static ESM
|
|
160
|
+
// imports compile fine via yao-pkg's ESM mode.
|
|
161
|
+
const entryScript = path.join(pkgConfigDir, "pack-entry.js");
|
|
162
|
+
const ensureUtf8Path = posixify(
|
|
163
|
+
path.join(cliRoot, "src", "lib", "ensure-utf8.js"),
|
|
164
|
+
);
|
|
165
|
+
const indexPath = posixify(path.join(cliRoot, "src", "index.js"));
|
|
166
|
+
fs.writeFileSync(
|
|
167
|
+
entryScript,
|
|
168
|
+
[
|
|
169
|
+
"#!/usr/bin/env node",
|
|
170
|
+
"// pkg entry — inlines the real CLI bin (see bin/chainlesschain.js).",
|
|
171
|
+
"// CC_PACK_MODE flips the WS layer to allow chat/agent over execute.",
|
|
172
|
+
"process.env.CC_PACK_MODE = '1';",
|
|
173
|
+
"import crypto from 'node:crypto';",
|
|
174
|
+
"import fs from 'node:fs';",
|
|
175
|
+
"import os from 'node:os';",
|
|
176
|
+
"import path from 'node:path';",
|
|
177
|
+
`import { ensureUtf8 } from '${ensureUtf8Path}';`,
|
|
178
|
+
`import { createProgram } from '${indexPath}';`,
|
|
179
|
+
"ensureUtf8();",
|
|
180
|
+
"// Build-time defaults baked by the packer. Each is overridable at",
|
|
181
|
+
"// runtime either via the matching env var or by passing the flag.",
|
|
182
|
+
`const BAKED = Object.freeze(${JSON.stringify(bakedObj)});`,
|
|
183
|
+
"// Expose BAKED on globalThis so downstream subcommands (e.g. `cc pack",
|
|
184
|
+
"// check-update`) can read packedCliVersion / updateManifestUrl without",
|
|
185
|
+
"// importing from the packer. No-op in CLI-only builds outside pkg.",
|
|
186
|
+
"globalThis.BAKED = BAKED;",
|
|
187
|
+
"// Merge-copy helper: copies src tree into dest, skipping files that",
|
|
188
|
+
"// already exist (preserves user modifications). Used only in project mode.",
|
|
189
|
+
"function copyRecursiveMerge(src, dest) {",
|
|
190
|
+
" if (!fs.existsSync(src)) return;",
|
|
191
|
+
" fs.mkdirSync(dest, { recursive: true });",
|
|
192
|
+
" for (const e of fs.readdirSync(src, { withFileTypes: true })) {",
|
|
193
|
+
" const sp = path.join(src, e.name), dp = path.join(dest, e.name);",
|
|
194
|
+
" if (e.isDirectory()) { copyRecursiveMerge(sp, dp); }",
|
|
195
|
+
" else if (!fs.existsSync(dp)) { fs.copyFileSync(sp, dp); }",
|
|
196
|
+
" else { console.warn('[cc-pack] Keeping existing file (user-modified):', dp); }",
|
|
197
|
+
" }",
|
|
198
|
+
"}",
|
|
199
|
+
"// ── Project mode: materialize bundled .chainlesschain/ to user-data dir ──",
|
|
200
|
+
"// BAKED.projectMode is absent (falsy) in CLI-only builds — entire block skipped.",
|
|
201
|
+
"if (BAKED.projectMode) {",
|
|
202
|
+
" const _userDataDir = path.join(",
|
|
203
|
+
" os.homedir(), '.chainlesschain-projects',",
|
|
204
|
+
" `${BAKED.projectName}-${BAKED.projectConfigSha.slice(0, 8)}`,",
|
|
205
|
+
" );",
|
|
206
|
+
" const _markerFile = path.join(_userDataDir, '.chainlesschain', '.pack-version');",
|
|
207
|
+
" const _needsMaterialize =",
|
|
208
|
+
" BAKED.forceRefreshOnLaunch ||",
|
|
209
|
+
" !fs.existsSync(_markerFile) ||",
|
|
210
|
+
" fs.readFileSync(_markerFile, 'utf8').trim() !== BAKED.projectConfigSha;",
|
|
211
|
+
" if (_needsMaterialize) {",
|
|
212
|
+
" copyRecursiveMerge(BAKED.projectBundledDir, path.join(_userDataDir, '.chainlesschain'));",
|
|
213
|
+
" fs.mkdirSync(path.dirname(_markerFile), { recursive: true });",
|
|
214
|
+
" fs.writeFileSync(_markerFile, BAKED.projectConfigSha, 'utf8');",
|
|
215
|
+
" }",
|
|
216
|
+
" process.env.CC_PROJECT_ROOT = _userDataDir;",
|
|
217
|
+
" if (BAKED.projectAllowedSubcommands) {",
|
|
218
|
+
" process.env.CC_PROJECT_ALLOWED_SUBCOMMANDS = BAKED.projectAllowedSubcommands.join(',');",
|
|
219
|
+
" }",
|
|
220
|
+
" // Phase 3b: expose the baked auto-persona via env so downstream",
|
|
221
|
+
" // consumers (skill-loader / persona resolver) can pick it up.",
|
|
222
|
+
" if (BAKED.projectAutoPersona) {",
|
|
223
|
+
" process.env.CC_PACK_AUTO_PERSONA = BAKED.projectAutoPersona;",
|
|
224
|
+
" }",
|
|
225
|
+
"}",
|
|
226
|
+
"// Double-click from Explorer arrives with no subcommand — without",
|
|
227
|
+
"// this default, commander prints help and exits, which looks like",
|
|
228
|
+
"// a black console 'flash and close'. In project mode the baked",
|
|
229
|
+
"// entry subcommand is used instead of the CLI-only 'ui' fallback.",
|
|
230
|
+
"const _rest = process.argv.slice(2);",
|
|
231
|
+
"const _hasSub = _rest.some((a) => a && !a.startsWith('-'));",
|
|
232
|
+
"const _argSet = new Set(_rest);",
|
|
233
|
+
"const _hasFlag = (...names) => names.some((n) => _argSet.has(n));",
|
|
234
|
+
"// Commander short-circuits --version / --help before running any",
|
|
235
|
+
"// command — don't pollute their stdout with the token banner and",
|
|
236
|
+
"// baked defaults they'd never see anyway.",
|
|
237
|
+
"const _shortCircuits = _hasFlag('-v', '--version', '-h', '--help');",
|
|
238
|
+
"if (!_hasSub && !_shortCircuits) {",
|
|
239
|
+
" const _entryCmd = BAKED.projectMode ? BAKED.projectEntry : 'ui';",
|
|
240
|
+
" for (const _p of _entryCmd.split(/\\s+/).filter(Boolean)) process.argv.push(_p);",
|
|
241
|
+
" if (_entryCmd.split(/\\s+/)[0] === 'ui') {",
|
|
242
|
+
" if (!_hasFlag('-p', '--port'))",
|
|
243
|
+
" process.argv.push('--port', process.env.CC_PACK_UI_PORT || BAKED.uiPort);",
|
|
244
|
+
" if (!_hasFlag('--ws-port'))",
|
|
245
|
+
" process.argv.push('--ws-port', process.env.CC_PACK_WS_PORT || BAKED.wsPort);",
|
|
246
|
+
" if (!_hasFlag('-H', '--host'))",
|
|
247
|
+
" process.argv.push('--host', process.env.CC_PACK_HOST || BAKED.host);",
|
|
248
|
+
" if (!_hasFlag('--token') && BAKED.tokenMode) {",
|
|
249
|
+
" let tok = process.env.CC_PACK_TOKEN || BAKED.tokenMode;",
|
|
250
|
+
" if (tok === 'auto') {",
|
|
251
|
+
" tok = crypto.randomBytes(16).toString('hex');",
|
|
252
|
+
" console.log('');",
|
|
253
|
+
" console.log(' \\u2139 Access token (auto-generated this run):');",
|
|
254
|
+
" console.log(' ' + tok);",
|
|
255
|
+
" console.log(' set CC_PACK_TOKEN=<value> to pin a stable token.');",
|
|
256
|
+
" console.log('');",
|
|
257
|
+
" }",
|
|
258
|
+
" process.argv.push('--token', tok);",
|
|
259
|
+
" }",
|
|
260
|
+
" }",
|
|
261
|
+
"}",
|
|
262
|
+
"// When launched from Explorer, Node exits as soon as the event loop",
|
|
263
|
+
"// drains. If something throws before the UI server starts listening,",
|
|
264
|
+
"// we want the user to see the error — pause on fatal errors.",
|
|
265
|
+
"process.on('uncaughtException', (err) => {",
|
|
266
|
+
" console.error('\\n[cc-pack] Fatal error:', err && err.stack || err);",
|
|
267
|
+
" if (process.stdin.isTTY) {",
|
|
268
|
+
" console.error('\\nPress any key to exit...');",
|
|
269
|
+
" try { process.stdin.setRawMode(true); process.stdin.resume(); process.stdin.once('data', () => process.exit(1)); } catch { process.exit(1); }",
|
|
270
|
+
" } else { process.exit(1); }",
|
|
271
|
+
"});",
|
|
272
|
+
"const program = createProgram();",
|
|
273
|
+
"program.parse(process.argv);",
|
|
274
|
+
"",
|
|
275
|
+
].join("\n"),
|
|
276
|
+
"utf-8",
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const assets = [
|
|
280
|
+
`${posixify(distDir)}/**/*`,
|
|
281
|
+
`${posixify(templatesDir)}/**/*`,
|
|
282
|
+
];
|
|
283
|
+
if (prebuildsDir) assets.push(`${posixify(prebuildsDir)}/**/*`);
|
|
284
|
+
// Project mode: bundle the collected .chainlesschain/ snapshot as an asset.
|
|
285
|
+
// At runtime, pkg surfaces it at its original absolute path in the snapshot FS.
|
|
286
|
+
if (project) assets.push(`${posixify(project.projectDir)}/**/*`);
|
|
287
|
+
|
|
288
|
+
const synthesizedPkg = {
|
|
289
|
+
name: realPkg.name + "-pack",
|
|
290
|
+
version: realPkg.version,
|
|
291
|
+
bin: "pack-entry.js",
|
|
292
|
+
type: "module",
|
|
293
|
+
pkg: {
|
|
294
|
+
scripts: [
|
|
295
|
+
// Glob covers the entire CLI source tree, since dependencies are
|
|
296
|
+
// resolved from cliRoot/node_modules.
|
|
297
|
+
`${posixify(cliRoot)}/src/**/*.js`,
|
|
298
|
+
`${posixify(cliRoot)}/src/**/*.cjs`,
|
|
299
|
+
`${posixify(cliRoot)}/bin/**/*.js`,
|
|
300
|
+
],
|
|
301
|
+
assets,
|
|
302
|
+
targets,
|
|
303
|
+
outputPath: path.dirname(outputPath),
|
|
304
|
+
compress: compress ? "GZip" : "None",
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const pkgConfigFile = path.join(pkgConfigDir, "package.json");
|
|
309
|
+
fs.writeFileSync(
|
|
310
|
+
pkgConfigFile,
|
|
311
|
+
JSON.stringify(synthesizedPkg, null, 2),
|
|
312
|
+
"utf-8",
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
pkgConfigDir,
|
|
317
|
+
pkgConfigFile,
|
|
318
|
+
entryScript,
|
|
319
|
+
projectMeta: projectBaked,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function posixify(p) {
|
|
324
|
+
return p.replace(/\\/g, "/");
|
|
325
|
+
}
|