@curdx/flow 2.0.19 → 2.0.21
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +7 -0
- package/README.zh.md +1 -1
- package/cli/doctor-workflow.js +121 -0
- package/cli/doctor.js +15 -95
- package/cli/install-curdx-plugin.js +62 -61
- package/cli/install-self-update.js +98 -4
- package/cli/install-workflow.js +209 -0
- package/cli/install.js +33 -123
- package/cli/uninstall-workflow.js +146 -0
- package/cli/uninstall.js +33 -106
- package/cli/upgrade-workflow.js +80 -0
- package/cli/upgrade.js +31 -39
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code Discipline Layer — spec-driven workflow + goal-backward verification + Karpathy 4 principles enforced via gates. Stops Claude from faking \"done\" on non-trivial features.",
|
|
9
|
-
"version": "2.0.
|
|
9
|
+
"version": "2.0.21"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "curdx-flow",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.21",
|
|
4
4
|
"description": "Claude Code Discipline Layer — spec-driven workflow + goal-backward verification + Karpathy 4 principles enforced via gates. Stops Claude from faking \"done\" on non-trivial features.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "wdx",
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to CurdX-Flow will be documented here.
|
|
4
4
|
|
|
5
|
+
## [2.0.21] - 2026-04-23
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `cli/install-workflow.js` + `cli/install-self-update.js` — after `npm install -g @curdx/flow@latest`, the self-update restart spawned a bare `curdx-flow`, which fails under `npx` (PATH not guaranteed) or when the global bin dir isn't on PATH, surfacing as `sh: curdx-flow: command not found`. `checkAndUpdateSelf` now returns the absolute entry path of the freshly installed package, and the restart re-launches with `process.execPath` + that absolute script path. No PATH dependency, no shell involvement. Two new unit tests assert the spawn contract.
|
|
10
|
+
- `README.zh.md` — corrected the `smart-ralph` credit URL. The previously listed `Nibzard/smart-ralph` 404s; the repo matching the described feature set ("Spec-driven + Ralph Wiggum loop + Claude Code plugin") is `tzachbon/smart-ralph`.
|
|
11
|
+
|
|
5
12
|
## [Unreleased]
|
|
6
13
|
|
|
7
14
|
### Fixed
|
package/README.zh.md
CHANGED
|
@@ -124,7 +124,7 @@ claude --plugin-dir ./curdx-flow
|
|
|
124
124
|
CurdX-Flow 是蒸馏,不是原创。深深致谢:
|
|
125
125
|
|
|
126
126
|
- [**andrej-karpathy-skills**](https://github.com/forrestchang/andrej-karpathy-skills) — 4 原则
|
|
127
|
-
- [**smart-ralph**](https://github.com/
|
|
127
|
+
- [**smart-ralph**](https://github.com/tzachbon/smart-ralph) — 规格引擎 + stop-hook 循环
|
|
128
128
|
- [**superpowers**](https://github.com/obra/superpowers) — Subagent 纪律 + TDD + 两阶段审查
|
|
129
129
|
- [**BMAD-METHOD**](https://github.com/bmad-code-org/BMAD-METHOD) — 协作流程 + 对抗审查
|
|
130
130
|
- [**get-shit-done**](https://github.com/ryangentry/get-shit-done) — 波形执行 + 决策锁定 + 多源审计
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
claudeVersion,
|
|
6
|
+
color,
|
|
7
|
+
ensureClaudeMemRuntimes,
|
|
8
|
+
listMcps,
|
|
9
|
+
listPluginMarketplaces,
|
|
10
|
+
listPlugins,
|
|
11
|
+
log,
|
|
12
|
+
readUserMcpConfig,
|
|
13
|
+
runSync,
|
|
14
|
+
} from "./utils.js";
|
|
15
|
+
|
|
16
|
+
export function createDoctorContext(args = []) {
|
|
17
|
+
return {
|
|
18
|
+
verbose: args.includes("--verbose") || args.includes("-v"),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function readProjectState(cwd = process.cwd()) {
|
|
23
|
+
const flowDir = path.join(cwd, ".flow");
|
|
24
|
+
try {
|
|
25
|
+
const stat = await fs.stat(flowDir);
|
|
26
|
+
if (!stat.isDirectory()) {
|
|
27
|
+
return { exists: false, activeSpec: null };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const activeSpec = await fs.readFile(path.join(flowDir, ".active-spec"), "utf-8");
|
|
32
|
+
return { exists: true, activeSpec: activeSpec.trim() };
|
|
33
|
+
} catch {
|
|
34
|
+
return { exists: true, activeSpec: null };
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
return { exists: false, activeSpec: null };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function collectDoctorData(
|
|
42
|
+
{
|
|
43
|
+
cwd = process.cwd(),
|
|
44
|
+
} = {},
|
|
45
|
+
{
|
|
46
|
+
claudeVersionImpl = claudeVersion,
|
|
47
|
+
listPluginsImpl = listPlugins,
|
|
48
|
+
listPluginMarketplacesImpl = listPluginMarketplaces,
|
|
49
|
+
listMcpsImpl = listMcps,
|
|
50
|
+
readUserMcpConfigImpl = readUserMcpConfig,
|
|
51
|
+
ensureClaudeMemRuntimesImpl = ensureClaudeMemRuntimes,
|
|
52
|
+
readProjectStateImpl = readProjectState,
|
|
53
|
+
} = {}
|
|
54
|
+
) {
|
|
55
|
+
const claudeVersionValue = claudeVersionImpl();
|
|
56
|
+
const plugins = claudeVersionValue ? listPluginsImpl() : [];
|
|
57
|
+
const marketplaces = claudeVersionValue ? listPluginMarketplacesImpl() : [];
|
|
58
|
+
const mcps = claudeVersionValue ? listMcpsImpl() : [];
|
|
59
|
+
const userMcpConfig = claudeVersionValue ? readUserMcpConfigImpl() : new Map();
|
|
60
|
+
const runtimeStatus = plugins.some(
|
|
61
|
+
(plugin) => plugin.name === "claude-mem" && plugin.status === "enabled"
|
|
62
|
+
)
|
|
63
|
+
? ensureClaudeMemRuntimesImpl()
|
|
64
|
+
: null;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
claudeVersionValue,
|
|
68
|
+
nodeVersion: process.version,
|
|
69
|
+
plugins,
|
|
70
|
+
marketplaces,
|
|
71
|
+
mcps,
|
|
72
|
+
userMcpConfig,
|
|
73
|
+
runtimeStatus,
|
|
74
|
+
cwd,
|
|
75
|
+
projectState: await readProjectStateImpl(cwd),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function renderReportLines(lines, { logImpl = log } = {}) {
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
if (line.level === "ok") {
|
|
82
|
+
logImpl.ok(line.text);
|
|
83
|
+
} else if (line.level === "warn") {
|
|
84
|
+
logImpl.warn(line.text);
|
|
85
|
+
} else if (line.level === "err") {
|
|
86
|
+
logImpl.err(line.text);
|
|
87
|
+
} else {
|
|
88
|
+
logImpl.info(line.text);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const detail of line.details || []) {
|
|
92
|
+
console.log(color.dim(` → ${detail}`));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function printDoctorSummary(
|
|
98
|
+
report,
|
|
99
|
+
{ logImpl = log, exitImpl = process.exit } = {}
|
|
100
|
+
) {
|
|
101
|
+
console.log();
|
|
102
|
+
if (report.errors > 0) {
|
|
103
|
+
console.log(color.red(`Summary: ${report.errors} error(s), ${report.warnings} warning(s)`));
|
|
104
|
+
console.log(color.dim("Fix errors and re-run curdx-flow doctor"));
|
|
105
|
+
exitImpl(1);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (report.warnings > 0) {
|
|
110
|
+
console.log(color.yellow(`Summary: ${report.warnings} warning(s). Usable, but worth addressing.`));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log(color.green("Summary: all healthy ✓"));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function printVerboseDoctorDetails({ runSyncImpl = runSync } = {}) {
|
|
118
|
+
console.log(`\n${color.bold("Details:")}`);
|
|
119
|
+
console.log(color.dim(" Plugins raw:"));
|
|
120
|
+
console.log(runSyncImpl("claude", ["plugin", "list"]).stdout);
|
|
121
|
+
}
|
package/cli/doctor.js
CHANGED
|
@@ -2,115 +2,35 @@
|
|
|
2
2
|
* doctor command — external health check (no need to enter Claude Code).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import fs from "node:fs/promises";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
|
|
8
5
|
import {
|
|
9
6
|
color,
|
|
10
7
|
log,
|
|
11
|
-
runSync,
|
|
12
|
-
claudeVersion,
|
|
13
|
-
listPlugins,
|
|
14
|
-
listPluginMarketplaces,
|
|
15
|
-
listMcps,
|
|
16
|
-
ensureClaudeMemRuntimes,
|
|
17
|
-
readUserMcpConfig,
|
|
18
8
|
} from "./utils.js";
|
|
19
9
|
import { buildDoctorReport } from "./lib/doctor-report.js";
|
|
10
|
+
import {
|
|
11
|
+
collectDoctorData,
|
|
12
|
+
createDoctorContext,
|
|
13
|
+
printDoctorSummary,
|
|
14
|
+
printVerboseDoctorDetails,
|
|
15
|
+
renderReportLines,
|
|
16
|
+
} from "./doctor-workflow.js";
|
|
20
17
|
|
|
21
18
|
export async function doctor(args = []) {
|
|
22
|
-
const
|
|
23
|
-
const claudeVersionValue = claudeVersion();
|
|
24
|
-
const plugins = claudeVersionValue ? listPlugins() : [];
|
|
25
|
-
const marketplaces = claudeVersionValue ? listPluginMarketplaces() : [];
|
|
26
|
-
const mcps = claudeVersionValue ? listMcps() : [];
|
|
27
|
-
const userMcpConfig = claudeVersionValue ? readUserMcpConfig() : new Map();
|
|
28
|
-
const runtimeStatus = plugins.some((plugin) => plugin.name === "claude-mem" && plugin.status === "enabled")
|
|
29
|
-
? ensureClaudeMemRuntimes()
|
|
30
|
-
: null;
|
|
19
|
+
const context = createDoctorContext(args);
|
|
31
20
|
|
|
32
21
|
log.title("🏥 CurdX-Flow Health Check");
|
|
33
22
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
nodeVersion: process.version,
|
|
37
|
-
plugins,
|
|
38
|
-
marketplaces,
|
|
39
|
-
mcps,
|
|
40
|
-
userMcpConfig,
|
|
41
|
-
runtimeStatus,
|
|
42
|
-
cwd: process.cwd(),
|
|
43
|
-
projectState: await readProjectState(),
|
|
44
|
-
});
|
|
23
|
+
const doctorData = await collectDoctorData();
|
|
24
|
+
const report = buildDoctorReport(doctorData);
|
|
45
25
|
|
|
46
|
-
|
|
26
|
+
renderReportLines(report.lines);
|
|
47
27
|
for (const section of report.sections) {
|
|
48
28
|
console.log(`\n${color.bold(section.title)}`);
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
printSummary(report);
|
|
53
|
-
if (verbose && claudeVersionValue) {
|
|
54
|
-
printVerboseDetails();
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function readProjectState() {
|
|
59
|
-
const cwd = process.cwd();
|
|
60
|
-
const flowDir = path.join(cwd, ".flow");
|
|
61
|
-
try {
|
|
62
|
-
const stat = await fs.stat(flowDir);
|
|
63
|
-
if (!stat.isDirectory()) {
|
|
64
|
-
return { exists: false, activeSpec: null };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
const activeSpec = await fs.readFile(path.join(flowDir, ".active-spec"), "utf-8");
|
|
69
|
-
return { exists: true, activeSpec: activeSpec.trim() };
|
|
70
|
-
} catch {
|
|
71
|
-
return { exists: true, activeSpec: null };
|
|
72
|
-
}
|
|
73
|
-
} catch {
|
|
74
|
-
return { exists: false, activeSpec: null };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function renderLines(lines) {
|
|
79
|
-
for (const line of lines) {
|
|
80
|
-
if (line.level === "ok") {
|
|
81
|
-
log.ok(line.text);
|
|
82
|
-
} else if (line.level === "warn") {
|
|
83
|
-
log.warn(line.text);
|
|
84
|
-
} else if (line.level === "err") {
|
|
85
|
-
log.err(line.text);
|
|
86
|
-
} else {
|
|
87
|
-
log.info(line.text);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
for (const detail of line.details || []) {
|
|
91
|
-
console.log(color.dim(` → ${detail}`));
|
|
92
|
-
}
|
|
29
|
+
renderReportLines(section.lines);
|
|
93
30
|
}
|
|
94
|
-
}
|
|
95
31
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
console.log(color.red(`Summary: ${report.errors} error(s), ${report.warnings} warning(s)`));
|
|
100
|
-
console.log(color.dim("Fix errors and re-run curdx-flow doctor"));
|
|
101
|
-
process.exit(1);
|
|
32
|
+
printDoctorSummary(report);
|
|
33
|
+
if (context.verbose && doctorData.claudeVersionValue) {
|
|
34
|
+
printVerboseDoctorDetails();
|
|
102
35
|
}
|
|
103
|
-
|
|
104
|
-
if (report.warnings > 0) {
|
|
105
|
-
console.log(color.yellow(`Summary: ${report.warnings} warning(s). Usable, but worth addressing.`));
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
console.log(color.green("Summary: all healthy ✓"));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function printVerboseDetails() {
|
|
113
|
-
console.log(`\n${color.bold("Details:")}`);
|
|
114
|
-
console.log(color.dim(" Plugins raw:"));
|
|
115
|
-
console.log(runSync("claude", ["plugin", "list"]).stdout);
|
|
116
36
|
}
|
|
@@ -5,6 +5,46 @@ import {
|
|
|
5
5
|
removePluginMarketplace,
|
|
6
6
|
} from "./lib/claude-ops.js";
|
|
7
7
|
|
|
8
|
+
const CURDX_FLOW_INSTALL_ENTRY = {
|
|
9
|
+
scope: "user",
|
|
10
|
+
installSpec: "curdx-flow@curdx-flow-marketplace",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function resolveCurdxFlowInstallPlan({ prevCurdxFlow, shippedVersion }) {
|
|
14
|
+
if (prevCurdxFlow && shippedVersion && prevCurdxFlow.version === shippedVersion) {
|
|
15
|
+
return {
|
|
16
|
+
intro: `curdx-flow already at v${prevCurdxFlow.version}, re-registering...`,
|
|
17
|
+
success: `curdx-flow re-registered at v${shippedVersion}`,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (prevCurdxFlow && shippedVersion) {
|
|
22
|
+
return {
|
|
23
|
+
intro: `curdx-flow v${prevCurdxFlow.version} → v${shippedVersion}, installing...`,
|
|
24
|
+
success: `curdx-flow upgraded to v${shippedVersion}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (prevCurdxFlow) {
|
|
29
|
+
return {
|
|
30
|
+
intro: `curdx-flow v${prevCurdxFlow.version} detected, re-registering...`,
|
|
31
|
+
success: "curdx-flow re-registered",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (shippedVersion) {
|
|
36
|
+
return {
|
|
37
|
+
intro: null,
|
|
38
|
+
success: `curdx-flow v${shippedVersion} installed`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
intro: null,
|
|
44
|
+
success: "curdx-flow installed",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
8
48
|
export async function addCurdxMarketplace({ marketplaceSource, marketplaceLabel, useOffline }) {
|
|
9
49
|
log.blank();
|
|
10
50
|
log.step(2, 5, `Adding curdx-flow marketplace from ${marketplaceLabel}...`);
|
|
@@ -24,72 +64,33 @@ export async function addCurdxMarketplace({ marketplaceSource, marketplaceLabel,
|
|
|
24
64
|
}
|
|
25
65
|
}
|
|
26
66
|
|
|
27
|
-
export async function installCurdxFlowPlugin(
|
|
28
|
-
|
|
29
|
-
|
|
67
|
+
export async function installCurdxFlowPlugin(
|
|
68
|
+
{ prevCurdxFlow, shippedVersion },
|
|
69
|
+
{
|
|
70
|
+
installPluginImpl = installPlugin,
|
|
71
|
+
exitImpl = process.exit,
|
|
72
|
+
logImpl = log,
|
|
73
|
+
} = {}
|
|
74
|
+
) {
|
|
75
|
+
logImpl.blank();
|
|
76
|
+
logImpl.step(3, 5, "Installing curdx-flow plugin...");
|
|
30
77
|
|
|
31
78
|
// Use the pre-Step-2 snapshot — by this point `claude plugin marketplace
|
|
32
79
|
// remove` has already evicted the plugin, so listPlugins() here would
|
|
33
80
|
// always return undefined for curdx-flow and we'd mis-report "installed"
|
|
34
81
|
// when we actually upgraded (the bug reported by @wdx's beta.14 log).
|
|
35
|
-
const
|
|
82
|
+
const plan = resolveCurdxFlowInstallPlan({ prevCurdxFlow, shippedVersion });
|
|
36
83
|
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
scope: "user",
|
|
47
|
-
installSpec: "curdx-flow@curdx-flow-marketplace",
|
|
48
|
-
});
|
|
49
|
-
if (r.code !== 0) {
|
|
50
|
-
log.err(`Install failed: ${resultOutput(r)}`);
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
log.ok(`curdx-flow re-registered at v${shippedVersion}`);
|
|
54
|
-
} else if (already && shippedVersion) {
|
|
55
|
-
log.info(
|
|
56
|
-
`curdx-flow v${already.version} → v${shippedVersion}, installing...`
|
|
57
|
-
);
|
|
58
|
-
const r = await installPlugin({
|
|
59
|
-
scope: "user",
|
|
60
|
-
installSpec: "curdx-flow@curdx-flow-marketplace",
|
|
61
|
-
});
|
|
62
|
-
if (r.code !== 0) {
|
|
63
|
-
log.err(`Install failed: ${resultOutput(r)}`);
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
66
|
-
log.ok(`curdx-flow upgraded to v${shippedVersion}`);
|
|
67
|
-
} else if (already) {
|
|
68
|
-
log.info(
|
|
69
|
-
`curdx-flow v${already.version} detected, re-registering...`
|
|
70
|
-
);
|
|
71
|
-
const r = await installPlugin({
|
|
72
|
-
scope: "user",
|
|
73
|
-
installSpec: "curdx-flow@curdx-flow-marketplace",
|
|
74
|
-
});
|
|
75
|
-
if (r.code !== 0) {
|
|
76
|
-
log.err(`Install failed: ${resultOutput(r)}`);
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
log.ok("curdx-flow re-registered");
|
|
80
|
-
} else {
|
|
81
|
-
const r = await installPlugin({
|
|
82
|
-
scope: "user",
|
|
83
|
-
installSpec: "curdx-flow@curdx-flow-marketplace",
|
|
84
|
-
});
|
|
85
|
-
if (r.code !== 0) {
|
|
86
|
-
log.err(`Install failed: ${resultOutput(r)}`);
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
89
|
-
if (shippedVersion) {
|
|
90
|
-
log.ok(`curdx-flow v${shippedVersion} installed`);
|
|
91
|
-
} else {
|
|
92
|
-
log.ok("curdx-flow installed");
|
|
93
|
-
}
|
|
84
|
+
if (plan.intro) {
|
|
85
|
+
logImpl.info(plan.intro);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = await installPluginImpl(CURDX_FLOW_INSTALL_ENTRY);
|
|
89
|
+
if (result.code !== 0) {
|
|
90
|
+
logImpl.err(`Install failed: ${resultOutput(result)}`);
|
|
91
|
+
exitImpl(1);
|
|
92
|
+
return;
|
|
94
93
|
}
|
|
94
|
+
|
|
95
|
+
logImpl.ok(plan.success);
|
|
95
96
|
}
|
|
@@ -3,6 +3,98 @@ import { join } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { log, run, runSync, VERSION } from "./utils.js";
|
|
5
5
|
|
|
6
|
+
function normalizeVersionToken(token) {
|
|
7
|
+
return /^\d+$/.test(token) ? Number(token) : token;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function parseVersion(version) {
|
|
11
|
+
const normalized = String(version || "").trim().replace(/^v/i, "");
|
|
12
|
+
const [coreRaw = "0", prereleaseRaw] = normalized.split("-", 2);
|
|
13
|
+
const core = coreRaw.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
14
|
+
const prerelease = prereleaseRaw
|
|
15
|
+
? prereleaseRaw.split(/[.-]/).filter(Boolean).map(normalizeVersionToken)
|
|
16
|
+
: [];
|
|
17
|
+
|
|
18
|
+
return { core, prerelease };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function compareIdentifier(left, right) {
|
|
22
|
+
if (left === right) {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const leftIsNumber = typeof left === "number";
|
|
27
|
+
const rightIsNumber = typeof right === "number";
|
|
28
|
+
|
|
29
|
+
if (leftIsNumber && rightIsNumber) {
|
|
30
|
+
return left > right ? 1 : -1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (leftIsNumber) {
|
|
34
|
+
return -1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (rightIsNumber) {
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return left > right ? 1 : -1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function compareVersions(leftVersion, rightVersion) {
|
|
45
|
+
const left = parseVersion(leftVersion);
|
|
46
|
+
const right = parseVersion(rightVersion);
|
|
47
|
+
const coreLength = Math.max(left.core.length, right.core.length);
|
|
48
|
+
|
|
49
|
+
for (let index = 0; index < coreLength; index += 1) {
|
|
50
|
+
const leftPart = left.core[index] ?? 0;
|
|
51
|
+
const rightPart = right.core[index] ?? 0;
|
|
52
|
+
if (leftPart !== rightPart) {
|
|
53
|
+
return leftPart > rightPart ? 1 : -1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const leftHasPrerelease = left.prerelease.length > 0;
|
|
58
|
+
const rightHasPrerelease = right.prerelease.length > 0;
|
|
59
|
+
|
|
60
|
+
if (!leftHasPrerelease && !rightHasPrerelease) {
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!leftHasPrerelease) {
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!rightHasPrerelease) {
|
|
69
|
+
return -1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const prereleaseLength = Math.max(left.prerelease.length, right.prerelease.length);
|
|
73
|
+
for (let index = 0; index < prereleaseLength; index += 1) {
|
|
74
|
+
const leftPart = left.prerelease[index];
|
|
75
|
+
const rightPart = right.prerelease[index];
|
|
76
|
+
|
|
77
|
+
if (leftPart === undefined) {
|
|
78
|
+
return -1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (rightPart === undefined) {
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const comparison = compareIdentifier(leftPart, rightPart);
|
|
86
|
+
if (comparison !== 0) {
|
|
87
|
+
return comparison;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function isVersionNewer(latestVersion, currentVersion) {
|
|
95
|
+
return compareVersions(latestVersion, currentVersion) > 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
6
98
|
/**
|
|
7
99
|
* Check for CLI updates and auto-update if available.
|
|
8
100
|
* @returns {Promise<{updated: boolean, version?: string}>}
|
|
@@ -31,9 +123,7 @@ export async function checkAndUpdateSelf() {
|
|
|
31
123
|
return { updated: false };
|
|
32
124
|
}
|
|
33
125
|
|
|
34
|
-
|
|
35
|
-
// this structural refactor.
|
|
36
|
-
if (latestVersion > currentVersion) {
|
|
126
|
+
if (isVersionNewer(latestVersion, currentVersion)) {
|
|
37
127
|
log.info(`New version available: v${currentVersion} → v${latestVersion}`);
|
|
38
128
|
log.info("Updating CLI...");
|
|
39
129
|
|
|
@@ -43,7 +133,11 @@ export async function checkAndUpdateSelf() {
|
|
|
43
133
|
|
|
44
134
|
if (updateRes.code === 0) {
|
|
45
135
|
log.ok(`CLI updated to v${latestVersion}`);
|
|
46
|
-
return {
|
|
136
|
+
return {
|
|
137
|
+
updated: true,
|
|
138
|
+
version: latestVersion,
|
|
139
|
+
entryPath: join(installedPath, "bin", "curdx-flow.js"),
|
|
140
|
+
};
|
|
47
141
|
}
|
|
48
142
|
|
|
49
143
|
log.warn("CLI update failed, continuing with current version");
|