@ctxo/cli 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -31,8 +31,8 @@ import {
|
|
|
31
31
|
} from "./chunk-PSKDDLSF.js";
|
|
32
32
|
|
|
33
33
|
// src/cli/cli-router.ts
|
|
34
|
-
import { readFileSync as
|
|
35
|
-
import { dirname as dirname8, join as
|
|
34
|
+
import { readFileSync as readFileSync15, existsSync as existsSync23 } from "fs";
|
|
35
|
+
import { dirname as dirname8, join as join25 } from "path";
|
|
36
36
|
|
|
37
37
|
// src/cli/index-command.ts
|
|
38
38
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -264,7 +264,6 @@ async function loadPlugins(projectRoot, options = {}) {
|
|
|
264
264
|
|
|
265
265
|
// src/cli/install-command.ts
|
|
266
266
|
import { existsSync as existsSync5 } from "fs";
|
|
267
|
-
import { spawn } from "child_process";
|
|
268
267
|
import { createRequire as createRequire2 } from "module";
|
|
269
268
|
import { join as join4 } from "path";
|
|
270
269
|
|
|
@@ -523,6 +522,16 @@ function buildInstallCommand(manager, packages, options = {}) {
|
|
|
523
522
|
}
|
|
524
523
|
}
|
|
525
524
|
|
|
525
|
+
// src/core/install/run-package-manager.ts
|
|
526
|
+
import { spawn } from "child_process";
|
|
527
|
+
function runPackageManager(command, args, cwd) {
|
|
528
|
+
return new Promise((resolve4, reject) => {
|
|
529
|
+
const child = spawn(command, [...args], { cwd, stdio: "inherit", shell: process.platform === "win32" });
|
|
530
|
+
child.on("exit", (code) => resolve4(code ?? 0));
|
|
531
|
+
child.on("error", (err) => reject(err));
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
526
535
|
// src/cli/install-command.ts
|
|
527
536
|
var InstallCommand = class {
|
|
528
537
|
constructor(projectRoot) {
|
|
@@ -579,7 +588,7 @@ var InstallCommand = class {
|
|
|
579
588
|
console.error("[ctxo] --dry-run set; not executing.");
|
|
580
589
|
return;
|
|
581
590
|
}
|
|
582
|
-
const code = await
|
|
591
|
+
const code = await runPackageManager(plan.command, plan.args, this.projectRoot);
|
|
583
592
|
if (code !== 0) {
|
|
584
593
|
console.error(`[ctxo] ${plan.command} exited with code ${code}`);
|
|
585
594
|
process.exitCode = code;
|
|
@@ -648,13 +657,6 @@ function isLockedCI(env) {
|
|
|
648
657
|
if (env["CI"] !== "true" && env["CI"] !== "1") return false;
|
|
649
658
|
return true;
|
|
650
659
|
}
|
|
651
|
-
function spawnInstall(command, args, cwd) {
|
|
652
|
-
return new Promise((resolve4, reject) => {
|
|
653
|
-
const child = spawn(command, [...args], { cwd, stdio: "inherit", shell: process.platform === "win32" });
|
|
654
|
-
child.on("exit", (code) => resolve4(code ?? 0));
|
|
655
|
-
child.on("error", (err) => reject(err));
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
660
|
|
|
659
661
|
// src/adapters/storage/json-index-writer.ts
|
|
660
662
|
import { mkdirSync, unlinkSync, existsSync as existsSync6 } from "fs";
|
|
@@ -1734,7 +1736,9 @@ var PLATFORMS = [
|
|
|
1734
1736
|
{ id: "windsurf", name: "Windsurf", file: ".windsurfrules", mode: "create", detectPaths: [".windsurfrules", ".windsurf"], starred: false },
|
|
1735
1737
|
{ id: "antigravity", name: "Google Antigravity", file: "AGENTS.md", mode: "create", detectPaths: ["AGENTS.md", "GEMINI.md", ".gemini"], starred: false },
|
|
1736
1738
|
{ id: "augment", name: "Augment Code", file: "augment-guidelines.md", mode: "create", detectPaths: ["augment-guidelines.md", ".augment"], starred: false },
|
|
1737
|
-
{ id: "amazonq", name: "Amazon Q", file: ".amazonq/rules/ctxo.md", mode: "create", detectPaths: [".amazonq"], starred: false }
|
|
1739
|
+
{ id: "amazonq", name: "Amazon Q", file: ".amazonq/rules/ctxo.md", mode: "create", detectPaths: [".amazonq"], starred: false },
|
|
1740
|
+
{ id: "gemini-cli", name: "Gemini CLI", file: "GEMINI.md", mode: "append", detectPaths: [".gemini/settings.json", "GEMINI.md"], starred: false },
|
|
1741
|
+
{ id: "continue", name: "Continue", file: ".continue/rules/ctxo.md", mode: "create", detectPaths: [".continue"], starred: false }
|
|
1738
1742
|
];
|
|
1739
1743
|
function detectPlatforms(projectRoot) {
|
|
1740
1744
|
const detected = /* @__PURE__ */ new Set();
|
|
@@ -1898,6 +1902,12 @@ function getMcpConfigTargets(selectedPlatformIds) {
|
|
|
1898
1902
|
case "amazonq":
|
|
1899
1903
|
targets.set(".amazonq/mcp.json", { file: ".amazonq/mcp.json", serverKey: "mcpServers" });
|
|
1900
1904
|
break;
|
|
1905
|
+
case "gemini-cli":
|
|
1906
|
+
targets.set(".gemini/settings.json", { file: ".gemini/settings.json", serverKey: "mcpServers" });
|
|
1907
|
+
break;
|
|
1908
|
+
case "continue":
|
|
1909
|
+
targets.set(".continue/mcpServers/ctxo.json", { file: ".continue/mcpServers/ctxo.json", serverKey: "mcpServers" });
|
|
1910
|
+
break;
|
|
1901
1911
|
}
|
|
1902
1912
|
}
|
|
1903
1913
|
return [...targets.values()];
|
|
@@ -3598,7 +3608,7 @@ var DoctorCommand = class {
|
|
|
3598
3608
|
this.ctxoRoot = join22(projectRoot, ".ctxo");
|
|
3599
3609
|
}
|
|
3600
3610
|
async run(options = {}) {
|
|
3601
|
-
const { getVersion: getVersion2 } = await import("./cli-router-
|
|
3611
|
+
const { getVersion: getVersion2 } = await import("./cli-router-L34BRMXQ.js");
|
|
3602
3612
|
console.error(`ctxo v${getVersion2()} \u2014 health check
|
|
3603
3613
|
`);
|
|
3604
3614
|
const checks = [
|
|
@@ -3859,20 +3869,363 @@ var VisualizeCommand = class {
|
|
|
3859
3869
|
}
|
|
3860
3870
|
};
|
|
3861
3871
|
|
|
3872
|
+
// src/cli/update-command.ts
|
|
3873
|
+
import { existsSync as existsSync22, readFileSync as readFileSync14 } from "fs";
|
|
3874
|
+
import { join as join24 } from "path";
|
|
3875
|
+
|
|
3876
|
+
// src/core/update/registry-client.ts
|
|
3877
|
+
import { request } from "https";
|
|
3878
|
+
var log5 = createLogger("ctxo:update:registry");
|
|
3879
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
3880
|
+
async function fetchDistTags(packageName, options = {}) {
|
|
3881
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
3882
|
+
const fetcher = options.fetcher ?? defaultHttpsFetcher;
|
|
3883
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
|
|
3884
|
+
let response;
|
|
3885
|
+
try {
|
|
3886
|
+
response = await fetcher(url, timeoutMs);
|
|
3887
|
+
} catch (err) {
|
|
3888
|
+
return classifyFetchError(packageName, err);
|
|
3889
|
+
}
|
|
3890
|
+
if (response.status === 404) {
|
|
3891
|
+
return { name: packageName, reason: "registry-404", status: 404 };
|
|
3892
|
+
}
|
|
3893
|
+
if (response.status < 200 || response.status >= 300) {
|
|
3894
|
+
return { name: packageName, reason: "registry-error", status: response.status };
|
|
3895
|
+
}
|
|
3896
|
+
let parsed;
|
|
3897
|
+
try {
|
|
3898
|
+
parsed = JSON.parse(response.body);
|
|
3899
|
+
} catch {
|
|
3900
|
+
return { name: packageName, reason: "registry-error", status: response.status, message: "invalid JSON" };
|
|
3901
|
+
}
|
|
3902
|
+
const distTags = parsed["dist-tags"];
|
|
3903
|
+
if (!distTags || typeof distTags !== "object") {
|
|
3904
|
+
return { name: packageName, reason: "registry-error", status: response.status, message: "missing dist-tags" };
|
|
3905
|
+
}
|
|
3906
|
+
return { name: packageName, distTags };
|
|
3907
|
+
}
|
|
3908
|
+
async function fetchDistTagsBatch(packageNames, options = {}) {
|
|
3909
|
+
return Promise.all(packageNames.map((name) => fetchDistTags(name, options)));
|
|
3910
|
+
}
|
|
3911
|
+
function classifyFetchError(name, err) {
|
|
3912
|
+
const code = err?.code;
|
|
3913
|
+
const message = err?.message ?? String(err);
|
|
3914
|
+
if (code === "ETIMEDOUT" || /timeout/i.test(message)) {
|
|
3915
|
+
return { name, reason: "timeout", message };
|
|
3916
|
+
}
|
|
3917
|
+
if (code === "ECONNREFUSED" || code === "ECONNRESET" || code === "ENOTFOUND" || code === "EAI_AGAIN") {
|
|
3918
|
+
return { name, reason: "network", message };
|
|
3919
|
+
}
|
|
3920
|
+
log5.error(`unexpected fetch error for ${name}: ${message}`);
|
|
3921
|
+
return { name, reason: "network", message };
|
|
3922
|
+
}
|
|
3923
|
+
var defaultHttpsFetcher = (url, timeoutMs) => new Promise((resolve4, reject) => {
|
|
3924
|
+
const req = request(url, { method: "GET", headers: { "user-agent": "ctxo-update", accept: "application/json" } }, (res) => {
|
|
3925
|
+
const chunks = [];
|
|
3926
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
3927
|
+
res.on("end", () => resolve4({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf-8") }));
|
|
3928
|
+
res.on("error", reject);
|
|
3929
|
+
});
|
|
3930
|
+
req.setTimeout(timeoutMs, () => {
|
|
3931
|
+
req.destroy(Object.assign(new Error(`timeout after ${timeoutMs}ms`), { code: "ETIMEDOUT" }));
|
|
3932
|
+
});
|
|
3933
|
+
req.on("error", reject);
|
|
3934
|
+
req.end();
|
|
3935
|
+
});
|
|
3936
|
+
|
|
3937
|
+
// src/core/update/update-plan.ts
|
|
3938
|
+
var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
3939
|
+
function parseSemver(version) {
|
|
3940
|
+
const match = SEMVER_RE.exec(version.trim());
|
|
3941
|
+
if (!match) return null;
|
|
3942
|
+
const prereleaseRaw = match[4] ?? "";
|
|
3943
|
+
return {
|
|
3944
|
+
major: Number(match[1]),
|
|
3945
|
+
minor: Number(match[2]),
|
|
3946
|
+
patch: Number(match[3]),
|
|
3947
|
+
prerelease: prereleaseRaw ? prereleaseRaw.split(".") : []
|
|
3948
|
+
};
|
|
3949
|
+
}
|
|
3950
|
+
function detectChannel(version) {
|
|
3951
|
+
const parsed = parseSemver(version);
|
|
3952
|
+
if (!parsed || parsed.prerelease.length === 0) return "latest";
|
|
3953
|
+
const first = parsed.prerelease[0];
|
|
3954
|
+
if (/^\d+$/.test(first)) return "latest";
|
|
3955
|
+
return first;
|
|
3956
|
+
}
|
|
3957
|
+
function compareSemver(a, b) {
|
|
3958
|
+
const pa = parseSemver(a);
|
|
3959
|
+
const pb = parseSemver(b);
|
|
3960
|
+
if (!pa && !pb) return 0;
|
|
3961
|
+
if (!pa) return -1;
|
|
3962
|
+
if (!pb) return 1;
|
|
3963
|
+
if (pa.major !== pb.major) return pa.major < pb.major ? -1 : 1;
|
|
3964
|
+
if (pa.minor !== pb.minor) return pa.minor < pb.minor ? -1 : 1;
|
|
3965
|
+
if (pa.patch !== pb.patch) return pa.patch < pb.patch ? -1 : 1;
|
|
3966
|
+
if (pa.prerelease.length === 0 && pb.prerelease.length === 0) return 0;
|
|
3967
|
+
if (pa.prerelease.length === 0) return 1;
|
|
3968
|
+
if (pb.prerelease.length === 0) return -1;
|
|
3969
|
+
const len = Math.max(pa.prerelease.length, pb.prerelease.length);
|
|
3970
|
+
for (let i = 0; i < len; i++) {
|
|
3971
|
+
const ai = pa.prerelease[i];
|
|
3972
|
+
const bi = pb.prerelease[i];
|
|
3973
|
+
if (ai === void 0) return -1;
|
|
3974
|
+
if (bi === void 0) return 1;
|
|
3975
|
+
const aNum = /^\d+$/.test(ai);
|
|
3976
|
+
const bNum = /^\d+$/.test(bi);
|
|
3977
|
+
if (aNum && bNum) {
|
|
3978
|
+
const an = Number(ai);
|
|
3979
|
+
const bn = Number(bi);
|
|
3980
|
+
if (an !== bn) return an < bn ? -1 : 1;
|
|
3981
|
+
} else if (aNum && !bNum) {
|
|
3982
|
+
return -1;
|
|
3983
|
+
} else if (!aNum && bNum) {
|
|
3984
|
+
return 1;
|
|
3985
|
+
} else if (ai !== bi) {
|
|
3986
|
+
return ai < bi ? -1 : 1;
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
return 0;
|
|
3990
|
+
}
|
|
3991
|
+
function computePackageStates(installed, results) {
|
|
3992
|
+
const byName = /* @__PURE__ */ new Map();
|
|
3993
|
+
for (const r of results) byName.set(r.name, r);
|
|
3994
|
+
return installed.map((pkg) => {
|
|
3995
|
+
const installedChannel = detectChannel(pkg.version);
|
|
3996
|
+
const result = byName.get(pkg.name);
|
|
3997
|
+
if (!result) {
|
|
3998
|
+
return { name: pkg.name, current: pkg.version, latest: null, channel: installedChannel, status: "unknown" };
|
|
3999
|
+
}
|
|
4000
|
+
if (!("distTags" in result)) {
|
|
4001
|
+
return { name: pkg.name, current: pkg.version, latest: null, channel: installedChannel, status: "unknown", reason: result.reason };
|
|
4002
|
+
}
|
|
4003
|
+
const hasInstalledChannel = typeof result.distTags[installedChannel] === "string";
|
|
4004
|
+
const targetChannel = hasInstalledChannel ? installedChannel : "latest";
|
|
4005
|
+
const target = result.distTags[targetChannel] ?? null;
|
|
4006
|
+
if (!target) {
|
|
4007
|
+
return { name: pkg.name, current: pkg.version, latest: null, channel: installedChannel, status: "unknown" };
|
|
4008
|
+
}
|
|
4009
|
+
const cmp = compareSemver(pkg.version, target);
|
|
4010
|
+
const status = cmp === 0 ? "current" : cmp < 0 ? "update" : "ahead";
|
|
4011
|
+
return { name: pkg.name, current: pkg.version, latest: target, channel: targetChannel, status };
|
|
4012
|
+
});
|
|
4013
|
+
}
|
|
4014
|
+
function selectInstallTargets(states) {
|
|
4015
|
+
return states.filter((s) => s.status === "update" && typeof s.latest === "string").filter((s) => isSafeVersionSpecifier(s.latest)).map((s) => ({ name: s.name, version: s.latest }));
|
|
4016
|
+
}
|
|
4017
|
+
var SAFE_SPECIFIER_RE = /^[0-9A-Za-z.+-]+$/;
|
|
4018
|
+
function isSafeVersionSpecifier(version) {
|
|
4019
|
+
return SAFE_SPECIFIER_RE.test(version);
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
// src/cli/update-command.ts
|
|
4023
|
+
var log6 = createLogger("ctxo:update");
|
|
4024
|
+
var CTXO_CLI_PACKAGE = "@ctxo/cli";
|
|
4025
|
+
function formatText(report) {
|
|
4026
|
+
const lines = [];
|
|
4027
|
+
lines.push("ctxo update \u2014 checking registry for updates...");
|
|
4028
|
+
lines.push("");
|
|
4029
|
+
const updatesExist = report.packages.some((p) => p.status === "update");
|
|
4030
|
+
if (!updatesExist && report.strategy !== "print") {
|
|
4031
|
+
const total = report.packages.length;
|
|
4032
|
+
lines.push(`All ${total} package${total === 1 ? "" : "s"} are up to date.`);
|
|
4033
|
+
return lines.join("\n");
|
|
4034
|
+
}
|
|
4035
|
+
const nameCol = Math.max(7, ...report.packages.map((p) => p.name.length));
|
|
4036
|
+
const curCol = Math.max(7, ...report.packages.map((p) => p.current.length));
|
|
4037
|
+
const latestCol = Math.max(15, ...report.packages.map((p) => (p.latest ?? "(not found)").length));
|
|
4038
|
+
lines.push(
|
|
4039
|
+
`${"PACKAGE".padEnd(nameCol)} ${"CURRENT".padEnd(curCol)} ${`LATEST (${report.channel})`.padEnd(latestCol)} STATUS`
|
|
4040
|
+
);
|
|
4041
|
+
for (const pkg of report.packages) {
|
|
4042
|
+
const latestText = pkg.latest ?? "(not found)";
|
|
4043
|
+
const statusText = renderStatus(pkg, report.channel);
|
|
4044
|
+
lines.push(
|
|
4045
|
+
`${pkg.name.padEnd(nameCol)} ${pkg.current.padEnd(curCol)} ${latestText.padEnd(latestCol)} ${statusText}`
|
|
4046
|
+
);
|
|
4047
|
+
}
|
|
4048
|
+
if (report.plan && report.strategy === "execute") {
|
|
4049
|
+
lines.push("");
|
|
4050
|
+
lines.push(`Plan: ${report.plan.command} ${report.plan.args.join(" ")}`);
|
|
4051
|
+
lines.push(`Using ${report.plan.manager} (${formatManagerSource(report.plan)})`);
|
|
4052
|
+
lines.push("");
|
|
4053
|
+
lines.push("Running...");
|
|
4054
|
+
} else if (report.plan && report.strategy === "print") {
|
|
4055
|
+
lines.push("");
|
|
4056
|
+
lines.push("To update, run:");
|
|
4057
|
+
lines.push(` ${report.plan.command} ${report.plan.args.join(" ")}`);
|
|
4058
|
+
}
|
|
4059
|
+
return lines.join("\n");
|
|
4060
|
+
}
|
|
4061
|
+
function renderStatus(pkg, reportChannel) {
|
|
4062
|
+
switch (pkg.status) {
|
|
4063
|
+
case "current":
|
|
4064
|
+
return "up to date";
|
|
4065
|
+
case "ahead":
|
|
4066
|
+
return "ahead of registry";
|
|
4067
|
+
case "unknown":
|
|
4068
|
+
return pkg.reason === "registry-404" ? "skipped" : `error${pkg.reason ? ` (${pkg.reason})` : ""}`;
|
|
4069
|
+
case "update":
|
|
4070
|
+
return pkg.channel === reportChannel ? "update" : `update (${pkg.channel})`;
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
function formatManagerSource(plan) {
|
|
4074
|
+
return plan.managerDetail ? `${plan.managerSource}: ${plan.managerDetail}` : plan.managerSource;
|
|
4075
|
+
}
|
|
4076
|
+
function formatJson2(report) {
|
|
4077
|
+
return JSON.stringify(
|
|
4078
|
+
{
|
|
4079
|
+
ctxo: report.ctxo,
|
|
4080
|
+
channel: report.channel,
|
|
4081
|
+
packages: report.packages,
|
|
4082
|
+
plan: report.plan,
|
|
4083
|
+
executed: report.executed,
|
|
4084
|
+
...report.exitCode !== void 0 ? { exitCode: report.exitCode } : {}
|
|
4085
|
+
},
|
|
4086
|
+
null,
|
|
4087
|
+
2
|
|
4088
|
+
);
|
|
4089
|
+
}
|
|
4090
|
+
var UpdateCommand = class {
|
|
4091
|
+
constructor(projectRoot, deps = {}) {
|
|
4092
|
+
this.projectRoot = projectRoot;
|
|
4093
|
+
this.deps = deps;
|
|
4094
|
+
}
|
|
4095
|
+
projectRoot;
|
|
4096
|
+
deps;
|
|
4097
|
+
async run(options = {}) {
|
|
4098
|
+
if (options.pm && !isPackageManager(options.pm)) {
|
|
4099
|
+
this.emitError(`Unknown --pm value "${options.pm}".`);
|
|
4100
|
+
this.deps.setExitCode?.(1);
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
const discover = this.deps.discoverInstalled ?? defaultDiscoverInstalled;
|
|
4104
|
+
const installed = await discover(this.projectRoot);
|
|
4105
|
+
if (installed.length === 0) {
|
|
4106
|
+
this.emit(options.json ? "{}\n" : "ctxo update \u2014 no @ctxo/* packages discovered.\n");
|
|
4107
|
+
return;
|
|
4108
|
+
}
|
|
4109
|
+
const names = installed.map((p) => p.name);
|
|
4110
|
+
const results = await fetchDistTagsBatch(names, { fetcher: this.deps.fetcher });
|
|
4111
|
+
const allMissed = results.every((r) => !("distTags" in r));
|
|
4112
|
+
if (allMissed) {
|
|
4113
|
+
this.emitError("registry unreachable for every package; aborting.");
|
|
4114
|
+
this.deps.setExitCode?.(1);
|
|
4115
|
+
return;
|
|
4116
|
+
}
|
|
4117
|
+
const states = computePackageStates(installed, results);
|
|
4118
|
+
const targets = selectInstallTargets(states);
|
|
4119
|
+
const cliVersion = getVersion();
|
|
4120
|
+
const channel = detectChannel(cliVersion);
|
|
4121
|
+
const plan = targets.length === 0 ? null : this.buildPlan(targets, options);
|
|
4122
|
+
const strategy = this.pickStrategy(targets, options);
|
|
4123
|
+
const baseReport = {
|
|
4124
|
+
ctxo: cliVersion,
|
|
4125
|
+
channel,
|
|
4126
|
+
packages: states,
|
|
4127
|
+
plan,
|
|
4128
|
+
strategy,
|
|
4129
|
+
executed: false
|
|
4130
|
+
};
|
|
4131
|
+
if (options.check) {
|
|
4132
|
+
const checkStrategy = plan ? "print" : strategy;
|
|
4133
|
+
this.emitReport({ ...baseReport, strategy: checkStrategy }, options.json ?? false);
|
|
4134
|
+
return;
|
|
4135
|
+
}
|
|
4136
|
+
if (strategy === "none" || strategy === "print" || !plan) {
|
|
4137
|
+
this.emitReport(baseReport, options.json ?? false);
|
|
4138
|
+
return;
|
|
4139
|
+
}
|
|
4140
|
+
if (!options.force && !options.global && isLockedCI2(this.deps.env ?? process.env)) {
|
|
4141
|
+
this.emitReport({ ...baseReport, strategy: "print" }, options.json ?? false);
|
|
4142
|
+
this.emitError("CI environment with frozen lockfile detected. Refusing to mutate. Use --force or --global.");
|
|
4143
|
+
this.deps.setExitCode?.(1);
|
|
4144
|
+
return;
|
|
4145
|
+
}
|
|
4146
|
+
if (!options.json) this.emitReport(baseReport, false);
|
|
4147
|
+
const run2 = this.deps.runner ?? runPackageManager;
|
|
4148
|
+
const code = await run2(plan.command, plan.args, this.projectRoot);
|
|
4149
|
+
const finalReport = { ...baseReport, executed: true, exitCode: code };
|
|
4150
|
+
if (options.json) this.emit(formatJson2(finalReport) + "\n");
|
|
4151
|
+
if (code !== 0) this.deps.setExitCode?.(code);
|
|
4152
|
+
}
|
|
4153
|
+
buildPlan(targets, options) {
|
|
4154
|
+
const resolution = resolvePackageManager({
|
|
4155
|
+
flag: options.pm,
|
|
4156
|
+
projectRoot: this.projectRoot,
|
|
4157
|
+
env: this.deps.env
|
|
4158
|
+
});
|
|
4159
|
+
const specifiers = targets.map((t) => `${t.name}@${t.version}`);
|
|
4160
|
+
const useGlobal = options.global || !this.projectHasCtxoDep();
|
|
4161
|
+
const invocation = buildInstallCommand(resolution.manager, specifiers, { global: useGlobal });
|
|
4162
|
+
return {
|
|
4163
|
+
manager: resolution.manager,
|
|
4164
|
+
managerSource: resolution.source,
|
|
4165
|
+
managerDetail: resolution.detail,
|
|
4166
|
+
global: useGlobal,
|
|
4167
|
+
command: invocation.command,
|
|
4168
|
+
args: invocation.args
|
|
4169
|
+
};
|
|
4170
|
+
}
|
|
4171
|
+
pickStrategy(targets, options) {
|
|
4172
|
+
if (targets.length === 0) return "none";
|
|
4173
|
+
if (options.print) return "print";
|
|
4174
|
+
if (options.global) return "execute";
|
|
4175
|
+
return this.projectHasCtxoDep() ? "execute" : "print";
|
|
4176
|
+
}
|
|
4177
|
+
projectHasCtxoDep() {
|
|
4178
|
+
const pkg = join24(this.projectRoot, "package.json");
|
|
4179
|
+
if (!existsSync22(pkg)) return false;
|
|
4180
|
+
try {
|
|
4181
|
+
const json = JSON.parse(readFileSync14(pkg, "utf-8"));
|
|
4182
|
+
const merged = { ...json.dependencies ?? {}, ...json.devDependencies ?? {} };
|
|
4183
|
+
return Object.keys(merged).some(
|
|
4184
|
+
(name) => name === CTXO_CLI_PACKAGE || name.startsWith("@ctxo/lang-") || name.startsWith("ctxo-lang-")
|
|
4185
|
+
);
|
|
4186
|
+
} catch (err) {
|
|
4187
|
+
log6.error(`failed to read project package.json: ${err.message}`);
|
|
4188
|
+
return false;
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
emit(text) {
|
|
4192
|
+
process.stdout.write(text);
|
|
4193
|
+
}
|
|
4194
|
+
emitError(text) {
|
|
4195
|
+
process.stderr.write(`[ctxo] ${text}
|
|
4196
|
+
`);
|
|
4197
|
+
}
|
|
4198
|
+
emitReport(report, json) {
|
|
4199
|
+
if (json) this.emit(formatJson2(report) + "\n");
|
|
4200
|
+
else this.emit(formatText(report) + "\n");
|
|
4201
|
+
}
|
|
4202
|
+
};
|
|
4203
|
+
async function defaultDiscoverInstalled(projectRoot) {
|
|
4204
|
+
const out = [{ name: CTXO_CLI_PACKAGE, version: getVersion() }];
|
|
4205
|
+
const manifestPath = loadManifestPath(projectRoot);
|
|
4206
|
+
if (!manifestPath) return out;
|
|
4207
|
+
const { plugins } = await discoverPlugins({ manifestPath });
|
|
4208
|
+
for (const p of plugins) out.push({ name: p.specifier, version: p.plugin.version });
|
|
4209
|
+
return out;
|
|
4210
|
+
}
|
|
4211
|
+
function isLockedCI2(env) {
|
|
4212
|
+
return env["CI"] === "true" || env["CI"] === "1";
|
|
4213
|
+
}
|
|
4214
|
+
|
|
3862
4215
|
// src/cli/cli-router.ts
|
|
3863
4216
|
function getVersion() {
|
|
3864
4217
|
let dir = import.meta.dirname;
|
|
3865
4218
|
for (let i = 0; i < 10; i++) {
|
|
3866
|
-
const pkg =
|
|
3867
|
-
if (
|
|
4219
|
+
const pkg = join25(dir, "package.json");
|
|
4220
|
+
if (existsSync23(pkg)) {
|
|
3868
4221
|
try {
|
|
3869
|
-
const json = JSON.parse(
|
|
4222
|
+
const json = JSON.parse(readFileSync15(pkg, "utf-8"));
|
|
3870
4223
|
return json.version ?? "unknown";
|
|
3871
4224
|
} catch {
|
|
3872
4225
|
break;
|
|
3873
4226
|
}
|
|
3874
4227
|
}
|
|
3875
|
-
const parent =
|
|
4228
|
+
const parent = join25(dir, "..");
|
|
3876
4229
|
if (parent === dir) break;
|
|
3877
4230
|
dir = parent;
|
|
3878
4231
|
}
|
|
@@ -3881,7 +4234,7 @@ function getVersion() {
|
|
|
3881
4234
|
function locateGitRoot(startDir) {
|
|
3882
4235
|
let dir = startDir;
|
|
3883
4236
|
for (let i = 0; i < 12; i++) {
|
|
3884
|
-
if (
|
|
4237
|
+
if (existsSync23(join25(dir, ".git"))) return dir;
|
|
3885
4238
|
const parent = dirname8(dir);
|
|
3886
4239
|
if (parent === dir) break;
|
|
3887
4240
|
dir = parent;
|
|
@@ -4034,6 +4387,43 @@ var CliRouter = class {
|
|
|
4034
4387
|
});
|
|
4035
4388
|
break;
|
|
4036
4389
|
}
|
|
4390
|
+
case "update": {
|
|
4391
|
+
const flagValues = {};
|
|
4392
|
+
for (let i = 1; i < args.length; i++) {
|
|
4393
|
+
const a = args[i];
|
|
4394
|
+
if (a === "--check") flagValues["check"] = true;
|
|
4395
|
+
else if (a === "--print") flagValues["print"] = true;
|
|
4396
|
+
else if (a === "--global" || a === "-g") flagValues["global"] = true;
|
|
4397
|
+
else if (a === "--json") flagValues["json"] = true;
|
|
4398
|
+
else if (a === "--force") flagValues["force"] = true;
|
|
4399
|
+
else if (a === "--pm") {
|
|
4400
|
+
const next = args[++i];
|
|
4401
|
+
if (!next) {
|
|
4402
|
+
console.error("[ctxo] --pm requires a value");
|
|
4403
|
+
process.exit(1);
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
flagValues["pm"] = next;
|
|
4407
|
+
} else {
|
|
4408
|
+
console.error(`[ctxo] Unknown update flag: ${a}`);
|
|
4409
|
+
process.exit(1);
|
|
4410
|
+
return;
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
await new UpdateCommand(this.projectRoot, {
|
|
4414
|
+
setExitCode: (code) => {
|
|
4415
|
+
process.exitCode = code;
|
|
4416
|
+
}
|
|
4417
|
+
}).run({
|
|
4418
|
+
check: flagValues["check"] === true,
|
|
4419
|
+
print: flagValues["print"] === true,
|
|
4420
|
+
global: flagValues["global"] === true,
|
|
4421
|
+
json: flagValues["json"] === true,
|
|
4422
|
+
force: flagValues["force"] === true,
|
|
4423
|
+
pm: typeof flagValues["pm"] === "string" ? flagValues["pm"] : void 0
|
|
4424
|
+
});
|
|
4425
|
+
break;
|
|
4426
|
+
}
|
|
4037
4427
|
case "visualize": {
|
|
4038
4428
|
const outputIdx = args.indexOf("--output");
|
|
4039
4429
|
const outputArg = outputIdx !== -1 ? args[outputIdx + 1] : void 0;
|
|
@@ -4081,6 +4471,13 @@ Usage:
|
|
|
4081
4471
|
ctxo install python -y Batch install, no prompt
|
|
4082
4472
|
ctxo install ts --global Install globally
|
|
4083
4473
|
ctxo install py --dry-run Show command without running
|
|
4474
|
+
ctxo update Check + apply updates for ctxo + plugins
|
|
4475
|
+
ctxo update --check Check only, no install
|
|
4476
|
+
ctxo update --print Print install command, never execute
|
|
4477
|
+
ctxo update --global Force a global install
|
|
4478
|
+
ctxo update --json Machine-readable output
|
|
4479
|
+
ctxo update --pm <pm> Override package manager (npm|pnpm|yarn|bun)
|
|
4480
|
+
ctxo update --force Bypass CI lockfile guard
|
|
4084
4481
|
ctxo --version Print core version
|
|
4085
4482
|
ctxo --version --verbose Print core + plugins + runtime
|
|
4086
4483
|
ctxo --version --json Machine-readable version payload
|
|
@@ -4094,4 +4491,4 @@ export {
|
|
|
4094
4491
|
getVersion,
|
|
4095
4492
|
locateGitRoot
|
|
4096
4493
|
};
|
|
4097
|
-
//# sourceMappingURL=cli-router-
|
|
4494
|
+
//# sourceMappingURL=cli-router-L34BRMXQ.js.map
|