@prestack/thread 0.1.3 → 0.1.5

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 (270) hide show
  1. package/CHANGELOG.md +563 -0
  2. package/dist/core/actor.d.ts +1 -1
  3. package/dist/core/actor.js +5 -4
  4. package/dist/core/actor.ts +5 -4
  5. package/dist/core/attach.d.ts +0 -7
  6. package/dist/core/attach.js +38 -67
  7. package/dist/core/attach.ts +34 -80
  8. package/dist/core/chain.js +13 -17
  9. package/dist/core/chain.ts +11 -15
  10. package/dist/core/condition.d.ts +4 -4
  11. package/dist/core/condition.js +2 -2
  12. package/dist/core/descend.d.ts +4 -2
  13. package/dist/core/descend.js +91 -23
  14. package/dist/core/descend.ts +90 -21
  15. package/dist/core/errors/catalogue.d.ts +52 -0
  16. package/dist/{operations/errors/errors.js → core/errors/catalogue.js} +1 -1
  17. package/dist/{operations/errors/errors.ts → core/errors/catalogue.ts} +1 -1
  18. package/dist/{operations/errors/catalog.d.ts → core/errors/error.d.ts} +2 -3
  19. package/dist/{operations/errors/catalog.js → core/errors/error.js} +1 -16
  20. package/dist/{operations/errors/catalog.ts → core/errors/error.ts} +3 -19
  21. package/dist/core/errors/namespaces.d.ts +3 -0
  22. package/dist/core/errors/namespaces.js +15 -0
  23. package/dist/core/errors/namespaces.ts +19 -0
  24. package/dist/core/finding.js +2 -2
  25. package/dist/core/finding.ts +1 -2
  26. package/dist/core/init.d.ts +1 -1
  27. package/dist/core/init.js +51 -5
  28. package/dist/core/init.ts +58 -5
  29. package/dist/core/invoked.d.ts +1 -1
  30. package/dist/core/invoked.js +3 -1
  31. package/dist/core/invoked.ts +5 -2
  32. package/dist/core/round.js +36 -22
  33. package/dist/core/round.ts +26 -9
  34. package/dist/core/rows.d.ts +4 -0
  35. package/dist/core/rows.js +31 -2
  36. package/dist/core/rows.ts +33 -1
  37. package/dist/core/scope.d.ts +1 -0
  38. package/dist/core/scope.js +1 -0
  39. package/dist/core/scope.ts +2 -0
  40. package/dist/core/thread.d.ts +3 -3
  41. package/dist/core/tree.d.ts +4 -1
  42. package/dist/core/tree.js +26 -6
  43. package/dist/core/tree.ts +29 -6
  44. package/dist/core/types.d.ts +3 -3
  45. package/dist/core/types.js +3 -1
  46. package/dist/core/types.ts +2 -1
  47. package/dist/derivation/attach.js +3 -2
  48. package/dist/derivation/attach.ts +2 -1
  49. package/dist/derivation/generators/build.js +6 -6
  50. package/dist/derivation/generators/build.ts +1 -1
  51. package/dist/derivation/generators/check.js +1 -1
  52. package/dist/derivation/generators/determinism.js +1 -1
  53. package/dist/derivation/generators/release.js +60 -47
  54. package/dist/derivation/generators/release.ts +66 -43
  55. package/dist/derivation/release.d.ts +7 -3
  56. package/dist/derivation/release.js +14 -1
  57. package/dist/derivation/release.ts +22 -2
  58. package/dist/integrations/attach.d.ts +4 -0
  59. package/dist/integrations/attach.js +37 -56
  60. package/dist/integrations/attach.ts +54 -60
  61. package/dist/integrations/cli.d.ts +37 -0
  62. package/dist/integrations/cli.js +440 -0
  63. package/dist/integrations/cli.ts +543 -0
  64. package/dist/{core/files.d.ts → integrations/disk.d.ts} +16 -24
  65. package/dist/integrations/disk.js +435 -0
  66. package/dist/integrations/disk.ts +465 -0
  67. package/dist/integrations/generators/read.js +6 -6
  68. package/dist/integrations/generators/read.ts +4 -4
  69. package/dist/integrations/generators/vault.js +10 -11
  70. package/dist/integrations/generators/vault.ts +4 -5
  71. package/dist/{operations/http.d.ts → integrations/https.d.ts} +10 -6
  72. package/dist/integrations/https.js +170 -0
  73. package/dist/integrations/https.ts +216 -0
  74. package/dist/integrations/pglite.d.ts +26 -0
  75. package/dist/integrations/pglite.js +143 -0
  76. package/dist/integrations/pglite.ts +206 -0
  77. package/dist/integrations/secret.d.ts +8 -2
  78. package/dist/integrations/secret.js +3 -5
  79. package/dist/integrations/secret.ts +10 -6
  80. package/dist/integrations/target.d.ts +155 -14
  81. package/dist/integrations/target.js +171 -50
  82. package/dist/integrations/target.ts +377 -61
  83. package/dist/integrations/vault.d.ts +1 -0
  84. package/dist/integrations/vault.js +20 -5
  85. package/dist/integrations/vault.ts +17 -5
  86. package/dist/integrity/attach.js +8 -77
  87. package/dist/integrity/attach.ts +7 -76
  88. package/dist/integrity/check-threads.js +5 -5
  89. package/dist/integrity/check-threads.ts +2 -2
  90. package/dist/integrity/constraint.d.ts +0 -1
  91. package/dist/integrity/constraint.js +267 -366
  92. package/dist/integrity/constraint.ts +267 -375
  93. package/dist/integrity/generators/compiler.js +14 -17
  94. package/dist/integrity/generators/compiler.ts +10 -15
  95. package/dist/integrity/generators/gate.js +15 -15
  96. package/dist/integrity/generators/gate.ts +10 -10
  97. package/dist/integrity/generators/issues.js +7 -7
  98. package/dist/integrity/generators/publish.js +39 -57
  99. package/dist/integrity/generators/publish.ts +44 -55
  100. package/dist/integrity/generators/trial.d.ts +3 -3
  101. package/dist/integrity/generators/trial.js +133 -165
  102. package/dist/integrity/generators/trial.ts +145 -169
  103. package/dist/integrity/lint.js +9 -13
  104. package/dist/integrity/lint.ts +7 -10
  105. package/dist/integrity/lsp/bin.js +5 -4
  106. package/dist/integrity/lsp/bin.ts +3 -1
  107. package/dist/integrity/lsp/diagnostics.js +3 -3
  108. package/dist/integrity/lsp/diagnostics.ts +1 -1
  109. package/dist/integrity/lsp/lint.js +19 -30
  110. package/dist/integrity/lsp/lint.ts +16 -32
  111. package/dist/integrity/lsp/server.js +3 -3
  112. package/dist/integrity/slopnator.d.ts +3 -13
  113. package/dist/integrity/slopnator.js +59 -38
  114. package/dist/integrity/slopnator.ts +54 -38
  115. package/dist/integrity/slopnators/sl_broken_lineage.js +2 -2
  116. package/dist/integrity/slopnators/sl_declared_elsewhere.js +1 -1
  117. package/dist/integrity/slopnators/sl_derived_drift.js +4 -4
  118. package/dist/integrity/slopnators/sl_derived_drift.ts +1 -1
  119. package/dist/integrity/slopnators/sl_door_in_a_node.js +220 -0
  120. package/dist/integrity/slopnators/sl_door_in_a_node.ts +250 -0
  121. package/dist/integrity/slopnators/sl_inert_island.js +10 -8
  122. package/dist/integrity/slopnators/sl_inert_island.ts +8 -6
  123. package/dist/integrity/slopnators/sl_missing_row.js +6 -51
  124. package/dist/integrity/slopnators/sl_missing_row.ts +4 -50
  125. package/dist/integrity/slopnators/sl_no_barrel.js +1 -1
  126. package/dist/integrity/slopnators/sl_no_child_process.js +36 -18
  127. package/dist/integrity/slopnators/sl_no_child_process.ts +44 -18
  128. package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +80 -149
  129. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +95 -147
  130. package/dist/integrity/slopnators/sl_no_import_cycle.js +15 -4
  131. package/dist/integrity/slopnators/sl_no_import_cycle.ts +20 -4
  132. package/dist/integrity/slopnators/sl_no_such_command.js +2 -2
  133. package/dist/integrity/slopnators/sl_no_such_command.ts +1 -1
  134. package/dist/integrity/slopnators/sl_outside_an_operation.js +20 -0
  135. package/dist/integrity/slopnators/sl_outside_an_operation.ts +25 -0
  136. package/dist/integrity/slopnators/sl_screen_holds_only_declared.d.ts +2 -0
  137. package/dist/integrity/slopnators/sl_screen_holds_only_declared.js +61 -0
  138. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +70 -0
  139. package/dist/integrity/slopnators/sl_secret_literal.js +1 -33
  140. package/dist/integrity/slopnators/sl_secret_literal.ts +1 -33
  141. package/dist/integrity/slopnators/sl_stale_path.js +5 -22
  142. package/dist/integrity/slopnators/sl_stale_path.ts +4 -20
  143. package/dist/integrity/slopnators/sl_twice_meant.js +1 -1
  144. package/dist/integrity/slopnators/sl_twice_parented.js +29 -5
  145. package/dist/integrity/slopnators/sl_twice_parented.ts +28 -4
  146. package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +4 -1
  147. package/dist/integrity/slopnators/sl_undeclared_crossing.js +426 -62
  148. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +454 -70
  149. package/dist/integrity/slopnators/sl_undeclared_destination.js +59 -25
  150. package/dist/integrity/slopnators/sl_undeclared_destination.ts +69 -21
  151. package/dist/integrity/slopnators/sl_undeclared_list.d.ts +2 -0
  152. package/dist/integrity/slopnators/sl_undeclared_list.js +363 -0
  153. package/dist/integrity/slopnators/sl_undeclared_list.ts +358 -0
  154. package/dist/integrity/slopnators/sl_undeclared_node.js +1 -11
  155. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -10
  156. package/dist/integrity/slopnators/sl_undeclared_operation.js +111 -29
  157. package/dist/integrity/slopnators/sl_undeclared_operation.ts +134 -26
  158. package/dist/integrity/slopnators/sl_undeclared_variant.js +2 -2
  159. package/dist/integrity/slopnators/sl_undeclared_webhook.d.ts +2 -0
  160. package/dist/integrity/slopnators/sl_undeclared_webhook.js +90 -0
  161. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +108 -0
  162. package/dist/integrity/slopnators/sl_underived_route.d.ts +2 -0
  163. package/dist/integrity/slopnators/sl_underived_route.js +61 -0
  164. package/dist/integrity/slopnators/sl_underived_route.ts +72 -0
  165. package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -3
  166. package/dist/integrity/slopnators/sl_ungoverned_island.ts +1 -1
  167. package/dist/integrity/slopnators/sl_unlisted_program.d.ts +2 -0
  168. package/dist/integrity/slopnators/sl_unlisted_program.js +39 -0
  169. package/dist/integrity/slopnators/sl_unlisted_program.ts +40 -0
  170. package/dist/integrity/slopnators/sl_unnamed_island.js +1 -1
  171. package/dist/integrity/slopnators/sl_unpaired_member.js +3 -3
  172. package/dist/integrity/slopnators/sl_unpaired_member.ts +1 -1
  173. package/dist/integrity/slopnators/sl_unpaired_slopnator.js +4 -4
  174. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +1 -1
  175. package/dist/integrity/slopnators/sl_unprefixed_member.js +5 -31
  176. package/dist/integrity/slopnators/sl_unprefixed_member.ts +2 -28
  177. package/dist/integrity/slopnators/sl_unthreaded.js +11 -7
  178. package/dist/integrity/slopnators/sl_unthreaded.ts +6 -7
  179. package/dist/integrity/slopnators/sl_untranslated_key.js +2 -25
  180. package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -25
  181. package/dist/integrity/slopnators/sl_unvaulted_secret.d.ts +2 -0
  182. package/dist/integrity/slopnators/sl_unvaulted_secret.js +107 -0
  183. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +132 -0
  184. package/dist/integrity/slopnators/sl_unversioned.js +6 -4
  185. package/dist/integrity/slopnators/sl_unversioned.ts +5 -3
  186. package/dist/integrity/sweep.d.ts +1 -0
  187. package/dist/integrity/sweep.js +105 -42
  188. package/dist/integrity/sweep.ts +115 -42
  189. package/dist/knowledge/attach.js +4 -3
  190. package/dist/knowledge/attach.ts +3 -2
  191. package/dist/knowledge/generators/docsgen.js +8 -6
  192. package/dist/knowledge/generators/docsgen.ts +4 -2
  193. package/dist/knowledge/generators/glossary.js +4 -4
  194. package/dist/knowledge/generators/glossary.ts +1 -1
  195. package/dist/operations/access.d.ts +23 -0
  196. package/dist/operations/access.js +28 -1
  197. package/dist/operations/access.ts +43 -0
  198. package/dist/operations/attach.js +33 -113
  199. package/dist/operations/attach.ts +51 -117
  200. package/dist/operations/capabilities.d.ts +14 -5
  201. package/dist/operations/capabilities.js +13 -4
  202. package/dist/operations/capabilities.ts +27 -8
  203. package/dist/operations/dispatch.d.ts +8 -2
  204. package/dist/operations/dispatch.js +128 -185
  205. package/dist/operations/dispatch.ts +138 -191
  206. package/dist/operations/error-results.d.ts +1 -1
  207. package/dist/operations/error-results.js +2 -2
  208. package/dist/operations/error-results.ts +2 -2
  209. package/dist/operations/generators/capability.d.ts +11 -1
  210. package/dist/operations/generators/capability.js +481 -224
  211. package/dist/operations/generators/capability.ts +562 -233
  212. package/dist/operations/generators/compile.js +66 -39
  213. package/dist/operations/generators/compile.ts +78 -39
  214. package/dist/operations/generators/permits.js +11 -12
  215. package/dist/operations/generators/permits.ts +3 -4
  216. package/dist/operations/generators/seed.d.ts +1 -0
  217. package/dist/operations/generators/seed.js +19 -17
  218. package/dist/operations/generators/seed.ts +22 -14
  219. package/dist/operations/generators/trial.d.ts +6 -8
  220. package/dist/operations/generators/trial.js +200 -70
  221. package/dist/operations/generators/trial.ts +230 -93
  222. package/dist/operations/nodes.d.ts +15 -16
  223. package/dist/operations/nodes.ts +16 -17
  224. package/dist/operations/operations.d.ts +40 -4
  225. package/dist/operations/operations.js +1 -0
  226. package/dist/operations/operations.ts +61 -4
  227. package/dist/operations/result.d.ts +2 -2
  228. package/dist/operations/result.ts +2 -2
  229. package/package.json +1 -4
  230. package/dist/core/answers.d.ts +0 -171
  231. package/dist/core/answers.js +0 -789
  232. package/dist/core/answers.ts +0 -999
  233. package/dist/core/client.d.ts +0 -63
  234. package/dist/core/client.js +0 -249
  235. package/dist/core/client.ts +0 -289
  236. package/dist/core/files.js +0 -171
  237. package/dist/core/files.ts +0 -222
  238. package/dist/integrations/module.d.ts +0 -6
  239. package/dist/integrations/module.js +0 -153
  240. package/dist/integrations/module.ts +0 -191
  241. package/dist/integrity/slopnators/sl_unkept_rows.js +0 -104
  242. package/dist/integrity/slopnators/sl_unkept_rows.ts +0 -111
  243. package/dist/integrity/slopnators/sl_unseamed_reach.js +0 -73
  244. package/dist/integrity/slopnators/sl_unseamed_reach.ts +0 -79
  245. package/dist/operations/cost.d.ts +0 -14
  246. package/dist/operations/cost.js +0 -37
  247. package/dist/operations/cost.ts +0 -58
  248. package/dist/operations/errors/errors.d.ts +0 -52
  249. package/dist/operations/http.js +0 -124
  250. package/dist/operations/http.ts +0 -169
  251. package/dist/operations/memory.d.ts +0 -104
  252. package/dist/operations/memory.js +0 -365
  253. package/dist/operations/memory.ts +0 -470
  254. package/dist/operations/metering.d.ts +0 -55
  255. package/dist/operations/metering.js +0 -247
  256. package/dist/operations/metering.ts +0 -328
  257. package/dist/operations/queue-policy.d.ts +0 -3
  258. package/dist/operations/queue-policy.js +0 -8
  259. package/dist/operations/queue-policy.ts +0 -10
  260. package/dist/operations/seams.d.ts +0 -239
  261. package/dist/operations/seams.js +0 -45
  262. package/dist/operations/seams.ts +0 -364
  263. package/dist/operations/store.d.ts +0 -14
  264. package/dist/operations/store.js +0 -1
  265. package/dist/operations/store.ts +0 -29
  266. /package/dist/{operations → core}/errors/classes.d.ts +0 -0
  267. /package/dist/{operations → core}/errors/classes.js +0 -0
  268. /package/dist/{operations → core}/errors/classes.ts +0 -0
  269. /package/dist/integrity/slopnators/{sl_unkept_rows.d.ts → sl_door_in_a_node.d.ts} +0 -0
  270. /package/dist/integrity/slopnators/{sl_unseamed_reach.d.ts → sl_outside_an_operation.d.ts} +0 -0
