amalgm 0.1.124 → 0.1.125
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/package.json +1 -1
- package/runtime/scripts/amalgm-mcp/automations/internal-workflows.js +482 -0
- package/runtime/scripts/amalgm-mcp/automations/store.js +51 -0
- package/runtime/scripts/amalgm-mcp/events/desktop-release-runner.js +1379 -0
- package/runtime/scripts/amalgm-mcp/events/npm-release-runner.js +406 -0
- package/runtime/scripts/amalgm-mcp/tests/desktop-release-automation-seed.test.js +58 -0
- package/runtime/scripts/amalgm-mcp/tests/desktop-release-runner.test.js +313 -0
- package/runtime/scripts/amalgm-mcp/tests/desktop-release-server.test.js +94 -0
- package/runtime/scripts/amalgm-mcp/tests/npm-release-runner.test.js +102 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const yaml = require('js-yaml');
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
absoluteAssetUrl,
|
|
12
|
+
applyDesktopReleaseEnv,
|
|
13
|
+
bridgeChannelInfoForBase,
|
|
14
|
+
desktopLegacyBridgePlan,
|
|
15
|
+
desktopLegacyBridgeRepository,
|
|
16
|
+
desktopReleaseArtifactPaths,
|
|
17
|
+
desktopReleaseProvider,
|
|
18
|
+
desktopReleasePublishTarget,
|
|
19
|
+
desktopReleaseRepository,
|
|
20
|
+
desktopReleaseToolEnv,
|
|
21
|
+
desktopUpdateUrlForLane,
|
|
22
|
+
desktopVersion,
|
|
23
|
+
envForBuild,
|
|
24
|
+
parseEnvFile,
|
|
25
|
+
publishDesktopRelease,
|
|
26
|
+
requestFromEnv,
|
|
27
|
+
shouldHandleRequest,
|
|
28
|
+
} = require('../events/desktop-release-runner');
|
|
29
|
+
|
|
30
|
+
test('desktop release runner maps engine pushes to engine SHA plus UI branch', () => {
|
|
31
|
+
const request = requestFromEnv({
|
|
32
|
+
AMALGM_DESKTOP_RELEASE_DRY_RUN: '1',
|
|
33
|
+
AMALGM_DESKTOP_RELEASE_SOURCE_REPO: 'amalgm-inc/amalgm-engine',
|
|
34
|
+
AMALGM_DESKTOP_RELEASE_REF: 'refs/heads/main',
|
|
35
|
+
AMALGM_DESKTOP_RELEASE_AFTER: '1'.repeat(40),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const result = publishDesktopRelease(request, {
|
|
39
|
+
checkoutRoot: '/tmp/amalgm-desktop-release-test',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
assert.equal(result.dryRun, true);
|
|
43
|
+
assert.equal(result.lane, 'main');
|
|
44
|
+
assert.deepEqual(result.refs, {
|
|
45
|
+
engineRef: '1'.repeat(40),
|
|
46
|
+
uiRef: 'main',
|
|
47
|
+
});
|
|
48
|
+
assert.equal(result.releaseProvider, 'fly');
|
|
49
|
+
assert.equal(result.updateProvider, 'generic');
|
|
50
|
+
assert.equal(result.updateUrl, 'https://amalgm-desktop-releases.fly.dev/main/');
|
|
51
|
+
assert.equal(result.releaseUrl, 'https://amalgm-desktop-releases.fly.dev/main/latest-mac.yml');
|
|
52
|
+
assert.equal(result.flyApp, 'amalgm-desktop-releases');
|
|
53
|
+
assert.equal(result.flyRemoteDir, '/data/main');
|
|
54
|
+
assert.equal(result.releaseRepository, 'amalgm-inc/amalgm-desktop-releases');
|
|
55
|
+
assert.deepEqual(result.legacyBridge, {
|
|
56
|
+
enabled: true,
|
|
57
|
+
repository: 'amalgm-inc/amalgm-engine',
|
|
58
|
+
channelFile: 'latest-mac.yml',
|
|
59
|
+
updateUrl: 'https://amalgm-desktop-releases.fly.dev/main/',
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('desktop release runner maps UI pushes to UI SHA plus engine branch', () => {
|
|
64
|
+
const request = requestFromEnv({
|
|
65
|
+
AMALGM_DESKTOP_RELEASE_DRY_RUN: 'true',
|
|
66
|
+
AMALGM_DESKTOP_RELEASE_SOURCE_REPO: 'amalgm-inc/amalgm-ui',
|
|
67
|
+
AMALGM_DESKTOP_RELEASE_REF: 'refs/heads/preview',
|
|
68
|
+
AMALGM_DESKTOP_RELEASE_AFTER: '2'.repeat(40),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const result = publishDesktopRelease(request, {
|
|
72
|
+
checkoutRoot: '/tmp/amalgm-desktop-release-test',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
assert.equal(result.dryRun, true);
|
|
76
|
+
assert.equal(result.lane, 'preview');
|
|
77
|
+
assert.deepEqual(result.refs, {
|
|
78
|
+
engineRef: 'preview',
|
|
79
|
+
uiRef: '2'.repeat(40),
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('desktop release runner skips irrelevant pushes and branch deletions', () => {
|
|
84
|
+
assert.equal(shouldHandleRequest({
|
|
85
|
+
repository: 'amalgm-inc/amalgm-engine',
|
|
86
|
+
branch: 'feature',
|
|
87
|
+
lane: null,
|
|
88
|
+
after: '3'.repeat(40),
|
|
89
|
+
}).ok, false);
|
|
90
|
+
|
|
91
|
+
assert.equal(shouldHandleRequest({
|
|
92
|
+
repository: 'amalgm-inc/amalgm-ui',
|
|
93
|
+
branch: 'main',
|
|
94
|
+
lane: 'main',
|
|
95
|
+
after: '0'.repeat(40),
|
|
96
|
+
}).ok, false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('desktop versions stay compatible with existing release ordering', () => {
|
|
100
|
+
assert.equal(desktopVersion('0.1.124', 'main', 45), '0.1.124000045');
|
|
101
|
+
assert.equal(desktopVersion('0.1.124-preview.1', 'preview', 45), '0.1.124000045-preview.1');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('desktop release runner loads env-file fallbacks without overriding explicit env', () => {
|
|
105
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-desktop-release-env-test-'));
|
|
106
|
+
const envFile = path.join(tempRoot, 'desktop-release.env');
|
|
107
|
+
fs.writeFileSync(envFile, [
|
|
108
|
+
'NEXT_PUBLIC_SUPABASE_URL=https://example.supabase.co',
|
|
109
|
+
'NEXT_PUBLIC_SUPABASE_ANON_KEY="anon-key"',
|
|
110
|
+
'NEXT_PUBLIC_SITE_URL=https://from-file.example',
|
|
111
|
+
'',
|
|
112
|
+
].join('\n'));
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const env = {
|
|
116
|
+
AMALGM_DESKTOP_RELEASE_ENV_FILE: envFile,
|
|
117
|
+
NEXT_PUBLIC_SITE_URL: 'https://explicit.example',
|
|
118
|
+
};
|
|
119
|
+
applyDesktopReleaseEnv(env);
|
|
120
|
+
assert.deepEqual(parseEnvFile('FOO=bar\nQUOTED="hi there"'), {
|
|
121
|
+
FOO: 'bar',
|
|
122
|
+
QUOTED: 'hi there',
|
|
123
|
+
});
|
|
124
|
+
assert.equal(env.NEXT_PUBLIC_SUPABASE_URL, 'https://example.supabase.co');
|
|
125
|
+
assert.equal(env.NEXT_PUBLIC_SUPABASE_ANON_KEY, 'anon-key');
|
|
126
|
+
assert.equal(env.NEXT_PUBLIC_SITE_URL, 'https://explicit.example');
|
|
127
|
+
} finally {
|
|
128
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('desktop release runner pins child build toolchain from env file', () => {
|
|
133
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-desktop-release-toolchain-test-'));
|
|
134
|
+
const envFile = path.join(tempRoot, 'desktop-release.env');
|
|
135
|
+
const nodeBin = path.join(tempRoot, 'node-bin', 'node');
|
|
136
|
+
fs.mkdirSync(path.dirname(nodeBin), { recursive: true });
|
|
137
|
+
fs.writeFileSync(nodeBin, '');
|
|
138
|
+
fs.writeFileSync(envFile, [
|
|
139
|
+
`AMALGM_DESKTOP_RELEASE_NODE_BIN=${nodeBin}`,
|
|
140
|
+
'AMALGM_DESKTOP_RELEASE_PYTHON=/usr/bin/python3',
|
|
141
|
+
'',
|
|
142
|
+
].join('\n'));
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const env = desktopReleaseToolEnv({
|
|
146
|
+
AMALGM_DESKTOP_RELEASE_ENV_FILE: envFile,
|
|
147
|
+
PATH: '/usr/local/bin:/usr/bin',
|
|
148
|
+
});
|
|
149
|
+
assert.equal(env.PATH.split(path.delimiter)[0], path.dirname(nodeBin));
|
|
150
|
+
assert.equal(env.npm_config_python, '/usr/bin/python3');
|
|
151
|
+
} finally {
|
|
152
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('desktop release runner uses electron-builder compatible signing identity name', () => {
|
|
157
|
+
const env = envForBuild(
|
|
158
|
+
{ GH_TOKEN: 'token' },
|
|
159
|
+
{ lane: 'preview', delivery: 'delivery-id' },
|
|
160
|
+
'0.1.124-canary.1',
|
|
161
|
+
'0.1.124000001-preview.1',
|
|
162
|
+
{ engineRef: 'engine', uiRef: 'ui' },
|
|
163
|
+
{ engineSha: 'engine-sha', uiSha: 'ui-sha' },
|
|
164
|
+
'https://preview.amalgm.ai',
|
|
165
|
+
);
|
|
166
|
+
assert.equal(env.CSC_NAME, 'amalgm, Inc. (C5S6UATV3L)');
|
|
167
|
+
assert.equal(env.CSC_NAME.includes('Developer ID Application:'), false);
|
|
168
|
+
assert.equal(env.AMALGM_DESKTOP_RELEASE_PROVIDER, 'fly');
|
|
169
|
+
assert.equal(env.AMALGM_DESKTOP_UPDATE_PROVIDER, 'generic');
|
|
170
|
+
assert.equal(env.AMALGM_DESKTOP_UPDATE_BASE_URL, 'https://amalgm-desktop-releases.fly.dev/');
|
|
171
|
+
assert.equal(env.AMALGM_DESKTOP_UPDATE_URL, 'https://amalgm-desktop-releases.fly.dev/preview/');
|
|
172
|
+
assert.equal(env.AMALGM_DESKTOP_RELEASE_UPLOAD_METHOD, 'http');
|
|
173
|
+
assert.equal(env.AMALGM_DESKTOP_RELEASE_UPLOAD_URL, 'https://amalgm-desktop-releases.fly.dev/');
|
|
174
|
+
assert.equal(env.AMALGM_DESKTOP_RELEASE_FLY_APP, 'amalgm-desktop-releases');
|
|
175
|
+
assert.equal(env.AMALGM_DESKTOP_RELEASE_FLY_ROOT, '/data');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('desktop release runner omits blank certificate file secrets', () => {
|
|
179
|
+
const env = envForBuild(
|
|
180
|
+
{
|
|
181
|
+
GH_TOKEN: 'token',
|
|
182
|
+
CSC_LINK: '',
|
|
183
|
+
CSC_KEY_PASSWORD: ' ',
|
|
184
|
+
},
|
|
185
|
+
{ lane: 'main', delivery: 'delivery-id' },
|
|
186
|
+
'0.1.124',
|
|
187
|
+
'0.1.124000001',
|
|
188
|
+
{ engineRef: 'engine', uiRef: 'ui' },
|
|
189
|
+
{ engineSha: 'engine-sha', uiSha: 'ui-sha' },
|
|
190
|
+
'https://amalgm.ai',
|
|
191
|
+
);
|
|
192
|
+
assert.equal(Object.hasOwn(env, 'CSC_LINK'), false);
|
|
193
|
+
assert.equal(Object.hasOwn(env, 'CSC_KEY_PASSWORD'), false);
|
|
194
|
+
assert.equal(env.CSC_NAME, 'amalgm, Inc. (C5S6UATV3L)');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('desktop release repository can be overridden as owner/repo or full name', () => {
|
|
198
|
+
assert.deepEqual(desktopReleaseRepository({
|
|
199
|
+
AMALGM_DESKTOP_UPDATE_OWNER: 'example',
|
|
200
|
+
AMALGM_DESKTOP_UPDATE_REPO: 'desktop-feed',
|
|
201
|
+
}), {
|
|
202
|
+
owner: 'example',
|
|
203
|
+
repo: 'desktop-feed',
|
|
204
|
+
fullName: 'example/desktop-feed',
|
|
205
|
+
});
|
|
206
|
+
assert.deepEqual(desktopReleaseRepository({
|
|
207
|
+
AMALGM_DESKTOP_RELEASE_REPOSITORY: 'other/feed',
|
|
208
|
+
}), {
|
|
209
|
+
owner: 'other',
|
|
210
|
+
repo: 'feed',
|
|
211
|
+
fullName: 'other/feed',
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('desktop release runner defaults to Fly generic feed and keeps GitHub as explicit fallback', () => {
|
|
216
|
+
assert.equal(desktopReleaseProvider({}), 'fly');
|
|
217
|
+
assert.equal(desktopUpdateUrlForLane('main', {}), 'https://amalgm-desktop-releases.fly.dev/main/');
|
|
218
|
+
assert.deepEqual(desktopReleasePublishTarget({}, 'preview'), {
|
|
219
|
+
provider: 'fly',
|
|
220
|
+
updateProvider: 'generic',
|
|
221
|
+
updateUrl: 'https://amalgm-desktop-releases.fly.dev/preview/',
|
|
222
|
+
channelFile: 'preview-mac.yml',
|
|
223
|
+
channelUrl: 'https://amalgm-desktop-releases.fly.dev/preview/preview-mac.yml',
|
|
224
|
+
uploadMethod: 'http',
|
|
225
|
+
uploadBaseUrl: 'https://amalgm-desktop-releases.fly.dev/',
|
|
226
|
+
flyApp: 'amalgm-desktop-releases',
|
|
227
|
+
flyRoot: '/data',
|
|
228
|
+
remoteDir: '/data/preview',
|
|
229
|
+
lane: 'preview',
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const githubTarget = desktopReleasePublishTarget({
|
|
233
|
+
AMALGM_DESKTOP_RELEASE_PROVIDER: 'github',
|
|
234
|
+
AMALGM_DESKTOP_RELEASE_REPOSITORY: 'example/desktop-feed',
|
|
235
|
+
}, 'main');
|
|
236
|
+
assert.equal(githubTarget.provider, 'github');
|
|
237
|
+
assert.equal(githubTarget.updateProvider, 'github');
|
|
238
|
+
assert.equal(githubTarget.releaseRepository.fullName, 'example/desktop-feed');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('desktop release runner builds legacy GitHub bridge metadata for old clients', () => {
|
|
242
|
+
assert.equal(
|
|
243
|
+
absoluteAssetUrl('https://amalgm-desktop-releases.fly.dev/main/', 'amalgm-0.1.1-arm64-mac.zip'),
|
|
244
|
+
'https://amalgm-desktop-releases.fly.dev/main/amalgm-0.1.1-arm64-mac.zip',
|
|
245
|
+
);
|
|
246
|
+
assert.equal(
|
|
247
|
+
absoluteAssetUrl('https://amalgm-desktop-releases.fly.dev/main/', 'https://cdn.example/release.zip'),
|
|
248
|
+
'https://cdn.example/release.zip',
|
|
249
|
+
);
|
|
250
|
+
assert.deepEqual(desktopLegacyBridgeRepository({}), {
|
|
251
|
+
owner: 'amalgm-inc',
|
|
252
|
+
repo: 'amalgm-engine',
|
|
253
|
+
fullName: 'amalgm-inc/amalgm-engine',
|
|
254
|
+
});
|
|
255
|
+
assert.deepEqual(desktopLegacyBridgePlan({}, desktopReleasePublishTarget({}, 'preview')), {
|
|
256
|
+
enabled: true,
|
|
257
|
+
repository: 'amalgm-inc/amalgm-engine',
|
|
258
|
+
channelFile: 'preview-mac.yml',
|
|
259
|
+
updateUrl: 'https://amalgm-desktop-releases.fly.dev/preview/',
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('desktop release runner rewrites bridge channel YAML to absolute Fly asset URLs', () => {
|
|
264
|
+
const bridged = bridgeChannelInfoForBase([
|
|
265
|
+
'version: 0.1.124000001',
|
|
266
|
+
'files:',
|
|
267
|
+
' - url: amalgm-0.1.124000001-arm64-mac.zip',
|
|
268
|
+
' sha512: zip-sha',
|
|
269
|
+
' size: 123',
|
|
270
|
+
' - url: amalgm-0.1.124000001-arm64.dmg',
|
|
271
|
+
' sha512: dmg-sha',
|
|
272
|
+
' size: 456',
|
|
273
|
+
'path: amalgm-0.1.124000001-arm64-mac.zip',
|
|
274
|
+
'sha512: zip-sha',
|
|
275
|
+
'packages:',
|
|
276
|
+
' arm64:',
|
|
277
|
+
' path: amalgm-0.1.124000001-arm64.pkg',
|
|
278
|
+
'',
|
|
279
|
+
].join('\n'), 'https://amalgm-desktop-releases.fly.dev/main/');
|
|
280
|
+
const info = yaml.load(bridged);
|
|
281
|
+
|
|
282
|
+
assert.equal(info.files[0].url, 'https://amalgm-desktop-releases.fly.dev/main/amalgm-0.1.124000001-arm64-mac.zip');
|
|
283
|
+
assert.equal(info.files[1].url, 'https://amalgm-desktop-releases.fly.dev/main/amalgm-0.1.124000001-arm64.dmg');
|
|
284
|
+
assert.equal(info.path, 'https://amalgm-desktop-releases.fly.dev/main/amalgm-0.1.124000001-arm64-mac.zip');
|
|
285
|
+
assert.equal(info.packages.arm64.path, 'https://amalgm-desktop-releases.fly.dev/main/amalgm-0.1.124000001-arm64.pkg');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('desktop release runner requires all updater artifacts', () => {
|
|
289
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-desktop-release-artifacts-test-'));
|
|
290
|
+
const version = '0.1.124000001-preview.1';
|
|
291
|
+
const files = [
|
|
292
|
+
`amalgm-preview-${version}-arm64.dmg`,
|
|
293
|
+
`amalgm-preview-${version}-arm64.dmg.blockmap`,
|
|
294
|
+
`amalgm-preview-${version}-arm64.zip`,
|
|
295
|
+
`amalgm-preview-${version}-arm64.zip.blockmap`,
|
|
296
|
+
'preview-mac.yml',
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
for (const file of files) fs.writeFileSync(path.join(tempRoot, file), '');
|
|
301
|
+
const artifacts = desktopReleaseArtifactPaths(tempRoot, 'preview', version)
|
|
302
|
+
.map((filePath) => path.basename(filePath));
|
|
303
|
+
assert.deepEqual(artifacts, files);
|
|
304
|
+
|
|
305
|
+
fs.rmSync(path.join(tempRoot, 'preview-mac.yml'));
|
|
306
|
+
assert.throws(
|
|
307
|
+
() => desktopReleaseArtifactPaths(tempRoot, 'preview', version),
|
|
308
|
+
/Missing desktop release artifact\(s\): preview-mac\.yml/,
|
|
309
|
+
);
|
|
310
|
+
} finally {
|
|
311
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
312
|
+
}
|
|
313
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-desktop-release-server-test-'));
|
|
10
|
+
process.env.DATA_ROOT = tempRoot;
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
createServer,
|
|
14
|
+
latestArtifactName,
|
|
15
|
+
latestDownloadTarget,
|
|
16
|
+
parseDownloadPath,
|
|
17
|
+
} = require(path.resolve(__dirname, '../../../../infra/desktop-release-server/server.js'));
|
|
18
|
+
|
|
19
|
+
test.after(() => {
|
|
20
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function writeReleaseMetadata(lane, artifacts) {
|
|
24
|
+
const laneDir = path.join(tempRoot, lane);
|
|
25
|
+
fs.mkdirSync(laneDir, { recursive: true });
|
|
26
|
+
fs.writeFileSync(path.join(laneDir, 'desktop-release.json'), `${JSON.stringify({ lane, artifacts }, null, 2)}\n`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test('desktop release server resolves stable bridge download targets from metadata', () => {
|
|
30
|
+
const artifacts = [
|
|
31
|
+
'amalgm-0.1.1-arm64-mac.zip',
|
|
32
|
+
'amalgm-0.1.1-arm64-mac.zip.blockmap',
|
|
33
|
+
'amalgm-0.1.1-arm64.dmg',
|
|
34
|
+
'amalgm-0.1.1-arm64.dmg.blockmap',
|
|
35
|
+
'latest-mac.yml',
|
|
36
|
+
];
|
|
37
|
+
writeReleaseMetadata('main', artifacts);
|
|
38
|
+
|
|
39
|
+
assert.deepEqual(parseDownloadPath('/main/download'), { lane: 'main', kind: 'dmg' });
|
|
40
|
+
assert.deepEqual(parseDownloadPath('/main/download/zip'), { lane: 'main', kind: 'zip' });
|
|
41
|
+
assert.deepEqual(parseDownloadPath('/main/latest.dmg'), { lane: 'main', kind: 'dmg' });
|
|
42
|
+
assert.equal(parseDownloadPath('/main/latest-mac.yml'), null);
|
|
43
|
+
assert.equal(latestArtifactName({ artifacts }, 'dmg'), 'amalgm-0.1.1-arm64.dmg');
|
|
44
|
+
assert.deepEqual(latestDownloadTarget('main', 'zip'), {
|
|
45
|
+
metadata: { lane: 'main', artifacts },
|
|
46
|
+
artifactName: 'amalgm-0.1.1-arm64-mac.zip',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('desktop release server redirects public bridge download URLs to current assets', async () => {
|
|
51
|
+
writeReleaseMetadata('preview', [
|
|
52
|
+
'amalgm-preview-0.1.1-preview.1-arm64.zip',
|
|
53
|
+
'amalgm-preview-0.1.1-preview.1-arm64.dmg',
|
|
54
|
+
'preview-mac.yml',
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const server = createServer();
|
|
58
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
59
|
+
const { port } = server.address();
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetch(`http://127.0.0.1:${port}/preview/download`, { redirect: 'manual' });
|
|
63
|
+
assert.equal(response.status, 302);
|
|
64
|
+
assert.equal(
|
|
65
|
+
response.headers.get('location'),
|
|
66
|
+
`http://127.0.0.1:${port}/preview/amalgm-preview-0.1.1-preview.1-arm64.dmg`,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const zipResponse = await fetch(`http://127.0.0.1:${port}/preview/latest.zip`, {
|
|
70
|
+
method: 'HEAD',
|
|
71
|
+
redirect: 'manual',
|
|
72
|
+
});
|
|
73
|
+
assert.equal(zipResponse.status, 302);
|
|
74
|
+
assert.equal(
|
|
75
|
+
zipResponse.headers.get('location'),
|
|
76
|
+
`http://127.0.0.1:${port}/preview/amalgm-preview-0.1.1-preview.1-arm64.zip`,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const forwardedResponse = await fetch(`http://127.0.0.1:${port}/preview/download`, {
|
|
80
|
+
headers: {
|
|
81
|
+
'x-forwarded-proto': 'https',
|
|
82
|
+
'x-forwarded-host': 'amalgm-desktop-releases.fly.dev',
|
|
83
|
+
},
|
|
84
|
+
redirect: 'manual',
|
|
85
|
+
});
|
|
86
|
+
assert.equal(forwardedResponse.status, 302);
|
|
87
|
+
assert.equal(
|
|
88
|
+
forwardedResponse.headers.get('location'),
|
|
89
|
+
'https://amalgm-desktop-releases.fly.dev/preview/amalgm-preview-0.1.1-preview.1-arm64.dmg',
|
|
90
|
+
);
|
|
91
|
+
} finally {
|
|
92
|
+
await new Promise((resolve) => server.close(resolve));
|
|
93
|
+
}
|
|
94
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
baseVersion,
|
|
8
|
+
npmReleasePlan,
|
|
9
|
+
publishNpmRelease,
|
|
10
|
+
requestFromEnv,
|
|
11
|
+
shouldHandleRequest,
|
|
12
|
+
timestampForVersion,
|
|
13
|
+
} = require('../events/npm-release-runner');
|
|
14
|
+
|
|
15
|
+
test('npm release runner maps main pushes to stable latest and main tags', () => {
|
|
16
|
+
const request = requestFromEnv({
|
|
17
|
+
AMALGM_NPM_RELEASE_DRY_RUN: '1',
|
|
18
|
+
AMALGM_NPM_RELEASE_SOURCE_REPO: 'amalgm-inc/amalgm-engine',
|
|
19
|
+
AMALGM_NPM_RELEASE_REF: 'refs/heads/main',
|
|
20
|
+
AMALGM_NPM_RELEASE_AFTER: '1'.repeat(40),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const result = publishNpmRelease(request, {
|
|
24
|
+
sourcePackageVersion: '0.1.124',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
assert.equal(result.dryRun, true);
|
|
28
|
+
assert.equal(result.lane, 'main');
|
|
29
|
+
assert.deepEqual(result.plan, {
|
|
30
|
+
lane: 'main',
|
|
31
|
+
version: '0.1.124',
|
|
32
|
+
publishTag: 'latest',
|
|
33
|
+
distTags: ['latest', 'main'],
|
|
34
|
+
sourcePackageVersion: '0.1.124',
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('npm release runner maps preview pushes to unique canary versions', () => {
|
|
39
|
+
const request = requestFromEnv({
|
|
40
|
+
AMALGM_NPM_RELEASE_DRY_RUN: 'true',
|
|
41
|
+
AMALGM_NPM_RELEASE_SOURCE_REPO: 'amalgm-inc/amalgm-engine',
|
|
42
|
+
AMALGM_NPM_RELEASE_REF: 'refs/heads/preview',
|
|
43
|
+
AMALGM_NPM_RELEASE_AFTER: '2'.repeat(40),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const result = publishNpmRelease(request, {
|
|
47
|
+
sourcePackageVersion: '0.1.124-canary.20260620215843',
|
|
48
|
+
timestamp: '20260621224500',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
assert.equal(result.dryRun, true);
|
|
52
|
+
assert.equal(result.lane, 'preview');
|
|
53
|
+
assert.deepEqual(result.plan, {
|
|
54
|
+
lane: 'preview',
|
|
55
|
+
version: '0.1.124-canary.20260621224500',
|
|
56
|
+
publishTag: 'canary',
|
|
57
|
+
distTags: ['canary'],
|
|
58
|
+
sourcePackageVersion: '0.1.124-canary.20260620215843',
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('npm release runner rejects unsupported pushes and skip markers', () => {
|
|
63
|
+
assert.equal(shouldHandleRequest({
|
|
64
|
+
repository: 'amalgm-inc/amalgm-ui',
|
|
65
|
+
branch: 'main',
|
|
66
|
+
lane: 'main',
|
|
67
|
+
after: '3'.repeat(40),
|
|
68
|
+
messages: [],
|
|
69
|
+
}).ok, false);
|
|
70
|
+
|
|
71
|
+
assert.equal(shouldHandleRequest({
|
|
72
|
+
repository: 'amalgm-inc/amalgm-engine',
|
|
73
|
+
branch: 'feature',
|
|
74
|
+
lane: null,
|
|
75
|
+
after: '3'.repeat(40),
|
|
76
|
+
messages: [],
|
|
77
|
+
}).ok, false);
|
|
78
|
+
|
|
79
|
+
assert.equal(shouldHandleRequest({
|
|
80
|
+
repository: 'amalgm-inc/amalgm-engine',
|
|
81
|
+
branch: 'preview',
|
|
82
|
+
lane: 'preview',
|
|
83
|
+
after: '3'.repeat(40),
|
|
84
|
+
messages: ['Release automation plumbing [skip npm]'],
|
|
85
|
+
}).ok, false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('npm release plans enforce stable main and derive canary base versions', () => {
|
|
89
|
+
assert.throws(
|
|
90
|
+
() => npmReleasePlan('0.1.124-canary.1', 'main'),
|
|
91
|
+
/Main npm releases must use a stable package version/,
|
|
92
|
+
);
|
|
93
|
+
assert.equal(baseVersion('0.1.124-canary.1'), '0.1.124');
|
|
94
|
+
assert.equal(
|
|
95
|
+
npmReleasePlan('0.1.124', 'preview', { timestamp: '20260621230000' }).version,
|
|
96
|
+
'0.1.124-canary.20260621230000',
|
|
97
|
+
);
|
|
98
|
+
assert.equal(
|
|
99
|
+
timestampForVersion(new Date(Date.UTC(2026, 5, 21, 22, 45, 7))),
|
|
100
|
+
'20260621224507',
|
|
101
|
+
);
|
|
102
|
+
});
|