@rehearsal-labs/retest 0.2.0 → 0.3.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 (218) hide show
  1. package/README.md +6 -5
  2. package/dist/api/source-location.d.ts.map +1 -1
  3. package/dist/api/source-location.js +3 -1
  4. package/dist/browser/builds.d.ts +35 -6
  5. package/dist/browser/builds.d.ts.map +1 -1
  6. package/dist/browser/builds.js +115 -16
  7. package/dist/browser/cdp/transport.d.ts.map +1 -1
  8. package/dist/browser/cdp/transport.js +4 -4
  9. package/dist/browser/chromium-process.d.ts +23 -0
  10. package/dist/browser/chromium-process.d.ts.map +1 -1
  11. package/dist/browser/chromium-process.js +78 -13
  12. package/dist/browser/electron.d.ts +1 -1
  13. package/dist/browser/electron.d.ts.map +1 -1
  14. package/dist/browser/electron.js +6 -3
  15. package/dist/browser/executables.d.ts +16 -1
  16. package/dist/browser/executables.d.ts.map +1 -1
  17. package/dist/browser/executables.js +50 -18
  18. package/dist/browser/firefox/accessible-names.d.ts +5 -4
  19. package/dist/browser/firefox/accessible-names.d.ts.map +1 -1
  20. package/dist/browser/firefox/accessible-names.js +11 -6
  21. package/dist/browser/firefox/bridge.js +3 -2
  22. package/dist/browser/firefox/executable.d.ts +14 -14
  23. package/dist/browser/firefox/executable.d.ts.map +1 -1
  24. package/dist/browser/firefox/executable.js +29 -21
  25. package/dist/browser/firefox/launch.js +1 -1
  26. package/dist/browser/firefox/navigation.d.ts +5 -2
  27. package/dist/browser/firefox/navigation.d.ts.map +1 -1
  28. package/dist/browser/firefox/navigation.js +48 -14
  29. package/dist/browser/firefox/orphans.d.ts.map +1 -1
  30. package/dist/browser/firefox/orphans.js +34 -2
  31. package/dist/browser/firefox/page.d.ts.map +1 -1
  32. package/dist/browser/firefox/page.js +113 -19
  33. package/dist/browser/firefox/process.d.ts +20 -1
  34. package/dist/browser/firefox/process.d.ts.map +1 -1
  35. package/dist/browser/firefox/process.js +124 -22
  36. package/dist/browser/firefox/profile.d.ts +5 -0
  37. package/dist/browser/firefox/profile.d.ts.map +1 -1
  38. package/dist/browser/firefox/profile.js +17 -1
  39. package/dist/browser/firefox/storage.d.ts.map +1 -1
  40. package/dist/browser/firefox/storage.js +4 -2
  41. package/dist/browser/firefox/subscriptions.d.ts +26 -0
  42. package/dist/browser/firefox/subscriptions.d.ts.map +1 -0
  43. package/dist/browser/firefox/subscriptions.js +10 -0
  44. package/dist/browser/launch.d.ts +6 -0
  45. package/dist/browser/launch.d.ts.map +1 -1
  46. package/dist/browser/launch.js +14 -1
  47. package/dist/cli/commands/doctor.js +3 -3
  48. package/dist/cli/commands/inspect.js +3 -2
  49. package/dist/cli/commands/install.d.ts.map +1 -1
  50. package/dist/cli/commands/install.js +26 -13
  51. package/dist/cli/commands/licences.d.ts +22 -0
  52. package/dist/cli/commands/licences.d.ts.map +1 -1
  53. package/dist/cli/commands/licences.js +62 -20
  54. package/dist/cli/commands/run.js +2 -2
  55. package/dist/cli/doctor/checks.js +4 -1
  56. package/dist/cli/inspect/diagnostics.js +3 -3
  57. package/dist/cli/inspect/test-timeline.js +10 -10
  58. package/dist/cli/install/doctor-rows.d.ts.map +1 -1
  59. package/dist/cli/install/doctor-rows.js +5 -4
  60. package/dist/cli/install/file-changes.d.ts +44 -0
  61. package/dist/cli/install/file-changes.d.ts.map +1 -0
  62. package/dist/cli/install/file-changes.js +81 -0
  63. package/dist/cli/install/install-archive.d.ts +3 -1
  64. package/dist/cli/install/install-archive.d.ts.map +1 -1
  65. package/dist/cli/install/install-archive.js +80 -33
  66. package/dist/cli/install/lock.d.ts.map +1 -1
  67. package/dist/cli/install/lock.js +108 -21
  68. package/dist/cli/install/media-doctor.d.ts.map +1 -1
  69. package/dist/cli/install/media-doctor.js +2 -1
  70. package/dist/cli/install/media-install.d.ts.map +1 -1
  71. package/dist/cli/install/media-install.js +38 -19
  72. package/dist/cli/install/media-pins.d.ts +7 -1
  73. package/dist/cli/install/media-pins.d.ts.map +1 -1
  74. package/dist/cli/install/media-pins.js +23 -12
  75. package/dist/cli/install/media-record.d.ts.map +1 -1
  76. package/dist/cli/install/media-record.js +10 -9
  77. package/dist/cli/install/media-report.d.ts +3 -1
  78. package/dist/cli/install/media-report.d.ts.map +1 -1
  79. package/dist/cli/install/media-report.js +7 -5
  80. package/dist/cli/install/media-tools.d.ts.map +1 -1
  81. package/dist/cli/install/media-tools.js +6 -4
  82. package/dist/cli/install/process-start.d.ts +3 -2
  83. package/dist/cli/install/process-start.d.ts.map +1 -1
  84. package/dist/cli/install/process-start.js +4 -0
  85. package/dist/cli/install/report.d.ts.map +1 -1
  86. package/dist/cli/install/report.js +11 -7
  87. package/dist/cli/install/supervisor-install.d.ts +33 -0
  88. package/dist/cli/install/supervisor-install.d.ts.map +1 -0
  89. package/dist/cli/install/supervisor-install.js +158 -0
  90. package/dist/cli/install/supervisor-locate.d.ts +47 -0
  91. package/dist/cli/install/supervisor-locate.d.ts.map +1 -0
  92. package/dist/cli/install/supervisor-locate.js +113 -0
  93. package/dist/cli/install/supervisor-pins.d.ts +21 -0
  94. package/dist/cli/install/supervisor-pins.d.ts.map +1 -0
  95. package/dist/cli/install/supervisor-pins.js +28 -0
  96. package/dist/cli/install/supervisor-record.d.ts +31 -0
  97. package/dist/cli/install/supervisor-record.d.ts.map +1 -0
  98. package/dist/cli/install/supervisor-record.js +104 -0
  99. package/dist/cli/install/supervisor-report.d.ts +11 -0
  100. package/dist/cli/install/supervisor-report.d.ts.map +1 -0
  101. package/dist/cli/install/supervisor-report.js +33 -0
  102. package/dist/cli/install/supervisor-tools.d.ts +43 -0
  103. package/dist/cli/install/supervisor-tools.d.ts.map +1 -0
  104. package/dist/cli/install/supervisor-tools.js +114 -0
  105. package/dist/cli/install/unpack.d.ts +23 -3
  106. package/dist/cli/install/unpack.d.ts.map +1 -1
  107. package/dist/cli/install/unpack.js +124 -10
  108. package/dist/cli/install/zip-member.d.ts +15 -0
  109. package/dist/cli/install/zip-member.d.ts.map +1 -0
  110. package/dist/cli/install/zip-member.js +77 -0
  111. package/dist/cli/interrupt.d.ts +1 -1
  112. package/dist/cli/interrupt.d.ts.map +1 -1
  113. package/dist/cli/interrupt.js +15 -10
  114. package/dist/cli/terminal.d.ts +9 -1
  115. package/dist/cli/terminal.d.ts.map +1 -1
  116. package/dist/cli/terminal.js +14 -0
  117. package/dist/config/define.d.ts +2 -2
  118. package/dist/config/define.js +2 -2
  119. package/dist/diagnostics/firefox-collector.d.ts +11 -10
  120. package/dist/diagnostics/firefox-collector.d.ts.map +1 -1
  121. package/dist/diagnostics/firefox-collector.js +16 -11
  122. package/dist/media/client.d.ts +2 -1
  123. package/dist/media/client.d.ts.map +1 -1
  124. package/dist/media/client.js +16 -5
  125. package/dist/media/host.d.ts +32 -0
  126. package/dist/media/host.d.ts.map +1 -0
  127. package/dist/media/host.js +50 -0
  128. package/dist/media/locate.d.ts.map +1 -1
  129. package/dist/media/locate.js +4 -3
  130. package/dist/native/processes.d.ts.map +1 -1
  131. package/dist/native/processes.js +7 -3
  132. package/dist/protocol/files.d.ts +1 -1
  133. package/dist/protocol/files.d.ts.map +1 -1
  134. package/dist/protocol/files.js +10 -5
  135. package/dist/reporters/commands.d.ts +12 -3
  136. package/dist/reporters/commands.d.ts.map +1 -1
  137. package/dist/reporters/commands.js +29 -11
  138. package/dist/reporters/human-card.js +1 -1
  139. package/dist/reporters/human.d.ts +3 -1
  140. package/dist/reporters/human.d.ts.map +1 -1
  141. package/dist/reporters/human.js +8 -8
  142. package/dist/reporters/style.d.ts +21 -2
  143. package/dist/reporters/style.d.ts.map +1 -1
  144. package/dist/reporters/style.js +11 -2
  145. package/dist/runner/app-server.d.ts +27 -7
  146. package/dist/runner/app-server.d.ts.map +1 -1
  147. package/dist/runner/app-server.js +74 -24
  148. package/dist/runner/child.js +6 -2
  149. package/dist/runner/media-leftovers.d.ts +7 -3
  150. package/dist/runner/media-leftovers.d.ts.map +1 -1
  151. package/dist/runner/media-leftovers.js +27 -6
  152. package/dist/runner/resources.d.ts +8 -0
  153. package/dist/runner/resources.d.ts.map +1 -1
  154. package/dist/runner/resources.js +12 -1
  155. package/dist/runner/run-session.d.ts.map +1 -1
  156. package/dist/runner/run-session.js +9 -4
  157. package/dist/runner/run.d.ts.map +1 -1
  158. package/dist/runner/run.js +2 -1
  159. package/dist/runner/target-drivers.d.ts +5 -2
  160. package/dist/runner/target-drivers.d.ts.map +1 -1
  161. package/dist/runner/target-drivers.js +10 -1
  162. package/dist/runner/test-file-process.d.ts +8 -0
  163. package/dist/runner/test-file-process.d.ts.map +1 -1
  164. package/dist/runner/test-file-process.js +26 -3
  165. package/dist/runner/test-pages.d.ts.map +1 -1
  166. package/dist/runner/test-pages.js +5 -2
  167. package/dist/shared/environment.d.ts +17 -0
  168. package/dist/shared/environment.d.ts.map +1 -0
  169. package/dist/shared/environment.js +27 -0
  170. package/dist/shared/metadata-process-worker.js +15 -7
  171. package/dist/shared/metadata-process.d.ts +5 -0
  172. package/dist/shared/metadata-process.d.ts.map +1 -1
  173. package/dist/shared/metadata-process.js +36 -6
  174. package/dist/shared/posix-path.d.ts +12 -0
  175. package/dist/shared/posix-path.d.ts.map +1 -1
  176. package/dist/shared/posix-path.js +16 -0
  177. package/dist/shared/private-file.d.ts +38 -0
  178. package/dist/shared/private-file.d.ts.map +1 -0
  179. package/dist/shared/private-file.js +132 -0
  180. package/dist/shared/process-ownership.d.ts +52 -2
  181. package/dist/shared/process-ownership.d.ts.map +1 -1
  182. package/dist/shared/process-ownership.js +104 -11
  183. package/dist/shared/process-supervisor.d.ts +309 -0
  184. package/dist/shared/process-supervisor.d.ts.map +1 -0
  185. package/dist/shared/process-supervisor.js +737 -0
  186. package/dist/shared/process-system.d.ts +113 -0
  187. package/dist/shared/process-system.d.ts.map +1 -0
  188. package/dist/shared/process-system.js +254 -0
  189. package/dist/shared/regular-file.d.ts +2 -2
  190. package/dist/shared/regular-file.d.ts.map +1 -1
  191. package/dist/shared/regular-file.js +19 -2
  192. package/dist/store/artifacts.d.ts +5 -4
  193. package/dist/store/artifacts.d.ts.map +1 -1
  194. package/dist/store/artifacts.js +18 -5
  195. package/dist/store/run-store.d.ts +5 -2
  196. package/dist/store/run-store.d.ts.map +1 -1
  197. package/dist/store/run-store.js +48 -4
  198. package/media/Cargo.lock +16 -0
  199. package/media/Cargo.toml +16 -1
  200. package/media/src/encoder.rs +56 -12
  201. package/media/src/frame.rs +7 -1
  202. package/media/src/main.rs +20 -2
  203. package/media/src/place.rs +230 -11
  204. package/media/src/process_ownership_windows.rs +1058 -0
  205. package/media/src/recording.rs +15 -7
  206. package/media/src/replies.rs +55 -0
  207. package/media/src/server.rs +72 -2
  208. package/media/src/store.rs +71 -2
  209. package/package.json +9 -4
  210. package/src/cli/install/media-notices.txt +465 -1
  211. package/supervisor/Cargo.lock +25 -0
  212. package/supervisor/Cargo.toml +22 -0
  213. package/supervisor/NOTICES +464 -0
  214. package/supervisor/build.rs +44 -0
  215. package/supervisor/src/command.rs +612 -0
  216. package/supervisor/src/jobs.rs +1650 -0
  217. package/supervisor/src/main.rs +71 -0
  218. package/supervisor/src/reply.rs +367 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor-locate.d.ts","sourceRoot":"","sources":["../../../src/cli/install/supervisor-locate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAA;AAC3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAa5D,6EAA6E;AAC7E,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;AAEhF,mEAAmE;AACnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,aAAa,EAAE,CAAC,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,gBAAgB,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC3C,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAGD,2DAA2D;AAC3D,eAAO,MAAM,wBAAwB,EAAE,MAAwC,CAAA;AAE/E;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAIzG;AAED,gIAAgI;AAChI,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAQtM;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,qBAAqB,GAAG,iBAAiB,CAsB9E"}
