@the-open-engine/zeroshot 6.5.0 → 6.7.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/bin/zeroshot-cluster-worker.js +16 -0
- package/cli/index.js +135 -12
- package/docs/openengine-cluster-protocol/v1/legacy-worker.md +117 -0
- package/lib/cluster-worker/contracts.js +194 -0
- package/lib/cluster-worker/engine-adapter.js +300 -0
- package/lib/cluster-worker/executable.js +229 -0
- package/lib/cluster-worker/index.d.ts +227 -0
- package/lib/cluster-worker/index.js +294 -0
- package/lib/cluster-worker/object-utils.js +17 -0
- package/lib/cluster-worker/process-stdio.js +37 -0
- package/lib/cluster-worker/profiles.js +81 -0
- package/lib/cluster-worker/runtime-engine.js +50 -0
- package/lib/cluster-worker/runtime-support.js +298 -0
- package/lib/cluster-worker/state-machine.js +147 -0
- package/lib/cluster-worker/terminal-normalizer.js +95 -0
- package/lib/cluster-worker/worker-internals.js +20 -0
- package/lib/detached-startup.js +127 -11
- package/lib/process-liveness.js +26 -0
- package/lib/start-cluster.js +93 -18
- package/package.json +10 -2
- package/protocol/openengine-cluster/v1/worker.schema.json +1174 -0
- package/scripts/assert-release-published.js +107 -0
- package/scripts/release-preflight.js +236 -0
- package/src/orchestrator.js +464 -61
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_ATTEMPTS = 24;
|
|
6
|
+
const DEFAULT_DELAY_MS = 5000;
|
|
7
|
+
|
|
8
|
+
function run(command, args) {
|
|
9
|
+
return execFileSync(command, args, { encoding: 'utf8' }).trim();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function packageName() {
|
|
13
|
+
return require('../package.json').name;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function npmLatest(name) {
|
|
17
|
+
return JSON.parse(run('npm', ['view', name, 'dist-tags.latest', '--json']));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function tagsPointingAtHead() {
|
|
21
|
+
run('git', ['fetch', '--tags', '--force']);
|
|
22
|
+
return run('git', ['tag', '--points-at', 'HEAD', '--list', 'v[0-9]*'])
|
|
23
|
+
.split(/\r?\n/)
|
|
24
|
+
.map((tag) => tag.trim())
|
|
25
|
+
.filter(Boolean);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function releaseTagParts(tag) {
|
|
29
|
+
const match = tag.match(/^v(\d+)\.(\d+)\.(\d+)$/);
|
|
30
|
+
if (!match) return null;
|
|
31
|
+
return match.slice(1).map((part) => Number(part));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function compareReleaseTags(left, right) {
|
|
35
|
+
const leftParts = releaseTagParts(left);
|
|
36
|
+
const rightParts = releaseTagParts(right);
|
|
37
|
+
for (let index = 0; index < leftParts.length; index += 1) {
|
|
38
|
+
if (leftParts[index] !== rightParts[index]) return leftParts[index] - rightParts[index];
|
|
39
|
+
}
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function latestReleaseTag(tags) {
|
|
44
|
+
const releaseTags = tags.filter((tag) => releaseTagParts(tag));
|
|
45
|
+
releaseTags.sort(compareReleaseTags);
|
|
46
|
+
return releaseTags.at(-1) || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sleep(ms) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
setTimeout(resolve, ms);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function waitForNpmLatest(name, expectedVersion, options = {}) {
|
|
56
|
+
const attempts =
|
|
57
|
+
options.attempts || Number(process.env.RELEASE_ASSERT_ATTEMPTS || DEFAULT_ATTEMPTS);
|
|
58
|
+
const delayMs =
|
|
59
|
+
options.delayMs || Number(process.env.RELEASE_ASSERT_DELAY_MS || DEFAULT_DELAY_MS);
|
|
60
|
+
|
|
61
|
+
let latest = null;
|
|
62
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
63
|
+
latest = npmLatest(name);
|
|
64
|
+
if (latest === expectedVersion) return latest;
|
|
65
|
+
|
|
66
|
+
if (attempt < attempts) {
|
|
67
|
+
console.log(
|
|
68
|
+
`npm latest for ${name} is ${latest}; waiting for ${expectedVersion} (${attempt}/${attempts})`
|
|
69
|
+
);
|
|
70
|
+
await sleep(delayMs);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
throw new Error(`expected npm latest for ${name} to be ${expectedVersion}, got ${latest}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function main() {
|
|
78
|
+
const name = packageName();
|
|
79
|
+
const headTags = tagsPointingAtHead();
|
|
80
|
+
const expectedTag = latestReleaseTag(headTags);
|
|
81
|
+
|
|
82
|
+
if (!expectedTag) {
|
|
83
|
+
throw new Error('expected a vX.Y.Z release tag to point at HEAD after release');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(`tags on HEAD: ${headTags.join(', ') || '(none)'}`);
|
|
87
|
+
const expectedVersion = expectedTag.slice(1);
|
|
88
|
+
const latest = await waitForNpmLatest(name, expectedVersion);
|
|
89
|
+
|
|
90
|
+
console.log(`npm latest for ${name}: ${latest}`);
|
|
91
|
+
|
|
92
|
+
console.log(`Release publication verified: ${name}@${latest}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (require.main === module) {
|
|
96
|
+
main().catch((error) => {
|
|
97
|
+
console.error(`Release publication check failed: ${error.message}`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = {
|
|
103
|
+
latestReleaseTag,
|
|
104
|
+
npmLatest,
|
|
105
|
+
tagsPointingAtHead,
|
|
106
|
+
waitForNpmLatest,
|
|
107
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
const { execFileSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const RELEASE_ORDER = ['patch', 'minor', 'major'];
|
|
8
|
+
const REQUIRED_PLUGINS = [
|
|
9
|
+
'@semantic-release/commit-analyzer',
|
|
10
|
+
'@semantic-release/release-notes-generator',
|
|
11
|
+
'@semantic-release/npm',
|
|
12
|
+
'@semantic-release/github',
|
|
13
|
+
];
|
|
14
|
+
const FORBIDDEN_EFFECTIVE_PLUGINS = new Set([
|
|
15
|
+
'@semantic-release/changelog',
|
|
16
|
+
'@semantic-release/git',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
function normalizePlugin(plugin) {
|
|
20
|
+
return Array.isArray(plugin) ? plugin[0] : plugin;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function releaseRank(type) {
|
|
24
|
+
return RELEASE_ORDER.indexOf(type);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function maxReleaseType(current, candidate) {
|
|
28
|
+
if (!candidate) return current;
|
|
29
|
+
if (!current) return candidate;
|
|
30
|
+
return releaseRank(candidate) > releaseRank(current) ? candidate : current;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function analyzeMessage(message) {
|
|
34
|
+
const firstLine = String(message || '')
|
|
35
|
+
.split(/\r?\n/, 1)[0]
|
|
36
|
+
.trim();
|
|
37
|
+
if (!firstLine) return null;
|
|
38
|
+
if (/BREAKING CHANGE:|BREAKING-CHANGE:/m.test(message)) return 'major';
|
|
39
|
+
|
|
40
|
+
const separator = firstLine.indexOf(': ');
|
|
41
|
+
if (separator <= 0) return null;
|
|
42
|
+
|
|
43
|
+
const header = firstLine.slice(0, separator);
|
|
44
|
+
const breaking = header.endsWith('!');
|
|
45
|
+
const typeAndScope = breaking ? header.slice(0, -1) : header;
|
|
46
|
+
const scopeStart = typeAndScope.indexOf('(');
|
|
47
|
+
const type = scopeStart === -1 ? typeAndScope : typeAndScope.slice(0, scopeStart);
|
|
48
|
+
|
|
49
|
+
if (!/^[a-z][a-z0-9-]*$/i.test(type)) return null;
|
|
50
|
+
if (breaking) return 'major';
|
|
51
|
+
|
|
52
|
+
switch (type) {
|
|
53
|
+
case 'release':
|
|
54
|
+
case 'feat':
|
|
55
|
+
return 'minor';
|
|
56
|
+
case 'fix':
|
|
57
|
+
case 'perf':
|
|
58
|
+
return 'patch';
|
|
59
|
+
default:
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getPluginNames(releaseConfig) {
|
|
65
|
+
return (releaseConfig.plugins || []).map(normalizePlugin);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function validateReleaseConfig(packageJson) {
|
|
69
|
+
const releaseConfig = packageJson.release;
|
|
70
|
+
if (!releaseConfig || typeof releaseConfig !== 'object') {
|
|
71
|
+
throw new Error(
|
|
72
|
+
'package.json#release is required so it takes precedence over stale .releaserc files'
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const branches = Array.isArray(releaseConfig.branches)
|
|
77
|
+
? releaseConfig.branches
|
|
78
|
+
: [releaseConfig.branches].filter(Boolean);
|
|
79
|
+
if (!branches.includes('main')) {
|
|
80
|
+
throw new Error('package.json#release.branches must include main');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const pluginNames = getPluginNames(releaseConfig);
|
|
84
|
+
for (const required of REQUIRED_PLUGINS) {
|
|
85
|
+
if (!pluginNames.includes(required)) {
|
|
86
|
+
throw new Error(`package.json#release.plugins is missing ${required}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const forbidden of FORBIDDEN_EFFECTIVE_PLUGINS) {
|
|
91
|
+
if (pluginNames.includes(forbidden)) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`${forbidden} must not be in the effective release config for protected main`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const npmPlugin = releaseConfig.plugins.find(
|
|
99
|
+
(plugin) => normalizePlugin(plugin) === '@semantic-release/npm'
|
|
100
|
+
);
|
|
101
|
+
const npmOptions = Array.isArray(npmPlugin) ? npmPlugin[1] || {} : {};
|
|
102
|
+
if (npmOptions.npmPublish !== true) {
|
|
103
|
+
throw new Error('@semantic-release/npm must publish to npm');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return pluginNames;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function readJson(path) {
|
|
110
|
+
return JSON.parse(fs.readFileSync(path, 'utf8'));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function git(args) {
|
|
114
|
+
return execFileSync('git', args, { encoding: 'utf8' }).trim();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function latestReleaseTag() {
|
|
118
|
+
return git(['describe', '--tags', '--abbrev=0', '--match', 'v[0-9]*']);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function commitMessagesSince(tag) {
|
|
122
|
+
const output = git(['log', '--format=%B%x1e', `${tag}..HEAD`]);
|
|
123
|
+
return output
|
|
124
|
+
.split('\x1e')
|
|
125
|
+
.map((message) => message.trim())
|
|
126
|
+
.filter(Boolean);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function prTitleFromEvent() {
|
|
130
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
131
|
+
if (!eventPath || !fs.existsSync(eventPath)) return null;
|
|
132
|
+
const event = readJson(eventPath);
|
|
133
|
+
return event.pull_request?.title || null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function prNumberFromMergeQueueRef() {
|
|
137
|
+
const refName = process.env.GITHUB_REF_NAME || process.env.GITHUB_REF || '';
|
|
138
|
+
const match = refName.match(/(?:^|\/)pr-(\d+)-/);
|
|
139
|
+
return match ? match[1] : null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function githubJson(path) {
|
|
143
|
+
const token = process.env.GITHUB_TOKEN;
|
|
144
|
+
const repository = process.env.GITHUB_REPOSITORY;
|
|
145
|
+
if (!token || !repository) return Promise.resolve(null);
|
|
146
|
+
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
const request = https.request(
|
|
149
|
+
{
|
|
150
|
+
hostname: 'api.github.com',
|
|
151
|
+
path: `/repos/${repository}${path}`,
|
|
152
|
+
headers: {
|
|
153
|
+
Accept: 'application/vnd.github+json',
|
|
154
|
+
Authorization: `Bearer ${token}`,
|
|
155
|
+
'User-Agent': 'zeroshot-release-preflight',
|
|
156
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
(response) => {
|
|
160
|
+
let body = '';
|
|
161
|
+
response.setEncoding('utf8');
|
|
162
|
+
response.on('data', (chunk) => {
|
|
163
|
+
body += chunk;
|
|
164
|
+
});
|
|
165
|
+
response.on('end', () => {
|
|
166
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
167
|
+
reject(new Error(`GitHub API ${path} returned ${response.statusCode}: ${body}`));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
resolve(JSON.parse(body));
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
request.on('error', reject);
|
|
175
|
+
request.end();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function prTitleFromMergeQueue() {
|
|
180
|
+
const number = prNumberFromMergeQueueRef();
|
|
181
|
+
if (!number) return null;
|
|
182
|
+
const pull = await githubJson(`/pulls/${number}`);
|
|
183
|
+
return pull?.title || null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function releaseSignal() {
|
|
187
|
+
const tag = latestReleaseTag();
|
|
188
|
+
const messages = commitMessagesSince(tag);
|
|
189
|
+
let releaseType = null;
|
|
190
|
+
for (const message of messages) {
|
|
191
|
+
releaseType = maxReleaseType(releaseType, analyzeMessage(message));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let title = prTitleFromEvent();
|
|
195
|
+
if (!title) title = await prTitleFromMergeQueue();
|
|
196
|
+
releaseType = maxReleaseType(releaseType, analyzeMessage(title));
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
latestTag: tag,
|
|
200
|
+
commitCount: messages.length,
|
|
201
|
+
prTitle: title,
|
|
202
|
+
releaseType,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function main() {
|
|
207
|
+
const packageJson = readJson('package.json');
|
|
208
|
+
const pluginNames = validateReleaseConfig(packageJson);
|
|
209
|
+
const signal = await releaseSignal();
|
|
210
|
+
|
|
211
|
+
console.log(`Effective release plugins: ${pluginNames.join(', ')}`);
|
|
212
|
+
console.log(`Latest release tag: ${signal.latestTag}`);
|
|
213
|
+
console.log(`Commits since tag: ${signal.commitCount}`);
|
|
214
|
+
if (signal.prTitle) console.log(`Release PR title: ${signal.prTitle}`);
|
|
215
|
+
|
|
216
|
+
if (!signal.releaseType) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
'Release promotion would publish nothing; use a release-worthy PR title/commit'
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.log(`Release preflight passed: ${signal.releaseType}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (require.main === module) {
|
|
226
|
+
main().catch((error) => {
|
|
227
|
+
console.error(`Release preflight failed: ${error.message}`);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = {
|
|
233
|
+
analyzeMessage,
|
|
234
|
+
maxReleaseType,
|
|
235
|
+
validateReleaseConfig,
|
|
236
|
+
};
|