0xray 3.3.1 → 3.4.2
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/.opencode/enforcer-config.json +1 -4
- package/.xray/codex.json +598 -0
- package/.xray/config.json +25 -0
- package/.xray/features.json +146 -0
- package/.xray/features.schema.json +33 -0
- package/AGENTS-consumer.md +127 -10
- package/AGENTS.md +172 -56
- package/README.md +159 -82
- package/SKILLS.md +79 -53
- package/dist/AGENTS.md +172 -56
- package/dist/CHANGELOG.md +90 -51
- package/dist/README.md +159 -82
- package/dist/SKILLS.md +79 -53
- package/dist/analytics/routing-refiner.js +0 -2
- package/dist/core/boot-orchestrator.js +9 -1
- package/dist/core/features-config.js +5 -1
- package/dist/integrations/grok/grok-cli.js +47 -25
- package/dist/integrations/grok/hooks/grok-hook-activity.js +53 -0
- package/dist/integrations/grok/hooks/grok-hook-utils.js +261 -0
- package/dist/integrations/grok/hooks/pre-tool-use.js +85 -115
- package/dist/integrations/grok/hooks/session-start.js +52 -0
- package/dist/integrations/grok/plugin/0xray/hooks/hooks.json +20 -3
- package/dist/integrations/hermes-agent/bridge.mjs +11 -1
- package/dist/mcps/config/server-config-registry.js +4 -3
- package/dist/mcps/orchestrator/handlers/complexity-handler.js +33 -3
- package/dist/mcps/orchestrator/server.js +1 -1
- package/dist/mcps/registry.json +0 -1
- package/dist/mcps/researcher.server.js +3 -0
- package/dist/nucleus/autonomy-kernel.js +176 -0
- package/dist/nucleus/index.js +1 -0
- package/dist/orchestrator/universal-registry-bridge.js +1 -1
- package/dist/skills/boot-orchestrator/SKILL.md +1 -5
- package/dist/skills/orchestrator/SKILL.md +35 -12
- package/dist/skills/registry.json +0 -1
- package/dist/skills/researcher/SKILL.md +17 -4
- package/dist/skills/strategist/SKILL.md +12 -0
- package/dist/skills/testing-strategy/SKILL.md +13 -2
- package/package.json +23 -8
- package/scripts/node/consumer-install-smoke.mjs +163 -0
- package/scripts/node/enforce-agents-md.mjs +0 -26
- package/scripts/node/enforce-version-compliance.sh +0 -1
- package/scripts/node/enforce-version-compliance.ts +0 -10
- package/scripts/node/install-bridges.cjs +154 -39
- package/scripts/node/pre-publish-guard.js +108 -165
- package/scripts/node/reconcile-version.mjs +147 -0
- package/scripts/node/release-gate.mjs +66 -0
- package/scripts/node/release.js +5 -147
- package/scripts/node/release.mjs +106 -181
- package/scripts/node/setup.cjs +21 -26
- package/scripts/node/sync-versions.mjs +12 -134
- package/scripts/node/universal-version-manager.js +22 -4
- package/scripts/node/validate-release-docs.mjs +299 -0
- package/scripts/node/version-manager.mjs +225 -49
- package/src/integrations/grok/plugin/0xray/hooks/hooks.json +20 -3
- package/src/mcps/config/__tests__/server-config-registry.test.ts +9 -0
- package/src/mcps/config/server-config-registry.ts +5 -3
- package/src/mcps/orchestrator/handlers/complexity-handler.ts +54 -3
- package/src/mcps/orchestrator/server.ts +1 -1
- package/src/mcps/registry.json +0 -1
- package/src/mcps/researcher.server.ts +4 -0
- package/src/opencode/enforcer-config.json +1 -4
- package/src/skills/boot-orchestrator/SKILL.md +1 -5
- package/src/skills/orchestrator/SKILL.md +35 -12
- package/src/skills/registry.json +0 -1
- package/src/skills/researcher/SKILL.md +17 -4
- package/src/skills/strategist/SKILL.md +12 -0
- package/src/skills/testing-strategy/SKILL.md +13 -2
- package/xray/config.json +25 -0
- package/xray/features.json +146 -0
- package/xray/features.schema.json +33 -0
|
@@ -2,30 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Pre-Publish Validation Script
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 4. All tests pass
|
|
11
|
-
* 5. Build succeeds
|
|
12
|
-
*
|
|
13
|
-
* Usage:
|
|
14
|
-
* node scripts/node/pre-publish-guard.js
|
|
15
|
-
* npm run pre-publish-guard
|
|
5
|
+
*
|
|
6
|
+
* Full mode: git + reconcile + build + test + consumer smoke
|
|
7
|
+
* --verify-only: git + reconcile + (optional smoke via release-gate)
|
|
8
|
+
* --skip-smoke: skip consumer install smoke
|
|
9
|
+
* --skip-build / --skip-tests: skip redundant steps when gate already ran
|
|
16
10
|
*/
|
|
17
11
|
|
|
18
12
|
import { execSync } from 'child_process';
|
|
19
13
|
import fs from 'fs';
|
|
20
14
|
import path from 'path';
|
|
21
15
|
import { fileURLToPath } from 'url';
|
|
22
|
-
import { createRequire } from 'module';
|
|
23
16
|
|
|
24
|
-
const require = createRequire(import.meta.url);
|
|
25
17
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
18
|
const rootDir = path.resolve(__dirname, '../..');
|
|
27
19
|
|
|
28
|
-
|
|
20
|
+
const verifyOnly = process.argv.includes('--verify-only');
|
|
21
|
+
const skipSmoke =
|
|
22
|
+
process.argv.includes('--skip-smoke') ||
|
|
23
|
+
process.env.SKIP_CONSUMER_SMOKE === '1' ||
|
|
24
|
+
verifyOnly;
|
|
25
|
+
const skipBuild = process.argv.includes('--skip-build') || verifyOnly;
|
|
26
|
+
const skipTests = process.argv.includes('--skip-tests') || verifyOnly;
|
|
27
|
+
|
|
29
28
|
const RED = '\x1b[31m';
|
|
30
29
|
const GREEN = '\x1b[32m';
|
|
31
30
|
const YELLOW = '\x1b[33m';
|
|
@@ -48,224 +47,168 @@ function log(msg, type = 'info') {
|
|
|
48
47
|
|
|
49
48
|
function run(cmd, options = {}) {
|
|
50
49
|
try {
|
|
51
|
-
return execSync(cmd, {
|
|
52
|
-
cwd: rootDir,
|
|
53
|
-
stdio: 'pipe',
|
|
50
|
+
return execSync(cmd, {
|
|
51
|
+
cwd: rootDir,
|
|
52
|
+
stdio: 'pipe',
|
|
54
53
|
encoding: 'utf-8',
|
|
55
|
-
...options
|
|
54
|
+
...options,
|
|
56
55
|
}).trim();
|
|
57
|
-
} catch
|
|
56
|
+
} catch {
|
|
58
57
|
return null;
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
|
|
61
|
+
/** Runtime/generated paths only — never ignore scripts/, src/, or CHANGELOG */
|
|
62
|
+
const ARTIFACT_PREFIXES = [
|
|
63
|
+
'.opencode/codex.codex',
|
|
64
|
+
'.opencode/enforcer-config.json',
|
|
65
|
+
'.opencode/package.json',
|
|
66
|
+
'.opencode/command/',
|
|
67
|
+
'.opencode/activity-report.json',
|
|
68
|
+
'.opencode/init.sh',
|
|
69
|
+
'.xray/inference/',
|
|
70
|
+
'logs/',
|
|
71
|
+
'backups/',
|
|
72
|
+
'.ci-reports/',
|
|
73
|
+
];
|
|
74
|
+
|
|
62
75
|
function checkGitStatus() {
|
|
63
76
|
log('Checking git status...', 'step');
|
|
64
|
-
|
|
65
|
-
// Check for uncommitted changes
|
|
77
|
+
|
|
66
78
|
const status = run('git status --porcelain');
|
|
67
79
|
if (status && status.length > 0) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const artifactPrefixes = [
|
|
74
|
-
'.opencode/codex.codex',
|
|
75
|
-
'.opencode/enforcer-config.json',
|
|
76
|
-
'.opencode/package.json',
|
|
77
|
-
'.opencode/command/',
|
|
78
|
-
'.opencode/activity-report.json',
|
|
79
|
-
'command/',
|
|
80
|
-
'enforcer-config.json',
|
|
81
|
-
'docs/',
|
|
82
|
-
'tests/',
|
|
83
|
-
'tweets/',
|
|
84
|
-
'scripts/',
|
|
85
|
-
'src/__tests__/',
|
|
86
|
-
'src/skills/registry.json',
|
|
87
|
-
'CHANGELOG.md',
|
|
88
|
-
'AGENTS.md',
|
|
89
|
-
'AGENTS-full.md',
|
|
90
|
-
'AGENTS-consumer.md',
|
|
91
|
-
'README.md',
|
|
92
|
-
'logs/',
|
|
93
|
-
'backups/',
|
|
94
|
-
'Users/',
|
|
95
|
-
];
|
|
96
|
-
|
|
97
|
-
const realChanges = lines.filter(l => {
|
|
98
|
-
const filePath = l.slice(3); // skip status prefix (e.g., " M " or "M ")
|
|
99
|
-
return !artifactPrefixes.some(prefix => filePath.startsWith(prefix));
|
|
80
|
+
const lines = status.split('\n').filter((l) => !l.startsWith('??'));
|
|
81
|
+
|
|
82
|
+
const realChanges = lines.filter((l) => {
|
|
83
|
+
const filePath = l.slice(2).trim();
|
|
84
|
+
return !ARTIFACT_PREFIXES.some((prefix) => filePath.startsWith(prefix));
|
|
100
85
|
});
|
|
101
|
-
|
|
86
|
+
|
|
102
87
|
if (realChanges.length > 0) {
|
|
103
|
-
errors.push(`Git has uncommitted
|
|
104
|
-
log('
|
|
88
|
+
errors.push(`Git has uncommitted changes:\n${realChanges.join('\n')}`);
|
|
89
|
+
log('Uncommitted changes block release', 'error');
|
|
105
90
|
return false;
|
|
106
91
|
}
|
|
107
|
-
|
|
92
|
+
|
|
108
93
|
if (lines.length > 0) {
|
|
109
|
-
log('
|
|
94
|
+
log('Generated artifacts present (OK)', 'info');
|
|
110
95
|
}
|
|
111
|
-
log('Untracked files present (OK)', 'warn');
|
|
112
96
|
}
|
|
113
|
-
|
|
114
|
-
// Check if current branch is up to date with remote
|
|
97
|
+
|
|
115
98
|
const currentBranch = run('git rev-parse --abbrev-ref HEAD');
|
|
116
99
|
const remoteCommit = run(`git rev-parse origin/${currentBranch} 2>/dev/null || echo ""`);
|
|
117
100
|
const localCommit = run('git rev-parse HEAD');
|
|
118
|
-
|
|
101
|
+
|
|
119
102
|
if (remoteCommit && remoteCommit !== localCommit) {
|
|
120
|
-
errors.push(`Local commit ${localCommit.slice(0,7)} not pushed to origin/${currentBranch}`);
|
|
103
|
+
errors.push(`Local commit ${localCommit.slice(0, 7)} not pushed to origin/${currentBranch}`);
|
|
121
104
|
log(`Not pushed to origin/${currentBranch}`, 'error');
|
|
122
105
|
return false;
|
|
123
106
|
}
|
|
124
|
-
|
|
125
|
-
log(
|
|
107
|
+
|
|
108
|
+
log('Git status OK', 'success');
|
|
126
109
|
return true;
|
|
127
110
|
}
|
|
128
111
|
|
|
129
|
-
function
|
|
130
|
-
log('Checking version
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
112
|
+
function checkVersionReconcile() {
|
|
113
|
+
log('Checking version reconcile...', 'step');
|
|
114
|
+
try {
|
|
115
|
+
execSync('node scripts/node/reconcile-version.mjs --check', {
|
|
116
|
+
cwd: rootDir,
|
|
117
|
+
stdio: 'inherit',
|
|
118
|
+
});
|
|
119
|
+
log('Version reconcile OK', 'success');
|
|
120
|
+
return true;
|
|
121
|
+
} catch {
|
|
122
|
+
errors.push('Version reconcile failed — run: npm run version:reconcile');
|
|
139
123
|
return false;
|
|
140
124
|
}
|
|
141
|
-
|
|
142
|
-
const vmVersion = versionMatch[1];
|
|
143
|
-
|
|
144
|
-
// Load package.json
|
|
145
|
-
const pkgPath = path.join(rootDir, 'package.json');
|
|
146
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
147
|
-
const pkgVersion = pkg.version;
|
|
148
|
-
|
|
149
|
-
if (pkgVersion !== vmVersion) {
|
|
150
|
-
// package.json is often 1 ahead of UVM after npm version patch — non-blocking
|
|
151
|
-
warnings.push(`Version mismatch: package.json (${pkgVersion}) != version manager (${vmVersion})`);
|
|
152
|
-
log(`Version mismatch: package.json=${pkgVersion}, version-manager=${vmVersion}`, 'warn');
|
|
153
|
-
return true; // non-blocking — matches pre-commit hook behavior
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
log(`Versions aligned: ${pkgVersion}`, 'success');
|
|
157
|
-
return true;
|
|
158
125
|
}
|
|
159
126
|
|
|
160
|
-
function
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// Check that files have been updated (look for recent timestamps or version)
|
|
168
|
-
const { resolveConfigPath } = require('../helpers/resolve-config-path.cjs');
|
|
169
|
-
let features;
|
|
127
|
+
function runBuild() {
|
|
128
|
+
if (skipBuild) {
|
|
129
|
+
log('Build skipped', 'info');
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
log('Running build...', 'step');
|
|
170
133
|
try {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (!features.version && !features.xray_version) {
|
|
175
|
-
warnings.push('features.json may not have been synced by version manager');
|
|
176
|
-
log('features.json may need version sync', 'warn');
|
|
177
|
-
} else {
|
|
178
|
-
log('Version manager appears to have run', 'success');
|
|
179
|
-
}
|
|
134
|
+
execSync('npm run build', { cwd: rootDir, stdio: 'inherit' });
|
|
135
|
+
log('Build succeeded', 'success');
|
|
136
|
+
return true;
|
|
180
137
|
} catch {
|
|
181
|
-
|
|
138
|
+
errors.push('Build failed');
|
|
139
|
+
return false;
|
|
182
140
|
}
|
|
183
|
-
|
|
184
|
-
return true;
|
|
185
141
|
}
|
|
186
142
|
|
|
187
143
|
function runTests() {
|
|
144
|
+
if (skipTests) {
|
|
145
|
+
log('Tests skipped', 'info');
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
188
148
|
log('Running tests...', 'step');
|
|
189
|
-
|
|
190
149
|
try {
|
|
191
150
|
execSync('npm test', { cwd: rootDir, stdio: 'inherit' });
|
|
192
151
|
log('Tests passed', 'success');
|
|
193
152
|
return true;
|
|
194
|
-
} catch
|
|
153
|
+
} catch {
|
|
195
154
|
errors.push('Tests failed');
|
|
196
|
-
log('Tests failed', 'error');
|
|
197
155
|
return false;
|
|
198
156
|
}
|
|
199
157
|
}
|
|
200
158
|
|
|
201
|
-
function
|
|
202
|
-
|
|
203
|
-
|
|
159
|
+
function runConsumerSmoke() {
|
|
160
|
+
if (skipSmoke) {
|
|
161
|
+
log('Consumer smoke skipped', 'warn');
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
log('Running consumer install smoke...', 'step');
|
|
204
165
|
try {
|
|
205
|
-
execSync('
|
|
206
|
-
log('
|
|
166
|
+
execSync('node scripts/node/consumer-install-smoke.mjs', { cwd: rootDir, stdio: 'inherit' });
|
|
167
|
+
log('Consumer smoke passed', 'success');
|
|
207
168
|
return true;
|
|
208
|
-
} catch
|
|
209
|
-
errors.push('
|
|
210
|
-
log('Build failed', 'error');
|
|
169
|
+
} catch {
|
|
170
|
+
errors.push('Consumer install smoke failed');
|
|
211
171
|
return false;
|
|
212
172
|
}
|
|
213
173
|
}
|
|
214
174
|
|
|
215
175
|
function main() {
|
|
216
176
|
console.log('\n' + '='.repeat(60));
|
|
217
|
-
log('PRE-PUBLISH VALIDATION', 'step');
|
|
177
|
+
log(verifyOnly ? 'PRE-PUBLISH VERIFY' : 'PRE-PUBLISH VALIDATION', 'step');
|
|
218
178
|
console.log('='.repeat(60) + '\n');
|
|
219
|
-
|
|
179
|
+
|
|
220
180
|
const checks = [
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
181
|
+
checkGitStatus,
|
|
182
|
+
checkVersionReconcile,
|
|
183
|
+
runBuild,
|
|
184
|
+
runTests,
|
|
185
|
+
runConsumerSmoke,
|
|
226
186
|
];
|
|
227
|
-
|
|
187
|
+
|
|
228
188
|
let allPassed = true;
|
|
229
|
-
for (const
|
|
230
|
-
if (!
|
|
189
|
+
for (const fn of checks) {
|
|
190
|
+
if (!fn()) {
|
|
231
191
|
allPassed = false;
|
|
232
192
|
break;
|
|
233
193
|
}
|
|
234
194
|
}
|
|
235
|
-
|
|
195
|
+
|
|
236
196
|
console.log('\n' + '='.repeat(60));
|
|
237
|
-
|
|
197
|
+
|
|
238
198
|
if (allPassed) {
|
|
239
|
-
log('ALL CHECKS PASSED
|
|
199
|
+
log('ALL CHECKS PASSED', 'success');
|
|
240
200
|
console.log('='.repeat(60) + '\n');
|
|
241
|
-
console.log(`Run: ${GREEN}npm publish${RESET}\n`);
|
|
242
201
|
process.exit(0);
|
|
243
|
-
} else {
|
|
244
|
-
log('VALIDATION FAILED', 'error');
|
|
245
|
-
console.log('='.repeat(60) + '\n');
|
|
246
|
-
|
|
247
|
-
if (errors.length > 0) {
|
|
248
|
-
console.log(`${RED}Errors:${RESET}`);
|
|
249
|
-
errors.forEach(e => console.log(` • ${e}`));
|
|
250
|
-
console.log('');
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (warnings.length > 0) {
|
|
254
|
-
console.log(`${YELLOW}Warnings:${RESET}`);
|
|
255
|
-
warnings.forEach(w => console.log(` • ${w}`));
|
|
256
|
-
console.log('');
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
console.log(`${YELLOW}To fix:${RESET}`);
|
|
260
|
-
console.log(' 1. Commit & push all changes: git add -A && git commit -m "..." && git push');
|
|
261
|
-
console.log(' 2. Bump version in package.json');
|
|
262
|
-
console.log(' 3. Update version in scripts/node/universal-version-manager.js');
|
|
263
|
-
console.log(' 4. Run: npm run version:sync');
|
|
264
|
-
console.log(' 5. Commit & push version changes');
|
|
265
|
-
console.log(' 6. Run: npm run pre-publish-guard\n');
|
|
266
|
-
|
|
267
|
-
process.exit(1);
|
|
268
202
|
}
|
|
203
|
+
|
|
204
|
+
log('VALIDATION FAILED', 'error');
|
|
205
|
+
console.log('='.repeat(60) + '\n');
|
|
206
|
+
if (errors.length) {
|
|
207
|
+
console.log(`${RED}Errors:${RESET}`);
|
|
208
|
+
errors.forEach((e) => console.log(` • ${e}`));
|
|
209
|
+
}
|
|
210
|
+
console.log(`\n${YELLOW}Fix:${RESET} npm run release:gate or npm run version:reconcile\n`);
|
|
211
|
+
process.exit(1);
|
|
269
212
|
}
|
|
270
213
|
|
|
271
|
-
main();
|
|
214
|
+
main();
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* reconcile-version.mjs — Single version truth before bump/tag/publish.
|
|
4
|
+
*
|
|
5
|
+
* Sources: npm registry, package.json, latest git tag.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node scripts/node/reconcile-version.mjs # report
|
|
9
|
+
* node scripts/node/reconcile-version.mjs --check # fail if not publish-ready
|
|
10
|
+
* node scripts/node/reconcile-version.mjs patch # print next patch target
|
|
11
|
+
* node scripts/node/reconcile-version.mjs patch --apply # write package.json
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { execSync } from "child_process";
|
|
15
|
+
import fs from "fs";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import { fileURLToPath } from "url";
|
|
18
|
+
|
|
19
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const rootDir = path.resolve(__dirname, "../..");
|
|
21
|
+
const pkgPath = path.join(rootDir, "package.json");
|
|
22
|
+
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
const checkMode = args.includes("--check");
|
|
25
|
+
const applyMode = args.includes("--apply");
|
|
26
|
+
const printTarget = args.includes("--print-target");
|
|
27
|
+
const bumpType = args.find((a) => ["major", "minor", "patch"].includes(a)) || null;
|
|
28
|
+
|
|
29
|
+
function run(cmd) {
|
|
30
|
+
try {
|
|
31
|
+
return execSync(cmd, { cwd: rootDir, encoding: "utf-8" }).trim();
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readLocalVersion() {
|
|
38
|
+
return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readPublishedVersion() {
|
|
42
|
+
const v = run("npm view 0xray version");
|
|
43
|
+
return v || "0.0.0";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readLatestTag() {
|
|
47
|
+
const tag = run('git describe --tags --abbrev=0 2>/dev/null');
|
|
48
|
+
if (!tag) return null;
|
|
49
|
+
return tag.replace(/^v/, "");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseVersion(version) {
|
|
53
|
+
const [major, minor, patch] = version.split(".").map((n) => parseInt(n, 10));
|
|
54
|
+
return { major, minor, patch };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function bumpVersion(version, type) {
|
|
58
|
+
const v = parseVersion(version);
|
|
59
|
+
switch (type) {
|
|
60
|
+
case "major":
|
|
61
|
+
return `${v.major + 1}.0.0`;
|
|
62
|
+
case "minor":
|
|
63
|
+
return `${v.major}.${v.minor + 1}.0`;
|
|
64
|
+
case "patch":
|
|
65
|
+
return `${v.major}.${v.minor}.${v.patch + 1}`;
|
|
66
|
+
default:
|
|
67
|
+
return version;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function compare(a, b) {
|
|
72
|
+
const va = parseVersion(a);
|
|
73
|
+
const vb = parseVersion(b);
|
|
74
|
+
if (va.major !== vb.major) return va.major - vb.major;
|
|
75
|
+
if (va.minor !== vb.minor) return va.minor - vb.minor;
|
|
76
|
+
return va.patch - vb.patch;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function maxVersion(...versions) {
|
|
80
|
+
return versions.reduce((best, cur) => (compare(cur, best) > 0 ? cur : best));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function fail(msg) {
|
|
84
|
+
console.error(`❌ ${msg}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function main() {
|
|
89
|
+
const local = readLocalVersion();
|
|
90
|
+
const npm = readPublishedVersion();
|
|
91
|
+
const tag = readLatestTag();
|
|
92
|
+
|
|
93
|
+
const baseline = maxVersion(npm, tag || "0.0.0");
|
|
94
|
+
const target = bumpType ? bumpVersion(baseline, bumpType) : local;
|
|
95
|
+
|
|
96
|
+
if (printTarget && bumpType) {
|
|
97
|
+
console.log(target);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log("\n=== Version Reconcile ===\n");
|
|
102
|
+
console.log(` npm registry : ${npm}`);
|
|
103
|
+
console.log(` package.json : ${local}`);
|
|
104
|
+
console.log(` latest tag : ${tag ? `v${tag}` : "(none)"}`);
|
|
105
|
+
if (bumpType) {
|
|
106
|
+
console.log(` bump ${bumpType} : ${target} (from baseline ${baseline})`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (applyMode && bumpType) {
|
|
110
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
111
|
+
pkg.version = target;
|
|
112
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
113
|
+
console.log(`\n✅ Updated package.json → ${target}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!checkMode) {
|
|
117
|
+
if (compare(local, npm) <= 0 && !bumpType) {
|
|
118
|
+
console.log("\n⚠️ Local version is not ahead of npm — bump before publishing.");
|
|
119
|
+
} else {
|
|
120
|
+
console.log("\n✅ Reconcile report complete.");
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Strict checks for release gate / pre-tag
|
|
126
|
+
if (compare(local, npm) <= 0) {
|
|
127
|
+
fail(`package.json (${local}) must be > npm (${npm}). Run: node scripts/node/reconcile-version.mjs patch --apply`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (tag && compare(local, tag) < 0) {
|
|
131
|
+
fail(`package.json (${local}) is behind latest tag v${tag}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (tag && compare(local, tag) === 0 && compare(npm, local) < 0) {
|
|
135
|
+
fail(`tag v${tag} exists but npm is only ${npm} — publish or retag`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (tag && compare(local, tag) > 0 && compare(npm, tag) === 0) {
|
|
139
|
+
console.log(`✅ Ready to tag v${local} (npm at ${npm})`);
|
|
140
|
+
} else if (compare(local, npm) > 0) {
|
|
141
|
+
console.log(`✅ Ready to publish ${local} (npm at ${npm})`);
|
|
142
|
+
} else {
|
|
143
|
+
console.log("✅ Version reconcile OK");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* release-gate.mjs — Single release checkpoint.
|
|
4
|
+
*
|
|
5
|
+
* Modes:
|
|
6
|
+
* (default) build + test + consumer smoke — run once before commit
|
|
7
|
+
* --verify-only git + reconcile + release docs + consumer smoke — after push, before tag
|
|
8
|
+
* --skip-smoke skip consumer install smoke
|
|
9
|
+
* --skip-docs skip release docs validation (not recommended for publish)
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node scripts/node/release-gate.mjs
|
|
13
|
+
* node scripts/node/release-gate.mjs --verify-only
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { execSync } from "child_process";
|
|
17
|
+
import path from "path";
|
|
18
|
+
import { fileURLToPath } from "url";
|
|
19
|
+
|
|
20
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const rootDir = path.resolve(__dirname, "../..");
|
|
22
|
+
const verifyOnly = process.argv.includes("--verify-only");
|
|
23
|
+
const skipSmoke = process.argv.includes("--skip-smoke");
|
|
24
|
+
const skipDocs = process.argv.includes("--skip-docs");
|
|
25
|
+
|
|
26
|
+
function step(label, cmd) {
|
|
27
|
+
console.log(`\n${"─".repeat(60)}\n🔄 ${label}\n${"─".repeat(60)}\n`);
|
|
28
|
+
execSync(cmd, { cwd: rootDir, stdio: "inherit" });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function main() {
|
|
32
|
+
const mode = verifyOnly ? "verify" : "full";
|
|
33
|
+
console.log("\n╔════════════════════════════════════════════════════════╗");
|
|
34
|
+
console.log(`║ 🛡️ 0xRay Release Gate (${mode})`.padEnd(57) + "║");
|
|
35
|
+
console.log("╚════════════════════════════════════════════════════════╝");
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
if (verifyOnly) {
|
|
39
|
+
step("1/4 Git + reconcile", "node scripts/node/pre-publish-guard.js --verify-only");
|
|
40
|
+
if (!skipDocs) {
|
|
41
|
+
step("2/4 Release docs", "node scripts/node/validate-release-docs.mjs");
|
|
42
|
+
}
|
|
43
|
+
if (!skipSmoke) {
|
|
44
|
+
step("3/4 Consumer install smoke", "node scripts/node/consumer-install-smoke.mjs");
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
step("1/4 Build", "npm run build");
|
|
48
|
+
step("2/4 Tests", "npm test");
|
|
49
|
+
if (!skipSmoke) {
|
|
50
|
+
step("3/4 Consumer install smoke", "node scripts/node/consumer-install-smoke.mjs");
|
|
51
|
+
}
|
|
52
|
+
if (!skipDocs && process.argv.includes("--with-docs")) {
|
|
53
|
+
step("4/4 Release docs", "node scripts/node/validate-release-docs.mjs");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log("\n╔════════════════════════════════════════════════════════╗");
|
|
58
|
+
console.log("║ ✅ RELEASE GATE PASSED ║");
|
|
59
|
+
console.log("╚════════════════════════════════════════════════════════╝\n");
|
|
60
|
+
} catch {
|
|
61
|
+
console.error("\n❌ RELEASE GATE FAILED — do not tag or publish\n");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
main();
|