@@ -0,0 +1,113 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { closeSync, fstatSync, lstatSync, openSync, readSync } from 'node:fs';
3
+ import { win32 } from 'node:path';
4
+ import { errorMessage } from "../../protocol/failures.js";
5
+ import { parse, s } from "../../protocol/schema.js";
6
+ import { isMissingFile } from "../../shared/error-code.js";
7
+ import { maximumSupervisorBinaryBytes, maximumSupervisorSourceBytes, supervisorExecutable, supervisorSourceDigest, supervisorTarget, supervisorVersion } from "./supervisor-pins.js";
8
+ const recordSchema = s.object({ schemaVersion: s.literal(1), version: s.string(), sourceDigest: s.string(), target: s.enum(['x86_64-pc-windows-msvc']), rustcVersion: s.string(), binarySha256: s.string(), notices: s.array(s.object({ path: s.string(), sha256: s.string() })), installedAt: s.string(), installedBy: s.string() });
9
+ /** The command a person runs to install the supervisor. */
10
+ export const supervisorInstallCommand = 'npx retest install supervisor';
11
+ /**
12
+ * The cache folder one supervisor build is installed in: `%LOCALAPPDATA%\retest\supervisor\…`, beside the browsers in
13
+ * the root `cacheFolders` gives on Windows, the one platform it is built for. LOCALAPPDATA is read in any case, as
14
+ * Windows reads it.
15
+ *
16
+ * @example supervisorFolder(process.env, 'x86_64-pc-windows-msvc') // 'C:\\Users\\ada\\AppData\\Local\\retest\\supervisor\\supervisor-0.1.0-x86_64-pc-windows-msvc'
17
+ */
18
+ export function supervisorFolder(env, target) {
19
+ const local = env['LOCALAPPDATA'] ?? Object.entries(env).find(([name]) => name.toUpperCase() === 'LOCALAPPDATA')?.[1];
20
+ if (local === undefined || !win32.isAbsolute(local))
21
+ return undefined;
22
+ return win32.join(local, 'retest', 'supervisor', `supervisor-${supervisorVersion}-${target}`);
23
+ }
24
+ /** A `build.json` read as this version's record, or why it is not one; its notices and toolchain are the install's to check. */
25
+ export function readSupervisorRecord(path, text, target) {
26
+ let parsed;
27
+ try {
28
+ parsed = parse(recordSchema, JSON.parse(text));
29
+ }
30
+ catch {
31
+ return { ok: false, problem: `${path} is not JSON.` };
32
+ }
33
+ if (!parsed.ok)
34
+ return { ok: false, problem: `${path} is not a valid supervisor build record.` };
35
+ const record = parsed.value;
36
+ if (record.version !== supervisorVersion || record.target !== target || record.sourceDigest !== supervisorSourceDigest)
37
+ return { ok: false, problem: `${path} names another supervisor version, source or target.` };
38
+ if (!/^[a-f0-9]{64}$/.test(record.binarySha256) || !Number.isFinite(Date.parse(record.installedAt)) || record.installedBy === '')
39
+ return { ok: false, problem: `${path} has an invalid checksum or installer identity.` };
40
+ return { ok: true, record };
41
+ }
42
+ /**
43
+ * The supervisor the runner runs, found as it starts its first process on Windows: the executable
44
+ * RETEST_SUPERVISOR_BINARY names, as a developer build is given, or the installed one, read back against its record and
45
+ * its checksum. It reads synchronously, since it runs once, right before a launch that cannot start without it. Throws a
46
+ * sentence that says how to install it.
47
+ *
48
+ * @example const program = locateSupervisor(process.env) // { command: '…\\retest-supervisor.exe', args: [], environment: { SystemRoot: 'C:\\Windows' } }
49
+ */
50
+ export function locateSupervisor(env) {
51
+ const environment = { SystemRoot: env['SystemRoot'] ?? env['SYSTEMROOT'] ?? 'C:\\Windows' };
52
+ const explicit = env['RETEST_SUPERVISOR_BINARY'];
53
+ if (explicit !== undefined && explicit !== '') {
54
+ if (!win32.isAbsolute(explicit))
55
+ throw new Error(`RETEST_SUPERVISOR_BINARY is ${explicit}, which is not an absolute path.`);
56
+ if (lstatSync(explicit, { throwIfNoEntry: false })?.isFile() !== true)
57
+ throw new Error(`RETEST_SUPERVISOR_BINARY names ${explicit}, which is not a file.`);
58
+ return { command: explicit, args: [], environment };
59
+ }
60
+ const target = supervisorTarget();
61
+ if (target === undefined)
62
+ throw new Error(`retest-supervisor is for Windows x64, and this machine is ${process.platform} ${process.arch}.`);
63
+ const folder = supervisorFolder(env, target);
64
+ if (folder === undefined)
65
+ throw new Error(`LOCALAPPDATA names no folder, so Retest cannot find retest-supervisor. ${missingSentence}`);
66
+ const executable = win32.join(folder, supervisorExecutable);
67
+ const recordPath = win32.join(folder, 'build.json');
68
+ const recordText = readBounded(recordPath, maximumSupervisorSourceBytes);
69
+ if (recordText === undefined)
70
+ throw new Error(missingSentence);
71
+ const checked = readSupervisorRecord(recordPath, recordText.toString('utf8'), target);
72
+ if (!checked.ok)
73
+ throw new Error(`${checked.problem} Remove ${folder}, then run ${supervisorInstallCommand}.`);
74
+ const binary = readBounded(executable, maximumSupervisorBinaryBytes);
75
+ if (binary === undefined)
76
+ throw new Error(`${executable} is missing. Remove ${folder}, then run ${supervisorInstallCommand}.`);
77
+ if (createHash('sha256').update(binary).digest('hex') !== checked.record.binarySha256)
78
+ throw new Error(`${executable} does not match its record. Remove ${folder}, then run ${supervisorInstallCommand}.`);
79
+ return { command: executable, args: [], environment };
80
+ }
81
+ const missingSentence = `Retest needs retest-supervisor on Windows to end everything it starts. Run ${supervisorInstallCommand}.`;
82
+ // A regular file's bytes, read through one descriptor, or undefined when nothing is there; a file past the bound, or one
83
+ // that is not a regular file, is refused by name.
84
+ function readBounded(path, maximumBytes) {
85
+ let descriptor;
86
+ try {
87
+ descriptor = openSync(path, 'r');
88
+ }
89
+ catch (error) {
90
+ if (isMissingFile(error))
91
+ return undefined;
92
+ throw new Error(`${path} cannot be read: ${errorMessage(error)}`);
93
+ }
94
+ try {
95
+ const stats = fstatSync(descriptor);
96
+ if (!stats.isFile() || stats.size > maximumBytes)
97
+ throw new Error(`${path} is not a regular file of at most ${maximumBytes} bytes.`);
98
+ const bytes = Buffer.alloc(stats.size);
99
+ let filled = 0;
100
+ while (filled < bytes.length) {
101
+ const read = readSync(descriptor, bytes, filled, bytes.length - filled, filled);
102
+ if (read === 0)
103
+ break;
104
+ filled += read;
105
+ }
106
+ if (filled !== stats.size)
107
+ throw new Error(`${path} changed size while it was read.`);
108
+ return bytes;
109
+ }
110
+ finally {
111
+ closeSync(descriptor);
112
+ }
113
+ }
@@ -0,0 +1,21 @@
1
+ export declare const supervisorVersion: string;
2
+ export declare const minimumSupervisorRust: string;
3
+ /** Fixed limits for the built binary and each shipped source or notice file. */
4
+ export declare const maximumSupervisorBinaryBytes: number;
5
+ export declare const maximumSupervisorSourceBytes: number;
6
+ export declare const supervisorSourceDigest: string;
7
+ export declare const supervisorSourceFiles: readonly {
8
+ readonly path: string;
9
+ readonly sha256: string;
10
+ }[];
11
+ /** The one target the supervisor is for: Windows on x64. Windows on Arm comes in a later version. */
12
+ export type SupervisorTarget = 'x86_64-pc-windows-msvc';
13
+ /**
14
+ * The supervisor's build target on this machine, or undefined where Retest needs no supervisor or has none yet.
15
+ *
16
+ * @example supervisorTarget('win32', 'x64') // 'x86_64-pc-windows-msvc'
17
+ */
18
+ export declare function supervisorTarget(platform?: NodeJS.Platform, arch?: string): SupervisorTarget | undefined;
19
+ /** The executable `cargo build` writes and the install keeps. */
20
+ export declare const supervisorExecutable: string;
21
+ //# sourceMappingURL=supervisor-pins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor-pins.d.ts","sourceRoot":"","sources":["../../../src/cli/install/supervisor-pins.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,iBAAiB,EAAE,MAAgB,CAAA;AAChD,eAAO,MAAM,qBAAqB,EAAE,MAAiB,CAAA;AACrD,gFAAgF;AAChF,eAAO,MAAM,4BAA4B,EAAE,MAAyB,CAAA;AACpE,eAAO,MAAM,4BAA4B,EAAE,MAAoB,CAAA;AAC/D,eAAO,MAAM,sBAAsB,EAAE,MAA2E,CAAA;AAChH,eAAO,MAAM,qBAAqB,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAU9F,CAAA;AAED,qGAAqG;AACrG,MAAM,MAAM,gBAAgB,GAAG,wBAAwB,CAAA;AAEvD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAAE,IAAI,GAAE,MAAqB,GAAG,gBAAgB,GAAG,SAAS,CAExI;AAED,iEAAiE;AACjE,eAAO,MAAM,oBAAoB,EAAE,MAAgC,CAAA"}
@@ -0,0 +1,28 @@
1
+ // Pinned source files and notices of retest-supervisor. Regenerate together after a reviewed crate or lock change.
2
+ export const supervisorVersion = '0.1.0';
3
+ export const minimumSupervisorRust = '1.88.0';
4
+ /** Fixed limits for the built binary and each shipped source or notice file. */
5
+ export const maximumSupervisorBinaryBytes = 16 * 1024 * 1024;
6
+ export const maximumSupervisorSourceBytes = 1024 * 1024;
7
+ export const supervisorSourceDigest = '7790e5960f4247f8370a6b361d7f183b2b153f938c3a3fc091e723edd7f4e8a8';
8
+ export const supervisorSourceFiles = [
9
+ { path: 'supervisor/Cargo.toml', sha256: '8b21a215325f93ae556e4119cbf5e1161592bd9798aedb64e892b21b730fbf85' },
10
+ { path: 'supervisor/Cargo.lock', sha256: '5b18a75f3d341cc37c4c0178195ab3b29888e46b205e4e7ae59cadc041b403c9' },
11
+ { path: 'supervisor/build.rs', sha256: 'a1a635b6ace744ef9f0cadf8540f01daeefa0783d9789da0de0266ff3206cc82' },
12
+ { path: 'supervisor/src/command.rs', sha256: 'f395868828e1d3dcf39e6deb1165af62150add2df90059753aa22f781bc89299' },
13
+ { path: 'supervisor/src/jobs.rs', sha256: '2b78eea05a8037b2442290215c7ad12634f8ccd962f491dfdb0f1f5f9bb0d579' },
14
+ { path: 'supervisor/src/main.rs', sha256: '7a4ac8c30bd4401bb2e3cbc4d6621ea15444f5ddf2820388b7d5faffe8bf7e83' },
15
+ { path: 'supervisor/src/reply.rs', sha256: '97cca24d9a3a3d0403f37697b39aa6e134860aebb1a3345f0dfc19d07b95d3bd' },
16
+ { path: 'supervisor/NOTICES', sha256: 'b78dcadcbc81915a91a475ff1a6603b1b5261bb23d83e0fd36919bf782af8a1d' },
17
+ { path: 'LICENSE', sha256: 'cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30' },
18
+ ];
19
+ /**
20
+ * The supervisor's build target on this machine, or undefined where Retest needs no supervisor or has none yet.
21
+ *
22
+ * @example supervisorTarget('win32', 'x64') // 'x86_64-pc-windows-msvc'
23
+ */
24
+ export function supervisorTarget(platform = process.platform, arch = process.arch) {
25
+ return platform === 'win32' && arch === 'x64' ? 'x86_64-pc-windows-msvc' : undefined;
26
+ }
27
+ /** The executable `cargo build` writes and the install keeps. */
28
+ export const supervisorExecutable = 'retest-supervisor.exe';
@@ -0,0 +1,31 @@
1
+ import type { FileIdentity, FileReadControl } from '../../shared/regular-file.ts';
2
+ import type { SupervisorEnvironment, SupervisorRecord } from './supervisor-locate.ts';
3
+ import type { SupervisorTarget } from './supervisor-pins.ts';
4
+ export type { SupervisorEnvironment, SupervisorRecord } from './supervisor-locate.ts';
5
+ export { locateSupervisor, supervisorFolder, supervisorInstallCommand } from './supervisor-locate.ts';
6
+ export type SupervisorInspection = {
7
+ readonly engine: 'supervisor';
8
+ readonly version: string;
9
+ readonly target: SupervisorTarget;
10
+ readonly state: 'missing' | 'installed' | 'damaged';
11
+ readonly folder: string;
12
+ readonly executablePath: string;
13
+ readonly problems: readonly string[];
14
+ readonly record?: SupervisorRecord;
15
+ readonly executableIdentity?: FileIdentity;
16
+ };
17
+ export declare function supervisorPackageRoot(): string;
18
+ export declare function supervisorLockPath(folder: string): string;
19
+ /** The notices an installed supervisor carries beside it, under the names it keeps them by. */
20
+ export declare function supervisorNoticePins(): {
21
+ path: string;
22
+ sha256: string;
23
+ }[];
24
+ /** Hashes the pinned allowlist only. A changed, absent or non-regular source is refused before cargo runs. */
25
+ export declare function checkSupervisorSource(root: string, options?: FileReadControl): Promise<string | undefined>;
26
+ /** Reads an installed supervisor back: its record, its binary and notices against the record, and nothing else beside them. */
27
+ export declare function inspectSupervisor(env: SupervisorEnvironment, options?: FileReadControl & {
28
+ readonly target?: SupervisorTarget;
29
+ readonly verify?: boolean;
30
+ }): Promise<SupervisorInspection | undefined>;
31
+ //# sourceMappingURL=supervisor-record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor-record.d.ts","sourceRoot":"","sources":["../../../src/cli/install/supervisor-record.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AACjF,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACrF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAa5D,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACrF,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AAErG,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAA;IAC1F,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IAC5E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzG,QAAQ,CAAC,kBAAkB,CAAC,EAAE,YAAY,CAAA;CAC3C,CAAA;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAAwE;AAEvH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAA+E;AAEzI,+FAA+F;AAC/F,wBAAgB,oBAAoB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,CAEzE;AAED,8GAA8G;AAC9G,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAWpH;AAED,+HAA+H;AAC/H,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,qBAAqB,EAAE,OAAO,GAAE,eAAe,GAAG;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAwChN"}
@@ -0,0 +1,104 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { lstat, readdir } from 'node:fs/promises';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { Deadline } from "../../protocol/deadline.js";
6
+ import { errorMessage } from "../../protocol/failures.js";
7
+ import { isMissingFile } from "../../shared/error-code.js";
8
+ import { checkFileIdentity, readFileSha256, readRecordText } from "../../shared/regular-file.js";
9
+ import { rustAtLeast } from "./media-tools.js";
10
+ import { readSupervisorRecord, supervisorFolder } from "./supervisor-locate.js";
11
+ import { maximumSupervisorBinaryBytes, maximumSupervisorSourceBytes, minimumSupervisorRust, supervisorExecutable, supervisorSourceDigest, supervisorSourceFiles, supervisorTarget, supervisorVersion } from "./supervisor-pins.js";
12
+ export { locateSupervisor, supervisorFolder, supervisorInstallCommand } from "./supervisor-locate.js";
13
+ export function supervisorPackageRoot() { return resolve(dirname(fileURLToPath(import.meta.url)), '../../..'); }
14
+ export function supervisorLockPath(folder) { return join(dirname(dirname(folder)), 'locks', `${basename(folder)}.lock`); }
15
+ /** The notices an installed supervisor carries beside it, under the names it keeps them by. */
16
+ export function supervisorNoticePins() {
17
+ return supervisorSourceFiles.filter((file) => file.path === 'LICENSE' || file.path === 'supervisor/NOTICES').map((file) => ({ path: file.path === 'LICENSE' ? 'LICENSE' : 'THIRD-PARTY-NOTICES.txt', sha256: file.sha256 }));
18
+ }
19
+ /** Hashes the pinned allowlist only. A changed, absent or non-regular source is refused before cargo runs. */
20
+ export async function checkSupervisorSource(root, options = {}) {
21
+ const deadline = options.deadline ?? new Deadline(15_000, { signal: options.signal });
22
+ const hash = createHash('sha256');
23
+ for (const file of supervisorSourceFiles) {
24
+ const path = join(root, ...file.path.split('/'));
25
+ const reading = await readFileSha256(path, { maximumBytes: maximumSupervisorSourceBytes, deadline, ...(options.signal === undefined ? {} : { signal: options.signal }) });
26
+ if (reading.kind !== 'file')
27
+ return reading.kind === 'other' ? reading.problem : `${path} is missing. Reinstall the Retest package with its supervisor source and notices.`;
28
+ if (reading.sha256 !== file.sha256)
29
+ return `${path} has SHA-256 ${reading.sha256}, not the pinned ${file.sha256}. Reinstall the Retest package; cargo was not run.`;
30
+ hash.update(`${file.path}\0${reading.sha256}\n`);
31
+ }
32
+ return hash.digest('hex') === supervisorSourceDigest ? undefined : 'The supervisor source manifest does not match its pinned digest. Reinstall Retest.';
33
+ }
34
+ /** Reads an installed supervisor back: its record, its binary and notices against the record, and nothing else beside them. */
35
+ export async function inspectSupervisor(env, options = {}) {
36
+ const deadline = options.deadline ?? new Deadline(15_000, { signal: options.signal });
37
+ const target = options.target ?? supervisorTarget();
38
+ if (target === undefined)
39
+ return undefined;
40
+ const folder = supervisorFolder(env, target);
41
+ if (folder === undefined)
42
+ return undefined;
43
+ const base = { engine: 'supervisor', version: supervisorVersion, target, folder, executablePath: join(folder, supervisorExecutable) };
44
+ const bad = (problem) => ({ ...base, state: 'damaged', problems: [problem] });
45
+ try {
46
+ const stats = await lstat(folder);
47
+ if (!stats.isDirectory())
48
+ return bad(`${folder} is not a regular cache folder. Remove it before installing again.`);
49
+ }
50
+ catch (error) {
51
+ return isMissingFile(error) ? { ...base, state: 'missing', problems: [] } : bad(`${folder} cannot be read: ${errorMessage(error)}`);
52
+ }
53
+ const path = join(folder, 'build.json');
54
+ const read = await readRecordText(path, { deadline, ...(options.signal === undefined ? {} : { signal: options.signal }) });
55
+ if (read.kind !== 'text')
56
+ return bad(`${path} cannot be read: ${read.kind === 'missing' ? 'it is missing' : read.problem}.`);
57
+ const checked = checkRecord(path, read.text, target);
58
+ if (!checked.ok)
59
+ return bad(checked.problem);
60
+ const record = checked.record;
61
+ const notices = supervisorNoticePins();
62
+ let executableIdentity;
63
+ for (const file of [{ path: supervisorExecutable, sha256: record.binarySha256 }, ...notices]) {
64
+ const filePath = join(folder, file.path);
65
+ const reading = await readFileSha256(filePath, { maximumBytes: file.path === supervisorExecutable ? maximumSupervisorBinaryBytes : maximumSupervisorSourceBytes, deadline, ...(options.signal === undefined ? {} : { signal: options.signal }) });
66
+ if (reading.kind !== 'file')
67
+ return bad(reading.kind === 'missing' ? `${filePath} is missing.` : reading.problem);
68
+ if (reading.sha256 !== file.sha256)
69
+ return bad(`${filePath} has SHA-256 ${reading.sha256}, not the recorded ${file.sha256}.`);
70
+ if (file.path === supervisorExecutable)
71
+ executableIdentity = reading.identity;
72
+ }
73
+ if (options.verify === true) {
74
+ const allowed = new Set(['build.json', supervisorExecutable, ...notices.map((notice) => notice.path)]);
75
+ let names;
76
+ try {
77
+ names = await readdir(folder);
78
+ }
79
+ catch (error) {
80
+ return bad(`${folder} cannot be checked: ${errorMessage(error)}.`);
81
+ }
82
+ const unexpected = names.filter((name) => !allowed.has(name));
83
+ if (unexpected.length > 0)
84
+ return bad(`${folder} holds unrecorded entries: ${unexpected.slice(0, 5).join(', ')}.`);
85
+ }
86
+ if (executableIdentity === undefined)
87
+ return bad(`${base.executablePath} has no hashed file identity.`);
88
+ const changed = await checkFileIdentity(base.executablePath, executableIdentity);
89
+ if (changed !== undefined)
90
+ return bad(changed);
91
+ return { ...base, state: 'installed', problems: [], record, executableIdentity };
92
+ }
93
+ // The record as the locator reads it, and also the toolchain it names and the notices it lists, which an install
94
+ // checks and a launch has no need to.
95
+ function checkRecord(path, text, target) {
96
+ const read = readSupervisorRecord(path, text, target);
97
+ if (!read.ok)
98
+ return read;
99
+ if (!rustAtLeast(read.record.rustcVersion, minimumSupervisorRust))
100
+ return { ok: false, problem: `${path} names no supported rustc build version.` };
101
+ if (JSON.stringify(read.record.notices) !== JSON.stringify(supervisorNoticePins()))
102
+ return { ok: false, problem: `${path} does not name the pinned licence notices.` };
103
+ return read;
104
+ }
@@ -0,0 +1,11 @@
1
+ import type { Marks } from '../../reporters/style.ts';
2
+ import type { SupervisorInspection } from './supervisor-record.ts';
3
+ import type { SupervisorInstallResult } from './supervisor-install.ts';
4
+ /**
5
+ * Adds the supervisor to the install list, as text or as one more build in the JSON document. Where no supervisor is
6
+ * needed there is nothing to add, and the list is left exactly as it was.
7
+ */
8
+ export declare function renderSupervisorListing(listing: string, inspection: SupervisorInspection | undefined, json: boolean): string;
9
+ /** The install's outcome, marked as the console can draw it: a plain Windows console has no check or cross glyph. */
10
+ export declare function renderSupervisorInstall(result: SupervisorInstallResult, marks?: Marks): string;
11
+ //# sourceMappingURL=supervisor-report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor-report.d.ts","sourceRoot":"","sources":["../../../src/cli/install/supervisor-report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAClE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AAMtE;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,oBAAoB,GAAG,SAAS,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAgB5H;AAED,qHAAqH;AACrH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,uBAAuB,EAAE,KAAK,GAAE,KAAoB,GAAG,MAAM,CAG5G"}
@@ -0,0 +1,33 @@
1
+ import { isArray, isPlainObject } from "../../protocol/schema.js";
2
+ import { unicodeMarks } from "../../reporters/style.js";
3
+ import { supervisorSourceDigest } from "./supervisor-pins.js";
4
+ import { supervisorInstallCommand } from "./supervisor-locate.js";
5
+ /**
6
+ * Adds the supervisor to the install list, as text or as one more build in the JSON document. Where no supervisor is
7
+ * needed there is nothing to add, and the list is left exactly as it was.
8
+ */
9
+ export function renderSupervisorListing(listing, inspection, json) {
10
+ if (inspection === undefined)
11
+ return listing;
12
+ if (!json) {
13
+ const problems = inspection.problems.map((problem) => ` ${problem}\n`).join('');
14
+ const last = inspection.record === undefined ? `Run ${supervisorInstallCommand}.` : `binary SHA-256 ${inspection.record.binarySha256}`;
15
+ return `${listing}\n supervisor retest-supervisor ${inspection.version}, ${inspection.target}\n ${inspection.state}\n ${inspection.executablePath}\n${problems} ${last}\n`;
16
+ }
17
+ const document = JSON.parse(listing);
18
+ if (!isPlainObject(document) || document['schemaVersion'] !== 1 || !isArray(document['builds']))
19
+ throw new Error('The install list did not match its versioned document.');
20
+ const supervisor = {
21
+ engine: 'supervisor', title: 'retest-supervisor', version: inspection.version, target: inspection.target, source: 'shipped crate source', pinnedSha256: null,
22
+ sourceDigest: supervisorSourceDigest, state: inspection.state, folder: inspection.folder, executablePath: inspection.executablePath,
23
+ binarySha256: inspection.record?.binarySha256, rustcVersion: inspection.record?.rustcVersion, installedAt: inspection.record?.installedAt,
24
+ problems: inspection.problems, install: { command: supervisorInstallCommand },
25
+ };
26
+ return `${JSON.stringify({ ...document, builds: [...document['builds'], supervisor] }, null, 2)}\n`;
27
+ }
28
+ /** The install's outcome, marked as the console can draw it: a plain Windows console has no check or cross glyph. */
29
+ export function renderSupervisorInstall(result, marks = unicodeMarks) {
30
+ if (!result.ok)
31
+ return ` ${marks.failed} supervisor ${result.message}\n`;
32
+ return ` ${marks.passed} supervisor retest-supervisor ${result.inspection.version} ${result.action === 'installed' ? 'installed' : 'was already installed'}\n ${result.inspection.executablePath}\n binary SHA-256 ${result.inspection.record?.binarySha256 ?? 'unavailable'}\n Runs find it in this checked cache.\n`;
33
+ }
@@ -0,0 +1,43 @@
1
+ import type { CommandResult } from '../../native/processes.ts';
2
+ import type { SupervisorEnvironment } from './supervisor-locate.ts';
3
+ export type SupervisorToolOptions = {
4
+ readonly env: SupervisorEnvironment;
5
+ readonly signal?: AbortSignal | undefined;
6
+ readonly cwd?: string;
7
+ readonly timeoutMs?: number;
8
+ readonly environment?: Readonly<Record<string, string>>;
9
+ };
10
+ export type SupervisorToolRunner = (command: string, args: readonly string[], options: SupervisorToolOptions) => Promise<CommandResult>;
11
+ /**
12
+ * Runs a build tool with only the variables `toolVariables` names, whatever case the environment spells them in. This
13
+ * is the one start on Windows no job holds, since the job's supervisor is what it builds: the tool is held by Node's own
14
+ * handle on it, which ends it at the deadline or the stop and lets no other process stand behind its pid, and the
15
+ * compilers it starts finish their own work. Nothing a test starts runs this way.
16
+ */
17
+ export declare const runSupervisorTool: SupervisorToolRunner;
18
+ /**
19
+ * A tool on PATH, by its own name, or by that name and `.exe` on Windows. A name with a folder in it is taken as given.
20
+ *
21
+ * @example await findSupervisorTool('cargo', process.env) // 'C:\\Users\\ada\\.cargo\\bin\\cargo.exe'
22
+ */
23
+ export declare function findSupervisorTool(name: string, env: SupervisorEnvironment, platform?: NodeJS.Platform): Promise<string | undefined>;
24
+ export type SupervisorRust = {
25
+ readonly ok: true;
26
+ readonly cargo: string;
27
+ readonly rustc: string;
28
+ readonly rustcVersion: string;
29
+ readonly target: string;
30
+ } | {
31
+ readonly ok: false;
32
+ readonly message: string;
33
+ };
34
+ export declare const supervisorRustFix: string;
35
+ /** cargo and rustc on PATH, new enough, and the target rustc builds for by default. */
36
+ export declare function readSupervisorRust(env: SupervisorEnvironment, signal: AbortSignal | undefined, run?: SupervisorToolRunner): Promise<SupervisorRust>;
37
+ /**
38
+ * Asks a built supervisor for its version line, which must name this version and the target it was built for.
39
+ *
40
+ * @example await probeSupervisor(executable, 'x86_64-pc-windows-msvc', { env: process.env }) // undefined
41
+ */
42
+ export declare function probeSupervisor(executable: string, target: string, options: SupervisorToolOptions, run?: SupervisorToolRunner): Promise<string | undefined>;
43
+ //# sourceMappingURL=supervisor-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor-tools.d.ts","sourceRoot":"","sources":["../../../src/cli/install/supervisor-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAWnE,MAAM,MAAM,qBAAqB,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,qBAAqB,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,CAAA;AACnO,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,qBAAqB,KAAK,OAAO,CAAC,aAAa,CAAC,CAAA;AASvI;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,oBAkB/B,CAAA;AAcD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,qBAAqB,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAa5J;AAED,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAC7M,eAAO,MAAM,iBAAiB,EAAE,MAAsJ,CAAA;AAEtL,uFAAuF;AACvF,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,EAAE,GAAG,GAAE,oBAAwC,GAAG,OAAO,CAAC,cAAc,CAAC,CAc5K;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,EAAE,GAAG,GAAE,oBAAwC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAMpL"}
@@ -0,0 +1,114 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { constants } from 'node:fs';
3
+ import { access, stat } from 'node:fs/promises';
4
+ import { delimiter, isAbsolute, join, resolve } from 'node:path';
5
+ import { describeCommand } from "../../native/processes.js";
6
+ import { errorCode } from "../../shared/error-code.js";
7
+ import { rustAtLeast } from "./media-tools.js";
8
+ import { minimumSupervisorRust, supervisorVersion } from "./supervisor-pins.js";
9
+ import { supervisorInstallCommand } from "./supervisor-locate.js";
10
+ // What cargo and the MSVC linker need to find themselves and their caches on Windows, and nothing that carries a
11
+ // credential: no test or evaluator variable reaches the build.
12
+ const toolVariables = ['PATH', 'PATHEXT', 'SystemRoot', 'SystemDrive', 'windir', 'USERPROFILE', 'HOMEDRIVE', 'HOMEPATH', 'LOCALAPPDATA', 'APPDATA', 'ProgramData', 'ProgramFiles', 'ProgramFiles(x86)', 'ProgramW6432', 'CommonProgramFiles', 'CommonProgramFiles(x86)', 'TEMP', 'TMP', 'CARGO_HOME', 'CARGO_NET_OFFLINE', 'RUSTUP_HOME', 'RUSTUP_TOOLCHAIN', 'NUMBER_OF_PROCESSORS', 'PROCESSOR_ARCHITECTURE', 'HOME', 'TMPDIR'];
13
+ // How much a build tool may print before it is stopped; a release build of the crate prints a few kilobytes.
14
+ const toolOutputLimit = 16 * 1024 * 1024;
15
+ /**
16
+ * Runs a build tool with only the variables `toolVariables` names, whatever case the environment spells them in. This
17
+ * is the one start on Windows no job holds, since the job's supervisor is what it builds: the tool is held by Node's own
18
+ * handle on it, which ends it at the deadline or the stop and lets no other process stand behind its pid, and the
19
+ * compilers it starts finish their own work. Nothing a test starts runs this way.
20
+ */
21
+ export const runSupervisorTool = (command, args, options) => {
22
+ const environment = {};
23
+ for (const name of toolVariables) {
24
+ const value = valueOf(options.env, name);
25
+ if (value !== undefined)
26
+ environment[name] = value;
27
+ }
28
+ const { promise, resolve: settle } = Promise.withResolvers();
29
+ const ended = { code: null, signal: null, stdout: '', stderr: '', timedOut: false, stopped: false, started: true, cleanupProblems: [] };
30
+ if (options.signal?.aborted === true)
31
+ return Promise.resolve({ ...ended, stopped: true, started: false });
32
+ execFile(command, [...args], { env: { ...environment, ...options.environment }, ...(options.cwd === undefined ? {} : { cwd: options.cwd }), timeout: options.timeoutMs ?? 10_000, maxBuffer: toolOutputLimit, windowsHide: true, encoding: 'utf8', ...(options.signal === undefined ? {} : { signal: options.signal }) }, (error, stdout, stderr) => {
33
+ if (error === null)
34
+ return settle({ ...ended, code: 0, stdout, stderr });
35
+ if (errorCode(error) === 'ENOENT')
36
+ return settle({ ...ended, code: 127, stderr: error.message, started: false });
37
+ const stopped = options.signal?.aborted === true;
38
+ const exitCode = 'code' in error && typeof error.code === 'number' ? error.code : null;
39
+ const signal = 'signal' in error && typeof error.signal === 'string' ? error.signal : null;
40
+ settle({ ...ended, code: exitCode, signal: isSignal(signal) ? signal : null, stdout, stderr, stopped, timedOut: !stopped && 'killed' in error && error.killed === true });
41
+ });
42
+ return promise;
43
+ };
44
+ function isSignal(name) {
45
+ return name !== null && /^SIG[A-Z0-9]+$/.test(name);
46
+ }
47
+ // Windows treats variable names without regard to case; a copied environment does not.
48
+ function valueOf(env, name) {
49
+ const exact = env[name];
50
+ if (exact !== undefined)
51
+ return exact;
52
+ const wanted = name.toLowerCase();
53
+ return Object.entries(env).find(([key]) => key.toLowerCase() === wanted)?.[1];
54
+ }
55
+ /**
56
+ * A tool on PATH, by its own name, or by that name and `.exe` on Windows. A name with a folder in it is taken as given.
57
+ *
58
+ * @example await findSupervisorTool('cargo', process.env) // 'C:\\Users\\ada\\.cargo\\bin\\cargo.exe'
59
+ */
60
+ export async function findSupervisorTool(name, env, platform = process.platform) {
61
+ const names = platform === 'win32' && !name.toLowerCase().endsWith('.exe') ? [`${name}.exe`, name] : [name];
62
+ const paths = isAbsolute(name) || name.includes('/') || name.includes('\\')
63
+ ? [resolve(name)]
64
+ : (valueOf(env, 'PATH') ?? '').split(delimiter).filter((part) => isAbsolute(part)).flatMap((part) => names.map((each) => join(part, each)));
65
+ for (const path of paths) {
66
+ try {
67
+ if (!(await stat(path)).isFile())
68
+ continue;
69
+ await access(path, constants.X_OK);
70
+ return path;
71
+ }
72
+ catch {
73
+ continue;
74
+ }
75
+ }
76
+ return undefined;
77
+ }
78
+ export const supervisorRustFix = `Install Rust ${minimumSupervisorRust} or later with the MSVC toolchain, with cargo and rustc on PATH, then run ${supervisorInstallCommand}.`;
79
+ /** cargo and rustc on PATH, new enough, and the target rustc builds for by default. */
80
+ export async function readSupervisorRust(env, signal, run = runSupervisorTool) {
81
+ const cargo = await findSupervisorTool('cargo', env);
82
+ if (cargo === undefined)
83
+ return { ok: false, message: `cargo is missing from PATH. ${supervisorRustFix}` };
84
+ const rustc = await findSupervisorTool('rustc', env);
85
+ if (rustc === undefined)
86
+ return { ok: false, message: `rustc is missing from PATH. ${supervisorRustFix}` };
87
+ const cargoVersion = await run(cargo, ['--version'], { env, signal });
88
+ if (cargoVersion.stopped || cargoVersion.timedOut || cargoVersion.cleanupProblems.length > 0)
89
+ return { ok: false, message: `${describeCommand('cargo --version', cargoVersion)}. ${supervisorRustFix}` };
90
+ if (cargoVersion.code !== 0 || !rustAtLeast(cargoVersion.stdout, minimumSupervisorRust))
91
+ return { ok: false, message: `cargo is unreadable or older than Rust ${minimumSupervisorRust}. ${supervisorRustFix}` };
92
+ const rust = await run(rustc, ['-vV'], { env, signal });
93
+ if (rust.code !== 0 || rust.stopped || rust.timedOut || rust.cleanupProblems.length > 0)
94
+ return { ok: false, message: `${describeCommand('rustc -vV', rust)}. ${supervisorRustFix}` };
95
+ const version = rust.stdout.split(/\r?\n/)[0]?.trim() ?? '';
96
+ const target = /^host: ([a-z0-9_-]+)\r?$/m.exec(rust.stdout)?.[1];
97
+ if (!rustAtLeast(version, minimumSupervisorRust) || target === undefined)
98
+ return { ok: false, message: `rustc is unreadable or older than Rust ${minimumSupervisorRust}. ${supervisorRustFix}` };
99
+ return { ok: true, cargo, rustc, rustcVersion: version, target };
100
+ }
101
+ /**
102
+ * Asks a built supervisor for its version line, which must name this version and the target it was built for.
103
+ *
104
+ * @example await probeSupervisor(executable, 'x86_64-pc-windows-msvc', { env: process.env }) // undefined
105
+ */
106
+ export async function probeSupervisor(executable, target, options, run = runSupervisorTool) {
107
+ const result = await run(executable, ['--version'], options);
108
+ if (result.code !== 0 || result.stopped || result.timedOut || result.cleanupProblems.length > 0)
109
+ return `${describeCommand('retest-supervisor --version', result)}. Nothing was installed.`;
110
+ const line = result.stdout.trim();
111
+ if (!line.startsWith(`retest-supervisor ${supervisorVersion} (${target},`))
112
+ return `The built supervisor names itself ${JSON.stringify(line.slice(0, 200))}, not retest-supervisor ${supervisorVersion} for ${target}. Nothing was installed.`;
113
+ return undefined;
114
+ }
@@ -4,6 +4,7 @@ export type UnpackTools = {
4
4
  readonly ditto: string;
5
5
  readonly unzip: string;
6
6
  readonly hdiutil: string;
7
+ readonly tar: string;
7
8
  };
