bleam 0.0.7 → 0.0.9
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 +23 -4
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +23 -4
- package/dist/{ui-234Plg7Z.d.cts → ui-Bg11tvlc.d.ts} +7 -7
- package/dist/ui.d.ts +1 -1
- package/dist/window.d.ts +1 -1
- package/package.json +4 -1
- package/templates/image-generation/app/index.tsx +1 -1
- package/templates/native/.gitattributes +1 -0
- package/templates/native/App.tsx +26 -0
- package/templates/native/app.json +12 -0
- package/templates/native/index.ts +8 -0
- package/templates/native/ios/.xcode.env +11 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/Flux2Config.swift +288 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryConfig.swift +233 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryOptimizationConfig.swift +157 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/ModelRegistry.swift +425 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/QuantizationConfig.swift +138 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/TransformerRepoProfile.swift +59 -0
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/VAEConfig.swift +134 -0
- package/templates/native/ios/Bleam/AI/Flux2/Flux2Core.swift +55 -0
- package/templates/native/ios/Bleam/AI/Flux2/Loading/WeightLoader.swift +1192 -0
- package/templates/native/ios/Bleam/AI/Flux2/Pipeline/FluxImageRunner.swift +1282 -0
- package/templates/native/ios/Bleam/AI/Flux2/Pipeline/LatentUtils.swift +191 -0
- package/templates/native/ios/Bleam/AI/Flux2/Scheduler/FlowMatchEulerScheduler.swift +279 -0
- package/templates/native/ios/Bleam/AI/Flux2/Text/KleinTextEncoder.swift +380 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Attention.swift +379 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Embeddings.swift +159 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FeedForward.swift +154 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FusedKernels.swift +198 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2KVCache.swift +36 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Modulation.swift +193 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2ParallelAttention.swift +389 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2RoPE.swift +304 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2SingleBlock.swift +290 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Transformer.swift +530 -0
- package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2TransformerBlock.swift +362 -0
- package/templates/native/ios/Bleam/AI/Flux2/Utils/Flux2Debug.swift +101 -0
- package/templates/native/ios/Bleam/AI/Flux2/Utils/MLXCheckpoint.swift +118 -0
- package/templates/native/ios/Bleam/AI/Flux2/Utils/MemoryManager.swift +201 -0
- package/templates/native/ios/Bleam/AI/Flux2/VAE/AutoencoderKL.swift +476 -0
- package/templates/native/ios/Bleam/AI/Flux2/VAE/ResnetBlock.swift +316 -0
- package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEDecoder.swift +120 -0
- package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEEncoder.swift +136 -0
- package/templates/native/ios/Bleam/AppDelegate.swift +373 -0
- package/templates/native/ios/Bleam/Appearance.swift +62 -0
- package/templates/native/ios/Bleam/Bridging-Header.h +3 -0
- package/templates/native/ios/Bleam/Images.xcassets/AppIcon.appiconset/Contents.json +13 -0
- package/templates/native/ios/Bleam/Images.xcassets/Contents.json +6 -0
- package/templates/native/ios/Bleam/Info.plist +76 -0
- package/templates/native/ios/Bleam/PrivacyInfo.xcprivacy +48 -0
- package/templates/native/ios/Bleam/SceneDelegate.swift +58 -0
- package/templates/native/ios/Bleam/SplashScreen.storyboard +47 -0
- package/templates/native/ios/Bleam/Supporting/Expo.plist +6 -0
- package/templates/native/ios/Bleam/bleam.entitlements +10 -0
- package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +1146 -0
- package/templates/native/ios/Bleam.xcodeproj/xcshareddata/xcschemes/Bleam.xcscheme +88 -0
- package/templates/native/ios/GenerationService/GenerationService.entitlements +10 -0
- package/templates/native/ios/GenerationService/ImageGenerationRunner.swift +591 -0
- package/templates/native/ios/GenerationService/Info.plist +31 -0
- package/templates/native/ios/GenerationService/main.swift +165 -0
- package/templates/native/ios/PlatformHelper/Info.plist +29 -0
- package/templates/native/ios/PlatformHelper/main.swift +335 -0
- package/templates/native/ios/Podfile +198 -0
- package/templates/native/ios/Podfile.lock +2284 -0
- package/templates/native/ios/Podfile.properties.json +5 -0
- package/templates/native/ios/Shared/Generation/GenerationServiceProtocol.swift +12 -0
- package/templates/native/ios/Shared/Generation/GenerationWorkerProtocol.swift +124 -0
- package/templates/native/metro.config.js +6 -0
- package/templates/native/modules/bleam-runtime/BleamRuntime.podspec +14 -0
- package/templates/native/modules/bleam-runtime/expo-module.config.json +8 -0
- package/templates/native/modules/bleam-runtime/ios/AIModule.swift +1258 -0
- package/templates/native/modules/bleam-runtime/ios/GenerationContracts.swift +135 -0
- package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +313 -0
- package/templates/native/modules/bleam-runtime/package.json +5 -0
- package/templates/native/package.json +39 -0
- package/templates/native/scripts/build/mlx-frameworks.ts +421 -0
- package/templates/native/scripts/start/index.ts +474 -0
- package/templates/native/scripts/start/known-failures.ts +20 -0
- package/templates/native/scripts/start/loader.ts +76 -0
- package/templates/native/scripts/start/native-state.ts +242 -0
- package/templates/native/scripts/start/package.json +4 -0
- package/templates/native/scripts/start/swift-packages.ts +414 -0
- package/templates/native/scripts/start/xcode-formatter.ts +106 -0
- package/templates/native/tsconfig.json +14 -0
- package/templates/native/yarn.lock +3749 -0
- package/dist/ui-BJmXhz9Q.d.ts +0 -92
- package/dist/ui-Dd7SXdbg.d.cts +0 -92
- package/dist/ui-Ds88eETu.d.ts +0 -92
- package/dist/ui-TaqnB5SP.d.ts +0 -92
- package/dist/ui-WQvnXL0W.d.ts +0 -92
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { type ChildProcess, execFileSync, spawn } from 'node:child_process'
|
|
4
|
+
import {
|
|
5
|
+
copyFileSync,
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from 'node:fs'
|
|
13
|
+
import http from 'node:http'
|
|
14
|
+
import path from 'node:path'
|
|
15
|
+
import { ensureMLXSwiftCatalystFrameworks } from '../build/mlx-frameworks.ts'
|
|
16
|
+
import { getKnownFailureGuidance } from './known-failures.ts'
|
|
17
|
+
import { Loader } from './loader.ts'
|
|
18
|
+
import {
|
|
19
|
+
clearNativeState,
|
|
20
|
+
computeNativeFingerprint,
|
|
21
|
+
getNativeChangeKind,
|
|
22
|
+
writeNativeState,
|
|
23
|
+
} from './native-state.ts'
|
|
24
|
+
import {
|
|
25
|
+
extractPackageProgressLabel,
|
|
26
|
+
getSwiftPackageResolvePlan,
|
|
27
|
+
type ResolvePackagesPlan,
|
|
28
|
+
} from './swift-packages.ts'
|
|
29
|
+
import { XcFormatter } from './xcode-formatter.ts'
|
|
30
|
+
|
|
31
|
+
const XCODEBUILD_LOG_PATH = path.resolve(process.cwd(), 'build/xcodebuild.log')
|
|
32
|
+
const SUCCESS_MARKER_PATH = path.resolve(
|
|
33
|
+
process.cwd(),
|
|
34
|
+
'build/.bleam-build-succeeded',
|
|
35
|
+
)
|
|
36
|
+
const XCODEBUILD_PROVISIONING_ARGS = [
|
|
37
|
+
'-allowProvisioningUpdates',
|
|
38
|
+
'-allowProvisioningDeviceRegistration',
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
class ReportedCommandError extends Error {
|
|
42
|
+
reported = true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ensureVendoredPodSources() {
|
|
46
|
+
const nodeModules = path.resolve(process.cwd(), 'node_modules')
|
|
47
|
+
const restoredFiles: string[] = []
|
|
48
|
+
|
|
49
|
+
const sqliteIos = path.join(nodeModules, 'expo-sqlite', 'ios')
|
|
50
|
+
const sqliteVendor = path.join(
|
|
51
|
+
nodeModules,
|
|
52
|
+
'expo-sqlite',
|
|
53
|
+
'vendor',
|
|
54
|
+
'sqlite3',
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if (existsSync(sqliteVendor)) {
|
|
58
|
+
for (const file of ['sqlite3.h', 'sqlite3.c']) {
|
|
59
|
+
const dest = path.join(sqliteIos, file)
|
|
60
|
+
const src = path.join(sqliteVendor, file)
|
|
61
|
+
if (!existsSync(dest) && existsSync(src)) {
|
|
62
|
+
copyFileSync(src, dest)
|
|
63
|
+
restoredFiles.push(`expo-sqlite/ios/${file}`)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (restoredFiles.length > 0) {
|
|
69
|
+
console.log(
|
|
70
|
+
`\x1b[33m▸ Restored ${restoredFiles.length} missing pod source${restoredFiles.length === 1 ? '' : 's'}\x1b[0m`,
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function findXcodeProj(dir: string): string | null {
|
|
76
|
+
try {
|
|
77
|
+
const files = readdirSync(dir)
|
|
78
|
+
const xcodeproj = files.find((file) => file.endsWith('.xcodeproj'))
|
|
79
|
+
return xcodeproj ? xcodeproj.replace('.xcodeproj', '') : null
|
|
80
|
+
} catch {
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getProjectName(): string {
|
|
86
|
+
const iosDir = path.resolve(process.cwd(), 'ios')
|
|
87
|
+
const name = findXcodeProj(iosDir)
|
|
88
|
+
|
|
89
|
+
if (!name) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
'Could not find .xcodeproj in ios/. Bleam.app must be a bare iOS project.',
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return name
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseArgs(argv: string[]): { clean: boolean; port: number } {
|
|
99
|
+
const clean = argv.includes('--clean') || argv.includes('-c')
|
|
100
|
+
const portFlagIndex = argv.findIndex(
|
|
101
|
+
(arg) => arg === '--port' || arg === '-p',
|
|
102
|
+
)
|
|
103
|
+
const inlinePort = argv
|
|
104
|
+
.find((arg) => arg.startsWith('--port='))
|
|
105
|
+
?.split('=')[1]
|
|
106
|
+
const portValue =
|
|
107
|
+
inlinePort ?? (portFlagIndex === -1 ? undefined : argv[portFlagIndex + 1])
|
|
108
|
+
const port = portValue ? Number(portValue) : 8081
|
|
109
|
+
|
|
110
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
111
|
+
throw new Error(`Invalid port: ${portValue}`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { clean, port }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function runXcodebuild(
|
|
118
|
+
args: string[],
|
|
119
|
+
label: string,
|
|
120
|
+
loader?: Loader,
|
|
121
|
+
onLine?: (line: string) => void,
|
|
122
|
+
): Promise<void> {
|
|
123
|
+
const output: string[] = []
|
|
124
|
+
const formatter = new XcFormatter()
|
|
125
|
+
let loaderStopped = false
|
|
126
|
+
let pendingStdout = ''
|
|
127
|
+
let pendingStderr = ''
|
|
128
|
+
|
|
129
|
+
const emitLines = (chunk: string, pending: string): string => {
|
|
130
|
+
const text = pending + chunk
|
|
131
|
+
const lines = text.split(/\r?\n/)
|
|
132
|
+
const nextPending = lines.pop() ?? ''
|
|
133
|
+
for (const line of lines) {
|
|
134
|
+
onLine?.(line.trimEnd())
|
|
135
|
+
}
|
|
136
|
+
return nextPending
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const stopLoader = () => {
|
|
140
|
+
if (loader && !loaderStopped) {
|
|
141
|
+
loader.stop()
|
|
142
|
+
loaderStopped = true
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
await new Promise<void>((resolve, reject) => {
|
|
147
|
+
const child = spawn('xcodebuild', args, {
|
|
148
|
+
cwd: process.cwd(),
|
|
149
|
+
env: process.env,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
child.stdout.on('data', (chunk: Buffer) => {
|
|
153
|
+
const text = chunk.toString()
|
|
154
|
+
output.push(text)
|
|
155
|
+
formatter.write(text)
|
|
156
|
+
pendingStdout = emitLines(text, pendingStdout)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
child.stderr.on('data', (chunk: Buffer) => {
|
|
160
|
+
const text = chunk.toString()
|
|
161
|
+
output.push(text)
|
|
162
|
+
formatter.write(text)
|
|
163
|
+
pendingStderr = emitLines(text, pendingStderr)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
child.on('error', reject)
|
|
167
|
+
child.on('close', (code) => {
|
|
168
|
+
if (pendingStdout) {
|
|
169
|
+
onLine?.(pendingStdout.trimEnd())
|
|
170
|
+
}
|
|
171
|
+
if (pendingStderr) {
|
|
172
|
+
onLine?.(pendingStderr.trimEnd())
|
|
173
|
+
}
|
|
174
|
+
formatter.finish()
|
|
175
|
+
stopLoader()
|
|
176
|
+
if (code === 0) {
|
|
177
|
+
resolve()
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
reject(new Error(`${label} failed`))
|
|
182
|
+
})
|
|
183
|
+
}).catch((error) => {
|
|
184
|
+
stopLoader()
|
|
185
|
+
const fullOutput = output.join('')
|
|
186
|
+
mkdirSync(path.dirname(XCODEBUILD_LOG_PATH), { recursive: true })
|
|
187
|
+
writeFileSync(XCODEBUILD_LOG_PATH, fullOutput, 'utf8')
|
|
188
|
+
|
|
189
|
+
const summary = formatter.getFailureSummary()
|
|
190
|
+
const fallbackSummary = fullOutput.split(/\r?\n/).filter(Boolean).slice(-80)
|
|
191
|
+
console.error(`\n\x1b[31m✖ ${label} failed\x1b[0m\n`)
|
|
192
|
+
if (summary.length > 0) {
|
|
193
|
+
console.error(summary.join('\n'))
|
|
194
|
+
console.error('')
|
|
195
|
+
} else if (fallbackSummary.length > 0) {
|
|
196
|
+
console.error(fallbackSummary.join('\n'))
|
|
197
|
+
console.error('')
|
|
198
|
+
}
|
|
199
|
+
const guidance = getKnownFailureGuidance(fullOutput)
|
|
200
|
+
if (guidance.length > 0) {
|
|
201
|
+
console.error(guidance.join('\n'))
|
|
202
|
+
}
|
|
203
|
+
console.error(
|
|
204
|
+
`Full log: ${path.relative(process.cwd(), XCODEBUILD_LOG_PATH)}`,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
const reportedError = new ReportedCommandError(
|
|
208
|
+
error instanceof Error ? error.message : String(error),
|
|
209
|
+
)
|
|
210
|
+
throw reportedError
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
mkdirSync(path.dirname(XCODEBUILD_LOG_PATH), { recursive: true })
|
|
214
|
+
writeFileSync(XCODEBUILD_LOG_PATH, output.join(''), 'utf8')
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function clearBuildSuccessMarker() {
|
|
218
|
+
rmSync(SUCCESS_MARKER_PATH, { force: true })
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function writeBuildSuccessMarker() {
|
|
222
|
+
mkdirSync(path.dirname(SUCCESS_MARKER_PATH), { recursive: true })
|
|
223
|
+
writeFileSync(SUCCESS_MARKER_PATH, `${Date.now()}\n`, 'utf8')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function hasSuccessfulBuild(appPath: string): boolean {
|
|
227
|
+
return existsSync(appPath) && existsSync(SUCCESS_MARKER_PATH)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function clearBuildReuseState() {
|
|
231
|
+
clearBuildSuccessMarker()
|
|
232
|
+
clearNativeState(process.cwd())
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function removeBuildPath(target: string) {
|
|
236
|
+
if (!existsSync(target)) {
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const tombstone = `${target}.deleting-${Date.now()}`
|
|
241
|
+
try {
|
|
242
|
+
renameSync(target, tombstone)
|
|
243
|
+
rmSync(tombstone, {
|
|
244
|
+
recursive: true,
|
|
245
|
+
force: true,
|
|
246
|
+
maxRetries: 10,
|
|
247
|
+
retryDelay: 100,
|
|
248
|
+
})
|
|
249
|
+
} catch {
|
|
250
|
+
rmSync(target, {
|
|
251
|
+
recursive: true,
|
|
252
|
+
force: true,
|
|
253
|
+
maxRetries: 10,
|
|
254
|
+
retryDelay: 100,
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function waitForMetro(
|
|
260
|
+
port: number = 8081,
|
|
261
|
+
timeoutMs: number = 120_000,
|
|
262
|
+
intervalMs: number = 500,
|
|
263
|
+
): Promise<void> {
|
|
264
|
+
return new Promise((resolve, reject) => {
|
|
265
|
+
const start = Date.now()
|
|
266
|
+
|
|
267
|
+
const poll = () => {
|
|
268
|
+
const req = http.get(`http://localhost:${port}/status`, (res) => {
|
|
269
|
+
let data = ''
|
|
270
|
+
res.on('data', (chunk: Buffer) => (data += chunk.toString()))
|
|
271
|
+
res.on('end', () => {
|
|
272
|
+
if (data.includes('packager-status:running')) {
|
|
273
|
+
resolve()
|
|
274
|
+
} else {
|
|
275
|
+
scheduleRetry()
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
req.on('error', () => {
|
|
281
|
+
scheduleRetry()
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
req.setTimeout(2000, () => {
|
|
285
|
+
req.destroy()
|
|
286
|
+
scheduleRetry()
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const scheduleRetry = () => {
|
|
291
|
+
if (Date.now() - start > timeoutMs) {
|
|
292
|
+
reject(new Error(`Metro did not start within ${timeoutMs / 1000}s`))
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
setTimeout(poll, intervalMs)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
poll()
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function buildApp(
|
|
303
|
+
projectName: string,
|
|
304
|
+
resolvePlan: ResolvePackagesPlan,
|
|
305
|
+
): Promise<void> {
|
|
306
|
+
clearBuildReuseState()
|
|
307
|
+
|
|
308
|
+
const resolveLoader = new Loader('Resolving package dependencies')
|
|
309
|
+
resolveLoader.start()
|
|
310
|
+
await runXcodebuild(
|
|
311
|
+
[
|
|
312
|
+
...XCODEBUILD_PROVISIONING_ARGS,
|
|
313
|
+
'-workspace',
|
|
314
|
+
`ios/${projectName}.xcworkspace`,
|
|
315
|
+
'-scheme',
|
|
316
|
+
projectName,
|
|
317
|
+
'-resolvePackageDependencies',
|
|
318
|
+
...resolvePlan.args,
|
|
319
|
+
'-clonedSourcePackagesDirPath',
|
|
320
|
+
'build/SourcePackages',
|
|
321
|
+
],
|
|
322
|
+
'Resolving package dependencies',
|
|
323
|
+
resolveLoader,
|
|
324
|
+
(line) => {
|
|
325
|
+
const packageLabel = extractPackageProgressLabel(line)
|
|
326
|
+
if (packageLabel) {
|
|
327
|
+
resolveLoader.update(`Resolving package dependencies - ${packageLabel}`)
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
const buildLoader = new Loader('Building for Mac Catalyst')
|
|
333
|
+
buildLoader.start()
|
|
334
|
+
await runXcodebuild(
|
|
335
|
+
[
|
|
336
|
+
...XCODEBUILD_PROVISIONING_ARGS,
|
|
337
|
+
'-workspace',
|
|
338
|
+
`ios/${projectName}.xcworkspace`,
|
|
339
|
+
'-scheme',
|
|
340
|
+
projectName,
|
|
341
|
+
'-configuration',
|
|
342
|
+
'Debug',
|
|
343
|
+
'-destination',
|
|
344
|
+
'platform=macOS,arch=arm64,variant=Mac Catalyst',
|
|
345
|
+
'-derivedDataPath',
|
|
346
|
+
'build',
|
|
347
|
+
'-clonedSourcePackagesDirPath',
|
|
348
|
+
'build/SourcePackages',
|
|
349
|
+
'build',
|
|
350
|
+
],
|
|
351
|
+
'Build',
|
|
352
|
+
buildLoader,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
writeBuildSuccessMarker()
|
|
356
|
+
writeNativeState(process.cwd(), computeNativeFingerprint(process.cwd()))
|
|
357
|
+
console.log('\n\x1b[32m✔ Build complete\x1b[0m\n')
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function startMetro(port: number): ChildProcess {
|
|
361
|
+
return spawn(
|
|
362
|
+
'npx',
|
|
363
|
+
['expo', 'start', '--localhost', '--port', String(port)],
|
|
364
|
+
{
|
|
365
|
+
stdio: 'inherit',
|
|
366
|
+
shell: true,
|
|
367
|
+
},
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function setupCleanup(metro: ChildProcess) {
|
|
372
|
+
const cleanup = (signal: NodeJS.Signals) => {
|
|
373
|
+
metro.kill(signal)
|
|
374
|
+
process.exit(0)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
process.on('SIGINT', () => cleanup('SIGINT'))
|
|
378
|
+
process.on('SIGTERM', () => cleanup('SIGTERM'))
|
|
379
|
+
|
|
380
|
+
metro.on('exit', (code) => {
|
|
381
|
+
process.exit(code ?? 0)
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function handleNativeChanges(clean: boolean) {
|
|
386
|
+
if (clean && existsSync('./build')) {
|
|
387
|
+
console.log('\x1b[33m▸ Cleaning build directory...\x1b[0m')
|
|
388
|
+
removeBuildPath('./build')
|
|
389
|
+
clearBuildReuseState()
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (clean) {
|
|
394
|
+
clearBuildReuseState()
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const changeKind = getNativeChangeKind(process.cwd())
|
|
399
|
+
if (changeKind === 'structural') {
|
|
400
|
+
console.log(
|
|
401
|
+
'\x1b[33m▸ iOS native config changed since last build - removing stale build/Build\x1b[0m',
|
|
402
|
+
)
|
|
403
|
+
removeBuildPath('./build/Build')
|
|
404
|
+
clearBuildReuseState()
|
|
405
|
+
} else if (changeKind === 'incremental') {
|
|
406
|
+
console.log(
|
|
407
|
+
'\x1b[33m▸ iOS sources changed since last build - rebuilding app\x1b[0m',
|
|
408
|
+
)
|
|
409
|
+
clearBuildReuseState()
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export async function startBleam(
|
|
414
|
+
argv: string[] = process.argv.slice(2),
|
|
415
|
+
): Promise<void> {
|
|
416
|
+
const { clean, port } = parseArgs(argv)
|
|
417
|
+
const projectName = getProjectName()
|
|
418
|
+
const appPath = `./build/Build/Products/Debug-maccatalyst/${projectName}.app`
|
|
419
|
+
|
|
420
|
+
ensureVendoredPodSources()
|
|
421
|
+
handleNativeChanges(clean)
|
|
422
|
+
|
|
423
|
+
const needsBuild = clean || !hasSuccessfulBuild(appPath)
|
|
424
|
+
|
|
425
|
+
if (needsBuild) {
|
|
426
|
+
console.log('\x1b[36m▸ Preparing MLX Swift Catalyst frameworks...\x1b[0m')
|
|
427
|
+
ensureMLXSwiftCatalystFrameworks(process.cwd())
|
|
428
|
+
const resolvePlan = getSwiftPackageResolvePlan(process.cwd(), projectName)
|
|
429
|
+
if (resolvePlan.reason === 'resolved-remote-packages') {
|
|
430
|
+
console.log(
|
|
431
|
+
'\x1b[36m▸ Reusing resolved remote package pins from Package.resolved\x1b[0m',
|
|
432
|
+
)
|
|
433
|
+
}
|
|
434
|
+
await buildApp(projectName, resolvePlan)
|
|
435
|
+
} else {
|
|
436
|
+
console.log('\x1b[32m▸ Using existing build\x1b[0m')
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const metroLoader = new Loader('Starting Metro')
|
|
440
|
+
metroLoader.start()
|
|
441
|
+
const metro = startMetro(port)
|
|
442
|
+
metroLoader.stop()
|
|
443
|
+
setupCleanup(metro)
|
|
444
|
+
|
|
445
|
+
const waitLoader = new Loader('Waiting for Metro')
|
|
446
|
+
waitLoader.start()
|
|
447
|
+
try {
|
|
448
|
+
await waitForMetro(port)
|
|
449
|
+
waitLoader.stop()
|
|
450
|
+
console.log('\n\x1b[36m▸ Opening app...\x1b[0m\n')
|
|
451
|
+
const bundleURL = `http://127.0.0.1:${port}/index.bundle?platform=ios&dev=true&minify=false`
|
|
452
|
+
execFileSync('open', [appPath, '--args', '--bleam-bundle-url', bundleURL], {
|
|
453
|
+
stdio: 'inherit',
|
|
454
|
+
})
|
|
455
|
+
} catch (err) {
|
|
456
|
+
waitLoader.fail()
|
|
457
|
+
console.error(
|
|
458
|
+
`\n\x1b[31m✖ ${err instanceof Error ? err.message : err}\x1b[0m`,
|
|
459
|
+
)
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export function isReportedCommandError(error: unknown): boolean {
|
|
464
|
+
return error instanceof ReportedCommandError && error.reported
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
startBleam().catch((error) => {
|
|
468
|
+
if (isReportedCommandError(error)) {
|
|
469
|
+
process.exit(1)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
console.error(error instanceof Error ? error.message : error)
|
|
473
|
+
process.exit(1)
|
|
474
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const FMT_CATALYST_FAILURE_PATTERNS = [
|
|
2
|
+
/Pods\/fmt\/include\/fmt\/format-inl\.h/i,
|
|
3
|
+
/call to consteval function/i,
|
|
4
|
+
/FMT_STRING\(/i,
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
export function isFmtCatalystFailure(output: string): boolean {
|
|
8
|
+
return FMT_CATALYST_FAILURE_PATTERNS.every((pattern) => pattern.test(output))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getKnownFailureGuidance(output: string): string[] {
|
|
12
|
+
if (!isFmtCatalystFailure(output)) {
|
|
13
|
+
return []
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return [
|
|
17
|
+
'\n\x1b[33m▸ Detected the React Native/fmt Mac Catalyst compile failure.\x1b[0m',
|
|
18
|
+
"\x1b[33m Re-run `pod install` so Bleam's Catalyst fmt and gnu++17 pod workarounds are applied before you run `yarn start` again.\x1b[0m",
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const FRAMES = ['|', '/', '-', '\\']
|
|
2
|
+
const CLEAR_LINE = '\r\x1b[2K'
|
|
3
|
+
|
|
4
|
+
export class Loader {
|
|
5
|
+
#frameIndex: number = 0
|
|
6
|
+
|
|
7
|
+
#timer: ReturnType<typeof setInterval> | null = null
|
|
8
|
+
|
|
9
|
+
#started: boolean = false
|
|
10
|
+
|
|
11
|
+
#currentMessage: string
|
|
12
|
+
|
|
13
|
+
#message: string
|
|
14
|
+
|
|
15
|
+
#output: NodeJS.WriteStream
|
|
16
|
+
|
|
17
|
+
constructor(message: string, output: NodeJS.WriteStream = process.stdout) {
|
|
18
|
+
this.#message = message
|
|
19
|
+
this.#output = output
|
|
20
|
+
this.#currentMessage = message
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
start() {
|
|
24
|
+
if (this.#started) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.#started = true
|
|
29
|
+
|
|
30
|
+
if (!this.#output.isTTY) {
|
|
31
|
+
this.#output.write(`▸ ${this.#message}...\n`)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.#render()
|
|
36
|
+
this.#timer = setInterval(() => {
|
|
37
|
+
this.#frameIndex = (this.#frameIndex + 1) % FRAMES.length
|
|
38
|
+
this.#render()
|
|
39
|
+
}, 100)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
stop() {
|
|
43
|
+
if (!this.#started) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (this.#timer) {
|
|
48
|
+
clearInterval(this.#timer)
|
|
49
|
+
this.#timer = null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (this.#output.isTTY) {
|
|
53
|
+
this.#output.write(CLEAR_LINE)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.#started = false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fail() {
|
|
60
|
+
this.stop()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
update(message: string) {
|
|
64
|
+
this.#currentMessage = message
|
|
65
|
+
|
|
66
|
+
if (this.#started && this.#output.isTTY) {
|
|
67
|
+
this.#render()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#render() {
|
|
72
|
+
this.#output.write(
|
|
73
|
+
`${CLEAR_LINE}\x1b[36m${FRAMES[this.#frameIndex]} ${this.#currentMessage}...\x1b[0m`,
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
}
|