bleam 0.0.4 → 0.0.6
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 +187 -0
- package/dist/ai.d.cts +52 -0
- package/dist/ai.d.ts +52 -0
- package/dist/ai.js +182 -0
- package/dist/animated.cjs +286 -0
- package/dist/animated.d.cts +47 -0
- package/dist/animated.d.ts +47 -0
- package/dist/animated.js +281 -0
- package/dist/app.d.cts +1 -2
- package/dist/app.d.ts +1 -2
- package/dist/app.js +1 -2
- package/dist/bundler.d.cts +1 -2
- package/dist/bundler.d.ts +1 -2
- package/dist/chrome.cjs +265 -0
- package/dist/chrome.d.cts +19 -0
- package/dist/chrome.d.ts +19 -0
- package/dist/chrome.js +262 -0
- package/dist/cli.cjs +918 -68
- package/dist/cli.js +917 -71
- package/dist/config-Bb6SULnR.cjs +46 -0
- package/dist/config-CfWa3q-k.js +28 -0
- package/dist/config-ClDGHNb3.d.ts +39 -0
- package/dist/config-D3gPaVMb.d.cts +39 -0
- package/dist/config.cjs +4 -25
- package/dist/config.d.cts +2 -28
- package/dist/config.d.ts +2 -28
- package/dist/config.js +2 -24
- package/dist/crypto-BB92-Upx.js +7 -0
- package/dist/crypto-CT3VZ7lF.cjs +13 -0
- package/dist/crypto.cjs +2 -2
- package/dist/crypto.d.cts +8 -1
- package/dist/crypto.d.ts +8 -1
- package/dist/crypto.js +2 -2
- package/dist/dev-protocol.d.cts +3 -3
- package/dist/dev-protocol.d.ts +3 -3
- package/dist/dev-protocol.js +1 -2
- package/dist/fs.cjs +12 -2
- package/dist/fs.d.cts +7 -1
- package/dist/fs.d.ts +7 -1
- package/dist/fs.js +10 -1
- package/dist/index.cjs +4 -5
- package/dist/index.d.cts +2 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -4
- package/dist/native-macos-D6p2SmUj.js +20 -0
- package/dist/native-macos-DLwo_ii9.cjs +31 -0
- package/dist/native-runtime-C85Nuc4F.js +10 -0
- package/dist/native-runtime-CbPzrs8j.d.cts +33 -0
- package/dist/{native-runtime-CdeT0JyQ.cjs → native-runtime-CsXnXkQn.cjs} +1 -14
- package/dist/native-runtime-DxHSrsTX.d.ts +33 -0
- package/dist/native.cjs +35 -32
- package/dist/native.d.cts +4 -34
- package/dist/native.d.ts +3 -33
- package/dist/native.js +36 -34
- package/dist/runtime.d.cts +1 -2
- package/dist/runtime.d.ts +1 -2
- package/dist/schema-BOuUHcqo.d.cts +58 -0
- package/dist/schema-DVmoD9JL.js +90 -0
- package/dist/schema-bxeeCyLn.cjs +102 -0
- package/dist/schema-ipsECAso.d.ts +58 -0
- package/dist/schema.cjs +4 -0
- package/dist/schema.d.cts +2 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +3 -0
- package/dist/state.cjs +765 -0
- package/dist/state.d.cts +129 -0
- package/dist/state.d.ts +129 -0
- package/dist/state.js +747 -0
- package/dist/styles.cjs +742 -0
- package/dist/styles.d.cts +701 -0
- package/dist/styles.d.ts +701 -0
- package/dist/styles.js +739 -0
- package/dist/ui.cjs +113 -0
- package/dist/ui.d.cts +30 -0
- package/dist/ui.d.ts +30 -0
- package/dist/ui.js +110 -0
- package/metro-config.cjs +18 -13
- package/package.json +48 -13
- package/templates/basic/app/index.tsx +136 -0
- package/templates/basic/app.config.ts +4 -2
- package/dist/app.d.cts.map +0 -1
- package/dist/app.d.ts.map +0 -1
- package/dist/app.js.map +0 -1
- package/dist/bundler.d.cts.map +0 -1
- package/dist/bundler.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config.d.cts.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/crypto-BNhWeXMj.cjs +0 -15
- package/dist/crypto-Bg8cD8wX.d.cts +0 -8
- package/dist/crypto-Bg8cD8wX.d.cts.map +0 -1
- package/dist/crypto-CCA_hvao.js +0 -11
- package/dist/crypto-CCA_hvao.js.map +0 -1
- package/dist/crypto-IOzbNoJD.d.ts +0 -8
- package/dist/crypto-IOzbNoJD.d.ts.map +0 -1
- package/dist/dev-protocol.d.cts.map +0 -1
- package/dist/dev-protocol.d.ts.map +0 -1
- package/dist/dev-protocol.js.map +0 -1
- package/dist/fs-BK7joD1g.js +0 -13
- package/dist/fs-BK7joD1g.js.map +0 -1
- package/dist/fs-CEhcjuN6.d.ts +0 -9
- package/dist/fs-CEhcjuN6.d.ts.map +0 -1
- package/dist/fs-CfE8CHf_.d.cts +0 -9
- package/dist/fs-CfE8CHf_.d.cts.map +0 -1
- package/dist/fs-DKHYTPua.cjs +0 -18
- package/dist/native-runtime-BoqybBBf.js +0 -18
- package/dist/native-runtime-BoqybBBf.js.map +0 -1
- package/dist/native.d.cts.map +0 -1
- package/dist/native.d.ts.map +0 -1
- package/dist/native.js.map +0 -1
- package/dist/runtime.d.cts.map +0 -1
- package/dist/runtime.d.ts.map +0 -1
- package/templates/basic/app.tsx +0 -18
package/dist/cli.js
CHANGED
|
@@ -2,32 +2,100 @@
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { 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";
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
+
import { WebSocket, WebSocketServer } from "ws";
|
|
11
|
+
import yoctoSpinner from "yocto-spinner";
|
|
12
|
+
import { bold, dim, green, red, yellow } from "yoctocolors";
|
|
10
13
|
|
|
11
14
|
//#region src/cli.ts
|
|
12
15
|
const require = createRequire(import.meta.url);
|
|
13
16
|
const packageRoot = path.dirname(fileURLToPath(new URL("../package.json", import.meta.url)));
|
|
14
17
|
const packageJson = require("../package.json");
|
|
15
18
|
const runtimeVersion = packageJson.version;
|
|
16
|
-
|
|
19
|
+
function artifactPlatform() {
|
|
20
|
+
return `macos-${process.arch}`;
|
|
21
|
+
}
|
|
22
|
+
function prettyPath(value) {
|
|
23
|
+
const home = os.homedir();
|
|
24
|
+
return value === home || value.startsWith(`${home}${path.sep}`) ? `~${value.slice(home.length)}` : value;
|
|
25
|
+
}
|
|
26
|
+
function commandSucceeds(command, args) {
|
|
27
|
+
const result = spawnSync(command, args, { stdio: "ignore" });
|
|
28
|
+
return !result.error && result.status === 0;
|
|
29
|
+
}
|
|
30
|
+
function commandText(command, args) {
|
|
31
|
+
return [command, ...args].join(" ");
|
|
32
|
+
}
|
|
33
|
+
function doctorHint(projectRoot) {
|
|
34
|
+
return projectRoot ? `Run ${bold(`bleam doctor ${projectRoot}`)} for diagnostics.` : `Run ${bold("bleam doctor")} for diagnostics.`;
|
|
35
|
+
}
|
|
36
|
+
function appConfigBuildExample() {
|
|
37
|
+
return [
|
|
38
|
+
"Example:",
|
|
39
|
+
"import { appConfig } from 'bleam'",
|
|
40
|
+
"",
|
|
41
|
+
"export default appConfig({",
|
|
42
|
+
" name: 'My App',",
|
|
43
|
+
" bundleIdentifier: 'com.example.myapp',",
|
|
44
|
+
" teamId: 'ABCDE12345',",
|
|
45
|
+
"})"
|
|
46
|
+
].join("\n");
|
|
47
|
+
}
|
|
48
|
+
function missingBuildFieldError(field) {
|
|
49
|
+
return /* @__PURE__ */ new Error(`app.config.ts must define ${field} for bleam build.\n\n${appConfigBuildExample()}`);
|
|
50
|
+
}
|
|
51
|
+
function heading(text) {
|
|
52
|
+
return bold(text);
|
|
53
|
+
}
|
|
54
|
+
function muted(text) {
|
|
55
|
+
return dim(text);
|
|
56
|
+
}
|
|
57
|
+
function statusLabel(status) {
|
|
58
|
+
if (status === "pass") return green("[ok]");
|
|
59
|
+
if (status === "warn") return yellow("[warn]");
|
|
60
|
+
return red("[fail]");
|
|
61
|
+
}
|
|
62
|
+
function startSpinner(text) {
|
|
63
|
+
if (!process.stderr.isTTY) {
|
|
64
|
+
console.log(text);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
return yoctoSpinner({
|
|
68
|
+
text,
|
|
69
|
+
handleSignals: false
|
|
70
|
+
}).start();
|
|
71
|
+
}
|
|
17
72
|
function printUsage() {
|
|
18
73
|
console.log(`Usage: bleam <command>
|
|
19
74
|
|
|
20
75
|
Commands:
|
|
21
76
|
new [dir] [--run] Create a new Bleam project
|
|
22
|
-
dev [dir] [--port <port>]
|
|
77
|
+
dev [dir] [--port <port>] [--run]
|
|
78
|
+
Start a Bleam dev server
|
|
79
|
+
build [dir] [--output <dir>]
|
|
80
|
+
Build App Store-ready native archives
|
|
81
|
+
doctor [dir] Check the local Bleam environment
|
|
82
|
+
check [dir] [--write] Format, lint, and organize imports
|
|
83
|
+
format [dir] [--check] Format project files
|
|
84
|
+
lint [dir] Lint project files`);
|
|
23
85
|
}
|
|
24
86
|
function invocationRoot() {
|
|
25
|
-
|
|
87
|
+
const pwd = process.env.PWD;
|
|
88
|
+
return pwd && path.isAbsolute(pwd) ? pwd : process.cwd();
|
|
89
|
+
}
|
|
90
|
+
function canonicalPath(value) {
|
|
91
|
+
const resolved = path.resolve(value);
|
|
92
|
+
return existsSync(resolved) ? realpathSync.native(resolved) : resolved;
|
|
26
93
|
}
|
|
27
94
|
function parseDevOptions(argv) {
|
|
28
95
|
const root = invocationRoot();
|
|
29
96
|
let projectRoot = root;
|
|
30
97
|
let port = 8081;
|
|
98
|
+
let run = false;
|
|
31
99
|
let hasProjectRootArg = false;
|
|
32
100
|
for (let index = 0; index < argv.length; index += 1) {
|
|
33
101
|
const arg = argv[index];
|
|
@@ -40,6 +108,10 @@ function parseDevOptions(argv) {
|
|
|
40
108
|
port = Number(arg.slice(7));
|
|
41
109
|
continue;
|
|
42
110
|
}
|
|
111
|
+
if (arg === "--run") {
|
|
112
|
+
run = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
43
115
|
if (arg === "--help" || arg === "-h") {
|
|
44
116
|
printUsage();
|
|
45
117
|
process.exit(0);
|
|
@@ -51,8 +123,9 @@ function parseDevOptions(argv) {
|
|
|
51
123
|
if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid port: ${port}`);
|
|
52
124
|
if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
|
|
53
125
|
return {
|
|
54
|
-
projectRoot:
|
|
55
|
-
port
|
|
126
|
+
projectRoot: canonicalPath(projectRoot),
|
|
127
|
+
port,
|
|
128
|
+
run
|
|
56
129
|
};
|
|
57
130
|
}
|
|
58
131
|
function parseNewOptions(argv) {
|
|
@@ -72,12 +145,139 @@ function parseNewOptions(argv) {
|
|
|
72
145
|
projectRoot = path.resolve(root, arg);
|
|
73
146
|
}
|
|
74
147
|
return {
|
|
75
|
-
projectRoot,
|
|
148
|
+
projectRoot: canonicalPath(projectRoot),
|
|
76
149
|
run
|
|
77
150
|
};
|
|
78
151
|
}
|
|
152
|
+
function parseBuildOptions(argv) {
|
|
153
|
+
const root = invocationRoot();
|
|
154
|
+
let projectRoot = root;
|
|
155
|
+
let hasProjectRootArg = false;
|
|
156
|
+
let output;
|
|
157
|
+
let configuration = "Release";
|
|
158
|
+
let exportOptionsPlist;
|
|
159
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
160
|
+
const arg = argv[index];
|
|
161
|
+
if (arg === "--output" || arg === "-o") {
|
|
162
|
+
output = argv[++index];
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (arg.startsWith("--output=")) {
|
|
166
|
+
output = arg.slice(9);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (arg === "--configuration") {
|
|
170
|
+
const value = argv[++index];
|
|
171
|
+
if (!value) throw new Error("Missing --configuration value");
|
|
172
|
+
configuration = value;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (arg.startsWith("--configuration=")) {
|
|
176
|
+
configuration = arg.slice(16);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (arg === "--export-options-plist") {
|
|
180
|
+
exportOptionsPlist = argv[++index];
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (arg.startsWith("--export-options-plist=")) {
|
|
184
|
+
exportOptionsPlist = arg.slice(23);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (arg === "--help" || arg === "-h") {
|
|
188
|
+
printUsage();
|
|
189
|
+
process.exit(0);
|
|
190
|
+
}
|
|
191
|
+
if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
|
|
192
|
+
projectRoot = path.resolve(root, arg);
|
|
193
|
+
hasProjectRootArg = true;
|
|
194
|
+
}
|
|
195
|
+
if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
|
|
196
|
+
const resolvedProjectRoot = canonicalPath(projectRoot);
|
|
197
|
+
return {
|
|
198
|
+
projectRoot: resolvedProjectRoot,
|
|
199
|
+
output: path.resolve(resolvedProjectRoot, output ?? "dist"),
|
|
200
|
+
configuration,
|
|
201
|
+
exportOptionsPlist: exportOptionsPlist ? path.resolve(root, exportOptionsPlist) : void 0
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function parseProjectToolOptions(argv) {
|
|
205
|
+
const root = invocationRoot();
|
|
206
|
+
let projectRoot = root;
|
|
207
|
+
let hasProjectRootArg = false;
|
|
208
|
+
let write = false;
|
|
209
|
+
let formatCheck = false;
|
|
210
|
+
for (const arg of argv) {
|
|
211
|
+
if (arg === "--write") {
|
|
212
|
+
write = true;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (arg === "--check") {
|
|
216
|
+
formatCheck = true;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (arg === "--help" || arg === "-h") {
|
|
220
|
+
printUsage();
|
|
221
|
+
process.exit(0);
|
|
222
|
+
}
|
|
223
|
+
if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
|
|
224
|
+
projectRoot = path.resolve(root, arg);
|
|
225
|
+
hasProjectRootArg = true;
|
|
226
|
+
}
|
|
227
|
+
if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
|
|
228
|
+
return {
|
|
229
|
+
projectRoot: canonicalPath(projectRoot),
|
|
230
|
+
write,
|
|
231
|
+
formatCheck
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function parseDoctorOptions(argv) {
|
|
235
|
+
const root = invocationRoot();
|
|
236
|
+
let projectRoot = root;
|
|
237
|
+
let hasProjectRootArg = false;
|
|
238
|
+
for (const arg of argv) {
|
|
239
|
+
if (arg === "--help" || arg === "-h") {
|
|
240
|
+
printUsage();
|
|
241
|
+
process.exit(0);
|
|
242
|
+
}
|
|
243
|
+
if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
|
|
244
|
+
projectRoot = path.resolve(root, arg);
|
|
245
|
+
hasProjectRootArg = true;
|
|
246
|
+
}
|
|
247
|
+
if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
|
|
248
|
+
return { projectRoot: canonicalPath(projectRoot) };
|
|
249
|
+
}
|
|
250
|
+
function parseInternalMetroOptions(argv) {
|
|
251
|
+
let workspaceRoot;
|
|
252
|
+
let port = 8081;
|
|
253
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
254
|
+
const arg = argv[index];
|
|
255
|
+
if (arg === "--workspace-root") {
|
|
256
|
+
workspaceRoot = path.resolve(invocationRoot(), argv[++index] ?? "");
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (arg === "--port") {
|
|
260
|
+
port = Number(argv[++index]);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (arg.startsWith("--port=")) {
|
|
264
|
+
port = Number(arg.slice(7));
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
throw new Error(`Unknown internal option: ${arg}`);
|
|
268
|
+
}
|
|
269
|
+
if (!workspaceRoot) throw new Error("Missing --workspace-root");
|
|
270
|
+
if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid port: ${port}`);
|
|
271
|
+
return {
|
|
272
|
+
workspaceRoot,
|
|
273
|
+
port
|
|
274
|
+
};
|
|
275
|
+
}
|
|
79
276
|
function isBleamProjectRoot(projectRoot) {
|
|
80
|
-
return existsSync(path.join(projectRoot, "app.config.ts")) && existsSync(path.join(projectRoot, "app.tsx"));
|
|
277
|
+
return existsSync(path.join(projectRoot, "app.config.ts")) && existsSync(path.join(projectRoot, "app", "index.tsx"));
|
|
278
|
+
}
|
|
279
|
+
function resolvedDependencyRoot() {
|
|
280
|
+
return path.dirname(path.dirname(require.resolve("react-native/package.json")));
|
|
81
281
|
}
|
|
82
282
|
function discoverBleamProjectRoot(searchRoot) {
|
|
83
283
|
const ignoredNames = new Set([
|
|
@@ -110,6 +310,23 @@ function assertStringField(value, field) {
|
|
|
110
310
|
if (typeof value !== "string" || value.trim() === "") throw new Error(`app.config.ts must define ${field}`);
|
|
111
311
|
return value;
|
|
112
312
|
}
|
|
313
|
+
function isHexColor(value) {
|
|
314
|
+
return typeof value === "string" && /^#[\da-f]{6}$/i.test(value);
|
|
315
|
+
}
|
|
316
|
+
function normalizeAppearance(value) {
|
|
317
|
+
const input = value && typeof value === "object" ? value : {};
|
|
318
|
+
const window = input.window && typeof input.window === "object" ? input.window : {};
|
|
319
|
+
const appearance = {};
|
|
320
|
+
if (window.backgroundColor !== void 0) {
|
|
321
|
+
if (!isHexColor(window.backgroundColor)) throw new Error("app.config.ts appearance.window.backgroundColor must be a #rrggbb color");
|
|
322
|
+
appearance.window = { backgroundColor: window.backgroundColor };
|
|
323
|
+
}
|
|
324
|
+
if (input.userInterfaceStyle !== void 0) {
|
|
325
|
+
if (input.userInterfaceStyle !== "automatic" && input.userInterfaceStyle !== "light" && input.userInterfaceStyle !== "dark") throw new Error("app.config.ts appearance.userInterfaceStyle must be automatic, light, or dark");
|
|
326
|
+
appearance.userInterfaceStyle = input.userInterfaceStyle;
|
|
327
|
+
}
|
|
328
|
+
return appearance;
|
|
329
|
+
}
|
|
113
330
|
function loadAppConfig(projectRoot) {
|
|
114
331
|
const configPath = path.join(projectRoot, "app.config.ts");
|
|
115
332
|
const loader = `
|
|
@@ -129,7 +346,7 @@ function loadAppConfig(projectRoot) {
|
|
|
129
346
|
...process.env,
|
|
130
347
|
NODE_PATH: [
|
|
131
348
|
path.join(projectRoot, "node_modules"),
|
|
132
|
-
|
|
349
|
+
resolvedDependencyRoot(),
|
|
133
350
|
process.env.NODE_PATH
|
|
134
351
|
].filter(Boolean).join(path.delimiter)
|
|
135
352
|
}
|
|
@@ -139,14 +356,21 @@ function loadAppConfig(projectRoot) {
|
|
|
139
356
|
return {
|
|
140
357
|
name: assertStringField(value.name, "name"),
|
|
141
358
|
version: typeof value.version === "string" ? value.version : void 0,
|
|
359
|
+
buildNumber: typeof value.buildNumber === "string" ? value.buildNumber : void 0,
|
|
142
360
|
bundleIdentifier: typeof value.bundleIdentifier === "string" ? value.bundleIdentifier : void 0,
|
|
143
|
-
teamId: typeof value.teamId === "string" ? value.teamId : void 0
|
|
361
|
+
teamId: typeof value.teamId === "string" ? value.teamId : void 0,
|
|
362
|
+
appearance: normalizeAppearance(value.appearance)
|
|
144
363
|
};
|
|
145
364
|
}
|
|
146
365
|
function assertBleamConfigFiles(projectRoot) {
|
|
147
366
|
if (existsSync(path.join(projectRoot, "app.json"))) throw new Error("Bleam projects use app.config.ts; app.json is not supported.");
|
|
148
367
|
if (!existsSync(path.join(projectRoot, "app.config.ts"))) throw new Error("Missing app.config.ts. Bleam projects must define app.config.ts.");
|
|
149
|
-
if (!existsSync(path.join(projectRoot, "app.tsx"))) throw new Error("Missing app.tsx. Bleam projects must define app.tsx.");
|
|
368
|
+
if (!existsSync(path.join(projectRoot, "app", "index.tsx"))) throw new Error("Missing app/index.tsx. Bleam projects must define app/index.tsx.");
|
|
369
|
+
}
|
|
370
|
+
function ensureProjectTsconfig(projectRoot) {
|
|
371
|
+
const tsconfigPath = path.join(projectRoot, "tsconfig.json");
|
|
372
|
+
if (existsSync(tsconfigPath)) return;
|
|
373
|
+
writeFileSync(tsconfigPath, `${JSON.stringify({ extends: "./.bleam/dev/tsconfig.json" }, null, 2)}\n`);
|
|
150
374
|
}
|
|
151
375
|
function readProjectConfig(options) {
|
|
152
376
|
assertBleamConfigFiles(options.projectRoot);
|
|
@@ -154,8 +378,10 @@ function readProjectConfig(options) {
|
|
|
154
378
|
return {
|
|
155
379
|
name: config.name,
|
|
156
380
|
version: config.version,
|
|
381
|
+
buildNumber: config.buildNumber ?? "1",
|
|
157
382
|
bundleIdentifier: config.bundleIdentifier,
|
|
158
383
|
teamId: config.teamId,
|
|
384
|
+
appearance: config.appearance ?? normalizeAppearance(void 0),
|
|
159
385
|
runtimeVersion,
|
|
160
386
|
sdkVersion: runtimeVersion
|
|
161
387
|
};
|
|
@@ -168,27 +394,51 @@ function nameFromDirectory(projectRoot) {
|
|
|
168
394
|
if (!name) return "Bleam App";
|
|
169
395
|
return name.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
170
396
|
}
|
|
397
|
+
function singleQuotedString(value) {
|
|
398
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n")}'`;
|
|
399
|
+
}
|
|
171
400
|
function createNewProject(options) {
|
|
172
401
|
const templateRoot = path.join(packageRoot, "templates", "basic");
|
|
173
402
|
if (!existsSync(templateRoot)) throw new Error(`Missing basic template: ${templateRoot}`);
|
|
174
403
|
if (existsSync(options.projectRoot) && readdirSync(options.projectRoot).length > 0) throw new Error(`Directory is not empty: ${options.projectRoot}`);
|
|
175
404
|
mkdirSync(options.projectRoot, { recursive: true });
|
|
176
405
|
cpSync(templateRoot, options.projectRoot, { recursive: true });
|
|
177
|
-
|
|
406
|
+
const name = nameFromDirectory(options.projectRoot);
|
|
407
|
+
writeFileSync(path.join(options.projectRoot, "app.config.ts"), `import { appConfig } from 'bleam'\n\nexport default appConfig({\n name: ${singleQuotedString(name)},\n})\n`);
|
|
408
|
+
writeFileSync(path.join(options.projectRoot, ".gitignore"), `.bleam\nbiome.json\n.DS_Store\n`);
|
|
409
|
+
createDevWorkspace(options.projectRoot, {
|
|
410
|
+
name,
|
|
411
|
+
buildNumber: "1",
|
|
412
|
+
appearance: normalizeAppearance(void 0),
|
|
413
|
+
runtimeVersion,
|
|
414
|
+
sdkVersion: runtimeVersion
|
|
415
|
+
});
|
|
178
416
|
console.log(`Created Bleam project at ${options.projectRoot}`);
|
|
179
417
|
}
|
|
180
418
|
function createDevWorkspace(projectRoot, config) {
|
|
181
419
|
const workspaceRoot = path.join(projectRoot, ".bleam", "dev");
|
|
182
420
|
mkdirSync(workspaceRoot, { recursive: true });
|
|
183
421
|
const workspaceNodeModules = path.join(workspaceRoot, "node_modules");
|
|
184
|
-
|
|
422
|
+
const dependencyRoot = resolvedDependencyRoot();
|
|
423
|
+
rmSync(workspaceNodeModules, {
|
|
424
|
+
recursive: true,
|
|
425
|
+
force: true
|
|
426
|
+
});
|
|
427
|
+
symlinkSync(dependencyRoot, workspaceNodeModules);
|
|
428
|
+
ensureProjectTsconfig(projectRoot);
|
|
185
429
|
writeFileSync(path.join(workspaceRoot, "app.config.js"), `module.exports = ${JSON.stringify({ expo: {
|
|
186
430
|
name: config.name,
|
|
187
431
|
slug: slugFromName(config.name),
|
|
188
432
|
version: config.version ?? "0.0.0",
|
|
189
433
|
orientation: "default",
|
|
190
434
|
platforms: ["ios"],
|
|
191
|
-
newArchEnabled: true
|
|
435
|
+
newArchEnabled: true,
|
|
436
|
+
extra: { bleam: {
|
|
437
|
+
appearance: config.appearance,
|
|
438
|
+
buildNumber: config.buildNumber,
|
|
439
|
+
runtimeVersion: config.runtimeVersion,
|
|
440
|
+
sdkVersion: config.sdkVersion
|
|
441
|
+
} }
|
|
192
442
|
} }, null, 2)}\n`);
|
|
193
443
|
writeFileSync(path.join(workspaceRoot, "index.js"), `import { registerApp } from 'bleam/app'\nimport App from ${JSON.stringify(path.join(projectRoot, "app"))}\n\nregisterApp(App)\n`);
|
|
194
444
|
writeFileSync(path.join(workspaceRoot, "metro.config.js"), `const { getDefaultConfig } = require(${JSON.stringify(path.join(packageRoot, "metro-config.cjs"))})\n\nmodule.exports = getDefaultConfig(__dirname)\n`);
|
|
@@ -199,6 +449,7 @@ function createDevWorkspace(projectRoot, config) {
|
|
|
199
449
|
main: "index.js",
|
|
200
450
|
dependencies: {
|
|
201
451
|
"@types/react": packageJson.dependencies?.["@types/react"],
|
|
452
|
+
"@biomejs/biome": packageJson.dependencies?.["@biomejs/biome"],
|
|
202
453
|
expo: packageJson.dependencies?.expo,
|
|
203
454
|
react: packageJson.dependencies?.react,
|
|
204
455
|
"react-native": packageJson.dependencies?.["react-native"],
|
|
@@ -208,10 +459,49 @@ function createDevWorkspace(projectRoot, config) {
|
|
|
208
459
|
}, null, 2)}\n`);
|
|
209
460
|
writeFileSync(path.join(workspaceRoot, "tsconfig.json"), `${JSON.stringify({
|
|
210
461
|
extends: "bleam/tsconfig",
|
|
211
|
-
|
|
462
|
+
compilerOptions: { paths: {
|
|
463
|
+
bleam: ["./node_modules/bleam/dist/index"],
|
|
464
|
+
"bleam/*": ["./node_modules/bleam/dist/*"],
|
|
465
|
+
expo: ["./node_modules/expo"],
|
|
466
|
+
"expo/*": ["./node_modules/expo/*"],
|
|
467
|
+
react: ["./node_modules/@types/react"],
|
|
468
|
+
"react/jsx-runtime": ["./node_modules/@types/react/jsx-runtime"],
|
|
469
|
+
"react/jsx-dev-runtime": ["./node_modules/@types/react/jsx-dev-runtime"],
|
|
470
|
+
"react-native": ["./node_modules/react-native"],
|
|
471
|
+
"react-native/*": ["./node_modules/react-native/*"]
|
|
472
|
+
} },
|
|
473
|
+
include: [path.relative(workspaceRoot, path.join(projectRoot, "app")), path.relative(workspaceRoot, path.join(projectRoot, "app.config.ts"))]
|
|
212
474
|
}, null, 2)}\n`);
|
|
213
475
|
return workspaceRoot;
|
|
214
476
|
}
|
|
477
|
+
function writeBiomeConfig(configPath) {
|
|
478
|
+
writeFileSync(configPath, `${JSON.stringify({
|
|
479
|
+
$schema: "https://biomejs.dev/schemas/2.5.2/schema.json",
|
|
480
|
+
files: { includes: [
|
|
481
|
+
"app/**/*",
|
|
482
|
+
"app.config.ts",
|
|
483
|
+
"tsconfig.json"
|
|
484
|
+
] },
|
|
485
|
+
formatter: {
|
|
486
|
+
enabled: true,
|
|
487
|
+
indentStyle: "space",
|
|
488
|
+
indentWidth: 2,
|
|
489
|
+
lineWidth: 80
|
|
490
|
+
},
|
|
491
|
+
javascript: { formatter: {
|
|
492
|
+
quoteStyle: "single",
|
|
493
|
+
semicolons: "asNeeded",
|
|
494
|
+
trailingCommas: "all"
|
|
495
|
+
} },
|
|
496
|
+
linter: {
|
|
497
|
+
enabled: true,
|
|
498
|
+
rules: {
|
|
499
|
+
preset: "recommended",
|
|
500
|
+
suspicious: { noExplicitAny: "off" }
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}, null, 2)}\n`);
|
|
504
|
+
}
|
|
215
505
|
function writeProjectRuntimeConfig(projectRoot, config, origin) {
|
|
216
506
|
const projectConfigPath = path.join(projectRoot, ".bleam", "project.json");
|
|
217
507
|
mkdirSync(path.dirname(projectConfigPath), { recursive: true });
|
|
@@ -220,28 +510,220 @@ function writeProjectRuntimeConfig(projectRoot, config, origin) {
|
|
|
220
510
|
version: config.version,
|
|
221
511
|
bundleIdentifier: config.bundleIdentifier,
|
|
222
512
|
teamId: config.teamId,
|
|
513
|
+
appearance: config.appearance,
|
|
223
514
|
devServer: origin,
|
|
224
515
|
runtimeVersion: config.runtimeVersion,
|
|
225
516
|
sdkVersion: config.sdkVersion
|
|
226
517
|
}, null, 2)}\n`);
|
|
227
518
|
return projectConfigPath;
|
|
228
519
|
}
|
|
520
|
+
function nativeRuntimeTemplateRoot() {
|
|
521
|
+
return path.resolve(packageRoot, "..", "..", "app", "bleam");
|
|
522
|
+
}
|
|
523
|
+
function safeArtifactName(name) {
|
|
524
|
+
return name.trim().replace(/[/:\\]/g, "-").replace(/\s+/g, " ") || "App";
|
|
525
|
+
}
|
|
526
|
+
function xcodeSettingValue(value) {
|
|
527
|
+
if (/^[A-Za-z0-9_.$-]+$/.test(value)) return value;
|
|
528
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
529
|
+
}
|
|
530
|
+
function plistString(value) {
|
|
531
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
532
|
+
}
|
|
533
|
+
function replacePlistString(plist, key, value) {
|
|
534
|
+
const pattern = /* @__PURE__ */ new RegExp(`(<key>${key}</key>\\s*<string>)([^<]*)(</string>)`);
|
|
535
|
+
if (!pattern.test(plist)) return plist;
|
|
536
|
+
return plist.replace(pattern, `$1${plistString(value)}$3`);
|
|
537
|
+
}
|
|
538
|
+
function writeGeneratedAppJson(nativeRoot, config) {
|
|
539
|
+
writeFileSync(path.join(nativeRoot, "app.json"), `${JSON.stringify({ expo: {
|
|
540
|
+
name: config.name,
|
|
541
|
+
slug: slugFromName(config.name),
|
|
542
|
+
version: config.version ?? "1.0.0",
|
|
543
|
+
platforms: ["ios"],
|
|
544
|
+
ios: {
|
|
545
|
+
appleTeamId: config.teamId,
|
|
546
|
+
buildNumber: config.buildNumber,
|
|
547
|
+
bundleIdentifier: config.bundleIdentifier
|
|
548
|
+
}
|
|
549
|
+
} }, null, 2)}\n`);
|
|
550
|
+
}
|
|
551
|
+
function patchNativeProject(nativeRoot, config) {
|
|
552
|
+
const projectPath = path.join(nativeRoot, "ios", "Bleam.xcodeproj", "project.pbxproj");
|
|
553
|
+
const infoPlistPath = path.join(nativeRoot, "ios", "Bleam", "Info.plist");
|
|
554
|
+
const productName = xcodeSettingValue(config.name);
|
|
555
|
+
const bundleIdentifier = xcodeSettingValue(config.bundleIdentifier ?? "");
|
|
556
|
+
const teamId = xcodeSettingValue(config.teamId ?? "");
|
|
557
|
+
const version = xcodeSettingValue(config.version ?? "1.0.0");
|
|
558
|
+
const buildNumber = xcodeSettingValue(config.buildNumber);
|
|
559
|
+
let project = readFileSync(projectPath, "utf8");
|
|
560
|
+
project = project.replace(/PRODUCT_BUNDLE_IDENTIFIER = [^;]+;/g, () => {
|
|
561
|
+
return `PRODUCT_BUNDLE_IDENTIFIER = ${bundleIdentifier};`;
|
|
562
|
+
}).replace(/DEVELOPMENT_TEAM = [^;]+;/g, () => {
|
|
563
|
+
return `DEVELOPMENT_TEAM = ${teamId};`;
|
|
564
|
+
}).replace(/MARKETING_VERSION = [^;]+;/g, () => {
|
|
565
|
+
return `MARKETING_VERSION = ${version};`;
|
|
566
|
+
}).replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, () => {
|
|
567
|
+
return `CURRENT_PROJECT_VERSION = ${buildNumber};`;
|
|
568
|
+
}).replace(/PRODUCT_NAME = [^;]+;/g, () => {
|
|
569
|
+
return `PRODUCT_NAME = ${productName};`;
|
|
570
|
+
});
|
|
571
|
+
writeFileSync(projectPath, project);
|
|
572
|
+
let infoPlist = readFileSync(infoPlistPath, "utf8");
|
|
573
|
+
infoPlist = replacePlistString(infoPlist, "CFBundleDisplayName", config.name);
|
|
574
|
+
infoPlist = replacePlistString(infoPlist, "CFBundleShortVersionString", config.version ?? "1.0.0");
|
|
575
|
+
infoPlist = replacePlistString(infoPlist, "CFBundleVersion", config.buildNumber);
|
|
576
|
+
writeFileSync(infoPlistPath, infoPlist);
|
|
577
|
+
}
|
|
578
|
+
function createBuildNativeWorkspace(options, config) {
|
|
579
|
+
const nativeRoot = path.join(options.projectRoot, ".bleam", "build", "native");
|
|
580
|
+
const templateRoot = nativeRuntimeTemplateRoot();
|
|
581
|
+
rmSync(nativeRoot, {
|
|
582
|
+
recursive: true,
|
|
583
|
+
force: true
|
|
584
|
+
});
|
|
585
|
+
mkdirSync(path.dirname(nativeRoot), { recursive: true });
|
|
586
|
+
cpSync(templateRoot, nativeRoot, {
|
|
587
|
+
recursive: true,
|
|
588
|
+
filter(source) {
|
|
589
|
+
return !path.relative(templateRoot, source).split(path.sep).some((part) => {
|
|
590
|
+
return part === "build" || part === "dist" || part === "node_modules" || part === ".expo" || part === "Pods" || part === ".xcode.env.local" || part === ".DS_Store" || part.endsWith(".xcworkspace");
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
const nodeModules = path.join(nativeRoot, "node_modules");
|
|
595
|
+
rmSync(nodeModules, {
|
|
596
|
+
recursive: true,
|
|
597
|
+
force: true
|
|
598
|
+
});
|
|
599
|
+
symlinkSync(resolvedDependencyRoot(), nodeModules);
|
|
600
|
+
writeGeneratedAppJson(nativeRoot, config);
|
|
601
|
+
writeFileSync(path.join(nativeRoot, "index.ts"), `import { registerApp } from 'bleam/app'\nimport App from ${JSON.stringify(path.join(options.projectRoot, "app"))}\n\nregisterApp(App)\n`);
|
|
602
|
+
writeFileSync(path.join(nativeRoot, "metro.config.js"), `const { getDefaultConfig } = require(${JSON.stringify(path.join(packageRoot, "metro-config.cjs"))})\n\nmodule.exports = getDefaultConfig(__dirname)\n`);
|
|
603
|
+
writeFileSync(path.join(nativeRoot, "ios", ".xcode.env"), `export NODE_BINARY=$(command -v node)\nexport BLEAM_PROJECT_ROOT=${singleQuotedString(options.projectRoot)}\nexport BLEAM_DEV_PLATFORM=macos\n`);
|
|
604
|
+
patchNativeProject(nativeRoot, config);
|
|
605
|
+
return nativeRoot;
|
|
606
|
+
}
|
|
607
|
+
function preflightBuild(options, config) {
|
|
608
|
+
if (process.platform !== "darwin") throw new Error(`bleam build requires macOS and Xcode. Current platform: ${process.platform}.\n${doctorHint(options.projectRoot)}`);
|
|
609
|
+
if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
|
|
610
|
+
if (!config.teamId) throw missingBuildFieldError("teamId");
|
|
611
|
+
const templateRoot = nativeRuntimeTemplateRoot();
|
|
612
|
+
if (!existsSync(templateRoot)) throw new Error(`Missing native runtime template: ${templateRoot}\n${doctorHint(options.projectRoot)}`);
|
|
613
|
+
if (!commandSucceeds("pod", ["--version"])) throw new Error(`CocoaPods is required for bleam build, but ${bold("pod")} is unavailable.\nInstall CocoaPods, then run bleam build again.\n${doctorHint(options.projectRoot)}`);
|
|
614
|
+
if (!commandSucceeds("xcodebuild", ["-version"])) throw new Error(`Xcode command line tools are required for bleam build, but ${bold("xcodebuild")} is unavailable.\nInstall Xcode or run ${bold("xcode-select --install")}, then run bleam build again.\n${doctorHint(options.projectRoot)}`);
|
|
615
|
+
}
|
|
616
|
+
function runInheritedCommand(command, args, cwd) {
|
|
617
|
+
const text = commandText(command, args);
|
|
618
|
+
const result = spawnSync(command, args, {
|
|
619
|
+
cwd,
|
|
620
|
+
stdio: "inherit",
|
|
621
|
+
env: process.env
|
|
622
|
+
});
|
|
623
|
+
if (result.error) throw new Error(`${text} could not start in ${cwd}: ${result.error.message}\n${doctorHint()}`);
|
|
624
|
+
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
|
+
}
|
|
626
|
+
function installPods(nativeRoot) {
|
|
627
|
+
console.log("Installing native dependencies...");
|
|
628
|
+
runInheritedCommand("pod", ["install"], path.join(nativeRoot, "ios"));
|
|
629
|
+
}
|
|
630
|
+
function archiveNativeApp(options) {
|
|
631
|
+
const platformOutput = path.join(options.outputRoot, "macos");
|
|
632
|
+
const archivePath = path.join(platformOutput, `${safeArtifactName(options.config.name)}.xcarchive`);
|
|
633
|
+
mkdirSync(platformOutput, { recursive: true });
|
|
634
|
+
rmSync(archivePath, {
|
|
635
|
+
recursive: true,
|
|
636
|
+
force: true
|
|
637
|
+
});
|
|
638
|
+
console.log(`Archiving ${options.config.name} for macOS...`);
|
|
639
|
+
runInheritedCommand("xcodebuild", [
|
|
640
|
+
"-allowProvisioningUpdates",
|
|
641
|
+
"-workspace",
|
|
642
|
+
path.join(options.nativeRoot, "ios", "Bleam.xcworkspace"),
|
|
643
|
+
"-scheme",
|
|
644
|
+
"bleam",
|
|
645
|
+
"-configuration",
|
|
646
|
+
options.configuration,
|
|
647
|
+
"-destination",
|
|
648
|
+
"platform=macOS,arch=arm64,variant=Mac Catalyst",
|
|
649
|
+
"-archivePath",
|
|
650
|
+
archivePath,
|
|
651
|
+
"archive"
|
|
652
|
+
], options.nativeRoot);
|
|
653
|
+
return archivePath;
|
|
654
|
+
}
|
|
655
|
+
function exportNativeArchive(options) {
|
|
656
|
+
const exportPath = path.join(options.outputRoot, "macos", "export");
|
|
657
|
+
rmSync(exportPath, {
|
|
658
|
+
recursive: true,
|
|
659
|
+
force: true
|
|
660
|
+
});
|
|
661
|
+
mkdirSync(exportPath, { recursive: true });
|
|
662
|
+
console.log("Exporting macOS archive...");
|
|
663
|
+
runInheritedCommand("xcodebuild", [
|
|
664
|
+
"-allowProvisioningUpdates",
|
|
665
|
+
"-exportArchive",
|
|
666
|
+
"-archivePath",
|
|
667
|
+
options.archivePath,
|
|
668
|
+
"-exportPath",
|
|
669
|
+
exportPath,
|
|
670
|
+
"-exportOptionsPlist",
|
|
671
|
+
options.exportOptionsPlist
|
|
672
|
+
], path.dirname(options.archivePath));
|
|
673
|
+
return exportPath;
|
|
674
|
+
}
|
|
675
|
+
async function buildNativeArchives(options) {
|
|
676
|
+
const config = readProjectConfig(options);
|
|
677
|
+
preflightBuild(options, config);
|
|
678
|
+
const nativeRoot = createBuildNativeWorkspace(options, config);
|
|
679
|
+
installPods(nativeRoot);
|
|
680
|
+
const archivePath = archiveNativeApp({
|
|
681
|
+
nativeRoot,
|
|
682
|
+
outputRoot: options.output,
|
|
683
|
+
configuration: options.configuration,
|
|
684
|
+
config
|
|
685
|
+
});
|
|
686
|
+
console.log(`Created ${archivePath}`);
|
|
687
|
+
if (options.exportOptionsPlist) {
|
|
688
|
+
const exportPath = exportNativeArchive({
|
|
689
|
+
archivePath,
|
|
690
|
+
outputRoot: options.output,
|
|
691
|
+
exportOptionsPlist: options.exportOptionsPlist
|
|
692
|
+
});
|
|
693
|
+
console.log(`Exported ${exportPath}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
229
696
|
function runtimeCacheRoot() {
|
|
230
|
-
return path.join(os.homedir(), "Library", "Caches", "bleam", "runtimes", runtimeVersion,
|
|
697
|
+
return path.join(os.homedir(), "Library", "Caches", "bleam", "runtimes", runtimeVersion, artifactPlatform());
|
|
231
698
|
}
|
|
232
699
|
function localDevelopmentRuntimeAppPath() {
|
|
233
|
-
|
|
700
|
+
const productsRoot = path.resolve(packageRoot, "..", "..", "app", "bleam", "build", "Build", "Products", "Debug-maccatalyst");
|
|
701
|
+
for (const appName of ["Bleam.app", "bleam.app"]) {
|
|
702
|
+
const appPath = path.join(productsRoot, appName);
|
|
703
|
+
if (existsSync(appPath)) return appPath;
|
|
704
|
+
}
|
|
705
|
+
return path.join(productsRoot, "Bleam.app");
|
|
234
706
|
}
|
|
235
707
|
function runtimeReleaseBaseUrl() {
|
|
236
708
|
return process.env.BLEAM_RUNTIME_RELEASE_BASE_URL ?? "https://github.com/bajedev/bleam-app/releases/download";
|
|
237
709
|
}
|
|
238
710
|
function runtimeArchiveName() {
|
|
239
|
-
return `bleam-runtime-v${runtimeVersion}-${
|
|
711
|
+
return `bleam-runtime-v${runtimeVersion}-${artifactPlatform()}.zip`;
|
|
240
712
|
}
|
|
241
713
|
function runtimeArchiveUrl() {
|
|
242
714
|
const tag = encodeURIComponent(`bleam@${runtimeVersion}`);
|
|
243
715
|
return `${runtimeReleaseBaseUrl()}/${tag}/${runtimeArchiveName()}`;
|
|
244
716
|
}
|
|
717
|
+
function runtimeResolutionDetails() {
|
|
718
|
+
return [
|
|
719
|
+
`Runtime version: ${runtimeVersion}`,
|
|
720
|
+
`Runtime platform: ${artifactPlatform()}`,
|
|
721
|
+
`Runtime cache: ${runtimeCacheRoot()}`,
|
|
722
|
+
`Release URL: ${runtimeArchiveUrl()}`,
|
|
723
|
+
`Override with BLEAM_RUNTIME_APP=/path/to/Bleam.app for local testing.`,
|
|
724
|
+
doctorHint()
|
|
725
|
+
].join("\n");
|
|
726
|
+
}
|
|
245
727
|
function sha256(filePath) {
|
|
246
728
|
return createHash("sha256").update(readFileSync(filePath)).digest("hex");
|
|
247
729
|
}
|
|
@@ -256,7 +738,8 @@ async function downloadExpectedSha256(url) {
|
|
|
256
738
|
return (await response.text()).trim().split(/\s+/)[0];
|
|
257
739
|
}
|
|
258
740
|
async function downloadRuntimeArtifact() {
|
|
259
|
-
if (process.platform !== "darwin") throw new Error("Bleam runtime artifacts are currently only available
|
|
741
|
+
if (process.platform !== "darwin") throw new Error("Bleam runtime artifacts are currently only available from macOS hosts.");
|
|
742
|
+
const runtimePlatform = artifactPlatform();
|
|
260
743
|
const cacheRoot = runtimeCacheRoot();
|
|
261
744
|
const cachedAppPath = path.join(cacheRoot, "Bleam.app");
|
|
262
745
|
if (existsSync(cachedAppPath)) return {
|
|
@@ -303,10 +786,11 @@ async function downloadRuntimeArtifact() {
|
|
|
303
786
|
};
|
|
304
787
|
}
|
|
305
788
|
async function resolveRuntimeArtifact() {
|
|
789
|
+
const runtimePlatform = artifactPlatform();
|
|
306
790
|
const overrideAppPath = process.env.BLEAM_RUNTIME_APP;
|
|
307
791
|
if (overrideAppPath) {
|
|
308
792
|
const appPath = path.resolve(overrideAppPath);
|
|
309
|
-
if (!existsSync(appPath)) throw new Error(`BLEAM_RUNTIME_APP
|
|
793
|
+
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()}`);
|
|
310
794
|
return {
|
|
311
795
|
appPath,
|
|
312
796
|
version: runtimeVersion,
|
|
@@ -319,7 +803,11 @@ async function resolveRuntimeArtifact() {
|
|
|
319
803
|
version: runtimeVersion,
|
|
320
804
|
platform: runtimePlatform
|
|
321
805
|
};
|
|
322
|
-
|
|
806
|
+
try {
|
|
807
|
+
return await downloadRuntimeArtifact();
|
|
808
|
+
} catch (error) {
|
|
809
|
+
throw new Error(`Failed to resolve the Bleam runtime.\n${runtimeResolutionDetails()}\n\nCause: ${error instanceof Error ? error.message : String(error)}`);
|
|
810
|
+
}
|
|
323
811
|
}
|
|
324
812
|
function copyRuntimeClient(projectRoot, runtime) {
|
|
325
813
|
const clientAppPath = path.join(projectRoot, ".bleam", "client", "Bleam.app");
|
|
@@ -331,39 +819,6 @@ function copyRuntimeClient(projectRoot, runtime) {
|
|
|
331
819
|
cpSync(runtime.appPath, clientAppPath, { recursive: true });
|
|
332
820
|
return clientAppPath;
|
|
333
821
|
}
|
|
334
|
-
function waitForDevServer(port, timeoutMs = 12e4) {
|
|
335
|
-
return new Promise((resolve, reject) => {
|
|
336
|
-
const startedAt = Date.now();
|
|
337
|
-
const poll = () => {
|
|
338
|
-
const req = http.get(`http://localhost:${port}/status`, (res) => {
|
|
339
|
-
let data = "";
|
|
340
|
-
res.on("data", (chunk) => {
|
|
341
|
-
data += chunk.toString();
|
|
342
|
-
});
|
|
343
|
-
res.on("end", () => {
|
|
344
|
-
if (data.includes("packager-status:running")) {
|
|
345
|
-
resolve();
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
retry();
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
req.on("error", retry);
|
|
352
|
-
req.setTimeout(2e3, () => {
|
|
353
|
-
req.destroy();
|
|
354
|
-
retry();
|
|
355
|
-
});
|
|
356
|
-
};
|
|
357
|
-
const retry = () => {
|
|
358
|
-
if (Date.now() - startedAt > timeoutMs) {
|
|
359
|
-
reject(/* @__PURE__ */ new Error(`Bleam dev server did not start within ${timeoutMs / 1e3}s`));
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
setTimeout(poll, 500);
|
|
363
|
-
};
|
|
364
|
-
poll();
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
822
|
function isBleamManifest(value) {
|
|
368
823
|
const manifest = value && typeof value === "object" ? value : {};
|
|
369
824
|
return typeof manifest.name === "string" && typeof manifest.bundleUrl === "string" && manifest.platform === "macos" && manifest.dev === true;
|
|
@@ -371,18 +826,138 @@ function isBleamManifest(value) {
|
|
|
371
826
|
async function readDevManifest(origin, timeoutMs = 3e4) {
|
|
372
827
|
const manifestUrl = `${origin}/bleam/manifest.json`;
|
|
373
828
|
const startedAt = Date.now();
|
|
829
|
+
const spinner = startSpinner("Waiting for Bleam dev server...");
|
|
374
830
|
while (Date.now() - startedAt <= timeoutMs) {
|
|
375
831
|
try {
|
|
376
832
|
const response = await fetch(manifestUrl, { cache: "no-store" });
|
|
377
833
|
const value = await response.json();
|
|
378
|
-
if (response.ok && isBleamManifest(value))
|
|
834
|
+
if (response.ok && isBleamManifest(value)) {
|
|
835
|
+
spinner?.success("Bleam dev server is ready");
|
|
836
|
+
return value;
|
|
837
|
+
}
|
|
379
838
|
} catch {}
|
|
380
839
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
381
840
|
}
|
|
841
|
+
spinner?.error("Bleam dev server did not become available");
|
|
382
842
|
throw new Error(`Bleam manifest did not become available: ${manifestUrl}`);
|
|
383
843
|
}
|
|
844
|
+
function pushDoctorCheck(checks, status, label, detail) {
|
|
845
|
+
checks.push({
|
|
846
|
+
status,
|
|
847
|
+
label,
|
|
848
|
+
detail
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
function printDoctorSection(title, checks) {
|
|
852
|
+
if (checks.length === 0) return;
|
|
853
|
+
console.log(`\n${heading(title)}`);
|
|
854
|
+
for (const check of checks) {
|
|
855
|
+
const detail = check.detail ? muted(`: ${check.detail}`) : "";
|
|
856
|
+
console.log(` ${statusLabel(check.status)} ${check.label}${detail}`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
function runDoctor(options) {
|
|
860
|
+
const projectChecks = [];
|
|
861
|
+
const runtimeChecks = [];
|
|
862
|
+
const environmentChecks = [];
|
|
863
|
+
const appConfigPath = path.join(options.projectRoot, "app.config.ts");
|
|
864
|
+
const appEntryPath = path.join(options.projectRoot, "app", "index.tsx");
|
|
865
|
+
if (existsSync(appConfigPath)) pushDoctorCheck(projectChecks, "pass", "app.config.ts found");
|
|
866
|
+
else pushDoctorCheck(projectChecks, "fail", "app.config.ts missing");
|
|
867
|
+
if (existsSync(appEntryPath)) pushDoctorCheck(projectChecks, "pass", "app/index.tsx found");
|
|
868
|
+
else pushDoctorCheck(projectChecks, "fail", "app/index.tsx missing");
|
|
869
|
+
try {
|
|
870
|
+
const config = readProjectConfig(options);
|
|
871
|
+
pushDoctorCheck(projectChecks, "pass", "config loaded");
|
|
872
|
+
pushDoctorCheck(projectChecks, "pass", "name", config.name);
|
|
873
|
+
if (config.bundleIdentifier) pushDoctorCheck(projectChecks, "pass", "bundleIdentifier", config.bundleIdentifier);
|
|
874
|
+
else pushDoctorCheck(projectChecks, "warn", "bundleIdentifier not configured", "required for bleam build");
|
|
875
|
+
if (config.teamId) pushDoctorCheck(projectChecks, "pass", "teamId", config.teamId);
|
|
876
|
+
else pushDoctorCheck(projectChecks, "warn", "teamId not configured", "required for bleam build");
|
|
877
|
+
} catch (error) {
|
|
878
|
+
pushDoctorCheck(projectChecks, "fail", "config failed to load", error instanceof Error ? error.message : String(error));
|
|
879
|
+
}
|
|
880
|
+
const cacheRoot = runtimeCacheRoot();
|
|
881
|
+
const cachedAppPath = path.join(cacheRoot, "Bleam.app");
|
|
882
|
+
const localAppPath = localDevelopmentRuntimeAppPath();
|
|
883
|
+
pushDoctorCheck(runtimeChecks, "pass", "version", runtimeVersion);
|
|
884
|
+
pushDoctorCheck(runtimeChecks, "pass", "platform", artifactPlatform());
|
|
885
|
+
pushDoctorCheck(runtimeChecks, "pass", "cache", prettyPath(cacheRoot));
|
|
886
|
+
if (existsSync(cachedAppPath)) pushDoctorCheck(runtimeChecks, "pass", "cached runtime found");
|
|
887
|
+
else pushDoctorCheck(runtimeChecks, "warn", "runtime not cached yet", "bleam dev can download it on demand");
|
|
888
|
+
if (existsSync(localAppPath)) pushDoctorCheck(runtimeChecks, "pass", "local development runtime found", prettyPath(localAppPath));
|
|
889
|
+
else pushDoctorCheck(runtimeChecks, "warn", "local development runtime not found", prettyPath(localAppPath));
|
|
890
|
+
const overrideAppPath = process.env.BLEAM_RUNTIME_APP;
|
|
891
|
+
if (overrideAppPath) {
|
|
892
|
+
const appPath = path.resolve(overrideAppPath);
|
|
893
|
+
pushDoctorCheck(runtimeChecks, existsSync(appPath) ? "pass" : "fail", "BLEAM_RUNTIME_APP", prettyPath(appPath));
|
|
894
|
+
}
|
|
895
|
+
pushDoctorCheck(runtimeChecks, "pass", "release artifact URL", runtimeArchiveUrl());
|
|
896
|
+
pushDoctorCheck(environmentChecks, process.platform === "darwin" ? "pass" : "fail", "macOS host", process.platform);
|
|
897
|
+
pushDoctorCheck(environmentChecks, "pass", "node", prettyPath(process.execPath));
|
|
898
|
+
const hasXcodebuild = commandSucceeds("xcodebuild", ["-version"]);
|
|
899
|
+
pushDoctorCheck(environmentChecks, hasXcodebuild ? "pass" : "warn", "xcodebuild", hasXcodebuild ? "available" : "unavailable");
|
|
900
|
+
const hasPod = commandSucceeds("pod", ["--version"]);
|
|
901
|
+
pushDoctorCheck(environmentChecks, hasPod ? "pass" : "warn", "pod", hasPod ? "available" : "unavailable");
|
|
902
|
+
const allChecks = [
|
|
903
|
+
...projectChecks,
|
|
904
|
+
...runtimeChecks,
|
|
905
|
+
...environmentChecks
|
|
906
|
+
];
|
|
907
|
+
const counts = {
|
|
908
|
+
pass: allChecks.filter((check) => check.status === "pass").length,
|
|
909
|
+
warn: allChecks.filter((check) => check.status === "warn").length,
|
|
910
|
+
fail: allChecks.filter((check) => check.status === "fail").length
|
|
911
|
+
};
|
|
912
|
+
console.log(heading("Bleam Doctor"));
|
|
913
|
+
printDoctorSection("Project", projectChecks);
|
|
914
|
+
printDoctorSection("Runtime", runtimeChecks);
|
|
915
|
+
printDoctorSection("Environment", environmentChecks);
|
|
916
|
+
console.log(`\n${heading("Summary")}`);
|
|
917
|
+
console.log(` ${green(`${counts.pass} passed`)}, ${yellow(`${counts.warn} warnings`)}, ${counts.fail > 0 ? red(`${counts.fail} failed`) : green("0 failed")}`);
|
|
918
|
+
if (counts.fail > 0) process.exit(1);
|
|
919
|
+
}
|
|
920
|
+
function formatDuration(ms) {
|
|
921
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
922
|
+
}
|
|
923
|
+
async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
|
|
924
|
+
const startedAt = Date.now();
|
|
925
|
+
const requestTimeoutMs = 3e4;
|
|
926
|
+
const spinner = startSpinner("Bundling app...");
|
|
927
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
928
|
+
const requestStartedAt = Date.now();
|
|
929
|
+
try {
|
|
930
|
+
const response = await fetch(bundleUrl, {
|
|
931
|
+
cache: "no-store",
|
|
932
|
+
signal: AbortSignal.timeout(requestTimeoutMs)
|
|
933
|
+
});
|
|
934
|
+
await response.body?.cancel();
|
|
935
|
+
if (response.ok) {
|
|
936
|
+
spinner?.success(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
|
|
937
|
+
if (!spinner) console.log(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
if (spinner) spinner.text = `Bundle request returned ${response.status}, retrying...`;
|
|
941
|
+
else console.log(`Bundle request returned ${response.status}, retrying...`);
|
|
942
|
+
} catch (error) {
|
|
943
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
944
|
+
const message = `Bundle request timed out after ${formatDuration(Date.now() - requestStartedAt)}, retrying...`;
|
|
945
|
+
if (spinner) spinner.text = message;
|
|
946
|
+
else console.log(message);
|
|
947
|
+
} else {
|
|
948
|
+
const message = `Bundle request failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
949
|
+
if (spinner) spinner.text = `${message}, retrying...`;
|
|
950
|
+
else console.log(message);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
954
|
+
}
|
|
955
|
+
spinner?.error("Bundle did not complete");
|
|
956
|
+
throw new Error(`Bleam bundle did not complete within ${formatDuration(timeoutMs)}.\nBundle URL: ${bundleUrl}\n${doctorHint()}`);
|
|
957
|
+
}
|
|
384
958
|
function openRuntimeClient(appPath, projectConfigPath, manifest) {
|
|
385
|
-
spawn("open", [
|
|
959
|
+
const child = spawn("open", [
|
|
960
|
+
"-n",
|
|
386
961
|
appPath,
|
|
387
962
|
"--args",
|
|
388
963
|
"--bleam-project-config",
|
|
@@ -391,10 +966,220 @@ function openRuntimeClient(appPath, projectConfigPath, manifest) {
|
|
|
391
966
|
manifest.bundleUrl,
|
|
392
967
|
"--bleam-title",
|
|
393
968
|
manifest.name
|
|
394
|
-
], {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
})
|
|
969
|
+
], { stdio: "ignore" });
|
|
970
|
+
child.on("error", (error) => {
|
|
971
|
+
console.error(`Failed to open Bleam runtime at ${appPath}: ${error.message}`);
|
|
972
|
+
});
|
|
973
|
+
child.on("exit", (code) => {
|
|
974
|
+
if (code && code !== 0) console.error(`Failed to open Bleam runtime at ${appPath}`);
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
async function launchRuntimeClient(appPath, projectConfigPath, manifest) {
|
|
978
|
+
openRuntimeClient(appPath, projectConfigPath, manifest);
|
|
979
|
+
}
|
|
980
|
+
function focusRuntimeClient() {
|
|
981
|
+
return spawnSync("osascript", ["-e", "tell application id \"dev.bleam.app\" to activate"], { stdio: "ignore" }).status === 0;
|
|
982
|
+
}
|
|
983
|
+
function sendJson(res, status, value) {
|
|
984
|
+
res.writeHead(status, {
|
|
985
|
+
"Cache-Control": "no-store",
|
|
986
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
987
|
+
});
|
|
988
|
+
res.end(JSON.stringify(value));
|
|
989
|
+
}
|
|
990
|
+
async function requestRuntimeReload(origin) {
|
|
991
|
+
const response = await fetch(`${origin}/bleam/reload`, {
|
|
992
|
+
method: "POST",
|
|
993
|
+
cache: "no-store"
|
|
994
|
+
});
|
|
995
|
+
const value = await response.json();
|
|
996
|
+
if (!response.ok) throw new Error(`Reload request failed: ${response.status}`);
|
|
997
|
+
const reload = value;
|
|
998
|
+
return typeof reload.clients === "number" ? reload.clients : 0;
|
|
999
|
+
}
|
|
1000
|
+
function biomeBinPath() {
|
|
1001
|
+
const biomePackageJsonPath = require.resolve("@biomejs/biome/package.json");
|
|
1002
|
+
const biomePackageRoot = path.dirname(biomePackageJsonPath);
|
|
1003
|
+
const biomePackage = require("@biomejs/biome/package.json");
|
|
1004
|
+
const bin = typeof biomePackage.bin === "string" ? biomePackage.bin : biomePackage.bin?.biome;
|
|
1005
|
+
if (!bin) throw new Error("Could not resolve @biomejs/biome binary");
|
|
1006
|
+
return path.join(biomePackageRoot, bin);
|
|
1007
|
+
}
|
|
1008
|
+
function ensureProjectToolWorkspace(options) {
|
|
1009
|
+
const config = readProjectConfig({ projectRoot: options.projectRoot });
|
|
1010
|
+
return createDevWorkspace(options.projectRoot, config);
|
|
1011
|
+
}
|
|
1012
|
+
function runBiomeCommand(command, options) {
|
|
1013
|
+
assertBleamConfigFiles(options.projectRoot);
|
|
1014
|
+
ensureProjectToolWorkspace(options);
|
|
1015
|
+
const configPath = path.join(options.projectRoot, "biome.json");
|
|
1016
|
+
const existingConfig = existsSync(configPath) ? readFileSync(configPath, "utf8") : void 0;
|
|
1017
|
+
const args = [
|
|
1018
|
+
command,
|
|
1019
|
+
"--config-path",
|
|
1020
|
+
"biome.json"
|
|
1021
|
+
];
|
|
1022
|
+
if (command === "check" && options.write) args.push("--write");
|
|
1023
|
+
if (command === "format" && !options.formatCheck) args.push("--write");
|
|
1024
|
+
args.push("app", "app.config.ts", "tsconfig.json");
|
|
1025
|
+
writeBiomeConfig(configPath);
|
|
1026
|
+
const result = spawnSync(biomeBinPath(), args, {
|
|
1027
|
+
cwd: options.projectRoot,
|
|
1028
|
+
stdio: "inherit"
|
|
1029
|
+
});
|
|
1030
|
+
if (existingConfig === void 0) rmSync(configPath, { force: true });
|
|
1031
|
+
else writeFileSync(configPath, existingConfig);
|
|
1032
|
+
if (result.error) throw result.error;
|
|
1033
|
+
process.exit(result.status ?? 1);
|
|
1034
|
+
}
|
|
1035
|
+
function setupInteractiveControls(options) {
|
|
1036
|
+
if (!process.stdin.isTTY) return () => {};
|
|
1037
|
+
const wasRaw = process.stdin.isRaw;
|
|
1038
|
+
let closed = false;
|
|
1039
|
+
const cleanup = () => {
|
|
1040
|
+
if (closed) return;
|
|
1041
|
+
closed = true;
|
|
1042
|
+
process.stdin.off("data", onData);
|
|
1043
|
+
process.stdin.setRawMode(wasRaw);
|
|
1044
|
+
};
|
|
1045
|
+
const onData = (data) => {
|
|
1046
|
+
for (const byte of data) {
|
|
1047
|
+
if (byte === 3) {
|
|
1048
|
+
cleanup();
|
|
1049
|
+
options.stop();
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
const key = String.fromCharCode(byte).toLowerCase();
|
|
1053
|
+
if (key === "o") {
|
|
1054
|
+
(async () => {
|
|
1055
|
+
try {
|
|
1056
|
+
if (!focusRuntimeClient()) {
|
|
1057
|
+
console.log("Opening Bleam app...");
|
|
1058
|
+
await launchRuntimeClient(options.clientAppPath, options.projectConfigPath, options.manifest);
|
|
1059
|
+
}
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1062
|
+
}
|
|
1063
|
+
})();
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
if (key === "r") requestRuntimeReload(options.origin).then((clients) => {
|
|
1067
|
+
console.log(`Reloaded app (${clients} client${clients === 1 ? "" : "s"})`);
|
|
1068
|
+
}).catch((error) => {
|
|
1069
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
process.stdin.setRawMode(true);
|
|
1074
|
+
process.stdin.resume();
|
|
1075
|
+
process.stdin.on("data", onData);
|
|
1076
|
+
console.log("Press o to open, r to reload, Ctrl+C to stop.");
|
|
1077
|
+
return cleanup;
|
|
1078
|
+
}
|
|
1079
|
+
async function startInternalMetroServer(options) {
|
|
1080
|
+
process.env.BLEAM_DEV_PLATFORM ??= "macos";
|
|
1081
|
+
const metro = require("@expo/metro/metro");
|
|
1082
|
+
const { loadConfig } = require("@expo/metro/metro-config");
|
|
1083
|
+
const config = await loadConfig({
|
|
1084
|
+
config: path.join(options.workspaceRoot, "metro.config.js"),
|
|
1085
|
+
cwd: options.workspaceRoot,
|
|
1086
|
+
port: options.port
|
|
1087
|
+
});
|
|
1088
|
+
const { middleware, end, attachHmrServer } = await metro.createConnectMiddleware(config, {
|
|
1089
|
+
watch: true,
|
|
1090
|
+
waitForBundler: true
|
|
1091
|
+
});
|
|
1092
|
+
const messageClients = /* @__PURE__ */ new Set();
|
|
1093
|
+
const messageServer = new WebSocketServer({ noServer: true });
|
|
1094
|
+
const broadcastReload = () => {
|
|
1095
|
+
const message = JSON.stringify({
|
|
1096
|
+
version: 2,
|
|
1097
|
+
method: "reload",
|
|
1098
|
+
params: {}
|
|
1099
|
+
});
|
|
1100
|
+
let clients = 0;
|
|
1101
|
+
for (const client of messageClients) if (client.readyState === WebSocket.OPEN) {
|
|
1102
|
+
client.send(message);
|
|
1103
|
+
clients += 1;
|
|
1104
|
+
}
|
|
1105
|
+
return clients;
|
|
1106
|
+
};
|
|
1107
|
+
messageServer.on("connection", (client) => {
|
|
1108
|
+
messageClients.add(client);
|
|
1109
|
+
client.on("close", () => messageClients.delete(client));
|
|
1110
|
+
});
|
|
1111
|
+
const server = http.createServer((req, res) => {
|
|
1112
|
+
if (req.url?.split("?")[0] === "/status") {
|
|
1113
|
+
res.writeHead(200, {
|
|
1114
|
+
"Cache-Control": "no-store",
|
|
1115
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
1116
|
+
"X-React-Native-Project-Root": encodeURI(config.projectRoot)
|
|
1117
|
+
});
|
|
1118
|
+
res.end("packager-status:running");
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
if (req.url?.split("?")[0] === "/bleam/reload") {
|
|
1122
|
+
if (req.method !== "POST") {
|
|
1123
|
+
res.writeHead(405, { Allow: "POST" });
|
|
1124
|
+
res.end();
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
sendJson(res, 200, {
|
|
1128
|
+
ok: true,
|
|
1129
|
+
clients: broadcastReload()
|
|
1130
|
+
});
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
middleware(req, res, (error) => {
|
|
1134
|
+
if (res.headersSent) return;
|
|
1135
|
+
if (error) {
|
|
1136
|
+
console.error(error.message);
|
|
1137
|
+
res.writeHead(500, {
|
|
1138
|
+
"Cache-Control": "no-store",
|
|
1139
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
1140
|
+
});
|
|
1141
|
+
res.end(error.message);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
res.writeHead(404, {
|
|
1145
|
+
"Cache-Control": "no-store",
|
|
1146
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
1147
|
+
});
|
|
1148
|
+
res.end("Not found");
|
|
1149
|
+
});
|
|
1150
|
+
});
|
|
1151
|
+
server.timeout = 0;
|
|
1152
|
+
attachHmrServer(server);
|
|
1153
|
+
const metroUpgradeListeners = server.listeners("upgrade");
|
|
1154
|
+
server.removeAllListeners("upgrade");
|
|
1155
|
+
server.on("upgrade", (req, socket, head) => {
|
|
1156
|
+
if (req.url?.split("?")[0] === "/message") {
|
|
1157
|
+
messageServer.handleUpgrade(req, socket, head, (client) => {
|
|
1158
|
+
messageServer.emit("connection", client, req);
|
|
1159
|
+
});
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
for (const listener of metroUpgradeListeners) listener.call(server, req, socket, head);
|
|
1163
|
+
});
|
|
1164
|
+
await new Promise((resolve, reject) => {
|
|
1165
|
+
server.on("error", reject);
|
|
1166
|
+
server.listen(options.port, "localhost", () => resolve());
|
|
1167
|
+
});
|
|
1168
|
+
server.on("error", (error) => {
|
|
1169
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1170
|
+
});
|
|
1171
|
+
console.log(`Bleam dev server running at http://localhost:${options.port}`);
|
|
1172
|
+
const close = () => {
|
|
1173
|
+
setTimeout(() => process.exit(0), 1e3).unref();
|
|
1174
|
+
messageServer.close();
|
|
1175
|
+
for (const client of messageClients) client.terminate();
|
|
1176
|
+
server.close(() => {
|
|
1177
|
+
end();
|
|
1178
|
+
process.exit(0);
|
|
1179
|
+
});
|
|
1180
|
+
};
|
|
1181
|
+
process.on("SIGINT", close);
|
|
1182
|
+
process.on("SIGTERM", close);
|
|
398
1183
|
}
|
|
399
1184
|
async function startDevServer(options) {
|
|
400
1185
|
const config = readProjectConfig(options);
|
|
@@ -404,10 +1189,10 @@ async function startDevServer(options) {
|
|
|
404
1189
|
const origin = `http://localhost:${options.port}`;
|
|
405
1190
|
const projectConfigPath = writeProjectRuntimeConfig(options.projectRoot, config, origin);
|
|
406
1191
|
const child = spawn(process.execPath, [
|
|
407
|
-
|
|
408
|
-
"
|
|
1192
|
+
fileURLToPath(import.meta.url),
|
|
1193
|
+
"internal:metro",
|
|
1194
|
+
"--workspace-root",
|
|
409
1195
|
workspaceRoot,
|
|
410
|
-
"--localhost",
|
|
411
1196
|
"--port",
|
|
412
1197
|
String(options.port)
|
|
413
1198
|
], {
|
|
@@ -419,11 +1204,12 @@ async function startDevServer(options) {
|
|
|
419
1204
|
BLEAM_DEV_NAME: config.name,
|
|
420
1205
|
BLEAM_DEV_ORIGIN: origin,
|
|
421
1206
|
BLEAM_DEV_PORT: String(options.port),
|
|
1207
|
+
BLEAM_DEV_PLATFORM: "macos",
|
|
422
1208
|
BLEAM_DEV_RUNTIME_VERSION: config.runtimeVersion,
|
|
423
1209
|
BLEAM_DEV_SDK_VERSION: config.sdkVersion,
|
|
424
1210
|
NODE_PATH: [
|
|
425
1211
|
path.join(options.projectRoot, "node_modules"),
|
|
426
|
-
|
|
1212
|
+
resolvedDependencyRoot(),
|
|
427
1213
|
path.dirname(path.dirname(require.resolve("expo/package.json"))),
|
|
428
1214
|
path.dirname(path.dirname(require.resolve("react/package.json"))),
|
|
429
1215
|
path.dirname(path.dirname(require.resolve("react-native/package.json"))),
|
|
@@ -431,15 +1217,51 @@ async function startDevServer(options) {
|
|
|
431
1217
|
].filter(Boolean).join(path.delimiter)
|
|
432
1218
|
}
|
|
433
1219
|
});
|
|
1220
|
+
let cleanupInteractiveControls = () => {};
|
|
1221
|
+
let stopping = false;
|
|
434
1222
|
const stop = (signal) => {
|
|
1223
|
+
if (stopping) return;
|
|
1224
|
+
stopping = true;
|
|
1225
|
+
cleanupInteractiveControls();
|
|
435
1226
|
child.kill(signal);
|
|
1227
|
+
setTimeout(() => process.exit(0), 2e3).unref();
|
|
436
1228
|
};
|
|
437
1229
|
process.on("SIGINT", () => stop("SIGINT"));
|
|
438
1230
|
process.on("SIGTERM", () => stop("SIGTERM"));
|
|
439
|
-
child.on("
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
1231
|
+
child.on("error", (error) => {
|
|
1232
|
+
cleanupInteractiveControls();
|
|
1233
|
+
console.error(`Bleam dev server process failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1234
|
+
console.error(doctorHint(options.projectRoot));
|
|
1235
|
+
process.exit(1);
|
|
1236
|
+
});
|
|
1237
|
+
child.on("exit", (code) => {
|
|
1238
|
+
cleanupInteractiveControls();
|
|
1239
|
+
process.exit(code ?? 0);
|
|
1240
|
+
});
|
|
1241
|
+
try {
|
|
1242
|
+
const manifest = await readDevManifest(origin);
|
|
1243
|
+
cleanupInteractiveControls = setupInteractiveControls({
|
|
1244
|
+
origin,
|
|
1245
|
+
clientAppPath,
|
|
1246
|
+
projectConfigPath,
|
|
1247
|
+
manifest,
|
|
1248
|
+
stop: () => stop("SIGINT")
|
|
1249
|
+
});
|
|
1250
|
+
if (options.run && process.env.BLEAM_NO_OPEN !== "1") try {
|
|
1251
|
+
await waitForDevBundle(manifest.bundleUrl);
|
|
1252
|
+
console.log("Opening Bleam app...");
|
|
1253
|
+
await launchRuntimeClient(clientAppPath, projectConfigPath, manifest);
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
console.error(`Initial app launch failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1256
|
+
console.error(doctorHint(options.projectRoot));
|
|
1257
|
+
console.error("Check BLEAM_RUNTIME_APP if you are using a runtime override.");
|
|
1258
|
+
console.error("Bleam dev server is still running.");
|
|
1259
|
+
}
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
cleanupInteractiveControls();
|
|
1262
|
+
child.kill("SIGTERM");
|
|
1263
|
+
throw error;
|
|
1264
|
+
}
|
|
443
1265
|
}
|
|
444
1266
|
async function main(argv = process.argv.slice(2)) {
|
|
445
1267
|
const [command, ...args] = argv;
|
|
@@ -452,7 +1274,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
452
1274
|
createNewProject(options);
|
|
453
1275
|
if (options.run) await startDevServer({
|
|
454
1276
|
projectRoot: options.projectRoot,
|
|
455
|
-
port: 8081
|
|
1277
|
+
port: 8081,
|
|
1278
|
+
run: true
|
|
456
1279
|
});
|
|
457
1280
|
return;
|
|
458
1281
|
}
|
|
@@ -460,6 +1283,30 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
460
1283
|
await startDevServer(parseDevOptions(args));
|
|
461
1284
|
return;
|
|
462
1285
|
}
|
|
1286
|
+
if (command === "build") {
|
|
1287
|
+
await buildNativeArchives(parseBuildOptions(args));
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
if (command === "doctor") {
|
|
1291
|
+
runDoctor(parseDoctorOptions(args));
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
if (command === "check") {
|
|
1295
|
+
runBiomeCommand("check", parseProjectToolOptions(args));
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
if (command === "format") {
|
|
1299
|
+
runBiomeCommand("format", parseProjectToolOptions(args));
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
if (command === "lint") {
|
|
1303
|
+
runBiomeCommand("lint", parseProjectToolOptions(args));
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
if (command === "internal:metro") {
|
|
1307
|
+
await startInternalMetroServer(parseInternalMetroOptions(args));
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
463
1310
|
throw new Error(`Unknown command: ${command}`);
|
|
464
1311
|
}
|
|
465
1312
|
main().catch((error) => {
|
|
@@ -468,5 +1315,4 @@ main().catch((error) => {
|
|
|
468
1315
|
});
|
|
469
1316
|
|
|
470
1317
|
//#endregion
|
|
471
|
-
export { };
|
|
472
|
-
//# sourceMappingURL=cli.js.map
|
|
1318
|
+
export { };
|