8
9
  export declare const systemUnpackTools: UnpackTools;
9
10
  /** What `unpackArchive` needs: the verified archive, how it is packed, an empty folder to unpack into, and the host. */
@@ -15,20 +16,39 @@ export type UnpackRequest = {
15
16
  readonly tools: UnpackTools;
16
17
  readonly signal: AbortSignal;
17
18
  readonly timeoutMs: number;
19
+ /** Where a line goes about something the unpack left behind without failing, such as a copy it could not remove. */
20
+ readonly report: (line: string) => void;
18
21
  };
19
22
  /**
20
23
  * Unpacks a verified archive into an empty folder, and returns why it could not, or undefined once it has. A zip is
21
24
  * unpacked whole, keeping its links and modes; from a disk image, only the app bundle the pin names is copied, and the
22
- * image is detached again whatever happened.
25
+ * image is detached again whatever happened; from a Windows installer, only the folder the pin names is unpacked, out
26
+ * of the 7-Zip archive inside it, and its self-extracting stub never runs.
23
27
  *
24
- * @example await unpackArchive({ archive, format: { format: 'zip' }, into, platform: 'darwin', tools: systemUnpackTools, signal, timeoutMs: 600_000 })
28
+ * @example await unpackArchive({ archive, format: { format: 'zip' }, into, platform: 'darwin', tools: systemUnpackTools, signal, timeoutMs: 600_000, report: console.log })
25
29
  */