@@ -0,0 +1,543 @@
1
+ import { spawn, spawnSync, type ChildProcessWithoutNullStreams, type SpawnSyncReturns } from 'node:child_process'
2
+ import { randomBytes } from 'node:crypto'
3
+ import { tmpdir, uptime } from 'node:os'
4
+ import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
5
+ import { allowedNodeEnvironmentFlags, env, execPath, kill, pid, stderr, stdout } from 'node:process'
6
+ import { fileURLToPath } from 'node:url'
7
+
8
+ import { CHANNEL, FOLDER, across, attached, createFileSync, existsSync, here, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from '../integrations/disk.ts'
9
+ import { VAULT_DIR } from '../integrations/vault.ts'
10
+
11
+ import { CALL_PREFIX, LOCKFILES, PACKAGER, REACH, managerOf, programOf, unlisted, type Answer, type Call, type How, type Order, type Ran, type Recorded, type Running, type Target } from '../integrations/target.ts'
12
+ import { CLI } from '../core/invoked.ts'
13
+ import { valuesIn } from '../core/rows.ts'
14
+ import { THING } from '../core/scope.ts'
15
+ import { findTree, inTree } from '../core/tree.ts'
16
+
17
+ export interface Starting {
18
+
19
+ readonly at?: string
20
+
21
+ readonly shows?: boolean | 'errors' | 'tee'
22
+
23
+ readonly hears?: string
24
+
25
+ readonly within?: number
26
+
27
+ readonly holds?: NodeJS.ProcessEnv
28
+ }
29
+
30
+ const answered = (went: SpawnSyncReturns<string>): Ran => ({
31
+ there: went.error === undefined,
32
+ ok: went.status === 0,
33
+ code: went.status ?? 1,
34
+ out: String(went.stdout ?? ''),
35
+ err: String(went.stderr ?? ''),
36
+ killed: went.signal ?? null,
37
+ why: went.error === undefined ? '' : went.error.message,
38
+ })
39
+
40
+ const standing = (at: string | undefined): string | undefined => (at === undefined || across(at) ? undefined : at)
41
+
42
+ export function starts(program: string, words: readonly string[], how: Starting = {}): Ran {
43
+ let went: SpawnSyncReturns<string>
44
+ try {
45
+ went = spawnSync(program, [...words], {
46
+ cwd: standing(how.at),
47
+ encoding: 'utf8',
48
+ shell: false,
49
+ stdio:
50
+ how.shows === true
51
+ ? [how.hears === undefined ? 'inherit' : 'pipe', 'inherit', 'inherit']
52
+ : how.shows === 'errors'
53
+ ? [how.hears === undefined ? 'ignore' : 'pipe', 'pipe', 'inherit']
54
+ : 'pipe',
55
+ input: how.hears,
56
+ env: how.holds,
57
+ ...(how.within === undefined ? {} : { timeout: how.within, killSignal: 'SIGKILL' as const }),
58
+ })
59
+ } catch (thrown) {
60
+ return nowhere(thrown instanceof Error ? thrown.message : String(thrown))
61
+ }
62
+ const ran = answered(went)
63
+ if (how.shows === 'tee') {
64
+ stdout.write(ran.out)
65
+ stderr.write(ran.err)
66
+ }
67
+ return ran
68
+ }
69
+
70
+ export function started(program: string, words: readonly string[], how: Starting = {}): Promise<Ran> {
71
+ return new Promise((keep) => {
72
+ let child: ChildProcessWithoutNullStreams
73
+ try {
74
+ child = spawn(program, [...words], { cwd: standing(how.at), shell: false, env: how.holds })
75
+ } catch (thrown) {
76
+ keep(nowhere(thrown instanceof Error ? thrown.message : String(thrown)))
77
+ return
78
+ }
79
+ let out = ''
80
+ let err = ''
81
+ let why = ''
82
+ const clock = how.within === undefined ? undefined : setTimeout(() => child.kill('SIGKILL'), how.within)
83
+ child.stdout.on('data', (chunk: Buffer) => (out += chunk.toString()))
84
+ child.stderr.on('data', (chunk: Buffer) => (err += chunk.toString()))
85
+ child.on('error', (error) => (why = error.message))
86
+ child.on('close', (code, killed) => {
87
+ if (clock !== undefined) clearTimeout(clock)
88
+ keep({ there: why === '', ok: why === '' && code === 0, code: code ?? 1, out, err, killed, why })
89
+ })
90
+ child.stdin.end(how.hears)
91
+ })
92
+ }
93
+
94
+ export function handed(
95
+ at: string,
96
+ program: string,
97
+ passed: readonly string[],
98
+ shows: boolean | 'errors' | 'tee' = true,
99
+ within?: number,
100
+ hears?: string,
101
+ ): Ran {
102
+ return starts(program, passed, { at, shows, ...(within === undefined ? {} : { within }), ...(hears === undefined ? {} : { hears }) })
103
+ }
104
+
105
+ const HOLDING = 'THREAD_RUNNING'
106
+
107
+ const CLAIMED = 'running.'
108
+
109
+ const CLAIM = '.json'
110
+
111
+ const STARTING = 10_000
112
+
113
+ export interface Holding {
114
+ readonly pid: number
115
+ readonly runs: string
116
+ readonly since: number
117
+ }
118
+
119
+ export interface Held {
120
+ readonly by?: Holding
121
+ }
122
+
123
+ let claimed = ''
124
+
125
+ const claimAt = (dir: string, one: number): string => join(dir, CLAIMED + String(one) + CLAIM)
126
+
127
+ const generations = (dir: string): readonly number[] =>
128
+ existsSync(dir)
129
+ ? readdirSync(dir)
130
+ .filter((one) => one.startsWith(CLAIMED) && one.endsWith(CLAIM))
131
+ .map((one) => Number(one.slice(CLAIMED.length, one.length - CLAIM.length)))
132
+ .filter((one) => Number.isInteger(one) && one >= 0)
133
+ : []
134
+
135
+ const living = (one: number): boolean => {
136
+ try {
137
+ kill(one, 0)
138
+ return true
139
+ } catch (thrown) {
140
+ return (thrown as NodeJS.ErrnoException).code === 'EPERM'
141
+ }
142
+ }
143
+
144
+ function holding(at: string): Held {
145
+ try {
146
+ const by = JSON.parse(readFileSync(at, 'utf8')) as Holding
147
+ return by.since > Date.now() - uptime() * 1000 && living(by.pid) ? { by } : {}
148
+ } catch {
149
+ try {
150
+ return Date.now() - statSync(at).mtimeMs < STARTING ? { by: { pid: 0, runs: '', since: Date.now() } } : {}
151
+ } catch {
152
+ return {}
153
+ }
154
+ }
155
+ }
156
+
157
+ export function releases(): void {
158
+ const at = claimed === '' ? env[HOLDING] : claimed
159
+ if (at !== undefined) rmSync(at, { force: true })
160
+ claimed = ''
161
+ Reflect.deleteProperty(env, HOLDING)
162
+ }
163
+
164
+ export function claims(root: string, runs: string): Held {
165
+ if (attached()) return {}
166
+ const dir = join(root, VAULT_DIR)
167
+ for (let tries = 0; tries < 5; tries += 1) {
168
+ const taken = generations(dir)
169
+ const top = Math.max(-1, ...taken)
170
+ const held: Held = top < 0 ? {} : holding(claimAt(dir, top))
171
+ if (held.by !== undefined) return env[HOLDING] === claimAt(dir, top) ? {} : held
172
+ const mine = claimAt(dir, top + 1)
173
+ if (!createFileSync(mine, JSON.stringify({ pid, runs, since: Date.now() }))) continue
174
+ claimed = mine
175
+ env[HOLDING] = mine
176
+ for (const one of taken) rmSync(claimAt(dir, one), { force: true })
177
+ process.on('exit', releases)
178
+ return {}
179
+ }
180
+ return { by: { pid: 0, runs: '', since: Date.now() } }
181
+ }
182
+
183
+ export function programFor(name: string, at: string): string {
184
+ const found = programOf(name, at, existsSync)
185
+ if (found === undefined) throw new Error(unlisted(name))
186
+ return found
187
+ }
188
+
189
+ export const SIGNING = 'THREAD_RECEIPTS_KEY'
190
+
191
+ const SIGNED_AT = 'thread-receipts.key'
192
+
193
+ let signed = ''
194
+
195
+ export function signing(): string {
196
+ if (signed !== '') return signed
197
+ const given = env[SIGNING]
198
+ if (given !== undefined && given !== '') {
199
+ signed = given
200
+ return signed
201
+ }
202
+ if (attached()) {
203
+ throw new Error(
204
+ 'this answers for a folder on another machine and was handed no key to seal receipts with, so it issues none. ' +
205
+ 'The server that started it hands one over in ' + SIGNING + '.',
206
+ )
207
+ }
208
+ const at = join(tmpdir(), SIGNED_AT)
209
+ if (!existsSync(at)) createFileSync(at, randomBytes(32).toString('hex'))
210
+ signed = readFileSync(at, 'utf8').trim()
211
+ return signed
212
+ }
213
+
214
+ const OWN = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..')
215
+
216
+ const GUARDS = ['--permission', '--experimental-permission']
217
+
218
+ const HUSHES = '--disable-warning'
219
+
220
+ const swapped = (path: string): string =>
221
+ [...path].map((one) => (one === one.toUpperCase() ? one.toLowerCase() : one.toUpperCase())).join('')
222
+
223
+ let unguarded = false
224
+
225
+ function guarded(): string {
226
+ const before = env.NODE_OPTIONS ?? ''
227
+ const on = GUARDS.find((one) => allowedNodeEnvironmentFlags.has(one))
228
+ if (on === undefined) {
229
+ if (!unguarded) {
230
+ stderr.write(
231
+ 'thread: this node has no permission model, so a thread command started for a folder runs unguarded: nothing ' +
232
+ 'here stops it reading that folder straight off this disk, and on one machine that would go unnoticed.\n',
233
+ )
234
+ unguarded = true
235
+ }
236
+ return before
237
+ }
238
+ const kept = [...new Set([tmpdir(), realpathSync(tmpdir())])]
239
+ const allowed = (flag: string, paths: readonly string[]): readonly string[] => paths.map((one) => flag + '="' + one + '"')
240
+ const quiet = allowedNodeEnvironmentFlags.has(HUSHES) ? [HUSHES + '=SecurityWarning'] : []
241
+ return [before, on, ...allowed('--allow-fs-read', [OWN, swapped(OWN), ...kept]), ...allowed('--allow-fs-write', kept), '--allow-child-process', '--allow-worker', ...quiet]
242
+ .filter((one) => one !== '')
243
+ .join(' ')
244
+ }
245
+
246
+ const SERVED = 'thread-serve'
247
+
248
+ const TICK = 2
249
+
250
+ const MARGIN = 5_000
251
+
252
+ const SAYING = '-saying'
253
+
254
+ const ORDER = '-order'
255
+
256
+ const ANSWER = '-answer'
257
+
258
+ interface Serving {
259
+ readonly of: string
260
+ readonly dir: string
261
+ readonly root: string
262
+ out: string
263
+ err: string
264
+ why: string
265
+ pending: string
266
+ waiting: ((held: Ran) => void)[]
267
+ parked: Ran[]
268
+ replaying: Recorded[]
269
+ handed: number
270
+ last?: Ran
271
+ stop?: () => void
272
+ watch?: ReturnType<typeof setInterval>
273
+ }
274
+
275
+ const serving = new Map<string, Serving>()
276
+
277
+ const still = (one: Serving, asks: readonly Order[]): Ran => ({ there: true, ok: false, code: -1, out: '', err: '', killed: null, why: '', of: one.of, asks })
278
+
279
+ const nowhere = (why: string): Ran => ({ there: false, ok: false, code: 1, out: '', err: '', killed: null, why })
280
+
281
+ const lost = (of: string): Ran => ({ there: true, ok: false, code: -1, out: '', err: '', killed: null, why: '', of, lost: of })
282
+
283
+ const told = (one: Order): string => JSON.stringify([one.order, one.at, one.said ?? null, one.mode ?? null, one.words ?? null])
284
+
285
+ const plainly = (one: Order): string => one.order + ' ' + (one.at === '' ? '.' : one.at)
286
+
287
+ const outside = (root: string, at: string): boolean => {
288
+ const step = relative(root, at)
289
+ return step.startsWith('..') ? true : isAbsolute(step)
290
+ }
291
+
292
+ function hand(one: Serving, held: Ran): void {
293
+ const settle = one.waiting.shift()
294
+ if (settle === undefined) one.parked.push(held)
295
+ else settle(held)
296
+ }
297
+
298
+ function reply(dir: string, name: string, held: Answer): void {
299
+ writeFileSync(join(dir, name + SAYING), JSON.stringify(held), 'utf8')
300
+ renameSync(join(dir, name + SAYING), join(dir, name + ANSWER))
301
+ }
302
+
303
+ function watching(one: Serving): void {
304
+ one.watch = setInterval(() => {
305
+ let names: readonly string[] = []
306
+ try {
307
+ names = readdirSync(one.dir)
308
+ } catch {
309
+ return
310
+ }
311
+ for (const name of names) {
312
+ if (!name.endsWith(ORDER)) continue
313
+ const file = join(one.dir, name)
314
+ let said = ''
315
+ try {
316
+ said = readFileSync(file, 'utf8')
317
+ } catch {
318
+ continue
319
+ }
320
+ rmSync(file, { force: true })
321
+ const order = JSON.parse(said) as Order
322
+ const short = name.slice(0, name.length - ORDER.length)
323
+ if (outside(one.root, order.at)) {
324
+ reply(one.dir, short, {
325
+ answered: 'refused',
326
+ because: order.at + ' is outside the folder this command was pointed at, and nothing outside it is ever asked of the machine answering.',
327
+ })
328
+ continue
329
+ }
330
+ const asked: Order = { ...order, at: relative(one.root, order.at) }
331
+ const due = one.replaying.shift()
332
+ if (due !== undefined) {
333
+ if (told(due.order) === told(asked)) {
334
+ reply(one.dir, short, due.answered)
335
+ continue
336
+ }
337
+ one.replaying = []
338
+ one.err +=
339
+ 'thread: this command was carried on here by running it again and handing it the answers it was given before, ' +
340
+ 'and this time it asked for ' + plainly(asked) + ' where before it asked for ' + plainly(due.order) + '. A command ' +
341
+ 'that does not ask the same things twice cannot be carried on from its answers, so it was stopped. Run it again.\n'
342
+ one.stop?.()
343
+ continue
344
+ }
345
+ one.pending = short
346
+ hand(one, still(one, [asked]))
347
+ }
348
+ }, TICK)
349
+ }
350
+
351
+ function next(one: Serving, within?: number): Promise<Ran> {
352
+ const held = one.parked.shift()
353
+ if (held !== undefined) return Promise.resolve(held)
354
+ return new Promise((settle) => {
355
+ const done = { is: false }
356
+ const mine = (ran: Ran): void => {
357
+ if (done.is) return
358
+ done.is = true
359
+ settle(ran)
360
+ }
361
+ one.waiting.push(mine)
362
+ if (within === undefined) return
363
+ setTimeout(() => {
364
+ if (done.is) return
365
+ done.is = true
366
+ one.waiting = one.waiting.filter((each) => each !== mine)
367
+ settle(still(one, []))
368
+ }, Math.max(1_000, within - MARGIN))
369
+ })
370
+ }
371
+
372
+ function begun(word: string, args: readonly string[], root: string, hears?: string, called?: string, recording: readonly Recorded[] = []): Serving {
373
+ const of = called ?? randomBytes(6).toString('hex')
374
+ const dir = join(tmpdir(), SERVED, randomBytes(6).toString('hex'))
375
+ writeFileSync(join(dir, 'channel'), '', 'utf8')
376
+ const one: Serving = { of, dir, root, out: '', err: '', why: '', pending: '', waiting: [], parked: [], replaying: [...recording], handed: recording.length }
377
+ const child = spawn(execPath, [...CLI, word, ...args], {
378
+ cwd: OWN,
379
+ shell: false,
380
+ env: { ...env, [CHANNEL]: dir, [FOLDER]: root, [SIGNING]: signing(), NODE_OPTIONS: guarded() },
381
+ })
382
+ one.stop = () => child.kill()
383
+ child.stdout.on('data', (chunk: Buffer) => (one.out += chunk.toString()))
384
+ child.stderr.on('data', (chunk: Buffer) => (one.err += chunk.toString()))
385
+ child.on('error', (thrown) => (one.why = thrown.message))
386
+ child.on('close', (code, killed) => {
387
+ if (one.watch !== undefined) clearInterval(one.watch)
388
+ serving.delete(of)
389
+ rmSync(dir, { recursive: true, force: true })
390
+ hand(one, { there: one.why === '', ok: one.why === '' && code === 0, code: code ?? 1, out: one.out, err: one.err, killed, why: one.why })
391
+ })
392
+ child.stdin.end(hears ?? '')
393
+ watching(one)
394
+ serving.set(of, one)
395
+ return one
396
+ }
397
+
398
+ const UNPLACED = ' against, so it did not start. A command on this server runs against the folder the person gave and nowhere else.'
399
+
400
+ function counted(one: Serving, held: Ran): Ran {
401
+ const asks = held.asks ?? []
402
+ if (asks.length === 0) return held
403
+ one.handed += asks.length
404
+ one.last = held
405
+ return held
406
+ }
407
+
408
+ async function served(word: string, args: readonly string[], within?: number, how?: How): Promise<Ran> {
409
+ const of = how?.of
410
+ if (of !== undefined) {
411
+ const one = serving.get(of)
412
+ if (one === undefined) {
413
+ const recording = how?.recording
414
+ if (recording === undefined) return lost(of)
415
+ const root = how?.at ?? ''
416
+ if (root === '') return nowhere('nothing said which folder to run thread ' + word + UNPLACED)
417
+ const again = begun(word, args, root, how?.hears, of, recording)
418
+ return counted(again, await next(again, within))
419
+ }
420
+ const answering = how?.answering
421
+ if (answering !== undefined && answering < one.handed && one.last !== undefined) return one.last
422
+ const given = (how?.answered ?? [])[0]
423
+ if (one.pending !== '' && given !== undefined) {
424
+ reply(one.dir, one.pending, given)
425
+ one.pending = ''
426
+ }
427
+ return counted(one, await next(one, within))
428
+ }
429
+ const root = how?.at ?? ''
430
+ if (root === '') return nowhere('nothing said which folder to run thread ' + word + UNPLACED)
431
+ const fresh = begun(word, args, root, how?.hears)
432
+ return counted(fresh, await next(fresh, within))
433
+ }
434
+
435
+ export function running(name: string, subcommands: Readonly<Record<string, string>>, at: string): Running {
436
+ const out: Record<
437
+ string,
438
+ (args: readonly string[], shows?: Starting['shows'], within?: number, how?: How) => Promise<Ran>
439
+ > = {}
440
+ for (const [key, word] of Object.entries(subcommands)) {
441
+ out[key] =
442
+ name === THING
443
+ ? async (args, shows, within, how) => served(word, args, within, how)
444
+ : async (args, shows, within, how) => {
445
+ const where = how?.at ?? at
446
+ const program = programFor(name, where)
447
+ if ((shows ?? false) !== false) return handed(where, program, [word, ...args], shows ?? false, within, how?.hears)
448
+ return await started(program, [word, ...args], { at: where, ...(within === undefined ? {} : { within }), ...(how?.hears === undefined ? {} : { hears: how.hears }) })
449
+ }
450
+ }
451
+ return out
452
+ }
453
+
454
+ interface Sheet {
455
+ readonly TARGETS?: readonly Target[]
456
+ readonly CALLS?: readonly Call[]
457
+ }
458
+
459
+ function sheet(at: string): Sheet {
460
+ const path = inTree(findTree(at), 'targets.ts')
461
+ return existsSync(path) ? (valuesIn(readFileSync(path, 'utf8')) as Sheet) : {}
462
+ }
463
+
464
+ export interface Expansion {
465
+ readonly program: string
466
+ readonly target: Target
467
+ readonly calls: readonly Call[]
468
+ }
469
+
470
+ export function expansions(at: string): readonly Expansion[] {
471
+ const held = sheet(at)
472
+ return (held.TARGETS ?? [])
473
+ .filter((one) => one.id.startsWith(REACH.cli))
474
+ .map((one) => ({ program: one.id.slice(REACH.cli.length), target: one, calls: (held.CALLS ?? []).filter((each) => each.of === one.id) }))
475
+ }
476
+
477
+ export function calls(target: string, at: string): Running {
478
+ const named: Record<string, string> = {}
479
+ for (const one of sheet(at).CALLS ?? []) if (one.of === target) named[one.id.replace(CALL_PREFIX, '')] = one.path
480
+ return running(target.replace(REACH.cli, ''), named, at)
481
+ }
482
+
483
+ export { LOCKFILES }
484
+
485
+ export const manager = (into: string): string => managerOf(into, existsSync)
486
+
487
+ const ADDS: Readonly<Record<string, string>> = { pnpm: 'add', yarn: 'add', bun: 'add', npm: 'install' }
488
+
489
+ export function adding(into: string): readonly [string, string] {
490
+ const tool = manager(into)
491
+ const verb = ADDS[tool]
492
+ if (verb === undefined) {
493
+ throw new Error(
494
+ tool + ' is what the lockfile in ' + into + ' picks, and nothing here knows the word it adds a package with.',
495
+ )
496
+ }
497
+ return [tool, verb]
498
+ }
499
+
500
+ export function needing(into: string, needs: Readonly<Record<string, string>>): readonly string[] {
501
+ const manifest = join(into, 'package.json')
502
+ const was = readFileSync(manifest, 'utf8')
503
+ const parsed = JSON.parse(was) as Record<string, unknown> & {
504
+ readonly dependencies?: Readonly<Record<string, string>>
505
+ readonly devDependencies?: Readonly<Record<string, string>>
506
+ }
507
+ const named = { ...parsed.devDependencies, ...parsed.dependencies }
508
+ const added = Object.keys(needs).filter((one) => named[one] === undefined).sort()
509
+ if (added.length === 0) return []
510
+ const dependencies: Record<string, string> = { ...parsed.dependencies }
511
+ for (const one of added) dependencies[one] = needs[one] ?? 'latest'
512
+ writeFileSync(manifest, `${JSON.stringify({ ...parsed, dependencies }, null, 2)}\n`, 'utf8')
513
+ try {
514
+ installing(into)
515
+ } catch (thrown) {
516
+ writeFileSync(manifest, was, 'utf8')
517
+ throw new Error(
518
+ `${added.join(', ')} could not be fetched — ${thrown instanceof Error ? thrown.message : String(thrown)} ` +
519
+ 'package.json is as it was, so nothing here names a package that is not on disk.',
520
+ )
521
+ }
522
+ return added
523
+ }
524
+
525
+ export function installing(at: string): string {
526
+ if (attached()) return here().fetch(at)
527
+ const tool = manager(at)
528
+ const ran = starts(programFor(PACKAGER, at), ['install'], { at })
529
+ const said = (ran.out + ran.err).trim()
530
+ if (!ran.there) {
531
+ throw new Error(
532
+ tool + ' is what the lockfile in ' + at + ' picks, and it is not on this machine, so nothing here can fetch ' +
533
+ 'what that tree depends on until that changes.',
534
+ )
535
+ }
536
+ if (!ran.ok) {
537
+ throw new Error(
538
+ tool + ' could not install what ' + at + ' depends on. It said: ' +
539
+ (said === '' ? 'nothing, and exited ' + String(ran.code) : said),
540
+ )
541
+ }
542
+ return said
543
+ }
@@ -1,29 +1,21 @@
1
1
  import * as disk from 'node:fs';
2
- export type Kind = 'file' | 'folder' | 'other' | 'none';
3
- export interface Listed {
4
- readonly name: string;
5
- readonly kind: Exclude<Kind, 'none'>;
6
- }
7
- export interface Measured {
8
- readonly size: number;
9
- readonly modified: number;
10
- }
11
- export interface Answering {
12
- readonly kind: (at: string) => Kind;
13
- readonly read: (at: string) => string;
14
- readonly write: (at: string, said: string) => void;
15
- readonly list: (at: string) => readonly Listed[];
16
- readonly stat: (at: string) => Measured;
17
- readonly remove: (at: string) => void;
18
- readonly real: (at: string) => string;
19
- readonly fetch: (at: string) => string;
20
- }
21
- export declare function answeredBy(one: Answering | undefined): void;
2
+ import { type Attached, type Kind, type Measured } from '../integrations/target.ts';
3
+ export type { Answering, Kind, Listed, Measured, Ran } from '../integrations/target.ts';
4
+ export declare const CHANNEL = "THREAD_CHANNEL";
5
+ export declare const FOLDER = "THREAD_FOLDER";
6
+ export declare function answeredBy(one?: Attached): void;
7
+ export declare function across(at: string): boolean;
8
+ export declare function beyond(at: string): boolean;
22
9
  export declare function attached(): boolean;
10
+ export declare function here(): Attached;
23
11
  export declare function kind(at: string): Kind;
24
12
  export declare function existsSync(at: disk.PathLike): boolean;
25
13
  export declare function readFileSync(at: disk.PathOrFileDescriptor, how?: BufferEncoding): string;
26
- export declare function writeFileSync(at: disk.PathOrFileDescriptor, said: string, how?: BufferEncoding): void;
14
+ export declare function writeFileSync(at: disk.PathOrFileDescriptor, said: string, how?: BufferEncoding | {
15
+ readonly encoding?: BufferEncoding;
16
+ readonly mode?: number;
17
+ }): void;
18
+ export declare function createFileSync(at: string, said: string): boolean;
27
19
  export interface Named {
28
20
  readonly name: string;
29
21
  readonly isFile: () => boolean;
@@ -41,9 +33,9 @@ export declare function statSync(at: disk.PathLike, how: {
41
33
  readonly throwIfNoEntry: false;
42
34
  }): Held | undefined;
43
35
  export declare function rmSync(at: disk.PathLike, how?: disk.RmOptions): void;
36
+ export declare function rmdirSync(at: disk.PathLike): void;
44
37
  export declare function renameSync(from: disk.PathLike, to: disk.PathLike): void;
45
38
  export declare function appendFileSync(at: disk.PathOrFileDescriptor, said: string): void;
46
39
  export declare function realpathSync(at: disk.PathLike): string;
47
- export declare function manager(into: string): readonly string[];
48
- export declare function installing(at: string): string;
49
- export declare function fetching(at: string): string;
40
+ export declare const HERE: Attached;
41
+ export declare function standing(root: string, given: ReadonlyMap<string, string>): Attached;