@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
@@ -21,14 +21,137 @@ function declared(): ReadonlySet<string> {
21
21
  const where = one.at?.split('\\').join('/')
22
22
  if (where === undefined || !where.includes(UNDER)) continue
23
23
  if (one.id !== undefined) found.add(one.id)
24
- const named = where.split('/').at(-1)?.replace(/\.[^.]+$/, '')
25
- if (named !== undefined) found.add(named)
26
24
  }
27
25
  }
28
26
 
29
27
  return found
30
28
  }
31
29
 
30
+ function bodies(): ReadonlySet<string> {
31
+ const found = new Set<string>()
32
+
33
+ descend()
34
+
35
+ for (const at of reached()) {
36
+ const text = read(at)
37
+ if (text === undefined) continue
38
+
39
+ for (const one of rowsIn(text)) {
40
+ const where = one.at?.split('\\').join('/')
41
+ if (where === undefined || !where.includes(UNDER)) continue
42
+ if (one.id !== undefined) found.add(where.replace(/\.[^./]+$/, ''))
43
+ }
44
+ }
45
+
46
+ return found
47
+ }
48
+
49
+ function declaring(): ReadonlySet<string> {
50
+ const found = new Set<string>()
51
+
52
+ descend()
53
+
54
+ for (const at of reached()) {
55
+ const text = read(at)
56
+ if (text === undefined) continue
57
+
58
+ for (const one of rowsIn(text)) {
59
+ const where = one.at?.split('\\').join('/')
60
+ if (where === undefined) continue
61
+ found.add(where.replace(/\.[^./]+$/, ''))
62
+ }
63
+ }
64
+
65
+ return found
66
+ }
67
+
68
+ interface Imported {
69
+ readonly type?: string
70
+ readonly source?: { readonly value?: unknown }
71
+ readonly specifiers?: readonly { readonly local?: { readonly name?: unknown } }[]
72
+ }
73
+
74
+ function importedFrom(name: string, ancestors: readonly unknown[]): string | undefined {
75
+ const program = ancestors.find((one) => (one as { readonly type?: string } | undefined)?.type === 'Program') as
76
+ | { readonly body?: readonly Imported[] }
77
+ | undefined
78
+ for (const one of program?.body ?? []) {
79
+ if (one.type !== 'ImportDeclaration' || typeof one.source?.value !== 'string') continue
80
+ if (!(one.specifiers ?? []).some((each) => each.local?.name === name)) continue
81
+ return one.source.value
82
+ }
83
+ return undefined
84
+ }
85
+
86
+ interface Typed {
87
+ readonly typeName?: { readonly name?: unknown }
88
+ readonly typeAnnotation?: Typed
89
+ readonly types?: readonly Typed[]
90
+ }
91
+
92
+ interface Annotated {
93
+ readonly type?: string
94
+ readonly name?: unknown
95
+ readonly typeAnnotation?: Typed
96
+ }
97
+
98
+ interface Held {
99
+ readonly type?: string
100
+ readonly params?: readonly Annotated[]
101
+ readonly body?: unknown
102
+ }
103
+
104
+ function namedIn(said: Typed | undefined): string | undefined {
105
+ if (said === undefined) return undefined
106
+ if (typeof said.typeName?.name === 'string') return said.typeName.name
107
+ const inner = namedIn(said.typeAnnotation)
108
+ if (inner !== undefined) return inner
109
+ for (const one of said.types ?? []) {
110
+ const found = namedIn(one)
111
+ if (found !== undefined) return found
112
+ }
113
+ return undefined
114
+ }
115
+
116
+ const typeNamed = (held: Annotated | undefined): string | undefined => namedIn(held?.typeAnnotation?.typeAnnotation)
117
+
118
+ interface Stated {
119
+ readonly type?: string
120
+ readonly declarations?: readonly { readonly id?: Annotated }[]
121
+ }
122
+
123
+ function annotating(name: string, ancestors: readonly unknown[]): string | undefined {
124
+ for (const held of ancestors) {
125
+ for (const param of (held as Held).params ?? []) {
126
+ if (param.type !== 'Identifier' || param.name !== name) continue
127
+ const said = typeNamed(param)
128
+ if (said !== undefined) return said
129
+ }
130
+
131
+ const body = (held as Held).body
132
+ for (const one of Array.isArray(body) ? (body as readonly Stated[]) : []) {
133
+ if (one.type !== 'VariableDeclaration') continue
134
+ for (const each of one.declarations ?? []) {
135
+ if (each.id?.type !== 'Identifier' || each.id.name !== name) continue
136
+ const said = typeNamed(each.id)
137
+ if (said !== undefined) return said
138
+ }
139
+ }
140
+ }
141
+ return undefined
142
+ }
143
+
144
+ const settled = (from: string, source: string): string => {
145
+ if (!source.startsWith('.')) return source.replace(/\.[^./]+$/, '')
146
+ const parts = from.split('/').slice(0, -1)
147
+ for (const step of source.replace(/\.[^./]+$/, '').split('/')) {
148
+ if (step === '.' || step === '') continue
149
+ if (step === '..') parts.pop()
150
+ else parts.push(step)
151
+ }
152
+ return parts.join('/')
153
+ }
154
+
32
155
  interface Called {
33
156
  readonly callee?: { readonly type?: string; readonly name?: string; readonly property?: { readonly type?: string; readonly name?: string } }
34
157
  readonly arguments?: readonly { readonly type?: string; readonly value?: unknown; readonly name?: string }[]
@@ -36,7 +159,7 @@ interface Called {
36
159
 
37
160
  export const slopnator: Slopnator = {
38
161
  on: ['CallExpression'],
39
- holds: (node) => {
162
+ holds: (node, at) => {
40
163
  const call = node as Called | undefined
41
164
  const callee = call?.callee
42
165
  const named =
@@ -55,34 +178,19 @@ export const slopnator: Slopnator = {
55
178
  )
56
179
  }
57
180
 
58
- /*
59
- * THE OPERATION ITSELF IS A BETTER ANSWER THAN ITS NAME, AND THIS USED TO REFUSE IT.
60
- *
61
- * This asked for a string literal and refused everything else, which was right when the only
62
- * caller was a UI seam handed a bare name. It has been wrong since dispatch stopped taking one:
63
- * the note above says so in as many words — "`dispatch` in packages/core/operations/src/dispatch.ts
64
- * takes an Operation object rather than a name". What ships now imports the compiled operation
65
- * and hands over the binding, and a binding cannot be assembled at run time: if it named nothing,
66
- * the file would not compile.
67
- *
68
- * Measured 2026-08-31 on a registry install: an operations-only app, everything made by the CLI,
69
- * failed its own sweep on `operations/_client.ts` — a file its own generator had just written,
70
- * naming an operation the same tree declares. The gate refuses the build now, so a law that
71
- * refuses a generated file is an app that cannot ship.
72
- *
73
- * Narrow on purpose. An identifier is accepted only when it IS one of the declared operations,
74
- * which is the shape the generators emit — `import { operation as wf_charge }` and then
75
- * `dispatch(wf_charge, …)`. A variable holding a computed name is still refused, because it does
76
- * not match a declaration, and that is the hole this law was written for.
77
- */
78
181
  const all = declared()
79
182
 
80
183
  if (first.type === 'Identifier' && typeof first.name === 'string') {
81
184
  if (all.has(first.name)) return false
185
+ const source = importedFrom(first.name, at.ancestors())
186
+ if (source !== undefined && bodies().has(settled(at.filename, source))) return false
187
+ const named = annotating(first.name, at.ancestors())
188
+ const typed = named === undefined ? undefined : importedFrom(named, at.ancestors())
189
+ if (typed !== undefined && declaring().has(settled(at.filename, typed))) return false
82
190
  return (
83
- `dispatch is handed \`${first.name}\`, and nothing declares an operation by that name. A binding is only ` +
84
- 'checkable when it is the operation — import the compiled operation and hand that over, so what runs and ' +
85
- 'what was declared are the same thing rather than two things that agree today'
191
+ `dispatch is handed \`${first.name}\`, and it is not imported from a file any row declares as an operation. ` +
192
+ 'A binding is only checkable when it is the operation — import the compiled operation and hand that over, so ' +
193
+ 'what runs and what was declared are the same thing rather than two things that agree today'
86
194
  )
87
195
  }
88
196
 
@@ -1,5 +1,5 @@
1
- import { descend, read, reached } from "../../core/descend.js";
2
- import { rowsIn } from "./sl_unthreaded.js";
1
+ import { descend, read, reached } from '../../core/descend.js';
2
+ import { rowsIn } from './sl_unthreaded.js';
3
3
  const ELEMENT = 'element';
4
4
  const VARIANT = 'variant';
5
5
  function nameOf(id) {
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,90 @@
1
+ import ts from 'typescript';
2
+ import { descend } from '../../core/descend.js';
3
+ const said = (held) => held !== undefined && ts.isStringLiteral(held) ? held.text : undefined;
4
+ const listed = (held) => held !== undefined && ts.isArrayLiteralExpression(held)
5
+ ? held.elements.filter((one) => ts.isStringLiteral(one)).map((one) => one.text)
6
+ : [];
7
+ const yes = (held) => held !== undefined && held.kind === ts.SyntaxKind.TrueKeyword;
8
+ function invoking(text) {
9
+ const file = ts.createSourceFile('row.ts', text, ts.ScriptTarget.Latest, true);
10
+ const rows = new Map();
11
+ const found = [];
12
+ const visit = (node) => {
13
+ if (ts.isObjectLiteralExpression(node)) {
14
+ const of = (name) => {
15
+ for (const one of node.properties) {
16
+ if (ts.isPropertyAssignment(one) && ts.isIdentifier(one.name) && one.name.text === name)
17
+ return one.initializer;
18
+ }
19
+ return undefined;
20
+ };
21
+ const id = said(of('id'));
22
+ if (id !== undefined)
23
+ rows.set(id, node);
24
+ const invokes = said(of('invokes'));
25
+ if (id !== undefined && invokes !== undefined) {
26
+ found.push({ id, invokes, carries: said(of('arrives')) !== undefined && listed(of('requires')).length > 0 });
27
+ }
28
+ }
29
+ ts.forEachChild(node, visit);
30
+ };
31
+ visit(file);
32
+ return { asking: found, rows };
33
+ }
34
+ const fieldOf = (node, name) => {
35
+ for (const one of node.properties) {
36
+ if (ts.isPropertyAssignment(one) && ts.isIdentifier(one.name) && one.name.text === name)
37
+ return one.initializer;
38
+ }
39
+ return undefined;
40
+ };
41
+ export const slopnator = {
42
+ holds: (_node, at) => {
43
+ const { asking, rows } = invoking(at.text);
44
+ if (asking.length === 0)
45
+ return false;
46
+ const found = [];
47
+ const pass = descend();
48
+ for (const one of asking) {
49
+ const here = rows.get(one.invokes);
50
+ const named = here === undefined ? undefined : listed(fieldOf(here, 'webhooks'));
51
+ const signs = here === undefined ? undefined : yes(fieldOf(here, 'signed'));
52
+ const runs = pass.stated.get(one.invokes);
53
+ if (here === undefined && runs === undefined) {
54
+ found.push(one.id + ' invokes ' + one.invokes + ', and nothing in this tree declares an operation by that name');
55
+ continue;
56
+ }
57
+ if (!(named ?? listed(runs?.fields.get('webhooks'))).includes(one.id)) {
58
+ found.push(one.id + ' invokes ' + one.invokes + ', and that operation names no webhook ' + one.id +
59
+ ' back, so it is reachable from outside by something it never agreed to');
60
+ }
61
+ if ((signs ?? yes(runs?.fields.get('signed'))) && !one.carries) {
62
+ found.push(one.invokes + ' takes only a signed webhook, and ' + one.id +
63
+ ' states no header a signature arrives on and no secret to check it against');
64
+ }
65
+ }
66
+ return found.length === 0 ? false : found.join('; ');
67
+ },
68
+ proves: {
69
+ wrong: [
70
+ {
71
+ files: [
72
+ { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly invokes?: string\n readonly webhooks?: readonly string[]\n readonly declares?: readonly Threaded[]\n}\n" },
73
+ { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n { at: 'threads/_probe_webhook.ts' },\n ],\n}\n" },
74
+ { at: 'threads/_probe_webhook.ts', source: "export const WEBHOOKS: Threaded = {\n id: 'webhooks',\n declares: [\n { id: 'wh_stripe', at: 'app/inbound/wh_stripe/route.ts', invokes: 'wf_charge' },\n { id: 'wf_charge', at: 'operations/wf_charge.ts' },\n ],\n}\n" },
75
+ ],
76
+ at: 'threads/_probe_webhook.ts',
77
+ },
78
+ ],
79
+ right: [
80
+ {
81
+ files: [
82
+ { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly version?: number\n readonly invokes?: string\n readonly webhooks?: readonly string[]\n readonly by?: string\n readonly runs?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n}\n" },
83
+ { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n { at: 'threads/_probe_webhook.ts' },\n ],\n}\n" },
84
+ { at: 'threads/_probe_webhook.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const WEBHOOKS: Threaded = {\n id: 'webhooks',\n version: 1,\n declares: [\n { id: 'wf_charge', at: 'operations/wf_charge.ts', webhooks: ['wh_stripe'] },\n ],\n derives: [\n { id: 'wh_stripe', at: 'app/inbound/wh_stripe/route.ts', invokes: 'wf_charge', by: 'thread capabilities', runs: 'thread capabilities --check' },\n ],\n}\n" },
85
+ ],
86
+ at: 'threads/_probe_webhook.ts',
87
+ },
88
+ ],
89
+ },
90
+ };
@@ -0,0 +1,108 @@
1
+ import ts from 'typescript'
2
+
3
+ import { descend } from '../../core/descend.ts'
4
+
5
+ import type { Slopnator } from '../slopnator.ts'
6
+
7
+ const said = (held: ts.Expression | undefined): string | undefined =>
8
+ held !== undefined && ts.isStringLiteral(held) ? held.text : undefined
9
+
10
+ const listed = (held: ts.Expression | undefined): readonly string[] =>
11
+ held !== undefined && ts.isArrayLiteralExpression(held)
12
+ ? held.elements.filter((one) => ts.isStringLiteral(one)).map((one) => (one as ts.StringLiteral).text)
13
+ : []
14
+
15
+ const yes = (held: ts.Expression | undefined): boolean =>
16
+ held !== undefined && held.kind === ts.SyntaxKind.TrueKeyword
17
+
18
+ interface Asking {
19
+ readonly id: string
20
+ readonly invokes: string
21
+ readonly carries: boolean
22
+ }
23
+
24
+ function invoking(text: string): { readonly asking: readonly Asking[]; readonly rows: ReadonlyMap<string, ts.ObjectLiteralExpression> } {
25
+ const file = ts.createSourceFile('row.ts', text, ts.ScriptTarget.Latest, true)
26
+ const rows = new Map<string, ts.ObjectLiteralExpression>()
27
+ const found: Asking[] = []
28
+ const visit = (node: ts.Node): void => {
29
+ if (ts.isObjectLiteralExpression(node)) {
30
+ const of = (name: string): ts.Expression | undefined => {
31
+ for (const one of node.properties) {
32
+ if (ts.isPropertyAssignment(one) && ts.isIdentifier(one.name) && one.name.text === name) return one.initializer
33
+ }
34
+ return undefined
35
+ }
36
+ const id = said(of('id'))
37
+ if (id !== undefined) rows.set(id, node)
38
+ const invokes = said(of('invokes'))
39
+ if (id !== undefined && invokes !== undefined) {
40
+ found.push({ id, invokes, carries: said(of('arrives')) !== undefined && listed(of('requires')).length > 0 })
41
+ }
42
+ }
43
+ ts.forEachChild(node, visit)
44
+ }
45
+ visit(file)
46
+ return { asking: found, rows }
47
+ }
48
+
49
+ const fieldOf = (node: ts.ObjectLiteralExpression, name: string): ts.Expression | undefined => {
50
+ for (const one of node.properties) {
51
+ if (ts.isPropertyAssignment(one) && ts.isIdentifier(one.name) && one.name.text === name) return one.initializer
52
+ }
53
+ return undefined
54
+ }
55
+
56
+ export const slopnator: Slopnator = {
57
+ holds: (_node, at) => {
58
+ const { asking, rows } = invoking(at.text)
59
+ if (asking.length === 0) return false
60
+ const found: string[] = []
61
+ const pass = descend()
62
+ for (const one of asking) {
63
+ const here = rows.get(one.invokes)
64
+ const named = here === undefined ? undefined : listed(fieldOf(here, 'webhooks'))
65
+ const signs = here === undefined ? undefined : yes(fieldOf(here, 'signed'))
66
+ const runs = pass.stated.get(one.invokes)
67
+ if (here === undefined && runs === undefined) {
68
+ found.push(one.id + ' invokes ' + one.invokes + ', and nothing in this tree declares an operation by that name')
69
+ continue
70
+ }
71
+ if (!(named ?? listed(runs?.fields.get('webhooks'))).includes(one.id)) {
72
+ found.push(
73
+ one.id + ' invokes ' + one.invokes + ', and that operation names no webhook ' + one.id +
74
+ ' back, so it is reachable from outside by something it never agreed to',
75
+ )
76
+ }
77
+ if ((signs ?? yes(runs?.fields.get('signed'))) && !one.carries) {
78
+ found.push(
79
+ one.invokes + ' takes only a signed webhook, and ' + one.id +
80
+ ' states no header a signature arrives on and no secret to check it against',
81
+ )
82
+ }
83
+ }
84
+ return found.length === 0 ? false : found.join('; ')
85
+ },
86
+ proves: {
87
+ wrong: [
88
+ {
89
+ files: [
90
+ { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly invokes?: string\n readonly webhooks?: readonly string[]\n readonly declares?: readonly Threaded[]\n}\n" },
91
+ { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n { at: 'threads/_probe_webhook.ts' },\n ],\n}\n" },
92
+ { at: 'threads/_probe_webhook.ts', source: "export const WEBHOOKS: Threaded = {\n id: 'webhooks',\n declares: [\n { id: 'wh_stripe', at: 'app/inbound/wh_stripe/route.ts', invokes: 'wf_charge' },\n { id: 'wf_charge', at: 'operations/wf_charge.ts' },\n ],\n}\n" },
93
+ ],
94
+ at: 'threads/_probe_webhook.ts',
95
+ },
96
+ ],
97
+ right: [
98
+ {
99
+ files: [
100
+ { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly version?: number\n readonly invokes?: string\n readonly webhooks?: readonly string[]\n readonly by?: string\n readonly runs?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n}\n" },
101
+ { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n { at: 'threads/_probe_webhook.ts' },\n ],\n}\n" },
102
+ { at: 'threads/_probe_webhook.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const WEBHOOKS: Threaded = {\n id: 'webhooks',\n version: 1,\n declares: [\n { id: 'wf_charge', at: 'operations/wf_charge.ts', webhooks: ['wh_stripe'] },\n ],\n derives: [\n { id: 'wh_stripe', at: 'app/inbound/wh_stripe/route.ts', invokes: 'wf_charge', by: 'thread capabilities', runs: 'thread capabilities --check' },\n ],\n}\n" },
103
+ ],
104
+ at: 'threads/_probe_webhook.ts',
105
+ },
106
+ ],
107
+ },
108
+ }
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,61 @@
1
+ import ts from 'typescript';
2
+ import { LISTS as LINEAGE } from '../../core/descend.js';
3
+ const LISTS = new Set(LINEAGE);
4
+ const ROUTE = '/route.ts';
5
+ const said = (held) => held !== undefined && ts.isStringLiteral(held) ? held.text : undefined;
6
+ const fieldOf = (node, name) => {
7
+ for (const one of node.properties) {
8
+ if (ts.isPropertyAssignment(one) && ts.isIdentifier(one.name) && one.name.text === name)
9
+ return one.initializer;
10
+ }
11
+ return undefined;
12
+ };
13
+ export const slopnator = {
14
+ holds: (_node, at) => {
15
+ const file = ts.createSourceFile('row.ts', at.text, ts.ScriptTarget.Latest, true);
16
+ const routes = new Map();
17
+ const permitted = new Set();
18
+ const visit = (node) => {
19
+ if (ts.isPropertyAssignment(node) &&
20
+ ts.isIdentifier(node.name) &&
21
+ LISTS.has(node.name.text) &&
22
+ ts.isArrayLiteralExpression(node.initializer)) {
23
+ const list = node.name.text;
24
+ for (const one of node.initializer.elements) {
25
+ if (!ts.isObjectLiteralExpression(one))
26
+ continue;
27
+ const where = said(fieldOf(one, 'at'));
28
+ if (where === undefined || !where.endsWith(ROUTE))
29
+ continue;
30
+ routes.set(where, list);
31
+ if (list === 'derives' && said(fieldOf(one, 'by')) !== undefined && said(fieldOf(one, 'runs')) !== undefined) {
32
+ permitted.add(where);
33
+ }
34
+ }
35
+ }
36
+ ts.forEachChild(node, visit);
37
+ };
38
+ visit(file);
39
+ const found = [];
40
+ for (const [where, list] of routes) {
41
+ if (permitted.has(where))
42
+ continue;
43
+ found.push(where + ' is named by a ' + list + ' row' +
44
+ (list === 'derives' ? ' that states no by and no runs' : '') +
45
+ ', and a route is only ever derived');
46
+ }
47
+ return found.length === 0 ? false : found.join('; ');
48
+ },
49
+ proves: {
50
+ wrong: ["const M = { seeds: [{ at: 'app/inbound/wh_stripe/route.ts' }] }"],
51
+ right: [
52
+ {
53
+ files: [
54
+ { at: 'threads/thread.ts', source: "export const THREAD = {\n declares: [\n { at: 'threads/_probe_route.ts' },\n ],\n}\n" },
55
+ { at: 'threads/_probe_route.ts', source: "export const M = {\n derives: [\n { at: 'app/inbound/wh_stripe/route.ts', by: 'thread capabilities', runs: 'thread capabilities --check' },\n ],\n}\n" },
56
+ ],
57
+ at: 'threads/_probe_route.ts',
58
+ },
59
+ ],
60
+ },
61
+ };
@@ -0,0 +1,72 @@
1
+ import ts from 'typescript'
2
+
3
+ import { LISTS as LINEAGE } from '../../core/descend.ts'
4
+
5
+ import type { Slopnator } from '../slopnator.ts'
6
+
7
+ const LISTS: ReadonlySet<string> = new Set(LINEAGE)
8
+
9
+ const ROUTE = '/route.ts'
10
+
11
+ const said = (held: ts.Expression | undefined): string | undefined =>
12
+ held !== undefined && ts.isStringLiteral(held) ? held.text : undefined
13
+
14
+ const fieldOf = (node: ts.ObjectLiteralExpression, name: string): ts.Expression | undefined => {
15
+ for (const one of node.properties) {
16
+ if (ts.isPropertyAssignment(one) && ts.isIdentifier(one.name) && one.name.text === name) return one.initializer
17
+ }
18
+ return undefined
19
+ }
20
+
21
+ export const slopnator: Slopnator = {
22
+ holds: (_node, at) => {
23
+ const file = ts.createSourceFile('row.ts', at.text, ts.ScriptTarget.Latest, true)
24
+ const routes = new Map<string, string>()
25
+ const permitted = new Set<string>()
26
+
27
+ const visit = (node: ts.Node): void => {
28
+ if (
29
+ ts.isPropertyAssignment(node) &&
30
+ ts.isIdentifier(node.name) &&
31
+ LISTS.has(node.name.text) &&
32
+ ts.isArrayLiteralExpression(node.initializer)
33
+ ) {
34
+ const list = node.name.text
35
+ for (const one of node.initializer.elements) {
36
+ if (!ts.isObjectLiteralExpression(one)) continue
37
+ const where = said(fieldOf(one, 'at'))
38
+ if (where === undefined || !where.endsWith(ROUTE)) continue
39
+ routes.set(where, list)
40
+ if (list === 'derives' && said(fieldOf(one, 'by')) !== undefined && said(fieldOf(one, 'runs')) !== undefined) {
41
+ permitted.add(where)
42
+ }
43
+ }
44
+ }
45
+ ts.forEachChild(node, visit)
46
+ }
47
+ visit(file)
48
+
49
+ const found: string[] = []
50
+ for (const [where, list] of routes) {
51
+ if (permitted.has(where)) continue
52
+ found.push(
53
+ where + ' is named by a ' + list + ' row' +
54
+ (list === 'derives' ? ' that states no by and no runs' : '') +
55
+ ', and a route is only ever derived',
56
+ )
57
+ }
58
+ return found.length === 0 ? false : found.join('; ')
59
+ },
60
+ proves: {
61
+ wrong: ["const M = { seeds: [{ at: 'app/inbound/wh_stripe/route.ts' }] }"],
62
+ right: [
63
+ {
64
+ files: [
65
+ { at: 'threads/thread.ts', source: "export const THREAD = {\n declares: [\n { at: 'threads/_probe_route.ts' },\n ],\n}\n" },
66
+ { at: 'threads/_probe_route.ts', source: "export const M = {\n derives: [\n { at: 'app/inbound/wh_stripe/route.ts', by: 'thread capabilities', runs: 'thread capabilities --check' },\n ],\n}\n" },
67
+ ],
68
+ at: 'threads/_probe_route.ts',
69
+ },
70
+ ],
71
+ },
72
+ }
@@ -1,5 +1,5 @@
1
- import { here, islandsDeclared } from "./sl_unthreaded.js";
2
- import { findTree } from "../../core/tree.js";
1
+ import { here, islandsDeclared } from './sl_unthreaded.js';
2
+ import { findTree } from '../../core/tree.js';
3
3
  const TREE = `${findTree().name}/`;
4
4
  const GOVERNS = /^\s*governs:\s*'([^']+)'/gm;
5
5
  export const slopnator = {
@@ -47,7 +47,7 @@ export const slopnator = {
47
47
  files: [
48
48
  {
49
49
  at: 'threads/_governed_probe.ts',
50
- source: "export const M: Islanding = {\n declares: [\n { id: 'events', at: 'packages/core/spec/src/islands/events.ts', governs: ['app/_declare/events.ts'] },\n ],\n}\n",
50
+ source: "export const M: Islanding = {\n declares: [\n { id: 'events', at: 'packages/core/spec/src/islands/events.ts', governs: ['operations/'] },\n ],\n}\n",
51
51
  },
52
52
  ],
53
53
  at: 'threads/_governed_probe.ts',
@@ -50,7 +50,7 @@ export const slopnator: Slopnator = {
50
50
  files: [
51
51
  {
52
52
  at: 'threads/_governed_probe.ts',
53
- source: "export const M: Islanding = {\n declares: [\n { id: 'events', at: 'packages/core/spec/src/islands/events.ts', governs: ['app/_declare/events.ts'] },\n ],\n}\n",
53
+ source: "export const M: Islanding = {\n declares: [\n { id: 'events', at: 'packages/core/spec/src/islands/events.ts', governs: ['operations/'] },\n ],\n}\n",
54
54
  },
55
55
  ],
56
56
  at: 'threads/_governed_probe.ts',
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,39 @@
1
+ import { ROOT } from '../../core/descend.js';
2
+ import { valuesIn } from '../../core/rows.js';
3
+ import { kind } from '../../integrations/disk.js';
4
+ import { REACH, programOf, unlisted } from '../../integrations/target.js';
5
+ const TARGETS = 'TARGETS';
6
+ const there = (path) => kind(path) === 'file';
7
+ export const slopnator = {
8
+ holds: (_node, at) => {
9
+ let held;
10
+ try {
11
+ held = valuesIn(at.text);
12
+ }
13
+ catch (thrown) {
14
+ if (!at.text.includes(TARGETS))
15
+ return false;
16
+ return 'this sheet names ' + TARGETS + ' and could not be read as values, so no target on it was checked — ' + (thrown instanceof Error ? thrown.message : String(thrown));
17
+ }
18
+ const found = [];
19
+ for (const one of held.TARGETS ?? []) {
20
+ if (typeof one.id !== 'string' || !one.id.startsWith(REACH.cli))
21
+ continue;
22
+ const name = one.id.slice(REACH.cli.length);
23
+ if (programOf(name, ROOT, there) === undefined)
24
+ found.push(unlisted(name));
25
+ }
26
+ return found.length === 0 ? false : found.join('. ');
27
+ },
28
+ proves: {
29
+ wrong: [
30
+ "export const TARGETS: readonly Target[] = [{ id: 'cli_bash', of: 'in_shell', describes: 'The shell on the machine a command runs on.', version: 1, takes: [], requires: [] }]",
31
+ ],
32
+ right: [
33
+ "export const TARGETS: readonly Target[] = [{ id: 'cli_tsc', of: 'in_typescript', describes: 'The compiler this tree is checked and built with.', version: 1, takes: [], requires: [] }]",
34
+ "export const TARGETS: readonly Target[] = [{ id: 'cli_packager', of: 'in_packager', describes: 'The package manager the lockfile beside this tree picks.', version: 1, takes: [], requires: [] }]",
35
+ "export const TARGETS: readonly Target[] = [{ id: 'cli_node', of: 'in_node', describes: 'The runtime this framework runs on.', version: 1, takes: [], requires: [] }]",
36
+ "export const TARGETS: readonly Target[] = [{ id: 'ht_registry', of: 'in_registry', describes: 'The registry every package goes to.', version: 1, takes: [], requires: [], address: 'https://registry.npmjs.org' }]",
37
+ ],
38
+ },
39
+ };
@@ -0,0 +1,40 @@
1
+ import { ROOT } from '../../core/descend.ts'
2
+ import { valuesIn } from '../../core/rows.ts'
3
+ import { kind } from '../../integrations/disk.ts'
4
+ import { REACH, programOf, unlisted } from '../../integrations/target.ts'
5
+
6
+ import type { Slopnator } from '../slopnator.ts'
7
+
8
+ const TARGETS = 'TARGETS'
9
+
10
+ const there = (path: string): boolean => kind(path) === 'file'
11
+
12
+ export const slopnator: Slopnator = {
13
+ holds: (_node, at) => {
14
+ let held: { readonly TARGETS?: readonly { readonly id?: unknown }[] }
15
+ try {
16
+ held = valuesIn(at.text) as { readonly TARGETS?: readonly { readonly id?: unknown }[] }
17
+ } catch (thrown) {
18
+ if (!at.text.includes(TARGETS)) return false
19
+ return 'this sheet names ' + TARGETS + ' and could not be read as values, so no target on it was checked — ' + (thrown instanceof Error ? thrown.message : String(thrown))
20
+ }
21
+ const found: string[] = []
22
+ for (const one of held.TARGETS ?? []) {
23
+ if (typeof one.id !== 'string' || !one.id.startsWith(REACH.cli)) continue
24
+ const name = one.id.slice(REACH.cli.length)
25
+ if (programOf(name, ROOT, there) === undefined) found.push(unlisted(name))
26
+ }
27
+ return found.length === 0 ? false : found.join('. ')
28
+ },
29
+ proves: {
30
+ wrong: [
31
+ "export const TARGETS: readonly Target[] = [{ id: 'cli_bash', of: 'in_shell', describes: 'The shell on the machine a command runs on.', version: 1, takes: [], requires: [] }]",
32
+ ],
33
+ right: [
34
+ "export const TARGETS: readonly Target[] = [{ id: 'cli_tsc', of: 'in_typescript', describes: 'The compiler this tree is checked and built with.', version: 1, takes: [], requires: [] }]",
35
+ "export const TARGETS: readonly Target[] = [{ id: 'cli_packager', of: 'in_packager', describes: 'The package manager the lockfile beside this tree picks.', version: 1, takes: [], requires: [] }]",
36
+ "export const TARGETS: readonly Target[] = [{ id: 'cli_node', of: 'in_node', describes: 'The runtime this framework runs on.', version: 1, takes: [], requires: [] }]",
37
+ "export const TARGETS: readonly Target[] = [{ id: 'ht_registry', of: 'in_registry', describes: 'The registry every package goes to.', version: 1, takes: [], requires: [], address: 'https://registry.npmjs.org' }]",
38
+ ],
39
+ },
40
+ }
@@ -1,4 +1,4 @@
1
- import { declaresIsland, here, islandsDeclared } from "./sl_unthreaded.js";
1
+ import { declaresIsland, here, islandsDeclared } from './sl_unthreaded.js';
2
2
  export const slopnator = {
3
3
  holds: (_node, at) => {
4
4
  const schema = declaresIsland(at.text);