bleam 0.0.9 → 0.0.10

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.
@@ -0,0 +1,385 @@
1
+ export type ArtifactFormat = 'bleam-ota-zip-v1' | 'bleam-macos-app-zip-v1'
2
+
3
+ export interface StoredArtifact {
4
+ format: ArtifactFormat
5
+ downloadId: string
6
+ sizeBytes: number
7
+ sha256: string
8
+ }
9
+
10
+ export interface ResolvedArtifact extends StoredArtifact {
11
+ url: string
12
+ }
13
+
14
+ interface ReleaseFields {
15
+ schemaVersion: 1
16
+ bundleIdentifier: string
17
+ teamIdentifier: string
18
+ channel: 'stable'
19
+ releaseId: string
20
+ publishedAt: string
21
+ }
22
+
23
+ export interface OtaRelease extends ReleaseFields {
24
+ kind: 'ota'
25
+ bundleVersion: string
26
+ runtimeVersion: string
27
+ artifact: StoredArtifact & { format: 'bleam-ota-zip-v1' }
28
+ }
29
+
30
+ export interface PlatformRelease extends ReleaseFields {
31
+ kind: 'platform'
32
+ appVersion: string
33
+ buildNumber: string
34
+ bleamPlatformVersion: string
35
+ platformFingerprint?: string
36
+ redeploy: boolean
37
+ target: {
38
+ os: 'macos'
39
+ architecture: 'arm64'
40
+ installLocation: 'user-applications'
41
+ minimumMacOSVersion: string
42
+ }
43
+ artifact: StoredArtifact & { format: 'bleam-macos-app-zip-v1' }
44
+ }
45
+
46
+ export type StoredRelease = OtaRelease | PlatformRelease
47
+
48
+ export type ResolvedRelease =
49
+ | (Omit<OtaRelease, 'artifact'> & { artifact: ResolvedArtifact })
50
+ | (Omit<PlatformRelease, 'artifact'> & { artifact: ResolvedArtifact })
51
+
52
+ export interface DownloadRecord {
53
+ schemaVersion: 1
54
+ downloadId: string
55
+ artifactKey: string
56
+ contentType: string
57
+ }
58
+
59
+ export class ManifestValidationError extends Error {
60
+ constructor(message: string) {
61
+ super(message)
62
+ this.name = 'ManifestValidationError'
63
+ }
64
+ }
65
+
66
+ function invalid(field: string, detail: string): never {
67
+ throw new ManifestValidationError(`${field} ${detail}`)
68
+ }
69
+
70
+ function record(value: unknown, field: string): Record<string, unknown> {
71
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
72
+ return invalid(field, 'must be an object')
73
+ }
74
+ return value as Record<string, unknown>
75
+ }
76
+
77
+ function string(value: unknown, field: string): string {
78
+ if (typeof value !== 'string' || !value.trim()) {
79
+ return invalid(field, 'must be a non-empty string')
80
+ }
81
+ return value
82
+ }
83
+
84
+ function exactString(value: unknown, field: string, expected: string): string {
85
+ const result = string(value, field)
86
+ if (result !== expected) {
87
+ return invalid(field, `must be ${JSON.stringify(expected)}`)
88
+ }
89
+ return result
90
+ }
91
+
92
+ function positiveInteger(value: unknown, field: string): number {
93
+ if (!Number.isSafeInteger(value) || (value as number) <= 0) {
94
+ return invalid(field, 'must be a positive integer')
95
+ }
96
+ return value as number
97
+ }
98
+
99
+ function version(value: unknown, field: string): string {
100
+ const result = string(value, field)
101
+ if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(result)) {
102
+ return invalid(field, 'must be a semantic version')
103
+ }
104
+ return result
105
+ }
106
+
107
+ function numericString(value: unknown, field: string): string {
108
+ const result = string(value, field)
109
+ if (!/^\d+$/.test(result)) {
110
+ return invalid(field, 'must contain only decimal digits')
111
+ }
112
+ return result
113
+ }
114
+
115
+ function optionalSha256(value: unknown, field: string): string | undefined {
116
+ if (value === undefined) return undefined
117
+ const result = string(value, field)
118
+ if (!/^[a-f0-9]{64}$/.test(result)) {
119
+ return invalid(field, 'must be a lowercase SHA-256 hex digest')
120
+ }
121
+ return result
122
+ }
123
+
124
+ function publishedAt(value: unknown): string {
125
+ const result = string(value, 'publishedAt')
126
+ if (
127
+ !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/.test(result) ||
128
+ Number.isNaN(Date.parse(result))
129
+ ) {
130
+ return invalid('publishedAt', 'must be an RFC 3339 UTC timestamp')
131
+ }
132
+ return result
133
+ }
134
+
135
+ export function isBundleIdentifier(value: string): boolean {
136
+ return /^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*$/.test(value)
137
+ }
138
+
139
+ export function isRuntimeVersion(value: string): boolean {
140
+ return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(value)
141
+ }
142
+
143
+ export function compareRuntimeVersions(left: string, right: string) {
144
+ const parse = (value: string) => {
145
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([^+]+))?(?:\+.*)?$/.exec(value)
146
+ if (!match) return invalid('runtimeVersion', 'must be a semantic version')
147
+ return {
148
+ core: match.slice(1, 4).map(Number),
149
+ prerelease: match[4]?.split('.'),
150
+ }
151
+ }
152
+ const a = parse(left)
153
+ const b = parse(right)
154
+ for (let index = 0; index < a.core.length; index += 1) {
155
+ if (a.core[index] !== b.core[index]) {
156
+ return (a.core[index] ?? 0) < (b.core[index] ?? 0) ? -1 : 1
157
+ }
158
+ }
159
+ if (!a.prerelease && !b.prerelease) return 0
160
+ if (!a.prerelease) return 1
161
+ if (!b.prerelease) return -1
162
+ const length = Math.max(a.prerelease.length, b.prerelease.length)
163
+ for (let index = 0; index < length; index += 1) {
164
+ const aPart = a.prerelease[index]
165
+ const bPart = b.prerelease[index]
166
+ if (aPart === undefined) return -1
167
+ if (bPart === undefined) return 1
168
+ if (aPart === bPart) continue
169
+ const aNumber = /^\d+$/.test(aPart) ? Number(aPart) : undefined
170
+ const bNumber = /^\d+$/.test(bPart) ? Number(bPart) : undefined
171
+ if (aNumber !== undefined && bNumber !== undefined) {
172
+ return aNumber < bNumber ? -1 : 1
173
+ }
174
+ if (aNumber !== undefined) return -1
175
+ if (bNumber !== undefined) return 1
176
+ return aPart < bPart ? -1 : 1
177
+ }
178
+ return 0
179
+ }
180
+
181
+ export function isDownloadId(value: string): boolean {
182
+ return /^[a-z][a-z0-9_-]{2,127}$/.test(value)
183
+ }
184
+
185
+ function releaseId(value: unknown): string {
186
+ const result = string(value, 'releaseId')
187
+ if (!isDownloadId(result)) {
188
+ return invalid(
189
+ 'releaseId',
190
+ 'must use lowercase letters, numbers, underscores, or hyphens',
191
+ )
192
+ }
193
+ return result
194
+ }
195
+
196
+ function bundleIdentifier(value: unknown): string {
197
+ const result = string(value, 'bundleIdentifier')
198
+ if (!isBundleIdentifier(result)) {
199
+ return invalid('bundleIdentifier', 'must be a bundle identifier')
200
+ }
201
+ return result
202
+ }
203
+
204
+ function teamIdentifier(value: unknown): string {
205
+ const result = string(value, 'teamIdentifier')
206
+ if (!/^[A-Z0-9]{10}$/.test(result)) {
207
+ return invalid('teamIdentifier', 'must be a 10-character Apple team ID')
208
+ }
209
+ return result
210
+ }
211
+
212
+ function artifact(value: unknown, kind: StoredRelease['kind']): StoredArtifact {
213
+ const input = record(value, 'artifact')
214
+ const expectedFormat =
215
+ kind === 'ota' ? 'bleam-ota-zip-v1' : 'bleam-macos-app-zip-v1'
216
+ exactString(input.format, 'artifact.format', expectedFormat)
217
+
218
+ const downloadId = string(input.downloadId, 'artifact.downloadId')
219
+ if (!isDownloadId(downloadId)) {
220
+ return invalid(
221
+ 'artifact.downloadId',
222
+ 'must use lowercase letters, numbers, underscores, or hyphens',
223
+ )
224
+ }
225
+
226
+ const sha256 = string(input.sha256, 'artifact.sha256')
227
+ if (!/^[a-f0-9]{64}$/.test(sha256)) {
228
+ return invalid('artifact.sha256', 'must be a lowercase SHA-256 hex digest')
229
+ }
230
+
231
+ return {
232
+ format: expectedFormat,
233
+ downloadId,
234
+ sizeBytes: positiveInteger(input.sizeBytes, 'artifact.sizeBytes'),
235
+ sha256,
236
+ }
237
+ }
238
+
239
+ function releaseFields(input: Record<string, unknown>): ReleaseFields {
240
+ if (input.schemaVersion !== 1) {
241
+ return invalid('schemaVersion', 'must be 1')
242
+ }
243
+
244
+ exactString(input.channel, 'channel', 'stable')
245
+ return {
246
+ schemaVersion: 1,
247
+ bundleIdentifier: bundleIdentifier(input.bundleIdentifier),
248
+ teamIdentifier: teamIdentifier(input.teamIdentifier),
249
+ channel: 'stable',
250
+ releaseId: releaseId(input.releaseId),
251
+ publishedAt: publishedAt(input.publishedAt),
252
+ }
253
+ }
254
+
255
+ export function parseStoredRelease(value: unknown): StoredRelease {
256
+ const input = record(value, 'manifest')
257
+ const kind = string(input.kind, 'kind')
258
+ const fields = releaseFields(input)
259
+
260
+ if (kind === 'ota') {
261
+ const parsedArtifact = artifact(input.artifact, kind)
262
+ return {
263
+ ...fields,
264
+ kind,
265
+ bundleVersion: string(input.bundleVersion, 'bundleVersion'),
266
+ runtimeVersion: version(input.runtimeVersion, 'runtimeVersion'),
267
+ artifact: {
268
+ ...parsedArtifact,
269
+ format: 'bleam-ota-zip-v1',
270
+ },
271
+ }
272
+ }
273
+
274
+ if (kind === 'platform') {
275
+ const target = record(input.target, 'target')
276
+ exactString(target.os, 'target.os', 'macos')
277
+ exactString(target.architecture, 'target.architecture', 'arm64')
278
+ exactString(
279
+ target.installLocation,
280
+ 'target.installLocation',
281
+ 'user-applications',
282
+ )
283
+ const minimumMacOSVersion = string(
284
+ target.minimumMacOSVersion,
285
+ 'target.minimumMacOSVersion',
286
+ )
287
+ if (!/^\d+(?:\.\d+){0,2}$/.test(minimumMacOSVersion)) {
288
+ return invalid(
289
+ 'target.minimumMacOSVersion',
290
+ 'must be a macOS version number',
291
+ )
292
+ }
293
+
294
+ const parsedArtifact = artifact(input.artifact, kind)
295
+ return {
296
+ ...fields,
297
+ kind,
298
+ appVersion: version(input.appVersion, 'appVersion'),
299
+ buildNumber: numericString(input.buildNumber, 'buildNumber'),
300
+ bleamPlatformVersion: version(
301
+ input.bleamPlatformVersion,
302
+ 'bleamPlatformVersion',
303
+ ),
304
+ platformFingerprint: optionalSha256(
305
+ input.platformFingerprint,
306
+ 'platformFingerprint',
307
+ ),
308
+ redeploy: input.redeploy === true,
309
+ target: {
310
+ os: 'macos',
311
+ architecture: 'arm64',
312
+ installLocation: 'user-applications',
313
+ minimumMacOSVersion,
314
+ },
315
+ artifact: {
316
+ ...parsedArtifact,
317
+ format: 'bleam-macos-app-zip-v1',
318
+ },
319
+ }
320
+ }
321
+
322
+ return invalid('kind', 'must be "ota" or "platform"')
323
+ }
324
+
325
+ export function parsePublishedRelease(value: unknown): StoredRelease {
326
+ const release = parseStoredRelease(value)
327
+ if (release.kind !== 'platform') return release
328
+
329
+ const input = record(value, 'manifest')
330
+ if (release.platformFingerprint === undefined) {
331
+ return invalid('platformFingerprint', 'is required')
332
+ }
333
+ if (typeof input.redeploy !== 'boolean') {
334
+ return invalid('redeploy', 'must be a boolean')
335
+ }
336
+ return release
337
+ }
338
+
339
+ export function parseDownloadRecord(value: unknown): DownloadRecord {
340
+ const input = record(value, 'download record')
341
+ if (input.schemaVersion !== 1) {
342
+ return invalid('schemaVersion', 'must be 1')
343
+ }
344
+
345
+ const downloadId = string(input.downloadId, 'downloadId')
346
+ if (!isDownloadId(downloadId)) {
347
+ return invalid(
348
+ 'downloadId',
349
+ 'must use lowercase letters, numbers, underscores, or hyphens',
350
+ )
351
+ }
352
+
353
+ const artifactKey = string(input.artifactKey, 'artifactKey')
354
+ if (
355
+ !/^artifacts\/[A-Za-z0-9._/-]+$/.test(artifactKey) ||
356
+ artifactKey.split('/').includes('..')
357
+ ) {
358
+ return invalid('artifactKey', 'must be a private artifacts path')
359
+ }
360
+
361
+ const contentType = string(input.contentType, 'contentType')
362
+ if (!/^[^/\s]+\/[^/\s]+$/.test(contentType)) {
363
+ return invalid('contentType', 'must be a MIME type')
364
+ }
365
+
366
+ return {
367
+ schemaVersion: 1,
368
+ downloadId,
369
+ artifactKey,
370
+ contentType,
371
+ }
372
+ }
373
+
374
+ export function resolveRelease(
375
+ release: StoredRelease,
376
+ artifactUrl: string,
377
+ ): ResolvedRelease {
378
+ return {
379
+ ...release,
380
+ artifact: {
381
+ ...release.artifact,
382
+ url: artifactUrl,
383
+ },
384
+ }
385
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "WebWorker"],
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "skipLibCheck": true
10
+ },
11
+ "include": ["src"]
12
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "bleam-updates-local",
3
+ "main": "src/index.ts",
4
+ "compatibility_date": "2026-07-13",
5
+ "compatibility_flags": ["nodejs_compat"],
6
+ "workers_dev": false,
7
+ "send_metrics": false,
8
+ "dev": {
9
+ "ip": "127.0.0.1",
10
+ "port": 8787,
11
+ "local_protocol": "http"
12
+ },
13
+ "r2_buckets": [
14
+ {
15
+ "binding": "UPDATES",
16
+ "bucket_name": "bleam-updates-local"
17
+ }
18
+ ]
19
+ }
@@ -1,29 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <plist version="1.0">
4
- <dict>
5
- <key>CFBundleDevelopmentRegion</key>
6
- <string>$(DEVELOPMENT_LANGUAGE)</string>
7
- <key>CFBundleExecutable</key>
8
- <string>$(EXECUTABLE_NAME)</string>
9
- <key>CFBundleIdentifier</key>
10
- <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11
- <key>CFBundleInfoDictionaryVersion</key>
12
- <string>6.0</string>
13
- <key>CFBundleName</key>
14
- <string>$(PRODUCT_NAME)</string>
15
- <key>CFBundlePackageType</key>
16
- <string>XPC!</string>
17
- <key>CFBundleShortVersionString</key>
18
- <string>$(MARKETING_VERSION)</string>
19
- <key>CFBundleVersion</key>
20
- <string>$(CURRENT_PROJECT_VERSION)</string>
21
- <key>BleamPlatformVersion</key>
22
- <string>0.0.9</string>
23
- <key>XPCService</key>
24
- <dict>
25
- <key>ServiceType</key>
26
- <string>Application</string>
27
- </dict>
28
- </dict>
29
- </plist>