0xray 3.4.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/.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 +79 -52
- package/dist/README.md +159 -82
- package/dist/SKILLS.md +79 -53
- package/dist/core/features-config.js +5 -0
- 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/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/researcher.server.js +3 -0
- package/dist/nucleus/autonomy-kernel.js +176 -0
- package/dist/nucleus/index.js +1 -0
- package/dist/skills/boot-orchestrator/SKILL.md +1 -5
- package/dist/skills/orchestrator/SKILL.md +35 -12
- 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/install-bridges.cjs +19 -6
- package/scripts/node/pre-publish-guard.js +107 -168
- 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 +12 -22
- package/scripts/node/sync-versions.mjs +12 -134
- package/scripts/node/universal-version-manager.js +4 -0
- 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/researcher.server.ts +4 -0
- package/src/skills/boot-orchestrator/SKILL.md +1 -5
- package/src/skills/orchestrator/SKILL.md +35 -12
- 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,228 +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
|
-
'.xray/inference/',
|
|
96
|
-
];
|
|
97
|
-
|
|
98
|
-
const realChanges = lines.filter(l => {
|
|
99
|
-
const filePath = l.slice(3); // skip status prefix (e.g., " M " or "M ")
|
|
100
|
-
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));
|
|
101
85
|
});
|
|
102
|
-
|
|
86
|
+
|
|
103
87
|
if (realChanges.length > 0) {
|
|
104
|
-
errors.push(`Git has uncommitted
|
|
105
|
-
log('
|
|
88
|
+
errors.push(`Git has uncommitted changes:\n${realChanges.join('\n')}`);
|
|
89
|
+
log('Uncommitted changes block release', 'error');
|
|
106
90
|
return false;
|
|
107
91
|
}
|
|
108
|
-
|
|
92
|
+
|
|
109
93
|
if (lines.length > 0) {
|
|
110
|
-
log('
|
|
94
|
+
log('Generated artifacts present (OK)', 'info');
|
|
111
95
|
}
|
|
112
|
-
log('Untracked files present (OK)', 'warn');
|
|
113
96
|
}
|
|
114
|
-
|
|
115
|
-
// Check if current branch is up to date with remote
|
|
97
|
+
|
|
116
98
|
const currentBranch = run('git rev-parse --abbrev-ref HEAD');
|
|
117
99
|
const remoteCommit = run(`git rev-parse origin/${currentBranch} 2>/dev/null || echo ""`);
|
|
118
100
|
const localCommit = run('git rev-parse HEAD');
|
|
119
|
-
|
|
101
|
+
|
|
120
102
|
if (remoteCommit && remoteCommit !== localCommit) {
|
|
121
|
-
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}`);
|
|
122
104
|
log(`Not pushed to origin/${currentBranch}`, 'error');
|
|
123
105
|
return false;
|
|
124
106
|
}
|
|
125
|
-
|
|
126
|
-
log(
|
|
107
|
+
|
|
108
|
+
log('Git status OK', 'success');
|
|
127
109
|
return true;
|
|
128
110
|
}
|
|
129
111
|
|
|
130
|
-
function
|
|
131
|
-
log('Checking version
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const vmContent = fs.readFileSync(vmPath, 'utf-8');
|
|
139
|
-
const usesPackageSsot =
|
|
140
|
-
vmContent.includes('readPackageVersion()') &&
|
|
141
|
-
/version:\s*PACKAGE_VERSION/.test(vmContent);
|
|
142
|
-
|
|
143
|
-
if (usesPackageSsot) {
|
|
144
|
-
log(`Version SSOT: package.json (${pkgVersion})`, 'success');
|
|
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');
|
|
145
120
|
return true;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const versionMatch = vmContent.match(/framework:\s*\{[\s\S]*?version:\s*"([^"]+)"/);
|
|
149
|
-
if (!versionMatch) {
|
|
150
|
-
errors.push('Cannot find framework version in version manager');
|
|
121
|
+
} catch {
|
|
122
|
+
errors.push('Version reconcile failed — run: npm run version:reconcile');
|
|
151
123
|
return false;
|
|
152
124
|
}
|
|
153
|
-
|
|
154
|
-
const vmVersion = versionMatch[1];
|
|
155
|
-
if (pkgVersion !== vmVersion) {
|
|
156
|
-
warnings.push(`Version mismatch: package.json (${pkgVersion}) != version manager (${vmVersion})`);
|
|
157
|
-
log(`Version mismatch: package.json=${pkgVersion}, version-manager=${vmVersion}`, 'warn');
|
|
158
|
-
} else {
|
|
159
|
-
log(`Versions aligned: ${pkgVersion}`, 'success');
|
|
160
|
-
}
|
|
161
|
-
return true;
|
|
162
125
|
}
|
|
163
126
|
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
// Check that files have been updated (look for recent timestamps or version)
|
|
172
|
-
const { resolveConfigPath } = require('../helpers/resolve-config-path.cjs');
|
|
173
|
-
let features;
|
|
127
|
+
function runBuild() {
|
|
128
|
+
if (skipBuild) {
|
|
129
|
+
log('Build skipped', 'info');
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
log('Running build...', 'step');
|
|
174
133
|
try {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (!features.version && !features.xray_version) {
|
|
179
|
-
warnings.push('features.json may not have been synced by version manager');
|
|
180
|
-
log('features.json may need version sync', 'warn');
|
|
181
|
-
} else {
|
|
182
|
-
log('Version manager appears to have run', 'success');
|
|
183
|
-
}
|
|
134
|
+
execSync('npm run build', { cwd: rootDir, stdio: 'inherit' });
|
|
135
|
+
log('Build succeeded', 'success');
|
|
136
|
+
return true;
|
|
184
137
|
} catch {
|
|
185
|
-
|
|
138
|
+
errors.push('Build failed');
|
|
139
|
+
return false;
|
|
186
140
|
}
|
|
187
|
-
|
|
188
|
-
return true;
|
|
189
141
|
}
|
|
190
142
|
|
|
191
143
|
function runTests() {
|
|
144
|
+
if (skipTests) {
|
|
145
|
+
log('Tests skipped', 'info');
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
192
148
|
log('Running tests...', 'step');
|
|
193
|
-
|
|
194
149
|
try {
|
|
195
150
|
execSync('npm test', { cwd: rootDir, stdio: 'inherit' });
|
|
196
151
|
log('Tests passed', 'success');
|
|
197
152
|
return true;
|
|
198
|
-
} catch
|
|
153
|
+
} catch {
|
|
199
154
|
errors.push('Tests failed');
|
|
200
|
-
log('Tests failed', 'error');
|
|
201
155
|
return false;
|
|
202
156
|
}
|
|
203
157
|
}
|
|
204
158
|
|
|
205
|
-
function
|
|
206
|
-
|
|
207
|
-
|
|
159
|
+
function runConsumerSmoke() {
|
|
160
|
+
if (skipSmoke) {
|
|
161
|
+
log('Consumer smoke skipped', 'warn');
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
log('Running consumer install smoke...', 'step');
|
|
208
165
|
try {
|
|
209
|
-
execSync('
|
|
210
|
-
log('
|
|
166
|
+
execSync('node scripts/node/consumer-install-smoke.mjs', { cwd: rootDir, stdio: 'inherit' });
|
|
167
|
+
log('Consumer smoke passed', 'success');
|
|
211
168
|
return true;
|
|
212
|
-
} catch
|
|
213
|
-
errors.push('
|
|
214
|
-
log('Build failed', 'error');
|
|
169
|
+
} catch {
|
|
170
|
+
errors.push('Consumer install smoke failed');
|
|
215
171
|
return false;
|
|
216
172
|
}
|
|
217
173
|
}
|
|
218
174
|
|
|
219
175
|
function main() {
|
|
220
176
|
console.log('\n' + '='.repeat(60));
|
|
221
|
-
log('PRE-PUBLISH VALIDATION', 'step');
|
|
177
|
+
log(verifyOnly ? 'PRE-PUBLISH VERIFY' : 'PRE-PUBLISH VALIDATION', 'step');
|
|
222
178
|
console.log('='.repeat(60) + '\n');
|
|
223
|
-
|
|
179
|
+
|
|
224
180
|
const checks = [
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
181
|
+
checkGitStatus,
|
|
182
|
+
checkVersionReconcile,
|
|
183
|
+
runBuild,
|
|
184
|
+
runTests,
|
|
185
|
+
runConsumerSmoke,
|
|
230
186
|
];
|
|
231
|
-
|
|
187
|
+
|
|
232
188
|
let allPassed = true;
|
|
233
|
-
for (const
|
|
234
|
-
if (!
|
|
189
|
+
for (const fn of checks) {
|
|
190
|
+
if (!fn()) {
|
|
235
191
|
allPassed = false;
|
|
236
192
|
break;
|
|
237
193
|
}
|
|
238
194
|
}
|
|
239
|
-
|
|
195
|
+
|
|
240
196
|
console.log('\n' + '='.repeat(60));
|
|
241
|
-
|
|
197
|
+
|
|
242
198
|
if (allPassed) {
|
|
243
|
-
log('ALL CHECKS PASSED
|
|
199
|
+
log('ALL CHECKS PASSED', 'success');
|
|
244
200
|
console.log('='.repeat(60) + '\n');
|
|
245
|
-
console.log(`Run: ${GREEN}npm publish${RESET}\n`);
|
|
246
201
|
process.exit(0);
|
|
247
|
-
} else {
|
|
248
|
-
log('VALIDATION FAILED', 'error');
|
|
249
|
-
console.log('='.repeat(60) + '\n');
|
|
250
|
-
|
|
251
|
-
if (errors.length > 0) {
|
|
252
|
-
console.log(`${RED}Errors:${RESET}`);
|
|
253
|
-
errors.forEach(e => console.log(` • ${e}`));
|
|
254
|
-
console.log('');
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (warnings.length > 0) {
|
|
258
|
-
console.log(`${YELLOW}Warnings:${RESET}`);
|
|
259
|
-
warnings.forEach(w => console.log(` • ${w}`));
|
|
260
|
-
console.log('');
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
console.log(`${YELLOW}To fix:${RESET}`);
|
|
264
|
-
console.log(' 1. Commit & push all changes: git add -A && git commit -m "..." && git push');
|
|
265
|
-
console.log(' 2. Bump version in package.json');
|
|
266
|
-
console.log(' 3. Update version in scripts/node/universal-version-manager.js');
|
|
267
|
-
console.log(' 4. Run: npm run version:sync');
|
|
268
|
-
console.log(' 5. Commit & push version changes');
|
|
269
|
-
console.log(' 6. Run: npm run pre-publish-guard\n');
|
|
270
|
-
|
|
271
|
-
process.exit(1);
|
|
272
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);
|
|
273
212
|
}
|
|
274
213
|
|
|
275
|
-
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();
|