@prestack/thread 0.1.2 → 0.1.4

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 (265) hide show
  1. package/CHANGELOG.md +193 -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 +67 -58
  7. package/dist/core/attach.ts +61 -68
  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 +89 -21
  14. package/dist/core/descend.ts +86 -17
  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 +0 -2
  27. package/dist/core/init.js +51 -15
  28. package/dist/core/init.ts +58 -15
  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/thread.d.ts +3 -3
  38. package/dist/core/tree.d.ts +4 -1
  39. package/dist/core/tree.js +26 -6
  40. package/dist/core/tree.ts +29 -6
  41. package/dist/core/types.d.ts +3 -3
  42. package/dist/core/types.js +3 -1
  43. package/dist/core/types.ts +2 -1
  44. package/dist/derivation/attach.js +1 -1
  45. package/dist/derivation/generators/build.js +6 -6
  46. package/dist/derivation/generators/build.ts +1 -1
  47. package/dist/derivation/generators/check.js +1 -1
  48. package/dist/derivation/generators/determinism.js +1 -1
  49. package/dist/derivation/generators/release.js +60 -47
  50. package/dist/derivation/generators/release.ts +66 -43
  51. package/dist/derivation/release.d.ts +7 -3
  52. package/dist/derivation/release.js +14 -1
  53. package/dist/derivation/release.ts +22 -2
  54. package/dist/integrations/attach.d.ts +4 -0
  55. package/dist/integrations/attach.js +33 -54
  56. package/dist/integrations/attach.ts +50 -57
  57. package/dist/integrations/cli.d.ts +37 -0
  58. package/dist/integrations/cli.js +440 -0
  59. package/dist/integrations/cli.ts +543 -0
  60. package/dist/{core/files.d.ts → integrations/disk.d.ts} +16 -20
  61. package/dist/integrations/disk.js +435 -0
  62. package/dist/integrations/disk.ts +465 -0
  63. package/dist/integrations/generators/read.js +6 -6
  64. package/dist/integrations/generators/read.ts +4 -4
  65. package/dist/integrations/generators/vault.js +10 -11
  66. package/dist/integrations/generators/vault.ts +4 -5
  67. package/dist/{operations/http.d.ts → integrations/https.d.ts} +10 -6
  68. package/dist/integrations/https.js +170 -0
  69. package/dist/integrations/https.ts +216 -0
  70. package/dist/integrations/pglite.d.ts +26 -0
  71. package/dist/integrations/pglite.js +143 -0
  72. package/dist/integrations/pglite.ts +206 -0
  73. package/dist/integrations/secret.d.ts +8 -2
  74. package/dist/integrations/secret.js +3 -5
  75. package/dist/integrations/secret.ts +10 -6
  76. package/dist/integrations/target.d.ts +155 -14
  77. package/dist/integrations/target.js +171 -50
  78. package/dist/integrations/target.ts +377 -61
  79. package/dist/integrations/vault.d.ts +1 -0
  80. package/dist/integrations/vault.js +20 -5
  81. package/dist/integrations/vault.ts +17 -5
  82. package/dist/integrity/attach.js +3 -73
  83. package/dist/integrity/attach.ts +1 -71
  84. package/dist/integrity/check-threads.js +5 -5
  85. package/dist/integrity/check-threads.ts +2 -2
  86. package/dist/integrity/constraint.d.ts +0 -1
  87. package/dist/integrity/constraint.js +267 -366
  88. package/dist/integrity/constraint.ts +267 -375
  89. package/dist/integrity/generators/compiler.js +14 -17
  90. package/dist/integrity/generators/compiler.ts +10 -15
  91. package/dist/integrity/generators/gate.js +15 -15
  92. package/dist/integrity/generators/gate.ts +10 -10
  93. package/dist/integrity/generators/issues.js +7 -7
  94. package/dist/integrity/generators/publish.js +39 -57
  95. package/dist/integrity/generators/publish.ts +44 -55
  96. package/dist/integrity/generators/trial.d.ts +3 -3
  97. package/dist/integrity/generators/trial.js +133 -161
  98. package/dist/integrity/generators/trial.ts +145 -165
  99. package/dist/integrity/lint.js +9 -13
  100. package/dist/integrity/lint.ts +7 -10
  101. package/dist/integrity/lsp/bin.js +5 -4
  102. package/dist/integrity/lsp/bin.ts +3 -1
  103. package/dist/integrity/lsp/diagnostics.js +3 -3
  104. package/dist/integrity/lsp/diagnostics.ts +1 -1
  105. package/dist/integrity/lsp/lint.js +19 -30
  106. package/dist/integrity/lsp/lint.ts +16 -32
  107. package/dist/integrity/lsp/server.js +3 -3
  108. package/dist/integrity/slopnator.d.ts +3 -13
  109. package/dist/integrity/slopnator.js +58 -37
  110. package/dist/integrity/slopnator.ts +53 -37
  111. package/dist/integrity/slopnators/sl_broken_lineage.js +2 -2
  112. package/dist/integrity/slopnators/sl_declared_elsewhere.js +1 -1
  113. package/dist/integrity/slopnators/sl_derived_drift.js +4 -4
  114. package/dist/integrity/slopnators/sl_derived_drift.ts +1 -1
  115. package/dist/integrity/slopnators/sl_door_in_a_node.js +220 -0
  116. package/dist/integrity/slopnators/sl_door_in_a_node.ts +250 -0
  117. package/dist/integrity/slopnators/sl_inert_island.js +10 -8
  118. package/dist/integrity/slopnators/sl_inert_island.ts +8 -6
  119. package/dist/integrity/slopnators/sl_missing_row.js +6 -51
  120. package/dist/integrity/slopnators/sl_missing_row.ts +4 -50
  121. package/dist/integrity/slopnators/sl_no_barrel.js +1 -1
  122. package/dist/integrity/slopnators/sl_no_child_process.js +36 -18
  123. package/dist/integrity/slopnators/sl_no_child_process.ts +44 -18
  124. package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +80 -149
  125. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +95 -147
  126. package/dist/integrity/slopnators/sl_no_import_cycle.js +15 -4
  127. package/dist/integrity/slopnators/sl_no_import_cycle.ts +20 -4
  128. package/dist/integrity/slopnators/sl_no_such_command.js +2 -2
  129. package/dist/integrity/slopnators/sl_no_such_command.ts +1 -1
  130. package/dist/integrity/slopnators/sl_outside_an_operation.js +20 -0
  131. package/dist/integrity/slopnators/sl_outside_an_operation.ts +25 -0
  132. package/dist/integrity/slopnators/sl_screen_holds_only_declared.d.ts +2 -0
  133. package/dist/integrity/slopnators/sl_screen_holds_only_declared.js +61 -0
  134. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +70 -0
  135. package/dist/integrity/slopnators/sl_secret_literal.js +1 -33
  136. package/dist/integrity/slopnators/sl_secret_literal.ts +1 -33
  137. package/dist/integrity/slopnators/sl_stale_path.js +5 -22
  138. package/dist/integrity/slopnators/sl_stale_path.ts +4 -20
  139. package/dist/integrity/slopnators/sl_twice_meant.js +1 -1
  140. package/dist/integrity/slopnators/sl_twice_parented.js +29 -5
  141. package/dist/integrity/slopnators/sl_twice_parented.ts +28 -4
  142. package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +4 -1
  143. package/dist/integrity/slopnators/sl_undeclared_crossing.js +426 -62
  144. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +454 -70
  145. package/dist/integrity/slopnators/sl_undeclared_destination.js +59 -25
  146. package/dist/integrity/slopnators/sl_undeclared_destination.ts +69 -21
  147. package/dist/integrity/slopnators/sl_undeclared_list.d.ts +2 -0
  148. package/dist/integrity/slopnators/sl_undeclared_list.js +363 -0
  149. package/dist/integrity/slopnators/sl_undeclared_list.ts +358 -0
  150. package/dist/integrity/slopnators/sl_undeclared_node.js +1 -11
  151. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -10
  152. package/dist/integrity/slopnators/sl_undeclared_operation.js +111 -29
  153. package/dist/integrity/slopnators/sl_undeclared_operation.ts +134 -26
  154. package/dist/integrity/slopnators/sl_undeclared_variant.js +2 -2
  155. package/dist/integrity/slopnators/sl_undeclared_webhook.d.ts +2 -0
  156. package/dist/integrity/slopnators/sl_undeclared_webhook.js +90 -0
  157. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +108 -0
  158. package/dist/integrity/slopnators/sl_underived_route.d.ts +2 -0
  159. package/dist/integrity/slopnators/sl_underived_route.js +61 -0
  160. package/dist/integrity/slopnators/sl_underived_route.ts +72 -0
  161. package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -3
  162. package/dist/integrity/slopnators/sl_ungoverned_island.ts +1 -1
  163. package/dist/integrity/slopnators/sl_unlisted_program.d.ts +2 -0
  164. package/dist/integrity/slopnators/sl_unlisted_program.js +39 -0
  165. package/dist/integrity/slopnators/sl_unlisted_program.ts +40 -0
  166. package/dist/integrity/slopnators/sl_unnamed_island.js +1 -1
  167. package/dist/integrity/slopnators/sl_unpaired_member.js +3 -3
  168. package/dist/integrity/slopnators/sl_unpaired_member.ts +1 -1
  169. package/dist/integrity/slopnators/sl_unpaired_slopnator.js +4 -4
  170. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +1 -1
  171. package/dist/integrity/slopnators/sl_unprefixed_member.js +5 -31
  172. package/dist/integrity/slopnators/sl_unprefixed_member.ts +2 -28
  173. package/dist/integrity/slopnators/sl_unthreaded.js +11 -7
  174. package/dist/integrity/slopnators/sl_unthreaded.ts +6 -7
  175. package/dist/integrity/slopnators/sl_untranslated_key.js +2 -25
  176. package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -25
  177. package/dist/integrity/slopnators/sl_unvaulted_secret.d.ts +2 -0
  178. package/dist/integrity/slopnators/sl_unvaulted_secret.js +107 -0
  179. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +132 -0
  180. package/dist/integrity/slopnators/sl_unversioned.js +6 -4
  181. package/dist/integrity/slopnators/sl_unversioned.ts +5 -3
  182. package/dist/integrity/sweep.d.ts +1 -0
  183. package/dist/integrity/sweep.js +105 -42
  184. package/dist/integrity/sweep.ts +115 -42
  185. package/dist/knowledge/attach.js +1 -1
  186. package/dist/knowledge/generators/docsgen.js +8 -6
  187. package/dist/knowledge/generators/docsgen.ts +4 -2
  188. package/dist/knowledge/generators/glossary.js +4 -4
  189. package/dist/knowledge/generators/glossary.ts +1 -1
  190. package/dist/operations/access.d.ts +23 -0
  191. package/dist/operations/access.js +28 -1
  192. package/dist/operations/access.ts +43 -0
  193. package/dist/operations/attach.js +28 -109
  194. package/dist/operations/attach.ts +46 -113
  195. package/dist/operations/capabilities.d.ts +14 -5
  196. package/dist/operations/capabilities.js +13 -4
  197. package/dist/operations/capabilities.ts +27 -8
  198. package/dist/operations/dispatch.d.ts +8 -2
  199. package/dist/operations/dispatch.js +128 -185
  200. package/dist/operations/dispatch.ts +138 -191
  201. package/dist/operations/error-results.d.ts +1 -1
  202. package/dist/operations/error-results.js +2 -2
  203. package/dist/operations/error-results.ts +2 -2
  204. package/dist/operations/generators/capability.d.ts +11 -1
  205. package/dist/operations/generators/capability.js +481 -224
  206. package/dist/operations/generators/capability.ts +562 -233
  207. package/dist/operations/generators/compile.js +66 -39
  208. package/dist/operations/generators/compile.ts +78 -39
  209. package/dist/operations/generators/permits.js +11 -12
  210. package/dist/operations/generators/permits.ts +3 -4
  211. package/dist/operations/generators/seed.d.ts +1 -0
  212. package/dist/operations/generators/seed.js +19 -17
  213. package/dist/operations/generators/seed.ts +22 -14
  214. package/dist/operations/generators/trial.d.ts +6 -8
  215. package/dist/operations/generators/trial.js +200 -70
  216. package/dist/operations/generators/trial.ts +230 -93
  217. package/dist/operations/nodes.d.ts +15 -16
  218. package/dist/operations/nodes.ts +16 -17
  219. package/dist/operations/operations.d.ts +40 -4
  220. package/dist/operations/operations.js +1 -0
  221. package/dist/operations/operations.ts +61 -4
  222. package/dist/operations/result.d.ts +2 -2
  223. package/dist/operations/result.ts +2 -2
  224. package/package.json +1 -4
  225. package/dist/core/answers.d.ts +0 -171
  226. package/dist/core/answers.js +0 -789
  227. package/dist/core/answers.ts +0 -999
  228. package/dist/core/client.d.ts +0 -57
  229. package/dist/core/client.js +0 -247
  230. package/dist/core/client.ts +0 -286
  231. package/dist/core/files.js +0 -145
  232. package/dist/core/files.ts +0 -193
  233. package/dist/integrations/module.d.ts +0 -6
  234. package/dist/integrations/module.js +0 -153
  235. package/dist/integrations/module.ts +0 -191
  236. package/dist/integrity/slopnators/sl_unkept_rows.js +0 -104
  237. package/dist/integrity/slopnators/sl_unkept_rows.ts +0 -111
  238. package/dist/integrity/slopnators/sl_unseamed_reach.js +0 -73
  239. package/dist/integrity/slopnators/sl_unseamed_reach.ts +0 -79
  240. package/dist/operations/cost.d.ts +0 -14
  241. package/dist/operations/cost.js +0 -37
  242. package/dist/operations/cost.ts +0 -58
  243. package/dist/operations/errors/errors.d.ts +0 -52
  244. package/dist/operations/http.js +0 -124
  245. package/dist/operations/http.ts +0 -169
  246. package/dist/operations/memory.d.ts +0 -104
  247. package/dist/operations/memory.js +0 -365
  248. package/dist/operations/memory.ts +0 -470
  249. package/dist/operations/metering.d.ts +0 -55
  250. package/dist/operations/metering.js +0 -247
  251. package/dist/operations/metering.ts +0 -328
  252. package/dist/operations/queue-policy.d.ts +0 -3
  253. package/dist/operations/queue-policy.js +0 -8
  254. package/dist/operations/queue-policy.ts +0 -10
  255. package/dist/operations/seams.d.ts +0 -239
  256. package/dist/operations/seams.js +0 -45
  257. package/dist/operations/seams.ts +0 -364
  258. package/dist/operations/store.d.ts +0 -14
  259. package/dist/operations/store.js +0 -1
  260. package/dist/operations/store.ts +0 -29
  261. /package/dist/{operations → core}/errors/classes.d.ts +0 -0
  262. /package/dist/{operations → core}/errors/classes.js +0 -0
  263. /package/dist/{operations → core}/errors/classes.ts +0 -0
  264. /package/dist/integrity/slopnators/{sl_unkept_rows.d.ts → sl_door_in_a_node.d.ts} +0 -0
  265. /package/dist/integrity/slopnators/{sl_unseamed_reach.d.ts → sl_outside_an_operation.d.ts} +0 -0
