stim 0.0.0 → 1.0.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 (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -0
  3. package/dist/android-cas-compiler.d.mts +1 -0
  4. package/dist/android-cas-compiler.mjs +71 -0
  5. package/dist/android-jc8MnUyL.mjs +514 -0
  6. package/dist/cache-manifest-4tH64LQ9.mjs +66 -0
  7. package/dist/cache-manifest.d.mts +27 -0
  8. package/dist/cache-manifest.mjs +2 -0
  9. package/dist/cli.d.mts +1 -0
  10. package/dist/cli.mjs +27182 -0
  11. package/dist/collector-run.d.mts +119 -0
  12. package/dist/collector-run.mjs +539 -0
  13. package/dist/config-7kmtuhO2.mjs +350 -0
  14. package/dist/errors-B6W7FkxQ.mjs +15 -0
  15. package/dist/exec-bsN9MJXb.mjs +63 -0
  16. package/dist/metro-store-COLl1pOk.mjs +1249 -0
  17. package/dist/ndjson-DcAtEx_K.mjs +131 -0
  18. package/dist/ndjson-DvHSsNU9.d.mts +16 -0
  19. package/dist/process-output-C7IlIzd7.mjs +40 -0
  20. package/dist/project-Dit1CckW.mjs +234 -0
  21. package/dist/server-bare-BmTVVmBB.mjs +351 -0
  22. package/dist/server-expo-BZJm--wA.mjs +2 -0
  23. package/dist/server-expo-CHJe1r6j.mjs +311 -0
  24. package/dist/state-2nush3MK.mjs +269 -0
  25. package/dist/supervisor-run.d.mts +85 -0
  26. package/dist/supervisor-run.mjs +210 -0
  27. package/package.json +61 -8
  28. package/shim/android-cas-pch.cmake +35 -0
  29. package/shim/android-cas.gradle +29 -0
  30. package/shim/android-cas.toolchain.cmake +12 -0
  31. package/shim/android-optimizations.gradle +30 -0
  32. package/shim/bundle-response.cjs +46 -0
  33. package/shim/bundle-response.d.cts +5 -0
  34. package/shim/expo-metro-config.cjs +188 -0
  35. package/shim/metro-cache-generation.cjs +20 -0
  36. package/shim/metro-cache-generation.d.cts +5 -0
  37. package/skill/SKILL.md +14 -0
@@ -0,0 +1,269 @@
1
+ import { E as withDirLock, l as getConfigDir, w as sameProcessRecord } from "./config-7kmtuhO2.mjs";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join, resolve } from "path";
4
+ import { buildCacheRoot as sharedBuildCache, workspaceName, workspaceStateDir as workspaceDir } from "@stim-cli/core";
5
+ import { dirname as dirname$1 } from "node:path";
6
+ //#region src/paths.ts
7
+ const WORKSPACE_METADATA_FILE_NAME = "workspace.json";
8
+ function workspaceMetadataFile(projectRoot) {
9
+ return join(workspaceDir(projectRoot), WORKSPACE_METADATA_FILE_NAME);
10
+ }
11
+ function ensureWorkspaceStorage(projectRoot) {
12
+ const canonicalRoot = resolve(projectRoot);
13
+ const dir = workspaceDir(canonicalRoot);
14
+ const file = workspaceMetadataFile(canonicalRoot);
15
+ mkdirSync(dir, { recursive: true });
16
+ return withDirLock(join(dir, "metadata.lock"), () => {
17
+ if (!existsSync(file)) {
18
+ const metadata = {
19
+ projectRoot: canonicalRoot,
20
+ workspace: workspaceName(canonicalRoot),
21
+ version: 1
22
+ };
23
+ const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
24
+ writeFileSync(tmp, `${JSON.stringify(metadata, null, 2)}\n`);
25
+ try {
26
+ renameSync(tmp, file);
27
+ } catch (error) {
28
+ rmSync(tmp, { force: true });
29
+ throw error;
30
+ }
31
+ return dir;
32
+ }
33
+ try {
34
+ if (JSON.parse(readFileSync(file, "utf-8")).projectRoot === canonicalRoot) return dir;
35
+ } catch {}
36
+ const error = /* @__PURE__ */ new Error(`Stim workspace collision at ${dir}: its workspace.json does not belong to ${canonicalRoot}.`);
37
+ error.code = "STIM_WORKSPACE_COLLISION";
38
+ throw error;
39
+ });
40
+ }
41
+ function workspaceLogsDir(projectRoot) {
42
+ return join(workspaceDir(projectRoot), "logs");
43
+ }
44
+ function workspaceDerivedData(projectRoot) {
45
+ return join(workspaceDir(projectRoot), "derived-data");
46
+ }
47
+ function supervisorPidFile(projectRoot) {
48
+ return join(workspaceDir(projectRoot), "supervisor.pid");
49
+ }
50
+ function workspaceStateFile(projectRoot) {
51
+ return join(workspaceDir(projectRoot), "state.json");
52
+ }
53
+ function workspaceStateLock(projectRoot) {
54
+ return join(workspaceDir(projectRoot), "state.lock");
55
+ }
56
+ function supervisorLogFile(projectRoot) {
57
+ return join(workspaceLogsDir(projectRoot), "supervisor.log");
58
+ }
59
+ function emulatorLogFile(projectRoot) {
60
+ return join(workspaceLogsDir(projectRoot), "emulator.log");
61
+ }
62
+ function sharedCompilationCache() {
63
+ return join(getConfigDir(), "compilation-cache");
64
+ }
65
+ function sharedCcache() {
66
+ return join(getConfigDir(), "ccache");
67
+ }
68
+ //#endregion
69
+ //#region src/supervisor/state.ts
70
+ const MODE_BARE = "bare-inproc";
71
+ const MODE_EXPO = "expo-child";
72
+ function readMetroTunnel(root) {
73
+ const record = readWorkspaceState(root)?.metroTunnel;
74
+ if (!record || typeof record !== "object") return null;
75
+ const kind = record.kind;
76
+ const url = record.url;
77
+ if (typeof url !== "string" || url.length === 0) return null;
78
+ if (kind === "expo") return {
79
+ kind: "expo",
80
+ url
81
+ };
82
+ if (kind === "managed") {
83
+ const provider = record.provider;
84
+ const pid = record.pid;
85
+ const port = record.port;
86
+ const startedAt = record.startedAt;
87
+ const processToken = record.processToken;
88
+ const logFile = record.logFile;
89
+ if (provider !== "ngrok" && provider !== "cloudflared" || typeof pid !== "number" || typeof port !== "number") return null;
90
+ return {
91
+ kind: "managed",
92
+ provider,
93
+ pid,
94
+ url,
95
+ port,
96
+ startedAt: typeof startedAt === "string" ? startedAt : "",
97
+ processToken: typeof processToken === "string" && processToken.length > 0 ? processToken : null,
98
+ ...typeof logFile === "string" && logFile.length > 0 ? { logFile } : {}
99
+ };
100
+ }
101
+ return null;
102
+ }
103
+ function readWorkspaceState(root) {
104
+ try {
105
+ const parsed = JSON.parse(readFileSync(workspaceStateFile(root), "utf-8"));
106
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
107
+ return parsed;
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+ function parseWorkspaceLaunchRecord(value) {
113
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
114
+ const record = value;
115
+ if (typeof record.appId !== "string" || record.appId.length === 0) return null;
116
+ if (typeof record.deviceId !== "string" || record.deviceId.length === 0) return null;
117
+ if (record.metroPort !== null && typeof record.metroPort !== "number") return null;
118
+ if (typeof record.release !== "boolean") return null;
119
+ if (record.deepLinkUrl !== null && typeof record.deepLinkUrl !== "string") return null;
120
+ if (typeof record.launchedAt !== "string") return null;
121
+ return record;
122
+ }
123
+ function readWorkspaceLaunches(root) {
124
+ const launches = readWorkspaceState(root)?.launches;
125
+ if (!launches || typeof launches !== "object" || Array.isArray(launches)) return {};
126
+ const ios = parseWorkspaceLaunchRecord(launches.ios);
127
+ const android = parseWorkspaceLaunchRecord(launches.android);
128
+ return {
129
+ ...ios ? { ios } : {},
130
+ ...android ? { android } : {}
131
+ };
132
+ }
133
+ function writeWorkspaceLaunch(root, platform, record) {
134
+ withWorkspaceStateLock(root, () => {
135
+ const state = readWorkspaceState(root) ?? {};
136
+ const launches = state.launches && typeof state.launches === "object" ? state.launches : {};
137
+ replaceWorkspaceState(root, {
138
+ ...state,
139
+ launches: {
140
+ ...launches,
141
+ [platform]: record
142
+ }
143
+ });
144
+ });
145
+ }
146
+ function readRemoteSession(root) {
147
+ const record = readWorkspaceState(root)?.remoteDevice;
148
+ if (!record || typeof record !== "object") return null;
149
+ const id = record.sessionId;
150
+ if (typeof id !== "string" || id.length === 0) return null;
151
+ const platform = record.platform;
152
+ const startedAt = record.startedAt;
153
+ return {
154
+ platform: platform === "ios" || platform === "android" ? platform : null,
155
+ sessionId: id,
156
+ startedAt: typeof startedAt === "string" ? startedAt : null
157
+ };
158
+ }
159
+ function readRemoteSessionId(root) {
160
+ return readRemoteSession(root)?.sessionId ?? null;
161
+ }
162
+ function clearRemoteSession(root, expectedSessionId) {
163
+ clearWorkspaceStateKey(root, "remoteDevice", (value) => {
164
+ if (typeof value !== "object" || value === null) return false;
165
+ return value.sessionId === expectedSessionId;
166
+ });
167
+ }
168
+ function withWorkspaceStateLock(root, fn) {
169
+ const file = workspaceStateFile(root);
170
+ return withDirLock(workspaceStateLock(root), fn, { ensureParent: () => {
171
+ ensureWorkspaceStorage(root);
172
+ mkdirSync(dirname$1(file), { recursive: true });
173
+ } });
174
+ }
175
+ function writeWorkspaceState(root, patch) {
176
+ return withWorkspaceStateLock(root, () => replaceWorkspaceState(root, {
177
+ ...readWorkspaceState(root),
178
+ ...patch
179
+ }));
180
+ }
181
+ function replaceWorkspaceState(root, state) {
182
+ const file = workspaceStateFile(root);
183
+ mkdirSync(dirname$1(file), { recursive: true });
184
+ const tmp = `${file}.tmp-${process.pid}`;
185
+ writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`);
186
+ renameSync(tmp, file);
187
+ return state;
188
+ }
189
+ function clearWorkspaceSupervisor(root, expected) {
190
+ return withWorkspaceStateLock(root, () => {
191
+ const current = readWorkspaceState(root)?.supervisor;
192
+ if (current && expected !== void 0 && !sameProcessRecord(current, expected)) return false;
193
+ clearWorkspaceStateKey(root, "supervisor", () => true);
194
+ clearExpoMetroTunnel(root);
195
+ if (expected === void 0 || readPidFile(root) === expected?.pid) rmSync(supervisorPidFile(root), { force: true });
196
+ return true;
197
+ });
198
+ }
199
+ function clearExpoMetroTunnel(root) {
200
+ clearWorkspaceStateKey(root, "metroTunnel", (value) => {
201
+ return typeof value === "object" && value !== null && value.kind === "expo";
202
+ });
203
+ }
204
+ function clearManagedMetroTunnel(root, expected) {
205
+ if (!existsSync(workspaceStateFile(root))) return true;
206
+ return clearWorkspaceStateKey(root, "metroTunnel", (value) => {
207
+ if (typeof value !== "object" || value === null) return false;
208
+ const record = value;
209
+ return record.kind === "managed" && record.provider === expected.provider && record.pid === expected.pid && record.url === expected.url && record.port === expected.port && record.startedAt === expected.startedAt && (record.processToken ?? null) === (expected.processToken ?? null) && (record.logFile ?? null) === (expected.logFile ?? null);
210
+ });
211
+ }
212
+ function clearWorkspaceStateKeys(root, keys) {
213
+ if (!existsSync(workspaceStateFile(root))) return;
214
+ withWorkspaceStateLock(root, () => {
215
+ const state = readWorkspaceState(root);
216
+ const file = workspaceStateFile(root);
217
+ if (!state) {
218
+ try {
219
+ rmSync(file, { force: true });
220
+ } catch {}
221
+ return;
222
+ }
223
+ let changed = false;
224
+ for (const key of keys) {
225
+ if (!(key in state)) continue;
226
+ delete state[key];
227
+ changed = true;
228
+ }
229
+ if (!changed) return;
230
+ if (Object.keys(state).length === 0) {
231
+ rmSync(file, { force: true });
232
+ return;
233
+ }
234
+ replaceWorkspaceState(root, state);
235
+ });
236
+ }
237
+ function clearWorkspaceStateKey(root, key, shouldClear) {
238
+ return withWorkspaceStateLock(root, () => {
239
+ const state = readWorkspaceState(root);
240
+ if (!state || !(key in state)) return true;
241
+ if (!shouldClear(state[key])) return false;
242
+ delete state[key];
243
+ const file = workspaceStateFile(root);
244
+ if (Object.keys(state).length === 0) {
245
+ try {
246
+ rmSync(file, { force: true });
247
+ } catch {}
248
+ return true;
249
+ }
250
+ replaceWorkspaceState(root, state);
251
+ return true;
252
+ });
253
+ }
254
+ function writePidFile(root, pid) {
255
+ const file = supervisorPidFile(root);
256
+ mkdirSync(dirname$1(file), { recursive: true });
257
+ writeFileSync(file, `${pid}\n`);
258
+ return file;
259
+ }
260
+ function readPidFile(root) {
261
+ try {
262
+ const pid = parseInt(readFileSync(supervisorPidFile(root), "utf-8").trim(), 10);
263
+ return Number.isFinite(pid) ? pid : null;
264
+ } catch {
265
+ return null;
266
+ }
267
+ }
268
+ //#endregion
269
+ export { supervisorLogFile as C, workspaceName as D, workspaceLogsDir as E, workspaceStateFile as O, sharedCompilationCache as S, workspaceDir as T, writeWorkspaceState as _, clearRemoteSession as a, sharedBuildCache as b, readMetroTunnel as c, readRemoteSessionId as d, readWorkspaceLaunches as f, writeWorkspaceLaunch as g, writePidFile as h, clearManagedMetroTunnel as i, readPidFile as l, withWorkspaceStateLock as m, MODE_EXPO as n, clearWorkspaceStateKeys as o, readWorkspaceState as p, clearExpoMetroTunnel as r, clearWorkspaceSupervisor as s, MODE_BARE as t, readRemoteSession as u, emulatorLogFile as v, workspaceDerivedData as w, sharedCcache as x, ensureWorkspaceStorage as y };
@@ -0,0 +1,85 @@
1
+ import { t as NdjsonWriter } from "./ndjson-DvHSsNU9.mjs";
2
+ //#region src/process-identity.d.ts
3
+ interface ProcessRecord {
4
+ pid?: unknown;
5
+ processToken?: unknown;
6
+ }
7
+ //#endregion
8
+ //#region src/supervisor/state.d.ts
9
+ declare const MODE_BARE = "bare-inproc";
10
+ declare const MODE_EXPO = "expo-child";
11
+ interface WorkspaceState {
12
+ metroCacheGeneration?: string;
13
+ supervisor?: Record<string, unknown>;
14
+ collectors?: Record<string, unknown>;
15
+ lastBuild?: Record<string, unknown>;
16
+ launches?: Partial<Record<WorkspaceLaunchPlatform, WorkspaceLaunchRecord>>;
17
+ remoteDevice?: Record<string, unknown>;
18
+ metroTunnel?: Record<string, unknown>;
19
+ [key: string]: unknown;
20
+ }
21
+ type WorkspaceLaunchPlatform = "ios" | "android";
22
+ interface WorkspaceLaunchRecord {
23
+ appId: string;
24
+ deviceId: string;
25
+ metroPort: number | null;
26
+ release: boolean;
27
+ deepLinkUrl: string | null;
28
+ launchedAt: string;
29
+ }
30
+ declare function readWorkspaceState(root: string): WorkspaceState | null;
31
+ declare function withWorkspaceStateLock<T>(root: string, fn: () => T): T;
32
+ declare function writeWorkspaceState(root: string, patch: WorkspaceState): WorkspaceState;
33
+ declare function clearWorkspaceSupervisor(root: string, expected?: ProcessRecord | null): boolean;
34
+ declare function clearExpoMetroTunnel(root: string): void;
35
+ declare function writePidFile(root: string, pid: number): string;
36
+ declare function readPidFile(root: string): number | null;
37
+ //#endregion
38
+ //#region src/supervisor/run.d.ts
39
+ interface ParsedSupervisorArgs {
40
+ root?: string;
41
+ port?: number;
42
+ tunnel?: boolean;
43
+ error?: string;
44
+ }
45
+ declare function parseArgs(argv: string[]): ParsedSupervisorArgs;
46
+ interface ServerExitInfo {
47
+ code?: number | null;
48
+ signal?: NodeJS.Signals | null;
49
+ reason?: string;
50
+ error?: Error;
51
+ }
52
+ interface ServerHandle {
53
+ close(): Promise<void> | void;
54
+ onExit?(cb: (info?: ServerExitInfo | null) => void): void;
55
+ serverPid?: number | null;
56
+ }
57
+ type ServerStarter = (opts: {
58
+ root: string;
59
+ port: number;
60
+ logsDir: string;
61
+ writer?: NdjsonWriter | null;
62
+ tunnel?: boolean;
63
+ onTunnelUrl?: ((url: string) => void) | null;
64
+ }) => Promise<ServerHandle>;
65
+ interface RunSupervisorOptions {
66
+ root: string;
67
+ port: number;
68
+ tunnel?: boolean;
69
+ isExpo?: (projectRoot: string) => boolean;
70
+ startBare?: ServerStarter | null;
71
+ startExpo?: ServerStarter | null;
72
+ now?: () => number;
73
+ onExit?: (code: number) => void;
74
+ attachSignals?: boolean;
75
+ stderr?: (line: string) => void;
76
+ }
77
+ declare function runSupervisor({ root, port, tunnel, isExpo, startBare, startExpo, now, onExit, attachSignals, stderr }: RunSupervisorOptions): Promise<{
78
+ mode: string;
79
+ server: ServerHandle | undefined;
80
+ shutdown: (code: number, event: string, msg: string) => Promise<void>;
81
+ startedAt: string;
82
+ } | null>;
83
+ declare function main(argv?: string[]): Promise<void>;
84
+ //#endregion
85
+ export { MODE_BARE, MODE_EXPO, RunSupervisorOptions, ServerExitInfo, ServerHandle, type WorkspaceState, clearExpoMetroTunnel, clearWorkspaceSupervisor, main, parseArgs, readPidFile, readWorkspaceState, runSupervisor, withWorkspaceStateLock, writePidFile, writeWorkspaceState };
@@ -0,0 +1,210 @@
1
+ import { S as captureProcessToken, a as clearSupervisor, y as setSupervisor } from "./config-7kmtuhO2.mjs";
2
+ import { i as detectIsExpo } from "./project-Dit1CckW.mjs";
3
+ import { E as workspaceLogsDir, _ as writeWorkspaceState, h as writePidFile, l as readPidFile, m as withWorkspaceStateLock, n as MODE_EXPO, p as readWorkspaceState, r as clearExpoMetroTunnel, s as clearWorkspaceSupervisor, t as MODE_BARE } from "./state-2nush3MK.mjs";
4
+ import { r as createNdjsonWriter } from "./ndjson-DcAtEx_K.mjs";
5
+ import { t as describeError } from "./errors-B6W7FkxQ.mjs";
6
+ import { existsSync, realpathSync } from "node:fs";
7
+ import { isAbsolute, join, resolve } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ //#region src/supervisor/run.ts
10
+ function parseArgs(argv) {
11
+ let root;
12
+ let port;
13
+ let tunnel = false;
14
+ for (let i = 0; i < argv.length; i++) {
15
+ const arg = argv[i];
16
+ if (arg === "--root") {
17
+ root = argv[++i];
18
+ continue;
19
+ }
20
+ if (arg === "--port") {
21
+ port = argv[++i];
22
+ continue;
23
+ }
24
+ if (arg === "--tunnel") {
25
+ tunnel = true;
26
+ continue;
27
+ }
28
+ return { error: `Unknown supervisor argument "${arg}". Usage: run.js --root <path> --port <n> [--tunnel]` };
29
+ }
30
+ if (!root) return { error: "Missing --root. Usage: run.js --root <path> --port <n> [--tunnel]" };
31
+ if (!isAbsolute(root)) return { error: `--root must be an absolute path, got "${root}".` };
32
+ const parsedPort = Number(port);
33
+ if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) return { error: `--port must be a TCP port number, got "${port}".` };
34
+ return {
35
+ root: resolve(root),
36
+ port: parsedPort,
37
+ tunnel
38
+ };
39
+ }
40
+ async function runSupervisor({ root, port, tunnel = false, isExpo = detectIsExpo, startBare = null, startExpo = null, now = Date.now, onExit = (code) => process.exit(code), attachSignals = true, stderr = (line) => console.error(line) }) {
41
+ root = realpathSync(root);
42
+ const logsDir = workspaceLogsDir(root);
43
+ const writer = createNdjsonWriter(join(logsDir, "metro.ndjson"));
44
+ const mode = isExpo(root) ? MODE_EXPO : MODE_BARE;
45
+ const startedAt = new Date(now()).toISOString();
46
+ const processToken = captureProcessToken(process.pid);
47
+ if (!processToken) {
48
+ stderr("Stim supervisor: could not capture process identity; refusing to start an unmanaged server.");
49
+ writer.close();
50
+ onExit(1);
51
+ return null;
52
+ }
53
+ const record = {
54
+ pid: process.pid,
55
+ processToken,
56
+ port,
57
+ mode,
58
+ startedAt
59
+ };
60
+ clearExpoMetroTunnel(root);
61
+ writePidFile(root, process.pid);
62
+ writeWorkspaceState(root, { supervisor: record });
63
+ try {
64
+ setSupervisor(root, record);
65
+ } catch (err) {
66
+ writer.write({
67
+ src: "metro",
68
+ level: "warn",
69
+ event: "supervisor_registration_failed",
70
+ msg: `Could not record the supervisor in the global config: ${describeError(err)}`
71
+ });
72
+ stderr(`Stim supervisor: global registration failed: ${describeError(err)}`);
73
+ }
74
+ writer.write({
75
+ src: "metro",
76
+ level: "info",
77
+ event: "supervisor_started",
78
+ msg: `supervisor pid ${process.pid} starting the ${mode} dev server on port ${port} for ${root}`
79
+ });
80
+ let stopping = false;
81
+ const finish = (code, event, level, msg) => {
82
+ writer.write({
83
+ src: "metro",
84
+ level,
85
+ event,
86
+ msg
87
+ });
88
+ try {
89
+ clearSupervisor(root, record);
90
+ } catch {}
91
+ try {
92
+ clearWorkspaceSupervisor(root, record);
93
+ } catch {}
94
+ const closed = writer.close();
95
+ if (closed.dropped > 0) stderr(`Stim supervisor: dropped ${closed.dropped} log record(s); last error: ${describeError(closed.lastError)}`);
96
+ onExit(code);
97
+ };
98
+ let server;
99
+ try {
100
+ server = await (mode === "expo-child" ? startExpo || (await import("./server-expo-BZJm--wA.mjs")).startExpoServer : startBare || (await import("./server-bare-BmTVVmBB.mjs")).startBareServer)({
101
+ root,
102
+ port,
103
+ logsDir,
104
+ writer,
105
+ tunnel,
106
+ onTunnelUrl: (url) => {
107
+ try {
108
+ withWorkspaceStateLock(root, () => {
109
+ if (readWorkspaceState(root)?.supervisor?.processToken === processToken) writeWorkspaceState(root, { metroTunnel: {
110
+ kind: "expo",
111
+ url
112
+ } });
113
+ });
114
+ } catch (err) {
115
+ stderr(`Stim supervisor: could not record the Expo tunnel URL: ${describeError(err)}`);
116
+ }
117
+ writer.write({
118
+ src: "metro",
119
+ level: "info",
120
+ event: "expo_tunnel_ready",
121
+ msg: `Expo tunnel ready: ${url}`
122
+ });
123
+ }
124
+ });
125
+ } catch (err) {
126
+ stderr(`Stim supervisor: failed to start the ${mode} dev server: ${describeError(err)}`);
127
+ finish(1, "supervisor_failed", "fatal", `failed to start the ${mode} dev server: ${describeError(err)}`);
128
+ return null;
129
+ }
130
+ const readyServer = server;
131
+ if (readyServer.serverPid) withWorkspaceStateLock(root, () => {
132
+ if (readWorkspaceState(root)?.supervisor?.processToken === processToken) writeWorkspaceState(root, { supervisor: {
133
+ ...record,
134
+ serverPid: readyServer.serverPid
135
+ } });
136
+ });
137
+ writer.write({
138
+ src: "metro",
139
+ level: "info",
140
+ event: "server_started",
141
+ msg: `${mode} dev server listening on port ${port}`
142
+ });
143
+ const shutdown = async (code, event, msg) => {
144
+ if (stopping) return;
145
+ stopping = true;
146
+ try {
147
+ await readyServer.close();
148
+ } catch (err) {
149
+ writer.write({
150
+ src: "metro",
151
+ level: "warn",
152
+ event: "server_close_failed",
153
+ msg: describeError(err)
154
+ });
155
+ }
156
+ finish(code, event, code === 0 ? "info" : "error", msg);
157
+ };
158
+ readyServer.onExit?.((info) => {
159
+ if (stopping) return;
160
+ const detail = info?.signal ? `signal ${info.signal}` : `exit code ${info?.code ?? "unknown"}`;
161
+ shutdown(1, "supervisor_stopped", `the ${mode} dev server exited unexpectedly (${detail}); shutting the supervisor down`);
162
+ });
163
+ if (attachSignals) for (const signal of ["SIGTERM", "SIGINT"]) process.on(signal, () => {
164
+ shutdown(0, "supervisor_stopped", `received ${signal}; stopping the ${mode} dev server`);
165
+ });
166
+ return {
167
+ mode,
168
+ server,
169
+ shutdown,
170
+ startedAt
171
+ };
172
+ }
173
+ async function main(argv = process.argv.slice(2)) {
174
+ const parsed = parseArgs(argv);
175
+ if (parsed.error) {
176
+ console.error(`Stim supervisor: ${parsed.error}`);
177
+ process.exit(2);
178
+ return;
179
+ }
180
+ const root = parsed.root;
181
+ if (!existsSync(root)) {
182
+ console.error(`Stim supervisor: --root ${root} does not exist.`);
183
+ process.exit(2);
184
+ return;
185
+ }
186
+ try {
187
+ process.chdir(root);
188
+ } catch {}
189
+ process.title = "stim-supervisor";
190
+ await runSupervisor({
191
+ root,
192
+ port: parsed.port,
193
+ tunnel: parsed.tunnel ?? false
194
+ });
195
+ }
196
+ function invokedDirectly() {
197
+ const entry = process.argv[1];
198
+ if (!entry) return false;
199
+ try {
200
+ return resolve(entry) === fileURLToPath(import.meta.url);
201
+ } catch {
202
+ return false;
203
+ }
204
+ }
205
+ if (invokedDirectly()) main().catch((err) => {
206
+ console.error(`Stim supervisor: ${describeError(err)}`);
207
+ process.exit(1);
208
+ });
209
+ //#endregion
210
+ export { MODE_BARE, MODE_EXPO, clearExpoMetroTunnel, clearWorkspaceSupervisor, main, parseArgs, readPidFile, readWorkspaceState, runSupervisor, withWorkspaceStateLock, writePidFile, writeWorkspaceState };
package/package.json CHANGED
@@ -1,11 +1,64 @@
1
1
  {
2
2
  "name": "stim",
3
- "version": "0.0.0",
4
- "description": "Stim",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
3
+ "version": "1.0.0",
4
+ "description": "Stim: fast, isolated React Native environments for coding agents",
5
+ "keywords": [
6
+ "agent",
7
+ "emulator",
8
+ "expo",
9
+ "metro",
10
+ "react-native",
11
+ "simulator",
12
+ "worktree"
13
+ ],
14
+ "homepage": "https://github.com/appandflow/stim#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/appandflow/stim/issues"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Janic Duplessis",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/appandflow/stim.git",
23
+ "directory": "packages/stim-cli"
24
+ },
25
+ "bin": {
26
+ "stim": "dist/cli.mjs"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "shim",
31
+ "skill",
32
+ "LICENSE",
33
+ "README.md"
34
+ ],
35
+ "type": "module",
36
+ "exports": {
37
+ "./cache-manifest": {
38
+ "types": "./dist/cache-manifest.d.mts",
39
+ "module-sync": "./dist/cache-manifest.mjs",
40
+ "default": "./dist/cache-manifest.mjs"
41
+ }
8
42
  },
9
- "author": "Jake Hamilton <jake.hamilton@hey.com>",
10
- "license": "Apache-2.0"
11
- }
43
+ "dependencies": {
44
+ "@expo/fingerprint": "^0.20.10",
45
+ "@stim-cli/cache": "^1.0.0",
46
+ "@stim-cli/core": "^1.0.0",
47
+ "@stim-cli/metro": "^1.0.0",
48
+ "chalk": "^5.4.1",
49
+ "commander": "^13.1.0",
50
+ "unique-pid": "0.1.1",
51
+ "ws": "^8.21.3"
52
+ },
53
+ "devDependencies": {
54
+ "@stim-cli/expo-build-cache": "1.0.0",
55
+ "@types/ws": "^8.18.1"
56
+ },
57
+ "engines": {
58
+ "node": "^20.19.4 || >=22.12.0"
59
+ },
60
+ "scripts": {
61
+ "test": "node --test test/*.test.js",
62
+ "build": "tsdown"
63
+ }
64
+ }
@@ -0,0 +1,35 @@
1
+ function(_stim_cas_pch directory)
2
+ get_property(targets DIRECTORY "${directory}" PROPERTY BUILDSYSTEM_TARGETS)
3
+ foreach(target IN LISTS targets)
4
+ get_target_property(headers "${target}" PRECOMPILE_HEADERS)
5
+ if(NOT headers)
6
+ continue()
7
+ endif()
8
+ set(portable)
9
+ foreach(header IN LISTS headers)
10
+ set(mapped "${header}")
11
+ if(IS_ABSOLUTE "${header}")
12
+ foreach(root IN ITEMS "${CMAKE_BINARY_DIR}" "$ENV{STIM_ANDROID_CAS_SOURCE_ROOT}")
13
+ string(FIND "${header}" "${root}/" position)
14
+ if(position EQUAL 0)
15
+ string(LENGTH "${root}/" length)
16
+ string(SUBSTRING "${header}" ${length} -1 suffix)
17
+ if(root STREQUAL CMAKE_BINARY_DIR)
18
+ set(mapped "\"/^build/${suffix}\"")
19
+ else()
20
+ set(mapped "\"/^src/${suffix}\"")
21
+ endif()
22
+ break()
23
+ endif()
24
+ endforeach()
25
+ endif()
26
+ list(APPEND portable "${mapped}")
27
+ endforeach()
28
+ set_property(TARGET "${target}" PROPERTY PRECOMPILE_HEADERS "${portable}")
29
+ endforeach()
30
+ get_property(children DIRECTORY "${directory}" PROPERTY SUBDIRECTORIES)
31
+ foreach(child IN LISTS children)
32
+ _stim_cas_pch("${child}")
33
+ endforeach()
34
+ endfunction()
35
+ cmake_language(DEFER CALL _stim_cas_pch "${CMAKE_CURRENT_SOURCE_DIR}")