stim 0.0.0 → 1.1.0

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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -0
  3. package/dist/android-CC5bUer7.mjs +204 -0
  4. package/dist/android-DnZlT3aU.mjs +2279 -0
  5. package/dist/android-cas-DGlTStWu.mjs +1078 -0
  6. package/dist/android-cas-compiler.d.mts +1 -0
  7. package/dist/android-cas-compiler.mjs +71 -0
  8. package/dist/android-xmyGBNy5.mjs +665 -0
  9. package/dist/app-install-oZIQRbdD.mjs +1141 -0
  10. package/dist/build-lock-vE45ZzJZ.mjs +645 -0
  11. package/dist/build-slots-CgacA9zQ.mjs +152 -0
  12. package/dist/cache-manifest-oeH18ERr.mjs +66 -0
  13. package/dist/cache-manifest.d.mts +27 -0
  14. package/dist/cache-manifest.mjs +2 -0
  15. package/dist/cli.d.mts +1 -0
  16. package/dist/cli.mjs +40 -0
  17. package/dist/collector-run.d.mts +119 -0
  18. package/dist/collector-run.mjs +541 -0
  19. package/dist/command-output-8wHMEde0.mjs +289 -0
  20. package/dist/config-D7uu8Gbq.mjs +381 -0
  21. package/dist/deps-64wVnpZc.mjs +435 -0
  22. package/dist/dev-client-Bvky1hya.mjs +557 -0
  23. package/dist/device-DJKv5Qej.mjs +250 -0
  24. package/dist/device-lease-D-G3SgVe.mjs +335 -0
  25. package/dist/device-pool-BlHXnYYs.mjs +408 -0
  26. package/dist/device-remote-CCgkG82J.mjs +1209 -0
  27. package/dist/doctor-BRF9aAJ3.mjs +530 -0
  28. package/dist/doctor-DlVxTtMq.mjs +1137 -0
  29. package/dist/error-diagnostics-B5IJwv3-.mjs +636 -0
  30. package/dist/errors-B6W7FkxQ.mjs +15 -0
  31. package/dist/exec-bsN9MJXb.mjs +63 -0
  32. package/dist/gc-DhXdgSdt.mjs +1651 -0
  33. package/dist/guide-DrhrDBo5.mjs +4319 -0
  34. package/dist/ios-BZxKPuqP.mjs +382 -0
  35. package/dist/ios-CK1Vrk6Q.mjs +2721 -0
  36. package/dist/ios-device-CYbuRNXR.mjs +81 -0
  37. package/dist/ios-device-CdSm0GDw.mjs +413 -0
  38. package/dist/logs-Dxoy16Qv.mjs +176 -0
  39. package/dist/logs-query-oqn47Ffx.mjs +273 -0
  40. package/dist/metro-B01H3EuD.mjs +184 -0
  41. package/dist/metro-store-CDDZHtPl.mjs +92 -0
  42. package/dist/ndjson-DcAtEx_K.mjs +131 -0
  43. package/dist/ndjson-DvHSsNU9.d.mts +16 -0
  44. package/dist/ownership-B9QIdQOw.mjs +475 -0
  45. package/dist/ownership-D8HmSv2E.mjs +60 -0
  46. package/dist/ownership-claim-CeJLEpjD.mjs +569 -0
  47. package/dist/process-output-C7IlIzd7.mjs +40 -0
  48. package/dist/project-COj6nzzh.mjs +243 -0
  49. package/dist/reclaim-BPvN2HlP.mjs +347 -0
  50. package/dist/reload-DYZEBbZy.mjs +332 -0
  51. package/dist/remote-cache-DOi5-zLj.mjs +934 -0
  52. package/dist/server-bare-BYHqEeyw.mjs +352 -0
  53. package/dist/server-expo-BEMr_m6p.mjs +312 -0
  54. package/dist/server-expo-rb0k-0w7.mjs +2 -0
  55. package/dist/settings-BSwrY4FZ.mjs +687 -0
  56. package/dist/spawn-entry-nCE6m770.mjs +14 -0
  57. package/dist/start-ByCylYtB.mjs +822 -0
  58. package/dist/state-IYEY-zi8.mjs +268 -0
  59. package/dist/stats-DZ4Q5J-7.mjs +62 -0
  60. package/dist/stats-wqxPm7Tv.mjs +307 -0
  61. package/dist/status-Dia0XTqG.mjs +363 -0
  62. package/dist/stop-BApyaF21.mjs +591 -0
  63. package/dist/stop-D6o5zAdm.mjs +3 -0
  64. package/dist/supervisor-run.d.mts +84 -0
  65. package/dist/supervisor-run.mjs +210 -0
  66. package/dist/workspace-process-lock-CxOO9-pf.mjs +47 -0
  67. package/dist/worktree-Dx3ZC5lS.mjs +539 -0
  68. package/dist/worktree-DxEmUI_n.mjs +1322 -0
  69. package/dist/xcode-CDB6fytw.mjs +1705 -0
  70. package/package.json +61 -8
  71. package/shim/android-cas-pch.cmake +35 -0
  72. package/shim/android-cas.gradle +29 -0
  73. package/shim/android-cas.toolchain.cmake +12 -0
  74. package/shim/android-optimizations.gradle +30 -0
  75. package/shim/bundle-response.cjs +46 -0
  76. package/shim/bundle-response.d.cts +5 -0
  77. package/shim/expo-metro-config.cjs +188 -0
  78. package/shim/metro-cache-generation.cjs +20 -0
  79. package/shim/metro-cache-generation.d.cts +5 -0
  80. package/skill/SKILL.md +14 -0