@@ -10,12 +10,12 @@ import { relative, resolve } from 'node:path';
10
10
  import { argv, stderr, stdout } from 'node:process';
11
11
  import { pathToFileURL } from 'node:url';
12
12
  import { build } from 'esbuild';
13
- import { descend, reached } from "../../core/descend.js";
14
- import { existsSync, readFileSync } from "../../core/files.js";
15
- import { exit, invoked } from "../../core/invoked.js";
16
- import { valuesIn } from "../../core/rows.js";
17
- import { findTree } from "../../core/tree.js";
18
- import { servesBrowser } from "../../integrations/target.js";
13
+ import { descend, reached } from '../../core/descend.js';
14
+ import { existsSync, readFileSync } from '../../integrations/disk.js';
15
+ import { exit, invoked } from '../../core/invoked.js';
16
+ import { valuesIn } from '../../core/rows.js';
17
+ import { findTree } from '../../core/tree.js';
18
+ import { servesBrowser } from '../../integrations/target.js';
19
19
  const TREE = findTree();
20
20
  const ROOT = TREE.at;
21
21
  const OUTSIDE = /(^|\/)node_modules(\/|$)/;
@@ -5,7 +5,7 @@ import { pathToFileURL } from 'node:url'
5
5
  import { build } from 'esbuild'
