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.
- package/dist/cli.cjs +246 -18
- package/dist/cli.d.cts +30 -2
- package/dist/cli.d.ts +30 -2
- package/dist/cli.js +247 -19
- package/dist/platform.d.cts +2 -0
- package/dist/platform.d.ts +2 -0
- package/dist/{ui-1WepaMS4.d.cts → ui-Dd7SXdbg.d.cts} +7 -7
- package/dist/ui.d.cts +1 -1
- package/dist/window.d.cts +1 -1
- package/package.json +2 -1
- package/templates/native/ios/Bleam/AppDelegate.swift +129 -20
- package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +19 -18
- package/templates/native/ios/PlatformHelper/main.swift +457 -66
- package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +21 -5
- package/templates/updates/README.md +272 -0
- package/templates/updates/src/index.ts +385 -0
- package/templates/updates/src/schema.ts +385 -0
- package/templates/updates/tsconfig.json +12 -0
- package/templates/updates/wrangler.jsonc +19 -0
- package/templates/native/ios/PlatformHelper/Info.plist +0 -29
|
@@ -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,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>
|