@pleri/olam-cli 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/auth-status.test.js +8 -7
- package/dist/__tests__/auth-status.test.js.map +1 -1
- package/dist/__tests__/help-output.test.d.ts +2 -0
- package/dist/__tests__/help-output.test.d.ts.map +1 -0
- package/dist/__tests__/help-output.test.js +74 -0
- package/dist/__tests__/help-output.test.js.map +1 -0
- package/dist/__tests__/image-presence.test.d.ts +2 -0
- package/dist/__tests__/image-presence.test.d.ts.map +1 -0
- package/dist/__tests__/image-presence.test.js +44 -0
- package/dist/__tests__/image-presence.test.js.map +1 -0
- package/dist/__tests__/protocol-version.test.d.ts +2 -0
- package/dist/__tests__/protocol-version.test.d.ts.map +1 -0
- package/dist/__tests__/protocol-version.test.js +170 -0
- package/dist/__tests__/protocol-version.test.js.map +1 -0
- package/dist/__tests__/registry-allowlist.test.d.ts +2 -0
- package/dist/__tests__/registry-allowlist.test.d.ts.map +1 -0
- package/dist/__tests__/registry-allowlist.test.js +129 -0
- package/dist/__tests__/registry-allowlist.test.js.map +1 -0
- package/dist/commands/__tests__/crystallize.test.d.ts +2 -0
- package/dist/commands/__tests__/crystallize.test.d.ts.map +1 -0
- package/dist/commands/__tests__/crystallize.test.js +133 -0
- package/dist/commands/__tests__/crystallize.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.all-three.test.d.ts +19 -0
- package/dist/commands/__tests__/upgrade.all-three.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.all-three.test.js +92 -0
- package/dist/commands/__tests__/upgrade.all-three.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.history.test.d.ts +15 -0
- package/dist/commands/__tests__/upgrade.history.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.history.test.js +199 -0
- package/dist/commands/__tests__/upgrade.history.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.lock.test.d.ts +15 -0
- package/dist/commands/__tests__/upgrade.lock.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.lock.test.js +253 -0
- package/dist/commands/__tests__/upgrade.lock.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.olam-tag.test.d.ts +21 -0
- package/dist/commands/__tests__/upgrade.olam-tag.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.olam-tag.test.js +127 -0
- package/dist/commands/__tests__/upgrade.olam-tag.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.poll.test.d.ts +14 -0
- package/dist/commands/__tests__/upgrade.poll.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.poll.test.js +136 -0
- package/dist/commands/__tests__/upgrade.poll.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.recreate.test.d.ts +17 -0
- package/dist/commands/__tests__/upgrade.recreate.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.recreate.test.js +95 -0
- package/dist/commands/__tests__/upgrade.recreate.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.rollback.test.d.ts +12 -0
- package/dist/commands/__tests__/upgrade.rollback.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.rollback.test.js +275 -0
- package/dist/commands/__tests__/upgrade.rollback.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.sha-capture.test.d.ts +12 -0
- package/dist/commands/__tests__/upgrade.sha-capture.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.sha-capture.test.js +63 -0
- package/dist/commands/__tests__/upgrade.sha-capture.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.smoke.test.d.ts +19 -0
- package/dist/commands/__tests__/upgrade.smoke.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.smoke.test.js +101 -0
- package/dist/commands/__tests__/upgrade.smoke.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.swap.test.d.ts +19 -0
- package/dist/commands/__tests__/upgrade.swap.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.swap.test.js +333 -0
- package/dist/commands/__tests__/upgrade.swap.test.js.map +1 -0
- package/dist/commands/auth-status.d.ts +8 -1
- package/dist/commands/auth-status.d.ts.map +1 -1
- package/dist/commands/auth-status.js +2 -1
- package/dist/commands/auth-status.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +31 -0
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/crystallize.d.ts +11 -1
- package/dist/commands/crystallize.d.ts.map +1 -1
- package/dist/commands/crystallize.js +32 -8
- package/dist/commands/crystallize.js.map +1 -1
- package/dist/commands/upgrade-history.d.ts +17 -0
- package/dist/commands/upgrade-history.d.ts.map +1 -0
- package/dist/commands/upgrade-history.js +40 -0
- package/dist/commands/upgrade-history.js.map +1 -0
- package/dist/commands/upgrade-lock.d.ts +102 -0
- package/dist/commands/upgrade-lock.d.ts.map +1 -0
- package/dist/commands/upgrade-lock.js +225 -0
- package/dist/commands/upgrade-lock.js.map +1 -0
- package/dist/commands/upgrade-log.d.ts +86 -0
- package/dist/commands/upgrade-log.d.ts.map +1 -0
- package/dist/commands/upgrade-log.js +146 -0
- package/dist/commands/upgrade-log.js.map +1 -0
- package/dist/commands/upgrade.d.ts +265 -0
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +840 -10
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/exit-codes.d.ts +35 -0
- package/dist/exit-codes.d.ts.map +1 -0
- package/dist/exit-codes.js +35 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/image-presence.d.ts +40 -0
- package/dist/image-presence.d.ts.map +1 -0
- package/dist/image-presence.js +39 -0
- package/dist/image-presence.js.map +1 -0
- package/dist/index.js +1058 -168
- package/dist/index.js.map +1 -1
- package/dist/pleri-config.d.ts +22 -0
- package/dist/pleri-config.d.ts.map +1 -0
- package/dist/pleri-config.js +42 -0
- package/dist/pleri-config.js.map +1 -0
- package/dist/protocol-version.d.ts +79 -0
- package/dist/protocol-version.d.ts.map +1 -0
- package/dist/protocol-version.js +133 -0
- package/dist/protocol-version.js.map +1 -0
- package/dist/registry-allowlist.d.ts +47 -0
- package/dist/registry-allowlist.d.ts.map +1 -0
- package/dist/registry-allowlist.js +67 -0
- package/dist/registry-allowlist.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -421,8 +421,8 @@ var init_parseUtil = __esm({
|
|
|
421
421
|
init_errors();
|
|
422
422
|
init_en();
|
|
423
423
|
makeIssue = (params) => {
|
|
424
|
-
const { data, path:
|
|
425
|
-
const fullPath = [...
|
|
424
|
+
const { data, path: path35, errorMaps, issueData } = params;
|
|
425
|
+
const fullPath = [...path35, ...issueData.path || []];
|
|
426
426
|
const fullIssue = {
|
|
427
427
|
...issueData,
|
|
428
428
|
path: fullPath
|
|
@@ -730,11 +730,11 @@ var init_types = __esm({
|
|
|
730
730
|
init_parseUtil();
|
|
731
731
|
init_util();
|
|
732
732
|
ParseInputLazyPath = class {
|
|
733
|
-
constructor(parent, value,
|
|
733
|
+
constructor(parent, value, path35, key) {
|
|
734
734
|
this._cachedPath = [];
|
|
735
735
|
this.parent = parent;
|
|
736
736
|
this.data = value;
|
|
737
|
-
this._path =
|
|
737
|
+
this._path = path35;
|
|
738
738
|
this._key = key;
|
|
739
739
|
}
|
|
740
740
|
get path() {
|
|
@@ -4221,7 +4221,7 @@ import YAML from "yaml";
|
|
|
4221
4221
|
function bootstrapStepCmd(entry) {
|
|
4222
4222
|
return typeof entry === "string" ? entry : entry.cmd;
|
|
4223
4223
|
}
|
|
4224
|
-
function refineForbiddenKeys(value,
|
|
4224
|
+
function refineForbiddenKeys(value, path35, ctx, rejectSource) {
|
|
4225
4225
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
4226
4226
|
return;
|
|
4227
4227
|
}
|
|
@@ -4229,12 +4229,12 @@ function refineForbiddenKeys(value, path32, ctx, rejectSource) {
|
|
|
4229
4229
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
4230
4230
|
ctx.addIssue({
|
|
4231
4231
|
code: external_exports.ZodIssueCode.custom,
|
|
4232
|
-
path: [...
|
|
4232
|
+
path: [...path35, key],
|
|
4233
4233
|
message: `forbidden key "${key}" (prototype-pollution surface)`
|
|
4234
4234
|
});
|
|
4235
4235
|
continue;
|
|
4236
4236
|
}
|
|
4237
|
-
if (rejectSource &&
|
|
4237
|
+
if (rejectSource && path35.length === 0 && key === "source") {
|
|
4238
4238
|
ctx.addIssue({
|
|
4239
4239
|
code: external_exports.ZodIssueCode.custom,
|
|
4240
4240
|
path: ["source"],
|
|
@@ -4244,30 +4244,30 @@ function refineForbiddenKeys(value, path32, ctx, rejectSource) {
|
|
|
4244
4244
|
}
|
|
4245
4245
|
refineForbiddenKeys(
|
|
4246
4246
|
value[key],
|
|
4247
|
-
[...
|
|
4247
|
+
[...path35, key],
|
|
4248
4248
|
ctx,
|
|
4249
4249
|
false
|
|
4250
4250
|
);
|
|
4251
4251
|
}
|
|
4252
4252
|
}
|
|
4253
|
-
function rejectForbiddenKeys(value,
|
|
4253
|
+
function rejectForbiddenKeys(value, path35, rejectSource) {
|
|
4254
4254
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
4255
4255
|
return;
|
|
4256
4256
|
}
|
|
4257
4257
|
for (const key of Object.keys(value)) {
|
|
4258
4258
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
4259
4259
|
throw new Error(
|
|
4260
|
-
`[manifest] ${
|
|
4260
|
+
`[manifest] ${path35}: forbidden key "${key}" (prototype-pollution surface)`
|
|
4261
4261
|
);
|
|
4262
4262
|
}
|
|
4263
4263
|
if (rejectSource && key === "source") {
|
|
4264
4264
|
throw new Error(
|
|
4265
|
-
`[manifest] ${
|
|
4265
|
+
`[manifest] ${path35}: top-level "source" is loader-stamped \u2014 manifests must not author it`
|
|
4266
4266
|
);
|
|
4267
4267
|
}
|
|
4268
4268
|
rejectForbiddenKeys(
|
|
4269
4269
|
value[key],
|
|
4270
|
-
`${
|
|
4270
|
+
`${path35}.${key}`,
|
|
4271
4271
|
false
|
|
4272
4272
|
);
|
|
4273
4273
|
}
|
|
@@ -5208,8 +5208,8 @@ var init_client = __esm({
|
|
|
5208
5208
|
throw new Error(`failed to report rate-limit for ${accountId} (HTTP ${res.status})`);
|
|
5209
5209
|
}
|
|
5210
5210
|
}
|
|
5211
|
-
async request(method,
|
|
5212
|
-
const url = `${this.baseUrl}${
|
|
5211
|
+
async request(method, path35, body, attempt = 0) {
|
|
5212
|
+
const url = `${this.baseUrl}${path35}`;
|
|
5213
5213
|
const controller = new AbortController();
|
|
5214
5214
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
5215
5215
|
const headers = {};
|
|
@@ -5225,7 +5225,7 @@ var init_client = __esm({
|
|
|
5225
5225
|
} catch (err) {
|
|
5226
5226
|
if (attempt < RETRY_COUNT && isTransient(err)) {
|
|
5227
5227
|
await sleep(RETRY_BACKOFF_MS * (attempt + 1));
|
|
5228
|
-
return this.request(method,
|
|
5228
|
+
return this.request(method, path35, body, attempt + 1);
|
|
5229
5229
|
}
|
|
5230
5230
|
throw err;
|
|
5231
5231
|
} finally {
|
|
@@ -6676,8 +6676,8 @@ var init_provider3 = __esm({
|
|
|
6676
6676
|
// -----------------------------------------------------------------------
|
|
6677
6677
|
// Internal fetch helper
|
|
6678
6678
|
// -----------------------------------------------------------------------
|
|
6679
|
-
async request(
|
|
6680
|
-
const url = `${this.config.workerUrl}${
|
|
6679
|
+
async request(path35, method, body) {
|
|
6680
|
+
const url = `${this.config.workerUrl}${path35}`;
|
|
6681
6681
|
const bearer = await this.config.mintToken();
|
|
6682
6682
|
const headers = {
|
|
6683
6683
|
Authorization: `Bearer ${bearer}`
|
|
@@ -7790,8 +7790,8 @@ import { execFileSync as execFileSync3 } from "node:child_process";
|
|
|
7790
7790
|
import * as fs13 from "node:fs";
|
|
7791
7791
|
import * as os9 from "node:os";
|
|
7792
7792
|
import * as path14 from "node:path";
|
|
7793
|
-
function expandHome(p,
|
|
7794
|
-
return p.replace(/^~(?=$|\/|\\)/,
|
|
7793
|
+
function expandHome(p, homedir17) {
|
|
7794
|
+
return p.replace(/^~(?=$|\/|\\)/, homedir17());
|
|
7795
7795
|
}
|
|
7796
7796
|
function sanitizeRepoFilename(name) {
|
|
7797
7797
|
const sanitized = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
@@ -7812,7 +7812,7 @@ ${stderr}`;
|
|
|
7812
7812
|
}
|
|
7813
7813
|
function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
|
|
7814
7814
|
const exec = deps.exec ?? ((cmd, args, opts) => execFileSync3(cmd, args, opts));
|
|
7815
|
-
const
|
|
7815
|
+
const homedir17 = deps.homedir ?? (() => os9.homedir());
|
|
7816
7816
|
const baselineDir = path14.join(workspacePath, ".olam", "baseline");
|
|
7817
7817
|
try {
|
|
7818
7818
|
fs13.mkdirSync(baselineDir, { recursive: true });
|
|
@@ -7827,7 +7827,7 @@ function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
|
|
|
7827
7827
|
if (!repo.path) continue;
|
|
7828
7828
|
const filename = `${sanitizeRepoFilename(repo.name)}.diff`;
|
|
7829
7829
|
const outPath = path14.join(baselineDir, filename);
|
|
7830
|
-
const repoPath = expandHome(repo.path,
|
|
7830
|
+
const repoPath = expandHome(repo.path, homedir17);
|
|
7831
7831
|
if (!fs13.existsSync(repoPath)) {
|
|
7832
7832
|
writeBaselineFile(outPath, `# repo: ${repo.name}
|
|
7833
7833
|
# (skipped: path ${repoPath} does not exist)
|
|
@@ -11780,6 +11780,61 @@ var init_context = __esm({
|
|
|
11780
11780
|
}
|
|
11781
11781
|
});
|
|
11782
11782
|
|
|
11783
|
+
// src/registry-allowlist.ts
|
|
11784
|
+
var registry_allowlist_exports = {};
|
|
11785
|
+
__export(registry_allowlist_exports, {
|
|
11786
|
+
decideAllowlist: () => decideAllowlist,
|
|
11787
|
+
resolveDevboxImageOverride: () => resolveDevboxImageOverride
|
|
11788
|
+
});
|
|
11789
|
+
function decideAllowlist(input) {
|
|
11790
|
+
const { imageRef, allowCustomRegistry } = input;
|
|
11791
|
+
const allowedByDefault = DEFAULT_ALLOWLIST_PATTERNS.some((re) => re.test(imageRef));
|
|
11792
|
+
if (allowedByDefault) {
|
|
11793
|
+
return {
|
|
11794
|
+
imageRef,
|
|
11795
|
+
allowedByDefault: true,
|
|
11796
|
+
accepted: true,
|
|
11797
|
+
stderrLine: ""
|
|
11798
|
+
};
|
|
11799
|
+
}
|
|
11800
|
+
if (allowCustomRegistry) {
|
|
11801
|
+
return {
|
|
11802
|
+
imageRef,
|
|
11803
|
+
allowedByDefault: false,
|
|
11804
|
+
accepted: true,
|
|
11805
|
+
stderrLine: `Warning: using custom devbox image '${imageRef}'. (--allow-custom-registry was specified.) Verify the source and digest before proceeding.`
|
|
11806
|
+
};
|
|
11807
|
+
}
|
|
11808
|
+
return {
|
|
11809
|
+
imageRef,
|
|
11810
|
+
allowedByDefault: false,
|
|
11811
|
+
accepted: false,
|
|
11812
|
+
stderrLine: `Error: image '${imageRef}' is outside allowed registries (ghcr.io/pleri/*).
|
|
11813
|
+
To override: re-run with --allow-custom-registry
|
|
11814
|
+
Verify the source and digest before doing so.`
|
|
11815
|
+
};
|
|
11816
|
+
}
|
|
11817
|
+
function resolveDevboxImageOverride(flagValue, env = process.env) {
|
|
11818
|
+
if (flagValue && flagValue.trim().length > 0) {
|
|
11819
|
+
return flagValue.trim();
|
|
11820
|
+
}
|
|
11821
|
+
const envValue = env.OLAM_DEVBOX_IMAGE;
|
|
11822
|
+
if (envValue && envValue.trim().length > 0) {
|
|
11823
|
+
return envValue.trim();
|
|
11824
|
+
}
|
|
11825
|
+
return void 0;
|
|
11826
|
+
}
|
|
11827
|
+
var DEFAULT_ALLOWLIST_PATTERNS;
|
|
11828
|
+
var init_registry_allowlist = __esm({
|
|
11829
|
+
"src/registry-allowlist.ts"() {
|
|
11830
|
+
"use strict";
|
|
11831
|
+
DEFAULT_ALLOWLIST_PATTERNS = [
|
|
11832
|
+
// ghcr.io/pleri/<anything>:<tag> or ghcr.io/pleri/<anything>@sha256:<digest>
|
|
11833
|
+
/^ghcr\.io\/pleri\/[^/\s]+(?::[^\s]+|@sha256:[a-f0-9]+)?$/
|
|
11834
|
+
];
|
|
11835
|
+
}
|
|
11836
|
+
});
|
|
11837
|
+
|
|
11783
11838
|
// ../core/src/orchestrator/enter.ts
|
|
11784
11839
|
var enter_exports = {};
|
|
11785
11840
|
__export(enter_exports, {
|
|
@@ -12249,11 +12304,11 @@ var UnknownArchetypeError = class extends Error {
|
|
|
12249
12304
|
known;
|
|
12250
12305
|
};
|
|
12251
12306
|
var ArchetypeCycleError = class extends Error {
|
|
12252
|
-
constructor(
|
|
12307
|
+
constructor(path35) {
|
|
12253
12308
|
super(
|
|
12254
|
-
`Archetype inheritance cycle detected: ${
|
|
12309
|
+
`Archetype inheritance cycle detected: ${path35.join(" \u2192 ")} \u2192 ${path35[0] ?? "?"}`
|
|
12255
12310
|
);
|
|
12256
|
-
this.path =
|
|
12311
|
+
this.path = path35;
|
|
12257
12312
|
this.name = "ArchetypeCycleError";
|
|
12258
12313
|
}
|
|
12259
12314
|
path;
|
|
@@ -12496,6 +12551,13 @@ function nextCooldownReset(accounts, now = Date.now()) {
|
|
|
12496
12551
|
|
|
12497
12552
|
// src/commands/auth-status.ts
|
|
12498
12553
|
init_auth();
|
|
12554
|
+
|
|
12555
|
+
// src/exit-codes.ts
|
|
12556
|
+
var EXIT_GENERIC_ERROR = 1;
|
|
12557
|
+
var EXIT_PLERI_NOT_CONFIGURED = 2;
|
|
12558
|
+
var EXIT_AUTH_NEEDS_ATTENTION = 5;
|
|
12559
|
+
|
|
12560
|
+
// src/commands/auth-status.ts
|
|
12499
12561
|
var LOCAL_DATA_DIR = path9.join(os5.homedir(), ".olam", "auth-data");
|
|
12500
12562
|
function localHHMM(isoStr) {
|
|
12501
12563
|
const d = new Date(isoStr);
|
|
@@ -12572,7 +12634,7 @@ function formatAuthStatus(accounts, now = Date.now()) {
|
|
|
12572
12634
|
lines.push(
|
|
12573
12635
|
resetIso ? pc5.yellow(`Next reset: ${localHHMM(resetIso)}`) : pc5.dim("No reset scheduled")
|
|
12574
12636
|
);
|
|
12575
|
-
return { output: lines.join("\n"), exitCode:
|
|
12637
|
+
return { output: lines.join("\n"), exitCode: EXIT_AUTH_NEEDS_ATTENTION };
|
|
12576
12638
|
}
|
|
12577
12639
|
return { output: lines.join("\n"), exitCode: 0 };
|
|
12578
12640
|
}
|
|
@@ -13065,10 +13127,10 @@ async function readHostCpToken2() {
|
|
|
13065
13127
|
if (!fs19.existsSync(tp)) return null;
|
|
13066
13128
|
return fs19.readFileSync(tp, "utf-8").trim();
|
|
13067
13129
|
}
|
|
13068
|
-
async function callHostCpProxy(method, worldId,
|
|
13130
|
+
async function callHostCpProxy(method, worldId, path35, body) {
|
|
13069
13131
|
const token = await readHostCpToken2();
|
|
13070
13132
|
if (!token) return { ok: false, status: 0, error: "no token (host CP not started)" };
|
|
13071
|
-
const url = `http://127.0.0.1:${HOST_CP_PORT}/api/world/${encodeURIComponent(worldId)}${
|
|
13133
|
+
const url = `http://127.0.0.1:${HOST_CP_PORT}/api/world/${encodeURIComponent(worldId)}${path35}`;
|
|
13072
13134
|
try {
|
|
13073
13135
|
const headers = {
|
|
13074
13136
|
Authorization: `Bearer ${token}`
|
|
@@ -13679,9 +13741,9 @@ function formatFreshnessWarning(result, image = DEFAULT_DEVBOX_IMAGE) {
|
|
|
13679
13741
|
"These source files have changed since the image was built; the",
|
|
13680
13742
|
"changes will NOT take effect in fresh worlds until you rebuild:"
|
|
13681
13743
|
];
|
|
13682
|
-
for (const { path:
|
|
13744
|
+
for (const { path: path35, mtimeMs } of result.newerSources) {
|
|
13683
13745
|
const when = new Date(mtimeMs).toISOString();
|
|
13684
|
-
lines.push(` \u2022 ${
|
|
13746
|
+
lines.push(` \u2022 ${path35} (modified ${when})`);
|
|
13685
13747
|
}
|
|
13686
13748
|
lines.push("");
|
|
13687
13749
|
lines.push("Rebuild with:");
|
|
@@ -13841,15 +13903,15 @@ init_context();
|
|
|
13841
13903
|
var HOST_CP_URL = "http://127.0.0.1:19000";
|
|
13842
13904
|
async function readHostCpTokenForCreate() {
|
|
13843
13905
|
try {
|
|
13844
|
-
const { default:
|
|
13845
|
-
const { default:
|
|
13846
|
-
const { default:
|
|
13847
|
-
const tp =
|
|
13848
|
-
process.env.OLAM_HOME ??
|
|
13906
|
+
const { default: fs31 } = await import("node:fs");
|
|
13907
|
+
const { default: os18 } = await import("node:os");
|
|
13908
|
+
const { default: path35 } = await import("node:path");
|
|
13909
|
+
const tp = path35.join(
|
|
13910
|
+
process.env.OLAM_HOME ?? path35.join(os18.homedir(), ".olam"),
|
|
13849
13911
|
"host-cp.token"
|
|
13850
13912
|
);
|
|
13851
|
-
if (!
|
|
13852
|
-
return
|
|
13913
|
+
if (!fs31.existsSync(tp)) return null;
|
|
13914
|
+
return fs31.readFileSync(tp, "utf-8").trim();
|
|
13853
13915
|
} catch {
|
|
13854
13916
|
return null;
|
|
13855
13917
|
}
|
|
@@ -13858,7 +13920,23 @@ function registerCreate(program2) {
|
|
|
13858
13920
|
program2.command("create").description("Create a new development world").option("--name <name>", "World name (required unless --from-prompt is set; auto-derived in that case)").option("--repos <repos...>", "Repos to include (names from .olam/config.yaml; wins over --workspace)").option("--workspace <name>", "Named workspace from the host catalog (~/.olam/workspaces/<name>.yaml)").option("--task <task>", "Initial task to dispatch").option("--branch <branch>", "Override default branch name").option("--plan <file>", "Path to a plan file to inject").option("--no-auth", "Skip auto-injecting host credentials").option("--no-host-cp", 'Suppress the host CP "you might want to start it" hint').option("--auto-codex-review", "Spawn a parallel codex-review lane that critiques main as it works").option("--no-open", "Suppress auto-opening the Host CP UI in the browser on success").option("--rebuild-base", "Rebuild olam-devbox:latest before creating (slow)").option("--no-freshness-check", "Skip the devbox image freshness check").option("--from-prompt <prompt>", "NL prompt \u2192 infer workspace + dispatch (CLI parity for olam_create_from_prompt MCP tool)").option("--keep-after-merge", "Disable auto-destroy when the world's PR merges (useful for inspection/debugging)").option("--carry-uncommitted", "Preserve operator's uncommitted edits in the world's worktree").option(
|
|
13859
13921
|
"--allow-bootstrap-failure",
|
|
13860
13922
|
"Treat bootstrap step failures as warnings instead of destroying the world (dogfood escape hatch for cross-repo seed coupling)"
|
|
13861
|
-
).action(async (opts) => {
|
|
13923
|
+
).option("--devbox-image <ref>", "Override the default devbox image (full registry/name:tag or @sha256: ref)").option("--allow-custom-registry", "Allow --devbox-image refs outside ghcr.io/pleri/* (logs a warning)").action(async (opts) => {
|
|
13924
|
+
const { resolveDevboxImageOverride: resolveDevboxImageOverride2, decideAllowlist: decideAllowlist2 } = await Promise.resolve().then(() => (init_registry_allowlist(), registry_allowlist_exports));
|
|
13925
|
+
const overrideRef = resolveDevboxImageOverride2(opts.devboxImage);
|
|
13926
|
+
if (overrideRef) {
|
|
13927
|
+
const decision = decideAllowlist2({
|
|
13928
|
+
imageRef: overrideRef,
|
|
13929
|
+
allowCustomRegistry: opts.allowCustomRegistry === true
|
|
13930
|
+
});
|
|
13931
|
+
if (!decision.accepted) {
|
|
13932
|
+
process.stderr.write(decision.stderrLine + "\n");
|
|
13933
|
+
process.exitCode = 1;
|
|
13934
|
+
return;
|
|
13935
|
+
}
|
|
13936
|
+
if (decision.stderrLine) {
|
|
13937
|
+
process.stderr.write(decision.stderrLine + "\n");
|
|
13938
|
+
}
|
|
13939
|
+
}
|
|
13862
13940
|
let resolvedName = opts.name;
|
|
13863
13941
|
let resolvedWorkspace = opts.workspace;
|
|
13864
13942
|
let resolvedRepos = opts.repos;
|
|
@@ -14163,12 +14241,12 @@ function defaultNameFromPrompt(prompt) {
|
|
|
14163
14241
|
}
|
|
14164
14242
|
async function readHostCpToken3() {
|
|
14165
14243
|
try {
|
|
14166
|
-
const { default:
|
|
14167
|
-
const { default:
|
|
14168
|
-
const { default:
|
|
14169
|
-
const tp =
|
|
14170
|
-
if (!
|
|
14171
|
-
const raw =
|
|
14244
|
+
const { default: fs31 } = await import("node:fs");
|
|
14245
|
+
const { default: os18 } = await import("node:os");
|
|
14246
|
+
const { default: path35 } = await import("node:path");
|
|
14247
|
+
const tp = path35.join(os18.homedir(), ".olam", "host-cp.token");
|
|
14248
|
+
if (!fs31.existsSync(tp)) return null;
|
|
14249
|
+
const raw = fs31.readFileSync(tp, "utf-8").trim();
|
|
14172
14250
|
return raw.length > 0 ? raw : null;
|
|
14173
14251
|
} catch {
|
|
14174
14252
|
return null;
|
|
@@ -14602,29 +14680,38 @@ import * as fs21 from "node:fs";
|
|
|
14602
14680
|
import "node:path";
|
|
14603
14681
|
import ora4 from "ora";
|
|
14604
14682
|
init_world_paths();
|
|
14605
|
-
function registerCrystallize(program2) {
|
|
14606
|
-
program2.command("crystallize").description("Crystallize thoughts from a world to Pleri Plane").argument("<world>", "World ID").action(async (worldId) => {
|
|
14683
|
+
function registerCrystallize(program2, options = {}) {
|
|
14684
|
+
const cmd = program2.command("crystallize").description("Crystallize thoughts from a world to Pleri Plane").argument("<world>", "World ID").action(async (worldId) => {
|
|
14607
14685
|
const { ctx, error } = await loadContext();
|
|
14686
|
+
if (!ctx && error?.name === "OlamConfigNotFoundError") {
|
|
14687
|
+
process.stderr.write(
|
|
14688
|
+
"warn: crystallize requires PLERI_BASE_URL \u2014 skipping (Olam is not initialised; run `olam init` to set up a PLERI block, then re-run).\n"
|
|
14689
|
+
);
|
|
14690
|
+
process.exitCode = EXIT_PLERI_NOT_CONFIGURED;
|
|
14691
|
+
return;
|
|
14692
|
+
}
|
|
14608
14693
|
if (!ctx) {
|
|
14609
14694
|
printError(error?.message ?? "Olam is not configured. Run `olam init` first.");
|
|
14610
|
-
process.exitCode =
|
|
14695
|
+
process.exitCode = EXIT_GENERIC_ERROR;
|
|
14611
14696
|
return;
|
|
14612
14697
|
}
|
|
14613
14698
|
if (!ctx.pleriClient) {
|
|
14614
|
-
|
|
14615
|
-
|
|
14699
|
+
process.stderr.write(
|
|
14700
|
+
"warn: crystallize requires PLERI_BASE_URL \u2014 skipping (no work performed). Configure pleri in .olam/config.yaml or set PLERI_BASE_URL to enable.\n"
|
|
14701
|
+
);
|
|
14702
|
+
process.exitCode = EXIT_PLERI_NOT_CONFIGURED;
|
|
14616
14703
|
return;
|
|
14617
14704
|
}
|
|
14618
14705
|
const world = ctx.worldManager.getWorld(worldId);
|
|
14619
14706
|
if (!world) {
|
|
14620
14707
|
printError(`World "${worldId}" not found.`);
|
|
14621
|
-
process.exitCode =
|
|
14708
|
+
process.exitCode = EXIT_GENERIC_ERROR;
|
|
14622
14709
|
return;
|
|
14623
14710
|
}
|
|
14624
14711
|
const thoughtDbPath = getWorldDbPath(world.workspacePath);
|
|
14625
14712
|
if (!fs21.existsSync(thoughtDbPath)) {
|
|
14626
14713
|
printError(`No thoughts captured yet for "${worldId}". Run a dispatch first.`);
|
|
14627
|
-
process.exitCode =
|
|
14714
|
+
process.exitCode = EXIT_GENERIC_ERROR;
|
|
14628
14715
|
return;
|
|
14629
14716
|
}
|
|
14630
14717
|
const spinner = ora4("Crystallizing thoughts...").start();
|
|
@@ -14682,9 +14769,12 @@ function registerCrystallize(program2) {
|
|
|
14682
14769
|
} catch (err) {
|
|
14683
14770
|
spinner.fail("Crystallization failed");
|
|
14684
14771
|
printError(err instanceof Error ? err.message : String(err));
|
|
14685
|
-
process.exitCode =
|
|
14772
|
+
process.exitCode = EXIT_GENERIC_ERROR;
|
|
14686
14773
|
}
|
|
14687
14774
|
});
|
|
14775
|
+
if (options.hidden) {
|
|
14776
|
+
cmd._hidden = true;
|
|
14777
|
+
}
|
|
14688
14778
|
}
|
|
14689
14779
|
|
|
14690
14780
|
// src/commands/pr.ts
|
|
@@ -16821,17 +16911,246 @@ function registerPolicyCheck(program2) {
|
|
|
16821
16911
|
}
|
|
16822
16912
|
|
|
16823
16913
|
// src/commands/upgrade.ts
|
|
16914
|
+
import * as fs24 from "node:fs";
|
|
16915
|
+
import * as path28 from "node:path";
|
|
16916
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
16917
|
+
import pc15 from "picocolors";
|
|
16918
|
+
|
|
16919
|
+
// src/commands/upgrade-lock.ts
|
|
16824
16920
|
import * as fs22 from "node:fs";
|
|
16921
|
+
import * as os13 from "node:os";
|
|
16825
16922
|
import * as path26 from "node:path";
|
|
16826
16923
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
16827
|
-
|
|
16924
|
+
var LOCK_FILE_PATH = path26.join(os13.homedir(), ".olam", ".upgrade.lock");
|
|
16925
|
+
var STALE_LOCK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
16926
|
+
function readLockFile(lockPath) {
|
|
16927
|
+
try {
|
|
16928
|
+
if (!fs22.existsSync(lockPath)) return null;
|
|
16929
|
+
const raw = fs22.readFileSync(lockPath, "utf-8").trim();
|
|
16930
|
+
if (raw.length === 0) return null;
|
|
16931
|
+
const parsed = JSON.parse(raw);
|
|
16932
|
+
if (typeof parsed.pid !== "number" || typeof parsed.startTs !== "number") return null;
|
|
16933
|
+
return { pid: parsed.pid, startTs: parsed.startTs };
|
|
16934
|
+
} catch {
|
|
16935
|
+
return null;
|
|
16936
|
+
}
|
|
16937
|
+
}
|
|
16938
|
+
function isPidAlive(pid) {
|
|
16939
|
+
try {
|
|
16940
|
+
process.kill(pid, 0);
|
|
16941
|
+
return true;
|
|
16942
|
+
} catch {
|
|
16943
|
+
return false;
|
|
16944
|
+
}
|
|
16945
|
+
}
|
|
16946
|
+
var PS_UNAVAILABLE = "__ps_unavailable__";
|
|
16947
|
+
function getPidCommand(pid) {
|
|
16948
|
+
const result = spawnSync6("ps", ["-p", String(pid), "-o", "comm="], {
|
|
16949
|
+
encoding: "utf-8",
|
|
16950
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
16951
|
+
});
|
|
16952
|
+
if (result.status === null || result.error !== void 0) return PS_UNAVAILABLE;
|
|
16953
|
+
if (result.status !== 0) return null;
|
|
16954
|
+
const out = result.stdout.trim();
|
|
16955
|
+
return out.length === 0 ? null : out;
|
|
16956
|
+
}
|
|
16957
|
+
function isOlamUpgradeCommand(comm) {
|
|
16958
|
+
if (!comm) return false;
|
|
16959
|
+
if (comm === PS_UNAVAILABLE) return false;
|
|
16960
|
+
const base = comm.split("/").pop() ?? comm;
|
|
16961
|
+
const stripped = base.replace(/\s*\(.*\)\s*$/, "").trim();
|
|
16962
|
+
return stripped === "node" || stripped === "olam" || stripped === "olam-cli";
|
|
16963
|
+
}
|
|
16964
|
+
function isStaleLock(content, nowMs = Date.now()) {
|
|
16965
|
+
if (!content) return true;
|
|
16966
|
+
if (nowMs - content.startTs > STALE_LOCK_TIMEOUT_MS) return true;
|
|
16967
|
+
if (!isPidAlive(content.pid)) return true;
|
|
16968
|
+
const comm = getPidCommand(content.pid);
|
|
16969
|
+
if (comm === PS_UNAVAILABLE) return false;
|
|
16970
|
+
if (!isOlamUpgradeCommand(comm)) return true;
|
|
16971
|
+
return false;
|
|
16972
|
+
}
|
|
16973
|
+
function acquireLock(lockPath = LOCK_FILE_PATH, nowMs = Date.now()) {
|
|
16974
|
+
const dir = path26.dirname(lockPath);
|
|
16975
|
+
fs22.mkdirSync(dir, { recursive: true });
|
|
16976
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
16977
|
+
try {
|
|
16978
|
+
const fd = fs22.openSync(lockPath, "wx", 420);
|
|
16979
|
+
try {
|
|
16980
|
+
const content = { pid: process.pid, startTs: nowMs };
|
|
16981
|
+
fs22.writeSync(fd, JSON.stringify(content));
|
|
16982
|
+
} finally {
|
|
16983
|
+
fs22.closeSync(fd);
|
|
16984
|
+
}
|
|
16985
|
+
return { acquired: true, lockPath };
|
|
16986
|
+
} catch (err) {
|
|
16987
|
+
const code = err.code;
|
|
16988
|
+
if (code !== "EEXIST") throw err;
|
|
16989
|
+
const existing2 = readLockFile(lockPath);
|
|
16990
|
+
if (isStaleLock(existing2, nowMs)) {
|
|
16991
|
+
try {
|
|
16992
|
+
fs22.unlinkSync(lockPath);
|
|
16993
|
+
} catch (unlinkErr) {
|
|
16994
|
+
const ucode = unlinkErr.code;
|
|
16995
|
+
if (ucode !== "ENOENT") throw unlinkErr;
|
|
16996
|
+
}
|
|
16997
|
+
continue;
|
|
16998
|
+
}
|
|
16999
|
+
return {
|
|
17000
|
+
acquired: false,
|
|
17001
|
+
reason: "live",
|
|
17002
|
+
...existing2?.pid !== void 0 && { existingPid: existing2.pid },
|
|
17003
|
+
...existing2?.startTs !== void 0 && { existingStartTs: existing2.startTs }
|
|
17004
|
+
};
|
|
17005
|
+
}
|
|
17006
|
+
}
|
|
17007
|
+
const existing = readLockFile(lockPath);
|
|
17008
|
+
return {
|
|
17009
|
+
acquired: false,
|
|
17010
|
+
reason: "live",
|
|
17011
|
+
...existing?.pid !== void 0 && { existingPid: existing.pid },
|
|
17012
|
+
...existing?.startTs !== void 0 && { existingStartTs: existing.startTs }
|
|
17013
|
+
};
|
|
17014
|
+
}
|
|
17015
|
+
function releaseLock(lockPath = LOCK_FILE_PATH) {
|
|
17016
|
+
try {
|
|
17017
|
+
fs22.unlinkSync(lockPath);
|
|
17018
|
+
} catch (err) {
|
|
17019
|
+
const code = err.code;
|
|
17020
|
+
if (code !== "ENOENT") throw err;
|
|
17021
|
+
}
|
|
17022
|
+
}
|
|
17023
|
+
function formatRefusalMessage(result, lockPath = LOCK_FILE_PATH) {
|
|
17024
|
+
const pidStr = result.existingPid !== void 0 ? ` (pid ${result.existingPid})` : "";
|
|
17025
|
+
const lines = [
|
|
17026
|
+
`Upgrade in progress${pidStr}.`,
|
|
17027
|
+
"Wait for the running upgrade to finish, or:",
|
|
17028
|
+
" - Check progress: olam upgrade --history",
|
|
17029
|
+
` - If stale (crashed CLI): rm ${lockPath}`
|
|
17030
|
+
];
|
|
17031
|
+
return lines.join("\n");
|
|
17032
|
+
}
|
|
17033
|
+
|
|
17034
|
+
// src/commands/upgrade-log.ts
|
|
17035
|
+
import * as fs23 from "node:fs";
|
|
17036
|
+
import * as os14 from "node:os";
|
|
17037
|
+
import * as path27 from "node:path";
|
|
17038
|
+
function getUpgradeLogPath() {
|
|
17039
|
+
const home = process.env["HOME"] ?? os14.homedir();
|
|
17040
|
+
return path27.join(home, ".olam", "upgrade.log");
|
|
17041
|
+
}
|
|
17042
|
+
var UPGRADE_LOG_PATH = getUpgradeLogPath();
|
|
17043
|
+
function appendUpgradeLog(row, logPath = getUpgradeLogPath()) {
|
|
17044
|
+
try {
|
|
17045
|
+
fs23.mkdirSync(path27.dirname(logPath), { recursive: true });
|
|
17046
|
+
const line = JSON.stringify(row) + "\n";
|
|
17047
|
+
fs23.appendFileSync(logPath, line, { mode: 420 });
|
|
17048
|
+
} catch (err) {
|
|
17049
|
+
process.stderr.write(
|
|
17050
|
+
`[upgrade-log] failed to append: ${err instanceof Error ? err.message : String(err)}
|
|
17051
|
+
`
|
|
17052
|
+
);
|
|
17053
|
+
}
|
|
17054
|
+
}
|
|
17055
|
+
function readUpgradeLog(limit = 10, logPath = getUpgradeLogPath()) {
|
|
17056
|
+
if (!fs23.existsSync(logPath)) return [];
|
|
17057
|
+
let raw;
|
|
17058
|
+
try {
|
|
17059
|
+
raw = fs23.readFileSync(logPath, "utf-8");
|
|
17060
|
+
} catch (err) {
|
|
17061
|
+
process.stderr.write(
|
|
17062
|
+
`[upgrade-log] failed to read: ${err instanceof Error ? err.message : String(err)}
|
|
17063
|
+
`
|
|
17064
|
+
);
|
|
17065
|
+
return [];
|
|
17066
|
+
}
|
|
17067
|
+
const lines = raw.split("\n").filter((l) => l.length > 0);
|
|
17068
|
+
const rows = [];
|
|
17069
|
+
for (let i = 0; i < lines.length; i++) {
|
|
17070
|
+
const line = lines[i];
|
|
17071
|
+
try {
|
|
17072
|
+
const parsed = JSON.parse(line);
|
|
17073
|
+
if (typeof parsed.ts === "string" && typeof parsed.started_at === "number" && typeof parsed.status === "string") {
|
|
17074
|
+
rows.push(parsed);
|
|
17075
|
+
} else {
|
|
17076
|
+
process.stderr.write(`[upgrade-log] skipped malformed row at line ${i + 1}
|
|
17077
|
+
`);
|
|
17078
|
+
}
|
|
17079
|
+
} catch {
|
|
17080
|
+
process.stderr.write(`[upgrade-log] skipped corrupt JSON at line ${i + 1}
|
|
17081
|
+
`);
|
|
17082
|
+
}
|
|
17083
|
+
}
|
|
17084
|
+
return rows.slice(-Math.max(0, limit));
|
|
17085
|
+
}
|
|
17086
|
+
function formatDuration(ms) {
|
|
17087
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
17088
|
+
const totalSec = Math.round(ms / 1e3);
|
|
17089
|
+
if (totalSec < 60) return `${totalSec}s`;
|
|
17090
|
+
const min = Math.floor(totalSec / 60);
|
|
17091
|
+
const sec = totalSec % 60;
|
|
17092
|
+
if (min < 60) return `${min}m${String(sec).padStart(2, "0")}s`;
|
|
17093
|
+
const hr = Math.floor(min / 60);
|
|
17094
|
+
const remMin = min % 60;
|
|
17095
|
+
return `${hr}h${String(remMin).padStart(2, "0")}m`;
|
|
17096
|
+
}
|
|
17097
|
+
function formatHistoryTable(rows) {
|
|
17098
|
+
if (rows.length === 0) {
|
|
17099
|
+
return "No upgrade history yet. Run `olam upgrade` to create your first record.";
|
|
17100
|
+
}
|
|
17101
|
+
const lines = [];
|
|
17102
|
+
lines.push("TIMESTAMP SHA STATUS DURATION FAILED-STEP");
|
|
17103
|
+
lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
17104
|
+
for (const r of rows) {
|
|
17105
|
+
const ts = r.ts.slice(0, 19).replace("T", " ");
|
|
17106
|
+
const sha = r.sha_target.slice(0, 8);
|
|
17107
|
+
const statusIcon = r.status === "success" ? "\u2713 success" : r.status === "rolled_back" ? "\u21A9 rolled_back" : "\u2717 failed";
|
|
17108
|
+
const dur = formatDuration(r.ended_at - r.started_at);
|
|
17109
|
+
const failed = r.failed_step ?? "";
|
|
17110
|
+
lines.push(
|
|
17111
|
+
`${ts.padEnd(28)}${sha.padEnd(10)}${statusIcon.padEnd(15)}${dur.padEnd(11)}${failed}`
|
|
17112
|
+
);
|
|
17113
|
+
}
|
|
17114
|
+
return lines.join("\n");
|
|
17115
|
+
}
|
|
17116
|
+
function formatHistoryJson(rows) {
|
|
17117
|
+
return rows.map((r) => JSON.stringify(r)).join("\n");
|
|
17118
|
+
}
|
|
17119
|
+
|
|
17120
|
+
// src/commands/upgrade-history.ts
|
|
17121
|
+
function parseHistoryOpts(raw) {
|
|
17122
|
+
const rawLimit = raw.n;
|
|
17123
|
+
const limit = typeof rawLimit === "number" ? rawLimit : typeof rawLimit === "string" ? Number.parseInt(rawLimit, 10) : 10;
|
|
17124
|
+
return {
|
|
17125
|
+
limit: Number.isFinite(limit) && limit > 0 ? limit : 10,
|
|
17126
|
+
json: raw.json === true
|
|
17127
|
+
};
|
|
17128
|
+
}
|
|
17129
|
+
function handleHistory(opts) {
|
|
17130
|
+
const rows = readUpgradeLog(opts.limit);
|
|
17131
|
+
if (opts.json) {
|
|
17132
|
+
process.stdout.write(formatHistoryJson(rows) + "\n");
|
|
17133
|
+
return;
|
|
17134
|
+
}
|
|
17135
|
+
if (rows.length === 0) {
|
|
17136
|
+
printInfo("Log file", UPGRADE_LOG_PATH);
|
|
17137
|
+
process.stdout.write(formatHistoryTable(rows) + "\n");
|
|
17138
|
+
return;
|
|
17139
|
+
}
|
|
17140
|
+
printInfo("Log file", UPGRADE_LOG_PATH);
|
|
17141
|
+
process.stdout.write(formatHistoryTable(rows) + "\n");
|
|
17142
|
+
}
|
|
17143
|
+
|
|
17144
|
+
// src/commands/upgrade.ts
|
|
17145
|
+
init_auth();
|
|
17146
|
+
var AUTH_HEALTH_URL2 = "http://127.0.0.1:9999/health";
|
|
16828
17147
|
function isNodeModulesInSync(cwd) {
|
|
16829
|
-
const lockPath =
|
|
16830
|
-
const markerPath =
|
|
16831
|
-
if (!
|
|
17148
|
+
const lockPath = path28.join(cwd, "package-lock.json");
|
|
17149
|
+
const markerPath = path28.join(cwd, "node_modules", ".package-lock.json");
|
|
17150
|
+
if (!fs24.existsSync(lockPath) || !fs24.existsSync(markerPath)) return false;
|
|
16832
17151
|
try {
|
|
16833
|
-
const lockStat =
|
|
16834
|
-
const markerStat =
|
|
17152
|
+
const lockStat = fs24.statSync(lockPath);
|
|
17153
|
+
const markerStat = fs24.statSync(markerPath);
|
|
16835
17154
|
return markerStat.mtimeMs >= lockStat.mtimeMs;
|
|
16836
17155
|
} catch {
|
|
16837
17156
|
return false;
|
|
@@ -16847,8 +17166,8 @@ function shouldSkipInstall(opts, cwd) {
|
|
|
16847
17166
|
return { skip: false };
|
|
16848
17167
|
}
|
|
16849
17168
|
function validateRepoRoot(cwd) {
|
|
16850
|
-
const marker =
|
|
16851
|
-
if (!
|
|
17169
|
+
const marker = path28.join(cwd, "packages/host-cp/compose.yaml");
|
|
17170
|
+
if (!fs24.existsSync(marker)) {
|
|
16852
17171
|
return {
|
|
16853
17172
|
ok: false,
|
|
16854
17173
|
error: `Not an olam repo root (expected ${marker}).
|
|
@@ -16858,11 +17177,19 @@ Run \`olam upgrade\` from the root of your olam checkout.`
|
|
|
16858
17177
|
return { ok: true };
|
|
16859
17178
|
}
|
|
16860
17179
|
function parseUpgradeOpts(raw) {
|
|
17180
|
+
const rawN = raw.n;
|
|
17181
|
+
const historyN = typeof rawN === "number" ? rawN : typeof rawN === "string" ? Number.parseInt(rawN, 10) : 10;
|
|
16861
17182
|
return {
|
|
16862
17183
|
yes: raw.yes === true,
|
|
16863
17184
|
skipImage: raw.skipImage === true,
|
|
16864
17185
|
skipInstall: raw.skipInstall === true,
|
|
16865
|
-
branch: raw.branch ?? null
|
|
17186
|
+
branch: raw.branch ?? null,
|
|
17187
|
+
rollback: raw.rollback === true,
|
|
17188
|
+
force: raw.force === true,
|
|
17189
|
+
noCache: raw.noCache === true,
|
|
17190
|
+
history: raw.history === true,
|
|
17191
|
+
historyN: Number.isFinite(historyN) && historyN > 0 ? historyN : 10,
|
|
17192
|
+
historyJson: raw.json === true
|
|
16866
17193
|
};
|
|
16867
17194
|
}
|
|
16868
17195
|
function extractBundleHash(indexHtml) {
|
|
@@ -16872,7 +17199,7 @@ function extractBundleHash(indexHtml) {
|
|
|
16872
17199
|
function runStep2(label, cmd, args, opts = {}) {
|
|
16873
17200
|
const start = Date.now();
|
|
16874
17201
|
process.stdout.write(` ${pc15.dim(label.padEnd(34))}`);
|
|
16875
|
-
const result =
|
|
17202
|
+
const result = spawnSync7(cmd, [...args], {
|
|
16876
17203
|
encoding: "utf-8",
|
|
16877
17204
|
stdio: ["ignore", "pipe", "pipe"],
|
|
16878
17205
|
cwd: opts.cwd ?? process.cwd(),
|
|
@@ -16891,7 +17218,7 @@ function runStep2(label, cmd, args, opts = {}) {
|
|
|
16891
17218
|
};
|
|
16892
17219
|
}
|
|
16893
17220
|
function isGitDirty(cwd) {
|
|
16894
|
-
const result =
|
|
17221
|
+
const result = spawnSync7("git", ["status", "--porcelain"], {
|
|
16895
17222
|
encoding: "utf-8",
|
|
16896
17223
|
stdio: ["ignore", "pipe", "pipe"],
|
|
16897
17224
|
cwd
|
|
@@ -16899,13 +17226,194 @@ function isGitDirty(cwd) {
|
|
|
16899
17226
|
return (result.stdout ?? "").trim().length > 0;
|
|
16900
17227
|
}
|
|
16901
17228
|
function hasGitUpstream(cwd) {
|
|
16902
|
-
const result =
|
|
17229
|
+
const result = spawnSync7("git", ["rev-parse", "--abbrev-ref", "@{u}"], {
|
|
16903
17230
|
encoding: "utf-8",
|
|
16904
17231
|
stdio: ["ignore", "pipe", "pipe"],
|
|
16905
17232
|
cwd
|
|
16906
17233
|
});
|
|
16907
17234
|
return result.status === 0;
|
|
16908
17235
|
}
|
|
17236
|
+
function captureHeadSha(cwd) {
|
|
17237
|
+
const result = spawnSync7("git", ["rev-parse", "HEAD"], {
|
|
17238
|
+
encoding: "utf-8",
|
|
17239
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
17240
|
+
cwd
|
|
17241
|
+
});
|
|
17242
|
+
if (result.status !== 0) return null;
|
|
17243
|
+
const sha = (result.stdout ?? "").trim();
|
|
17244
|
+
if (!/^[0-9a-f]{40}$/.test(sha)) return null;
|
|
17245
|
+
return sha;
|
|
17246
|
+
}
|
|
17247
|
+
function abbreviateSha(sha) {
|
|
17248
|
+
return sha.slice(0, 8);
|
|
17249
|
+
}
|
|
17250
|
+
function imageExists(tag) {
|
|
17251
|
+
try {
|
|
17252
|
+
const result = spawnSync7("docker", ["image", "inspect", "--format", "{{.Id}}", tag], {
|
|
17253
|
+
encoding: "utf-8",
|
|
17254
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
17255
|
+
});
|
|
17256
|
+
return result.status === 0;
|
|
17257
|
+
} catch {
|
|
17258
|
+
return false;
|
|
17259
|
+
}
|
|
17260
|
+
}
|
|
17261
|
+
function checkRollbackSetExists(plan) {
|
|
17262
|
+
const missing = plan.filter((p) => !imageExists(p.rollback)).map((p) => p.rollback);
|
|
17263
|
+
if (missing.length === 0) return null;
|
|
17264
|
+
return missing.join(", ");
|
|
17265
|
+
}
|
|
17266
|
+
function smokeImage(image, targetSha) {
|
|
17267
|
+
const createResult = spawnSync7("docker", ["create", "--name", `olam-smoke-${Date.now()}`, image], {
|
|
17268
|
+
encoding: "utf-8",
|
|
17269
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
17270
|
+
});
|
|
17271
|
+
if (createResult.status !== 0) {
|
|
17272
|
+
return {
|
|
17273
|
+
image,
|
|
17274
|
+
ok: false,
|
|
17275
|
+
bakedSha: null,
|
|
17276
|
+
error: `docker create failed: ${(createResult.stderr ?? "").trim()}`
|
|
17277
|
+
};
|
|
17278
|
+
}
|
|
17279
|
+
const containerId = (createResult.stdout ?? "").trim();
|
|
17280
|
+
const inspectResult = spawnSync7(
|
|
17281
|
+
"docker",
|
|
17282
|
+
["inspect", "--format", '{{index .Config.Labels "olam_build_sha"}}', image],
|
|
17283
|
+
{
|
|
17284
|
+
encoding: "utf-8",
|
|
17285
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
17286
|
+
}
|
|
17287
|
+
);
|
|
17288
|
+
if (containerId.length > 0) {
|
|
17289
|
+
spawnSync7("docker", ["rm", "-f", containerId], {
|
|
17290
|
+
encoding: "utf-8",
|
|
17291
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
17292
|
+
});
|
|
17293
|
+
}
|
|
17294
|
+
if (inspectResult.status !== 0) {
|
|
17295
|
+
return {
|
|
17296
|
+
image,
|
|
17297
|
+
ok: false,
|
|
17298
|
+
bakedSha: null,
|
|
17299
|
+
error: `docker inspect failed: ${(inspectResult.stderr ?? "").trim()}`
|
|
17300
|
+
};
|
|
17301
|
+
}
|
|
17302
|
+
const bakedSha = (inspectResult.stdout ?? "").trim();
|
|
17303
|
+
if (bakedSha.length === 0) {
|
|
17304
|
+
return {
|
|
17305
|
+
image,
|
|
17306
|
+
ok: false,
|
|
17307
|
+
bakedSha: null,
|
|
17308
|
+
error: "olam_build_sha label is missing or empty"
|
|
17309
|
+
};
|
|
17310
|
+
}
|
|
17311
|
+
if (bakedSha !== targetSha) {
|
|
17312
|
+
return {
|
|
17313
|
+
image,
|
|
17314
|
+
ok: false,
|
|
17315
|
+
bakedSha,
|
|
17316
|
+
error: `baked SHA ${abbreviateSha(bakedSha)} \u2260 target SHA ${abbreviateSha(targetSha)}`
|
|
17317
|
+
};
|
|
17318
|
+
}
|
|
17319
|
+
return { image, ok: true, bakedSha };
|
|
17320
|
+
}
|
|
17321
|
+
var PRODUCTION_SWAP_PLAN = [
|
|
17322
|
+
{ transient: "olam-auth:olam-next", canonical: "olam-auth:local", rollback: "olam-auth:olam-rollback" },
|
|
17323
|
+
{ transient: "olam-devbox:olam-next", canonical: "olam-devbox:latest", rollback: "olam-devbox:olam-rollback" },
|
|
17324
|
+
{ transient: "olam-host-cp:olam-next", canonical: "olam-host-cp:latest", rollback: "olam-host-cp:olam-rollback" }
|
|
17325
|
+
];
|
|
17326
|
+
function dockerTag(source, dest) {
|
|
17327
|
+
try {
|
|
17328
|
+
const result = spawnSync7("docker", ["tag", source, dest], {
|
|
17329
|
+
encoding: "utf-8",
|
|
17330
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
17331
|
+
});
|
|
17332
|
+
if (result.status === 0 && result.error === void 0) return { ok: true };
|
|
17333
|
+
return {
|
|
17334
|
+
ok: false,
|
|
17335
|
+
error: (result.stderr ?? "").trim() || result.error?.message || "docker tag failed"
|
|
17336
|
+
};
|
|
17337
|
+
} catch (err) {
|
|
17338
|
+
return {
|
|
17339
|
+
ok: false,
|
|
17340
|
+
error: err instanceof Error ? `spawnSync threw: ${err.message}` : "spawnSync threw"
|
|
17341
|
+
};
|
|
17342
|
+
}
|
|
17343
|
+
}
|
|
17344
|
+
function performAtomicSwap(plan) {
|
|
17345
|
+
const steps = plan.map((p) => ({
|
|
17346
|
+
image: p.canonical,
|
|
17347
|
+
rollbackSaved: false,
|
|
17348
|
+
canonicalAdvanced: false
|
|
17349
|
+
}));
|
|
17350
|
+
for (let i = 0; i < plan.length; i++) {
|
|
17351
|
+
const p = plan[i];
|
|
17352
|
+
const r = dockerTag(p.canonical, p.rollback);
|
|
17353
|
+
steps[i] = {
|
|
17354
|
+
...steps[i],
|
|
17355
|
+
rollbackSaved: r.ok,
|
|
17356
|
+
...r.error !== void 0 && { rollbackError: r.error }
|
|
17357
|
+
};
|
|
17358
|
+
}
|
|
17359
|
+
let advanceFailed = false;
|
|
17360
|
+
let firstFailureIdx = -1;
|
|
17361
|
+
for (let i = 0; i < plan.length; i++) {
|
|
17362
|
+
const p = plan[i];
|
|
17363
|
+
if (advanceFailed) {
|
|
17364
|
+
steps[i] = { ...steps[i], canonicalAdvanced: false };
|
|
17365
|
+
continue;
|
|
17366
|
+
}
|
|
17367
|
+
const r = dockerTag(p.transient, p.canonical);
|
|
17368
|
+
steps[i] = {
|
|
17369
|
+
...steps[i],
|
|
17370
|
+
canonicalAdvanced: r.ok,
|
|
17371
|
+
...r.error !== void 0 && { canonicalError: r.error }
|
|
17372
|
+
};
|
|
17373
|
+
if (!r.ok) {
|
|
17374
|
+
advanceFailed = true;
|
|
17375
|
+
firstFailureIdx = i;
|
|
17376
|
+
}
|
|
17377
|
+
}
|
|
17378
|
+
const allAdvanced = steps.every((s) => s.canonicalAdvanced);
|
|
17379
|
+
const noneAdvanced = steps.every((s) => !s.canonicalAdvanced);
|
|
17380
|
+
const partialAdvance = !allAdvanced && !noneAdvanced;
|
|
17381
|
+
const rollbackCoherent = steps.every((s) => s.rollbackSaved);
|
|
17382
|
+
let summary;
|
|
17383
|
+
if (allAdvanced) {
|
|
17384
|
+
const rollbacks = steps.filter((s) => s.rollbackSaved).length;
|
|
17385
|
+
summary = `Swapped ${plan.length} canonical tags; ${rollbacks} :olam-rollback preserved`;
|
|
17386
|
+
} else if (partialAdvance) {
|
|
17387
|
+
const advanced = steps.filter((s) => s.canonicalAdvanced).length;
|
|
17388
|
+
const failedStep = steps[firstFailureIdx];
|
|
17389
|
+
const recoveryHint = rollbackCoherent ? `Run \`olam upgrade --rollback\` to restore coherent prior state.` : `Rollback set INCOHERENT (${steps.filter((s) => s.rollbackSaved).length} of ${plan.length} :olam-rollback tags written). Manual recovery required: inspect images and re-tag canonical from a known-good source.`;
|
|
17390
|
+
summary = `PARTIAL: ${advanced} of ${plan.length} canonical tags advanced before failure on ${failedStep?.image}: ${failedStep?.canonicalError}. ${recoveryHint}`;
|
|
17391
|
+
} else {
|
|
17392
|
+
const failedStep = steps[firstFailureIdx];
|
|
17393
|
+
summary = `Failed on first canonical-advance (${failedStep?.image}): ${failedStep?.canonicalError}. Canonical tags untouched.`;
|
|
17394
|
+
}
|
|
17395
|
+
return {
|
|
17396
|
+
ok: allAdvanced,
|
|
17397
|
+
steps,
|
|
17398
|
+
partialAdvance,
|
|
17399
|
+
rollbackCoherent,
|
|
17400
|
+
summary
|
|
17401
|
+
};
|
|
17402
|
+
}
|
|
17403
|
+
function performRollbackSwap(plan) {
|
|
17404
|
+
const results = [];
|
|
17405
|
+
for (const p of plan) {
|
|
17406
|
+
const r = dockerTag(p.rollback, p.canonical);
|
|
17407
|
+
results.push({
|
|
17408
|
+
image: p.canonical,
|
|
17409
|
+
ok: r.ok,
|
|
17410
|
+
...r.error !== void 0 && { error: r.error }
|
|
17411
|
+
});
|
|
17412
|
+
}
|
|
17413
|
+
const allOk = results.every((r) => r.ok);
|
|
17414
|
+
const summary = allOk ? `Rolled back ${plan.length} canonical tags from :olam-rollback` : `PARTIAL rollback: ${results.filter((r) => r.ok).length} of ${plan.length} succeeded; failed: ${results.filter((r) => !r.ok).map((r) => r.image).join(", ")}`;
|
|
17415
|
+
return { ok: allOk, results, summary };
|
|
17416
|
+
}
|
|
16909
17417
|
async function confirm2(message) {
|
|
16910
17418
|
if (!process.stdin.isTTY) return true;
|
|
16911
17419
|
const { createInterface: createInterface2 } = await import("node:readline");
|
|
@@ -16931,10 +17439,87 @@ async function waitForHealth(timeoutMs = 1e4) {
|
|
|
16931
17439
|
}
|
|
16932
17440
|
return false;
|
|
16933
17441
|
}
|
|
17442
|
+
async function waitForVersionMatch(targetSha, timeoutMs = 6e4, pollIntervalMs = 1e3) {
|
|
17443
|
+
const deadline = Date.now() + timeoutMs;
|
|
17444
|
+
let lastSnapshot = null;
|
|
17445
|
+
while (Date.now() < deadline) {
|
|
17446
|
+
try {
|
|
17447
|
+
const res = await fetch("http://127.0.0.1:19000/api/version/status", {
|
|
17448
|
+
signal: AbortSignal.timeout(2e3)
|
|
17449
|
+
});
|
|
17450
|
+
if (res.ok) {
|
|
17451
|
+
const snapshot = await res.json();
|
|
17452
|
+
lastSnapshot = snapshot;
|
|
17453
|
+
if (snapshot.hostCp?.running === targetSha && snapshot.authService?.running === targetSha && snapshot.devbox?.running === targetSha) {
|
|
17454
|
+
return { matched: true, snapshot };
|
|
17455
|
+
}
|
|
17456
|
+
}
|
|
17457
|
+
} catch {
|
|
17458
|
+
}
|
|
17459
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
17460
|
+
}
|
|
17461
|
+
return { matched: false, snapshot: lastSnapshot };
|
|
17462
|
+
}
|
|
17463
|
+
function formatVersionMismatch(targetSha, snapshot) {
|
|
17464
|
+
if (!snapshot) return "No /api/version/status response received within timeout.";
|
|
17465
|
+
const lines = [];
|
|
17466
|
+
for (const [name, comp] of [
|
|
17467
|
+
["host-cp", snapshot.hostCp],
|
|
17468
|
+
["auth-service", snapshot.authService],
|
|
17469
|
+
["devbox", snapshot.devbox]
|
|
17470
|
+
]) {
|
|
17471
|
+
const match2 = comp?.running === targetSha;
|
|
17472
|
+
lines.push(` ${match2 ? "\u2713" : "\u2717"} ${name}: running=${abbreviateSha(comp?.running ?? "unknown")} target=${abbreviateSha(targetSha)}`);
|
|
17473
|
+
}
|
|
17474
|
+
return lines.join("\n");
|
|
17475
|
+
}
|
|
17476
|
+
async function waitForAuthHealthLocal(timeoutMs = 15e3) {
|
|
17477
|
+
const deadline = Date.now() + timeoutMs;
|
|
17478
|
+
while (Date.now() < deadline) {
|
|
17479
|
+
try {
|
|
17480
|
+
const res = await fetch(AUTH_HEALTH_URL2, { signal: AbortSignal.timeout(2e3) });
|
|
17481
|
+
if (res.ok) return true;
|
|
17482
|
+
} catch {
|
|
17483
|
+
}
|
|
17484
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
17485
|
+
}
|
|
17486
|
+
return false;
|
|
17487
|
+
}
|
|
17488
|
+
async function recreateAuthService() {
|
|
17489
|
+
const start = Date.now();
|
|
17490
|
+
try {
|
|
17491
|
+
spawnSync7("docker", ["stop", "olam-auth"], {
|
|
17492
|
+
encoding: "utf-8",
|
|
17493
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
17494
|
+
});
|
|
17495
|
+
spawnSync7("docker", ["rm", "olam-auth"], {
|
|
17496
|
+
encoding: "utf-8",
|
|
17497
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
17498
|
+
});
|
|
17499
|
+
const controller = new AuthContainerController();
|
|
17500
|
+
controller.start();
|
|
17501
|
+
const healthy = await waitForAuthHealthLocal(15e3);
|
|
17502
|
+
const durationMs = Date.now() - start;
|
|
17503
|
+
if (!healthy) {
|
|
17504
|
+
return {
|
|
17505
|
+
ok: false,
|
|
17506
|
+
durationMs,
|
|
17507
|
+
error: "auth-service /health did not respond within 15s after recreate"
|
|
17508
|
+
};
|
|
17509
|
+
}
|
|
17510
|
+
return { ok: true, durationMs };
|
|
17511
|
+
} catch (err) {
|
|
17512
|
+
return {
|
|
17513
|
+
ok: false,
|
|
17514
|
+
durationMs: Date.now() - start,
|
|
17515
|
+
error: err instanceof Error ? err.message : String(err)
|
|
17516
|
+
};
|
|
17517
|
+
}
|
|
17518
|
+
}
|
|
16934
17519
|
function readBundleHash(cwd) {
|
|
16935
|
-
const indexPath =
|
|
16936
|
-
if (!
|
|
16937
|
-
return extractBundleHash(
|
|
17520
|
+
const indexPath = path28.join(cwd, "packages/control-plane/public/index.html");
|
|
17521
|
+
if (!fs24.existsSync(indexPath)) return null;
|
|
17522
|
+
return extractBundleHash(fs24.readFileSync(indexPath, "utf-8"));
|
|
16938
17523
|
}
|
|
16939
17524
|
async function handleUpgrade(opts) {
|
|
16940
17525
|
const cwd = process.cwd();
|
|
@@ -16969,6 +17554,142 @@ async function handleUpgrade(opts) {
|
|
|
16969
17554
|
return;
|
|
16970
17555
|
}
|
|
16971
17556
|
}
|
|
17557
|
+
if (opts.history) {
|
|
17558
|
+
handleHistory(parseHistoryOpts({ n: opts.historyN, json: opts.historyJson }));
|
|
17559
|
+
return;
|
|
17560
|
+
}
|
|
17561
|
+
if (opts.rollback) {
|
|
17562
|
+
return await handleRollback();
|
|
17563
|
+
}
|
|
17564
|
+
const lock = acquireLock();
|
|
17565
|
+
if (!lock.acquired) {
|
|
17566
|
+
printError(formatRefusalMessage(lock, LOCK_FILE_PATH));
|
|
17567
|
+
process.exitCode = 1;
|
|
17568
|
+
return;
|
|
17569
|
+
}
|
|
17570
|
+
let signalReleased = false;
|
|
17571
|
+
const releaseOnSignal = (signal) => {
|
|
17572
|
+
if (signalReleased) return;
|
|
17573
|
+
signalReleased = true;
|
|
17574
|
+
try {
|
|
17575
|
+
releaseLock();
|
|
17576
|
+
} catch {
|
|
17577
|
+
}
|
|
17578
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
17579
|
+
};
|
|
17580
|
+
process.once("SIGINT", releaseOnSignal);
|
|
17581
|
+
process.once("SIGTERM", releaseOnSignal);
|
|
17582
|
+
const logRow = {
|
|
17583
|
+
started_at: Date.now(),
|
|
17584
|
+
durations_ms: {},
|
|
17585
|
+
sha_target: "",
|
|
17586
|
+
failed_step: null,
|
|
17587
|
+
status: "failed"
|
|
17588
|
+
// default; flipped to 'success' on clean exit
|
|
17589
|
+
};
|
|
17590
|
+
try {
|
|
17591
|
+
await runUpgradeStepsWithLockHeld(opts, cwd, logRow);
|
|
17592
|
+
if (process.exitCode !== 1) logRow.status = "success";
|
|
17593
|
+
} finally {
|
|
17594
|
+
const ended_at = Date.now();
|
|
17595
|
+
const row = {
|
|
17596
|
+
ts: new Date(ended_at).toISOString(),
|
|
17597
|
+
started_at: logRow.started_at,
|
|
17598
|
+
ended_at,
|
|
17599
|
+
sha_target: logRow.sha_target,
|
|
17600
|
+
status: logRow.status,
|
|
17601
|
+
failed_step: logRow.failed_step,
|
|
17602
|
+
durations_ms: logRow.durations_ms
|
|
17603
|
+
};
|
|
17604
|
+
appendUpgradeLog(row);
|
|
17605
|
+
releaseLock();
|
|
17606
|
+
process.removeListener("SIGINT", releaseOnSignal);
|
|
17607
|
+
process.removeListener("SIGTERM", releaseOnSignal);
|
|
17608
|
+
}
|
|
17609
|
+
}
|
|
17610
|
+
async function handleRollback() {
|
|
17611
|
+
printHeader("olam upgrade --rollback");
|
|
17612
|
+
const missing = checkRollbackSetExists(PRODUCTION_SWAP_PLAN);
|
|
17613
|
+
if (missing !== null) {
|
|
17614
|
+
printError(
|
|
17615
|
+
`No rollback-set available \u2014 missing :olam-rollback tag(s): ${missing}
|
|
17616
|
+
|
|
17617
|
+
A rollback-set is created by the FIRST successful \`olam upgrade\`. If this
|
|
17618
|
+
is your first install, run \`olam upgrade\` to populate the rollback set.
|
|
17619
|
+
If a previous upgrade was incomplete, the rollback set may be partial;
|
|
17620
|
+
manually inspect images with \`docker images olam-*:olam-rollback\`.`
|
|
17621
|
+
);
|
|
17622
|
+
process.exitCode = 1;
|
|
17623
|
+
return;
|
|
17624
|
+
}
|
|
17625
|
+
const lock = acquireLock();
|
|
17626
|
+
if (!lock.acquired) {
|
|
17627
|
+
printError(formatRefusalMessage(lock, LOCK_FILE_PATH));
|
|
17628
|
+
process.exitCode = 1;
|
|
17629
|
+
return;
|
|
17630
|
+
}
|
|
17631
|
+
let signalReleased = false;
|
|
17632
|
+
const releaseOnSignal = (signal) => {
|
|
17633
|
+
if (signalReleased) return;
|
|
17634
|
+
signalReleased = true;
|
|
17635
|
+
try {
|
|
17636
|
+
releaseLock();
|
|
17637
|
+
} catch {
|
|
17638
|
+
}
|
|
17639
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
17640
|
+
};
|
|
17641
|
+
process.once("SIGINT", releaseOnSignal);
|
|
17642
|
+
process.once("SIGTERM", releaseOnSignal);
|
|
17643
|
+
try {
|
|
17644
|
+
process.stdout.write(` ${pc15.dim("rollback retag (3 ops)".padEnd(34))}`);
|
|
17645
|
+
const swapStart = Date.now();
|
|
17646
|
+
const swapResult = performRollbackSwap(PRODUCTION_SWAP_PLAN);
|
|
17647
|
+
const swapDur = `${((Date.now() - swapStart) / 1e3).toFixed(1)}s`;
|
|
17648
|
+
process.stdout.write(`${swapResult.ok ? pc15.green("\u2713") : pc15.red("\u2717")} ${swapDur}
|
|
17649
|
+
`);
|
|
17650
|
+
if (!swapResult.ok) {
|
|
17651
|
+
printError(`Rollback retag failed: ${swapResult.summary}`);
|
|
17652
|
+
process.exitCode = 1;
|
|
17653
|
+
return;
|
|
17654
|
+
}
|
|
17655
|
+
printInfo("Rollback", swapResult.summary);
|
|
17656
|
+
const cwd = process.cwd();
|
|
17657
|
+
const composeFile = path28.join(cwd, "packages/host-cp/compose.yaml");
|
|
17658
|
+
const authSecret = readAuthSecret2();
|
|
17659
|
+
process.stdout.write(` ${pc15.dim("docker compose recreate host-cp".padEnd(34))}`);
|
|
17660
|
+
const composeStart = Date.now();
|
|
17661
|
+
const composeResult = runCompose(["up", "-d", "--force-recreate", "host-cp"], composeFile, buildComposeEnv(authSecret));
|
|
17662
|
+
const composeDur = `${((Date.now() - composeStart) / 1e3).toFixed(1)}s`;
|
|
17663
|
+
process.stdout.write(`${composeResult.ok ? pc15.green("\u2713") : pc15.red("\u2717")} ${composeDur}
|
|
17664
|
+
`);
|
|
17665
|
+
if (!composeResult.ok) {
|
|
17666
|
+
printError(
|
|
17667
|
+
`Rollback compose recreate failed:
|
|
17668
|
+
${composeResult.stderr}
|
|
17669
|
+
Canonical tags are at :olam-rollback (good); container restart pending. Manually: \`docker compose -f packages/host-cp/compose.yaml up -d --force-recreate host-cp\`.`
|
|
17670
|
+
);
|
|
17671
|
+
process.exitCode = 1;
|
|
17672
|
+
return;
|
|
17673
|
+
}
|
|
17674
|
+
process.stdout.write(` ${pc15.dim("recreate auth-service".padEnd(34))}`);
|
|
17675
|
+
const authResult = await recreateAuthService();
|
|
17676
|
+
const authDur = `${(authResult.durationMs / 1e3).toFixed(1)}s`;
|
|
17677
|
+
process.stdout.write(`${authResult.ok ? pc15.green("\u2713") : pc15.red("\u2717")} ${authDur}
|
|
17678
|
+
`);
|
|
17679
|
+
if (!authResult.ok) {
|
|
17680
|
+
printError(`Auth-service recreate failed: ${authResult.error ?? "unknown"}`);
|
|
17681
|
+
process.exitCode = 1;
|
|
17682
|
+
return;
|
|
17683
|
+
}
|
|
17684
|
+
process.stdout.write("\n");
|
|
17685
|
+
printSuccess("Rollback complete \u2014 canonical tags restored from :olam-rollback");
|
|
17686
|
+
} finally {
|
|
17687
|
+
releaseLock();
|
|
17688
|
+
process.removeListener("SIGINT", releaseOnSignal);
|
|
17689
|
+
process.removeListener("SIGTERM", releaseOnSignal);
|
|
17690
|
+
}
|
|
17691
|
+
}
|
|
17692
|
+
async function runUpgradeStepsWithLockHeld(opts, cwd, logRow) {
|
|
16972
17693
|
if (opts.branch !== null) {
|
|
16973
17694
|
if (isGitDirty(cwd)) {
|
|
16974
17695
|
printError(
|
|
@@ -17026,6 +17747,17 @@ If there are conflicts, resolve them manually then re-run \`olam upgrade\`.`
|
|
|
17026
17747
|
process.exitCode = 1;
|
|
17027
17748
|
return;
|
|
17028
17749
|
}
|
|
17750
|
+
const _targetSha = captureHeadSha(cwd);
|
|
17751
|
+
logRow.sha_target = _targetSha ?? "";
|
|
17752
|
+
if (_targetSha === null) {
|
|
17753
|
+
logRow.failed_step = "capture HEAD SHA";
|
|
17754
|
+
printError(
|
|
17755
|
+
"Failed to capture HEAD SHA via `git rev-parse HEAD`. Aborting upgrade.\nRe-run from a clean git checkout; ensure `git rev-parse HEAD` returns a 40-char SHA."
|
|
17756
|
+
);
|
|
17757
|
+
process.exitCode = 1;
|
|
17758
|
+
return;
|
|
17759
|
+
}
|
|
17760
|
+
printInfo("Target SHA", abbreviateSha(_targetSha));
|
|
17029
17761
|
const installDecision = shouldSkipInstall(opts, cwd);
|
|
17030
17762
|
if (installDecision.skip) {
|
|
17031
17763
|
printInfo("npm install", `skipped \u2014 ${installDecision.reason}`);
|
|
@@ -17063,7 +17795,7 @@ ${buildResult.stderr}`);
|
|
|
17063
17795
|
return;
|
|
17064
17796
|
}
|
|
17065
17797
|
const authSecret = readAuthSecret2();
|
|
17066
|
-
const spaDir =
|
|
17798
|
+
const spaDir = path28.join(cwd, "packages/control-plane/app");
|
|
17067
17799
|
const spaResult = runStep2(
|
|
17068
17800
|
"vite build (SPA)",
|
|
17069
17801
|
"npx",
|
|
@@ -17085,21 +17817,107 @@ ${spaResult.stderr}`);
|
|
|
17085
17817
|
printTimings2(timings);
|
|
17086
17818
|
return;
|
|
17087
17819
|
}
|
|
17088
|
-
const
|
|
17089
|
-
|
|
17090
|
-
|
|
17091
|
-
|
|
17092
|
-
|
|
17093
|
-
{
|
|
17094
|
-
|
|
17095
|
-
|
|
17096
|
-
|
|
17097
|
-
|
|
17098
|
-
|
|
17820
|
+
const olamTagEnv = { OLAM_TAG: "olam-next" };
|
|
17821
|
+
if (opts.noCache) {
|
|
17822
|
+
olamTagEnv.DOCKER_BUILD_NO_CACHE = "1";
|
|
17823
|
+
}
|
|
17824
|
+
const buildScripts = [
|
|
17825
|
+
{ label: "bash build-auth.sh", relPath: "packages/adapters/src/docker/build-auth.sh", tee: false },
|
|
17826
|
+
{ label: "bash build-devbox.sh", relPath: "packages/adapters/src/docker/build-devbox.sh", tee: true },
|
|
17827
|
+
{ label: "bash build-host-cp.sh", relPath: "packages/adapters/src/docker/build-host-cp.sh", tee: false }
|
|
17828
|
+
];
|
|
17829
|
+
for (const step of buildScripts) {
|
|
17830
|
+
const scriptPath = path28.join(cwd, step.relPath);
|
|
17831
|
+
if (step.tee) {
|
|
17832
|
+
process.stdout.write(` ${pc15.dim(step.label.padEnd(34))}
|
|
17833
|
+
`);
|
|
17834
|
+
const start = Date.now();
|
|
17835
|
+
const result = spawnSync7("bash", [scriptPath], {
|
|
17836
|
+
stdio: "inherit",
|
|
17837
|
+
cwd,
|
|
17838
|
+
env: { ...process.env, ...olamTagEnv }
|
|
17839
|
+
});
|
|
17840
|
+
const durationMs = Date.now() - start;
|
|
17841
|
+
const ok = result.status === 0 && result.error === void 0;
|
|
17842
|
+
const dur = `${(durationMs / 1e3).toFixed(1)}s`;
|
|
17843
|
+
process.stdout.write(` ${pc15.dim(step.label.padEnd(34))}${ok ? pc15.green("\u2713") : pc15.red("\u2717")} ${dur}
|
|
17844
|
+
`);
|
|
17845
|
+
timings.push({ label: step.label, durationMs });
|
|
17846
|
+
if (!ok) {
|
|
17847
|
+
printError(`${step.label} failed (see output above for details).`);
|
|
17848
|
+
process.exitCode = 1;
|
|
17849
|
+
return;
|
|
17850
|
+
}
|
|
17851
|
+
} else {
|
|
17852
|
+
const result = runStep2(step.label, "bash", [scriptPath], {
|
|
17853
|
+
cwd,
|
|
17854
|
+
env: olamTagEnv
|
|
17855
|
+
});
|
|
17856
|
+
timings.push({ label: step.label, durationMs: result.durationMs });
|
|
17857
|
+
logRow.durations_ms[step.label] = result.durationMs;
|
|
17858
|
+
if (!result.ok) {
|
|
17859
|
+
logRow.failed_step = step.label;
|
|
17860
|
+
printError(`${step.label} failed:
|
|
17861
|
+
${result.stderr.split("\n").slice(-3).join("\n")}`);
|
|
17862
|
+
process.exitCode = 1;
|
|
17863
|
+
return;
|
|
17864
|
+
}
|
|
17865
|
+
}
|
|
17866
|
+
}
|
|
17867
|
+
for (const t of timings) logRow.durations_ms[t.label] = t.durationMs;
|
|
17868
|
+
const smokeStart = Date.now();
|
|
17869
|
+
process.stdout.write(` ${pc15.dim("smoke (docker create + inspect)".padEnd(34))}`);
|
|
17870
|
+
const smokeImages = [
|
|
17871
|
+
"olam-auth:olam-next",
|
|
17872
|
+
"olam-devbox:olam-next",
|
|
17873
|
+
"olam-host-cp:olam-next"
|
|
17874
|
+
];
|
|
17875
|
+
const smokeResults = smokeImages.map((img) => smokeImage(img, _targetSha));
|
|
17876
|
+
const smokeFailures = smokeResults.filter((r) => !r.ok);
|
|
17877
|
+
const smokeDurationMs = Date.now() - smokeStart;
|
|
17878
|
+
const smokeDur = `${(smokeDurationMs / 1e3).toFixed(1)}s`;
|
|
17879
|
+
process.stdout.write(`${smokeFailures.length === 0 ? pc15.green("\u2713") : pc15.red("\u2717")} ${smokeDur}
|
|
17880
|
+
`);
|
|
17881
|
+
timings.push({ label: "smoke", durationMs: smokeDurationMs });
|
|
17882
|
+
if (smokeFailures.length > 0) {
|
|
17883
|
+
printError(
|
|
17884
|
+
`Smoke failed for ${smokeFailures.length} of ${smokeResults.length} images:
|
|
17885
|
+
` + smokeFailures.map((r) => ` - ${r.image}: ${r.error}`).join("\n") + "\nCanonical tags (`:latest`/`:local`) untouched. Investigate the failed image(s), then re-run `olam upgrade` (--no-cache if cache-poisoning suspected)."
|
|
17886
|
+
);
|
|
17099
17887
|
process.exitCode = 1;
|
|
17100
17888
|
return;
|
|
17101
17889
|
}
|
|
17102
|
-
const
|
|
17890
|
+
const swapBoundarySha = captureHeadSha(cwd);
|
|
17891
|
+
if (swapBoundarySha !== null && swapBoundarySha !== _targetSha && !opts.force) {
|
|
17892
|
+
printError(
|
|
17893
|
+
`HEAD drifted during build window:
|
|
17894
|
+
captured (after pull): ${abbreviateSha(_targetSha)}
|
|
17895
|
+
current at swap: ${abbreviateSha(swapBoundarySha)}
|
|
17896
|
+
|
|
17897
|
+
Operator-driven \`git checkout\` or \`git reset\` triggered drift.
|
|
17898
|
+
Recovery options:
|
|
17899
|
+
\u2022 Re-run \`olam upgrade\` (will rebuild against current HEAD).
|
|
17900
|
+
\u2022 Pass \`--force\` to swap anyway (canonical advances to the
|
|
17901
|
+
captured-at-pull SHA, NOT current HEAD).`
|
|
17902
|
+
);
|
|
17903
|
+
process.exitCode = 1;
|
|
17904
|
+
return;
|
|
17905
|
+
}
|
|
17906
|
+
process.stdout.write(` ${pc15.dim("atomic 6-tag swap".padEnd(34))}`);
|
|
17907
|
+
const swapStart = Date.now();
|
|
17908
|
+
const swapResult = performAtomicSwap(PRODUCTION_SWAP_PLAN);
|
|
17909
|
+
const swapDurationMs = Date.now() - swapStart;
|
|
17910
|
+
const swapDur = `${(swapDurationMs / 1e3).toFixed(1)}s`;
|
|
17911
|
+
process.stdout.write(`${swapResult.ok ? pc15.green("\u2713") : pc15.red("\u2717")} ${swapDur}
|
|
17912
|
+
`);
|
|
17913
|
+
timings.push({ label: "atomic swap", durationMs: swapDurationMs });
|
|
17914
|
+
if (!swapResult.ok) {
|
|
17915
|
+
printError(`Atomic swap failed: ${swapResult.summary}`);
|
|
17916
|
+
process.exitCode = 1;
|
|
17917
|
+
return;
|
|
17918
|
+
}
|
|
17919
|
+
printInfo("Swap", swapResult.summary);
|
|
17920
|
+
const composeFile = path28.join(cwd, "packages/host-cp/compose.yaml");
|
|
17103
17921
|
process.stdout.write(` ${pc15.dim("docker compose recreate".padEnd(34))}`);
|
|
17104
17922
|
const composeStart = Date.now();
|
|
17105
17923
|
const composeResult = runCompose(
|
|
@@ -17114,8 +17932,33 @@ ${imageResult.stderr}`);
|
|
|
17114
17932
|
`);
|
|
17115
17933
|
timings.push({ label: "container recreate", durationMs: composeDurationMs });
|
|
17116
17934
|
if (!composeOk) {
|
|
17117
|
-
printError(
|
|
17118
|
-
|
|
17935
|
+
printError(
|
|
17936
|
+
`docker compose up --force-recreate failed:
|
|
17937
|
+
${composeResult.stderr}
|
|
17938
|
+
|
|
17939
|
+
Canonical tags advanced to new SHA but the stack failed to start.
|
|
17940
|
+
Recovery options:
|
|
17941
|
+
\u2022 Run \`olam upgrade --rollback\` to restore the prior :olam-rollback set, then investigate.
|
|
17942
|
+
\u2022 Manually \`docker logs olam-host-cp\` to diagnose; if recoverable, retry recreate without rollback.`
|
|
17943
|
+
);
|
|
17944
|
+
process.exitCode = 1;
|
|
17945
|
+
return;
|
|
17946
|
+
}
|
|
17947
|
+
process.stdout.write(` ${pc15.dim("recreate auth-service".padEnd(34))}`);
|
|
17948
|
+
const authResult = await recreateAuthService();
|
|
17949
|
+
const authDur = `${(authResult.durationMs / 1e3).toFixed(1)}s`;
|
|
17950
|
+
process.stdout.write(`${authResult.ok ? pc15.green("\u2713") : pc15.red("\u2717")} ${authDur}
|
|
17951
|
+
`);
|
|
17952
|
+
timings.push({ label: "auth recreate", durationMs: authResult.durationMs });
|
|
17953
|
+
if (!authResult.ok) {
|
|
17954
|
+
printError(
|
|
17955
|
+
`Auth-service recreate failed: ${authResult.error ?? "unknown"}
|
|
17956
|
+
|
|
17957
|
+
Canonical tags advanced to new SHA; host-cp recreated but auth-service is broken.
|
|
17958
|
+
Recovery options:
|
|
17959
|
+
\u2022 Run \`olam upgrade --rollback\` to restore the prior :olam-rollback set + working stack.
|
|
17960
|
+
\u2022 Manually: \`docker logs olam-auth\` to diagnose; \`olam auth up\` to restart.`
|
|
17961
|
+
);
|
|
17119
17962
|
process.exitCode = 1;
|
|
17120
17963
|
return;
|
|
17121
17964
|
}
|
|
@@ -17128,7 +17971,23 @@ ${composeResult.stderr}`);
|
|
|
17128
17971
|
`);
|
|
17129
17972
|
timings.push({ label: "/health", durationMs: healthDurationMs });
|
|
17130
17973
|
if (!healthy) {
|
|
17131
|
-
printWarning(
|
|
17974
|
+
printWarning(
|
|
17975
|
+
"Host CP started but /health did not respond within 10s.\n \u2022 Check: docker logs olam-host-cp\n \u2022 If the new SHA is broken: `olam upgrade --rollback` restores the prior set in <30s."
|
|
17976
|
+
);
|
|
17977
|
+
}
|
|
17978
|
+
process.stdout.write(` ${pc15.dim("verify /version/status round-trip".padEnd(34))}`);
|
|
17979
|
+
const versionStart = Date.now();
|
|
17980
|
+
const versionMatch = await waitForVersionMatch(_targetSha, 6e4);
|
|
17981
|
+
const versionDurationMs = Date.now() - versionStart;
|
|
17982
|
+
const versionDur = `${(versionDurationMs / 1e3).toFixed(1)}s`;
|
|
17983
|
+
process.stdout.write(`${versionMatch.matched ? pc15.green("\u2713") : pc15.yellow("?")} ${versionDur}
|
|
17984
|
+
`);
|
|
17985
|
+
timings.push({ label: "/version/status round-trip", durationMs: versionDurationMs });
|
|
17986
|
+
if (!versionMatch.matched) {
|
|
17987
|
+
printWarning(
|
|
17988
|
+
`Version round-trip incomplete after ${(versionDurationMs / 1e3).toFixed(0)}s:
|
|
17989
|
+
` + formatVersionMismatch(_targetSha, versionMatch.snapshot) + "\n \u2022 Banner may still show UPDATE AVAILABLE until host-cp's next poll cycle (~60s).\n \u2022 If the mismatch persists, `olam upgrade --rollback` restores the prior :olam-rollback set."
|
|
17990
|
+
);
|
|
17132
17991
|
}
|
|
17133
17992
|
process.stdout.write("\n");
|
|
17134
17993
|
printSuccess("Upgrade complete");
|
|
@@ -17145,10 +18004,22 @@ function printTimings2(timings) {
|
|
|
17145
18004
|
printInfo("total", `${(total / 1e3).toFixed(1)}s`);
|
|
17146
18005
|
}
|
|
17147
18006
|
function registerUpgrade(program2) {
|
|
17148
|
-
program2.command("upgrade").description("Self-upgrade the local Olam dev stack (pull + rebuild + restart
|
|
18007
|
+
program2.command("upgrade").description("Self-upgrade the local Olam dev stack (pull + rebuild + restart all three components)").option("-y, --yes", "Skip the confirmation prompt").option("--skip-image", "Skip docker image rebuild + container recreate (source rebuild only)").option(
|
|
17149
18008
|
"--skip-install",
|
|
17150
18009
|
"Skip npm install entirely (use existing node_modules as-is). Useful when a native-module build failure blocks the normal upgrade path."
|
|
17151
|
-
).option("--branch <name>", "Switch to this branch before pulling (refuses if working tree is dirty)").
|
|
18010
|
+
).option("--branch <name>", "Switch to this branch before pulling (refuses if working tree is dirty)").option(
|
|
18011
|
+
"--rollback",
|
|
18012
|
+
"Restore canonical tags from the :olam-rollback set (created by the prior successful upgrade).\n No git pull, no build, no smoke \u2014 just retag + recreate."
|
|
18013
|
+
).option(
|
|
18014
|
+
"--force",
|
|
18015
|
+
"Bypass HEAD-drift refusal at the swap boundary. Swap advances canonical to the\n captured-at-pull SHA even if current HEAD differs."
|
|
18016
|
+
).option(
|
|
18017
|
+
"--no-cache",
|
|
18018
|
+
"Pass --no-cache to all three build scripts (DOCKER_BUILD_NO_CACHE=1).\n Useful when retrying after a cache-poisoning failure."
|
|
18019
|
+
).option(
|
|
18020
|
+
"--history",
|
|
18021
|
+
"Print the upgrade history (~/.olam/upgrade.log) and exit.\n No upgrade is performed."
|
|
18022
|
+
).option("-n <count>", "Number of history rows to print (default 10)", "10").option("--json", "Emit history as JSONL instead of a table").action(async (opts) => {
|
|
17152
18023
|
await handleUpgrade(parseUpgradeOpts(opts));
|
|
17153
18024
|
});
|
|
17154
18025
|
}
|
|
@@ -17282,7 +18153,7 @@ function registerLogs(program2) {
|
|
|
17282
18153
|
// src/commands/ps.ts
|
|
17283
18154
|
init_context();
|
|
17284
18155
|
import pc17 from "picocolors";
|
|
17285
|
-
import { spawnSync as
|
|
18156
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
17286
18157
|
var SAFE_IDENT4 = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
17287
18158
|
function parseDockerTop(stdout) {
|
|
17288
18159
|
const trimmed = stdout.trim();
|
|
@@ -17382,7 +18253,7 @@ function registerPs(program2) {
|
|
|
17382
18253
|
const containerName = `olam-${worldId}-devbox`;
|
|
17383
18254
|
let watchInterval;
|
|
17384
18255
|
function fetchAndPrint() {
|
|
17385
|
-
const result =
|
|
18256
|
+
const result = spawnSync8(
|
|
17386
18257
|
"docker",
|
|
17387
18258
|
["top", containerName, "pid", "user", "pcpu", "pmem", "stime", "stat", "cmd"],
|
|
17388
18259
|
{ encoding: "utf-8", timeout: 3e3 }
|
|
@@ -17418,20 +18289,20 @@ ${pc17.dim(`world: ${worldId} sort: ${sortKey} refresh: 5s Ctrl-C to exit`)}
|
|
|
17418
18289
|
}
|
|
17419
18290
|
|
|
17420
18291
|
// src/commands/keys.ts
|
|
17421
|
-
import * as
|
|
17422
|
-
import * as
|
|
17423
|
-
import * as
|
|
18292
|
+
import * as fs25 from "node:fs";
|
|
18293
|
+
import * as os15 from "node:os";
|
|
18294
|
+
import * as path29 from "node:path";
|
|
17424
18295
|
import YAML4 from "yaml";
|
|
17425
18296
|
function olamHome2() {
|
|
17426
|
-
return process.env.OLAM_HOME ??
|
|
18297
|
+
return process.env.OLAM_HOME ?? path29.join(os15.homedir(), ".olam");
|
|
17427
18298
|
}
|
|
17428
18299
|
function keysFilePath() {
|
|
17429
|
-
return
|
|
18300
|
+
return path29.join(olamHome2(), "keys.yaml");
|
|
17430
18301
|
}
|
|
17431
18302
|
function readKeysFile() {
|
|
17432
18303
|
const filePath = keysFilePath();
|
|
17433
|
-
if (!
|
|
17434
|
-
const raw =
|
|
18304
|
+
if (!fs25.existsSync(filePath)) return null;
|
|
18305
|
+
const raw = fs25.readFileSync(filePath, "utf-8").trim();
|
|
17435
18306
|
if (raw.length === 0) return null;
|
|
17436
18307
|
try {
|
|
17437
18308
|
const parsed = YAML4.parse(raw);
|
|
@@ -17447,13 +18318,13 @@ function readKeysFile() {
|
|
|
17447
18318
|
}
|
|
17448
18319
|
function writeKeysFile(keys) {
|
|
17449
18320
|
const dir = olamHome2();
|
|
17450
|
-
if (!
|
|
17451
|
-
|
|
18321
|
+
if (!fs25.existsSync(dir)) {
|
|
18322
|
+
fs25.mkdirSync(dir, { recursive: true });
|
|
17452
18323
|
}
|
|
17453
18324
|
const filePath = keysFilePath();
|
|
17454
18325
|
const content = YAML4.stringify(keys);
|
|
17455
|
-
|
|
17456
|
-
|
|
18326
|
+
fs25.writeFileSync(filePath, content, { encoding: "utf-8", mode: 384 });
|
|
18327
|
+
fs25.chmodSync(filePath, 384);
|
|
17457
18328
|
}
|
|
17458
18329
|
function redact(value) {
|
|
17459
18330
|
if (value.length <= 8) return value + "...";
|
|
@@ -17496,7 +18367,7 @@ function registerKeys(program2) {
|
|
|
17496
18367
|
}
|
|
17497
18368
|
const { [key]: _removed, ...rest } = existing;
|
|
17498
18369
|
if (Object.keys(rest).length === 0) {
|
|
17499
|
-
|
|
18370
|
+
fs25.unlinkSync(keysFilePath());
|
|
17500
18371
|
} else {
|
|
17501
18372
|
writeKeysFile(rest);
|
|
17502
18373
|
}
|
|
@@ -17519,26 +18390,26 @@ function registerKeys(program2) {
|
|
|
17519
18390
|
}
|
|
17520
18391
|
|
|
17521
18392
|
// src/commands/world-snapshot.ts
|
|
17522
|
-
import * as
|
|
17523
|
-
import * as
|
|
18393
|
+
import * as fs27 from "node:fs";
|
|
18394
|
+
import * as path31 from "node:path";
|
|
17524
18395
|
import { execSync as execSync9 } from "node:child_process";
|
|
17525
18396
|
import pc18 from "picocolors";
|
|
17526
18397
|
|
|
17527
18398
|
// ../core/src/world/snapshot.ts
|
|
17528
18399
|
import * as crypto6 from "node:crypto";
|
|
17529
|
-
import * as
|
|
17530
|
-
import * as
|
|
17531
|
-
import * as
|
|
18400
|
+
import * as fs26 from "node:fs";
|
|
18401
|
+
import * as os16 from "node:os";
|
|
18402
|
+
import * as path30 from "node:path";
|
|
17532
18403
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
17533
18404
|
function snapshotsDir() {
|
|
17534
|
-
return process.env["OLAM_SNAPSHOTS_DIR"] ??
|
|
18405
|
+
return process.env["OLAM_SNAPSHOTS_DIR"] ?? path30.join(os16.homedir(), ".olam", "snapshots");
|
|
17535
18406
|
}
|
|
17536
18407
|
function snapshotKindDir(worldId, kind) {
|
|
17537
|
-
return
|
|
18408
|
+
return path30.join(snapshotsDir(), worldId, kind);
|
|
17538
18409
|
}
|
|
17539
18410
|
function snapshotTarPath(worldId, kind, repoName, hash) {
|
|
17540
18411
|
const base = repoName ? `${repoName}-${hash}` : hash;
|
|
17541
|
-
return
|
|
18412
|
+
return path30.join(snapshotKindDir(worldId, kind), `${base}.tar.gz`);
|
|
17542
18413
|
}
|
|
17543
18414
|
function manifestPath(tarPath) {
|
|
17544
18415
|
return tarPath.replace(/\.tar\.gz$/, ".manifest.json");
|
|
@@ -17555,16 +18426,16 @@ function hashBuffers(entries) {
|
|
|
17555
18426
|
return hash.digest("hex").slice(0, 12);
|
|
17556
18427
|
}
|
|
17557
18428
|
function computeGemsFingerprint(repoDir) {
|
|
17558
|
-
const lockfile =
|
|
17559
|
-
if (!
|
|
17560
|
-
return hashBuffers([{ path: "Gemfile.lock", content:
|
|
18429
|
+
const lockfile = path30.join(repoDir, "Gemfile.lock");
|
|
18430
|
+
if (!fs26.existsSync(lockfile)) return null;
|
|
18431
|
+
return hashBuffers([{ path: "Gemfile.lock", content: fs26.readFileSync(lockfile) }]);
|
|
17561
18432
|
}
|
|
17562
18433
|
function computeNodeFingerprint(repoDir) {
|
|
17563
18434
|
const candidates = ["yarn.lock", "pnpm-lock.yaml", "package-lock.json"];
|
|
17564
18435
|
for (const name of candidates) {
|
|
17565
|
-
const lockfile =
|
|
17566
|
-
if (
|
|
17567
|
-
return hashBuffers([{ path: name, content:
|
|
18436
|
+
const lockfile = path30.join(repoDir, name);
|
|
18437
|
+
if (fs26.existsSync(lockfile)) {
|
|
18438
|
+
return hashBuffers([{ path: name, content: fs26.readFileSync(lockfile) }]);
|
|
17568
18439
|
}
|
|
17569
18440
|
}
|
|
17570
18441
|
return null;
|
|
@@ -17574,59 +18445,59 @@ function computePgFingerprint(repoDirs) {
|
|
|
17574
18445
|
const entries = [];
|
|
17575
18446
|
for (const repoDir of repoDirs) {
|
|
17576
18447
|
for (const pattern of patterns) {
|
|
17577
|
-
const filePath =
|
|
17578
|
-
if (
|
|
17579
|
-
entries.push({ path: filePath, content:
|
|
18448
|
+
const filePath = path30.join(repoDir, pattern);
|
|
18449
|
+
if (fs26.existsSync(filePath)) {
|
|
18450
|
+
entries.push({ path: filePath, content: fs26.readFileSync(filePath) });
|
|
17580
18451
|
}
|
|
17581
18452
|
}
|
|
17582
18453
|
}
|
|
17583
18454
|
return entries.length > 0 ? hashBuffers(entries) : null;
|
|
17584
18455
|
}
|
|
17585
18456
|
function packTarball(srcDir, destPath, opts = {}) {
|
|
17586
|
-
|
|
18457
|
+
fs26.mkdirSync(path30.dirname(destPath), { recursive: true });
|
|
17587
18458
|
const tmp = `${destPath}.tmp`;
|
|
17588
18459
|
const args = [];
|
|
17589
18460
|
if (opts.followSymlinks) args.push("-h");
|
|
17590
18461
|
args.push("-czf", tmp, "-C", srcDir, ".");
|
|
17591
18462
|
try {
|
|
17592
18463
|
execFileSync4("tar", args, { stdio: "pipe" });
|
|
17593
|
-
|
|
18464
|
+
fs26.renameSync(tmp, destPath);
|
|
17594
18465
|
} catch (err) {
|
|
17595
18466
|
try {
|
|
17596
|
-
|
|
18467
|
+
fs26.rmSync(tmp, { force: true });
|
|
17597
18468
|
} catch {
|
|
17598
18469
|
}
|
|
17599
18470
|
throw err;
|
|
17600
18471
|
}
|
|
17601
18472
|
}
|
|
17602
18473
|
function writeManifest(manifest, tarPath) {
|
|
17603
|
-
|
|
18474
|
+
fs26.writeFileSync(manifestPath(tarPath), JSON.stringify(manifest, null, 2), "utf-8");
|
|
17604
18475
|
}
|
|
17605
18476
|
function readManifest(tarPath) {
|
|
17606
18477
|
const mPath = manifestPath(tarPath);
|
|
17607
|
-
if (!
|
|
18478
|
+
if (!fs26.existsSync(mPath)) return null;
|
|
17608
18479
|
try {
|
|
17609
|
-
return JSON.parse(
|
|
18480
|
+
return JSON.parse(fs26.readFileSync(mPath, "utf-8"));
|
|
17610
18481
|
} catch {
|
|
17611
18482
|
return null;
|
|
17612
18483
|
}
|
|
17613
18484
|
}
|
|
17614
18485
|
function listSnapshots(worldIdFilter) {
|
|
17615
18486
|
const root = snapshotsDir();
|
|
17616
|
-
if (!
|
|
18487
|
+
if (!fs26.existsSync(root)) return [];
|
|
17617
18488
|
const now = Date.now();
|
|
17618
18489
|
const results = [];
|
|
17619
|
-
const worlds = worldIdFilter ? [worldIdFilter] :
|
|
18490
|
+
const worlds = worldIdFilter ? [worldIdFilter] : fs26.readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
17620
18491
|
for (const worldId of worlds) {
|
|
17621
|
-
const worldDir =
|
|
17622
|
-
if (!
|
|
18492
|
+
const worldDir = path30.join(root, worldId);
|
|
18493
|
+
if (!fs26.existsSync(worldDir) || !fs26.statSync(worldDir).isDirectory()) continue;
|
|
17623
18494
|
for (const kind of ["gems", "node", "pg"]) {
|
|
17624
|
-
const kindDir =
|
|
17625
|
-
if (!
|
|
17626
|
-
const tarballs =
|
|
18495
|
+
const kindDir = path30.join(worldDir, kind);
|
|
18496
|
+
if (!fs26.existsSync(kindDir)) continue;
|
|
18497
|
+
const tarballs = fs26.readdirSync(kindDir).filter((f) => f.endsWith(".tar.gz"));
|
|
17627
18498
|
for (const tarFile of tarballs) {
|
|
17628
|
-
const tarPath =
|
|
17629
|
-
const stat =
|
|
18499
|
+
const tarPath = path30.join(kindDir, tarFile);
|
|
18500
|
+
const stat = fs26.statSync(tarPath);
|
|
17630
18501
|
const manifest = readManifest(tarPath);
|
|
17631
18502
|
if (!manifest) continue;
|
|
17632
18503
|
results.push({ manifest, tarPath, ageMs: now - stat.mtimeMs });
|
|
@@ -17705,17 +18576,17 @@ function resolveKinds(arg) {
|
|
|
17705
18576
|
return [];
|
|
17706
18577
|
}
|
|
17707
18578
|
async function captureGems(worldId, workspacePath, repo) {
|
|
17708
|
-
const repoDir =
|
|
18579
|
+
const repoDir = path31.join(workspacePath, repo);
|
|
17709
18580
|
const fingerprint = computeGemsFingerprint(repoDir);
|
|
17710
18581
|
if (!fingerprint) {
|
|
17711
18582
|
return { ok: false, tarPath: "", msg: "no Gemfile.lock \u2014 layer does not apply" };
|
|
17712
18583
|
}
|
|
17713
18584
|
const tarPath = snapshotTarPath(worldId, "gems", repo, fingerprint);
|
|
17714
|
-
const vendorBundle =
|
|
17715
|
-
if (
|
|
18585
|
+
const vendorBundle = path31.join(repoDir, "vendor", "bundle");
|
|
18586
|
+
if (fs27.existsSync(vendorBundle)) {
|
|
17716
18587
|
try {
|
|
17717
18588
|
packTarball(vendorBundle, tarPath);
|
|
17718
|
-
const stat =
|
|
18589
|
+
const stat = fs27.statSync(tarPath);
|
|
17719
18590
|
const manifest = {
|
|
17720
18591
|
kind: "gems",
|
|
17721
18592
|
worldId,
|
|
@@ -17748,10 +18619,10 @@ async function captureGems(worldId, workspacePath, repo) {
|
|
|
17748
18619
|
`docker exec ${containerName} sh -c 'mkdir -p "$(dirname ${tmpTar})" && tar -czf ${tmpTar}.tmp -C ${bundlePath} . && mv ${tmpTar}.tmp ${tmpTar}'`,
|
|
17749
18620
|
{ stdio: "pipe", timeout: 12e4 }
|
|
17750
18621
|
);
|
|
17751
|
-
|
|
18622
|
+
fs27.mkdirSync(path31.dirname(tarPath), { recursive: true });
|
|
17752
18623
|
execSync9(`docker cp ${containerName}:${tmpTar} "${tarPath}"`, { stdio: "pipe", timeout: 12e4 });
|
|
17753
18624
|
execSync9(`docker exec ${containerName} rm -f ${tmpTar}`, { stdio: "pipe" });
|
|
17754
|
-
const stat =
|
|
18625
|
+
const stat = fs27.statSync(tarPath);
|
|
17755
18626
|
const manifest = {
|
|
17756
18627
|
kind: "gems",
|
|
17757
18628
|
worldId,
|
|
@@ -17768,19 +18639,19 @@ async function captureGems(worldId, workspacePath, repo) {
|
|
|
17768
18639
|
}
|
|
17769
18640
|
}
|
|
17770
18641
|
async function captureNode(worldId, workspacePath, repo) {
|
|
17771
|
-
const repoDir =
|
|
18642
|
+
const repoDir = path31.join(workspacePath, repo);
|
|
17772
18643
|
const fingerprint = computeNodeFingerprint(repoDir);
|
|
17773
18644
|
if (!fingerprint) {
|
|
17774
18645
|
return { ok: false, tarPath: "", msg: "no lockfile \u2014 layer does not apply" };
|
|
17775
18646
|
}
|
|
17776
|
-
const nodeModules =
|
|
17777
|
-
if (!
|
|
18647
|
+
const nodeModules = path31.join(repoDir, "node_modules");
|
|
18648
|
+
if (!fs27.existsSync(nodeModules)) {
|
|
17778
18649
|
return { ok: false, tarPath: "", msg: "node_modules not installed yet" };
|
|
17779
18650
|
}
|
|
17780
18651
|
const tarPath = snapshotTarPath(worldId, "node", repo, fingerprint);
|
|
17781
18652
|
try {
|
|
17782
18653
|
packTarball(nodeModules, tarPath);
|
|
17783
|
-
const stat =
|
|
18654
|
+
const stat = fs27.statSync(tarPath);
|
|
17784
18655
|
const manifest = {
|
|
17785
18656
|
kind: "node",
|
|
17786
18657
|
worldId,
|
|
@@ -17797,7 +18668,7 @@ async function captureNode(worldId, workspacePath, repo) {
|
|
|
17797
18668
|
}
|
|
17798
18669
|
}
|
|
17799
18670
|
async function capturePg(worldId, workspacePath, repoNames) {
|
|
17800
|
-
const repoDirs = repoNames.map((r) =>
|
|
18671
|
+
const repoDirs = repoNames.map((r) => path31.join(workspacePath, r));
|
|
17801
18672
|
const fingerprint = computePgFingerprint(repoDirs);
|
|
17802
18673
|
if (!fingerprint) {
|
|
17803
18674
|
return { ok: false, tarPath: "", msg: "no Gemfile.lock / schema.rb \u2014 layer does not apply" };
|
|
@@ -17812,13 +18683,13 @@ async function capturePg(worldId, workspacePath, repoNames) {
|
|
|
17812
18683
|
}
|
|
17813
18684
|
try {
|
|
17814
18685
|
execSync9(`docker stop ${containerName}`, { stdio: "pipe", timeout: 3e4 });
|
|
17815
|
-
|
|
18686
|
+
fs27.mkdirSync(path31.dirname(tarPath), { recursive: true });
|
|
17816
18687
|
execSync9(
|
|
17817
|
-
`docker run --rm -v "${volumeName2}:/pgdata:ro" -v "${
|
|
18688
|
+
`docker run --rm -v "${volumeName2}:/pgdata:ro" -v "${path31.dirname(tarPath)}:/dest" alpine sh -c 'tar -czf /dest/${path31.basename(tarPath)}.tmp -C /pgdata . && mv /dest/${path31.basename(tarPath)}.tmp /dest/${path31.basename(tarPath)}'`,
|
|
17818
18689
|
{ stdio: "pipe", timeout: 18e4 }
|
|
17819
18690
|
);
|
|
17820
18691
|
execSync9(`docker start ${containerName}`, { stdio: "pipe", timeout: 3e4 });
|
|
17821
|
-
const stat =
|
|
18692
|
+
const stat = fs27.statSync(tarPath);
|
|
17822
18693
|
const manifest = {
|
|
17823
18694
|
kind: "pg",
|
|
17824
18695
|
worldId,
|
|
@@ -17892,35 +18763,35 @@ function formatAge2(ms) {
|
|
|
17892
18763
|
|
|
17893
18764
|
// src/commands/refresh.ts
|
|
17894
18765
|
init_context();
|
|
17895
|
-
import * as
|
|
17896
|
-
import * as
|
|
17897
|
-
import * as
|
|
17898
|
-
import { spawnSync as
|
|
18766
|
+
import * as fs29 from "node:fs";
|
|
18767
|
+
import * as os17 from "node:os";
|
|
18768
|
+
import * as path33 from "node:path";
|
|
18769
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
17899
18770
|
import ora5 from "ora";
|
|
17900
18771
|
|
|
17901
18772
|
// src/commands/refresh-helpers.ts
|
|
17902
|
-
import * as
|
|
17903
|
-
import * as
|
|
18773
|
+
import * as fs28 from "node:fs";
|
|
18774
|
+
import * as path32 from "node:path";
|
|
17904
18775
|
function collectCpSourceFiles(standaloneDir) {
|
|
17905
|
-
if (!
|
|
18776
|
+
if (!fs28.existsSync(standaloneDir)) {
|
|
17906
18777
|
throw new Error(`CP standalone dir not found: ${standaloneDir}`);
|
|
17907
18778
|
}
|
|
17908
18779
|
const entries = [];
|
|
17909
|
-
const topLevel =
|
|
17910
|
-
const stat =
|
|
18780
|
+
const topLevel = fs28.readdirSync(standaloneDir).filter((f) => {
|
|
18781
|
+
const stat = fs28.statSync(path32.join(standaloneDir, f));
|
|
17911
18782
|
return stat.isFile() && f.endsWith(".mjs") && !f.endsWith(".test.mjs");
|
|
17912
18783
|
}).sort();
|
|
17913
18784
|
for (const f of topLevel) {
|
|
17914
|
-
entries.push({ srcPath:
|
|
18785
|
+
entries.push({ srcPath: path32.join(standaloneDir, f), destRelPath: f });
|
|
17915
18786
|
}
|
|
17916
|
-
const libDir =
|
|
17917
|
-
if (
|
|
17918
|
-
const libFiles =
|
|
17919
|
-
const stat =
|
|
18787
|
+
const libDir = path32.join(standaloneDir, "lib");
|
|
18788
|
+
if (fs28.existsSync(libDir) && fs28.statSync(libDir).isDirectory()) {
|
|
18789
|
+
const libFiles = fs28.readdirSync(libDir).filter((f) => {
|
|
18790
|
+
const stat = fs28.statSync(path32.join(libDir, f));
|
|
17920
18791
|
return stat.isFile() && f.endsWith(".mjs") && !f.endsWith(".test.mjs");
|
|
17921
18792
|
}).sort();
|
|
17922
18793
|
for (const f of libFiles) {
|
|
17923
|
-
entries.push({ srcPath:
|
|
18794
|
+
entries.push({ srcPath: path32.join(libDir, f), destRelPath: `lib/${f}` });
|
|
17924
18795
|
}
|
|
17925
18796
|
}
|
|
17926
18797
|
return entries;
|
|
@@ -17939,7 +18810,7 @@ var RESTART_TIMEOUT_S = 30;
|
|
|
17939
18810
|
var HEALTH_POLL_MS = 500;
|
|
17940
18811
|
var HEALTH_TIMEOUT_MS = 3e4;
|
|
17941
18812
|
function docker(args) {
|
|
17942
|
-
const result =
|
|
18813
|
+
const result = spawnSync9("docker", args, {
|
|
17943
18814
|
encoding: "utf-8",
|
|
17944
18815
|
stdio: ["ignore", "pipe", "pipe"]
|
|
17945
18816
|
});
|
|
@@ -17978,16 +18849,16 @@ async function refreshWorld(worldId, portOffset, standaloneDir, opts) {
|
|
|
17978
18849
|
error: err instanceof Error ? err.message : String(err)
|
|
17979
18850
|
};
|
|
17980
18851
|
}
|
|
17981
|
-
const stagingDir =
|
|
17982
|
-
|
|
18852
|
+
const stagingDir = fs29.mkdtempSync(
|
|
18853
|
+
path33.join(os17.tmpdir(), `olam-refresh-${worldId}-`)
|
|
17983
18854
|
);
|
|
17984
18855
|
try {
|
|
17985
18856
|
const hasLib = entries.some((e) => e.destRelPath.startsWith("lib/"));
|
|
17986
18857
|
if (hasLib) {
|
|
17987
|
-
|
|
18858
|
+
fs29.mkdirSync(path33.join(stagingDir, "lib"), { recursive: true });
|
|
17988
18859
|
}
|
|
17989
18860
|
for (const { srcPath, destRelPath } of entries) {
|
|
17990
|
-
|
|
18861
|
+
fs29.copyFileSync(srcPath, path33.join(stagingDir, destRelPath));
|
|
17991
18862
|
}
|
|
17992
18863
|
const cpResult = docker([
|
|
17993
18864
|
"cp",
|
|
@@ -18002,7 +18873,7 @@ async function refreshWorld(worldId, portOffset, standaloneDir, opts) {
|
|
|
18002
18873
|
};
|
|
18003
18874
|
}
|
|
18004
18875
|
} finally {
|
|
18005
|
-
|
|
18876
|
+
fs29.rmSync(stagingDir, { recursive: true, force: true });
|
|
18006
18877
|
}
|
|
18007
18878
|
if (opts.restart) {
|
|
18008
18879
|
const restartResult = docker([
|
|
@@ -18039,11 +18910,11 @@ function registerRefresh(program2) {
|
|
|
18039
18910
|
process.exitCode = 1;
|
|
18040
18911
|
return;
|
|
18041
18912
|
}
|
|
18042
|
-
const standaloneDir =
|
|
18913
|
+
const standaloneDir = path33.join(
|
|
18043
18914
|
process.cwd(),
|
|
18044
18915
|
"packages/control-plane/standalone"
|
|
18045
18916
|
);
|
|
18046
|
-
if (!
|
|
18917
|
+
if (!fs29.existsSync(standaloneDir)) {
|
|
18047
18918
|
printError(
|
|
18048
18919
|
`CP standalone source not found at ${standaloneDir}.
|
|
18049
18920
|
Run \`olam refresh\` from the olam repo root.`
|
|
@@ -18121,6 +18992,25 @@ Run \`olam refresh\` from the olam repo root.`
|
|
|
18121
18992
|
});
|
|
18122
18993
|
}
|
|
18123
18994
|
|
|
18995
|
+
// src/pleri-config.ts
|
|
18996
|
+
import * as fs30 from "node:fs";
|
|
18997
|
+
import * as path34 from "node:path";
|
|
18998
|
+
function isPleriConfigured(configDir = process.env.OLAM_CONFIG_DIR ?? ".olam") {
|
|
18999
|
+
if (process.env.PLERI_BASE_URL) {
|
|
19000
|
+
return true;
|
|
19001
|
+
}
|
|
19002
|
+
const configPath = path34.join(configDir, "config.yaml");
|
|
19003
|
+
if (!fs30.existsSync(configPath)) {
|
|
19004
|
+
return false;
|
|
19005
|
+
}
|
|
19006
|
+
try {
|
|
19007
|
+
const contents = fs30.readFileSync(configPath, "utf8");
|
|
19008
|
+
return /^[^#\n]*\bpleri:/m.test(contents);
|
|
19009
|
+
} catch {
|
|
19010
|
+
return false;
|
|
19011
|
+
}
|
|
19012
|
+
}
|
|
19013
|
+
|
|
18124
19014
|
// src/index.ts
|
|
18125
19015
|
var program = new Command();
|
|
18126
19016
|
program.name("olam").description("Olam \u2014 isolated development worlds with thought graph capture").version("0.1.0");
|
|
@@ -18134,7 +19024,7 @@ registerList(program);
|
|
|
18134
19024
|
registerStatus(program);
|
|
18135
19025
|
registerDestroy(program);
|
|
18136
19026
|
registerEnter(program);
|
|
18137
|
-
registerCrystallize(program);
|
|
19027
|
+
registerCrystallize(program, { hidden: !isPleriConfigured() });
|
|
18138
19028
|
registerPr(program);
|
|
18139
19029
|
registerWorkspace(program);
|
|
18140
19030
|
registerHostCp(program);
|