opencode-ship 1.1.1 → 1.1.2-rc.1
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/CHANGELOG.md +49 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/dist/cli.js +379 -165
- package/dist/core.js +1 -1
- package/dist/plugin.js +1136 -109
- package/package.json +1 -1
- package/schema/ship-lock.schema.json +7 -18
- package/tests/plugin/expected-tools.mjs +13 -5
- package/tests/plugin/plugin-load.test.mjs +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// opencode-ship CLI v1.1.1
|
|
2
|
+
// opencode-ship CLI v1.1.2-rc.1
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __esm = (fn, res) => function __init() {
|
|
@@ -110,7 +110,7 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
110
110
|
var PACKAGE_VERSION, TEMPLATE_SET;
|
|
111
111
|
var init_version = __esm({
|
|
112
112
|
"src/version.js"() {
|
|
113
|
-
PACKAGE_VERSION = "1.1.1";
|
|
113
|
+
PACKAGE_VERSION = "1.1.2-rc.1";
|
|
114
114
|
TEMPLATE_SET = `v${PACKAGE_VERSION}`;
|
|
115
115
|
}
|
|
116
116
|
});
|
|
@@ -1145,6 +1145,34 @@ var init_root_config = __esm({
|
|
|
1145
1145
|
pointer: "/agent/build/permission/task/delivery-verifier",
|
|
1146
1146
|
strategy: "value",
|
|
1147
1147
|
value: "allow"
|
|
1148
|
+
},
|
|
1149
|
+
// Build -> ship-controller delegation so the deep plan/build/review
|
|
1150
|
+
// chain works with subagent_depth=2.
|
|
1151
|
+
{
|
|
1152
|
+
pointer: "/agent/build/permission/task/ship-controller",
|
|
1153
|
+
strategy: "value",
|
|
1154
|
+
value: "allow"
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
pointer: "/subagent_depth",
|
|
1158
|
+
strategy: "value",
|
|
1159
|
+
value: 2
|
|
1160
|
+
},
|
|
1161
|
+
// Build-tool permission ask/allow/deny surface.
|
|
1162
|
+
{
|
|
1163
|
+
pointer: "/agent/build/permission/ship_plan_approve",
|
|
1164
|
+
strategy: "value",
|
|
1165
|
+
value: "ask"
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
pointer: "/agent/build/permission/ship_resume",
|
|
1169
|
+
strategy: "value",
|
|
1170
|
+
value: "allow"
|
|
1171
|
+
},
|
|
1172
|
+
{
|
|
1173
|
+
pointer: "/agent/build/permission/ship_status",
|
|
1174
|
+
strategy: "value",
|
|
1175
|
+
value: "allow"
|
|
1148
1176
|
}
|
|
1149
1177
|
];
|
|
1150
1178
|
ROOT_PATH_CANDIDATES = ["opencode.json", "opencode.jsonc"];
|
|
@@ -1278,6 +1306,114 @@ var init_root_config = __esm({
|
|
|
1278
1306
|
}
|
|
1279
1307
|
});
|
|
1280
1308
|
|
|
1309
|
+
// src/installer/lock.js
|
|
1310
|
+
import { readFile as readFile4, writeFile as writeFile2, rename as rename2, mkdir as mkdir2 } from "node:fs/promises";
|
|
1311
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
1312
|
+
import { dirname as dirname4, resolve as resolve6 } from "node:path";
|
|
1313
|
+
function lockPath(repoRoot) {
|
|
1314
|
+
return resolve6(repoRoot, ".opencode", "ship.lock.json");
|
|
1315
|
+
}
|
|
1316
|
+
function computeIntegrity(lock) {
|
|
1317
|
+
const { integrity: _ignored, ...without } = lock ?? {};
|
|
1318
|
+
void _ignored;
|
|
1319
|
+
return {
|
|
1320
|
+
lockSha256: bytesHashString(stableStringify(without))
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
function normalizeLegacyLock(lock) {
|
|
1324
|
+
if (!lock || typeof lock !== "object") return lock;
|
|
1325
|
+
const { cleanupPending: _drop, ...rest } = lock;
|
|
1326
|
+
void _drop;
|
|
1327
|
+
return rest;
|
|
1328
|
+
}
|
|
1329
|
+
function validateLock(rawLock) {
|
|
1330
|
+
if (rawLock === null || rawLock === void 0) {
|
|
1331
|
+
return { ok: true, kind: "missing", issues: [] };
|
|
1332
|
+
}
|
|
1333
|
+
if (typeof rawLock !== "object" || Array.isArray(rawLock)) {
|
|
1334
|
+
return { ok: false, kind: "shape", issues: ["lock root must be an object"] };
|
|
1335
|
+
}
|
|
1336
|
+
const issues = [];
|
|
1337
|
+
let kind = "ok";
|
|
1338
|
+
if (rawLock.contractVersion !== CURRENT_LOCK_SCHEMA && rawLock.contractVersion !== 3 && rawLock.contractVersion !== 2 && rawLock.contractVersion !== 1) {
|
|
1339
|
+
issues.push(`unsupported contractVersion: ${JSON.stringify(rawLock.contractVersion)} (expected ${CURRENT_LOCK_SCHEMA}, 3, 2, or 1)`);
|
|
1340
|
+
kind = "schema";
|
|
1341
|
+
}
|
|
1342
|
+
const manager = rawLock.manager;
|
|
1343
|
+
if (manager === void 0) {
|
|
1344
|
+
issues.push("manager section missing");
|
|
1345
|
+
kind = kind === "ok" ? "shape" : kind;
|
|
1346
|
+
} else if (typeof manager !== "object" || manager === null) {
|
|
1347
|
+
issues.push("manager section must be an object");
|
|
1348
|
+
kind = kind === "ok" ? "shape" : kind;
|
|
1349
|
+
} else if (manager.schemaVersion !== CURRENT_LOCK_SCHEMA && manager.schemaVersion !== 3 && manager.schemaVersion !== 2 && manager.schemaVersion !== 1) {
|
|
1350
|
+
issues.push(`unsupported manager.schemaVersion: ${JSON.stringify(manager.schemaVersion)} (expected ${CURRENT_LOCK_SCHEMA}, 3, 2, or 1)`);
|
|
1351
|
+
kind = "schema";
|
|
1352
|
+
} else if (manager.name !== "opencode-ship") {
|
|
1353
|
+
issues.push(`unknown manager.name: ${JSON.stringify(manager.name)}`);
|
|
1354
|
+
kind = "shape";
|
|
1355
|
+
} else if (rawLock.contractVersion >= 2 && manager.schemaVersion >= 2 && manager.profile !== void 0 && manager.profile !== "core" && manager.profile !== "engineering") {
|
|
1356
|
+
issues.push(`invalid manager.profile: ${JSON.stringify(manager.profile)} (expected one of: engineering, core [legacy])`);
|
|
1357
|
+
kind = "shape";
|
|
1358
|
+
}
|
|
1359
|
+
if (!rawLock.files || !Array.isArray(rawLock.files)) {
|
|
1360
|
+
issues.push("files must be an array");
|
|
1361
|
+
kind = kind === "ok" ? "shape" : kind;
|
|
1362
|
+
}
|
|
1363
|
+
if (!rawLock.integrity || typeof rawLock.integrity !== "object") {
|
|
1364
|
+
issues.push("integrity section missing");
|
|
1365
|
+
kind = kind === "ok" ? "shape" : kind;
|
|
1366
|
+
} else {
|
|
1367
|
+
const expected = computeIntegrity(rawLock).lockSha256;
|
|
1368
|
+
if (expected !== rawLock.integrity.lockSha256) {
|
|
1369
|
+
issues.push(`integrity mismatch: stored ${rawLock.integrity.lockSha256} != computed ${expected}`);
|
|
1370
|
+
kind = "integrity";
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
return { ok: issues.length === 0, kind, issues };
|
|
1374
|
+
}
|
|
1375
|
+
async function readValidatedLock(repoRoot) {
|
|
1376
|
+
const path = lockPath(repoRoot);
|
|
1377
|
+
if (!existsSync8(path)) {
|
|
1378
|
+
return { kind: "missing", lock: null, issues: [] };
|
|
1379
|
+
}
|
|
1380
|
+
let raw;
|
|
1381
|
+
try {
|
|
1382
|
+
const text = await readFile4(path, "utf8");
|
|
1383
|
+
raw = JSON.parse(text);
|
|
1384
|
+
} catch (e) {
|
|
1385
|
+
return {
|
|
1386
|
+
kind: "integrity",
|
|
1387
|
+
lock: null,
|
|
1388
|
+
issues: [`unable to parse lock JSON: ${e?.message ?? String(e)}`]
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
const validation = validateLock(raw);
|
|
1392
|
+
if (validation.ok) {
|
|
1393
|
+
return {
|
|
1394
|
+
kind: validation.kind,
|
|
1395
|
+
lock: normalizeLegacyLock(raw),
|
|
1396
|
+
issues: []
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
return { kind: validation.kind, lock: null, issues: validation.issues };
|
|
1400
|
+
}
|
|
1401
|
+
function isSetupComplete(lock) {
|
|
1402
|
+
if (!lock || typeof lock !== "object") return false;
|
|
1403
|
+
const manager = lock.manager;
|
|
1404
|
+
if (!manager || typeof manager !== "object") return false;
|
|
1405
|
+
return manager.setupComplete === true;
|
|
1406
|
+
}
|
|
1407
|
+
var CURRENT_LOCK_SCHEMA;
|
|
1408
|
+
var init_lock = __esm({
|
|
1409
|
+
"src/installer/lock.js"() {
|
|
1410
|
+
init_hash();
|
|
1411
|
+
init_json_pointer();
|
|
1412
|
+
init_profile();
|
|
1413
|
+
CURRENT_LOCK_SCHEMA = 4;
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1281
1417
|
// src/installer/agent-renderer.js
|
|
1282
1418
|
var agent_renderer_exports = {};
|
|
1283
1419
|
__export(agent_renderer_exports, {
|
|
@@ -1346,18 +1482,116 @@ var init_agent_renderer = __esm({
|
|
|
1346
1482
|
}
|
|
1347
1483
|
});
|
|
1348
1484
|
|
|
1485
|
+
// src/installer/setup-pending.js
|
|
1486
|
+
import { existsSync as existsSync14, readFileSync as readFileSync5, unlinkSync, writeFile as writeFile7, mkdir as mkdirAsync } from "node:fs";
|
|
1487
|
+
import { promisify } from "node:util";
|
|
1488
|
+
import { resolve as resolve13, dirname as dirname9 } from "node:path";
|
|
1489
|
+
function setupPendingPath(repoRoot) {
|
|
1490
|
+
return resolve13(repoRoot, REL_PATH);
|
|
1491
|
+
}
|
|
1492
|
+
async function writeSetupPending(repoRoot, payload) {
|
|
1493
|
+
const path = setupPendingPath(repoRoot);
|
|
1494
|
+
await mkdirAsyncAsync(dirname9(path), { recursive: true });
|
|
1495
|
+
await writeFileAsync(path, JSON.stringify(payload, null, 2) + "\n", "utf8");
|
|
1496
|
+
}
|
|
1497
|
+
function clearSetupPending(repoRoot) {
|
|
1498
|
+
const path = setupPendingPath(repoRoot);
|
|
1499
|
+
if (!existsSync14(path)) return false;
|
|
1500
|
+
try {
|
|
1501
|
+
unlinkSync(path);
|
|
1502
|
+
return true;
|
|
1503
|
+
} catch {
|
|
1504
|
+
return false;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
var writeFileAsync, mkdirAsyncAsync, REL_PATH;
|
|
1508
|
+
var init_setup_pending = __esm({
|
|
1509
|
+
"src/installer/setup-pending.js"() {
|
|
1510
|
+
writeFileAsync = promisify(writeFile7);
|
|
1511
|
+
mkdirAsyncAsync = promisify(mkdirAsync);
|
|
1512
|
+
REL_PATH = ".opencode/ship.setup-pending.json";
|
|
1513
|
+
}
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
// src/installer/setup-state.js
|
|
1517
|
+
var setup_state_exports = {};
|
|
1518
|
+
__export(setup_state_exports, {
|
|
1519
|
+
SETUP_PENDING_REL_PATH: () => SETUP_PENDING_REL_PATH,
|
|
1520
|
+
SETUP_REQUIREMENTS: () => SETUP_REQUIREMENTS,
|
|
1521
|
+
modelsComplete: () => modelsComplete,
|
|
1522
|
+
setupComplete: () => setupComplete
|
|
1523
|
+
});
|
|
1524
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
1525
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
1526
|
+
import { resolve as resolve14, dirname as dirname10 } from "node:path";
|
|
1527
|
+
function modelsComplete(repoRoot, configValue) {
|
|
1528
|
+
if (configValue === void 0) return false;
|
|
1529
|
+
return hasCompletedModels(configValue);
|
|
1530
|
+
}
|
|
1531
|
+
async function setupComplete(repoRoot, configValue) {
|
|
1532
|
+
const missing = [];
|
|
1533
|
+
for (const rel of REQUIRED_DOCS) {
|
|
1534
|
+
const path = resolve14(repoRoot, rel);
|
|
1535
|
+
if (!existsSync15(path)) missing.push(rel);
|
|
1536
|
+
}
|
|
1537
|
+
const agentsPath = resolve14(repoRoot, "AGENTS.md");
|
|
1538
|
+
let agentsOk = false;
|
|
1539
|
+
if (existsSync15(agentsPath)) {
|
|
1540
|
+
try {
|
|
1541
|
+
const raw = await readFile10(agentsPath, "utf8");
|
|
1542
|
+
agentsOk = /##\s+Ship workflow\b/.test(raw);
|
|
1543
|
+
} catch {
|
|
1544
|
+
agentsOk = false;
|
|
1545
|
+
}
|
|
1546
|
+
if (!agentsOk) missing.push("AGENTS.md Ship workflow block");
|
|
1547
|
+
} else {
|
|
1548
|
+
missing.push("AGENTS.md");
|
|
1549
|
+
}
|
|
1550
|
+
const lockResult = await readValidatedLock(repoRoot);
|
|
1551
|
+
const cfgOk = modelsComplete(repoRoot, configValue);
|
|
1552
|
+
const markerPath = setupPendingPath(repoRoot);
|
|
1553
|
+
if (existsSync15(markerPath)) missing.push("setup-pending marker");
|
|
1554
|
+
const lockOk = lockResult.kind === "ok" || lockResult.kind === "missing";
|
|
1555
|
+
const ok = cfgOk && lockOk && missing.length === 0;
|
|
1556
|
+
return {
|
|
1557
|
+
ok,
|
|
1558
|
+
missing,
|
|
1559
|
+
config: { ok: cfgOk, lock: { ok: lockOk, setupComplete: isSetupComplete(lockResult.lock) } }
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
var REQUIRED_DOCS, SETUP_PENDING_REL_PATH, SETUP_REQUIREMENTS;
|
|
1563
|
+
var init_setup_state = __esm({
|
|
1564
|
+
"src/installer/setup-state.js"() {
|
|
1565
|
+
init_config();
|
|
1566
|
+
init_lock();
|
|
1567
|
+
init_setup_pending();
|
|
1568
|
+
REQUIRED_DOCS = [
|
|
1569
|
+
"docs/agents/issue-tracker.md",
|
|
1570
|
+
"docs/agents/domain.md",
|
|
1571
|
+
"docs/agents/triage-labels.md"
|
|
1572
|
+
];
|
|
1573
|
+
SETUP_PENDING_REL_PATH = ".opencode/ship.setup-pending.json";
|
|
1574
|
+
SETUP_REQUIREMENTS = Object.freeze({
|
|
1575
|
+
docs: REQUIRED_DOCS,
|
|
1576
|
+
agentFile: "AGENTS.md",
|
|
1577
|
+
markerPath: SETUP_PENDING_REL_PATH
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1349
1582
|
// src/installer/cli-args.js
|
|
1350
1583
|
init_profile();
|
|
1351
1584
|
var USAGE = `opencode-ship <command> [options]
|
|
1352
1585
|
|
|
1353
1586
|
Commands:
|
|
1354
|
-
init
|
|
1355
|
-
diff
|
|
1356
|
-
update
|
|
1357
|
-
doctor
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
--
|
|
1587
|
+
init Install managed files in this project. One-liner: pnpm dlx opencode-ship@latest init
|
|
1588
|
+
diff Show what would change without writing.
|
|
1589
|
+
update Apply pending updates after recovering the journal.
|
|
1590
|
+
doctor Validate environment, lock, and references.
|
|
1591
|
+
setup-complete Sole writer of lock.manager.setupComplete = true. Validates models + docs + AGENTS.md.
|
|
1592
|
+
uninstall Remove managed files that still match the lock.
|
|
1593
|
+
--version Print the version and exit.
|
|
1594
|
+
--help Show this usage and exit.
|
|
1361
1595
|
|
|
1362
1596
|
Options:
|
|
1363
1597
|
--root <path> Project root (defaults to cwd).
|
|
@@ -1372,8 +1606,10 @@ Options:
|
|
|
1372
1606
|
--final-reviewer-model <id> Final Standards + Spec reviewer model id (init only, optional).
|
|
1373
1607
|
--json Emit a JSON envelope instead of human output.
|
|
1374
1608
|
|
|
1375
|
-
After init succeeds, restart OpenCode and run /setup-ship-workflow
|
|
1376
|
-
|
|
1609
|
+
After init succeeds, restart OpenCode and run /setup-ship-workflow.
|
|
1610
|
+
The setup skill calls 'opencode-ship setup-complete' at the end so
|
|
1611
|
+
the lock.manager.setupComplete flag flips to true and the ship
|
|
1612
|
+
controller can dispatch.
|
|
1377
1613
|
`;
|
|
1378
1614
|
var MODEL_ID_RE = /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/;
|
|
1379
1615
|
function parseFlags(argv) {
|
|
@@ -1444,6 +1680,7 @@ function parseCommand(argv) {
|
|
|
1444
1680
|
case "diff":
|
|
1445
1681
|
case "update":
|
|
1446
1682
|
case "doctor":
|
|
1683
|
+
case "setup-complete":
|
|
1447
1684
|
case "uninstall":
|
|
1448
1685
|
return { command: cmd, options: flags };
|
|
1449
1686
|
default:
|
|
@@ -1457,7 +1694,7 @@ function helpText() {
|
|
|
1457
1694
|
// src/installer/commands/init.js
|
|
1458
1695
|
import { promisify as promisify2 } from "node:util";
|
|
1459
1696
|
import { writeFile as writeFile8, mkdir as mkdirAsync2 } from "node:fs/promises";
|
|
1460
|
-
import { dirname as
|
|
1697
|
+
import { dirname as dirname11, resolve as resolvePath } from "node:path";
|
|
1461
1698
|
|
|
1462
1699
|
// src/installer/executor.js
|
|
1463
1700
|
init_catalog();
|
|
@@ -2212,104 +2449,8 @@ async function planRootConfigApply({ repoRoot, lock, forceRepair, planMode = nul
|
|
|
2212
2449
|
});
|
|
2213
2450
|
}
|
|
2214
2451
|
|
|
2215
|
-
// src/installer/lock.js
|
|
2216
|
-
init_hash();
|
|
2217
|
-
init_json_pointer();
|
|
2218
|
-
init_profile();
|
|
2219
|
-
import { readFile as readFile4, writeFile as writeFile2, rename as rename2, mkdir as mkdir2 } from "node:fs/promises";
|
|
2220
|
-
import { existsSync as existsSync8 } from "node:fs";
|
|
2221
|
-
import { dirname as dirname4, resolve as resolve6 } from "node:path";
|
|
2222
|
-
var CURRENT_LOCK_SCHEMA = 4;
|
|
2223
|
-
function lockPath(repoRoot) {
|
|
2224
|
-
return resolve6(repoRoot, ".opencode", "ship.lock.json");
|
|
2225
|
-
}
|
|
2226
|
-
function computeIntegrity(lock) {
|
|
2227
|
-
const { integrity: _ignored, ...without } = lock ?? {};
|
|
2228
|
-
void _ignored;
|
|
2229
|
-
return {
|
|
2230
|
-
lockSha256: bytesHashString(stableStringify(without))
|
|
2231
|
-
};
|
|
2232
|
-
}
|
|
2233
|
-
function normalizeLegacyLock(lock) {
|
|
2234
|
-
if (!lock || typeof lock !== "object") return lock;
|
|
2235
|
-
const { cleanupPending: _drop, ...rest } = lock;
|
|
2236
|
-
void _drop;
|
|
2237
|
-
return rest;
|
|
2238
|
-
}
|
|
2239
|
-
function validateLock(rawLock) {
|
|
2240
|
-
if (rawLock === null || rawLock === void 0) {
|
|
2241
|
-
return { ok: true, kind: "missing", issues: [] };
|
|
2242
|
-
}
|
|
2243
|
-
if (typeof rawLock !== "object" || Array.isArray(rawLock)) {
|
|
2244
|
-
return { ok: false, kind: "shape", issues: ["lock root must be an object"] };
|
|
2245
|
-
}
|
|
2246
|
-
const issues = [];
|
|
2247
|
-
let kind = "ok";
|
|
2248
|
-
if (rawLock.contractVersion !== CURRENT_LOCK_SCHEMA && rawLock.contractVersion !== 3 && rawLock.contractVersion !== 2 && rawLock.contractVersion !== 1) {
|
|
2249
|
-
issues.push(`unsupported contractVersion: ${JSON.stringify(rawLock.contractVersion)} (expected ${CURRENT_LOCK_SCHEMA}, 3, 2, or 1)`);
|
|
2250
|
-
kind = "schema";
|
|
2251
|
-
}
|
|
2252
|
-
const manager = rawLock.manager;
|
|
2253
|
-
if (manager === void 0) {
|
|
2254
|
-
issues.push("manager section missing");
|
|
2255
|
-
kind = kind === "ok" ? "shape" : kind;
|
|
2256
|
-
} else if (typeof manager !== "object" || manager === null) {
|
|
2257
|
-
issues.push("manager section must be an object");
|
|
2258
|
-
kind = kind === "ok" ? "shape" : kind;
|
|
2259
|
-
} else if (manager.schemaVersion !== CURRENT_LOCK_SCHEMA && manager.schemaVersion !== 3 && manager.schemaVersion !== 2 && manager.schemaVersion !== 1) {
|
|
2260
|
-
issues.push(`unsupported manager.schemaVersion: ${JSON.stringify(manager.schemaVersion)} (expected ${CURRENT_LOCK_SCHEMA}, 3, 2, or 1)`);
|
|
2261
|
-
kind = "schema";
|
|
2262
|
-
} else if (manager.name !== "opencode-ship") {
|
|
2263
|
-
issues.push(`unknown manager.name: ${JSON.stringify(manager.name)}`);
|
|
2264
|
-
kind = "shape";
|
|
2265
|
-
} else if (rawLock.contractVersion >= 2 && manager.schemaVersion >= 2 && manager.profile !== void 0 && manager.profile !== "core" && manager.profile !== "engineering") {
|
|
2266
|
-
issues.push(`invalid manager.profile: ${JSON.stringify(manager.profile)} (expected one of: engineering, core [legacy])`);
|
|
2267
|
-
kind = "shape";
|
|
2268
|
-
}
|
|
2269
|
-
if (!rawLock.files || !Array.isArray(rawLock.files)) {
|
|
2270
|
-
issues.push("files must be an array");
|
|
2271
|
-
kind = kind === "ok" ? "shape" : kind;
|
|
2272
|
-
}
|
|
2273
|
-
if (!rawLock.integrity || typeof rawLock.integrity !== "object") {
|
|
2274
|
-
issues.push("integrity section missing");
|
|
2275
|
-
kind = kind === "ok" ? "shape" : kind;
|
|
2276
|
-
} else {
|
|
2277
|
-
const expected = computeIntegrity(rawLock).lockSha256;
|
|
2278
|
-
if (expected !== rawLock.integrity.lockSha256) {
|
|
2279
|
-
issues.push(`integrity mismatch: stored ${rawLock.integrity.lockSha256} != computed ${expected}`);
|
|
2280
|
-
kind = "integrity";
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
return { ok: issues.length === 0, kind, issues };
|
|
2284
|
-
}
|
|
2285
|
-
async function readValidatedLock(repoRoot) {
|
|
2286
|
-
const path = lockPath(repoRoot);
|
|
2287
|
-
if (!existsSync8(path)) {
|
|
2288
|
-
return { kind: "missing", lock: null, issues: [] };
|
|
2289
|
-
}
|
|
2290
|
-
let raw;
|
|
2291
|
-
try {
|
|
2292
|
-
const text = await readFile4(path, "utf8");
|
|
2293
|
-
raw = JSON.parse(text);
|
|
2294
|
-
} catch (e) {
|
|
2295
|
-
return {
|
|
2296
|
-
kind: "integrity",
|
|
2297
|
-
lock: null,
|
|
2298
|
-
issues: [`unable to parse lock JSON: ${e?.message ?? String(e)}`]
|
|
2299
|
-
};
|
|
2300
|
-
}
|
|
2301
|
-
const validation = validateLock(raw);
|
|
2302
|
-
if (validation.ok) {
|
|
2303
|
-
return {
|
|
2304
|
-
kind: validation.kind,
|
|
2305
|
-
lock: normalizeLegacyLock(raw),
|
|
2306
|
-
issues: []
|
|
2307
|
-
};
|
|
2308
|
-
}
|
|
2309
|
-
return { kind: validation.kind, lock: null, issues: validation.issues };
|
|
2310
|
-
}
|
|
2311
|
-
|
|
2312
2452
|
// src/installer/executor.js
|
|
2453
|
+
init_lock();
|
|
2313
2454
|
init_config();
|
|
2314
2455
|
init_hash();
|
|
2315
2456
|
init_json_pointer();
|
|
@@ -2459,8 +2600,11 @@ function detectProject(repoRoot = process.cwd()) {
|
|
|
2459
2600
|
|
|
2460
2601
|
// src/installer/executor.js
|
|
2461
2602
|
init_root_config();
|
|
2603
|
+
init_lock();
|
|
2462
2604
|
|
|
2463
2605
|
// src/installer/transaction.js
|
|
2606
|
+
init_lock();
|
|
2607
|
+
init_hash();
|
|
2464
2608
|
import {
|
|
2465
2609
|
writeFile as writeFile4,
|
|
2466
2610
|
rename as rename4,
|
|
@@ -2472,7 +2616,6 @@ import {
|
|
|
2472
2616
|
} from "node:fs/promises";
|
|
2473
2617
|
import { existsSync as existsSync11 } from "node:fs";
|
|
2474
2618
|
import { dirname as dirname6, resolve as resolve10, join as join5 } from "node:path";
|
|
2475
|
-
init_hash();
|
|
2476
2619
|
|
|
2477
2620
|
// src/state/git-common-dir.js
|
|
2478
2621
|
import { spawn } from "node:child_process";
|
|
@@ -2897,10 +3040,11 @@ async function rollback(lockDir, journal) {
|
|
|
2897
3040
|
}
|
|
2898
3041
|
|
|
2899
3042
|
// src/installer/migration.js
|
|
3043
|
+
init_lock();
|
|
3044
|
+
init_config();
|
|
2900
3045
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
2901
3046
|
import { existsSync as existsSync12 } from "node:fs";
|
|
2902
3047
|
import { resolve as resolve11 } from "node:path";
|
|
2903
|
-
init_config();
|
|
2904
3048
|
function legacyAdapterPath(repoRoot) {
|
|
2905
3049
|
return resolve11(repoRoot, ".opencode", "delivery.json");
|
|
2906
3050
|
}
|
|
@@ -3095,7 +3239,7 @@ async function previewInstall({ rootPath, profile = null, replaceManaged, forceC
|
|
|
3095
3239
|
});
|
|
3096
3240
|
const planMode = null;
|
|
3097
3241
|
const rootPlan = await planRootConfigApply({ repoRoot, lock, forceRepair: Boolean(forceRootConfig), planMode });
|
|
3098
|
-
const setupPending = resolved.profile === "engineering" && !
|
|
3242
|
+
const setupPending = resolved.profile === "engineering" && !hasCompletedModels(configValue);
|
|
3099
3243
|
const plan = [...filePlan ?? [], ...staleFilePlan, ...migrationPlan, configPlan, rootPlan];
|
|
3100
3244
|
const conflicts = plan.filter((p) => p && p.kind === "conflict");
|
|
3101
3245
|
const summary = summarise(plan);
|
|
@@ -3146,7 +3290,7 @@ function summarise(plan) {
|
|
|
3146
3290
|
}
|
|
3147
3291
|
return counts;
|
|
3148
3292
|
}
|
|
3149
|
-
async function assembleLock({ repoRoot, plan, lock, configPlan, rootPlan, profile = null, models = null }) {
|
|
3293
|
+
async function assembleLock({ repoRoot, plan, lock, configPlan, rootPlan, profile = null, models = null, fullSetupComplete = false }) {
|
|
3150
3294
|
const files = [];
|
|
3151
3295
|
const remain = lock?.files?.filter((f) => !plan.some((op) => op?.relPath === f.path)) ?? [];
|
|
3152
3296
|
for (const op of plan) {
|
|
@@ -3182,11 +3326,11 @@ async function assembleLock({ repoRoot, plan, lock, configPlan, rootPlan, profil
|
|
|
3182
3326
|
manager: {
|
|
3183
3327
|
schemaVersion: CURRENT_LOCK_SCHEMA,
|
|
3184
3328
|
name: "opencode-ship",
|
|
3185
|
-
version: "1.1.1",
|
|
3329
|
+
version: "1.1.2-rc.1",
|
|
3186
3330
|
templateSet: TEMPLATE_SET_ID,
|
|
3187
3331
|
profile: resolvedProfile,
|
|
3188
3332
|
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3189
|
-
setupComplete: completedModels,
|
|
3333
|
+
setupComplete: Boolean(fullSetupComplete) && completedModels,
|
|
3190
3334
|
config: {
|
|
3191
3335
|
path: ".opencode/ship.config.json",
|
|
3192
3336
|
sha256: configSha ?? lock?.manager?.config?.sha256 ?? "",
|
|
@@ -3201,7 +3345,7 @@ async function assembleLock({ repoRoot, plan, lock, configPlan, rootPlan, profil
|
|
|
3201
3345
|
files
|
|
3202
3346
|
};
|
|
3203
3347
|
}
|
|
3204
|
-
async function commitInstall(preview, { json, command }) {
|
|
3348
|
+
async function commitInstall(preview, { json, command, fullSetupComplete = false }) {
|
|
3205
3349
|
if (!preview.ok) {
|
|
3206
3350
|
return {
|
|
3207
3351
|
ok: false,
|
|
@@ -3237,7 +3381,8 @@ async function commitInstall(preview, { json, command }) {
|
|
|
3237
3381
|
lock: preview.lock,
|
|
3238
3382
|
configPlan,
|
|
3239
3383
|
rootPlan,
|
|
3240
|
-
profile: preview.profile?.profile
|
|
3384
|
+
profile: preview.profile?.profile,
|
|
3385
|
+
fullSetupComplete
|
|
3241
3386
|
});
|
|
3242
3387
|
const txPlan = await stageFiles(fileOnly, repoRoot);
|
|
3243
3388
|
if (configPlan && (configPlan.kind === "create" || configPlan.kind === "update")) {
|
|
@@ -3327,13 +3472,14 @@ function relativeTemplate(source) {
|
|
|
3327
3472
|
}
|
|
3328
3473
|
|
|
3329
3474
|
// src/installer/commands/doctor.js
|
|
3330
|
-
|
|
3331
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
3475
|
+
init_lock();
|
|
3332
3476
|
init_config();
|
|
3477
|
+
import { existsSync as existsSync16, readFileSync as readFileSync6 } from "node:fs";
|
|
3478
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
3333
3479
|
init_hash();
|
|
3334
3480
|
init_catalog();
|
|
3335
3481
|
init_root_config();
|
|
3336
|
-
import { resolve as
|
|
3482
|
+
import { resolve as resolve15 } from "node:path";
|
|
3337
3483
|
|
|
3338
3484
|
// src/installer/report.js
|
|
3339
3485
|
var REPORT_VERSION = 1;
|
|
@@ -3422,9 +3568,9 @@ function checkPackageIntegrity() {
|
|
|
3422
3568
|
function buildSourceHashIndex() {
|
|
3423
3569
|
const idx = /* @__PURE__ */ new Map();
|
|
3424
3570
|
for (const entry of CATALOG) {
|
|
3425
|
-
if (!
|
|
3571
|
+
if (!existsSync16(entry.source)) continue;
|
|
3426
3572
|
try {
|
|
3427
|
-
const buf =
|
|
3573
|
+
const buf = readFileSync6(entry.source, "utf8");
|
|
3428
3574
|
idx.set(entry.source, bytesHashString(buf));
|
|
3429
3575
|
} catch {
|
|
3430
3576
|
}
|
|
@@ -3435,13 +3581,13 @@ async function checkCatalogInstall(repoRoot, sourceHashes, profile, renderedAgen
|
|
|
3435
3581
|
const rows = [];
|
|
3436
3582
|
const scoped = profile ? filterCatalogByProfile(CATALOG, profile) : CATALOG;
|
|
3437
3583
|
for (const entry of scoped) {
|
|
3438
|
-
const target =
|
|
3439
|
-
if (!
|
|
3584
|
+
const target = resolve15(repoRoot, entry.path);
|
|
3585
|
+
if (!existsSync16(target)) {
|
|
3440
3586
|
rows.push(`${entry.id}: missing`);
|
|
3441
3587
|
continue;
|
|
3442
3588
|
}
|
|
3443
3589
|
try {
|
|
3444
|
-
const buf =
|
|
3590
|
+
const buf = readFileSync6(target, "utf8");
|
|
3445
3591
|
const actual = bytesHashString(buf);
|
|
3446
3592
|
const rendered = renderedAgentMap.get(entry.path);
|
|
3447
3593
|
const expected = rendered ? rendered.sha256 : sourceHashes.get(entry.source);
|
|
@@ -3497,12 +3643,12 @@ async function checkManagedHashes(repoRoot, validatedLock) {
|
|
|
3497
3643
|
const drift = [];
|
|
3498
3644
|
const renderedAgents = await loadRenderedAgentOverrides(repoRoot);
|
|
3499
3645
|
for (const entry of validatedLock.lock.files ?? []) {
|
|
3500
|
-
const p =
|
|
3501
|
-
if (!
|
|
3646
|
+
const p = resolve15(repoRoot, entry.path);
|
|
3647
|
+
if (!existsSync16(p)) {
|
|
3502
3648
|
drift.push(`missing:${entry.path}`);
|
|
3503
3649
|
continue;
|
|
3504
3650
|
}
|
|
3505
|
-
const buf =
|
|
3651
|
+
const buf = readFileSync6(p, "utf8");
|
|
3506
3652
|
const actual = bytesHashString(buf);
|
|
3507
3653
|
if (actual !== entry.sha256) drift.push(`drift:${entry.path}`);
|
|
3508
3654
|
}
|
|
@@ -3552,6 +3698,18 @@ async function checkRootConfig(repoRoot) {
|
|
|
3552
3698
|
detail: conflict ? `conflict on ${conflict.pointer}` : `applied=${r.applied.length}, skipped=${r.skipped.length}`
|
|
3553
3699
|
};
|
|
3554
3700
|
}
|
|
3701
|
+
async function checkSetupState(repoRoot, configValue) {
|
|
3702
|
+
const { setupComplete: setupComplete2 } = await Promise.resolve().then(() => (init_setup_state(), setup_state_exports));
|
|
3703
|
+
const state = await setupComplete2(repoRoot, configValue);
|
|
3704
|
+
if (state.ok) {
|
|
3705
|
+
return { name: "setup-complete", ok: true, detail: "models + docs + AGENTS.md all present" };
|
|
3706
|
+
}
|
|
3707
|
+
return {
|
|
3708
|
+
name: "setup-complete",
|
|
3709
|
+
ok: true,
|
|
3710
|
+
detail: `pending: ${state.missing.join(", ") || "(none)"}`
|
|
3711
|
+
};
|
|
3712
|
+
}
|
|
3555
3713
|
function writeEnvelope({ command, plan, summary, diagnostics, json, exitCode }) {
|
|
3556
3714
|
const conflicts = plan.filter((p) => p.kind === "conflict");
|
|
3557
3715
|
if (json) {
|
|
@@ -3599,7 +3757,8 @@ async function runDoctor({ rootPath, profile, json, writeOutput = true }) {
|
|
|
3599
3757
|
await checkConfig(repoRoot),
|
|
3600
3758
|
await checkManagedHashes(repoRoot, validatedLock),
|
|
3601
3759
|
await checkActiveProfileFootprint(repoRoot, validatedLock, resolved.profile),
|
|
3602
|
-
await checkRootConfig(repoRoot)
|
|
3760
|
+
await checkRootConfig(repoRoot),
|
|
3761
|
+
await checkSetupState(repoRoot, configValue)
|
|
3603
3762
|
];
|
|
3604
3763
|
const issues = checks.filter((c) => !c.ok).map((c) => `${c.name}: ${c.detail}`);
|
|
3605
3764
|
const plan = checks.map((c) => ({
|
|
@@ -3622,34 +3781,7 @@ async function runDoctor({ rootPath, profile, json, writeOutput = true }) {
|
|
|
3622
3781
|
// src/installer/commands/init.js
|
|
3623
3782
|
init_catalog();
|
|
3624
3783
|
init_config();
|
|
3625
|
-
|
|
3626
|
-
// src/installer/setup-pending.js
|
|
3627
|
-
import { existsSync as existsSync15, readFileSync as readFileSync6, unlinkSync, writeFile as writeFile7, mkdir as mkdirAsync } from "node:fs";
|
|
3628
|
-
import { promisify } from "node:util";
|
|
3629
|
-
import { resolve as resolve14, dirname as dirname9 } from "node:path";
|
|
3630
|
-
var writeFileAsync = promisify(writeFile7);
|
|
3631
|
-
var mkdirAsyncAsync = promisify(mkdirAsync);
|
|
3632
|
-
var REL_PATH = ".opencode/ship.setup-pending.json";
|
|
3633
|
-
function setupPendingPath(repoRoot) {
|
|
3634
|
-
return resolve14(repoRoot, REL_PATH);
|
|
3635
|
-
}
|
|
3636
|
-
async function writeSetupPending(repoRoot, payload) {
|
|
3637
|
-
const path = setupPendingPath(repoRoot);
|
|
3638
|
-
await mkdirAsyncAsync(dirname9(path), { recursive: true });
|
|
3639
|
-
await writeFileAsync(path, JSON.stringify(payload, null, 2) + "\n", "utf8");
|
|
3640
|
-
}
|
|
3641
|
-
function clearSetupPending(repoRoot) {
|
|
3642
|
-
const path = setupPendingPath(repoRoot);
|
|
3643
|
-
if (!existsSync15(path)) return false;
|
|
3644
|
-
try {
|
|
3645
|
-
unlinkSync(path);
|
|
3646
|
-
return true;
|
|
3647
|
-
} catch {
|
|
3648
|
-
return false;
|
|
3649
|
-
}
|
|
3650
|
-
}
|
|
3651
|
-
|
|
3652
|
-
// src/installer/commands/init.js
|
|
3784
|
+
init_setup_pending();
|
|
3653
3785
|
var writeFileAsync2 = promisify2(writeFile8);
|
|
3654
3786
|
var mkdirAsyncAsync2 = promisify2(mkdirAsync2);
|
|
3655
3787
|
async function runInit(options) {
|
|
@@ -3914,6 +4046,7 @@ async function runDiff(options) {
|
|
|
3914
4046
|
|
|
3915
4047
|
// src/installer/commands/update.js
|
|
3916
4048
|
init_catalog();
|
|
4049
|
+
init_setup_pending();
|
|
3917
4050
|
async function runUpdate(options) {
|
|
3918
4051
|
try {
|
|
3919
4052
|
validateCatalog();
|
|
@@ -3948,9 +4081,6 @@ async function runUpdate(options) {
|
|
|
3948
4081
|
return emitFailure2(3, "modified managed files; rerun with --replace-managed", options.json, "update");
|
|
3949
4082
|
}
|
|
3950
4083
|
const committed = await commitInstall(preview, { json: options.json, command: "update" });
|
|
3951
|
-
if (committed.extra?.exitCode === 0 && preview.repoRoot && !preview.setupPending) {
|
|
3952
|
-
clearSetupPending(preview.repoRoot);
|
|
3953
|
-
}
|
|
3954
4084
|
if (options.json) {
|
|
3955
4085
|
process.stdout.write(JSON.stringify({
|
|
3956
4086
|
reportVersion: 1,
|
|
@@ -3991,6 +4121,87 @@ function emitFailure2(code, message, json, command) {
|
|
|
3991
4121
|
return { ok: false, exitCode: code };
|
|
3992
4122
|
}
|
|
3993
4123
|
|
|
4124
|
+
// src/installer/commands/setup-complete.js
|
|
4125
|
+
init_config();
|
|
4126
|
+
init_setup_state();
|
|
4127
|
+
init_setup_pending();
|
|
4128
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
4129
|
+
import { resolve as resolve16 } from "node:path";
|
|
4130
|
+
async function runSetupComplete(options) {
|
|
4131
|
+
const repoRoot = options.rootPath ?? process.cwd();
|
|
4132
|
+
if (!existsSync17(resolve16(repoRoot, ".git"))) {
|
|
4133
|
+
return emitFailure3(2, "not a git repository", options.json);
|
|
4134
|
+
}
|
|
4135
|
+
clearSetupPending(repoRoot);
|
|
4136
|
+
const config = await loadConfig(repoRoot);
|
|
4137
|
+
const configValue = config?.ok ? config.value : null;
|
|
4138
|
+
const state = await setupComplete(repoRoot, configValue);
|
|
4139
|
+
const diagnostics = [];
|
|
4140
|
+
if (!state.config.ok) diagnostics.push("workflow.models incomplete");
|
|
4141
|
+
if (!state.config.lock.ok) diagnostics.push("lock is not v4 or fails integrity");
|
|
4142
|
+
if (state.missing.length > 0) {
|
|
4143
|
+
diagnostics.push(`missing: ${state.missing.join(", ")}`);
|
|
4144
|
+
}
|
|
4145
|
+
if (!state.ok) {
|
|
4146
|
+
return emitFailure3(6, `setup incomplete: ${diagnostics.join("; ")}`, options.json, { state });
|
|
4147
|
+
}
|
|
4148
|
+
const preview = await previewInstall({
|
|
4149
|
+
rootPath: repoRoot,
|
|
4150
|
+
profile: "engineering",
|
|
4151
|
+
replaceManaged: false,
|
|
4152
|
+
forceConfig: false,
|
|
4153
|
+
forceRootConfig: false
|
|
4154
|
+
});
|
|
4155
|
+
if (!preview.ok) {
|
|
4156
|
+
return emitFailure3(2, `preview failed: ${preview.error?.kind ?? "unknown"}`, options.json);
|
|
4157
|
+
}
|
|
4158
|
+
if (preview.conflicts.length > 0) {
|
|
4159
|
+
return emitFailure3(3, "managed files conflict; resolve with `opencode-ship update --replace-managed` first", options.json);
|
|
4160
|
+
}
|
|
4161
|
+
const committed = await commitInstall(preview, {
|
|
4162
|
+
json: options.json,
|
|
4163
|
+
command: "setup-complete",
|
|
4164
|
+
fullSetupComplete: true
|
|
4165
|
+
});
|
|
4166
|
+
if (committed.extra?.exitCode !== 0) {
|
|
4167
|
+
return emitFailure3(committed.extra?.exitCode ?? 1, "commit failed", options.json, { committed });
|
|
4168
|
+
}
|
|
4169
|
+
if (options.json) {
|
|
4170
|
+
process.stdout.write(JSON.stringify({
|
|
4171
|
+
reportVersion: 1,
|
|
4172
|
+
command: "setup-complete",
|
|
4173
|
+
status: "ok",
|
|
4174
|
+
setupComplete: true,
|
|
4175
|
+
requirements: SETUP_REQUIREMENTS,
|
|
4176
|
+
plan: serializePlan(committed.plan ?? []),
|
|
4177
|
+
summary: committed.summary ?? {},
|
|
4178
|
+
exitCode: 0
|
|
4179
|
+
}, null, 2) + "\n");
|
|
4180
|
+
} else {
|
|
4181
|
+
process.stdout.write("opencode-ship: setup complete\n");
|
|
4182
|
+
}
|
|
4183
|
+
process.exitCode = 0;
|
|
4184
|
+
return committed;
|
|
4185
|
+
}
|
|
4186
|
+
function emitFailure3(code, message, json, extra) {
|
|
4187
|
+
if (json) {
|
|
4188
|
+
process.stdout.write(JSON.stringify({
|
|
4189
|
+
reportVersion: 1,
|
|
4190
|
+
command: "setup-complete",
|
|
4191
|
+
status: "error",
|
|
4192
|
+
setupComplete: false,
|
|
4193
|
+
diagnostics: [message],
|
|
4194
|
+
...extra ?? {},
|
|
4195
|
+
exitCode: code
|
|
4196
|
+
}, null, 2) + "\n");
|
|
4197
|
+
} else {
|
|
4198
|
+
process.stdout.write(`opencode-ship: ${message}
|
|
4199
|
+
`);
|
|
4200
|
+
}
|
|
4201
|
+
process.exitCode = code;
|
|
4202
|
+
return { ok: false, exitCode: code };
|
|
4203
|
+
}
|
|
4204
|
+
|
|
3994
4205
|
// src/installer/commands/uninstall.js
|
|
3995
4206
|
init_config();
|
|
3996
4207
|
import { unlink as unlink4 } from "node:fs/promises";
|
|
@@ -3998,12 +4209,12 @@ async function runUninstall(options) {
|
|
|
3998
4209
|
const preview = await previewUninstall({ rootPath: options.rootPath });
|
|
3999
4210
|
if (!preview.ok) {
|
|
4000
4211
|
if (preview.error?.kind === "unsupported-lock-schema") {
|
|
4001
|
-
return
|
|
4212
|
+
return emitFailure4(5, `unsupported lock schema: ${(preview.error.issues ?? []).join("; ")}`, options.json);
|
|
4002
4213
|
}
|
|
4003
4214
|
if (preview.error?.kind === "lock-invalid") {
|
|
4004
|
-
return
|
|
4215
|
+
return emitFailure4(3, `lock invalid: ${(preview.error.issues ?? []).join("; ")}`, options.json);
|
|
4005
4216
|
}
|
|
4006
|
-
return
|
|
4217
|
+
return emitFailure4(2, preview.error?.kind ?? "invalid-project", options.json);
|
|
4007
4218
|
}
|
|
4008
4219
|
const { repoRoot, plan, conflicts, summary } = preview;
|
|
4009
4220
|
if (options.purgeConfig) {
|
|
@@ -4021,11 +4232,11 @@ async function runUninstall(options) {
|
|
|
4021
4232
|
}
|
|
4022
4233
|
const tx = await executePlan({ repoRoot, plan, newLockBuilder: null });
|
|
4023
4234
|
if (!tx.ok) {
|
|
4024
|
-
return
|
|
4235
|
+
return emitFailure4(4, tx.error?.message ?? "transaction failure", options.json);
|
|
4025
4236
|
}
|
|
4026
4237
|
return emitReport(plan, [], summary, options.json, 0, [tx.recovered ? "journal recovered before uninstall" : ""].filter(Boolean));
|
|
4027
4238
|
}
|
|
4028
|
-
function
|
|
4239
|
+
function emitFailure4(code, message, json) {
|
|
4029
4240
|
if (json) {
|
|
4030
4241
|
process.stdout.write(JSON.stringify({
|
|
4031
4242
|
reportVersion: 1,
|
|
@@ -4136,6 +4347,9 @@ ${helpText()}`);
|
|
|
4136
4347
|
case "doctor":
|
|
4137
4348
|
await runDoctor({ json: !!opts.json, rootPath: opts.rootPath, profile });
|
|
4138
4349
|
return;
|
|
4350
|
+
case "setup-complete":
|
|
4351
|
+
await runSetupComplete({ json: !!opts.json, rootPath: opts.rootPath });
|
|
4352
|
+
return;
|
|
4139
4353
|
case "uninstall":
|
|
4140
4354
|
await runUninstall({ json: !!opts.json, rootPath: opts.rootPath, profile, purgeConfig: !!opts.purgeConfig });
|
|
4141
4355
|
return;
|