@sanqianx/ai-coding-event-bridge-console 0.7.2 → 0.7.4
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/LICENSE +21 -21
- package/README.md +81 -81
- package/console/index.html +953 -953
- package/package.json +26 -26
- package/src/agent-detect.js +128 -128
- package/src/bridge/bin/commit-boundary.js +64 -64
- package/src/bridge/connectors/capture-guard.js +24 -24
- package/src/bridge/connectors/claude-code/hook-entry.js +45 -45
- package/src/bridge/connectors/claude-code/notify.js +57 -57
- package/src/bridge/connectors/codex/cursor-store.js +83 -83
- package/src/bridge/connectors/codex/hook-entry.js +215 -215
- package/src/bridge/connectors/codex/session-parser.js +100 -100
- package/src/bridge/connectors/opencode/hook-entry.js +55 -55
- package/src/bridge/core/bridge.js +249 -246
- package/src/bridge/core/commit-projection.js +288 -288
- package/src/bridge/core/compaction.js +23 -23
- package/src/bridge/core/consumer-registry.js +111 -111
- package/src/bridge/core/event-schema.js +127 -127
- package/src/bridge/core/fs-utils.js +81 -81
- package/src/bridge/core/journal-router.js +39 -39
- package/src/bridge/core/journal.js +507 -507
- package/src/bridge/core/lock.js +96 -96
- package/src/bridge/core/normalizer.js +114 -114
- package/src/bridge/core/paths.js +12 -12
- package/src/bridge/core/project-registry.js +176 -176
- package/src/bridge/core/repo-context.js +191 -191
- package/src/bridge/core/runtime-home.js +189 -189
- package/src/bridge/core/semver.js +23 -23
- package/src/bridge/core/turn-identity.js +127 -127
- package/src/bridge/index.js +86 -86
- package/src/bridge/installers/claude-code/installer.js +181 -181
- package/src/bridge/installers/codex/installer.js +114 -114
- package/src/bridge/installers/opencode/installer.js +96 -96
- package/src/bridge/installers/zcode/installer.js +200 -200
- package/src/bridge/query/conversation-query.js +194 -194
- package/src/server.js +825 -825
|
@@ -1,189 +1,189 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { bridgeHome } = require('./paths');
|
|
6
|
-
const { CrossProcessLock, lockPathFor } = require('./lock');
|
|
7
|
-
const { writeJsonAtomicSync, readJsonIfExists, ensureDirSync, copyDirSync } = require('./fs-utils');
|
|
8
|
-
const semver = require('./semver');
|
|
9
|
-
|
|
10
|
-
// The shim is intentionally version-independent: AI client configs point at this
|
|
11
|
-
// stable path forever, and it delegates to whatever runtime version is active.
|
|
12
|
-
// It reads the client payload from stdin, forwards it to the active runtime and
|
|
13
|
-
// always exits 0 — fail-open is a product invariant.
|
|
14
|
-
const SHIM_SOURCE = `#!/usr/bin/env node
|
|
15
|
-
'use strict';
|
|
16
|
-
const fs = require('fs');
|
|
17
|
-
const path = require('path');
|
|
18
|
-
|
|
19
|
-
function home() {
|
|
20
|
-
return process.env.AI_CODING_EVENT_BRIDGE_HOME || path.resolve(__dirname, '..');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function readStdin() {
|
|
24
|
-
return new Promise((resolve) => {
|
|
25
|
-
let data = '';
|
|
26
|
-
try {
|
|
27
|
-
process.stdin.setEncoding('utf8');
|
|
28
|
-
process.stdin.on('data', (chunk) => (data += chunk));
|
|
29
|
-
process.stdin.on('end', () => resolve(data));
|
|
30
|
-
process.stdin.on('error', () => resolve(data));
|
|
31
|
-
} catch (_) {
|
|
32
|
-
resolve(data);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
(async () => {
|
|
38
|
-
try {
|
|
39
|
-
const raw = await readStdin();
|
|
40
|
-
let payload = null;
|
|
41
|
-
try {
|
|
42
|
-
payload = JSON.parse(raw);
|
|
43
|
-
} catch (_) {
|
|
44
|
-
payload = null;
|
|
45
|
-
}
|
|
46
|
-
const active = JSON.parse(fs.readFileSync(path.join(home(), 'active-runtime.json'), 'utf8'));
|
|
47
|
-
const hookPath = path.join(home(), 'runtime', String(active.version), 'hook.cjs');
|
|
48
|
-
if (fs.existsSync(hookPath)) {
|
|
49
|
-
const runtime = require(hookPath);
|
|
50
|
-
if (typeof runtime.main === 'function') {
|
|
51
|
-
await runtime.main({ payload, home: home(), argv: process.argv.slice(2), env: process.env });
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
} catch (_) {
|
|
55
|
-
// Fail open by design: a broken Bridge must never block the AI client.
|
|
56
|
-
} finally {
|
|
57
|
-
process.exit(0);
|
|
58
|
-
}
|
|
59
|
-
})();
|
|
60
|
-
`;
|
|
61
|
-
|
|
62
|
-
// The versioned runtime entry: dispatches the client payload into the
|
|
63
|
-
// self-contained bridge tree materialized next to it. The capture-disable
|
|
64
|
-
// environment marker is honored before any connector is even loaded.
|
|
65
|
-
const RUNTIME_HOOK_SOURCE = `#!/usr/bin/env node
|
|
66
|
-
'use strict';
|
|
67
|
-
async function main({ payload, home, argv, env }) {
|
|
68
|
-
const effectiveEnv = env || process.env;
|
|
69
|
-
if (effectiveEnv && effectiveEnv.AI_CODING_EVENT_BRIDGE_CAPTURE === '0') {
|
|
70
|
-
return { status: 'ignored', reason: 'capture-disabled' };
|
|
71
|
-
}
|
|
72
|
-
if (payload && typeof payload === 'object') {
|
|
73
|
-
// Claude Code ships hook_event_name (also seen as hookName/hook_name);
|
|
74
|
-
// all three identify a Claude hook payload and MUST route to the Claude
|
|
75
|
-
// connector — never fall through to Codex just because session_id exists.
|
|
76
|
-
if (payload.hookName || payload.hook_event_name || payload.hook_name) {
|
|
77
|
-
const claudeEntry = require('./bridge/connectors/claude-code/hook-entry.js');
|
|
78
|
-
const sourceIndex = Array.isArray(argv) ? argv.indexOf('--source') : -1;
|
|
79
|
-
const source = sourceIndex >= 0 && argv[sourceIndex + 1] === 'zcode' ? 'zcode' : 'claude-code';
|
|
80
|
-
return claudeEntry.mainFailOpen({ home, payload, source, argv, env });
|
|
81
|
-
}
|
|
82
|
-
if (payload.type === 'user' || payload.type === 'assistant') {
|
|
83
|
-
const opencodeEntry = require('./bridge/connectors/opencode/hook-entry.js');
|
|
84
|
-
return opencodeEntry.mainFailOpen({ home, payload });
|
|
85
|
-
}
|
|
86
|
-
if (payload.session_id || payload.sessionId) {
|
|
87
|
-
const codexEntry = require('./bridge/connectors/codex/hook-entry.js');
|
|
88
|
-
return codexEntry.mainFailOpen({ home, payload });
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return { status: 'ignored' };
|
|
92
|
-
}
|
|
93
|
-
module.exports = { main };
|
|
94
|
-
`;
|
|
95
|
-
|
|
96
|
-
function materializeBridgeTree(runtimeDir) {
|
|
97
|
-
const srcRoot = path.join(__dirname, '..');
|
|
98
|
-
copyDirSync(path.join(srcRoot, 'core'), path.join(runtimeDir, 'bridge', 'core'));
|
|
99
|
-
copyDirSync(path.join(srcRoot, 'connectors'), path.join(runtimeDir, 'bridge', 'connectors'));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function writeTextAtomicSync(file, content) {
|
|
103
|
-
const tmp = `${file}.tmp`;
|
|
104
|
-
const fd = fs.openSync(tmp, 'w');
|
|
105
|
-
try {
|
|
106
|
-
fs.writeSync(fd, content);
|
|
107
|
-
fs.fsyncSync(fd);
|
|
108
|
-
} finally {
|
|
109
|
-
fs.closeSync(fd);
|
|
110
|
-
}
|
|
111
|
-
fs.renameSync(tmp, file);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function ensureRuntimeHome({ homeDir, version } = {}) {
|
|
115
|
-
const home = bridgeHome(homeDir);
|
|
116
|
-
const requestedVersion = version || require('../../../package.json').version;
|
|
117
|
-
const lock = new CrossProcessLock(lockPathFor(home, 'install.lock'));
|
|
118
|
-
return lock.withLock(async () => {
|
|
119
|
-
ensureDirSync(path.join(home, 'bin'));
|
|
120
|
-
ensureDirSync(path.join(home, 'runtime'));
|
|
121
|
-
ensureDirSync(path.join(home, 'journal'));
|
|
122
|
-
ensureDirSync(path.join(home, 'locks'));
|
|
123
|
-
|
|
124
|
-
const shimPath = path.join(home, 'bin', 'bridge-hook.cjs');
|
|
125
|
-
writeTextAtomicSync(shimPath, SHIM_SOURCE);
|
|
126
|
-
|
|
127
|
-
const runtimeDir = path.join(home, 'runtime', requestedVersion);
|
|
128
|
-
const activeFile = path.join(home, 'active-runtime.json');
|
|
129
|
-
const active = readJsonIfExists(activeFile);
|
|
130
|
-
|
|
131
|
-
const materializeRuntime = () => {
|
|
132
|
-
ensureDirSync(runtimeDir);
|
|
133
|
-
writeTextAtomicSync(path.join(runtimeDir, 'hook.cjs'), RUNTIME_HOOK_SOURCE);
|
|
134
|
-
materializeBridgeTree(runtimeDir);
|
|
135
|
-
if (!fs.existsSync(path.join(runtimeDir, 'manifest.json'))) {
|
|
136
|
-
writeJsonAtomicSync(path.join(runtimeDir, 'manifest.json'), {
|
|
137
|
-
version: requestedVersion,
|
|
138
|
-
createdAt: new Date().toISOString()
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
let action;
|
|
144
|
-
if (!active || !active.version) {
|
|
145
|
-
materializeRuntime();
|
|
146
|
-
writeJsonAtomicSync(activeFile, { version: requestedVersion, activatedAt: new Date().toISOString() });
|
|
147
|
-
action = 'activated';
|
|
148
|
-
} else {
|
|
149
|
-
const cmp = semver.compare(active.version, requestedVersion);
|
|
150
|
-
if (cmp === null) {
|
|
151
|
-
materializeRuntime();
|
|
152
|
-
writeJsonAtomicSync(activeFile, { version: requestedVersion, activatedAt: new Date().toISOString() });
|
|
153
|
-
action = 'activated-unparseable-active';
|
|
154
|
-
} else if (cmp === 0) {
|
|
155
|
-
materializeRuntime();
|
|
156
|
-
action = 'noop-same-version';
|
|
157
|
-
} else if (cmp < 0) {
|
|
158
|
-
const activeMajor = semver.parse(active.version).major;
|
|
159
|
-
const requestedMajor = semver.parse(requestedVersion).major;
|
|
160
|
-
if (activeMajor !== requestedMajor) {
|
|
161
|
-
return {
|
|
162
|
-
home,
|
|
163
|
-
shimPath,
|
|
164
|
-
conflict: true,
|
|
165
|
-
activeVersion: active.version,
|
|
166
|
-
requestedVersion,
|
|
167
|
-
action: 'conflict-major-mismatch'
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
materializeRuntime();
|
|
171
|
-
writeJsonAtomicSync(activeFile, { version: requestedVersion, activatedAt: new Date().toISOString() });
|
|
172
|
-
action = 'upgraded';
|
|
173
|
-
} else {
|
|
174
|
-
action = 'kept-newer';
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
home,
|
|
180
|
-
shimPath,
|
|
181
|
-
conflict: false,
|
|
182
|
-
activeVersion: readJsonIfExists(activeFile).version,
|
|
183
|
-
requestedVersion,
|
|
184
|
-
action
|
|
185
|
-
};
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
module.exports = { ensureRuntimeHome, SHIM_SOURCE };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { bridgeHome } = require('./paths');
|
|
6
|
+
const { CrossProcessLock, lockPathFor } = require('./lock');
|
|
7
|
+
const { writeJsonAtomicSync, readJsonIfExists, ensureDirSync, copyDirSync } = require('./fs-utils');
|
|
8
|
+
const semver = require('./semver');
|
|
9
|
+
|
|
10
|
+
// The shim is intentionally version-independent: AI client configs point at this
|
|
11
|
+
// stable path forever, and it delegates to whatever runtime version is active.
|
|
12
|
+
// It reads the client payload from stdin, forwards it to the active runtime and
|
|
13
|
+
// always exits 0 — fail-open is a product invariant.
|
|
14
|
+
const SHIM_SOURCE = `#!/usr/bin/env node
|
|
15
|
+
'use strict';
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
function home() {
|
|
20
|
+
return process.env.AI_CODING_EVENT_BRIDGE_HOME || path.resolve(__dirname, '..');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readStdin() {
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
let data = '';
|
|
26
|
+
try {
|
|
27
|
+
process.stdin.setEncoding('utf8');
|
|
28
|
+
process.stdin.on('data', (chunk) => (data += chunk));
|
|
29
|
+
process.stdin.on('end', () => resolve(data));
|
|
30
|
+
process.stdin.on('error', () => resolve(data));
|
|
31
|
+
} catch (_) {
|
|
32
|
+
resolve(data);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
(async () => {
|
|
38
|
+
try {
|
|
39
|
+
const raw = await readStdin();
|
|
40
|
+
let payload = null;
|
|
41
|
+
try {
|
|
42
|
+
payload = JSON.parse(raw);
|
|
43
|
+
} catch (_) {
|
|
44
|
+
payload = null;
|
|
45
|
+
}
|
|
46
|
+
const active = JSON.parse(fs.readFileSync(path.join(home(), 'active-runtime.json'), 'utf8'));
|
|
47
|
+
const hookPath = path.join(home(), 'runtime', String(active.version), 'hook.cjs');
|
|
48
|
+
if (fs.existsSync(hookPath)) {
|
|
49
|
+
const runtime = require(hookPath);
|
|
50
|
+
if (typeof runtime.main === 'function') {
|
|
51
|
+
await runtime.main({ payload, home: home(), argv: process.argv.slice(2), env: process.env });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch (_) {
|
|
55
|
+
// Fail open by design: a broken Bridge must never block the AI client.
|
|
56
|
+
} finally {
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
})();
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
// The versioned runtime entry: dispatches the client payload into the
|
|
63
|
+
// self-contained bridge tree materialized next to it. The capture-disable
|
|
64
|
+
// environment marker is honored before any connector is even loaded.
|
|
65
|
+
const RUNTIME_HOOK_SOURCE = `#!/usr/bin/env node
|
|
66
|
+
'use strict';
|
|
67
|
+
async function main({ payload, home, argv, env }) {
|
|
68
|
+
const effectiveEnv = env || process.env;
|
|
69
|
+
if (effectiveEnv && effectiveEnv.AI_CODING_EVENT_BRIDGE_CAPTURE === '0') {
|
|
70
|
+
return { status: 'ignored', reason: 'capture-disabled' };
|
|
71
|
+
}
|
|
72
|
+
if (payload && typeof payload === 'object') {
|
|
73
|
+
// Claude Code ships hook_event_name (also seen as hookName/hook_name);
|
|
74
|
+
// all three identify a Claude hook payload and MUST route to the Claude
|
|
75
|
+
// connector — never fall through to Codex just because session_id exists.
|
|
76
|
+
if (payload.hookName || payload.hook_event_name || payload.hook_name) {
|
|
77
|
+
const claudeEntry = require('./bridge/connectors/claude-code/hook-entry.js');
|
|
78
|
+
const sourceIndex = Array.isArray(argv) ? argv.indexOf('--source') : -1;
|
|
79
|
+
const source = sourceIndex >= 0 && argv[sourceIndex + 1] === 'zcode' ? 'zcode' : 'claude-code';
|
|
80
|
+
return claudeEntry.mainFailOpen({ home, payload, source, argv, env });
|
|
81
|
+
}
|
|
82
|
+
if (payload.type === 'user' || payload.type === 'assistant') {
|
|
83
|
+
const opencodeEntry = require('./bridge/connectors/opencode/hook-entry.js');
|
|
84
|
+
return opencodeEntry.mainFailOpen({ home, payload });
|
|
85
|
+
}
|
|
86
|
+
if (payload.session_id || payload.sessionId) {
|
|
87
|
+
const codexEntry = require('./bridge/connectors/codex/hook-entry.js');
|
|
88
|
+
return codexEntry.mainFailOpen({ home, payload });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { status: 'ignored' };
|
|
92
|
+
}
|
|
93
|
+
module.exports = { main };
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
function materializeBridgeTree(runtimeDir) {
|
|
97
|
+
const srcRoot = path.join(__dirname, '..');
|
|
98
|
+
copyDirSync(path.join(srcRoot, 'core'), path.join(runtimeDir, 'bridge', 'core'));
|
|
99
|
+
copyDirSync(path.join(srcRoot, 'connectors'), path.join(runtimeDir, 'bridge', 'connectors'));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function writeTextAtomicSync(file, content) {
|
|
103
|
+
const tmp = `${file}.tmp`;
|
|
104
|
+
const fd = fs.openSync(tmp, 'w');
|
|
105
|
+
try {
|
|
106
|
+
fs.writeSync(fd, content);
|
|
107
|
+
fs.fsyncSync(fd);
|
|
108
|
+
} finally {
|
|
109
|
+
fs.closeSync(fd);
|
|
110
|
+
}
|
|
111
|
+
fs.renameSync(tmp, file);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function ensureRuntimeHome({ homeDir, version } = {}) {
|
|
115
|
+
const home = bridgeHome(homeDir);
|
|
116
|
+
const requestedVersion = version || require('../../../package.json').version;
|
|
117
|
+
const lock = new CrossProcessLock(lockPathFor(home, 'install.lock'));
|
|
118
|
+
return lock.withLock(async () => {
|
|
119
|
+
ensureDirSync(path.join(home, 'bin'));
|
|
120
|
+
ensureDirSync(path.join(home, 'runtime'));
|
|
121
|
+
ensureDirSync(path.join(home, 'journal'));
|
|
122
|
+
ensureDirSync(path.join(home, 'locks'));
|
|
123
|
+
|
|
124
|
+
const shimPath = path.join(home, 'bin', 'bridge-hook.cjs');
|
|
125
|
+
writeTextAtomicSync(shimPath, SHIM_SOURCE);
|
|
126
|
+
|
|
127
|
+
const runtimeDir = path.join(home, 'runtime', requestedVersion);
|
|
128
|
+
const activeFile = path.join(home, 'active-runtime.json');
|
|
129
|
+
const active = readJsonIfExists(activeFile);
|
|
130
|
+
|
|
131
|
+
const materializeRuntime = () => {
|
|
132
|
+
ensureDirSync(runtimeDir);
|
|
133
|
+
writeTextAtomicSync(path.join(runtimeDir, 'hook.cjs'), RUNTIME_HOOK_SOURCE);
|
|
134
|
+
materializeBridgeTree(runtimeDir);
|
|
135
|
+
if (!fs.existsSync(path.join(runtimeDir, 'manifest.json'))) {
|
|
136
|
+
writeJsonAtomicSync(path.join(runtimeDir, 'manifest.json'), {
|
|
137
|
+
version: requestedVersion,
|
|
138
|
+
createdAt: new Date().toISOString()
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
let action;
|
|
144
|
+
if (!active || !active.version) {
|
|
145
|
+
materializeRuntime();
|
|
146
|
+
writeJsonAtomicSync(activeFile, { version: requestedVersion, activatedAt: new Date().toISOString() });
|
|
147
|
+
action = 'activated';
|
|
148
|
+
} else {
|
|
149
|
+
const cmp = semver.compare(active.version, requestedVersion);
|
|
150
|
+
if (cmp === null) {
|
|
151
|
+
materializeRuntime();
|
|
152
|
+
writeJsonAtomicSync(activeFile, { version: requestedVersion, activatedAt: new Date().toISOString() });
|
|
153
|
+
action = 'activated-unparseable-active';
|
|
154
|
+
} else if (cmp === 0) {
|
|
155
|
+
materializeRuntime();
|
|
156
|
+
action = 'noop-same-version';
|
|
157
|
+
} else if (cmp < 0) {
|
|
158
|
+
const activeMajor = semver.parse(active.version).major;
|
|
159
|
+
const requestedMajor = semver.parse(requestedVersion).major;
|
|
160
|
+
if (activeMajor !== requestedMajor) {
|
|
161
|
+
return {
|
|
162
|
+
home,
|
|
163
|
+
shimPath,
|
|
164
|
+
conflict: true,
|
|
165
|
+
activeVersion: active.version,
|
|
166
|
+
requestedVersion,
|
|
167
|
+
action: 'conflict-major-mismatch'
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
materializeRuntime();
|
|
171
|
+
writeJsonAtomicSync(activeFile, { version: requestedVersion, activatedAt: new Date().toISOString() });
|
|
172
|
+
action = 'upgraded';
|
|
173
|
+
} else {
|
|
174
|
+
action = 'kept-newer';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
home,
|
|
180
|
+
shimPath,
|
|
181
|
+
conflict: false,
|
|
182
|
+
activeVersion: readJsonIfExists(activeFile).version,
|
|
183
|
+
requestedVersion,
|
|
184
|
+
action
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = { ensureRuntimeHome, SHIM_SOURCE };
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
function parse(version) {
|
|
4
|
-
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(String(version));
|
|
5
|
-
if (!match) return null;
|
|
6
|
-
return {
|
|
7
|
-
major: Number(match[1]),
|
|
8
|
-
minor: Number(match[2]),
|
|
9
|
-
patch: Number(match[3])
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function compare(a, b) {
|
|
14
|
-
const pa = parse(a);
|
|
15
|
-
const pb = parse(b);
|
|
16
|
-
if (!pa || !pb) return null;
|
|
17
|
-
if (pa.major !== pb.major) return pa.major < pb.major ? -1 : 1;
|
|
18
|
-
if (pa.minor !== pb.minor) return pa.minor < pb.minor ? -1 : 1;
|
|
19
|
-
if (pa.patch !== pb.patch) return pa.patch < pb.patch ? -1 : 1;
|
|
20
|
-
return 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
module.exports = { parse, compare };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function parse(version) {
|
|
4
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(String(version));
|
|
5
|
+
if (!match) return null;
|
|
6
|
+
return {
|
|
7
|
+
major: Number(match[1]),
|
|
8
|
+
minor: Number(match[2]),
|
|
9
|
+
patch: Number(match[3])
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function compare(a, b) {
|
|
14
|
+
const pa = parse(a);
|
|
15
|
+
const pb = parse(b);
|
|
16
|
+
if (!pa || !pb) return null;
|
|
17
|
+
if (pa.major !== pb.major) return pa.major < pb.major ? -1 : 1;
|
|
18
|
+
if (pa.minor !== pb.minor) return pa.minor < pb.minor ? -1 : 1;
|
|
19
|
+
if (pa.patch !== pb.patch) return pa.patch < pb.patch ? -1 : 1;
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { parse, compare };
|