6
6
 
7
7
  import { descend, reached, type Pass } from '../../core/descend.ts'
8
- import { existsSync, readFileSync } from '../../core/files.ts'
8
+ import { existsSync, readFileSync } from '../../integrations/disk.ts'
9
9
  import { exit, invoked } from '../../core/invoked.ts'
10
10
  import { valuesIn } from '../../core/rows.ts'
11
11
  import { findTree } from '../../core/tree.ts'
@@ -1,4 +1,4 @@
1
- import { bodyHash, parseGenerated } from "./determinism.js";
1
+ import { bodyHash, parseGenerated } from './determinism.js';
2
2
  export function checkGenerated(expected, read, options = {}) {
3
3
  const checks = [];
4
4
  const expectedPaths = new Set(expected.map((file) => file.path));
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { named } from "../../core/scope.js";
2
+ import { named } from '../../core/scope.js';
3
3
  export function sortedBy(items, key) {
4
4
  return [...items].sort((a, b) => compare(key(a), key(b)));
5
5
  }
@@ -1,14 +1,14 @@
1
1
  import { join } from 'node:path';
2
2
  import { argv, stderr, stdout } from 'node:process';
3
- import { marked, wordsIn } from "../../core/answers.js";
4
- import { existsSync, readFileSync, writeFileSync } from "../../core/files.js";
5
- import { exit, invoked } from "../../core/invoked.js";
6
- import { valuesIn } from "../../core/rows.js";
7
- import { ours } from "../../core/scope.js";
8
- import { findTree, inTree } from "../../core/tree.js";
9
- import { CHECKED_BY, KINDS, LISTENS, SAID, WRITES, WRITTEN_BY, kindOf } from "../release.js";
3
+ import { existsSync, readFileSync, writeFileSync } from '../../integrations/disk.js';
4
+ import { exit, invoked } from '../../core/invoked.js';
5
+ import { marked, valuesIn } from '../../core/rows.js';
6
+ import { ours } from '../../core/scope.js';
7
+ import { findTree, inTree } from '../../core/tree.js';
8
+ import { CHECKED_BY, KINDS, LISTENS, SAID, WRITES, WRITTEN_BY, kindOf } from '../release.js';
10
9
  const TREE = findTree();
11
10
  const MANIFEST = 'package.json';
11
+ const addressed = (rows) => rows.flatMap((one) => [...(one.at === undefined ? [] : [one.at]), ...addressed(one.derives ?? [])]);
12
12
  const mark = (one) => marked([one.id ?? '', one.at ?? '']);
13
13
  function releases() {
14
14
  const at = inTree(TREE, 'derivation.ts');
@@ -22,45 +22,41 @@ function releases() {
22
22
  return [];
23
23
  }
24
24
  }
25
- function named() {
26
- const at = inTree(TREE, 'secrets.ts');
27
- if (!existsSync(at))
28
- return [];
25
+ function manager() {
29
26
  try {
30
- const held = valuesIn(readFileSync(at, 'utf8'));
31
- return (held.SECRETS ?? []).map((one) => one.id.toUpperCase()).sort();
27
+ const held = JSON.parse(readFileSync(join(TREE.at, MANIFEST), 'utf8'));
28
+ const said = typeof held.packageManager === 'string' ? held.packageManager.split('@')[0] : undefined;
29
+ return said === undefined || said === '' ? 'npm' : said;
32
30
  }
33
31
  catch {
34
- return [];
32
+ return 'npm';
35
33
  }
36
34
  }
37
35
  function scripted() {
38
- try {
39
- const held = JSON.parse(readFileSync(join(TREE.at, MANIFEST), 'utf8'));
40
- const scripts = held.scripts ?? {};
41
- return {
42
- build: scripts['build'] === undefined ? 'npm run build' : 'npm run build',
43
- start: scripts['start'] === undefined ? 'npm start' : 'npm start',
44
- };
45
- }
46
- catch {
47
- return { build: 'npm run build', start: 'npm start' };
48
- }
36
+ const tool = manager();
37
+ return { build: `${tool} run build`, start: tool === 'npm' ? 'npm start' : `${tool} start` };
49
38
  }
50
39
  function dockerfile(build, start) {
40
+ const tool = manager();
41
+ const corepack = tool === 'npm' ? [] : ['RUN corepack enable'];
42
+ const lock = tool === 'npm' ? 'package-lock.json*' : `${tool}-lock.yaml*`;
43
+ const first = tool === 'npm'
44
+ ? 'RUN npm ci --omit=dev --ignore-scripts || npm install --omit=dev --ignore-scripts'
45
+ : `RUN ${tool} install --prod --frozen-lockfile --ignore-scripts || ${tool} install --prod --ignore-scripts`;
46
+ const all = tool === 'npm' ? 'RUN npm install --ignore-scripts' : `RUN ${tool} install --ignore-scripts`;
51
47
  return [
52
- '# GENERATED by thread — do not edit. It is derived from what this app declares.',
53
- '',
54
48
  'FROM node:22-slim AS build',
55
49
  'WORKDIR /app',
56
- 'COPY package.json package-lock.json* ./',
57
- 'RUN npm ci --omit=dev --ignore-scripts || npm install --omit=dev --ignore-scripts',
50
+ ...corepack,
51
+ `COPY package.json ${lock} ./`,
52
+ first,
58
53
  'COPY . .',
59
- 'RUN npm install --ignore-scripts',
54
+ all,
60
55
  `RUN ${build}`,
61
56
  '',
62
57
  'FROM node:22-slim',
63
58
  'WORKDIR /app',
59
+ ...corepack,
64
60
  'ENV NODE_ENV=production',
65
61
  'COPY --from=build /app ./',
66
62
  `EXPOSE ${String(LISTENS)}`,
@@ -68,28 +64,27 @@ function dockerfile(build, start) {
68
64
  '',
69
65
  ].join('\n');
70
66
  }
71
- function compose(id, secrets) {
67
+ function compose(id) {
72
68
  return [
73
- '# GENERATED by thread — do not edit. It is derived from what this app declares.',
74
- '',
75
69
  'services:',
76
70
  ` ${id}:`,
77
71
  ' build: .',
78
72
  ' ports:',
79
73
  ` - "${String(LISTENS)}:${String(LISTENS)}"`,
80
- ...(secrets.length === 0
81
- ? []
82
- : [
83
- ' environment:',
84
- ...secrets.map((one) => ` ${one}: \${${one}}`),
85
- ]),
74
+ '',
75
+ ].join('\n');
76
+ }
77
+ function withheld() {
78
+ return [
79
+ '.thread',
80
+ 'node_modules',
81
+ '.git',
86
82
  '',
87
83
  ].join('\n');
88
84
  }
89
85
  function made() {
90
86
  const found = new Map();
91
87
  const rows = releases();
92
- const secrets = named();
93
88
  const { build, start } = scripted();
94
89
  for (const one of rows) {
95
90
  const id = one.id;
@@ -100,7 +95,8 @@ function made() {
100
95
  continue;
101
96
  if (kind === 'docker') {
102
97
  found.set('Dockerfile', dockerfile(build, start));
103
- found.set('compose.yaml', compose(id, secrets));
98
+ found.set('compose.yaml', compose(id));
99
+ found.set('.dockerignore', withheld());
104
100
  }
105
101
  }
106
102
  return found;
@@ -120,7 +116,12 @@ function run() {
120
116
  `\`thread create release\` — it is ${KINDS.join(' or ')}, and the prefix on its id says which.\n`);
121
117
  return 0;
122
118
  }
123
- const words = wordsIn(['read']);
119
+ const words = argv.slice(2).filter((one) => !one.startsWith('-'));
120
+ if (words[0] !== undefined && words[0] !== 'read') {
121
+ stderr.write('release: `' + words[0] + '` is not something this command does. `thread release` writes the release, and ' +
122
+ '`thread release read <mark>` prints one. Nothing was written.\n');
123
+ return 1;
124
+ }
124
125
  if (words[0] === 'read') {
125
126
  const which = words[1];
126
127
  const one = rows.find((row) => mark(row) === which);
@@ -132,10 +133,10 @@ function run() {
132
133
  }
133
134
  const kind = kindOf(one.id ?? '');
134
135
  stdout.write(`${String(one.id)} — ${kind === undefined ? 'a release of a kind this generator does not recognise' : SAID[kind]}\n`);
135
- for (const file of kind === undefined ? [] : WRITES[kind]) {
136
- stdout.write(`\n${file}\n`);
136
+ for (const one of kind === undefined ? [] : WRITES[kind]) {
137
+ stdout.write(`\n${one.at} — ${one.describes}\n`);
137
138
  try {
138
- stdout.write(readFileSync(join(TREE.at, file), 'utf8'));
139
+ stdout.write(readFileSync(join(TREE.at, one.at), 'utf8'));
139
140
  }
140
141
  catch {
141
142
  stdout.write('(nothing is there)\n');
@@ -145,6 +146,16 @@ function run() {
145
146
  }
146
147
  const files = made();
147
148
  const checking = argv.includes('--check');
149
+ const stated = new Set(addressed(rows));
150
+ const stray = [...files.keys()].filter((one) => !stated.has(one));
151
+ if (stray.length > 0) {
152
+ stderr.write(`release: this release produces ${stray.join(', ')}, and no row names ${stray.length === 1 ? 'it' : 'them'}. ` +
153
+ `Writing ${stray.length === 1 ? 'it' : 'them'} would leave a file this tree does not declare, and the descent ` +
154
+ 'reports that three steps later as a file nobody can account for. Take the release out with `thread remove ' +
155
+ '<its id>` and declare it again — the maker writes one row per file this kind produces, so all of them arrive ' +
156
+ 'together. Nothing was written.\n');
157
+ return 1;
158
+ }
148
159
  const shipped = installed();
149
160
  if (shipped.length > 0) {
150
161
  stderr.write(`release: this app installs ${shipped.join(', ')} as a dependency, and what a host runs may hold ` +
@@ -176,8 +187,10 @@ function run() {
176
187
  }
177
188
  for (const [at, text] of files)
178
189
  writeFileSync(join(TREE.at, at), text, 'utf8');
179
- stdout.write(`release: wrote ${[...files.keys()].join(', ')} — built and started by what this app's own manifest ` +
180
- `declares, listening on ${String(LISTENS)}, naming ${String(named().length)} secret(s) and holding none of them.\n`);
190
+ stdout.write('release: wrote ' + [...files.keys()].join(', ') + ' — built and started by what this app\'s own manifest ' +
191
+ 'declares, listening on ' + String(LISTENS) + '.\n' +
192
+ 'release: no secret is named there. A secret is read from the sealed store at the moment it is used, and ' +
193
+ 'naming it here would be telling whoever deploys this to put a credential in the environment instead.\n');
181
194
  return 0;
182
195
  }
183
196
  if (invoked(import.meta.url))
@@ -1,10 +1,9 @@
1
1
  import { join } from 'node:path'
2
2
  import { argv, stderr, stdout } from 'node:process'
3
3
 
4
- import { marked, wordsIn } from '../../core/answers.ts'
5
- import { existsSync, readFileSync, writeFileSync } from '../../core/files.ts'
4
+ import { existsSync, readFileSync, writeFileSync } from '../../integrations/disk.ts'
6
5
  import { exit, invoked } from '../../core/invoked.ts'
7
- import { valuesIn } from '../../core/rows.ts'
6
+ import { marked, valuesIn } from '../../core/rows.ts'
8
7
  import { ours } from '../../core/scope.ts'
9
8
  import { findTree, inTree } from '../../core/tree.ts'
10
9
 
@@ -17,8 +16,12 @@ const MANIFEST = 'package.json'
17
16
  interface Row {
18
17
  readonly id?: string
19
18
  readonly at?: string
19
+ readonly derives?: readonly Row[]
20
20
  }
21
21
 
22
+ const addressed = (rows: readonly Row[]): readonly string[] =>
23
+ rows.flatMap((one) => [...(one.at === undefined ? [] : [one.at]), ...addressed(one.derives ?? [])])
24
+
22
25
  const mark = (one: Row): string => marked([one.id ?? '', one.at ?? ''])
23
26
 
24
27
  function releases(): readonly Row[] {
@@ -32,46 +35,44 @@ function releases(): readonly Row[] {
32
35
  }
33
36
  }
34
37
 
35
- function named(): readonly string[] {
36
- const at = inTree(TREE, 'secrets.ts')
37
- if (!existsSync(at)) return []
38
+ function manager(): string {
38
39
  try {
39
- const held = valuesIn(readFileSync(at, 'utf8')) as { SECRETS?: readonly { readonly id: string }[] }
40
- return (held.SECRETS ?? []).map((one) => one.id.toUpperCase()).sort()
40
+ const held = JSON.parse(readFileSync(join(TREE.at, MANIFEST), 'utf8')) as { packageManager?: unknown }
41
+ const said = typeof held.packageManager === 'string' ? held.packageManager.split('@')[0] : undefined
42
+ return said === undefined || said === '' ? 'npm' : said
41
43
  } catch {
42
- return []
44
+ return 'npm'
43
45
  }
44
46
  }
45
47
 
46
48
  function scripted(): { readonly build: string; readonly start: string } {
47
- try {
48
- const held = JSON.parse(readFileSync(join(TREE.at, MANIFEST), 'utf8')) as {
49
- scripts?: Readonly<Record<string, string>>
50
- }
51
- const scripts = held.scripts ?? {}
52
- return {
53
- build: scripts['build'] === undefined ? 'npm run build' : 'npm run build',
54
- start: scripts['start'] === undefined ? 'npm start' : 'npm start',
55
- }
56
- } catch {
57
- return { build: 'npm run build', start: 'npm start' }
58
- }
49
+ const tool = manager()
50
+ return { build: `${tool} run build`, start: tool === 'npm' ? 'npm start' : `${tool} start` }
59
51
  }
60
52
 
61
53
  function dockerfile(build: string, start: string): string {
54
+ const tool = manager()
55
+ const corepack = tool === 'npm' ? [] : ['RUN corepack enable']
56
+ const lock = tool === 'npm' ? 'package-lock.json*' : `${tool}-lock.yaml*`
57
+ const first =
58
+ tool === 'npm'
59
+ ? 'RUN npm ci --omit=dev --ignore-scripts || npm install --omit=dev --ignore-scripts'
60
+ : `RUN ${tool} install --prod --frozen-lockfile --ignore-scripts || ${tool} install --prod --ignore-scripts`
61
+ const all = tool === 'npm' ? 'RUN npm install --ignore-scripts' : `RUN ${tool} install --ignore-scripts`
62
+
62
63
  return [
63
- '# GENERATED by thread — do not edit. It is derived from what this app declares.',
64
- '',
65
64
  'FROM node:22-slim AS build',
66
65
  'WORKDIR /app',
67
- 'COPY package.json package-lock.json* ./',
68
- 'RUN npm ci --omit=dev --ignore-scripts || npm install --omit=dev --ignore-scripts',
66
+ ...corepack,
67
+ `COPY package.json ${lock} ./`,
68
+ first,
69
69
  'COPY . .',
70
- 'RUN npm install --ignore-scripts',
70
+ all,
71
71
  `RUN ${build}`,
72
72
  '',
73
73
  'FROM node:22-slim',
74
74
  'WORKDIR /app',
75
+ ...corepack,
75
76
  'ENV NODE_ENV=production',
76
77
  'COPY --from=build /app ./',
77
78
  `EXPOSE ${String(LISTENS)}`,
@@ -80,22 +81,22 @@ function dockerfile(build: string, start: string): string {
80
81
  ].join('\n')
81
82
  }
82
83
 
83
- function compose(id: string, secrets: readonly string[]): string {
84
+ function compose(id: string): string {
84
85
  return [
85
- '# GENERATED by thread — do not edit. It is derived from what this app declares.',
86
- '',
87
86
  'services:',
88
87
  ` ${id}:`,
89
88
  ' build: .',
90
89
  ' ports:',
91
90
  ` - "${String(LISTENS)}:${String(LISTENS)}"`,
92
- ...(secrets.length === 0
93
- ? []
94
- : [
95
- ' environment:',
91
+ '',
92
+ ].join('\n')
93
+ }
96
94
 
97
- ...secrets.map((one) => ` ${one}: \${${one}}`),
98
- ]),
95
+ function withheld(): string {
96
+ return [
97
+ '.thread',
98
+ 'node_modules',
99
+ '.git',
99
100
  '',
100
101
  ].join('\n')
101
102
  }
@@ -103,7 +104,6 @@ function compose(id: string, secrets: readonly string[]): string {
103
104
  function made(): ReadonlyMap<string, string> {
104
105
  const found = new Map<string, string>()
105
106
  const rows = releases()
106
- const secrets = named()
107
107
  const { build, start } = scripted()
108
108
 
109
109
  for (const one of rows) {
@@ -113,7 +113,8 @@ function made(): ReadonlyMap<string, string> {
113
113
  if (kind === undefined) continue
114
114
  if (kind === 'docker') {
115
115
  found.set('Dockerfile', dockerfile(build, start))
116
- found.set('compose.yaml', compose(id, secrets))
116
+ found.set('compose.yaml', compose(id))
117
+ found.set('.dockerignore', withheld())
117
118
  }
118
119
  }
119
120
  return found
@@ -138,7 +139,14 @@ function run(): number {
138
139
  return 0
139
140
  }
140
141
 
141
- const words = wordsIn(['read'])
142
+ const words = argv.slice(2).filter((one) => !one.startsWith('-'))
143
+ if (words[0] !== undefined && words[0] !== 'read') {
144
+ stderr.write(
145
+ 'release: `' + words[0] + '` is not something this command does. `thread release` writes the release, and ' +
146
+ '`thread release read <mark>` prints one. Nothing was written.\n',
147
+ )
148
+ return 1
149
+ }
142
150
  if (words[0] === 'read') {
143
151
  const which = words[1]
144
152
  const one = rows.find((row) => mark(row) === which)
@@ -153,10 +161,10 @@ function run(): number {
153
161
 
154
162
  const kind = kindOf(one.id ?? '')
155
163
  stdout.write(`${String(one.id)} — ${kind === undefined ? 'a release of a kind this generator does not recognise' : SAID[kind]}\n`)
156
- for (const file of kind === undefined ? [] : WRITES[kind]) {
157
- stdout.write(`\n${file}\n`)
164
+ for (const one of kind === undefined ? [] : WRITES[kind]) {
165
+ stdout.write(`\n${one.at} — ${one.describes}\n`)
158
166
  try {
159
- stdout.write(readFileSync(join(TREE.at, file), 'utf8'))
167
+ stdout.write(readFileSync(join(TREE.at, one.at), 'utf8'))
160
168
  } catch {
161
169
  stdout.write('(nothing is there)\n')
162
170
  }
@@ -167,6 +175,19 @@ function run(): number {
167
175
  const files = made()
168
176
  const checking = argv.includes('--check')
169
177
 
178
+ const stated = new Set(addressed(rows))
179
+ const stray = [...files.keys()].filter((one) => !stated.has(one))
180
+ if (stray.length > 0) {
181
+ stderr.write(
182
+ `release: this release produces ${stray.join(', ')}, and no row names ${stray.length === 1 ? 'it' : 'them'}. ` +
183
+ `Writing ${stray.length === 1 ? 'it' : 'them'} would leave a file this tree does not declare, and the descent ` +
184
+ 'reports that three steps later as a file nobody can account for. Take the release out with `thread remove ' +
185
+ '<its id>` and declare it again — the maker writes one row per file this kind produces, so all of them arrive ' +
186
+ 'together. Nothing was written.\n',
187
+ )
188
+ return 1
189
+ }
190
+
170
191
  const shipped = installed()
171
192
  if (shipped.length > 0) {
172
193
  stderr.write(
@@ -202,8 +223,10 @@ function run(): number {
202
223
 
203
224
  for (const [at, text] of files) writeFileSync(join(TREE.at, at), text, 'utf8')
204
225
  stdout.write(
205
- `release: wrote ${[...files.keys()].join(', ')} — built and started by what this app's own manifest ` +
206
- `declares, listening on ${String(LISTENS)}, naming ${String(named().length)} secret(s) and holding none of them.\n`,
226
+ 'release: wrote ' + [...files.keys()].join(', ') + ' — built and started by what this app\'s own manifest ' +
227
+ 'declares, listening on ' + String(LISTENS) + '.\n' +
228
+ 'release: no secret is named there. A secret is read from the sealed store at the moment it is used, and ' +
229
+ 'naming it here would be telling whoever deploys this to put a credential in the environment instead.\n',
207
230
  )
208
231
  return 0
209
232
  }
@@ -1,8 +1,12 @@
1
1
  export declare const PREFIXES: {
2
- readonly docker: "dk_";
2
+ readonly docker: 'dk_';
3
3
  };
4
- export declare const KINDS: readonly ["docker"];
5
- export declare const WRITES: Readonly<Record<(typeof KINDS)[number], readonly string[]>>;
4
+ export declare const KINDS: readonly ['docker'];
5
+ export interface Written {
6
+ readonly at: string;
7
+ readonly describes: string;
8
+ }
9
+ export declare const WRITES: Readonly<Record<(typeof KINDS)[number], readonly Written[]>>;
6
10
  export declare const LISTENS = 3000;
7
11
  export declare const SAID: Readonly<Record<(typeof KINDS)[number], string>>;
8
12
  export declare const kindOf: (id: string) => (typeof KINDS)[number] | undefined;
@@ -1,7 +1,20 @@
1
1
  export const PREFIXES = { docker: 'dk_' };
2
2
  export const KINDS = ['docker'];
3
3
  export const WRITES = {
4
- docker: ['Dockerfile', 'compose.yaml'],
4
+ docker: [
5
+ {
6
+ at: 'Dockerfile',
7
+ describes: 'How this app becomes an image and what starts it inside one — the build and start commands its own manifest declares, run with the package manager that manifest names, and the port its server listens on.',
8
+ },
9
+ {
10
+ at: 'compose.yaml',
11
+ describes: 'What runs the image this release builds, named beside it because one row states one address.',
12
+ },
13
+ {
14
+ at: '.dockerignore',
15
+ describes: 'What a build of this image must never carry out of this directory: the sealed store and the key that opens it, the installed packages, and the history. Without it the build copies everything beside it, and every sealed value goes into the image and into whoever pulls it.',
16
+ },
17
+ ],
5
18
  };
6
19
  export const LISTENS = 3000;
7
20
  export const SAID = {
@@ -4,8 +4,28 @@ export const PREFIXES = { docker: 'dk_' } as const
4
4
 
5
5
  export const KINDS = ['docker'] as const
6
6
 
7
- export const WRITES: Readonly<Record<(typeof KINDS)[number], readonly string[]>> = {
8
- docker: ['Dockerfile', 'compose.yaml'],
7
+ export interface Written {
8
+ readonly at: string
9
+ readonly describes: string
10
+ }
11
+
12
+ export const WRITES: Readonly<Record<(typeof KINDS)[number], readonly Written[]>> = {
13
+ docker: [
14
+ {
15
+ at: 'Dockerfile',
16
+ describes:
17
+ 'How this app becomes an image and what starts it inside one — the build and start commands its own manifest declares, run with the package manager that manifest names, and the port its server listens on.',
18
+ },
19
+ {
20
+ at: 'compose.yaml',
21
+ describes: 'What runs the image this release builds, named beside it because one row states one address.',
22
+ },
23
+ {
24
+ at: '.dockerignore',
25
+ describes:
26
+ 'What a build of this image must never carry out of this directory: the sealed store and the key that opens it, the installed packages, and the history. Without it the build copies everything beside it, and every sealed value goes into the image and into whoever pulls it.',
27
+ },
28
+ ],
9
29
  }
10
30
 
11
31
  export const LISTENS = 3000
@@ -2,5 +2,9 @@ import type { Attaching } from '../core/attach.ts';
2
2
  export declare const CONCEPTS: readonly {
3
3
  readonly id: string;
4
4
  readonly describes: string;
5
+ readonly cites: readonly {
6
+ readonly on: string;
7
+ readonly said: string;
8
+ }[];
5
9
  }[];
6
10
  export declare const ATTACH: Attaching;