@@ -0,0 +1,352 @@
1
+ import { p as readWorkspaceState } from "./state-IYEY-zi8.mjs";
2
+ import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
3
+ import { S as projectMetroSharedCache } from "./settings-BSwrY4FZ.mjs";
4
+ import { a as metroStoreRoot, o as registerMetroStore, t as appendCacheStore } from "./metro-store-CDDZHtPl.mjs";
5
+ import { createRequire } from "node:module";
6
+ import { dirname, isAbsolute, join, relative, sep } from "node:path";
7
+ //#region \0rolldown/runtime.js
8
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
9
+ var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
10
+ //#endregion
11
+ //#region shim/bundle-response.cjs
12
+ var require_bundle_response = /* @__PURE__ */ __commonJSMin(((exports, module) => {
13
+ const { randomUUID } = __require("node:crypto");
14
+ function bundleResponseMiddleware(write) {
15
+ return (req, res, next) => {
16
+ let url;
17
+ try {
18
+ url = new URL(req.url, "http://localhost");
19
+ } catch {
20
+ return next();
21
+ }
22
+ const platform = url.searchParams.get("platform");
23
+ if (req.method !== "GET" || !/\.bundle\/*$/.test(url.pathname) || !["ios", "android"].includes(platform)) return next();
24
+ const requestId = randomUUID();
25
+ const emit = (event, statusCode) => {
26
+ try {
27
+ write({
28
+ ts: Date.now(),
29
+ src: "metro",
30
+ level: event === "bundle_response_failed" ? "error" : "debug",
31
+ event,
32
+ platform,
33
+ requestId,
34
+ statusCode,
35
+ msg: `${platform} bundle response ${event.slice(16)}`
36
+ });
37
+ } catch {}
38
+ };
39
+ emit("bundle_response_started");
40
+ let ended = false;
41
+ const finish = (complete) => {
42
+ if (ended) return;
43
+ ended = true;
44
+ const success = complete && (res.statusCode === 200 || res.statusCode === 304);
45
+ emit(success ? "bundle_response_finished" : "bundle_response_failed", res.statusCode);
46
+ };
47
+ res.once("finish", () => finish(true));
48
+ res.once("close", () => finish(false));
49
+ return next();
50
+ };
51
+ }
52
+ module.exports = { bundleResponseMiddleware };
53
+ }));
54
+ //#endregion
55
+ //#region shim/metro-cache-generation.cjs
56
+ var require_metro_cache_generation = /* @__PURE__ */ __commonJSMin(((exports, module) => {
57
+ const { createHash } = __require("node:crypto");
58
+ const { mkdirSync } = __require("node:fs");
59
+ const { join: join$1 } = __require("node:path");
60
+ function applyMetroCacheGeneration(config, generation, directory) {
61
+ if (!generation) return config;
62
+ const fileMapCacheDirectory = join$1(directory, generation);
63
+ mkdirSync(fileMapCacheDirectory, { recursive: true });
64
+ return {
65
+ ...config,
66
+ cacheVersion: createHash("sha256").update(JSON.stringify([config.cacheVersion ?? "1.0", generation])).digest("hex"),
67
+ fileMapCacheDirectory
68
+ };
69
+ }
70
+ module.exports = { applyMetroCacheGeneration };
71
+ }));
72
+ //#endregion
73
+ //#region src/supervisor/server-bare.ts
74
+ var import_bundle_response = require_bundle_response();
75
+ var import_metro_cache_generation = require_metro_cache_generation();
76
+ const BARE_PACKAGES = [
77
+ "metro",
78
+ "@react-native/dev-middleware",
79
+ "@react-native-community/cli-server-api"
80
+ ];
81
+ const REQUIRED_EXPORTS = {
82
+ metro: ["loadConfig", "runServer"],
83
+ "@react-native/dev-middleware": ["createDevMiddleware"],
84
+ "@react-native-community/cli-server-api": ["createDevServerMiddleware"]
85
+ };
86
+ function projectRequire(root) {
87
+ return createRequire(join(root, "package.json"));
88
+ }
89
+ function normalizeModule(mod, names) {
90
+ if (!mod || typeof mod !== "object") return mod;
91
+ const has = (obj) => obj && names.every((n) => typeof obj[n] === "function");
92
+ if (has(mod)) return mod;
93
+ if (has(mod.default)) return mod.default;
94
+ return mod;
95
+ }
96
+ function checkBareApi(modules) {
97
+ const problems = [];
98
+ for (const name of BARE_PACKAGES) {
99
+ const mod = modules?.[name];
100
+ if (!mod) {
101
+ problems.push(`${name} loaded but exported nothing`);
102
+ continue;
103
+ }
104
+ for (const fn of REQUIRED_EXPORTS[name] ?? []) if (typeof mod[fn] !== "function") problems.push(`${name} does not export ${fn}()`);
105
+ }
106
+ return problems;
107
+ }
108
+ function resolveBareDeps(root, { requireFrom = projectRequire } = {}) {
109
+ const localRequire = requireFrom(root);
110
+ const modules = {};
111
+ const missing = [];
112
+ for (const name of BARE_PACKAGES) {
113
+ let resolved;
114
+ try {
115
+ resolved = localRequire.resolve(name);
116
+ } catch {
117
+ missing.push(name);
118
+ continue;
119
+ }
120
+ try {
121
+ modules[name] = normalizeModule(localRequire(resolved), REQUIRED_EXPORTS[name] ?? []);
122
+ } catch (err) {
123
+ throw supervisorError("STIM_BARE_LOAD", `${name} is installed in ${root} but failed to load: ${err?.message || err}`, `Check that ${name} matches this project's React Native version, then reinstall node_modules.`);
124
+ }
125
+ }
126
+ if (missing.length > 0) throw supervisorError("STIM_BARE_DEPS", `Cannot host a bare React Native dev server for ${root}: ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} not resolvable from the project.`, "Run `npm install` in the project. If this is an Expo project that was detected as bare, check that `expo` is in its dependencies and that it has an Expo config.");
127
+ const problems = checkBareApi(modules);
128
+ if (problems.length > 0) throw supervisorError("STIM_BARE_API", `The dev server packages in ${root} are not the API Stim expects: ${problems.join("; ")}.`, "Upgrade or reinstall the project's React Native toolchain so metro, @react-native/dev-middleware and @react-native-community/cli-server-api match.");
129
+ return {
130
+ metro: modules["metro"],
131
+ devMiddleware: modules["@react-native/dev-middleware"],
132
+ serverApi: modules["@react-native-community/cli-server-api"]
133
+ };
134
+ }
135
+ function loadNdjsonReporter(root, { requireFrom = createRequire } = {}) {
136
+ for (const from of [import.meta.url, join(root, "package.json")]) try {
137
+ const factory = requireFrom(from)("@stim-cli/metro").ndjsonReporter;
138
+ if (typeof factory === "function") return factory;
139
+ } catch {}
140
+ return null;
141
+ }
142
+ function loadFileStore(root, { requireFrom = projectRequire } = {}) {
143
+ try {
144
+ const Store = requireFrom(root)("metro-cache").FileStore;
145
+ return typeof Store === "function" ? Store : null;
146
+ } catch {
147
+ return null;
148
+ }
149
+ }
150
+ function installSharedCacheStore({ root, config, writer, enabled, FileStore }) {
151
+ if (!enabled) {
152
+ writer?.write({
153
+ src: "metro",
154
+ level: "debug",
155
+ event: "cache_store_skipped",
156
+ msg: "the shared Metro transform store is off in configuration"
157
+ });
158
+ return false;
159
+ }
160
+ if (!FileStore) {
161
+ writer?.write({
162
+ src: "metro",
163
+ level: "warn",
164
+ event: "cache_store_skipped",
165
+ msg: `metro-cache is not resolvable from ${root}, so this dev server runs on whatever transform cache the project configured`
166
+ });
167
+ return false;
168
+ }
169
+ const storeRoot = metroStoreRoot(root);
170
+ const result = appendCacheStore(config, {
171
+ storeRoot,
172
+ FileStore
173
+ });
174
+ registerMetroStore(storeRoot);
175
+ if (!result.added) {
176
+ writer?.write({
177
+ src: "metro",
178
+ level: "debug",
179
+ event: "cache_store_present",
180
+ msg: `the shared Metro transform store at ${storeRoot} was already configured (${result.reason})`
181
+ });
182
+ return true;
183
+ }
184
+ writer?.write({
185
+ src: "metro",
186
+ level: "debug",
187
+ event: "cache_store_added",
188
+ msg: `appended the shared Metro transform store at ${storeRoot} to this project's cacheStores`
189
+ });
190
+ return true;
191
+ }
192
+ const ASYNC_REQUIRE_MODULE = "metro-runtime/src/modules/asyncRequire";
193
+ function normalizeMetroTransformerPaths(config, root) {
194
+ const current = config?.transformer?.asyncRequireModulePath;
195
+ if (typeof current !== "string" || !isAbsolute(current)) return false;
196
+ if (relative(root, current).split(sep).join("/") !== `node_modules/${ASYNC_REQUIRE_MODULE}.js`) return false;
197
+ config.transformer.asyncRequireModulePath = ASYNC_REQUIRE_MODULE;
198
+ return true;
199
+ }
200
+ function ensureNativePlatform(config) {
201
+ const platforms = config?.resolver?.platforms;
202
+ if (!Array.isArray(platforms) || platforms.includes("native")) return false;
203
+ config.resolver.platforms = [...platforms, "native"];
204
+ return true;
205
+ }
206
+ function reporterLogger(reporter) {
207
+ const at = (level) => (...data) => {
208
+ try {
209
+ reporter.update({
210
+ type: "unstable_server_log",
211
+ level,
212
+ data
213
+ });
214
+ } catch {}
215
+ };
216
+ return {
217
+ info: at("info"),
218
+ warn: at("warn"),
219
+ error: at("error")
220
+ };
221
+ }
222
+ function symbolicationReporterMiddleware(reporter) {
223
+ return (req, res, next) => {
224
+ if (typeof req?.url !== "string" || req.url.split("?", 1)[0] !== "/symbolicate") {
225
+ next();
226
+ return;
227
+ }
228
+ const end = res.end;
229
+ res.end = function(chunk, ...args) {
230
+ try {
231
+ const text = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf8") : null;
232
+ const body = text ? JSON.parse(text) : null;
233
+ if (body !== null && typeof body === "object" && !Array.isArray(body)) reporter.update({
234
+ type: "client_symbolication",
235
+ codeFrame: body.codeFrame,
236
+ stack: body.stack
237
+ });
238
+ } catch {}
239
+ return end.call(this, chunk, ...args);
240
+ };
241
+ next();
242
+ };
243
+ }
244
+ function closeHttpServer(httpServer, timeoutMs) {
245
+ return new Promise((resolve) => {
246
+ let done = false;
247
+ const finish = () => {
248
+ if (!done) {
249
+ done = true;
250
+ resolve();
251
+ }
252
+ };
253
+ const timer = setTimeout(finish, timeoutMs);
254
+ if (typeof timer.unref === "function") timer.unref();
255
+ try {
256
+ httpServer.closeAllConnections?.();
257
+ httpServer.close(() => {
258
+ clearTimeout(timer);
259
+ finish();
260
+ });
261
+ } catch {
262
+ clearTimeout(timer);
263
+ finish();
264
+ }
265
+ });
266
+ }
267
+ async function startBareServer({ root, port, logsDir, writer = null, deps = null, reporterFactory = void 0, cacheStore = void 0, fileStore = void 0, closeTimeoutMs = 5e3 }) {
268
+ const { metro, devMiddleware, serverApi } = deps || resolveBareDeps(root);
269
+ const makeReporter = reporterFactory === void 0 ? loadNdjsonReporter(root) : reporterFactory;
270
+ const config = (0, import_metro_cache_generation.applyMetroCacheGeneration)(await metro.loadConfig({
271
+ cwd: root,
272
+ port
273
+ }), readWorkspaceState(root)?.metroCacheGeneration, join(dirname(logsDir), "metro-file-map"));
274
+ if (installSharedCacheStore({
275
+ root,
276
+ config,
277
+ writer,
278
+ enabled: cacheStore === void 0 ? projectMetroSharedCache(root) : cacheStore,
279
+ FileStore: fileStore === void 0 ? loadFileStore(root) : fileStore
280
+ }) && normalizeMetroTransformerPaths(config, root)) writer?.write({
281
+ src: "metro",
282
+ level: "debug",
283
+ event: "config_adjusted",
284
+ msg: "normalized the project-local asyncRequireModulePath for cross-worktree transform keys"
285
+ });
286
+ if (ensureNativePlatform(config)) writer?.write({
287
+ src: "metro",
288
+ level: "debug",
289
+ event: "config_adjusted",
290
+ msg: "added 'native' to resolver.platforms, as the React Native CLI does"
291
+ });
292
+ let reporter;
293
+ if (makeReporter) {
294
+ reporter = makeReporter({ dir: logsDir });
295
+ config.reporter = reporter;
296
+ } else {
297
+ writer?.write({
298
+ src: "metro",
299
+ level: "warn",
300
+ event: "reporter_missing",
301
+ msg: "@stim-cli/metro is not installed in this project or beside Stim, so bundler and client logs will not be captured. Install it as a devDependency to get them."
302
+ });
303
+ reporter = { update() {} };
304
+ }
305
+ const hostname = "localhost";
306
+ const devServerUrl = `http://${hostname}:${port}`;
307
+ const { middleware: communityMiddleware, websocketEndpoints: communityWebsocketEndpoints } = serverApi.createDevServerMiddleware({
308
+ host: hostname,
309
+ port,
310
+ watchFolders: config.watchFolders
311
+ });
312
+ const { middleware, websocketEndpoints } = devMiddleware.createDevMiddleware({
313
+ serverBaseUrl: devServerUrl,
314
+ logger: reporterLogger(reporter)
315
+ });
316
+ const httpServer = await metro.runServer(config, {
317
+ unstable_extraMiddleware: [
318
+ (0, import_bundle_response.bundleResponseMiddleware)((record) => writer?.write(record)),
319
+ symbolicationReporterMiddleware(reporter),
320
+ communityMiddleware,
321
+ middleware
322
+ ],
323
+ websocketEndpoints: {
324
+ ...communityWebsocketEndpoints,
325
+ ...websocketEndpoints
326
+ }
327
+ });
328
+ let exited = false;
329
+ const listeners = [];
330
+ httpServer.on?.("close", () => {
331
+ if (exited) return;
332
+ exited = true;
333
+ for (const cb of listeners) cb({
334
+ code: 0,
335
+ reason: "the Metro http server closed"
336
+ });
337
+ });
338
+ return {
339
+ mode: "bare-inproc",
340
+ serverPid: null,
341
+ httpServer,
342
+ onExit(cb) {
343
+ listeners.push(cb);
344
+ },
345
+ async close() {
346
+ exited = true;
347
+ await closeHttpServer(httpServer, closeTimeoutMs);
348
+ }
349
+ };
350
+ }
351
+ //#endregion
352
+ export { startBareServer };
@@ -0,0 +1,312 @@
1
+ import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
+ import { r as createNdjsonWriter } from "./ndjson-DcAtEx_K.mjs";
3
+ import { p as readWorkspaceState } from "./state-IYEY-zi8.mjs";
4
+ import { l as resolvePackageJson } from "./project-COj6nzzh.mjs";
5
+ import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
6
+ import { n as stripAnsi, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
7
+ import { S as projectMetroSharedCache } from "./settings-BSwrY4FZ.mjs";
8
+ import { a as metroStoreRoot, i as metroStoreConfirmedRoot, n as expoMetroConfigPath, o as registerMetroStore, r as expoMetroStoreEnv } from "./metro-store-CDDZHtPl.mjs";
9
+ import { accessSync, constants, existsSync, readFileSync } from "node:fs";
10
+ import { dirname, join, parse } from "node:path";
11
+ //#region src/supervisor/server-expo.ts
12
+ function delay(ms) {
13
+ return new Promise((resolve) => {
14
+ const timer = setTimeout(resolve, ms);
15
+ if (typeof timer.unref === "function") timer.unref();
16
+ });
17
+ }
18
+ function expoBinPath(root) {
19
+ const fromPackage = expoBinFromPackage(resolvePackageJson(root, "expo"));
20
+ if (fromPackage) return fromPackage;
21
+ return findBinUpward(root, "expo");
22
+ }
23
+ function expoSdkMajor(root) {
24
+ const packageJsonPath = resolvePackageJson(root, "expo");
25
+ if (!packageJsonPath) return null;
26
+ try {
27
+ const version = JSON.parse(readFileSync(packageJsonPath, "utf-8"))?.version;
28
+ if (typeof version !== "string") return null;
29
+ const match = /^(\d+)/.exec(version);
30
+ return match ? Number(match[1]) : null;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ function expoBinFromPackage(packageJsonPath, binName = "expo") {
36
+ if (!packageJsonPath) return null;
37
+ let pkg;
38
+ try {
39
+ pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
40
+ } catch {
41
+ return null;
42
+ }
43
+ const bin = pkg?.bin;
44
+ const rel = typeof bin === "string" ? bin : bin && typeof bin === "object" ? bin[binName] : null;
45
+ if (typeof rel !== "string" || rel.trim() === "") return null;
46
+ const file = join(dirname(packageJsonPath), rel);
47
+ return isExecutableFile(file) ? file : null;
48
+ }
49
+ function findBinUpward(startDir, name, { exists = existsSync } = {}) {
50
+ let dir = startDir;
51
+ const stop = parse(startDir).root;
52
+ while (true) {
53
+ const candidate = join(dir, "node_modules", ".bin", name);
54
+ if (exists(candidate)) return candidate;
55
+ if (dir === stop) return null;
56
+ const parent = dirname(dir);
57
+ if (parent === dir) return null;
58
+ dir = parent;
59
+ }
60
+ }
61
+ function isExecutableFile(file) {
62
+ try {
63
+ accessSync(file, constants.X_OK);
64
+ return true;
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+ function expoBinRefusal(root, what = "start an Expo dev server for") {
70
+ return {
71
+ message: `Cannot ${what} ${root}: the \`expo\` package is not resolvable from it (require.resolve("expo/package.json") failed, and no node_modules/.bin/expo exists in it or in any parent).`,
72
+ remedy: "Install the project's dependencies (in a monorepo, from the workspace root), or check that this package really depends on `expo`."
73
+ };
74
+ }
75
+ const CROSS = "✖";
76
+ const CROSS_MARK = "❌";
77
+ const WARNING_SIGN = "⚠";
78
+ const UNHANDLED_NAVIGATE = /The action '(?:NAVIGATE|NAVIGATE_DEPRECATED)' with payload .*was not handled by any navigator/;
79
+ const DEV_CLIENT_ROUTE = "expo-development-client";
80
+ function isDevClientNavigationNotice(line) {
81
+ const text = String(line);
82
+ return text.includes(DEV_CLIENT_ROUTE) && UNHANDLED_NAVIGATE.test(text);
83
+ }
84
+ function inferLevel(line) {
85
+ const text = String(line).trimStart();
86
+ if (!text) return "info";
87
+ if (isDevClientNavigationNotice(text)) return "info";
88
+ const first = text[0];
89
+ if (first === CROSS || first === CROSS_MARK) return "error";
90
+ if (first === WARNING_SIGN) return "warn";
91
+ const lead = /^([A-Za-z]+)/.exec(text)?.[1]?.toLowerCase() ?? "";
92
+ if (lead === "error" || lead === "fatal") return "error";
93
+ if (lead === "warn" || lead === "warning") return "warn";
94
+ if (/\bBundling failed\b/.test(text)) return "error";
95
+ if (/^Unable to resolve\b/.test(text)) return "error";
96
+ if (/^Failed to (load|resolve|compile|build)\b/.test(text)) return "error";
97
+ if (/^[A-Z][A-Za-z]*Error:/.test(text)) return "error";
98
+ return "info";
99
+ }
100
+ function isBundleMarker(line) {
101
+ const text = String(line);
102
+ return /\bBundled\b/.test(text) || /\bBundling failed\b/.test(text);
103
+ }
104
+ function cleanLine(line) {
105
+ const parts = stripAnsi(line).split("\r");
106
+ return (parts[parts.length - 1] ?? "").trimEnd();
107
+ }
108
+ function recordFromLine(line, { stream = "stdout" } = {}) {
109
+ const msg = cleanLine(line);
110
+ if (!msg.trim()) return null;
111
+ if (stream === "stderr" && msg.startsWith("stim-bundle-response: ")) try {
112
+ const record = JSON.parse(msg.slice(22));
113
+ if (record.src === "metro" && [
114
+ "bundle_response_started",
115
+ "bundle_response_finished",
116
+ "bundle_response_failed"
117
+ ].includes(record.event) && ["ios", "android"].includes(record.platform) && typeof record.requestId === "string" && Number.isFinite(record.ts)) return record;
118
+ } catch {}
119
+ const confirmed = metroStoreConfirmedRoot(msg);
120
+ if (confirmed) return {
121
+ src: "metro",
122
+ level: "debug",
123
+ event: "cache_store_added",
124
+ msg: `sharing Metro transforms through ${confirmed} (the dev server process confirmed the store is in the config Metro loaded)`
125
+ };
126
+ const record = {
127
+ src: "metro",
128
+ level: inferLevel(msg),
129
+ msg,
130
+ raw: true,
131
+ event: stream === "stderr" ? "expo_stderr" : "expo_stdout"
132
+ };
133
+ if (isBundleMarker(msg)) record.marker = true;
134
+ return record;
135
+ }
136
+ function expoProxyEnv(env) {
137
+ if (env.EXPO_PACKAGER_PROXY_URL) return {};
138
+ const publicUrl = env.STIM_METRO_PUBLIC_URL?.trim();
139
+ return publicUrl ? { EXPO_PACKAGER_PROXY_URL: publicUrl.replace(/\/+$/, "") } : {};
140
+ }
141
+ const WAITING_ON_RE = /^Waiting on (\S+)/;
142
+ const URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
143
+ const HTTP_SCHEME_RE = /^https?:\/\//i;
144
+ function parseExpoWaitingOnUrl(line) {
145
+ const match = WAITING_ON_RE.exec(String(line ?? ""));
146
+ if (!match) return null;
147
+ const url = match[1];
148
+ if (HTTP_SCHEME_RE.test(url)) return url;
149
+ return URL_SCHEME_RE.test(url) ? url.replace(/^[^:]+:/, "https:") : url;
150
+ }
151
+ function resolveMetroStoreInjection(root, { log, env }) {
152
+ const sharedCache = projectMetroSharedCache(root);
153
+ if (!sharedCache) log.write({
154
+ src: "metro",
155
+ level: "debug",
156
+ event: "cache_store_skipped",
157
+ msg: "the shared Metro transform store is off in configuration"
158
+ });
159
+ const sdkMajor = expoSdkMajor(root);
160
+ if (sdkMajor === null || sdkMajor < 54) {
161
+ log.write({
162
+ src: "metro",
163
+ level: "debug",
164
+ event: "cache_store_skipped",
165
+ msg: sdkMajor === null ? "could not determine this project's Expo SDK, so Stim left its Metro cache unchanged" : `Expo SDK ${sdkMajor} predates the config override added in SDK 54, so it runs with its normal Metro cache`
166
+ });
167
+ return null;
168
+ }
169
+ const adapterPath = expoMetroConfigPath();
170
+ if (!adapterPath) {
171
+ log.write({
172
+ src: "metro",
173
+ level: "warn",
174
+ event: "cache_store_skipped",
175
+ msg: "Stim's Expo Metro config adapter is missing from this install, so the dev server runs on whatever transform cache the project configured"
176
+ });
177
+ return null;
178
+ }
179
+ const storeRoot = sharedCache ? metroStoreRoot(root) : "";
180
+ const additions = expoMetroStoreEnv({
181
+ root,
182
+ storeRoot,
183
+ adapterPath,
184
+ existingOverride: env.EXPO_OVERRIDE_METRO_CONFIG
185
+ });
186
+ if (!sharedCache) return additions;
187
+ registerMetroStore(storeRoot);
188
+ log.write({
189
+ src: "metro",
190
+ level: "debug",
191
+ event: "cache_store_requested",
192
+ msg: `asked this project's Expo dev server to share Metro transforms through ${storeRoot} (EXPO_OVERRIDE_METRO_CONFIG, no metro.config.js change); the config adapter in that process reports the outcome`
193
+ });
194
+ return additions;
195
+ }
196
+ async function startExpoServer({ root, port, logsDir, writer = null, spawnFn = null, killTimeoutMs = 5e3, tunnel = false, onTunnelUrl = null }) {
197
+ const bin = expoBinPath(root);
198
+ if (!bin) {
199
+ const refusal = expoBinRefusal(root);
200
+ throw supervisorError("STIM_EXPO_BIN", refusal.message, refusal.remedy);
201
+ }
202
+ const log = writer || createNdjsonWriter(join(logsDir, "metro.ndjson"));
203
+ const spawn = spawnFn || ((cmd, args, opts) => getExecutor().spawn(cmd, args, opts));
204
+ const args = tunnel ? [
205
+ "start",
206
+ "--port",
207
+ String(port),
208
+ "--tunnel"
209
+ ] : [
210
+ "start",
211
+ "--port",
212
+ String(port)
213
+ ];
214
+ const storeEnv = resolveMetroStoreInjection(root, {
215
+ log,
216
+ env: process.env
217
+ });
218
+ const generation = readWorkspaceState(root)?.metroCacheGeneration;
219
+ if (generation && !storeEnv) throw supervisorError("STIM_BAD_ARG", "The saved Metro cache reset requires the Expo config adapter.", "Use Expo SDK 54 or newer and repair the Stim installation before starting this app.");
220
+ const child = spawn(bin, args, {
221
+ cwd: root,
222
+ stdio: [
223
+ "ignore",
224
+ "pipe",
225
+ "pipe"
226
+ ],
227
+ detached: false,
228
+ env: {
229
+ ...process.env,
230
+ FORCE_COLOR: "0",
231
+ ...expoProxyEnv(process.env),
232
+ ...storeEnv,
233
+ STIM_METRO_CACHE_GENERATION: generation ?? "",
234
+ STIM_METRO_FILE_MAP: join(dirname(logsDir), "metro-file-map"),
235
+ ...tunnel ? { EXPO_UNSTABLE_TUNNEL_V2: "1" } : {}
236
+ }
237
+ });
238
+ let lastMsg = null;
239
+ let lastAt = 0;
240
+ let tunnelUrlSeen = false;
241
+ const emit = (stream) => (chunk) => {
242
+ const record = recordFromLine(chunk, { stream });
243
+ if (!record) return;
244
+ const now = Date.now();
245
+ if (record.msg === lastMsg && now - lastAt < 1e3) return;
246
+ lastMsg = typeof record.msg === "string" ? record.msg : null;
247
+ lastAt = now;
248
+ log.write(record);
249
+ if (tunnel && !tunnelUrlSeen && onTunnelUrl && typeof record.msg === "string") {
250
+ const url = parseExpoWaitingOnUrl(record.msg);
251
+ if (url) {
252
+ tunnelUrlSeen = true;
253
+ onTunnelUrl(url);
254
+ }
255
+ }
256
+ };
257
+ const outReader = createLineReader(emit("stdout"));
258
+ const errReader = createLineReader(emit("stderr"));
259
+ child.stdout?.setEncoding?.("utf-8");
260
+ child.stderr?.setEncoding?.("utf-8");
261
+ child.stdout?.on("data", (chunk) => outReader.push(chunk));
262
+ child.stderr?.on("data", (chunk) => errReader.push(chunk));
263
+ let exited = false;
264
+ let exitInfo = null;
265
+ const listeners = [];
266
+ child.on("exit", (code, signal) => {
267
+ exited = true;
268
+ exitInfo = {
269
+ code,
270
+ signal
271
+ };
272
+ outReader.flush();
273
+ errReader.flush();
274
+ for (const cb of listeners) cb(exitInfo);
275
+ });
276
+ child.on("error", (err) => {
277
+ if (exited) return;
278
+ exited = true;
279
+ exitInfo = {
280
+ code: null,
281
+ signal: null,
282
+ error: err
283
+ };
284
+ for (const cb of listeners) cb(exitInfo);
285
+ });
286
+ return {
287
+ mode: "expo-child",
288
+ serverPid: child.pid ?? null,
289
+ child,
290
+ onExit(cb) {
291
+ if (exited) {
292
+ cb(exitInfo);
293
+ return;
294
+ }
295
+ listeners.push(cb);
296
+ },
297
+ async close() {
298
+ if (exited || !child.pid) return;
299
+ const dead = new Promise((resolve) => {
300
+ child.once("exit", () => resolve());
301
+ });
302
+ if (!child.kill("SIGTERM")) return;
303
+ await Promise.race([dead, delay(killTimeoutMs)]);
304
+ if (!exited) {
305
+ child.kill("SIGKILL");
306
+ await Promise.race([dead, delay(killTimeoutMs)]);
307
+ }
308
+ }
309
+ };
310
+ }
311
+ //#endregion
312
+ export { expoProxyEnv as a, inferLevel as c, parseExpoWaitingOnUrl as d, recordFromLine as f, expoBinRefusal as i, isBundleMarker as l, expoBinFromPackage as n, expoSdkMajor as o, startExpoServer as p, expoBinPath as r, findBinUpward as s, cleanLine as t, isDevClientNavigationNotice as u };
@@ -0,0 +1,2 @@
1
+ import { p as startExpoServer } from "./server-expo-BEMr_m6p.mjs";
2
+ export { startExpoServer };