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,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the "Governance V2" pattern used across 86+ surfaces
|
|
3
|
+
* (iter16–iter28 V2 ports) in packages/cli/src/lib/*.js.
|
|
4
|
+
*
|
|
5
|
+
* Every gov-V2 module declares roughly the same scaffolding:
|
|
6
|
+
* - two Maps (profiles + jobs/sessions/etc.)
|
|
7
|
+
* - four numeric caps (maxActivePerOwner, maxPendingPerProfile,
|
|
8
|
+
* idleMs, stuckMs) with matching get/set pairs
|
|
9
|
+
* - a positive-integer validator
|
|
10
|
+
* - 4-state and 5-state transition tables + validators
|
|
11
|
+
* - a _resetStateXxxGovV2() that re-initialises everything
|
|
12
|
+
*
|
|
13
|
+
* The shapes vary only in names and defaults, so these helpers let new
|
|
14
|
+
* (and migrated) modules replace ~50 lines of boilerplate with ~10.
|
|
15
|
+
*
|
|
16
|
+
* Migration guide: see GOV_V2_MIGRATION.md at repo root.
|
|
17
|
+
* `packages/cli` is an ESM package, so this helper exports ESM bindings.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validate that `n` is a finite positive integer. Throws with a label.
|
|
22
|
+
*/
|
|
23
|
+
function positiveInteger(n, label) {
|
|
24
|
+
const v = Math.floor(Number(n));
|
|
25
|
+
if (!Number.isFinite(v) || v <= 0) {
|
|
26
|
+
throw new Error(`${label} must be positive integer`);
|
|
27
|
+
}
|
|
28
|
+
return v;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build a `checkTransition(from, to)` fn from a state-transition map.
|
|
33
|
+
* Throws with a domain-specific message when the transition is invalid.
|
|
34
|
+
*
|
|
35
|
+
* @param {Map<string, Set<string>>} transitions
|
|
36
|
+
* @param {string} label e.g. "shgov profile"
|
|
37
|
+
*/
|
|
38
|
+
function createTransitionChecker(transitions, label) {
|
|
39
|
+
return function checkTransition(from, to) {
|
|
40
|
+
const allowed = transitions.get(from);
|
|
41
|
+
if (!allowed || !allowed.has(to)) {
|
|
42
|
+
throw new Error(`invalid ${label} transition ${from} → ${to}`);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build a suite of cap (limit) accessors wired to a single mutable state
|
|
49
|
+
* object. Returns an object with `get<Name>V2()` / `set<Name>V2(n)` pairs
|
|
50
|
+
* for every field in `defaults`. The set variants run `positiveInteger`
|
|
51
|
+
* validation and apply the new value; reset() re-applies the defaults.
|
|
52
|
+
*
|
|
53
|
+
* @param {Object<string, number>} defaults map of capName → default value
|
|
54
|
+
* @returns {{ caps, resetCaps, setters, getters }}
|
|
55
|
+
*/
|
|
56
|
+
function createCapRegistry(defaults) {
|
|
57
|
+
const caps = { ...defaults };
|
|
58
|
+
const setters = {};
|
|
59
|
+
const getters = {};
|
|
60
|
+
for (const [name, defaultValue] of Object.entries(defaults)) {
|
|
61
|
+
setters[name] = (n) => {
|
|
62
|
+
caps[name] = positiveInteger(n, name);
|
|
63
|
+
};
|
|
64
|
+
getters[name] = () => caps[name];
|
|
65
|
+
// sanity: reject non-numeric defaults
|
|
66
|
+
if (!Number.isFinite(defaultValue) || defaultValue <= 0) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`governance-v2-helpers: default for ${name} must be a positive number`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function resetCaps() {
|
|
73
|
+
for (const [name, defaultValue] of Object.entries(defaults)) {
|
|
74
|
+
caps[name] = defaultValue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { caps, resetCaps, setters, getters };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Count entries in a Map whose values satisfy `predicate`.
|
|
82
|
+
*/
|
|
83
|
+
function countBy(map, predicate) {
|
|
84
|
+
let c = 0;
|
|
85
|
+
for (const v of map.values()) {
|
|
86
|
+
if (predicate(v)) c++;
|
|
87
|
+
}
|
|
88
|
+
return c;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Build a state-transition Map from a plain object
|
|
93
|
+
* { active: ["paused", "archived"], paused: ["active"] }
|
|
94
|
+
*/
|
|
95
|
+
function buildTransitionMap(table) {
|
|
96
|
+
const m = new Map();
|
|
97
|
+
for (const [from, tos] of Object.entries(table)) {
|
|
98
|
+
m.set(from, new Set(tos));
|
|
99
|
+
}
|
|
100
|
+
return m;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
positiveInteger,
|
|
105
|
+
createTransitionChecker,
|
|
106
|
+
createCapRegistry,
|
|
107
|
+
countBy,
|
|
108
|
+
buildTransitionMap,
|
|
109
|
+
};
|
|
@@ -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 };
|