26
30
  export declare function unpackArchive(request: UnpackRequest): Promise<string | undefined>;
31
+ /** Where a 7-Zip archive starts inside a file, or why none was found there. */
32
+ export type SevenZipStart = {
33
+ readonly ok: true;
34
+ readonly offset: number;
35
+ } | {
36
+ readonly ok: false;
37
+ readonly problem: string;
38
+ };
39
+ /**
40
+ * Where the 7-Zip archive inside a file begins: the first place its signature stands, within the stub's reach, with a
41
+ * start header whose checksum holds and whose index lies inside the file with the checksum the header gives it. A
42
+ * signature that is only bytes of the stub fails those checks and is passed over.
43
+ *
44
+ * @example await sevenZipStart('/…/Firefox Setup 158.0.1.exe') // { ok: true, offset: 131665 }
45
+ */
46
+ export declare function sevenZipStart(path: string): Promise<SevenZipStart>;
27
47
  /**
28
48
  * The whole-disk devices `hdiutil info` lists for images mounted on one of `mountPoints`, or attached from one of
29
49
  * `images` with no mount point at all. An image mounted elsewhere is not counted.
30
50
  *
31
- * @example attachedDevices(text, { images: ['/…/Firefox 133.0.3.dmg'], mountPoints: ['/private/var/…/retest-install-image-a1b2'] }) // ['/dev/disk6']
51
+ * @example attachedDevices(text, { images: ['/…/Firefox 158.0.1.dmg'], mountPoints: ['/private/var/…/retest-install-image-a1b2'] }) // ['/dev/disk6']
32
52
  */
33
53
  export declare function attachedDevices(info: string, names: {
34
54
  readonly images: readonly string[];
@@ -1 +1 @@
1
- {"version":3,"file":"unpack.d.ts","sourceRoot":"","sources":["../../../src/cli/install/unpack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAY5D,wDAAwD;AACxD,MAAM,MAAM,WAAW,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtG,eAAO,MAAM,iBAAiB,EAAE,WAA+F,CAAA;AAE/H,wHAAwH;AACxH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAA;IAClC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAKvF;AAyFD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG,MAAM,EAAE,CAY9I"}
1
+ {"version":3,"file":"unpack.d.ts","sourceRoot":"","sources":["../../../src/cli/install/unpack.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAqB5D,wDAAwD;AACxD,MAAM,MAAM,WAAW,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAM5H,eAAO,MAAM,iBAAiB,EAAE,WAAgH,CAAA;AAEhJ,wHAAwH;AACxH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAA;IAClC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,oHAAoH;IACpH,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC,CAAA;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAOvF;AA8CD,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAQ7H;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAaxE;AAuGD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG,MAAM,EAAE,CAY9I"}