bleam 0.0.6 → 0.0.8
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/ai.cjs +3984 -25
- package/dist/ai.d.cts +503 -20
- package/dist/ai.d.ts +503 -20
- package/dist/ai.js +3946 -26
- package/dist/animated.cjs +1 -1
- package/dist/animated.js +1 -1
- package/dist/app.cjs +92 -0
- package/dist/app.js +92 -0
- package/dist/cli.cjs +653 -86
- package/dist/cli.d.cts +89 -1
- package/dist/cli.d.ts +89 -1
- package/dist/cli.js +654 -88
- package/dist/{config-ClDGHNb3.d.ts → config-Cms0rvqg.d.ts} +1 -0
- package/dist/{config-D3gPaVMb.d.cts → config-CufOVJV3.d.cts} +1 -0
- package/dist/config.d.cts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/crypto.d.cts +0 -2
- package/dist/crypto.d.ts +1 -1
- package/dist/dev-protocol.d.cts +14 -1
- package/dist/dev-protocol.d.ts +14 -1
- package/dist/files-Bo7h9fik.cjs +468 -0
- package/dist/files-Ds1wT8C2.js +445 -0
- package/dist/files-DwA7pzr3.d.cts +99 -0
- package/dist/files-VrkQlKIT.d.ts +99 -0
- package/dist/files.cjs +7 -0
- package/dist/files.d.cts +2 -0
- package/dist/files.d.ts +2 -0
- package/dist/files.js +4 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/native-sqlite-xcGdamRD.js +64 -0
- package/dist/native-sqlite-yQLD5s9i.cjs +66 -0
- package/dist/platform.cjs +27 -0
- package/dist/platform.d.cts +39 -0
- package/dist/platform.d.ts +39 -0
- package/dist/platform.js +21 -0
- package/dist/runtime.d.cts +1 -6
- package/dist/runtime.d.ts +1 -6
- package/dist/{schema-ipsECAso.d.ts → schema-DOOjfXvs.d.ts} +1 -1
- package/dist/{schema-BOuUHcqo.d.cts → schema-ENSMX_1t.d.cts} +1 -1
- package/dist/schema.cjs +1 -1
- package/dist/schema.d.cts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +1 -1
- package/dist/state-Bx0VlTlO.cjs +852 -0
- package/dist/state-CAwe-Vw1.js +767 -0
- package/dist/state.cjs +17 -763
- package/dist/state.d.cts +107 -77
- package/dist/state.d.ts +107 -77
- package/dist/state.js +4 -745
- package/dist/ui-1WepaMS4.d.cts +92 -0
- package/dist/ui-Ce-pnUUA.d.ts +92 -0
- package/dist/ui.cjs +286 -35
- package/dist/ui.d.cts +2 -30
- package/dist/ui.d.ts +2 -30
- package/dist/ui.js +283 -36
- package/dist/window.cjs +596 -0
- package/dist/window.d.cts +63 -0
- package/dist/window.d.ts +63 -0
- package/dist/window.js +589 -0
- package/package.json +16 -14
- package/templates/basic/app/index.tsx +8 -5
- package/templates/foundation-models/app/index.tsx +32 -0
- package/templates/foundation-models/app.config.ts +5 -0
- package/templates/image-generation/app/index.tsx +39 -0
- package/templates/image-generation/app.config.ts +5 -0
- package/templates/state/app/index.tsx +30 -0
- package/templates/state/app.config.ts +5 -0
- package/dist/chrome.cjs +0 -265
- package/dist/chrome.d.cts +0 -19
- package/dist/chrome.d.ts +0 -19
- package/dist/chrome.js +0 -262
- package/dist/fs.cjs +0 -13
- package/dist/fs.d.cts +0 -8
- package/dist/fs.d.ts +0 -8
- package/dist/fs.js +0 -12
- package/dist/native-macos-D6p2SmUj.js +0 -20
- package/dist/native-macos-DLwo_ii9.cjs +0 -31
- package/dist/native-runtime-CbPzrs8j.d.cts +0 -33
- package/dist/native-runtime-DxHSrsTX.d.ts +0 -33
- package/dist/native.cjs +0 -135
- package/dist/native.d.cts +0 -34
- package/dist/native.d.ts +0 -34
- package/dist/native.js +0 -135
- /package/dist/{schema-DVmoD9JL.js → schema-Bo5Jvqus.js} +0 -0
- /package/dist/{schema-bxeeCyLn.cjs → schema-rQ13mrpD.cjs} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
import { appendFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
6
6
|
import http from "node:http";
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
import path from "node:path";
|
|
@@ -16,6 +16,7 @@ const require = createRequire(import.meta.url);
|
|
|
16
16
|
const packageRoot = path.dirname(fileURLToPath(new URL("../package.json", import.meta.url)));
|
|
17
17
|
const packageJson = require("../package.json");
|
|
18
18
|
const runtimeVersion = packageJson.version;
|
|
19
|
+
const starterAppName = "Bleam Basic";
|
|
19
20
|
function artifactPlatform() {
|
|
20
21
|
return `macos-${process.arch}`;
|
|
21
22
|
}
|
|
@@ -73,16 +74,52 @@ function printUsage() {
|
|
|
73
74
|
console.log(`Usage: bleam <command>
|
|
74
75
|
|
|
75
76
|
Commands:
|
|
76
|
-
new
|
|
77
|
-
|
|
77
|
+
new <dir> [--template <name>]
|
|
78
|
+
Create and run a Bleam project
|
|
79
|
+
dev [dir] [--port <port>] [--no-run] [--clean]
|
|
78
80
|
Start a Bleam dev server
|
|
79
|
-
build [dir] [--output <dir>]
|
|
80
|
-
|
|
81
|
+
build [dir] [--output <dir>] [--clean]
|
|
82
|
+
Export standalone native archives
|
|
83
|
+
install [dir] [--output <dir>] [--clean]
|
|
84
|
+
Build and install a standalone macOS app
|
|
85
|
+
publish ota [dir] [--service-url <url>]
|
|
86
|
+
Publish an OTA update to the local update service
|
|
87
|
+
publish platform [dir] [--service-url <url>]
|
|
88
|
+
Build and publish a platform update locally
|
|
81
89
|
doctor [dir] Check the local Bleam environment
|
|
90
|
+
typecheck [dir] Typecheck project files
|
|
82
91
|
check [dir] [--write] Format, lint, and organize imports
|
|
83
92
|
format [dir] [--check] Format project files
|
|
84
93
|
lint [dir] Lint project files`);
|
|
85
94
|
}
|
|
95
|
+
function parsePublishOtaOptions(argv) {
|
|
96
|
+
const root = invocationRoot();
|
|
97
|
+
let projectRoot = root;
|
|
98
|
+
let hasProjectRootArg = false;
|
|
99
|
+
let serviceUrl = process.env.BLEAM_UPDATE_SERVICE_URL ?? "http://127.0.0.1:8787";
|
|
100
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
101
|
+
const arg = argv[index];
|
|
102
|
+
if (arg === "--service-url") {
|
|
103
|
+
serviceUrl = argv[++index] ?? "";
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (arg.startsWith("--service-url=")) {
|
|
107
|
+
serviceUrl = arg.slice(14);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
|
|
111
|
+
if (hasProjectRootArg) throw new Error("Only one project directory may be specified");
|
|
112
|
+
projectRoot = path.resolve(root, arg);
|
|
113
|
+
hasProjectRootArg = true;
|
|
114
|
+
}
|
|
115
|
+
const url = new URL(serviceUrl);
|
|
116
|
+
if (url.protocol !== "http:" || !["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("Local publishing requires an http://localhost service URL");
|
|
117
|
+
if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
|
|
118
|
+
return {
|
|
119
|
+
projectRoot: canonicalPath(projectRoot),
|
|
120
|
+
serviceUrl: url.origin
|
|
121
|
+
};
|
|
122
|
+
}
|
|
86
123
|
function invocationRoot() {
|
|
87
124
|
const pwd = process.env.PWD;
|
|
88
125
|
return pwd && path.isAbsolute(pwd) ? pwd : process.cwd();
|
|
@@ -95,7 +132,8 @@ function parseDevOptions(argv) {
|
|
|
95
132
|
const root = invocationRoot();
|
|
96
133
|
let projectRoot = root;
|
|
97
134
|
let port = 8081;
|
|
98
|
-
let run =
|
|
135
|
+
let run = true;
|
|
136
|
+
let clean = false;
|
|
99
137
|
let hasProjectRootArg = false;
|
|
100
138
|
for (let index = 0; index < argv.length; index += 1) {
|
|
101
139
|
const arg = argv[index];
|
|
@@ -112,6 +150,14 @@ function parseDevOptions(argv) {
|
|
|
112
150
|
run = true;
|
|
113
151
|
continue;
|
|
114
152
|
}
|
|
153
|
+
if (arg === "--no-run") {
|
|
154
|
+
run = false;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (arg === "--clean" || arg === "-c") {
|
|
158
|
+
clean = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
115
161
|
if (arg === "--help" || arg === "-h") {
|
|
116
162
|
printUsage();
|
|
117
163
|
process.exit(0);
|
|
@@ -125,36 +171,55 @@ function parseDevOptions(argv) {
|
|
|
125
171
|
return {
|
|
126
172
|
projectRoot: canonicalPath(projectRoot),
|
|
127
173
|
port,
|
|
128
|
-
run
|
|
174
|
+
run,
|
|
175
|
+
clean
|
|
129
176
|
};
|
|
130
177
|
}
|
|
131
178
|
function parseNewOptions(argv) {
|
|
132
179
|
const root = invocationRoot();
|
|
133
|
-
let projectRoot
|
|
134
|
-
let
|
|
135
|
-
for (
|
|
136
|
-
|
|
137
|
-
run = true;
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
180
|
+
let projectRoot;
|
|
181
|
+
let template = "basic";
|
|
182
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
183
|
+
const arg = argv[index];
|
|
140
184
|
if (arg === "--help" || arg === "-h") {
|
|
141
185
|
printUsage();
|
|
142
186
|
process.exit(0);
|
|
143
187
|
}
|
|
188
|
+
if (arg === "--template") {
|
|
189
|
+
template = projectTemplate(argv[++index] ?? "");
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (arg.startsWith("--template=")) {
|
|
193
|
+
template = projectTemplate(arg.slice(11));
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
144
196
|
if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
|
|
197
|
+
if (projectRoot) throw new Error("Only one project directory may be specified");
|
|
145
198
|
projectRoot = path.resolve(root, arg);
|
|
146
199
|
}
|
|
200
|
+
if (!projectRoot) throw new Error("Usage: bleam new <dir>");
|
|
147
201
|
return {
|
|
148
202
|
projectRoot: canonicalPath(projectRoot),
|
|
149
|
-
|
|
203
|
+
template
|
|
150
204
|
};
|
|
151
205
|
}
|
|
206
|
+
const projectTemplates = [
|
|
207
|
+
"basic",
|
|
208
|
+
"state",
|
|
209
|
+
"foundation-models",
|
|
210
|
+
"image-generation"
|
|
211
|
+
];
|
|
212
|
+
function projectTemplate(value) {
|
|
213
|
+
if (projectTemplates.includes(value)) return value;
|
|
214
|
+
throw new Error(`Unknown template: ${value}. Expected ${projectTemplates.join(", ")}`);
|
|
215
|
+
}
|
|
152
216
|
function parseBuildOptions(argv) {
|
|
153
217
|
const root = invocationRoot();
|
|
154
218
|
let projectRoot = root;
|
|
155
219
|
let hasProjectRootArg = false;
|
|
156
220
|
let output;
|
|
157
221
|
let configuration = "Release";
|
|
222
|
+
let clean = false;
|
|
158
223
|
let exportOptionsPlist;
|
|
159
224
|
for (let index = 0; index < argv.length; index += 1) {
|
|
160
225
|
const arg = argv[index];
|
|
@@ -176,6 +241,10 @@ function parseBuildOptions(argv) {
|
|
|
176
241
|
configuration = arg.slice(16);
|
|
177
242
|
continue;
|
|
178
243
|
}
|
|
244
|
+
if (arg === "--clean") {
|
|
245
|
+
clean = true;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
179
248
|
if (arg === "--export-options-plist") {
|
|
180
249
|
exportOptionsPlist = argv[++index];
|
|
181
250
|
continue;
|
|
@@ -198,6 +267,7 @@ function parseBuildOptions(argv) {
|
|
|
198
267
|
projectRoot: resolvedProjectRoot,
|
|
199
268
|
output: path.resolve(resolvedProjectRoot, output ?? "dist"),
|
|
200
269
|
configuration,
|
|
270
|
+
clean,
|
|
201
271
|
exportOptionsPlist: exportOptionsPlist ? path.resolve(root, exportOptionsPlist) : void 0
|
|
202
272
|
};
|
|
203
273
|
}
|
|
@@ -250,6 +320,7 @@ function parseDoctorOptions(argv) {
|
|
|
250
320
|
function parseInternalMetroOptions(argv) {
|
|
251
321
|
let workspaceRoot;
|
|
252
322
|
let port = 8081;
|
|
323
|
+
let resetCache = false;
|
|
253
324
|
for (let index = 0; index < argv.length; index += 1) {
|
|
254
325
|
const arg = argv[index];
|
|
255
326
|
if (arg === "--workspace-root") {
|
|
@@ -264,13 +335,18 @@ function parseInternalMetroOptions(argv) {
|
|
|
264
335
|
port = Number(arg.slice(7));
|
|
265
336
|
continue;
|
|
266
337
|
}
|
|
338
|
+
if (arg === "--reset-cache") {
|
|
339
|
+
resetCache = true;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
267
342
|
throw new Error(`Unknown internal option: ${arg}`);
|
|
268
343
|
}
|
|
269
344
|
if (!workspaceRoot) throw new Error("Missing --workspace-root");
|
|
270
345
|
if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid port: ${port}`);
|
|
271
346
|
return {
|
|
272
347
|
workspaceRoot,
|
|
273
|
-
port
|
|
348
|
+
port,
|
|
349
|
+
resetCache
|
|
274
350
|
};
|
|
275
351
|
}
|
|
276
352
|
function isBleamProjectRoot(projectRoot) {
|
|
@@ -310,6 +386,11 @@ function assertStringField(value, field) {
|
|
|
310
386
|
if (typeof value !== "string" || value.trim() === "") throw new Error(`app.config.ts must define ${field}`);
|
|
311
387
|
return value;
|
|
312
388
|
}
|
|
389
|
+
function normalizeAppGroups(value, bundleIdentifier) {
|
|
390
|
+
const configured = Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
391
|
+
if (configured.length > 0) return configured;
|
|
392
|
+
return bundleIdentifier ? [`group.${bundleIdentifier}`] : [];
|
|
393
|
+
}
|
|
313
394
|
function isHexColor(value) {
|
|
314
395
|
return typeof value === "string" && /^#[\da-f]{6}$/i.test(value);
|
|
315
396
|
}
|
|
@@ -353,11 +434,13 @@ function loadAppConfig(projectRoot) {
|
|
|
353
434
|
});
|
|
354
435
|
if (result.status !== 0) throw new Error(result.stderr.trim() || "Failed to load app.config.ts");
|
|
355
436
|
const value = JSON.parse(result.stdout);
|
|
437
|
+
const bundleIdentifier = typeof value.bundleIdentifier === "string" ? value.bundleIdentifier : void 0;
|
|
356
438
|
return {
|
|
357
439
|
name: assertStringField(value.name, "name"),
|
|
358
440
|
version: typeof value.version === "string" ? value.version : void 0,
|
|
359
441
|
buildNumber: typeof value.buildNumber === "string" ? value.buildNumber : void 0,
|
|
360
|
-
bundleIdentifier
|
|
442
|
+
bundleIdentifier,
|
|
443
|
+
appGroups: normalizeAppGroups(value.appGroups, bundleIdentifier),
|
|
361
444
|
teamId: typeof value.teamId === "string" ? value.teamId : void 0,
|
|
362
445
|
appearance: normalizeAppearance(value.appearance)
|
|
363
446
|
};
|
|
@@ -380,6 +463,7 @@ function readProjectConfig(options) {
|
|
|
380
463
|
version: config.version,
|
|
381
464
|
buildNumber: config.buildNumber ?? "1",
|
|
382
465
|
bundleIdentifier: config.bundleIdentifier,
|
|
466
|
+
appGroups: config.appGroups ?? [],
|
|
383
467
|
teamId: config.teamId,
|
|
384
468
|
appearance: config.appearance ?? normalizeAppearance(void 0),
|
|
385
469
|
runtimeVersion,
|
|
@@ -397,22 +481,40 @@ function nameFromDirectory(projectRoot) {
|
|
|
397
481
|
function singleQuotedString(value) {
|
|
398
482
|
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n")}'`;
|
|
399
483
|
}
|
|
400
|
-
function
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
484
|
+
function updateStarterAppName(projectRoot, name) {
|
|
485
|
+
const appPath = path.join(projectRoot, "app", "index.tsx");
|
|
486
|
+
const source = readFileSync(appPath, "utf8");
|
|
487
|
+
const placeholder = `const appName = ${singleQuotedString(starterAppName)}`;
|
|
488
|
+
if (!source.includes(placeholder)) throw new Error(`Missing starter app name placeholder: ${appPath}`);
|
|
489
|
+
writeFileSync(appPath, source.replace(placeholder, `const appName = ${singleQuotedString(name)}`));
|
|
490
|
+
}
|
|
491
|
+
function createProject(options) {
|
|
492
|
+
const template = options.template ?? "basic";
|
|
493
|
+
const templateRoot = path.join(packageRoot, "templates", template);
|
|
494
|
+
if (!existsSync(templateRoot)) throw new Error(`Missing ${template} template: ${templateRoot}`);
|
|
495
|
+
if (existsSync(options.projectRoot)) throw new Error(`Destination already exists: ${options.projectRoot}`);
|
|
406
496
|
const name = nameFromDirectory(options.projectRoot);
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
name
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
497
|
+
mkdirSync(options.projectRoot, { recursive: true });
|
|
498
|
+
try {
|
|
499
|
+
cpSync(templateRoot, options.projectRoot, { recursive: true });
|
|
500
|
+
updateStarterAppName(options.projectRoot, name);
|
|
501
|
+
writeFileSync(path.join(options.projectRoot, "app.config.ts"), `import { appConfig } from 'bleam'\n\nexport default appConfig({\n name: ${singleQuotedString(name)},\n})\n`);
|
|
502
|
+
writeFileSync(path.join(options.projectRoot, ".gitignore"), `.bleam\nbiome.json\n.DS_Store\n`);
|
|
503
|
+
createDevWorkspace(options.projectRoot, {
|
|
504
|
+
name,
|
|
505
|
+
buildNumber: "1",
|
|
506
|
+
appGroups: [],
|
|
507
|
+
appearance: normalizeAppearance(void 0),
|
|
508
|
+
runtimeVersion,
|
|
509
|
+
sdkVersion: runtimeVersion
|
|
510
|
+
});
|
|
511
|
+
} catch (error) {
|
|
512
|
+
rmSync(options.projectRoot, {
|
|
513
|
+
recursive: true,
|
|
514
|
+
force: true
|
|
515
|
+
});
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
416
518
|
console.log(`Created Bleam project at ${options.projectRoot}`);
|
|
417
519
|
}
|
|
418
520
|
function createDevWorkspace(projectRoot, config) {
|
|
@@ -426,7 +528,7 @@ function createDevWorkspace(projectRoot, config) {
|
|
|
426
528
|
});
|
|
427
529
|
symlinkSync(dependencyRoot, workspaceNodeModules);
|
|
428
530
|
ensureProjectTsconfig(projectRoot);
|
|
429
|
-
|
|
531
|
+
const expoConfig = {
|
|
430
532
|
name: config.name,
|
|
431
533
|
slug: slugFromName(config.name),
|
|
432
534
|
version: config.version ?? "0.0.0",
|
|
@@ -439,7 +541,10 @@ function createDevWorkspace(projectRoot, config) {
|
|
|
439
541
|
runtimeVersion: config.runtimeVersion,
|
|
440
542
|
sdkVersion: config.sdkVersion
|
|
441
543
|
} }
|
|
442
|
-
}
|
|
544
|
+
};
|
|
545
|
+
if (existsSync(path.join(projectRoot, "icon.png"))) expoConfig.icon = "../../icon.png";
|
|
546
|
+
if (existsSync(path.join(projectRoot, "splash.png"))) expoConfig.plugins = [["expo-splash-screen", { image: "../../splash.png" }]];
|
|
547
|
+
writeFileSync(path.join(workspaceRoot, "app.config.js"), `module.exports = ${JSON.stringify({ expo: expoConfig }, null, 2)}\n`);
|
|
443
548
|
writeFileSync(path.join(workspaceRoot, "index.js"), `import { registerApp } from 'bleam/app'\nimport App from ${JSON.stringify(path.join(projectRoot, "app"))}\n\nregisterApp(App)\n`);
|
|
444
549
|
writeFileSync(path.join(workspaceRoot, "metro.config.js"), `const { getDefaultConfig } = require(${JSON.stringify(path.join(packageRoot, "metro-config.cjs"))})\n\nmodule.exports = getDefaultConfig(__dirname)\n`);
|
|
445
550
|
writeFileSync(path.join(workspaceRoot, "package.json"), `${JSON.stringify({
|
|
@@ -451,6 +556,7 @@ function createDevWorkspace(projectRoot, config) {
|
|
|
451
556
|
"@types/react": packageJson.dependencies?.["@types/react"],
|
|
452
557
|
"@biomejs/biome": packageJson.dependencies?.["@biomejs/biome"],
|
|
453
558
|
expo: packageJson.dependencies?.expo,
|
|
559
|
+
"expo-splash-screen": packageJson.dependencies?.["expo-splash-screen"],
|
|
454
560
|
react: packageJson.dependencies?.react,
|
|
455
561
|
"react-native": packageJson.dependencies?.["react-native"],
|
|
456
562
|
typescript: packageJson.dependencies?.typescript
|
|
@@ -535,6 +641,24 @@ function replacePlistString(plist, key, value) {
|
|
|
535
641
|
if (!pattern.test(plist)) return plist;
|
|
536
642
|
return plist.replace(pattern, `$1${plistString(value)}$3`);
|
|
537
643
|
}
|
|
644
|
+
function plistStringArray(values) {
|
|
645
|
+
return values.map((value) => `\t\t<string>${plistString(value)}</string>`).join("\n");
|
|
646
|
+
}
|
|
647
|
+
function replaceOrInsertPlistString(plist, key, value) {
|
|
648
|
+
const replaced = replacePlistString(plist, key, value);
|
|
649
|
+
if (replaced !== plist) return replaced;
|
|
650
|
+
const closingDictionary = plist.lastIndexOf("</dict>");
|
|
651
|
+
if (closingDictionary < 0) return plist;
|
|
652
|
+
return `${plist.slice(0, closingDictionary)}\t<key>${key}</key>\n\t<string>${plistString(value)}</string>\n${plist.slice(closingDictionary)}`;
|
|
653
|
+
}
|
|
654
|
+
function replaceOrInsertPlistStringArray(plist, key, values) {
|
|
655
|
+
const pattern = /* @__PURE__ */ new RegExp(`(<key>${key}</key>\\s*<array>)([\\s\\S]*?)(</array>)`);
|
|
656
|
+
const replacement = `$1\n${plistStringArray(values)}\n\t$3`;
|
|
657
|
+
if (pattern.test(plist)) return plist.replace(pattern, replacement);
|
|
658
|
+
const closingDictionary = plist.lastIndexOf("</dict>");
|
|
659
|
+
if (closingDictionary < 0) return plist;
|
|
660
|
+
return `${plist.slice(0, closingDictionary)}\t<key>${key}</key>\n\t<array>\n${plistStringArray(values)}\n\t</array>\n${plist.slice(closingDictionary)}`;
|
|
661
|
+
}
|
|
538
662
|
function writeGeneratedAppJson(nativeRoot, config) {
|
|
539
663
|
writeFileSync(path.join(nativeRoot, "app.json"), `${JSON.stringify({ expo: {
|
|
540
664
|
name: config.name,
|
|
@@ -551,40 +675,60 @@ function writeGeneratedAppJson(nativeRoot, config) {
|
|
|
551
675
|
function patchNativeProject(nativeRoot, config) {
|
|
552
676
|
const projectPath = path.join(nativeRoot, "ios", "Bleam.xcodeproj", "project.pbxproj");
|
|
553
677
|
const infoPlistPath = path.join(nativeRoot, "ios", "Bleam", "Info.plist");
|
|
678
|
+
const generationInfoPlistPath = path.join(nativeRoot, "ios", "GenerationService", "Info.plist");
|
|
679
|
+
const platformInfoPlistPath = path.join(nativeRoot, "ios", "PlatformHelper", "Info.plist");
|
|
680
|
+
const appEntitlementsPath = path.join(nativeRoot, "ios", "Bleam", "bleam.entitlements");
|
|
681
|
+
const generationEntitlementsPath = path.join(nativeRoot, "ios", "GenerationService", "GenerationService.entitlements");
|
|
554
682
|
const productName = xcodeSettingValue(config.name);
|
|
555
683
|
const bundleIdentifier = xcodeSettingValue(config.bundleIdentifier ?? "");
|
|
684
|
+
const generationBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.GenerationService`);
|
|
685
|
+
const platformBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.PlatformHelper`);
|
|
556
686
|
const teamId = xcodeSettingValue(config.teamId ?? "");
|
|
557
687
|
const version = xcodeSettingValue(config.version ?? "1.0.0");
|
|
558
688
|
const buildNumber = xcodeSettingValue(config.buildNumber);
|
|
689
|
+
const primaryAppGroup = config.appGroups[0];
|
|
559
690
|
let project = readFileSync(projectPath, "utf8");
|
|
560
|
-
project = project.replace(/PRODUCT_BUNDLE_IDENTIFIER = [^;]
|
|
561
|
-
return `PRODUCT_BUNDLE_IDENTIFIER = ${bundleIdentifier};`;
|
|
691
|
+
project = project.replace(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+);/g, (_, current) => {
|
|
692
|
+
return `PRODUCT_BUNDLE_IDENTIFIER = ${current.endsWith(".GenerationService") ? generationBundleIdentifier : current.endsWith(".PlatformHelper") ? platformBundleIdentifier : bundleIdentifier};`;
|
|
562
693
|
}).replace(/DEVELOPMENT_TEAM = [^;]+;/g, () => {
|
|
563
694
|
return `DEVELOPMENT_TEAM = ${teamId};`;
|
|
564
695
|
}).replace(/MARKETING_VERSION = [^;]+;/g, () => {
|
|
565
696
|
return `MARKETING_VERSION = ${version};`;
|
|
566
697
|
}).replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, () => {
|
|
567
698
|
return `CURRENT_PROJECT_VERSION = ${buildNumber};`;
|
|
568
|
-
}).replace(/PRODUCT_NAME =
|
|
699
|
+
}).replace(/PRODUCT_NAME = bleam;/g, () => {
|
|
569
700
|
return `PRODUCT_NAME = ${productName};`;
|
|
570
701
|
});
|
|
571
702
|
writeFileSync(projectPath, project);
|
|
572
703
|
let infoPlist = readFileSync(infoPlistPath, "utf8");
|
|
573
704
|
infoPlist = replacePlistString(infoPlist, "CFBundleDisplayName", config.name);
|
|
574
705
|
infoPlist = replacePlistString(infoPlist, "CFBundleShortVersionString", config.version ?? "1.0.0");
|
|
706
|
+
infoPlist = replaceOrInsertPlistString(infoPlist, "BleamRuntimeVersion", config.runtimeVersion);
|
|
707
|
+
infoPlist = replaceOrInsertPlistString(infoPlist, "BleamTeamIdentifier", config.teamId ?? "");
|
|
708
|
+
const updateServiceUrl = process.env.BLEAM_UPDATE_SERVICE_URL;
|
|
709
|
+
if (updateServiceUrl) infoPlist = replaceOrInsertPlistString(infoPlist, "BleamUpdateServiceURL", updateServiceUrl);
|
|
575
710
|
infoPlist = replacePlistString(infoPlist, "CFBundleVersion", config.buildNumber);
|
|
711
|
+
if (primaryAppGroup) infoPlist = replaceOrInsertPlistString(infoPlist, "AppGroupIdentifier", primaryAppGroup);
|
|
576
712
|
writeFileSync(infoPlistPath, infoPlist);
|
|
713
|
+
if (primaryAppGroup) {
|
|
714
|
+
let generationInfoPlist = readFileSync(generationInfoPlistPath, "utf8");
|
|
715
|
+
generationInfoPlist = replaceOrInsertPlistString(generationInfoPlist, "AppGroupIdentifier", primaryAppGroup);
|
|
716
|
+
writeFileSync(generationInfoPlistPath, generationInfoPlist);
|
|
717
|
+
writeFileSync(platformInfoPlistPath, replaceOrInsertPlistString(readFileSync(platformInfoPlistPath, "utf8"), "BleamPlatformVersion", config.runtimeVersion));
|
|
718
|
+
for (const entitlementsPath of [appEntitlementsPath, generationEntitlementsPath]) writeFileSync(entitlementsPath, replaceOrInsertPlistStringArray(readFileSync(entitlementsPath, "utf8"), "com.apple.security.application-groups", config.appGroups));
|
|
719
|
+
}
|
|
577
720
|
}
|
|
578
721
|
function createBuildNativeWorkspace(options, config) {
|
|
579
722
|
const nativeRoot = path.join(options.projectRoot, ".bleam", "build", "native");
|
|
580
723
|
const templateRoot = nativeRuntimeTemplateRoot();
|
|
581
|
-
rmSync(nativeRoot, {
|
|
724
|
+
if (options.clean) rmSync(nativeRoot, {
|
|
582
725
|
recursive: true,
|
|
583
726
|
force: true
|
|
584
727
|
});
|
|
585
728
|
mkdirSync(path.dirname(nativeRoot), { recursive: true });
|
|
586
729
|
cpSync(templateRoot, nativeRoot, {
|
|
587
730
|
recursive: true,
|
|
731
|
+
verbatimSymlinks: true,
|
|
588
732
|
filter(source) {
|
|
589
733
|
return !path.relative(templateRoot, source).split(path.sep).some((part) => {
|
|
590
734
|
return part === "build" || part === "dist" || part === "node_modules" || part === ".expo" || part === "Pods" || part === ".xcode.env.local" || part === ".DS_Store" || part.endsWith(".xcworkspace");
|
|
@@ -624,32 +768,66 @@ function runInheritedCommand(command, args, cwd) {
|
|
|
624
768
|
if (result.status !== 0) throw new Error(`${text} failed in ${cwd} with exit code ${result.status ?? "unknown"}.\nNative command output above may contain the underlying Xcode/CocoaPods error.\n${doctorHint()}`);
|
|
625
769
|
}
|
|
626
770
|
function installPods(nativeRoot) {
|
|
771
|
+
const iosRoot = path.join(nativeRoot, "ios");
|
|
772
|
+
const expectedStamp = podInstallInputStamp(iosRoot);
|
|
773
|
+
if (hasCurrentPodInstall(iosRoot, expectedStamp)) {
|
|
774
|
+
console.log("Using cached native dependencies");
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
627
777
|
console.log("Installing native dependencies...");
|
|
628
|
-
runInheritedCommand("pod", ["install"],
|
|
778
|
+
runInheritedCommand("pod", ["install"], iosRoot);
|
|
779
|
+
writeFileSync(podInstallStampPath(iosRoot), `${expectedStamp}\n`);
|
|
780
|
+
}
|
|
781
|
+
function podInstallInputStamp(iosRoot) {
|
|
782
|
+
const inputHash = createHash("sha256");
|
|
783
|
+
for (const relative of [
|
|
784
|
+
"Podfile",
|
|
785
|
+
"Podfile.lock",
|
|
786
|
+
"../package.json"
|
|
787
|
+
]) {
|
|
788
|
+
const input = path.resolve(iosRoot, relative);
|
|
789
|
+
if (existsSync(input)) inputHash.update(readFileSync(input));
|
|
790
|
+
}
|
|
791
|
+
return inputHash.digest("hex");
|
|
629
792
|
}
|
|
630
|
-
function
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
runInheritedCommand("xcodebuild", [
|
|
793
|
+
function podInstallStampPath(iosRoot) {
|
|
794
|
+
return path.join(iosRoot, ".bleam-pods-input");
|
|
795
|
+
}
|
|
796
|
+
function hasCurrentPodInstall(iosRoot, expectedStamp) {
|
|
797
|
+
const stampPath = podInstallStampPath(iosRoot);
|
|
798
|
+
return existsSync(path.join(iosRoot, "Bleam.xcworkspace")) && existsSync(path.join(iosRoot, "Pods", "Manifest.lock")) && existsSync(stampPath) && readFileSync(stampPath, "utf8").trim() === expectedStamp;
|
|
799
|
+
}
|
|
800
|
+
function nativeArchiveArgs(options) {
|
|
801
|
+
return [
|
|
640
802
|
"-allowProvisioningUpdates",
|
|
641
803
|
"-workspace",
|
|
642
804
|
path.join(options.nativeRoot, "ios", "Bleam.xcworkspace"),
|
|
643
805
|
"-scheme",
|
|
644
|
-
"
|
|
806
|
+
"Bleam",
|
|
645
807
|
"-configuration",
|
|
646
808
|
options.configuration,
|
|
647
809
|
"-destination",
|
|
648
810
|
"platform=macOS,arch=arm64,variant=Mac Catalyst",
|
|
649
811
|
"-archivePath",
|
|
650
|
-
archivePath,
|
|
812
|
+
options.archivePath,
|
|
813
|
+
"ARCHS=arm64",
|
|
651
814
|
"archive"
|
|
652
|
-
]
|
|
815
|
+
];
|
|
816
|
+
}
|
|
817
|
+
function archiveNativeApp(options) {
|
|
818
|
+
const platformOutput = path.join(options.outputRoot, "macos");
|
|
819
|
+
const archivePath = path.join(platformOutput, `${safeArtifactName(options.config.name)}.xcarchive`);
|
|
820
|
+
mkdirSync(platformOutput, { recursive: true });
|
|
821
|
+
rmSync(archivePath, {
|
|
822
|
+
recursive: true,
|
|
823
|
+
force: true
|
|
824
|
+
});
|
|
825
|
+
console.log(`Archiving ${options.config.name} for macOS...`);
|
|
826
|
+
runInheritedCommand("xcodebuild", nativeArchiveArgs({
|
|
827
|
+
nativeRoot: options.nativeRoot,
|
|
828
|
+
archivePath,
|
|
829
|
+
configuration: options.configuration
|
|
830
|
+
}), options.nativeRoot);
|
|
653
831
|
return archivePath;
|
|
654
832
|
}
|
|
655
833
|
function exportNativeArchive(options) {
|
|
@@ -692,6 +870,177 @@ async function buildNativeArchives(options) {
|
|
|
692
870
|
});
|
|
693
871
|
console.log(`Exported ${exportPath}`);
|
|
694
872
|
}
|
|
873
|
+
return archivePath;
|
|
874
|
+
}
|
|
875
|
+
async function installNativeApp(options) {
|
|
876
|
+
const destination = installArchivedApp(await buildNativeArchives(options));
|
|
877
|
+
console.log(`Installed ${destination}`);
|
|
878
|
+
}
|
|
879
|
+
function installArchivedApp(archivePath, applications = path.join(os.homedir(), "Applications")) {
|
|
880
|
+
const products = path.join(archivePath, "Products", "Applications");
|
|
881
|
+
const appName = readdirSync(products).find((name) => name.endsWith(".app"));
|
|
882
|
+
if (!appName) throw new Error(`Archive does not contain a macOS app: ${archivePath}`);
|
|
883
|
+
const source = path.join(products, appName);
|
|
884
|
+
const destination = applications === path.join(os.homedir(), "Applications") ? installedAppPath(appName) : path.join(applications, appName);
|
|
885
|
+
mkdirSync(applications, { recursive: true });
|
|
886
|
+
rmSync(destination, {
|
|
887
|
+
recursive: true,
|
|
888
|
+
force: true
|
|
889
|
+
});
|
|
890
|
+
execFileSync("ditto", [source, destination], { stdio: "inherit" });
|
|
891
|
+
return destination;
|
|
892
|
+
}
|
|
893
|
+
function installedAppPath(appName) {
|
|
894
|
+
if (path.basename(appName) !== appName || !appName.endsWith(".app")) throw new Error(`Invalid macOS app name: ${appName}`);
|
|
895
|
+
return path.join(os.homedir(), "Applications", appName);
|
|
896
|
+
}
|
|
897
|
+
async function publishOta(options) {
|
|
898
|
+
const config = readProjectConfig(options);
|
|
899
|
+
if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
|
|
900
|
+
if (!config.teamId) throw missingBuildFieldError("teamId");
|
|
901
|
+
const workspaceRoot = createBuildNativeWorkspace({
|
|
902
|
+
projectRoot: options.projectRoot,
|
|
903
|
+
output: path.join(options.projectRoot, "dist"),
|
|
904
|
+
configuration: "Release",
|
|
905
|
+
clean: false
|
|
906
|
+
}, config);
|
|
907
|
+
const outputRoot = path.join(options.projectRoot, ".bleam", "publish", "ota");
|
|
908
|
+
const bundleRoot = path.join(outputRoot, "bundle");
|
|
909
|
+
const bundlePath = path.join(bundleRoot, "bundle.js");
|
|
910
|
+
const archivePath = path.join(outputRoot, "ota.zip");
|
|
911
|
+
rmSync(outputRoot, {
|
|
912
|
+
recursive: true,
|
|
913
|
+
force: true
|
|
914
|
+
});
|
|
915
|
+
mkdirSync(bundleRoot, { recursive: true });
|
|
916
|
+
console.log(`Bundling OTA update for runtime ${runtimeVersion}...`);
|
|
917
|
+
runInheritedCommand(process.execPath, [
|
|
918
|
+
require.resolve("@expo/cli"),
|
|
919
|
+
"export:embed",
|
|
920
|
+
"--entry-file",
|
|
921
|
+
path.join(workspaceRoot, "index.ts"),
|
|
922
|
+
"--platform",
|
|
923
|
+
"ios",
|
|
924
|
+
"--dev",
|
|
925
|
+
"false",
|
|
926
|
+
"--bundle-output",
|
|
927
|
+
bundlePath,
|
|
928
|
+
"--assets-dest",
|
|
929
|
+
path.join(bundleRoot, "assets"),
|
|
930
|
+
"--minify",
|
|
931
|
+
"true"
|
|
932
|
+
], workspaceRoot);
|
|
933
|
+
runInheritedCommand("ditto", [
|
|
934
|
+
"-c",
|
|
935
|
+
"-k",
|
|
936
|
+
"--keepParent",
|
|
937
|
+
bundleRoot,
|
|
938
|
+
archivePath
|
|
939
|
+
], outputRoot);
|
|
940
|
+
const sizeBytes = readFileSync(archivePath).byteLength;
|
|
941
|
+
const digest = sha256(archivePath);
|
|
942
|
+
const releaseId = `ota_${Date.now().toString(36)}_${digest.slice(0, 12)}`;
|
|
943
|
+
const release = {
|
|
944
|
+
schemaVersion: 1,
|
|
945
|
+
kind: "ota",
|
|
946
|
+
bundleIdentifier: config.bundleIdentifier,
|
|
947
|
+
teamIdentifier: config.teamId,
|
|
948
|
+
channel: "stable",
|
|
949
|
+
releaseId,
|
|
950
|
+
bundleVersion: `${config.version ?? "1.0.0"}+${config.buildNumber}`,
|
|
951
|
+
runtimeVersion,
|
|
952
|
+
artifact: {
|
|
953
|
+
format: "bleam-ota-zip-v1",
|
|
954
|
+
downloadId: releaseId,
|
|
955
|
+
sizeBytes,
|
|
956
|
+
sha256: digest
|
|
957
|
+
},
|
|
958
|
+
publishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
959
|
+
};
|
|
960
|
+
const response = await fetch(`${options.serviceUrl}/v1/local/releases/ota`, {
|
|
961
|
+
method: "POST",
|
|
962
|
+
headers: { "content-type": "application/json" },
|
|
963
|
+
body: JSON.stringify({
|
|
964
|
+
release,
|
|
965
|
+
artifactBase64: readFileSync(archivePath).toString("base64")
|
|
966
|
+
})
|
|
967
|
+
});
|
|
968
|
+
if (!response.ok) throw new Error(`Local OTA publication failed: ${response.status} ${await response.text()}`);
|
|
969
|
+
console.log(`Published OTA update ${releaseId}`);
|
|
970
|
+
console.log(`${options.serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier)}/ota/${runtimeVersion}`);
|
|
971
|
+
}
|
|
972
|
+
async function publishPlatform(options) {
|
|
973
|
+
const config = readProjectConfig(options);
|
|
974
|
+
if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
|
|
975
|
+
if (!config.teamId) throw missingBuildFieldError("teamId");
|
|
976
|
+
const archivePath = await buildNativeArchives({
|
|
977
|
+
projectRoot: options.projectRoot,
|
|
978
|
+
output: path.join(options.projectRoot, "dist"),
|
|
979
|
+
configuration: "Release",
|
|
980
|
+
clean: false
|
|
981
|
+
});
|
|
982
|
+
const products = path.join(archivePath, "Products", "Applications");
|
|
983
|
+
const appName = readdirSync(products).find((name) => name.endsWith(".app"));
|
|
984
|
+
if (!appName) throw new Error(`Archive does not contain a macOS app: ${archivePath}`);
|
|
985
|
+
const outputRoot = path.join(options.projectRoot, ".bleam", "publish", "platform");
|
|
986
|
+
const stagingRoot = path.join(outputRoot, "app");
|
|
987
|
+
const artifactPath = path.join(outputRoot, "platform.zip");
|
|
988
|
+
rmSync(outputRoot, {
|
|
989
|
+
recursive: true,
|
|
990
|
+
force: true
|
|
991
|
+
});
|
|
992
|
+
mkdirSync(stagingRoot, { recursive: true });
|
|
993
|
+
execFileSync("ditto", [path.join(products, appName), path.join(stagingRoot, appName)]);
|
|
994
|
+
runInheritedCommand("zip", [
|
|
995
|
+
"-q",
|
|
996
|
+
"-r",
|
|
997
|
+
"-y",
|
|
998
|
+
artifactPath,
|
|
999
|
+
"app"
|
|
1000
|
+
], outputRoot);
|
|
1001
|
+
const sizeBytes = readFileSync(artifactPath).byteLength;
|
|
1002
|
+
const digest = sha256(artifactPath);
|
|
1003
|
+
const releaseId = `platform_${Date.now().toString(36)}_${digest.slice(0, 12)}`;
|
|
1004
|
+
const release = platformRelease(config, releaseId, sizeBytes, digest);
|
|
1005
|
+
const response = await fetch(`${options.serviceUrl}/v1/local/releases/platform`, {
|
|
1006
|
+
method: "POST",
|
|
1007
|
+
headers: { "content-type": "application/json" },
|
|
1008
|
+
body: JSON.stringify({
|
|
1009
|
+
release,
|
|
1010
|
+
artifactBase64: readFileSync(artifactPath).toString("base64")
|
|
1011
|
+
})
|
|
1012
|
+
});
|
|
1013
|
+
if (!response.ok) throw new Error(`Local platform publication failed: ${response.status} ${await response.text()}`);
|
|
1014
|
+
console.log(`Published platform update ${releaseId}`);
|
|
1015
|
+
console.log(`${options.serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier)}/platform/macos-arm64`);
|
|
1016
|
+
}
|
|
1017
|
+
function platformRelease(config, releaseId, sizeBytes, digest) {
|
|
1018
|
+
if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
|
|
1019
|
+
if (!config.teamId) throw missingBuildFieldError("teamId");
|
|
1020
|
+
return {
|
|
1021
|
+
schemaVersion: 1,
|
|
1022
|
+
kind: "platform",
|
|
1023
|
+
bundleIdentifier: config.bundleIdentifier,
|
|
1024
|
+
teamIdentifier: config.teamId,
|
|
1025
|
+
channel: "stable",
|
|
1026
|
+
releaseId,
|
|
1027
|
+
appVersion: config.version ?? "1.0.0",
|
|
1028
|
+
buildNumber: config.buildNumber,
|
|
1029
|
+
bleamPlatformVersion: runtimeVersion,
|
|
1030
|
+
target: {
|
|
1031
|
+
os: "macos",
|
|
1032
|
+
architecture: "arm64",
|
|
1033
|
+
installLocation: "user-applications",
|
|
1034
|
+
minimumMacOSVersion: "14.0"
|
|
1035
|
+
},
|
|
1036
|
+
artifact: {
|
|
1037
|
+
format: "bleam-macos-app-zip-v1",
|
|
1038
|
+
downloadId: releaseId,
|
|
1039
|
+
sizeBytes,
|
|
1040
|
+
sha256: digest
|
|
1041
|
+
},
|
|
1042
|
+
publishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1043
|
+
};
|
|
695
1044
|
}
|
|
696
1045
|
function runtimeCacheRoot() {
|
|
697
1046
|
return path.join(os.homedir(), "Library", "Caches", "bleam", "runtimes", runtimeVersion, artifactPlatform());
|
|
@@ -742,16 +1091,22 @@ async function downloadRuntimeArtifact() {
|
|
|
742
1091
|
const runtimePlatform = artifactPlatform();
|
|
743
1092
|
const cacheRoot = runtimeCacheRoot();
|
|
744
1093
|
const cachedAppPath = path.join(cacheRoot, "Bleam.app");
|
|
745
|
-
if (existsSync(cachedAppPath))
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1094
|
+
if (existsSync(cachedAppPath)) {
|
|
1095
|
+
console.log(`Using cached Bleam runtime at ${prettyPath(cachedAppPath)}`);
|
|
1096
|
+
return {
|
|
1097
|
+
appPath: cachedAppPath,
|
|
1098
|
+
version: runtimeVersion,
|
|
1099
|
+
platform: runtimePlatform
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
750
1102
|
mkdirSync(cacheRoot, { recursive: true });
|
|
751
1103
|
const archiveUrl = runtimeArchiveUrl();
|
|
752
1104
|
const archivePath = path.join(cacheRoot, runtimeArchiveName());
|
|
753
1105
|
const extractRoot = path.join(cacheRoot, "extract");
|
|
1106
|
+
console.log(`Downloading Bleam runtime ${runtimeVersion} for ${runtimePlatform}`);
|
|
1107
|
+
console.log(`Runtime archive: ${archiveUrl}`);
|
|
754
1108
|
await downloadFile(archiveUrl, archivePath);
|
|
1109
|
+
console.log("Verifying Bleam runtime archive");
|
|
755
1110
|
const expectedSha256 = await downloadExpectedSha256(archiveUrl);
|
|
756
1111
|
if (expectedSha256 && sha256(archivePath) !== expectedSha256) {
|
|
757
1112
|
rmSync(archivePath, { force: true });
|
|
@@ -762,6 +1117,7 @@ async function downloadRuntimeArtifact() {
|
|
|
762
1117
|
force: true
|
|
763
1118
|
});
|
|
764
1119
|
mkdirSync(extractRoot, { recursive: true });
|
|
1120
|
+
console.log("Extracting Bleam runtime archive");
|
|
765
1121
|
execFileSync("ditto", [
|
|
766
1122
|
"-x",
|
|
767
1123
|
"-k",
|
|
@@ -774,11 +1130,15 @@ async function downloadRuntimeArtifact() {
|
|
|
774
1130
|
recursive: true,
|
|
775
1131
|
force: true
|
|
776
1132
|
});
|
|
777
|
-
cpSync(extractedAppPath, cachedAppPath, {
|
|
1133
|
+
cpSync(extractedAppPath, cachedAppPath, {
|
|
1134
|
+
recursive: true,
|
|
1135
|
+
verbatimSymlinks: true
|
|
1136
|
+
});
|
|
778
1137
|
rmSync(extractRoot, {
|
|
779
1138
|
recursive: true,
|
|
780
1139
|
force: true
|
|
781
1140
|
});
|
|
1141
|
+
console.log(`Cached Bleam runtime at ${prettyPath(cachedAppPath)}`);
|
|
782
1142
|
return {
|
|
783
1143
|
appPath: cachedAppPath,
|
|
784
1144
|
version: runtimeVersion,
|
|
@@ -787,10 +1147,12 @@ async function downloadRuntimeArtifact() {
|
|
|
787
1147
|
}
|
|
788
1148
|
async function resolveRuntimeArtifact() {
|
|
789
1149
|
const runtimePlatform = artifactPlatform();
|
|
1150
|
+
console.log(`Resolving Bleam runtime ${runtimeVersion} for ${runtimePlatform}`);
|
|
790
1151
|
const overrideAppPath = process.env.BLEAM_RUNTIME_APP;
|
|
791
1152
|
if (overrideAppPath) {
|
|
792
1153
|
const appPath = path.resolve(overrideAppPath);
|
|
793
1154
|
if (!existsSync(appPath)) throw new Error(`BLEAM_RUNTIME_APP points to a missing app bundle: ${appPath}\nUnset BLEAM_RUNTIME_APP or point it to a valid Bleam.app.\n${doctorHint()}`);
|
|
1155
|
+
console.log(`Using BLEAM_RUNTIME_APP runtime at ${prettyPath(appPath)}`);
|
|
794
1156
|
return {
|
|
795
1157
|
appPath,
|
|
796
1158
|
version: runtimeVersion,
|
|
@@ -798,11 +1160,14 @@ async function resolveRuntimeArtifact() {
|
|
|
798
1160
|
};
|
|
799
1161
|
}
|
|
800
1162
|
const localAppPath = localDevelopmentRuntimeAppPath();
|
|
801
|
-
if (existsSync(localAppPath))
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1163
|
+
if (existsSync(localAppPath)) {
|
|
1164
|
+
console.log(`Using local development runtime at ${prettyPath(localAppPath)}`);
|
|
1165
|
+
return {
|
|
1166
|
+
appPath: localAppPath,
|
|
1167
|
+
version: runtimeVersion,
|
|
1168
|
+
platform: runtimePlatform
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
806
1171
|
try {
|
|
807
1172
|
return await downloadRuntimeArtifact();
|
|
808
1173
|
} catch (error) {
|
|
@@ -811,14 +1176,31 @@ async function resolveRuntimeArtifact() {
|
|
|
811
1176
|
}
|
|
812
1177
|
function copyRuntimeClient(projectRoot, runtime) {
|
|
813
1178
|
const clientAppPath = path.join(projectRoot, ".bleam", "client", "Bleam.app");
|
|
1179
|
+
console.log(`Preparing Bleam app at ${prettyPath(clientAppPath)}`);
|
|
814
1180
|
rmSync(clientAppPath, {
|
|
815
1181
|
recursive: true,
|
|
816
1182
|
force: true
|
|
817
1183
|
});
|
|
818
1184
|
mkdirSync(path.dirname(clientAppPath), { recursive: true });
|
|
819
|
-
cpSync(runtime.appPath, clientAppPath, {
|
|
1185
|
+
cpSync(runtime.appPath, clientAppPath, {
|
|
1186
|
+
recursive: true,
|
|
1187
|
+
verbatimSymlinks: true
|
|
1188
|
+
});
|
|
820
1189
|
return clientAppPath;
|
|
821
1190
|
}
|
|
1191
|
+
function cleanDevArtifacts(projectRoot) {
|
|
1192
|
+
const bleamRoot = path.join(projectRoot, ".bleam");
|
|
1193
|
+
const targets = [
|
|
1194
|
+
"client",
|
|
1195
|
+
"dev",
|
|
1196
|
+
"project.json"
|
|
1197
|
+
].map((name) => path.join(bleamRoot, name));
|
|
1198
|
+
console.log("Cleaning Bleam dev artifacts");
|
|
1199
|
+
for (const target of targets) rmSync(target, {
|
|
1200
|
+
recursive: true,
|
|
1201
|
+
force: true
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
822
1204
|
function isBleamManifest(value) {
|
|
823
1205
|
const manifest = value && typeof value === "object" ? value : {};
|
|
824
1206
|
return typeof manifest.name === "string" && typeof manifest.bundleUrl === "string" && manifest.platform === "macos" && manifest.dev === true;
|
|
@@ -920,6 +1302,15 @@ function runDoctor(options) {
|
|
|
920
1302
|
function formatDuration(ms) {
|
|
921
1303
|
return `${(ms / 1e3).toFixed(1)}s`;
|
|
922
1304
|
}
|
|
1305
|
+
async function bundleErrorPreview(response) {
|
|
1306
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1307
|
+
if (!contentType.includes("json") && !contentType.includes("text")) return "";
|
|
1308
|
+
try {
|
|
1309
|
+
return (await response.text()).trim().slice(0, 1e3);
|
|
1310
|
+
} catch {
|
|
1311
|
+
return "";
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
923
1314
|
async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
|
|
924
1315
|
const startedAt = Date.now();
|
|
925
1316
|
const requestTimeoutMs = 3e4;
|
|
@@ -931,14 +1322,17 @@ async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
|
|
|
931
1322
|
cache: "no-store",
|
|
932
1323
|
signal: AbortSignal.timeout(requestTimeoutMs)
|
|
933
1324
|
});
|
|
934
|
-
await response.body?.cancel();
|
|
935
1325
|
if (response.ok) {
|
|
1326
|
+
await response.body?.cancel();
|
|
936
1327
|
spinner?.success(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
|
|
937
1328
|
if (!spinner) console.log(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
|
|
938
1329
|
return;
|
|
939
1330
|
}
|
|
940
|
-
|
|
941
|
-
|
|
1331
|
+
const preview = await bundleErrorPreview(response);
|
|
1332
|
+
const detail = preview ? `\n${preview}` : "";
|
|
1333
|
+
const message = `Bundle request returned ${response.status}, retrying...${detail}`;
|
|
1334
|
+
if (spinner) spinner.text = message;
|
|
1335
|
+
else console.log(message);
|
|
942
1336
|
} catch (error) {
|
|
943
1337
|
if (error instanceof Error && error.name === "TimeoutError") {
|
|
944
1338
|
const message = `Bundle request timed out after ${formatDuration(Date.now() - requestStartedAt)}, retrying...`;
|
|
@@ -974,7 +1368,11 @@ function openRuntimeClient(appPath, projectConfigPath, manifest) {
|
|
|
974
1368
|
if (code && code !== 0) console.error(`Failed to open Bleam runtime at ${appPath}`);
|
|
975
1369
|
});
|
|
976
1370
|
}
|
|
1371
|
+
function quitRuntimeClient() {
|
|
1372
|
+
spawnSync("osascript", ["-e", "tell application id \"dev.bleam.app\" to quit"], { stdio: "ignore" });
|
|
1373
|
+
}
|
|
977
1374
|
async function launchRuntimeClient(appPath, projectConfigPath, manifest) {
|
|
1375
|
+
quitRuntimeClient();
|
|
978
1376
|
openRuntimeClient(appPath, projectConfigPath, manifest);
|
|
979
1377
|
}
|
|
980
1378
|
function focusRuntimeClient() {
|
|
@@ -987,6 +1385,49 @@ function sendJson(res, status, value) {
|
|
|
987
1385
|
});
|
|
988
1386
|
res.end(JSON.stringify(value));
|
|
989
1387
|
}
|
|
1388
|
+
function readRequestBody(req, limit = 1e6) {
|
|
1389
|
+
return new Promise((resolve, reject) => {
|
|
1390
|
+
const chunks = [];
|
|
1391
|
+
let size = 0;
|
|
1392
|
+
req.on("data", (chunk) => {
|
|
1393
|
+
size += chunk.length;
|
|
1394
|
+
if (size > limit) {
|
|
1395
|
+
reject(/* @__PURE__ */ new Error("Request body too large"));
|
|
1396
|
+
req.destroy();
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
chunks.push(chunk);
|
|
1400
|
+
});
|
|
1401
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
1402
|
+
req.on("error", reject);
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
function isLogLevel(value) {
|
|
1406
|
+
return value === "debug" || value === "info" || value === "warn" || value === "error";
|
|
1407
|
+
}
|
|
1408
|
+
function isLogSource(value) {
|
|
1409
|
+
return value === "cli" || value === "metro" || value === "runtime" || value === "js" || value === "native" || value === "xpc";
|
|
1410
|
+
}
|
|
1411
|
+
function normalizeDevLogEvent(value) {
|
|
1412
|
+
const input = value && typeof value === "object" ? value : {};
|
|
1413
|
+
const message = typeof input.message === "string" ? input.message : input.message === void 0 ? "" : JSON.stringify(input.message);
|
|
1414
|
+
return {
|
|
1415
|
+
type: "log",
|
|
1416
|
+
id: typeof input.id === "string" && input.id ? input.id : randomUUID(),
|
|
1417
|
+
time: typeof input.time === "string" && input.time ? input.time : (/* @__PURE__ */ new Date()).toISOString(),
|
|
1418
|
+
source: isLogSource(input.source) ? input.source : "runtime",
|
|
1419
|
+
level: isLogLevel(input.level) ? input.level : "info",
|
|
1420
|
+
category: typeof input.category === "string" && input.category ? input.category : void 0,
|
|
1421
|
+
message,
|
|
1422
|
+
details: input.details,
|
|
1423
|
+
jobId: typeof input.jobId === "string" && input.jobId ? input.jobId : void 0
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
function formatDevLogEvent(event) {
|
|
1427
|
+
const category = event.category ? `:${event.category}` : "";
|
|
1428
|
+
const job = event.jobId ? ` ${event.jobId.slice(0, 8)}` : "";
|
|
1429
|
+
return `[${event.source}${category}:${event.level}${job}] ${event.message}`;
|
|
1430
|
+
}
|
|
990
1431
|
async function requestRuntimeReload(origin) {
|
|
991
1432
|
const response = await fetch(`${origin}/bleam/reload`, {
|
|
992
1433
|
method: "POST",
|
|
@@ -1005,6 +1446,13 @@ function biomeBinPath() {
|
|
|
1005
1446
|
if (!bin) throw new Error("Could not resolve @biomejs/biome binary");
|
|
1006
1447
|
return path.join(biomePackageRoot, bin);
|
|
1007
1448
|
}
|
|
1449
|
+
function typescriptBinPath() {
|
|
1450
|
+
const typescriptPackageJsonPath = require.resolve("typescript/package.json");
|
|
1451
|
+
const typescriptPackageRoot = path.dirname(typescriptPackageJsonPath);
|
|
1452
|
+
const bin = require("typescript/package.json").bin?.tsc;
|
|
1453
|
+
if (!bin) throw new Error("Could not resolve TypeScript compiler binary");
|
|
1454
|
+
return path.join(typescriptPackageRoot, bin);
|
|
1455
|
+
}
|
|
1008
1456
|
function ensureProjectToolWorkspace(options) {
|
|
1009
1457
|
const config = readProjectConfig({ projectRoot: options.projectRoot });
|
|
1010
1458
|
return createDevWorkspace(options.projectRoot, config);
|
|
@@ -1032,6 +1480,20 @@ function runBiomeCommand(command, options) {
|
|
|
1032
1480
|
if (result.error) throw result.error;
|
|
1033
1481
|
process.exit(result.status ?? 1);
|
|
1034
1482
|
}
|
|
1483
|
+
function runTypecheckCommand(options) {
|
|
1484
|
+
assertBleamConfigFiles(options.projectRoot);
|
|
1485
|
+
ensureProjectToolWorkspace(options);
|
|
1486
|
+
const result = spawnSync(typescriptBinPath(), [
|
|
1487
|
+
"--noEmit",
|
|
1488
|
+
"-p",
|
|
1489
|
+
path.join(options.projectRoot, "tsconfig.json")
|
|
1490
|
+
], {
|
|
1491
|
+
cwd: options.projectRoot,
|
|
1492
|
+
stdio: "inherit"
|
|
1493
|
+
});
|
|
1494
|
+
if (result.error) throw result.error;
|
|
1495
|
+
process.exit(result.status ?? 1);
|
|
1496
|
+
}
|
|
1035
1497
|
function setupInteractiveControls(options) {
|
|
1036
1498
|
if (!process.stdin.isTTY) return () => {};
|
|
1037
1499
|
const wasRaw = process.stdin.isRaw;
|
|
@@ -1053,21 +1515,26 @@ function setupInteractiveControls(options) {
|
|
|
1053
1515
|
if (key === "o") {
|
|
1054
1516
|
(async () => {
|
|
1055
1517
|
try {
|
|
1056
|
-
if (
|
|
1057
|
-
console.log("
|
|
1058
|
-
|
|
1518
|
+
if (focusRuntimeClient()) {
|
|
1519
|
+
console.log("Focused Bleam app");
|
|
1520
|
+
return;
|
|
1059
1521
|
}
|
|
1522
|
+
console.log(`Opening Bleam app at ${prettyPath(options.clientAppPath)}`);
|
|
1523
|
+
await launchRuntimeClient(options.clientAppPath, options.projectConfigPath, options.manifest);
|
|
1060
1524
|
} catch (error) {
|
|
1061
1525
|
console.error(error instanceof Error ? error.message : String(error));
|
|
1062
1526
|
}
|
|
1063
1527
|
})();
|
|
1064
1528
|
continue;
|
|
1065
1529
|
}
|
|
1066
|
-
if (key === "r")
|
|
1067
|
-
console.log(
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1530
|
+
if (key === "r") {
|
|
1531
|
+
console.log("Requesting app reload");
|
|
1532
|
+
requestRuntimeReload(options.origin).then((clients) => {
|
|
1533
|
+
console.log(`Reloaded app (${clients} client${clients === 1 ? "" : "s"})`);
|
|
1534
|
+
}).catch((error) => {
|
|
1535
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1071
1538
|
}
|
|
1072
1539
|
};
|
|
1073
1540
|
process.stdin.setRawMode(true);
|
|
@@ -1078,12 +1545,18 @@ function setupInteractiveControls(options) {
|
|
|
1078
1545
|
}
|
|
1079
1546
|
async function startInternalMetroServer(options) {
|
|
1080
1547
|
process.env.BLEAM_DEV_PLATFORM ??= "macos";
|
|
1548
|
+
const projectRoot = process.env.BLEAM_PROJECT_ROOT ?? options.workspaceRoot;
|
|
1549
|
+
const logPath = path.join(projectRoot, ".bleam", "dev", "logs", "dev.log");
|
|
1550
|
+
const recentLogs = [];
|
|
1551
|
+
const logClients = /* @__PURE__ */ new Set();
|
|
1552
|
+
const maxRecentLogs = 1e3;
|
|
1081
1553
|
const metro = require("@expo/metro/metro");
|
|
1082
1554
|
const { loadConfig } = require("@expo/metro/metro-config");
|
|
1083
1555
|
const config = await loadConfig({
|
|
1084
1556
|
config: path.join(options.workspaceRoot, "metro.config.js"),
|
|
1085
1557
|
cwd: options.workspaceRoot,
|
|
1086
|
-
port: options.port
|
|
1558
|
+
port: options.port,
|
|
1559
|
+
resetCache: options.resetCache
|
|
1087
1560
|
});
|
|
1088
1561
|
const { middleware, end, attachHmrServer } = await metro.createConnectMiddleware(config, {
|
|
1089
1562
|
watch: true,
|
|
@@ -1091,6 +1564,16 @@ async function startInternalMetroServer(options) {
|
|
|
1091
1564
|
});
|
|
1092
1565
|
const messageClients = /* @__PURE__ */ new Set();
|
|
1093
1566
|
const messageServer = new WebSocketServer({ noServer: true });
|
|
1567
|
+
const logServer = new WebSocketServer({ noServer: true });
|
|
1568
|
+
const recordLog = (event) => {
|
|
1569
|
+
recentLogs.push(event);
|
|
1570
|
+
if (recentLogs.length > maxRecentLogs) recentLogs.splice(0, recentLogs.length - maxRecentLogs);
|
|
1571
|
+
mkdirSync(path.dirname(logPath), { recursive: true });
|
|
1572
|
+
appendFileSync(logPath, `${JSON.stringify(event)}\n`, "utf8");
|
|
1573
|
+
const payload = JSON.stringify(event);
|
|
1574
|
+
for (const client of logClients) if (client.readyState === WebSocket.OPEN) client.send(payload);
|
|
1575
|
+
console.log(formatDevLogEvent(event));
|
|
1576
|
+
};
|
|
1094
1577
|
const broadcastReload = () => {
|
|
1095
1578
|
const message = JSON.stringify({
|
|
1096
1579
|
version: 2,
|
|
@@ -1108,6 +1591,11 @@ async function startInternalMetroServer(options) {
|
|
|
1108
1591
|
messageClients.add(client);
|
|
1109
1592
|
client.on("close", () => messageClients.delete(client));
|
|
1110
1593
|
});
|
|
1594
|
+
logServer.on("connection", (client) => {
|
|
1595
|
+
logClients.add(client);
|
|
1596
|
+
for (const event of recentLogs) client.send(JSON.stringify(event));
|
|
1597
|
+
client.on("close", () => logClients.delete(client));
|
|
1598
|
+
});
|
|
1111
1599
|
const server = http.createServer((req, res) => {
|
|
1112
1600
|
if (req.url?.split("?")[0] === "/status") {
|
|
1113
1601
|
res.writeHead(200, {
|
|
@@ -1130,6 +1618,29 @@ async function startInternalMetroServer(options) {
|
|
|
1130
1618
|
});
|
|
1131
1619
|
return;
|
|
1132
1620
|
}
|
|
1621
|
+
if (req.url?.split("?")[0] === "/bleam/logs") {
|
|
1622
|
+
if (req.method === "GET") {
|
|
1623
|
+
sendJson(res, 200, { logs: recentLogs });
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
if (req.method !== "POST") {
|
|
1627
|
+
res.writeHead(405, { Allow: "GET, POST" });
|
|
1628
|
+
res.end();
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
readRequestBody(req).then((body) => {
|
|
1632
|
+
const parsed = body ? JSON.parse(body) : {};
|
|
1633
|
+
const events = Array.isArray(parsed) ? parsed : [parsed];
|
|
1634
|
+
for (const value of events) recordLog(normalizeDevLogEvent(value));
|
|
1635
|
+
sendJson(res, 200, { ok: true });
|
|
1636
|
+
}).catch((error) => {
|
|
1637
|
+
sendJson(res, 400, {
|
|
1638
|
+
ok: false,
|
|
1639
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1640
|
+
});
|
|
1641
|
+
});
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1133
1644
|
middleware(req, res, (error) => {
|
|
1134
1645
|
if (res.headersSent) return;
|
|
1135
1646
|
if (error) {
|
|
@@ -1159,6 +1670,12 @@ async function startInternalMetroServer(options) {
|
|
|
1159
1670
|
});
|
|
1160
1671
|
return;
|
|
1161
1672
|
}
|
|
1673
|
+
if (req.url?.split("?")[0] === "/bleam/logs/stream") {
|
|
1674
|
+
logServer.handleUpgrade(req, socket, head, (client) => {
|
|
1675
|
+
logServer.emit("connection", client, req);
|
|
1676
|
+
});
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1162
1679
|
for (const listener of metroUpgradeListeners) listener.call(server, req, socket, head);
|
|
1163
1680
|
});
|
|
1164
1681
|
await new Promise((resolve, reject) => {
|
|
@@ -1172,7 +1689,9 @@ async function startInternalMetroServer(options) {
|
|
|
1172
1689
|
const close = () => {
|
|
1173
1690
|
setTimeout(() => process.exit(0), 1e3).unref();
|
|
1174
1691
|
messageServer.close();
|
|
1692
|
+
logServer.close();
|
|
1175
1693
|
for (const client of messageClients) client.terminate();
|
|
1694
|
+
for (const client of logClients) client.terminate();
|
|
1176
1695
|
server.close(() => {
|
|
1177
1696
|
end();
|
|
1178
1697
|
process.exit(0);
|
|
@@ -1183,8 +1702,10 @@ async function startInternalMetroServer(options) {
|
|
|
1183
1702
|
}
|
|
1184
1703
|
async function startDevServer(options) {
|
|
1185
1704
|
const config = readProjectConfig(options);
|
|
1705
|
+
if (options.clean) cleanDevArtifacts(options.projectRoot);
|
|
1186
1706
|
const runtime = await resolveRuntimeArtifact();
|
|
1187
1707
|
const clientAppPath = copyRuntimeClient(options.projectRoot, runtime);
|
|
1708
|
+
console.log("Preparing Bleam dev workspace");
|
|
1188
1709
|
const workspaceRoot = createDevWorkspace(options.projectRoot, config);
|
|
1189
1710
|
const origin = `http://localhost:${options.port}`;
|
|
1190
1711
|
const projectConfigPath = writeProjectRuntimeConfig(options.projectRoot, config, origin);
|
|
@@ -1194,7 +1715,8 @@ async function startDevServer(options) {
|
|
|
1194
1715
|
"--workspace-root",
|
|
1195
1716
|
workspaceRoot,
|
|
1196
1717
|
"--port",
|
|
1197
|
-
String(options.port)
|
|
1718
|
+
String(options.port),
|
|
1719
|
+
...options.clean ? ["--reset-cache"] : []
|
|
1198
1720
|
], {
|
|
1199
1721
|
stdio: "inherit",
|
|
1200
1722
|
env: {
|
|
@@ -1218,24 +1740,32 @@ async function startDevServer(options) {
|
|
|
1218
1740
|
}
|
|
1219
1741
|
});
|
|
1220
1742
|
let cleanupInteractiveControls = () => {};
|
|
1743
|
+
let didLaunchRuntimeClient = false;
|
|
1744
|
+
let didCleanupDevSession = false;
|
|
1221
1745
|
let stopping = false;
|
|
1746
|
+
const cleanupDevSession = () => {
|
|
1747
|
+
if (didCleanupDevSession) return;
|
|
1748
|
+
didCleanupDevSession = true;
|
|
1749
|
+
cleanupInteractiveControls();
|
|
1750
|
+
if (didLaunchRuntimeClient) quitRuntimeClient();
|
|
1751
|
+
};
|
|
1222
1752
|
const stop = (signal) => {
|
|
1223
1753
|
if (stopping) return;
|
|
1224
1754
|
stopping = true;
|
|
1225
|
-
|
|
1755
|
+
cleanupDevSession();
|
|
1226
1756
|
child.kill(signal);
|
|
1227
1757
|
setTimeout(() => process.exit(0), 2e3).unref();
|
|
1228
1758
|
};
|
|
1229
1759
|
process.on("SIGINT", () => stop("SIGINT"));
|
|
1230
1760
|
process.on("SIGTERM", () => stop("SIGTERM"));
|
|
1231
1761
|
child.on("error", (error) => {
|
|
1232
|
-
|
|
1762
|
+
cleanupDevSession();
|
|
1233
1763
|
console.error(`Bleam dev server process failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1234
1764
|
console.error(doctorHint(options.projectRoot));
|
|
1235
1765
|
process.exit(1);
|
|
1236
1766
|
});
|
|
1237
1767
|
child.on("exit", (code) => {
|
|
1238
|
-
|
|
1768
|
+
cleanupDevSession();
|
|
1239
1769
|
process.exit(code ?? 0);
|
|
1240
1770
|
});
|
|
1241
1771
|
try {
|
|
@@ -1249,8 +1779,9 @@ async function startDevServer(options) {
|
|
|
1249
1779
|
});
|
|
1250
1780
|
if (options.run && process.env.BLEAM_NO_OPEN !== "1") try {
|
|
1251
1781
|
await waitForDevBundle(manifest.bundleUrl);
|
|
1252
|
-
console.log(
|
|
1782
|
+
console.log(`Opening Bleam app at ${prettyPath(clientAppPath)}`);
|
|
1253
1783
|
await launchRuntimeClient(clientAppPath, projectConfigPath, manifest);
|
|
1784
|
+
didLaunchRuntimeClient = true;
|
|
1254
1785
|
} catch (error) {
|
|
1255
1786
|
console.error(`Initial app launch failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1256
1787
|
console.error(doctorHint(options.projectRoot));
|
|
@@ -1258,7 +1789,7 @@ async function startDevServer(options) {
|
|
|
1258
1789
|
console.error("Bleam dev server is still running.");
|
|
1259
1790
|
}
|
|
1260
1791
|
} catch (error) {
|
|
1261
|
-
|
|
1792
|
+
cleanupDevSession();
|
|
1262
1793
|
child.kill("SIGTERM");
|
|
1263
1794
|
throw error;
|
|
1264
1795
|
}
|
|
@@ -1271,11 +1802,12 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1271
1802
|
}
|
|
1272
1803
|
if (command === "new") {
|
|
1273
1804
|
const options = parseNewOptions(args);
|
|
1274
|
-
|
|
1275
|
-
|
|
1805
|
+
createProject(options);
|
|
1806
|
+
await startDevServer({
|
|
1276
1807
|
projectRoot: options.projectRoot,
|
|
1277
1808
|
port: 8081,
|
|
1278
|
-
run: true
|
|
1809
|
+
run: true,
|
|
1810
|
+
clean: false
|
|
1279
1811
|
});
|
|
1280
1812
|
return;
|
|
1281
1813
|
}
|
|
@@ -1283,14 +1815,34 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1283
1815
|
await startDevServer(parseDevOptions(args));
|
|
1284
1816
|
return;
|
|
1285
1817
|
}
|
|
1818
|
+
if (command === "install") {
|
|
1819
|
+
await installNativeApp(parseBuildOptions(args));
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1286
1822
|
if (command === "build") {
|
|
1287
1823
|
await buildNativeArchives(parseBuildOptions(args));
|
|
1288
1824
|
return;
|
|
1289
1825
|
}
|
|
1826
|
+
if (command === "publish") {
|
|
1827
|
+
const [kind, ...publishArgs] = args;
|
|
1828
|
+
if (kind === "ota") {
|
|
1829
|
+
await publishOta(parsePublishOtaOptions(publishArgs));
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
if (kind === "platform") {
|
|
1833
|
+
await publishPlatform(parsePublishOtaOptions(publishArgs));
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
throw new Error("Usage: bleam publish <ota|platform> [dir] [--service-url <url>]");
|
|
1837
|
+
}
|
|
1290
1838
|
if (command === "doctor") {
|
|
1291
1839
|
runDoctor(parseDoctorOptions(args));
|
|
1292
1840
|
return;
|
|
1293
1841
|
}
|
|
1842
|
+
if (command === "typecheck") {
|
|
1843
|
+
runTypecheckCommand(parseProjectToolOptions(args));
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1294
1846
|
if (command === "check") {
|
|
1295
1847
|
runBiomeCommand("check", parseProjectToolOptions(args));
|
|
1296
1848
|
return;
|
|
@@ -1309,10 +1861,24 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1309
1861
|
}
|
|
1310
1862
|
throw new Error(`Unknown command: ${command}`);
|
|
1311
1863
|
}
|
|
1312
|
-
main().catch((error) => {
|
|
1864
|
+
if (process.env.VITEST !== "true") main().catch((error) => {
|
|
1313
1865
|
console.error(error instanceof Error ? error.message : String(error));
|
|
1314
1866
|
process.exit(1);
|
|
1315
1867
|
});
|
|
1868
|
+
const __test = {
|
|
1869
|
+
createProject,
|
|
1870
|
+
createBuildNativeWorkspace,
|
|
1871
|
+
hasCurrentPodInstall,
|
|
1872
|
+
installedAppPath,
|
|
1873
|
+
installArchivedApp,
|
|
1874
|
+
nativeArchiveArgs,
|
|
1875
|
+
parseBuildOptions,
|
|
1876
|
+
parseNewOptions,
|
|
1877
|
+
parsePublishOtaOptions,
|
|
1878
|
+
platformRelease,
|
|
1879
|
+
podInstallInputStamp,
|
|
1880
|
+
replaceOrInsertPlistString
|
|
1881
|
+
};
|
|
1316
1882
|
|
|
1317
1883
|
//#endregion
|
|
1318
|
-
export {
|
|
1884
|
+
export { __test };
|