bleam 0.0.5 → 0.0.7
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 +4146 -0
- package/dist/ai.d.cts +535 -0
- package/dist/ai.d.ts +535 -0
- package/dist/ai.js +4102 -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.cjs +92 -0
- package/dist/app.d.cts +1 -2
- package/dist/app.d.ts +1 -2
- package/dist/app.js +93 -2
- package/dist/bundler.d.cts +1 -2
- package/dist/bundler.d.ts +1 -2
- package/dist/cli.cjs +1375 -71
- package/dist/cli.d.cts +89 -1
- package/dist/cli.d.ts +89 -1
- package/dist/cli.js +1373 -74
- package/dist/config-Bb6SULnR.cjs +46 -0
- package/dist/config-CfWa3q-k.js +28 -0
- package/dist/config-Cms0rvqg.d.ts +40 -0
- package/dist/config-CufOVJV3.d.cts +40 -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 +6 -1
- package/dist/crypto.d.ts +8 -1
- package/dist/crypto.js +2 -2
- package/dist/dev-protocol.d.cts +16 -3
- package/dist/dev-protocol.d.ts +16 -3
- package/dist/dev-protocol.js +1 -2
- 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.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-runtime-C85Nuc4F.js +10 -0
- package/dist/{native-runtime-CdeT0JyQ.cjs → native-runtime-CsXnXkQn.cjs} +1 -14
- 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 -7
- package/dist/runtime.d.ts +1 -7
- package/dist/schema-Bo5Jvqus.js +90 -0
- package/dist/schema-DOOjfXvs.d.ts +58 -0
- package/dist/schema-ENSMX_1t.d.cts +58 -0
- package/dist/schema-rQ13mrpD.cjs +102 -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-Bx0VlTlO.cjs +852 -0
- package/dist/state-CAwe-Vw1.js +767 -0
- package/dist/state.cjs +19 -0
- package/dist/state.d.cts +159 -0
- package/dist/state.d.ts +159 -0
- package/dist/state.js +6 -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-1WepaMS4.d.cts +92 -0
- package/dist/ui-234Plg7Z.d.cts +92 -0
- package/dist/ui-BJmXhz9Q.d.ts +92 -0
- package/dist/ui-Dd7SXdbg.d.cts +92 -0
- package/dist/ui-Ds88eETu.d.ts +92 -0
- package/dist/ui-TaqnB5SP.d.ts +92 -0
- package/dist/ui-WQvnXL0W.d.ts +92 -0
- package/dist/ui.cjs +364 -0
- package/dist/ui.d.cts +2 -0
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +357 -0
- 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/metro-config.cjs +18 -13
- package/package.json +57 -21
- package/templates/basic/app/index.tsx +133 -12
- package/templates/basic/app.config.ts +4 -2
- 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/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/fs.cjs +0 -3
- package/dist/fs.d.cts +0 -2
- package/dist/fs.d.ts +0 -2
- package/dist/fs.js +0 -3
- package/dist/native-runtime-BoqybBBf.js +0 -18
- package/dist/native-runtime-BoqybBBf.js.map +0 -1
- package/dist/native.cjs +0 -132
- package/dist/native.d.cts +0 -64
- package/dist/native.d.cts.map +0 -1
- package/dist/native.d.ts +0 -64
- package/dist/native.d.ts.map +0 -1
- package/dist/native.js +0 -133
- package/dist/native.js.map +0 -1
- package/dist/runtime.d.cts.map +0 -1
- package/dist/runtime.d.ts.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,33 +1,139 @@
|
|
|
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, 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";
|
|
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
|
-
const
|
|
19
|
+
const starterAppName = "Bleam Basic";
|
|
20
|
+
function artifactPlatform() {
|
|
21
|
+
return `macos-${process.arch}`;
|
|
22
|
+
}
|
|
23
|
+
function prettyPath(value) {
|
|
24
|
+
const home = os.homedir();
|
|
25
|
+
return value === home || value.startsWith(`${home}${path.sep}`) ? `~${value.slice(home.length)}` : value;
|
|
26
|
+
}
|
|
27
|
+
function commandSucceeds(command, args) {
|
|
28
|
+
const result = spawnSync(command, args, { stdio: "ignore" });
|
|
29
|
+
return !result.error && result.status === 0;
|
|
30
|
+
}
|
|
31
|
+
function commandText(command, args) {
|
|
32
|
+
return [command, ...args].join(" ");
|
|
33
|
+
}
|
|
34
|
+
function doctorHint(projectRoot) {
|
|
35
|
+
return projectRoot ? `Run ${bold(`bleam doctor ${projectRoot}`)} for diagnostics.` : `Run ${bold("bleam doctor")} for diagnostics.`;
|
|
36
|
+
}
|
|
37
|
+
function appConfigBuildExample() {
|
|
38
|
+
return [
|
|
39
|
+
"Example:",
|
|
40
|
+
"import { appConfig } from 'bleam'",
|
|
41
|
+
"",
|
|
42
|
+
"export default appConfig({",
|
|
43
|
+
" name: 'My App',",
|
|
44
|
+
" bundleIdentifier: 'com.example.myapp',",
|
|
45
|
+
" teamId: 'ABCDE12345',",
|
|
46
|
+
"})"
|
|
47
|
+
].join("\n");
|
|
48
|
+
}
|
|
49
|
+
function missingBuildFieldError(field) {
|
|
50
|
+
return /* @__PURE__ */ new Error(`app.config.ts must define ${field} for bleam build.\n\n${appConfigBuildExample()}`);
|
|
51
|
+
}
|
|
52
|
+
function heading(text) {
|
|
53
|
+
return bold(text);
|
|
54
|
+
}
|
|
55
|
+
function muted(text) {
|
|
56
|
+
return dim(text);
|
|
57
|
+
}
|
|
58
|
+
function statusLabel(status) {
|
|
59
|
+
if (status === "pass") return green("[ok]");
|
|
60
|
+
if (status === "warn") return yellow("[warn]");
|
|
61
|
+
return red("[fail]");
|
|
62
|
+
}
|
|
63
|
+
function startSpinner(text) {
|
|
64
|
+
if (!process.stderr.isTTY) {
|
|
65
|
+
console.log(text);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
return yoctoSpinner({
|
|
69
|
+
text,
|
|
70
|
+
handleSignals: false
|
|
71
|
+
}).start();
|
|
72
|
+
}
|
|
17
73
|
function printUsage() {
|
|
18
74
|
console.log(`Usage: bleam <command>
|
|
19
75
|
|
|
20
76
|
Commands:
|
|
21
|
-
new
|
|
22
|
-
|
|
77
|
+
new <dir> [--template <name>]
|
|
78
|
+
Create and run a Bleam project
|
|
79
|
+
dev [dir] [--port <port>] [--no-run] [--clean]
|
|
80
|
+
Start a Bleam dev server
|
|
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
|
|
89
|
+
doctor [dir] Check the local Bleam environment
|
|
90
|
+
typecheck [dir] Typecheck project files
|
|
91
|
+
check [dir] [--write] Format, lint, and organize imports
|
|
92
|
+
format [dir] [--check] Format project files
|
|
93
|
+
lint [dir] Lint project files`);
|
|
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
|
+
};
|
|
23
122
|
}
|
|
24
123
|
function invocationRoot() {
|
|
25
|
-
|
|
124
|
+
const pwd = process.env.PWD;
|
|
125
|
+
return pwd && path.isAbsolute(pwd) ? pwd : process.cwd();
|
|
126
|
+
}
|
|
127
|
+
function canonicalPath(value) {
|
|
128
|
+
const resolved = path.resolve(value);
|
|
129
|
+
return existsSync(resolved) ? realpathSync.native(resolved) : resolved;
|
|
26
130
|
}
|
|
27
131
|
function parseDevOptions(argv) {
|
|
28
132
|
const root = invocationRoot();
|
|
29
133
|
let projectRoot = root;
|
|
30
134
|
let port = 8081;
|
|
135
|
+
let run = true;
|
|
136
|
+
let clean = false;
|
|
31
137
|
let hasProjectRootArg = false;
|
|
32
138
|
for (let index = 0; index < argv.length; index += 1) {
|
|
33
139
|
const arg = argv[index];
|
|
@@ -40,6 +146,18 @@ function parseDevOptions(argv) {
|
|
|
40
146
|
port = Number(arg.slice(7));
|
|
41
147
|
continue;
|
|
42
148
|
}
|
|
149
|
+
if (arg === "--run") {
|
|
150
|
+
run = true;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (arg === "--no-run") {
|
|
154
|
+
run = false;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (arg === "--clean" || arg === "-c") {
|
|
158
|
+
clean = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
43
161
|
if (arg === "--help" || arg === "-h") {
|
|
44
162
|
printUsage();
|
|
45
163
|
process.exit(0);
|
|
@@ -51,17 +169,121 @@ function parseDevOptions(argv) {
|
|
|
51
169
|
if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid port: ${port}`);
|
|
52
170
|
if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
|
|
53
171
|
return {
|
|
54
|
-
projectRoot:
|
|
55
|
-
port
|
|
172
|
+
projectRoot: canonicalPath(projectRoot),
|
|
173
|
+
port,
|
|
174
|
+
run,
|
|
175
|
+
clean
|
|
56
176
|
};
|
|
57
177
|
}
|
|
58
178
|
function parseNewOptions(argv) {
|
|
179
|
+
const root = invocationRoot();
|
|
180
|
+
let projectRoot;
|
|
181
|
+
let template = "basic";
|
|
182
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
183
|
+
const arg = argv[index];
|
|
184
|
+
if (arg === "--help" || arg === "-h") {
|
|
185
|
+
printUsage();
|
|
186
|
+
process.exit(0);
|
|
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
|
+
}
|
|
196
|
+
if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
|
|
197
|
+
if (projectRoot) throw new Error("Only one project directory may be specified");
|
|
198
|
+
projectRoot = path.resolve(root, arg);
|
|
199
|
+
}
|
|
200
|
+
if (!projectRoot) throw new Error("Usage: bleam new <dir>");
|
|
201
|
+
return {
|
|
202
|
+
projectRoot: canonicalPath(projectRoot),
|
|
203
|
+
template
|
|
204
|
+
};
|
|
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
|
+
}
|
|
216
|
+
function parseBuildOptions(argv) {
|
|
217
|
+
const root = invocationRoot();
|
|
218
|
+
let projectRoot = root;
|
|
219
|
+
let hasProjectRootArg = false;
|
|
220
|
+
let output;
|
|
221
|
+
let configuration = "Release";
|
|
222
|
+
let clean = false;
|
|
223
|
+
let exportOptionsPlist;
|
|
224
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
225
|
+
const arg = argv[index];
|
|
226
|
+
if (arg === "--output" || arg === "-o") {
|
|
227
|
+
output = argv[++index];
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (arg.startsWith("--output=")) {
|
|
231
|
+
output = arg.slice(9);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (arg === "--configuration") {
|
|
235
|
+
const value = argv[++index];
|
|
236
|
+
if (!value) throw new Error("Missing --configuration value");
|
|
237
|
+
configuration = value;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (arg.startsWith("--configuration=")) {
|
|
241
|
+
configuration = arg.slice(16);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (arg === "--clean") {
|
|
245
|
+
clean = true;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (arg === "--export-options-plist") {
|
|
249
|
+
exportOptionsPlist = argv[++index];
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (arg.startsWith("--export-options-plist=")) {
|
|
253
|
+
exportOptionsPlist = arg.slice(23);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (arg === "--help" || arg === "-h") {
|
|
257
|
+
printUsage();
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
260
|
+
if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
|
|
261
|
+
projectRoot = path.resolve(root, arg);
|
|
262
|
+
hasProjectRootArg = true;
|
|
263
|
+
}
|
|
264
|
+
if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
|
|
265
|
+
const resolvedProjectRoot = canonicalPath(projectRoot);
|
|
266
|
+
return {
|
|
267
|
+
projectRoot: resolvedProjectRoot,
|
|
268
|
+
output: path.resolve(resolvedProjectRoot, output ?? "dist"),
|
|
269
|
+
configuration,
|
|
270
|
+
clean,
|
|
271
|
+
exportOptionsPlist: exportOptionsPlist ? path.resolve(root, exportOptionsPlist) : void 0
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function parseProjectToolOptions(argv) {
|
|
59
275
|
const root = invocationRoot();
|
|
60
276
|
let projectRoot = root;
|
|
61
|
-
let
|
|
277
|
+
let hasProjectRootArg = false;
|
|
278
|
+
let write = false;
|
|
279
|
+
let formatCheck = false;
|
|
62
280
|
for (const arg of argv) {
|
|
63
|
-
if (arg === "--
|
|
64
|
-
|
|
281
|
+
if (arg === "--write") {
|
|
282
|
+
write = true;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (arg === "--check") {
|
|
286
|
+
formatCheck = true;
|
|
65
287
|
continue;
|
|
66
288
|
}
|
|
67
289
|
if (arg === "--help" || arg === "-h") {
|
|
@@ -70,15 +292,35 @@ function parseNewOptions(argv) {
|
|
|
70
292
|
}
|
|
71
293
|
if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
|
|
72
294
|
projectRoot = path.resolve(root, arg);
|
|
295
|
+
hasProjectRootArg = true;
|
|
73
296
|
}
|
|
297
|
+
if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
|
|
74
298
|
return {
|
|
75
|
-
projectRoot,
|
|
76
|
-
|
|
299
|
+
projectRoot: canonicalPath(projectRoot),
|
|
300
|
+
write,
|
|
301
|
+
formatCheck
|
|
77
302
|
};
|
|
78
303
|
}
|
|
304
|
+
function parseDoctorOptions(argv) {
|
|
305
|
+
const root = invocationRoot();
|
|
306
|
+
let projectRoot = root;
|
|
307
|
+
let hasProjectRootArg = false;
|
|
308
|
+
for (const arg of argv) {
|
|
309
|
+
if (arg === "--help" || arg === "-h") {
|
|
310
|
+
printUsage();
|
|
311
|
+
process.exit(0);
|
|
312
|
+
}
|
|
313
|
+
if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
|
|
314
|
+
projectRoot = path.resolve(root, arg);
|
|
315
|
+
hasProjectRootArg = true;
|
|
316
|
+
}
|
|
317
|
+
if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
|
|
318
|
+
return { projectRoot: canonicalPath(projectRoot) };
|
|
319
|
+
}
|
|
79
320
|
function parseInternalMetroOptions(argv) {
|
|
80
321
|
let workspaceRoot;
|
|
81
322
|
let port = 8081;
|
|
323
|
+
let resetCache = false;
|
|
82
324
|
for (let index = 0; index < argv.length; index += 1) {
|
|
83
325
|
const arg = argv[index];
|
|
84
326
|
if (arg === "--workspace-root") {
|
|
@@ -93,13 +335,18 @@ function parseInternalMetroOptions(argv) {
|
|
|
93
335
|
port = Number(arg.slice(7));
|
|
94
336
|
continue;
|
|
95
337
|
}
|
|
338
|
+
if (arg === "--reset-cache") {
|
|
339
|
+
resetCache = true;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
96
342
|
throw new Error(`Unknown internal option: ${arg}`);
|
|
97
343
|
}
|
|
98
344
|
if (!workspaceRoot) throw new Error("Missing --workspace-root");
|
|
99
345
|
if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid port: ${port}`);
|
|
100
346
|
return {
|
|
101
347
|
workspaceRoot,
|
|
102
|
-
port
|
|
348
|
+
port,
|
|
349
|
+
resetCache
|
|
103
350
|
};
|
|
104
351
|
}
|
|
105
352
|
function isBleamProjectRoot(projectRoot) {
|
|
@@ -139,6 +386,28 @@ function assertStringField(value, field) {
|
|
|
139
386
|
if (typeof value !== "string" || value.trim() === "") throw new Error(`app.config.ts must define ${field}`);
|
|
140
387
|
return value;
|
|
141
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
|
+
}
|
|
394
|
+
function isHexColor(value) {
|
|
395
|
+
return typeof value === "string" && /^#[\da-f]{6}$/i.test(value);
|
|
396
|
+
}
|
|
397
|
+
function normalizeAppearance(value) {
|
|
398
|
+
const input = value && typeof value === "object" ? value : {};
|
|
399
|
+
const window = input.window && typeof input.window === "object" ? input.window : {};
|
|
400
|
+
const appearance = {};
|
|
401
|
+
if (window.backgroundColor !== void 0) {
|
|
402
|
+
if (!isHexColor(window.backgroundColor)) throw new Error("app.config.ts appearance.window.backgroundColor must be a #rrggbb color");
|
|
403
|
+
appearance.window = { backgroundColor: window.backgroundColor };
|
|
404
|
+
}
|
|
405
|
+
if (input.userInterfaceStyle !== void 0) {
|
|
406
|
+
if (input.userInterfaceStyle !== "automatic" && input.userInterfaceStyle !== "light" && input.userInterfaceStyle !== "dark") throw new Error("app.config.ts appearance.userInterfaceStyle must be automatic, light, or dark");
|
|
407
|
+
appearance.userInterfaceStyle = input.userInterfaceStyle;
|
|
408
|
+
}
|
|
409
|
+
return appearance;
|
|
410
|
+
}
|
|
142
411
|
function loadAppConfig(projectRoot) {
|
|
143
412
|
const configPath = path.join(projectRoot, "app.config.ts");
|
|
144
413
|
const loader = `
|
|
@@ -165,11 +434,15 @@ function loadAppConfig(projectRoot) {
|
|
|
165
434
|
});
|
|
166
435
|
if (result.status !== 0) throw new Error(result.stderr.trim() || "Failed to load app.config.ts");
|
|
167
436
|
const value = JSON.parse(result.stdout);
|
|
437
|
+
const bundleIdentifier = typeof value.bundleIdentifier === "string" ? value.bundleIdentifier : void 0;
|
|
168
438
|
return {
|
|
169
439
|
name: assertStringField(value.name, "name"),
|
|
170
440
|
version: typeof value.version === "string" ? value.version : void 0,
|
|
171
|
-
|
|
172
|
-
|
|
441
|
+
buildNumber: typeof value.buildNumber === "string" ? value.buildNumber : void 0,
|
|
442
|
+
bundleIdentifier,
|
|
443
|
+
appGroups: normalizeAppGroups(value.appGroups, bundleIdentifier),
|
|
444
|
+
teamId: typeof value.teamId === "string" ? value.teamId : void 0,
|
|
445
|
+
appearance: normalizeAppearance(value.appearance)
|
|
173
446
|
};
|
|
174
447
|
}
|
|
175
448
|
function assertBleamConfigFiles(projectRoot) {
|
|
@@ -188,8 +461,11 @@ function readProjectConfig(options) {
|
|
|
188
461
|
return {
|
|
189
462
|
name: config.name,
|
|
190
463
|
version: config.version,
|
|
464
|
+
buildNumber: config.buildNumber ?? "1",
|
|
191
465
|
bundleIdentifier: config.bundleIdentifier,
|
|
466
|
+
appGroups: config.appGroups ?? [],
|
|
192
467
|
teamId: config.teamId,
|
|
468
|
+
appearance: config.appearance ?? normalizeAppearance(void 0),
|
|
193
469
|
runtimeVersion,
|
|
194
470
|
sdkVersion: runtimeVersion
|
|
195
471
|
};
|
|
@@ -202,20 +478,43 @@ function nameFromDirectory(projectRoot) {
|
|
|
202
478
|
if (!name) return "Bleam App";
|
|
203
479
|
return name.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
204
480
|
}
|
|
205
|
-
function
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
481
|
+
function singleQuotedString(value) {
|
|
482
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n")}'`;
|
|
483
|
+
}
|
|
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}`);
|
|
211
496
|
const name = nameFromDirectory(options.projectRoot);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
name
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
+
}
|
|
219
518
|
console.log(`Created Bleam project at ${options.projectRoot}`);
|
|
220
519
|
}
|
|
221
520
|
function createDevWorkspace(projectRoot, config) {
|
|
@@ -229,14 +528,23 @@ function createDevWorkspace(projectRoot, config) {
|
|
|
229
528
|
});
|
|
230
529
|
symlinkSync(dependencyRoot, workspaceNodeModules);
|
|
231
530
|
ensureProjectTsconfig(projectRoot);
|
|
232
|
-
|
|
531
|
+
const expoConfig = {
|
|
233
532
|
name: config.name,
|
|
234
533
|
slug: slugFromName(config.name),
|
|
235
534
|
version: config.version ?? "0.0.0",
|
|
236
535
|
orientation: "default",
|
|
237
536
|
platforms: ["ios"],
|
|
238
|
-
newArchEnabled: true
|
|
239
|
-
|
|
537
|
+
newArchEnabled: true,
|
|
538
|
+
extra: { bleam: {
|
|
539
|
+
appearance: config.appearance,
|
|
540
|
+
buildNumber: config.buildNumber,
|
|
541
|
+
runtimeVersion: config.runtimeVersion,
|
|
542
|
+
sdkVersion: config.sdkVersion
|
|
543
|
+
} }
|
|
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`);
|
|
240
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`);
|
|
241
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`);
|
|
242
550
|
writeFileSync(path.join(workspaceRoot, "package.json"), `${JSON.stringify({
|
|
@@ -246,7 +554,9 @@ function createDevWorkspace(projectRoot, config) {
|
|
|
246
554
|
main: "index.js",
|
|
247
555
|
dependencies: {
|
|
248
556
|
"@types/react": packageJson.dependencies?.["@types/react"],
|
|
557
|
+
"@biomejs/biome": packageJson.dependencies?.["@biomejs/biome"],
|
|
249
558
|
expo: packageJson.dependencies?.expo,
|
|
559
|
+
"expo-splash-screen": packageJson.dependencies?.["expo-splash-screen"],
|
|
250
560
|
react: packageJson.dependencies?.react,
|
|
251
561
|
"react-native": packageJson.dependencies?.["react-native"],
|
|
252
562
|
typescript: packageJson.dependencies?.typescript
|
|
@@ -256,8 +566,8 @@ function createDevWorkspace(projectRoot, config) {
|
|
|
256
566
|
writeFileSync(path.join(workspaceRoot, "tsconfig.json"), `${JSON.stringify({
|
|
257
567
|
extends: "bleam/tsconfig",
|
|
258
568
|
compilerOptions: { paths: {
|
|
259
|
-
bleam: ["./node_modules/bleam"],
|
|
260
|
-
"bleam/*": ["./node_modules/bleam/*"],
|
|
569
|
+
bleam: ["./node_modules/bleam/dist/index"],
|
|
570
|
+
"bleam/*": ["./node_modules/bleam/dist/*"],
|
|
261
571
|
expo: ["./node_modules/expo"],
|
|
262
572
|
"expo/*": ["./node_modules/expo/*"],
|
|
263
573
|
react: ["./node_modules/@types/react"],
|
|
@@ -270,6 +580,34 @@ function createDevWorkspace(projectRoot, config) {
|
|
|
270
580
|
}, null, 2)}\n`);
|
|
271
581
|
return workspaceRoot;
|
|
272
582
|
}
|
|
583
|
+
function writeBiomeConfig(configPath) {
|
|
584
|
+
writeFileSync(configPath, `${JSON.stringify({
|
|
585
|
+
$schema: "https://biomejs.dev/schemas/2.5.2/schema.json",
|
|
586
|
+
files: { includes: [
|
|
587
|
+
"app/**/*",
|
|
588
|
+
"app.config.ts",
|
|
589
|
+
"tsconfig.json"
|
|
590
|
+
] },
|
|
591
|
+
formatter: {
|
|
592
|
+
enabled: true,
|
|
593
|
+
indentStyle: "space",
|
|
594
|
+
indentWidth: 2,
|
|
595
|
+
lineWidth: 80
|
|
596
|
+
},
|
|
597
|
+
javascript: { formatter: {
|
|
598
|
+
quoteStyle: "single",
|
|
599
|
+
semicolons: "asNeeded",
|
|
600
|
+
trailingCommas: "all"
|
|
601
|
+
} },
|
|
602
|
+
linter: {
|
|
603
|
+
enabled: true,
|
|
604
|
+
rules: {
|
|
605
|
+
preset: "recommended",
|
|
606
|
+
suspicious: { noExplicitAny: "off" }
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}, null, 2)}\n`);
|
|
610
|
+
}
|
|
273
611
|
function writeProjectRuntimeConfig(projectRoot, config, origin) {
|
|
274
612
|
const projectConfigPath = path.join(projectRoot, ".bleam", "project.json");
|
|
275
613
|
mkdirSync(path.dirname(projectConfigPath), { recursive: true });
|
|
@@ -278,28 +616,463 @@ function writeProjectRuntimeConfig(projectRoot, config, origin) {
|
|
|
278
616
|
version: config.version,
|
|
279
617
|
bundleIdentifier: config.bundleIdentifier,
|
|
280
618
|
teamId: config.teamId,
|
|
619
|
+
appearance: config.appearance,
|
|
281
620
|
devServer: origin,
|
|
282
621
|
runtimeVersion: config.runtimeVersion,
|
|
283
622
|
sdkVersion: config.sdkVersion
|
|
284
623
|
}, null, 2)}\n`);
|
|
285
624
|
return projectConfigPath;
|
|
286
625
|
}
|
|
626
|
+
function nativeRuntimeTemplateRoot() {
|
|
627
|
+
return path.resolve(packageRoot, "..", "..", "app", "bleam");
|
|
628
|
+
}
|
|
629
|
+
function safeArtifactName(name) {
|
|
630
|
+
return name.trim().replace(/[/:\\]/g, "-").replace(/\s+/g, " ") || "App";
|
|
631
|
+
}
|
|
632
|
+
function xcodeSettingValue(value) {
|
|
633
|
+
if (/^[A-Za-z0-9_.$-]+$/.test(value)) return value;
|
|
634
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
635
|
+
}
|
|
636
|
+
function plistString(value) {
|
|
637
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
638
|
+
}
|
|
639
|
+
function replacePlistString(plist, key, value) {
|
|
640
|
+
const pattern = /* @__PURE__ */ new RegExp(`(<key>${key}</key>\\s*<string>)([^<]*)(</string>)`);
|
|
641
|
+
if (!pattern.test(plist)) return plist;
|
|
642
|
+
return plist.replace(pattern, `$1${plistString(value)}$3`);
|
|
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
|
+
}
|
|
662
|
+
function writeGeneratedAppJson(nativeRoot, config) {
|
|
663
|
+
writeFileSync(path.join(nativeRoot, "app.json"), `${JSON.stringify({ expo: {
|
|
664
|
+
name: config.name,
|
|
665
|
+
slug: slugFromName(config.name),
|
|
666
|
+
version: config.version ?? "1.0.0",
|
|
667
|
+
platforms: ["ios"],
|
|
668
|
+
ios: {
|
|
669
|
+
appleTeamId: config.teamId,
|
|
670
|
+
buildNumber: config.buildNumber,
|
|
671
|
+
bundleIdentifier: config.bundleIdentifier
|
|
672
|
+
}
|
|
673
|
+
} }, null, 2)}\n`);
|
|
674
|
+
}
|
|
675
|
+
function patchNativeProject(nativeRoot, config) {
|
|
676
|
+
const projectPath = path.join(nativeRoot, "ios", "Bleam.xcodeproj", "project.pbxproj");
|
|
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");
|
|
682
|
+
const productName = xcodeSettingValue(config.name);
|
|
683
|
+
const bundleIdentifier = xcodeSettingValue(config.bundleIdentifier ?? "");
|
|
684
|
+
const generationBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.GenerationService`);
|
|
685
|
+
const platformBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.PlatformHelper`);
|
|
686
|
+
const teamId = xcodeSettingValue(config.teamId ?? "");
|
|
687
|
+
const version = xcodeSettingValue(config.version ?? "1.0.0");
|
|
688
|
+
const buildNumber = xcodeSettingValue(config.buildNumber);
|
|
689
|
+
const primaryAppGroup = config.appGroups[0];
|
|
690
|
+
let project = readFileSync(projectPath, "utf8");
|
|
691
|
+
project = project.replace(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+);/g, (_, current) => {
|
|
692
|
+
return `PRODUCT_BUNDLE_IDENTIFIER = ${current.endsWith(".GenerationService") ? generationBundleIdentifier : current.endsWith(".PlatformHelper") ? platformBundleIdentifier : bundleIdentifier};`;
|
|
693
|
+
}).replace(/DEVELOPMENT_TEAM = [^;]+;/g, () => {
|
|
694
|
+
return `DEVELOPMENT_TEAM = ${teamId};`;
|
|
695
|
+
}).replace(/MARKETING_VERSION = [^;]+;/g, () => {
|
|
696
|
+
return `MARKETING_VERSION = ${version};`;
|
|
697
|
+
}).replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, () => {
|
|
698
|
+
return `CURRENT_PROJECT_VERSION = ${buildNumber};`;
|
|
699
|
+
}).replace(/PRODUCT_NAME = bleam;/g, () => {
|
|
700
|
+
return `PRODUCT_NAME = ${productName};`;
|
|
701
|
+
});
|
|
702
|
+
writeFileSync(projectPath, project);
|
|
703
|
+
let infoPlist = readFileSync(infoPlistPath, "utf8");
|
|
704
|
+
infoPlist = replacePlistString(infoPlist, "CFBundleDisplayName", config.name);
|
|
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);
|
|
710
|
+
infoPlist = replacePlistString(infoPlist, "CFBundleVersion", config.buildNumber);
|
|
711
|
+
if (primaryAppGroup) infoPlist = replaceOrInsertPlistString(infoPlist, "AppGroupIdentifier", primaryAppGroup);
|
|
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
|
+
}
|
|
720
|
+
}
|
|
721
|
+
function createBuildNativeWorkspace(options, config) {
|
|
722
|
+
const nativeRoot = path.join(options.projectRoot, ".bleam", "build", "native");
|
|
723
|
+
const templateRoot = nativeRuntimeTemplateRoot();
|
|
724
|
+
if (options.clean) rmSync(nativeRoot, {
|
|
725
|
+
recursive: true,
|
|
726
|
+
force: true
|
|
727
|
+
});
|
|
728
|
+
mkdirSync(path.dirname(nativeRoot), { recursive: true });
|
|
729
|
+
cpSync(templateRoot, nativeRoot, {
|
|
730
|
+
recursive: true,
|
|
731
|
+
verbatimSymlinks: true,
|
|
732
|
+
filter(source) {
|
|
733
|
+
return !path.relative(templateRoot, source).split(path.sep).some((part) => {
|
|
734
|
+
return part === "build" || part === "dist" || part === "node_modules" || part === ".expo" || part === "Pods" || part === ".xcode.env.local" || part === ".DS_Store" || part.endsWith(".xcworkspace");
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
const nodeModules = path.join(nativeRoot, "node_modules");
|
|
739
|
+
rmSync(nodeModules, {
|
|
740
|
+
recursive: true,
|
|
741
|
+
force: true
|
|
742
|
+
});
|
|
743
|
+
symlinkSync(resolvedDependencyRoot(), nodeModules);
|
|
744
|
+
writeGeneratedAppJson(nativeRoot, config);
|
|
745
|
+
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`);
|
|
746
|
+
writeFileSync(path.join(nativeRoot, "metro.config.js"), `const { getDefaultConfig } = require(${JSON.stringify(path.join(packageRoot, "metro-config.cjs"))})\n\nmodule.exports = getDefaultConfig(__dirname)\n`);
|
|
747
|
+
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`);
|
|
748
|
+
patchNativeProject(nativeRoot, config);
|
|
749
|
+
return nativeRoot;
|
|
750
|
+
}
|
|
751
|
+
function preflightBuild(options, config) {
|
|
752
|
+
if (process.platform !== "darwin") throw new Error(`bleam build requires macOS and Xcode. Current platform: ${process.platform}.\n${doctorHint(options.projectRoot)}`);
|
|
753
|
+
if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
|
|
754
|
+
if (!config.teamId) throw missingBuildFieldError("teamId");
|
|
755
|
+
const templateRoot = nativeRuntimeTemplateRoot();
|
|
756
|
+
if (!existsSync(templateRoot)) throw new Error(`Missing native runtime template: ${templateRoot}\n${doctorHint(options.projectRoot)}`);
|
|
757
|
+
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)}`);
|
|
758
|
+
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)}`);
|
|
759
|
+
}
|
|
760
|
+
function runInheritedCommand(command, args, cwd) {
|
|
761
|
+
const text = commandText(command, args);
|
|
762
|
+
const result = spawnSync(command, args, {
|
|
763
|
+
cwd,
|
|
764
|
+
stdio: "inherit",
|
|
765
|
+
env: process.env
|
|
766
|
+
});
|
|
767
|
+
if (result.error) throw new Error(`${text} could not start in ${cwd}: ${result.error.message}\n${doctorHint()}`);
|
|
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()}`);
|
|
769
|
+
}
|
|
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
|
+
}
|
|
777
|
+
console.log("Installing native dependencies...");
|
|
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");
|
|
792
|
+
}
|
|
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 [
|
|
802
|
+
"-allowProvisioningUpdates",
|
|
803
|
+
"-workspace",
|
|
804
|
+
path.join(options.nativeRoot, "ios", "Bleam.xcworkspace"),
|
|
805
|
+
"-scheme",
|
|
806
|
+
"Bleam",
|
|
807
|
+
"-configuration",
|
|
808
|
+
options.configuration,
|
|
809
|
+
"-destination",
|
|
810
|
+
"platform=macOS,arch=arm64,variant=Mac Catalyst",
|
|
811
|
+
"-archivePath",
|
|
812
|
+
options.archivePath,
|
|
813
|
+
"ARCHS=arm64",
|
|
814
|
+
"archive"
|
|
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);
|
|
831
|
+
return archivePath;
|
|
832
|
+
}
|
|
833
|
+
function exportNativeArchive(options) {
|
|
834
|
+
const exportPath = path.join(options.outputRoot, "macos", "export");
|
|
835
|
+
rmSync(exportPath, {
|
|
836
|
+
recursive: true,
|
|
837
|
+
force: true
|
|
838
|
+
});
|
|
839
|
+
mkdirSync(exportPath, { recursive: true });
|
|
840
|
+
console.log("Exporting macOS archive...");
|
|
841
|
+
runInheritedCommand("xcodebuild", [
|
|
842
|
+
"-allowProvisioningUpdates",
|
|
843
|
+
"-exportArchive",
|
|
844
|
+
"-archivePath",
|
|
845
|
+
options.archivePath,
|
|
846
|
+
"-exportPath",
|
|
847
|
+
exportPath,
|
|
848
|
+
"-exportOptionsPlist",
|
|
849
|
+
options.exportOptionsPlist
|
|
850
|
+
], path.dirname(options.archivePath));
|
|
851
|
+
return exportPath;
|
|
852
|
+
}
|
|
853
|
+
async function buildNativeArchives(options) {
|
|
854
|
+
const config = readProjectConfig(options);
|
|
855
|
+
preflightBuild(options, config);
|
|
856
|
+
const nativeRoot = createBuildNativeWorkspace(options, config);
|
|
857
|
+
installPods(nativeRoot);
|
|
858
|
+
const archivePath = archiveNativeApp({
|
|
859
|
+
nativeRoot,
|
|
860
|
+
outputRoot: options.output,
|
|
861
|
+
configuration: options.configuration,
|
|
862
|
+
config
|
|
863
|
+
});
|
|
864
|
+
console.log(`Created ${archivePath}`);
|
|
865
|
+
if (options.exportOptionsPlist) {
|
|
866
|
+
const exportPath = exportNativeArchive({
|
|
867
|
+
archivePath,
|
|
868
|
+
outputRoot: options.output,
|
|
869
|
+
exportOptionsPlist: options.exportOptionsPlist
|
|
870
|
+
});
|
|
871
|
+
console.log(`Exported ${exportPath}`);
|
|
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
|
+
};
|
|
1044
|
+
}
|
|
287
1045
|
function runtimeCacheRoot() {
|
|
288
|
-
return path.join(os.homedir(), "Library", "Caches", "bleam", "runtimes", runtimeVersion,
|
|
1046
|
+
return path.join(os.homedir(), "Library", "Caches", "bleam", "runtimes", runtimeVersion, artifactPlatform());
|
|
289
1047
|
}
|
|
290
1048
|
function localDevelopmentRuntimeAppPath() {
|
|
291
|
-
|
|
1049
|
+
const productsRoot = path.resolve(packageRoot, "..", "..", "app", "bleam", "build", "Build", "Products", "Debug-maccatalyst");
|
|
1050
|
+
for (const appName of ["Bleam.app", "bleam.app"]) {
|
|
1051
|
+
const appPath = path.join(productsRoot, appName);
|
|
1052
|
+
if (existsSync(appPath)) return appPath;
|
|
1053
|
+
}
|
|
1054
|
+
return path.join(productsRoot, "Bleam.app");
|
|
292
1055
|
}
|
|
293
1056
|
function runtimeReleaseBaseUrl() {
|
|
294
1057
|
return process.env.BLEAM_RUNTIME_RELEASE_BASE_URL ?? "https://github.com/bajedev/bleam-app/releases/download";
|
|
295
1058
|
}
|
|
296
1059
|
function runtimeArchiveName() {
|
|
297
|
-
return `bleam-runtime-v${runtimeVersion}-${
|
|
1060
|
+
return `bleam-runtime-v${runtimeVersion}-${artifactPlatform()}.zip`;
|
|
298
1061
|
}
|
|
299
1062
|
function runtimeArchiveUrl() {
|
|
300
1063
|
const tag = encodeURIComponent(`bleam@${runtimeVersion}`);
|
|
301
1064
|
return `${runtimeReleaseBaseUrl()}/${tag}/${runtimeArchiveName()}`;
|
|
302
1065
|
}
|
|
1066
|
+
function runtimeResolutionDetails() {
|
|
1067
|
+
return [
|
|
1068
|
+
`Runtime version: ${runtimeVersion}`,
|
|
1069
|
+
`Runtime platform: ${artifactPlatform()}`,
|
|
1070
|
+
`Runtime cache: ${runtimeCacheRoot()}`,
|
|
1071
|
+
`Release URL: ${runtimeArchiveUrl()}`,
|
|
1072
|
+
`Override with BLEAM_RUNTIME_APP=/path/to/Bleam.app for local testing.`,
|
|
1073
|
+
doctorHint()
|
|
1074
|
+
].join("\n");
|
|
1075
|
+
}
|
|
303
1076
|
function sha256(filePath) {
|
|
304
1077
|
return createHash("sha256").update(readFileSync(filePath)).digest("hex");
|
|
305
1078
|
}
|
|
@@ -314,19 +1087,26 @@ async function downloadExpectedSha256(url) {
|
|
|
314
1087
|
return (await response.text()).trim().split(/\s+/)[0];
|
|
315
1088
|
}
|
|
316
1089
|
async function downloadRuntimeArtifact() {
|
|
317
|
-
if (process.platform !== "darwin") throw new Error("Bleam runtime artifacts are currently only available
|
|
1090
|
+
if (process.platform !== "darwin") throw new Error("Bleam runtime artifacts are currently only available from macOS hosts.");
|
|
1091
|
+
const runtimePlatform = artifactPlatform();
|
|
318
1092
|
const cacheRoot = runtimeCacheRoot();
|
|
319
1093
|
const cachedAppPath = path.join(cacheRoot, "Bleam.app");
|
|
320
|
-
if (existsSync(cachedAppPath))
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
+
}
|
|
325
1102
|
mkdirSync(cacheRoot, { recursive: true });
|
|
326
1103
|
const archiveUrl = runtimeArchiveUrl();
|
|
327
1104
|
const archivePath = path.join(cacheRoot, runtimeArchiveName());
|
|
328
1105
|
const extractRoot = path.join(cacheRoot, "extract");
|
|
1106
|
+
console.log(`Downloading Bleam runtime ${runtimeVersion} for ${runtimePlatform}`);
|
|
1107
|
+
console.log(`Runtime archive: ${archiveUrl}`);
|
|
329
1108
|
await downloadFile(archiveUrl, archivePath);
|
|
1109
|
+
console.log("Verifying Bleam runtime archive");
|
|
330
1110
|
const expectedSha256 = await downloadExpectedSha256(archiveUrl);
|
|
331
1111
|
if (expectedSha256 && sha256(archivePath) !== expectedSha256) {
|
|
332
1112
|
rmSync(archivePath, { force: true });
|
|
@@ -337,6 +1117,7 @@ async function downloadRuntimeArtifact() {
|
|
|
337
1117
|
force: true
|
|
338
1118
|
});
|
|
339
1119
|
mkdirSync(extractRoot, { recursive: true });
|
|
1120
|
+
console.log("Extracting Bleam runtime archive");
|
|
340
1121
|
execFileSync("ditto", [
|
|
341
1122
|
"-x",
|
|
342
1123
|
"-k",
|
|
@@ -349,11 +1130,15 @@ async function downloadRuntimeArtifact() {
|
|
|
349
1130
|
recursive: true,
|
|
350
1131
|
force: true
|
|
351
1132
|
});
|
|
352
|
-
cpSync(extractedAppPath, cachedAppPath, {
|
|
1133
|
+
cpSync(extractedAppPath, cachedAppPath, {
|
|
1134
|
+
recursive: true,
|
|
1135
|
+
verbatimSymlinks: true
|
|
1136
|
+
});
|
|
353
1137
|
rmSync(extractRoot, {
|
|
354
1138
|
recursive: true,
|
|
355
1139
|
force: true
|
|
356
1140
|
});
|
|
1141
|
+
console.log(`Cached Bleam runtime at ${prettyPath(cachedAppPath)}`);
|
|
357
1142
|
return {
|
|
358
1143
|
appPath: cachedAppPath,
|
|
359
1144
|
version: runtimeVersion,
|
|
@@ -361,10 +1146,13 @@ async function downloadRuntimeArtifact() {
|
|
|
361
1146
|
};
|
|
362
1147
|
}
|
|
363
1148
|
async function resolveRuntimeArtifact() {
|
|
1149
|
+
const runtimePlatform = artifactPlatform();
|
|
1150
|
+
console.log(`Resolving Bleam runtime ${runtimeVersion} for ${runtimePlatform}`);
|
|
364
1151
|
const overrideAppPath = process.env.BLEAM_RUNTIME_APP;
|
|
365
1152
|
if (overrideAppPath) {
|
|
366
1153
|
const appPath = path.resolve(overrideAppPath);
|
|
367
|
-
if (!existsSync(appPath)) throw new Error(`BLEAM_RUNTIME_APP
|
|
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)}`);
|
|
368
1156
|
return {
|
|
369
1157
|
appPath,
|
|
370
1158
|
version: runtimeVersion,
|
|
@@ -372,23 +1160,47 @@ async function resolveRuntimeArtifact() {
|
|
|
372
1160
|
};
|
|
373
1161
|
}
|
|
374
1162
|
const localAppPath = localDevelopmentRuntimeAppPath();
|
|
375
|
-
if (existsSync(localAppPath))
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
+
}
|
|
1171
|
+
try {
|
|
1172
|
+
return await downloadRuntimeArtifact();
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
throw new Error(`Failed to resolve the Bleam runtime.\n${runtimeResolutionDetails()}\n\nCause: ${error instanceof Error ? error.message : String(error)}`);
|
|
1175
|
+
}
|
|
381
1176
|
}
|
|
382
1177
|
function copyRuntimeClient(projectRoot, runtime) {
|
|
383
1178
|
const clientAppPath = path.join(projectRoot, ".bleam", "client", "Bleam.app");
|
|
1179
|
+
console.log(`Preparing Bleam app at ${prettyPath(clientAppPath)}`);
|
|
384
1180
|
rmSync(clientAppPath, {
|
|
385
1181
|
recursive: true,
|
|
386
1182
|
force: true
|
|
387
1183
|
});
|
|
388
1184
|
mkdirSync(path.dirname(clientAppPath), { recursive: true });
|
|
389
|
-
cpSync(runtime.appPath, clientAppPath, {
|
|
1185
|
+
cpSync(runtime.appPath, clientAppPath, {
|
|
1186
|
+
recursive: true,
|
|
1187
|
+
verbatimSymlinks: true
|
|
1188
|
+
});
|
|
390
1189
|
return clientAppPath;
|
|
391
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
|
+
}
|
|
392
1204
|
function isBleamManifest(value) {
|
|
393
1205
|
const manifest = value && typeof value === "object" ? value : {};
|
|
394
1206
|
return typeof manifest.name === "string" && typeof manifest.bundleUrl === "string" && manifest.platform === "macos" && manifest.dev === true;
|
|
@@ -396,24 +1208,113 @@ function isBleamManifest(value) {
|
|
|
396
1208
|
async function readDevManifest(origin, timeoutMs = 3e4) {
|
|
397
1209
|
const manifestUrl = `${origin}/bleam/manifest.json`;
|
|
398
1210
|
const startedAt = Date.now();
|
|
399
|
-
|
|
1211
|
+
const spinner = startSpinner("Waiting for Bleam dev server...");
|
|
400
1212
|
while (Date.now() - startedAt <= timeoutMs) {
|
|
401
1213
|
try {
|
|
402
1214
|
const response = await fetch(manifestUrl, { cache: "no-store" });
|
|
403
1215
|
const value = await response.json();
|
|
404
|
-
if (response.ok && isBleamManifest(value))
|
|
1216
|
+
if (response.ok && isBleamManifest(value)) {
|
|
1217
|
+
spinner?.success("Bleam dev server is ready");
|
|
1218
|
+
return value;
|
|
1219
|
+
}
|
|
405
1220
|
} catch {}
|
|
406
1221
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
407
1222
|
}
|
|
1223
|
+
spinner?.error("Bleam dev server did not become available");
|
|
408
1224
|
throw new Error(`Bleam manifest did not become available: ${manifestUrl}`);
|
|
409
1225
|
}
|
|
1226
|
+
function pushDoctorCheck(checks, status, label, detail) {
|
|
1227
|
+
checks.push({
|
|
1228
|
+
status,
|
|
1229
|
+
label,
|
|
1230
|
+
detail
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
function printDoctorSection(title, checks) {
|
|
1234
|
+
if (checks.length === 0) return;
|
|
1235
|
+
console.log(`\n${heading(title)}`);
|
|
1236
|
+
for (const check of checks) {
|
|
1237
|
+
const detail = check.detail ? muted(`: ${check.detail}`) : "";
|
|
1238
|
+
console.log(` ${statusLabel(check.status)} ${check.label}${detail}`);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
function runDoctor(options) {
|
|
1242
|
+
const projectChecks = [];
|
|
1243
|
+
const runtimeChecks = [];
|
|
1244
|
+
const environmentChecks = [];
|
|
1245
|
+
const appConfigPath = path.join(options.projectRoot, "app.config.ts");
|
|
1246
|
+
const appEntryPath = path.join(options.projectRoot, "app", "index.tsx");
|
|
1247
|
+
if (existsSync(appConfigPath)) pushDoctorCheck(projectChecks, "pass", "app.config.ts found");
|
|
1248
|
+
else pushDoctorCheck(projectChecks, "fail", "app.config.ts missing");
|
|
1249
|
+
if (existsSync(appEntryPath)) pushDoctorCheck(projectChecks, "pass", "app/index.tsx found");
|
|
1250
|
+
else pushDoctorCheck(projectChecks, "fail", "app/index.tsx missing");
|
|
1251
|
+
try {
|
|
1252
|
+
const config = readProjectConfig(options);
|
|
1253
|
+
pushDoctorCheck(projectChecks, "pass", "config loaded");
|
|
1254
|
+
pushDoctorCheck(projectChecks, "pass", "name", config.name);
|
|
1255
|
+
if (config.bundleIdentifier) pushDoctorCheck(projectChecks, "pass", "bundleIdentifier", config.bundleIdentifier);
|
|
1256
|
+
else pushDoctorCheck(projectChecks, "warn", "bundleIdentifier not configured", "required for bleam build");
|
|
1257
|
+
if (config.teamId) pushDoctorCheck(projectChecks, "pass", "teamId", config.teamId);
|
|
1258
|
+
else pushDoctorCheck(projectChecks, "warn", "teamId not configured", "required for bleam build");
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
pushDoctorCheck(projectChecks, "fail", "config failed to load", error instanceof Error ? error.message : String(error));
|
|
1261
|
+
}
|
|
1262
|
+
const cacheRoot = runtimeCacheRoot();
|
|
1263
|
+
const cachedAppPath = path.join(cacheRoot, "Bleam.app");
|
|
1264
|
+
const localAppPath = localDevelopmentRuntimeAppPath();
|
|
1265
|
+
pushDoctorCheck(runtimeChecks, "pass", "version", runtimeVersion);
|
|
1266
|
+
pushDoctorCheck(runtimeChecks, "pass", "platform", artifactPlatform());
|
|
1267
|
+
pushDoctorCheck(runtimeChecks, "pass", "cache", prettyPath(cacheRoot));
|
|
1268
|
+
if (existsSync(cachedAppPath)) pushDoctorCheck(runtimeChecks, "pass", "cached runtime found");
|
|
1269
|
+
else pushDoctorCheck(runtimeChecks, "warn", "runtime not cached yet", "bleam dev can download it on demand");
|
|
1270
|
+
if (existsSync(localAppPath)) pushDoctorCheck(runtimeChecks, "pass", "local development runtime found", prettyPath(localAppPath));
|
|
1271
|
+
else pushDoctorCheck(runtimeChecks, "warn", "local development runtime not found", prettyPath(localAppPath));
|
|
1272
|
+
const overrideAppPath = process.env.BLEAM_RUNTIME_APP;
|
|
1273
|
+
if (overrideAppPath) {
|
|
1274
|
+
const appPath = path.resolve(overrideAppPath);
|
|
1275
|
+
pushDoctorCheck(runtimeChecks, existsSync(appPath) ? "pass" : "fail", "BLEAM_RUNTIME_APP", prettyPath(appPath));
|
|
1276
|
+
}
|
|
1277
|
+
pushDoctorCheck(runtimeChecks, "pass", "release artifact URL", runtimeArchiveUrl());
|
|
1278
|
+
pushDoctorCheck(environmentChecks, process.platform === "darwin" ? "pass" : "fail", "macOS host", process.platform);
|
|
1279
|
+
pushDoctorCheck(environmentChecks, "pass", "node", prettyPath(process.execPath));
|
|
1280
|
+
const hasXcodebuild = commandSucceeds("xcodebuild", ["-version"]);
|
|
1281
|
+
pushDoctorCheck(environmentChecks, hasXcodebuild ? "pass" : "warn", "xcodebuild", hasXcodebuild ? "available" : "unavailable");
|
|
1282
|
+
const hasPod = commandSucceeds("pod", ["--version"]);
|
|
1283
|
+
pushDoctorCheck(environmentChecks, hasPod ? "pass" : "warn", "pod", hasPod ? "available" : "unavailable");
|
|
1284
|
+
const allChecks = [
|
|
1285
|
+
...projectChecks,
|
|
1286
|
+
...runtimeChecks,
|
|
1287
|
+
...environmentChecks
|
|
1288
|
+
];
|
|
1289
|
+
const counts = {
|
|
1290
|
+
pass: allChecks.filter((check) => check.status === "pass").length,
|
|
1291
|
+
warn: allChecks.filter((check) => check.status === "warn").length,
|
|
1292
|
+
fail: allChecks.filter((check) => check.status === "fail").length
|
|
1293
|
+
};
|
|
1294
|
+
console.log(heading("Bleam Doctor"));
|
|
1295
|
+
printDoctorSection("Project", projectChecks);
|
|
1296
|
+
printDoctorSection("Runtime", runtimeChecks);
|
|
1297
|
+
printDoctorSection("Environment", environmentChecks);
|
|
1298
|
+
console.log(`\n${heading("Summary")}`);
|
|
1299
|
+
console.log(` ${green(`${counts.pass} passed`)}, ${yellow(`${counts.warn} warnings`)}, ${counts.fail > 0 ? red(`${counts.fail} failed`) : green("0 failed")}`);
|
|
1300
|
+
if (counts.fail > 0) process.exit(1);
|
|
1301
|
+
}
|
|
410
1302
|
function formatDuration(ms) {
|
|
411
1303
|
return `${(ms / 1e3).toFixed(1)}s`;
|
|
412
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
|
+
}
|
|
413
1314
|
async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
|
|
414
1315
|
const startedAt = Date.now();
|
|
415
1316
|
const requestTimeoutMs = 3e4;
|
|
416
|
-
|
|
1317
|
+
const spinner = startSpinner("Bundling app...");
|
|
417
1318
|
while (Date.now() - startedAt <= timeoutMs) {
|
|
418
1319
|
const requestStartedAt = Date.now();
|
|
419
1320
|
try {
|
|
@@ -421,19 +1322,32 @@ async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
|
|
|
421
1322
|
cache: "no-store",
|
|
422
1323
|
signal: AbortSignal.timeout(requestTimeoutMs)
|
|
423
1324
|
});
|
|
424
|
-
await response.body?.cancel();
|
|
425
1325
|
if (response.ok) {
|
|
426
|
-
|
|
1326
|
+
await response.body?.cancel();
|
|
1327
|
+
spinner?.success(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
|
|
1328
|
+
if (!spinner) console.log(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
|
|
427
1329
|
return;
|
|
428
1330
|
}
|
|
429
|
-
|
|
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);
|
|
430
1336
|
} catch (error) {
|
|
431
|
-
if (error instanceof Error && error.name === "TimeoutError")
|
|
432
|
-
|
|
1337
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
1338
|
+
const message = `Bundle request timed out after ${formatDuration(Date.now() - requestStartedAt)}, retrying...`;
|
|
1339
|
+
if (spinner) spinner.text = message;
|
|
1340
|
+
else console.log(message);
|
|
1341
|
+
} else {
|
|
1342
|
+
const message = `Bundle request failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
1343
|
+
if (spinner) spinner.text = `${message}, retrying...`;
|
|
1344
|
+
else console.log(message);
|
|
1345
|
+
}
|
|
433
1346
|
}
|
|
434
1347
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
435
1348
|
}
|
|
436
|
-
|
|
1349
|
+
spinner?.error("Bundle did not complete");
|
|
1350
|
+
throw new Error(`Bleam bundle did not complete within ${formatDuration(timeoutMs)}.\nBundle URL: ${bundleUrl}\n${doctorHint()}`);
|
|
437
1351
|
}
|
|
438
1352
|
function openRuntimeClient(appPath, projectConfigPath, manifest) {
|
|
439
1353
|
const child = spawn("open", [
|
|
@@ -454,18 +1368,234 @@ function openRuntimeClient(appPath, projectConfigPath, manifest) {
|
|
|
454
1368
|
if (code && code !== 0) console.error(`Failed to open Bleam runtime at ${appPath}`);
|
|
455
1369
|
});
|
|
456
1370
|
}
|
|
1371
|
+
function quitRuntimeClient() {
|
|
1372
|
+
spawnSync("osascript", ["-e", "tell application id \"dev.bleam.app\" to quit"], { stdio: "ignore" });
|
|
1373
|
+
}
|
|
1374
|
+
async function launchRuntimeClient(appPath, projectConfigPath, manifest) {
|
|
1375
|
+
quitRuntimeClient();
|
|
1376
|
+
openRuntimeClient(appPath, projectConfigPath, manifest);
|
|
1377
|
+
}
|
|
1378
|
+
function focusRuntimeClient() {
|
|
1379
|
+
return spawnSync("osascript", ["-e", "tell application id \"dev.bleam.app\" to activate"], { stdio: "ignore" }).status === 0;
|
|
1380
|
+
}
|
|
1381
|
+
function sendJson(res, status, value) {
|
|
1382
|
+
res.writeHead(status, {
|
|
1383
|
+
"Cache-Control": "no-store",
|
|
1384
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
1385
|
+
});
|
|
1386
|
+
res.end(JSON.stringify(value));
|
|
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
|
+
}
|
|
1431
|
+
async function requestRuntimeReload(origin) {
|
|
1432
|
+
const response = await fetch(`${origin}/bleam/reload`, {
|
|
1433
|
+
method: "POST",
|
|
1434
|
+
cache: "no-store"
|
|
1435
|
+
});
|
|
1436
|
+
const value = await response.json();
|
|
1437
|
+
if (!response.ok) throw new Error(`Reload request failed: ${response.status}`);
|
|
1438
|
+
const reload = value;
|
|
1439
|
+
return typeof reload.clients === "number" ? reload.clients : 0;
|
|
1440
|
+
}
|
|
1441
|
+
function biomeBinPath() {
|
|
1442
|
+
const biomePackageJsonPath = require.resolve("@biomejs/biome/package.json");
|
|
1443
|
+
const biomePackageRoot = path.dirname(biomePackageJsonPath);
|
|
1444
|
+
const biomePackage = require("@biomejs/biome/package.json");
|
|
1445
|
+
const bin = typeof biomePackage.bin === "string" ? biomePackage.bin : biomePackage.bin?.biome;
|
|
1446
|
+
if (!bin) throw new Error("Could not resolve @biomejs/biome binary");
|
|
1447
|
+
return path.join(biomePackageRoot, bin);
|
|
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
|
+
}
|
|
1456
|
+
function ensureProjectToolWorkspace(options) {
|
|
1457
|
+
const config = readProjectConfig({ projectRoot: options.projectRoot });
|
|
1458
|
+
return createDevWorkspace(options.projectRoot, config);
|
|
1459
|
+
}
|
|
1460
|
+
function runBiomeCommand(command, options) {
|
|
1461
|
+
assertBleamConfigFiles(options.projectRoot);
|
|
1462
|
+
ensureProjectToolWorkspace(options);
|
|
1463
|
+
const configPath = path.join(options.projectRoot, "biome.json");
|
|
1464
|
+
const existingConfig = existsSync(configPath) ? readFileSync(configPath, "utf8") : void 0;
|
|
1465
|
+
const args = [
|
|
1466
|
+
command,
|
|
1467
|
+
"--config-path",
|
|
1468
|
+
"biome.json"
|
|
1469
|
+
];
|
|
1470
|
+
if (command === "check" && options.write) args.push("--write");
|
|
1471
|
+
if (command === "format" && !options.formatCheck) args.push("--write");
|
|
1472
|
+
args.push("app", "app.config.ts", "tsconfig.json");
|
|
1473
|
+
writeBiomeConfig(configPath);
|
|
1474
|
+
const result = spawnSync(biomeBinPath(), args, {
|
|
1475
|
+
cwd: options.projectRoot,
|
|
1476
|
+
stdio: "inherit"
|
|
1477
|
+
});
|
|
1478
|
+
if (existingConfig === void 0) rmSync(configPath, { force: true });
|
|
1479
|
+
else writeFileSync(configPath, existingConfig);
|
|
1480
|
+
if (result.error) throw result.error;
|
|
1481
|
+
process.exit(result.status ?? 1);
|
|
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
|
+
}
|
|
1497
|
+
function setupInteractiveControls(options) {
|
|
1498
|
+
if (!process.stdin.isTTY) return () => {};
|
|
1499
|
+
const wasRaw = process.stdin.isRaw;
|
|
1500
|
+
let closed = false;
|
|
1501
|
+
const cleanup = () => {
|
|
1502
|
+
if (closed) return;
|
|
1503
|
+
closed = true;
|
|
1504
|
+
process.stdin.off("data", onData);
|
|
1505
|
+
process.stdin.setRawMode(wasRaw);
|
|
1506
|
+
};
|
|
1507
|
+
const onData = (data) => {
|
|
1508
|
+
for (const byte of data) {
|
|
1509
|
+
if (byte === 3) {
|
|
1510
|
+
cleanup();
|
|
1511
|
+
options.stop();
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
const key = String.fromCharCode(byte).toLowerCase();
|
|
1515
|
+
if (key === "o") {
|
|
1516
|
+
(async () => {
|
|
1517
|
+
try {
|
|
1518
|
+
if (focusRuntimeClient()) {
|
|
1519
|
+
console.log("Focused Bleam app");
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
console.log(`Opening Bleam app at ${prettyPath(options.clientAppPath)}`);
|
|
1523
|
+
await launchRuntimeClient(options.clientAppPath, options.projectConfigPath, options.manifest);
|
|
1524
|
+
} catch (error) {
|
|
1525
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1526
|
+
}
|
|
1527
|
+
})();
|
|
1528
|
+
continue;
|
|
1529
|
+
}
|
|
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
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
};
|
|
1540
|
+
process.stdin.setRawMode(true);
|
|
1541
|
+
process.stdin.resume();
|
|
1542
|
+
process.stdin.on("data", onData);
|
|
1543
|
+
console.log("Press o to open, r to reload, Ctrl+C to stop.");
|
|
1544
|
+
return cleanup;
|
|
1545
|
+
}
|
|
457
1546
|
async function startInternalMetroServer(options) {
|
|
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;
|
|
458
1553
|
const metro = require("@expo/metro/metro");
|
|
459
1554
|
const { loadConfig } = require("@expo/metro/metro-config");
|
|
460
1555
|
const config = await loadConfig({
|
|
461
1556
|
config: path.join(options.workspaceRoot, "metro.config.js"),
|
|
462
1557
|
cwd: options.workspaceRoot,
|
|
463
|
-
port: options.port
|
|
1558
|
+
port: options.port,
|
|
1559
|
+
resetCache: options.resetCache
|
|
464
1560
|
});
|
|
465
1561
|
const { middleware, end, attachHmrServer } = await metro.createConnectMiddleware(config, {
|
|
466
1562
|
watch: true,
|
|
467
1563
|
waitForBundler: true
|
|
468
1564
|
});
|
|
1565
|
+
const messageClients = /* @__PURE__ */ new Set();
|
|
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
|
+
};
|
|
1577
|
+
const broadcastReload = () => {
|
|
1578
|
+
const message = JSON.stringify({
|
|
1579
|
+
version: 2,
|
|
1580
|
+
method: "reload",
|
|
1581
|
+
params: {}
|
|
1582
|
+
});
|
|
1583
|
+
let clients = 0;
|
|
1584
|
+
for (const client of messageClients) if (client.readyState === WebSocket.OPEN) {
|
|
1585
|
+
client.send(message);
|
|
1586
|
+
clients += 1;
|
|
1587
|
+
}
|
|
1588
|
+
return clients;
|
|
1589
|
+
};
|
|
1590
|
+
messageServer.on("connection", (client) => {
|
|
1591
|
+
messageClients.add(client);
|
|
1592
|
+
client.on("close", () => messageClients.delete(client));
|
|
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
|
+
});
|
|
469
1599
|
const server = http.createServer((req, res) => {
|
|
470
1600
|
if (req.url?.split("?")[0] === "/status") {
|
|
471
1601
|
res.writeHead(200, {
|
|
@@ -476,10 +1606,78 @@ async function startInternalMetroServer(options) {
|
|
|
476
1606
|
res.end("packager-status:running");
|
|
477
1607
|
return;
|
|
478
1608
|
}
|
|
479
|
-
|
|
1609
|
+
if (req.url?.split("?")[0] === "/bleam/reload") {
|
|
1610
|
+
if (req.method !== "POST") {
|
|
1611
|
+
res.writeHead(405, { Allow: "POST" });
|
|
1612
|
+
res.end();
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
sendJson(res, 200, {
|
|
1616
|
+
ok: true,
|
|
1617
|
+
clients: broadcastReload()
|
|
1618
|
+
});
|
|
1619
|
+
return;
|
|
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
|
+
}
|
|
1644
|
+
middleware(req, res, (error) => {
|
|
1645
|
+
if (res.headersSent) return;
|
|
1646
|
+
if (error) {
|
|
1647
|
+
console.error(error.message);
|
|
1648
|
+
res.writeHead(500, {
|
|
1649
|
+
"Cache-Control": "no-store",
|
|
1650
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
1651
|
+
});
|
|
1652
|
+
res.end(error.message);
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
res.writeHead(404, {
|
|
1656
|
+
"Cache-Control": "no-store",
|
|
1657
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
1658
|
+
});
|
|
1659
|
+
res.end("Not found");
|
|
1660
|
+
});
|
|
480
1661
|
});
|
|
481
1662
|
server.timeout = 0;
|
|
482
1663
|
attachHmrServer(server);
|
|
1664
|
+
const metroUpgradeListeners = server.listeners("upgrade");
|
|
1665
|
+
server.removeAllListeners("upgrade");
|
|
1666
|
+
server.on("upgrade", (req, socket, head) => {
|
|
1667
|
+
if (req.url?.split("?")[0] === "/message") {
|
|
1668
|
+
messageServer.handleUpgrade(req, socket, head, (client) => {
|
|
1669
|
+
messageServer.emit("connection", client, req);
|
|
1670
|
+
});
|
|
1671
|
+
return;
|
|
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
|
+
}
|
|
1679
|
+
for (const listener of metroUpgradeListeners) listener.call(server, req, socket, head);
|
|
1680
|
+
});
|
|
483
1681
|
await new Promise((resolve, reject) => {
|
|
484
1682
|
server.on("error", reject);
|
|
485
1683
|
server.listen(options.port, "localhost", () => resolve());
|
|
@@ -489,6 +1687,11 @@ async function startInternalMetroServer(options) {
|
|
|
489
1687
|
});
|
|
490
1688
|
console.log(`Bleam dev server running at http://localhost:${options.port}`);
|
|
491
1689
|
const close = () => {
|
|
1690
|
+
setTimeout(() => process.exit(0), 1e3).unref();
|
|
1691
|
+
messageServer.close();
|
|
1692
|
+
logServer.close();
|
|
1693
|
+
for (const client of messageClients) client.terminate();
|
|
1694
|
+
for (const client of logClients) client.terminate();
|
|
492
1695
|
server.close(() => {
|
|
493
1696
|
end();
|
|
494
1697
|
process.exit(0);
|
|
@@ -499,8 +1702,10 @@ async function startInternalMetroServer(options) {
|
|
|
499
1702
|
}
|
|
500
1703
|
async function startDevServer(options) {
|
|
501
1704
|
const config = readProjectConfig(options);
|
|
1705
|
+
if (options.clean) cleanDevArtifacts(options.projectRoot);
|
|
502
1706
|
const runtime = await resolveRuntimeArtifact();
|
|
503
1707
|
const clientAppPath = copyRuntimeClient(options.projectRoot, runtime);
|
|
1708
|
+
console.log("Preparing Bleam dev workspace");
|
|
504
1709
|
const workspaceRoot = createDevWorkspace(options.projectRoot, config);
|
|
505
1710
|
const origin = `http://localhost:${options.port}`;
|
|
506
1711
|
const projectConfigPath = writeProjectRuntimeConfig(options.projectRoot, config, origin);
|
|
@@ -510,7 +1715,8 @@ async function startDevServer(options) {
|
|
|
510
1715
|
"--workspace-root",
|
|
511
1716
|
workspaceRoot,
|
|
512
1717
|
"--port",
|
|
513
|
-
String(options.port)
|
|
1718
|
+
String(options.port),
|
|
1719
|
+
...options.clean ? ["--reset-cache"] : []
|
|
514
1720
|
], {
|
|
515
1721
|
stdio: "inherit",
|
|
516
1722
|
env: {
|
|
@@ -520,6 +1726,7 @@ async function startDevServer(options) {
|
|
|
520
1726
|
BLEAM_DEV_NAME: config.name,
|
|
521
1727
|
BLEAM_DEV_ORIGIN: origin,
|
|
522
1728
|
BLEAM_DEV_PORT: String(options.port),
|
|
1729
|
+
BLEAM_DEV_PLATFORM: "macos",
|
|
523
1730
|
BLEAM_DEV_RUNTIME_VERSION: config.runtimeVersion,
|
|
524
1731
|
BLEAM_DEV_SDK_VERSION: config.sdkVersion,
|
|
525
1732
|
NODE_PATH: [
|
|
@@ -532,20 +1739,57 @@ async function startDevServer(options) {
|
|
|
532
1739
|
].filter(Boolean).join(path.delimiter)
|
|
533
1740
|
}
|
|
534
1741
|
});
|
|
1742
|
+
let cleanupInteractiveControls = () => {};
|
|
1743
|
+
let didLaunchRuntimeClient = false;
|
|
1744
|
+
let didCleanupDevSession = false;
|
|
1745
|
+
let stopping = false;
|
|
1746
|
+
const cleanupDevSession = () => {
|
|
1747
|
+
if (didCleanupDevSession) return;
|
|
1748
|
+
didCleanupDevSession = true;
|
|
1749
|
+
cleanupInteractiveControls();
|
|
1750
|
+
if (didLaunchRuntimeClient) quitRuntimeClient();
|
|
1751
|
+
};
|
|
535
1752
|
const stop = (signal) => {
|
|
1753
|
+
if (stopping) return;
|
|
1754
|
+
stopping = true;
|
|
1755
|
+
cleanupDevSession();
|
|
536
1756
|
child.kill(signal);
|
|
1757
|
+
setTimeout(() => process.exit(0), 2e3).unref();
|
|
537
1758
|
};
|
|
538
1759
|
process.on("SIGINT", () => stop("SIGINT"));
|
|
539
1760
|
process.on("SIGTERM", () => stop("SIGTERM"));
|
|
540
|
-
child.on("
|
|
1761
|
+
child.on("error", (error) => {
|
|
1762
|
+
cleanupDevSession();
|
|
1763
|
+
console.error(`Bleam dev server process failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1764
|
+
console.error(doctorHint(options.projectRoot));
|
|
1765
|
+
process.exit(1);
|
|
1766
|
+
});
|
|
1767
|
+
child.on("exit", (code) => {
|
|
1768
|
+
cleanupDevSession();
|
|
1769
|
+
process.exit(code ?? 0);
|
|
1770
|
+
});
|
|
541
1771
|
try {
|
|
542
1772
|
const manifest = await readDevManifest(origin);
|
|
543
|
-
|
|
1773
|
+
cleanupInteractiveControls = setupInteractiveControls({
|
|
1774
|
+
origin,
|
|
1775
|
+
clientAppPath,
|
|
1776
|
+
projectConfigPath,
|
|
1777
|
+
manifest,
|
|
1778
|
+
stop: () => stop("SIGINT")
|
|
1779
|
+
});
|
|
1780
|
+
if (options.run && process.env.BLEAM_NO_OPEN !== "1") try {
|
|
544
1781
|
await waitForDevBundle(manifest.bundleUrl);
|
|
545
|
-
console.log(
|
|
546
|
-
|
|
1782
|
+
console.log(`Opening Bleam app at ${prettyPath(clientAppPath)}`);
|
|
1783
|
+
await launchRuntimeClient(clientAppPath, projectConfigPath, manifest);
|
|
1784
|
+
didLaunchRuntimeClient = true;
|
|
1785
|
+
} catch (error) {
|
|
1786
|
+
console.error(`Initial app launch failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1787
|
+
console.error(doctorHint(options.projectRoot));
|
|
1788
|
+
console.error("Check BLEAM_RUNTIME_APP if you are using a runtime override.");
|
|
1789
|
+
console.error("Bleam dev server is still running.");
|
|
547
1790
|
}
|
|
548
1791
|
} catch (error) {
|
|
1792
|
+
cleanupDevSession();
|
|
549
1793
|
child.kill("SIGTERM");
|
|
550
1794
|
throw error;
|
|
551
1795
|
}
|
|
@@ -558,10 +1802,12 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
558
1802
|
}
|
|
559
1803
|
if (command === "new") {
|
|
560
1804
|
const options = parseNewOptions(args);
|
|
561
|
-
|
|
562
|
-
|
|
1805
|
+
createProject(options);
|
|
1806
|
+
await startDevServer({
|
|
563
1807
|
projectRoot: options.projectRoot,
|
|
564
|
-
port: 8081
|
|
1808
|
+
port: 8081,
|
|
1809
|
+
run: true,
|
|
1810
|
+
clean: false
|
|
565
1811
|
});
|
|
566
1812
|
return;
|
|
567
1813
|
}
|
|
@@ -569,17 +1815,70 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
569
1815
|
await startDevServer(parseDevOptions(args));
|
|
570
1816
|
return;
|
|
571
1817
|
}
|
|
1818
|
+
if (command === "install") {
|
|
1819
|
+
await installNativeApp(parseBuildOptions(args));
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
if (command === "build") {
|
|
1823
|
+
await buildNativeArchives(parseBuildOptions(args));
|
|
1824
|
+
return;
|
|
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
|
+
}
|
|
1838
|
+
if (command === "doctor") {
|
|
1839
|
+
runDoctor(parseDoctorOptions(args));
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
if (command === "typecheck") {
|
|
1843
|
+
runTypecheckCommand(parseProjectToolOptions(args));
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
if (command === "check") {
|
|
1847
|
+
runBiomeCommand("check", parseProjectToolOptions(args));
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
if (command === "format") {
|
|
1851
|
+
runBiomeCommand("format", parseProjectToolOptions(args));
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
if (command === "lint") {
|
|
1855
|
+
runBiomeCommand("lint", parseProjectToolOptions(args));
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
572
1858
|
if (command === "internal:metro") {
|
|
573
1859
|
await startInternalMetroServer(parseInternalMetroOptions(args));
|
|
574
1860
|
return;
|
|
575
1861
|
}
|
|
576
1862
|
throw new Error(`Unknown command: ${command}`);
|
|
577
1863
|
}
|
|
578
|
-
main().catch((error) => {
|
|
1864
|
+
if (process.env.VITEST !== "true") main().catch((error) => {
|
|
579
1865
|
console.error(error instanceof Error ? error.message : String(error));
|
|
580
1866
|
process.exit(1);
|
|
581
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
|
+
};
|
|
582
1882
|
|
|
583
1883
|
//#endregion
|
|
584
|
-
export {
|
|
585
|
-
//# sourceMappingURL=cli.js.map
|
|
1884
|
+
export { __test };
|