stim 1.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.
- package/README.md +22 -10
- package/dist/android-CC5bUer7.mjs +204 -0
- package/dist/android-DnZlT3aU.mjs +2279 -0
- package/dist/android-cas-DGlTStWu.mjs +1078 -0
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/android-xmyGBNy5.mjs +665 -0
- package/dist/app-install-oZIQRbdD.mjs +1141 -0
- package/dist/build-lock-vE45ZzJZ.mjs +645 -0
- package/dist/build-slots-CgacA9zQ.mjs +152 -0
- package/dist/{cache-manifest-4tH64LQ9.mjs → cache-manifest-oeH18ERr.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +24 -27166
- package/dist/collector-run.mjs +6 -4
- package/dist/command-output-8wHMEde0.mjs +289 -0
- package/dist/{config-7kmtuhO2.mjs → config-D7uu8Gbq.mjs} +43 -12
- package/dist/deps-64wVnpZc.mjs +435 -0
- package/dist/dev-client-Bvky1hya.mjs +557 -0
- package/dist/device-DJKv5Qej.mjs +250 -0
- package/dist/device-lease-D-G3SgVe.mjs +335 -0
- package/dist/device-pool-BlHXnYYs.mjs +408 -0
- package/dist/device-remote-CCgkG82J.mjs +1209 -0
- package/dist/doctor-BRF9aAJ3.mjs +530 -0
- package/dist/doctor-DlVxTtMq.mjs +1137 -0
- package/dist/error-diagnostics-B5IJwv3-.mjs +636 -0
- package/dist/gc-DhXdgSdt.mjs +1651 -0
- package/dist/guide-DrhrDBo5.mjs +4319 -0
- package/dist/ios-BZxKPuqP.mjs +382 -0
- package/dist/ios-CK1Vrk6Q.mjs +2721 -0
- package/dist/ios-device-CYbuRNXR.mjs +81 -0
- package/dist/ios-device-CdSm0GDw.mjs +413 -0
- package/dist/logs-Dxoy16Qv.mjs +176 -0
- package/dist/logs-query-oqn47Ffx.mjs +273 -0
- package/dist/metro-B01H3EuD.mjs +184 -0
- package/dist/metro-store-CDDZHtPl.mjs +92 -0
- package/dist/ownership-B9QIdQOw.mjs +475 -0
- package/dist/ownership-D8HmSv2E.mjs +60 -0
- package/dist/ownership-claim-CeJLEpjD.mjs +569 -0
- package/dist/{project-Dit1CckW.mjs → project-COj6nzzh.mjs} +12 -3
- package/dist/reclaim-BPvN2HlP.mjs +347 -0
- package/dist/reload-DYZEBbZy.mjs +332 -0
- package/dist/remote-cache-DOi5-zLj.mjs +934 -0
- package/dist/{server-bare-BmTVVmBB.mjs → server-bare-BYHqEeyw.mjs} +3 -2
- package/dist/{server-expo-CHJe1r6j.mjs → server-expo-BEMr_m6p.mjs} +5 -4
- package/dist/server-expo-rb0k-0w7.mjs +2 -0
- package/dist/settings-BSwrY4FZ.mjs +687 -0
- package/dist/spawn-entry-nCE6m770.mjs +14 -0
- package/dist/start-ByCylYtB.mjs +822 -0
- package/dist/{state-2nush3MK.mjs → state-IYEY-zi8.mjs} +21 -22
- package/dist/stats-DZ4Q5J-7.mjs +62 -0
- package/dist/stats-wqxPm7Tv.mjs +307 -0
- package/dist/status-Dia0XTqG.mjs +363 -0
- package/dist/stop-BApyaF21.mjs +591 -0
- package/dist/stop-D6o5zAdm.mjs +3 -0
- package/dist/supervisor-run.d.mts +0 -1
- package/dist/supervisor-run.mjs +4 -4
- package/dist/workspace-process-lock-CxOO9-pf.mjs +47 -0
- package/dist/worktree-Dx3ZC5lS.mjs +539 -0
- package/dist/worktree-DxEmUI_n.mjs +1322 -0
- package/dist/xcode-CDB6fytw.mjs +1705 -0
- package/package.json +5 -5
- package/dist/android-jc8MnUyL.mjs +0 -514
- package/dist/metro-store-COLl1pOk.mjs +0 -1249
- package/dist/server-expo-BZJm--wA.mjs +0 -2
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as sameProcessRecord, k as withDirLock, u as getConfigDir } from "./config-D7uu8Gbq.mjs";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { join as join$1, resolve as resolve$1 } from "path";
|
|
5
|
+
import { buildCacheRoot as sharedBuildCache, workspaceId, workspaceName as workspaceName$1, workspaceStateDir as workspaceDir } from "@stim-cli/core";
|
|
6
6
|
//#region src/paths.ts
|
|
7
7
|
const WORKSPACE_METADATA_FILE_NAME = "workspace.json";
|
|
8
8
|
function workspaceMetadataFile(projectRoot) {
|
|
9
|
-
return join(workspaceDir(projectRoot), WORKSPACE_METADATA_FILE_NAME);
|
|
9
|
+
return join$1(workspaceDir(projectRoot), WORKSPACE_METADATA_FILE_NAME);
|
|
10
10
|
}
|
|
11
11
|
function ensureWorkspaceStorage(projectRoot) {
|
|
12
|
-
const canonicalRoot = resolve(projectRoot);
|
|
12
|
+
const canonicalRoot = resolve$1(projectRoot);
|
|
13
13
|
const dir = workspaceDir(canonicalRoot);
|
|
14
14
|
const file = workspaceMetadataFile(canonicalRoot);
|
|
15
15
|
mkdirSync(dir, { recursive: true });
|
|
16
|
-
return withDirLock(join(dir, "metadata.lock"), () => {
|
|
16
|
+
return withDirLock(join$1(dir, "metadata.lock"), () => {
|
|
17
17
|
if (!existsSync(file)) {
|
|
18
18
|
const metadata = {
|
|
19
19
|
projectRoot: canonicalRoot,
|
|
20
|
-
workspace: workspaceName(canonicalRoot),
|
|
20
|
+
workspace: workspaceName$1(canonicalRoot),
|
|
21
21
|
version: 1
|
|
22
22
|
};
|
|
23
23
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
@@ -39,31 +39,31 @@ function ensureWorkspaceStorage(projectRoot) {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
function workspaceLogsDir(projectRoot) {
|
|
42
|
-
return join(workspaceDir(projectRoot), "logs");
|
|
42
|
+
return join$1(workspaceDir(projectRoot), "logs");
|
|
43
43
|
}
|
|
44
44
|
function workspaceDerivedData(projectRoot) {
|
|
45
|
-
return join(workspaceDir(projectRoot), "derived-data");
|
|
45
|
+
return join$1(workspaceDir(projectRoot), "derived-data");
|
|
46
46
|
}
|
|
47
47
|
function supervisorPidFile(projectRoot) {
|
|
48
|
-
return join(workspaceDir(projectRoot), "supervisor.pid");
|
|
48
|
+
return join$1(workspaceDir(projectRoot), "supervisor.pid");
|
|
49
49
|
}
|
|
50
50
|
function workspaceStateFile(projectRoot) {
|
|
51
|
-
return join(workspaceDir(projectRoot), "state.json");
|
|
51
|
+
return join$1(workspaceDir(projectRoot), "state.json");
|
|
52
52
|
}
|
|
53
53
|
function workspaceStateLock(projectRoot) {
|
|
54
|
-
return join(workspaceDir(projectRoot), "state.lock");
|
|
54
|
+
return join$1(workspaceDir(projectRoot), "state.lock");
|
|
55
55
|
}
|
|
56
56
|
function supervisorLogFile(projectRoot) {
|
|
57
|
-
return join(workspaceLogsDir(projectRoot), "supervisor.log");
|
|
57
|
+
return join$1(workspaceLogsDir(projectRoot), "supervisor.log");
|
|
58
58
|
}
|
|
59
59
|
function emulatorLogFile(projectRoot) {
|
|
60
|
-
return join(workspaceLogsDir(projectRoot), "emulator.log");
|
|
60
|
+
return join$1(workspaceLogsDir(projectRoot), "emulator.log");
|
|
61
61
|
}
|
|
62
62
|
function sharedCompilationCache() {
|
|
63
|
-
return join(getConfigDir(), "compilation-cache");
|
|
63
|
+
return join$1(getConfigDir(), "compilation-cache");
|
|
64
64
|
}
|
|
65
65
|
function sharedCcache() {
|
|
66
|
-
return join(getConfigDir(), "ccache");
|
|
66
|
+
return join$1(getConfigDir(), "ccache");
|
|
67
67
|
}
|
|
68
68
|
//#endregion
|
|
69
69
|
//#region src/supervisor/state.ts
|
|
@@ -116,7 +116,6 @@ function parseWorkspaceLaunchRecord(value) {
|
|
|
116
116
|
if (typeof record.deviceId !== "string" || record.deviceId.length === 0) return null;
|
|
117
117
|
if (record.metroPort !== null && typeof record.metroPort !== "number") return null;
|
|
118
118
|
if (typeof record.release !== "boolean") return null;
|
|
119
|
-
if (record.deepLinkUrl !== null && typeof record.deepLinkUrl !== "string") return null;
|
|
120
119
|
if (typeof record.launchedAt !== "string") return null;
|
|
121
120
|
return record;
|
|
122
121
|
}
|
|
@@ -169,7 +168,7 @@ function withWorkspaceStateLock(root, fn) {
|
|
|
169
168
|
const file = workspaceStateFile(root);
|
|
170
169
|
return withDirLock(workspaceStateLock(root), fn, { ensureParent: () => {
|
|
171
170
|
ensureWorkspaceStorage(root);
|
|
172
|
-
mkdirSync(dirname
|
|
171
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
173
172
|
} });
|
|
174
173
|
}
|
|
175
174
|
function writeWorkspaceState(root, patch) {
|
|
@@ -180,7 +179,7 @@ function writeWorkspaceState(root, patch) {
|
|
|
180
179
|
}
|
|
181
180
|
function replaceWorkspaceState(root, state) {
|
|
182
181
|
const file = workspaceStateFile(root);
|
|
183
|
-
mkdirSync(dirname
|
|
182
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
184
183
|
const tmp = `${file}.tmp-${process.pid}`;
|
|
185
184
|
writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`);
|
|
186
185
|
renameSync(tmp, file);
|
|
@@ -253,7 +252,7 @@ function clearWorkspaceStateKey(root, key, shouldClear) {
|
|
|
253
252
|
}
|
|
254
253
|
function writePidFile(root, pid) {
|
|
255
254
|
const file = supervisorPidFile(root);
|
|
256
|
-
mkdirSync(dirname
|
|
255
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
257
256
|
writeFileSync(file, `${pid}\n`);
|
|
258
257
|
return file;
|
|
259
258
|
}
|
|
@@ -266,4 +265,4 @@ function readPidFile(root) {
|
|
|
266
265
|
}
|
|
267
266
|
}
|
|
268
267
|
//#endregion
|
|
269
|
-
export { supervisorLogFile as C,
|
|
268
|
+
export { supervisorLogFile as C, workspaceLogsDir as D, workspaceId as E, workspaceName$1 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, workspaceStateFile as k, 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,62 @@
|
|
|
1
|
+
import { g as repoRoot, s as gitCommonDir } from "./worktree-Dx3ZC5lS.mjs";
|
|
2
|
+
import { a as findProjectRoot } from "./project-COj6nzzh.mjs";
|
|
3
|
+
import { o as formatLongDuration } from "./command-output-8wHMEde0.mjs";
|
|
4
|
+
import { a as statsProjectKey, r as readStats } from "./stats-wqxPm7Tv.mjs";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
//#region src/commands/stats.ts
|
|
7
|
+
const PLATFORMS = ["ios", "android"];
|
|
8
|
+
const PLATFORM_WIDTH = 9;
|
|
9
|
+
function statsCommand(program) {
|
|
10
|
+
program.command("stats").description("Show how many `ios` and `android` runs this project and this machine have recorded, how many hit the build cache, and an estimate of the time that cache saved.").option("--json", "print the aggregates as JSON").action((opts) => {
|
|
11
|
+
const root = findProjectRoot(process.cwd());
|
|
12
|
+
const key = root ? statsProjectKey({
|
|
13
|
+
root,
|
|
14
|
+
commonDir: gitCommonDir(root),
|
|
15
|
+
repoRoot: repoRoot(root)
|
|
16
|
+
}) : null;
|
|
17
|
+
const { record, note } = readStats();
|
|
18
|
+
if (note) console.error(chalk.dim(note));
|
|
19
|
+
const machine = record?.machine ?? {};
|
|
20
|
+
const project = key ? record?.projects?.[key] ?? {} : {};
|
|
21
|
+
if (opts.json) {
|
|
22
|
+
console.log(JSON.stringify({
|
|
23
|
+
version: 1,
|
|
24
|
+
project: key ? {
|
|
25
|
+
key,
|
|
26
|
+
ios: project.ios ?? null,
|
|
27
|
+
android: project.android ?? null
|
|
28
|
+
} : null,
|
|
29
|
+
machine: {
|
|
30
|
+
ios: machine.ios ?? null,
|
|
31
|
+
android: machine.android ?? null
|
|
32
|
+
}
|
|
33
|
+
}));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const lines = [];
|
|
37
|
+
if (key) lines.push(`project ${key}`, ...sectionLines(project));
|
|
38
|
+
lines.push("machine", ...sectionLines(machine));
|
|
39
|
+
for (const line of lines) console.log(line);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function sectionLines(scope) {
|
|
43
|
+
const lines = PLATFORMS.filter((platform) => scope[platform]).map((platform) => bucketLine(platform, scope[platform]));
|
|
44
|
+
return lines.length ? lines : [chalk.dim(" no runs recorded")];
|
|
45
|
+
}
|
|
46
|
+
function bucketLine(platform, bucket) {
|
|
47
|
+
const finished = bucket.runs - bucket.failed;
|
|
48
|
+
const cells = [
|
|
49
|
+
`${bucket.runs} runs${bucket.failed > 0 ? ` (${bucket.failed} failed)` : ""}`,
|
|
50
|
+
`${bucket.hits} hits (${finished > 0 ? `${Math.round(bucket.hits / finished * 100)}%` : "-"})`,
|
|
51
|
+
`cold run ${average(bucket.coldRunMs, bucket.coldRuns)} avg`,
|
|
52
|
+
`hit run ${average(bucket.hitRunMs, bucket.hitRuns)} avg`,
|
|
53
|
+
`saved ~${formatLongDuration(bucket.timeSavedMs)} (estimated)`,
|
|
54
|
+
`since ${bucket.firstRunAt.slice(0, 10)}`
|
|
55
|
+
];
|
|
56
|
+
return ` ${platform.padEnd(PLATFORM_WIDTH)}${cells.join(" ")}`;
|
|
57
|
+
}
|
|
58
|
+
function average(totalMs, runs) {
|
|
59
|
+
return runs > 0 ? formatLongDuration(Math.round(totalMs / runs)) : "-";
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { statsCommand as default };
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { C as withConfigLock, u as getConfigDir } from "./config-D7uu8Gbq.mjs";
|
|
2
|
+
import { readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
const PLATFORMS = ["ios", "android"];
|
|
5
|
+
function statsFile() {
|
|
6
|
+
return join(getConfigDir(), "stats.json");
|
|
7
|
+
}
|
|
8
|
+
function emptyStats() {
|
|
9
|
+
return {
|
|
10
|
+
version: 1,
|
|
11
|
+
machine: {},
|
|
12
|
+
projects: {}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function statsProjectKey({ root, commonDir, repoRoot }) {
|
|
16
|
+
if (commonDir && repoRoot && basename(commonDir) === ".git") return canonical(join(dirname(commonDir), relative(repoRoot, root)));
|
|
17
|
+
return canonical(root);
|
|
18
|
+
}
|
|
19
|
+
function updateStats(record, run, now) {
|
|
20
|
+
const at = new Date(now).toISOString();
|
|
21
|
+
const durationMs = wholeMs(run.durationMs);
|
|
22
|
+
const phases = {
|
|
23
|
+
coldBuildMs: wholeMs(run.coldBuildMs),
|
|
24
|
+
podsMs: wholeMs(run.podsMs)
|
|
25
|
+
};
|
|
26
|
+
const projects = { ...record.projects };
|
|
27
|
+
const scope = { ...projects[run.projectKey] };
|
|
28
|
+
const machine = { ...record.machine };
|
|
29
|
+
const before = scope[run.platform] ?? null;
|
|
30
|
+
const credit = creditMs(before, run, durationMs);
|
|
31
|
+
scope[run.platform] = applyRun(before, run, {
|
|
32
|
+
at,
|
|
33
|
+
durationMs,
|
|
34
|
+
credit,
|
|
35
|
+
phases
|
|
36
|
+
});
|
|
37
|
+
projects[run.projectKey] = scope;
|
|
38
|
+
machine[run.platform] = applyRun(machine[run.platform] ?? null, run, {
|
|
39
|
+
at,
|
|
40
|
+
durationMs,
|
|
41
|
+
credit,
|
|
42
|
+
phases
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
version: 1,
|
|
46
|
+
machine,
|
|
47
|
+
projects
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function readStats() {
|
|
51
|
+
const path = statsFile();
|
|
52
|
+
let text;
|
|
53
|
+
try {
|
|
54
|
+
text = readFileSync(path, "utf-8");
|
|
55
|
+
} catch {
|
|
56
|
+
return {
|
|
57
|
+
record: null,
|
|
58
|
+
note: null
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const parsed = parseRecord(text);
|
|
62
|
+
if (!parsed) return {
|
|
63
|
+
record: null,
|
|
64
|
+
note: `Run statistics at ${path} could not be read; the next ios or android run moves them aside and starts a new file.`
|
|
65
|
+
};
|
|
66
|
+
if (parsed.version > 1) return {
|
|
67
|
+
record: null,
|
|
68
|
+
note: `Run statistics at ${path} are version ${parsed.version}, which this Stim does not understand, so none are shown.`
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
record: normalize(parsed),
|
|
72
|
+
note: null
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function projectEstimates(record, projectKey, platform) {
|
|
76
|
+
const bucket = record?.projects?.[projectKey]?.[platform] ?? null;
|
|
77
|
+
return {
|
|
78
|
+
coldBuildMs: positive(bucket?.lastColdBuildMs),
|
|
79
|
+
podsMs: positive(bucket?.lastPodsMs)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function readRunEstimates({ projectKey, platform, read = readStats }) {
|
|
83
|
+
if (!projectKey) return {
|
|
84
|
+
coldBuildMs: null,
|
|
85
|
+
podsMs: null
|
|
86
|
+
};
|
|
87
|
+
try {
|
|
88
|
+
return projectEstimates(read().record, projectKey, platform);
|
|
89
|
+
} catch {
|
|
90
|
+
return {
|
|
91
|
+
coldBuildMs: null,
|
|
92
|
+
podsMs: null
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function recordRunStats(run, now) {
|
|
97
|
+
return withConfigLock(() => {
|
|
98
|
+
const path = statsFile();
|
|
99
|
+
const loaded = loadForUpdate(path, now);
|
|
100
|
+
if (loaded.newerVersion !== null) return {
|
|
101
|
+
recorded: false,
|
|
102
|
+
note: `Run statistics at ${path} are version ${loaded.newerVersion}, which this Stim does not understand, so this run was not recorded.`
|
|
103
|
+
};
|
|
104
|
+
writeStats(path, updateStats(loaded.record, run, now));
|
|
105
|
+
return {
|
|
106
|
+
recorded: true,
|
|
107
|
+
note: loaded.note
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function createRunRecorder({ platform, write, now, note }) {
|
|
112
|
+
let projectKey = null;
|
|
113
|
+
let cacheKey = null;
|
|
114
|
+
let coldBuildMs = 0;
|
|
115
|
+
let podsMs = 0;
|
|
116
|
+
let recorded = false;
|
|
117
|
+
return {
|
|
118
|
+
setProject(key) {
|
|
119
|
+
projectKey = key;
|
|
120
|
+
},
|
|
121
|
+
setCacheKey(key) {
|
|
122
|
+
cacheKey = key;
|
|
123
|
+
},
|
|
124
|
+
setBuildMs(ms) {
|
|
125
|
+
coldBuildMs = wholeMs(ms);
|
|
126
|
+
},
|
|
127
|
+
setPodsMs(ms) {
|
|
128
|
+
podsMs = wholeMs(ms);
|
|
129
|
+
},
|
|
130
|
+
record({ failed, cacheHit = false, waited = null, durationMs }) {
|
|
131
|
+
if (!projectKey || !cacheKey || recorded) return;
|
|
132
|
+
recorded = true;
|
|
133
|
+
try {
|
|
134
|
+
const outcome = write({
|
|
135
|
+
platform,
|
|
136
|
+
projectKey,
|
|
137
|
+
failed,
|
|
138
|
+
cacheHit,
|
|
139
|
+
waitedForBuild: Boolean(waited),
|
|
140
|
+
durationMs,
|
|
141
|
+
...coldBuildMs > 0 ? { coldBuildMs } : {},
|
|
142
|
+
...podsMs > 0 ? { podsMs } : {}
|
|
143
|
+
}, now());
|
|
144
|
+
if (outcome?.note) note(outcome.note);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
note(`Run statistics could not be recorded: ${error?.message || error}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function loadForUpdate(path, now) {
|
|
152
|
+
let text;
|
|
153
|
+
try {
|
|
154
|
+
text = readFileSync(path, "utf-8");
|
|
155
|
+
} catch {
|
|
156
|
+
return {
|
|
157
|
+
record: emptyStats(),
|
|
158
|
+
note: null,
|
|
159
|
+
newerVersion: null
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const parsed = parseRecord(text);
|
|
163
|
+
if (!parsed) {
|
|
164
|
+
const aside = `${path}.corrupt-${now}`;
|
|
165
|
+
renameSync(path, aside);
|
|
166
|
+
return {
|
|
167
|
+
record: emptyStats(),
|
|
168
|
+
note: `Run statistics at ${path} could not be read, so they were moved to ${aside} and a new file was started.`,
|
|
169
|
+
newerVersion: null
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (parsed.version > 1) return {
|
|
173
|
+
record: emptyStats(),
|
|
174
|
+
note: null,
|
|
175
|
+
newerVersion: parsed.version
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
record: normalize(parsed),
|
|
179
|
+
note: null,
|
|
180
|
+
newerVersion: null
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function writeStats(path, record) {
|
|
184
|
+
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
185
|
+
writeFileSync(tmp, `${JSON.stringify(record)}\n`);
|
|
186
|
+
try {
|
|
187
|
+
renameSync(tmp, path);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
rmSync(tmp, { force: true });
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function parseRecord(text) {
|
|
194
|
+
let parsed;
|
|
195
|
+
try {
|
|
196
|
+
parsed = JSON.parse(text);
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
201
|
+
const version = parsed.version;
|
|
202
|
+
if (typeof version !== "number" || !Number.isInteger(version)) return null;
|
|
203
|
+
return parsed;
|
|
204
|
+
}
|
|
205
|
+
function normalize(record) {
|
|
206
|
+
const projects = {};
|
|
207
|
+
const source = record.projects;
|
|
208
|
+
if (source && typeof source === "object" && !Array.isArray(source)) for (const [key, scope] of Object.entries(source)) projects[key] = normalizeScope(scope);
|
|
209
|
+
return {
|
|
210
|
+
version: 1,
|
|
211
|
+
machine: normalizeScope(record.machine),
|
|
212
|
+
projects
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function normalizeScope(scope) {
|
|
216
|
+
const normalized = {};
|
|
217
|
+
if (!scope || typeof scope !== "object" || Array.isArray(scope)) return normalized;
|
|
218
|
+
for (const platform of PLATFORMS) {
|
|
219
|
+
const bucket = scope[platform];
|
|
220
|
+
if (bucket && typeof bucket === "object" && !Array.isArray(bucket)) normalized[platform] = normalizeBucket(bucket);
|
|
221
|
+
}
|
|
222
|
+
return normalized;
|
|
223
|
+
}
|
|
224
|
+
function normalizeBucket(bucket) {
|
|
225
|
+
return {
|
|
226
|
+
runs: count(bucket.runs),
|
|
227
|
+
failed: count(bucket.failed),
|
|
228
|
+
hits: count(bucket.hits),
|
|
229
|
+
misses: count(bucket.misses),
|
|
230
|
+
coldRuns: count(bucket.coldRuns),
|
|
231
|
+
coldRunMs: count(bucket.coldRunMs),
|
|
232
|
+
hitRuns: count(bucket.hitRuns),
|
|
233
|
+
hitRunMs: count(bucket.hitRunMs),
|
|
234
|
+
timeSavedMs: count(bucket.timeSavedMs),
|
|
235
|
+
firstRunAt: timestamp(bucket.firstRunAt),
|
|
236
|
+
lastRunAt: timestamp(bucket.lastRunAt),
|
|
237
|
+
...count(bucket.lastColdBuildMs) > 0 ? { lastColdBuildMs: count(bucket.lastColdBuildMs) } : {},
|
|
238
|
+
...count(bucket.lastPodsMs) > 0 ? { lastPodsMs: count(bucket.lastPodsMs) } : {}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function applyRun(bucket, run, { at, durationMs, credit, phases }) {
|
|
242
|
+
const next = bucket ? { ...bucket } : {
|
|
243
|
+
runs: 0,
|
|
244
|
+
failed: 0,
|
|
245
|
+
hits: 0,
|
|
246
|
+
misses: 0,
|
|
247
|
+
coldRuns: 0,
|
|
248
|
+
coldRunMs: 0,
|
|
249
|
+
hitRuns: 0,
|
|
250
|
+
hitRunMs: 0,
|
|
251
|
+
timeSavedMs: 0,
|
|
252
|
+
firstRunAt: at,
|
|
253
|
+
lastRunAt: at
|
|
254
|
+
};
|
|
255
|
+
next.runs += 1;
|
|
256
|
+
next.lastRunAt = at;
|
|
257
|
+
if (phases.coldBuildMs > 0) next.lastColdBuildMs = phases.coldBuildMs;
|
|
258
|
+
if (phases.podsMs > 0) next.lastPodsMs = phases.podsMs;
|
|
259
|
+
if (run.failed) {
|
|
260
|
+
next.failed += 1;
|
|
261
|
+
return next;
|
|
262
|
+
}
|
|
263
|
+
if (isHit(run.cacheHit)) {
|
|
264
|
+
next.hits += 1;
|
|
265
|
+
if (!run.waitedForBuild) {
|
|
266
|
+
next.hitRuns += 1;
|
|
267
|
+
next.hitRunMs += durationMs;
|
|
268
|
+
next.timeSavedMs += credit;
|
|
269
|
+
}
|
|
270
|
+
return next;
|
|
271
|
+
}
|
|
272
|
+
next.misses += 1;
|
|
273
|
+
next.coldRuns += 1;
|
|
274
|
+
next.coldRunMs += durationMs;
|
|
275
|
+
return next;
|
|
276
|
+
}
|
|
277
|
+
function creditMs(bucket, run, durationMs) {
|
|
278
|
+
if (run.failed || !isHit(run.cacheHit) || run.waitedForBuild) return 0;
|
|
279
|
+
if (!bucket || bucket.coldRuns <= 0) return 0;
|
|
280
|
+
return Math.max(0, Math.round(bucket.coldRunMs / bucket.coldRuns) - durationMs);
|
|
281
|
+
}
|
|
282
|
+
function isHit(cacheHit) {
|
|
283
|
+
return cacheHit === "local" || cacheHit === "remote";
|
|
284
|
+
}
|
|
285
|
+
function positive(value) {
|
|
286
|
+
const ms = wholeMs(value);
|
|
287
|
+
return ms > 0 ? ms : null;
|
|
288
|
+
}
|
|
289
|
+
function wholeMs(value) {
|
|
290
|
+
const ms = Number(value);
|
|
291
|
+
return Number.isFinite(ms) && ms > 0 ? Math.round(ms) : 0;
|
|
292
|
+
}
|
|
293
|
+
function count(value) {
|
|
294
|
+
return wholeMs(value);
|
|
295
|
+
}
|
|
296
|
+
function timestamp(value) {
|
|
297
|
+
return typeof value === "string" && value !== "" ? value : (/* @__PURE__ */ new Date(0)).toISOString();
|
|
298
|
+
}
|
|
299
|
+
function canonical(path) {
|
|
300
|
+
try {
|
|
301
|
+
return realpathSync(path);
|
|
302
|
+
} catch {
|
|
303
|
+
return resolve(path);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
//#endregion
|
|
307
|
+
export { statsProjectKey as a, recordRunStats as i, readRunEstimates as n, readStats as r, createRunRecorder as t };
|