@prestack/thread 0.0.3 → 0.1.1

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 (183) hide show
  1. package/CHANGELOG.md +310 -0
  2. package/dist/core/{asking.d.ts → answers.d.ts} +6 -14
  3. package/dist/core/{asking.js → answers.js} +13 -332
  4. package/dist/core/{asking.ts → answers.ts} +14 -357
  5. package/dist/core/attach.js +2 -2
  6. package/dist/core/attach.ts +2 -3
  7. package/dist/core/chain.js +4 -11
  8. package/dist/core/chain.ts +4 -3
  9. package/dist/core/client.d.ts +57 -0
  10. package/dist/core/client.js +243 -0
  11. package/dist/core/client.ts +279 -0
  12. package/dist/core/descend.d.ts +2 -0
  13. package/dist/core/descend.js +109 -16
  14. package/dist/core/descend.ts +120 -16
  15. package/dist/core/files.d.ts +45 -0
  16. package/dist/core/files.js +145 -0
  17. package/dist/core/files.ts +193 -0
  18. package/dist/core/init.d.ts +1 -0
  19. package/dist/core/init.js +10 -2
  20. package/dist/core/init.ts +8 -2
  21. package/dist/core/invoked.js +1 -1
  22. package/dist/core/invoked.ts +1 -1
  23. package/dist/core/rows.d.ts +2 -0
  24. package/dist/core/rows.js +65 -0
  25. package/dist/core/rows.ts +58 -0
  26. package/dist/core/tree.js +1 -1
  27. package/dist/core/tree.ts +1 -1
  28. package/dist/derivation/attach.js +0 -12
  29. package/dist/derivation/attach.ts +0 -12
  30. package/dist/derivation/generators/build.js +8 -7
  31. package/dist/derivation/generators/build.ts +8 -7
  32. package/dist/derivation/generators/release.d.ts +1 -1
  33. package/dist/derivation/generators/release.js +15 -23
  34. package/dist/derivation/generators/release.ts +15 -15
  35. package/dist/integrations/attach.js +1 -19
  36. package/dist/integrations/attach.ts +1 -19
  37. package/dist/integrations/generators/read.js +2 -2
  38. package/dist/integrations/generators/read.ts +4 -4
  39. package/dist/integrations/generators/vault.js +4 -12
  40. package/dist/integrations/generators/vault.ts +4 -4
  41. package/dist/integrations/module.js +5 -3
  42. package/dist/integrations/module.ts +5 -3
  43. package/dist/integrations/vault.js +5 -5
  44. package/dist/integrations/vault.ts +5 -5
  45. package/dist/integrity/attach.js +0 -12
  46. package/dist/integrity/attach.ts +0 -12
  47. package/dist/integrity/check-threads.js +1 -1
  48. package/dist/integrity/check-threads.ts +1 -1
  49. package/dist/integrity/constraint.d.ts +11 -2
  50. package/dist/integrity/constraint.js +163 -20
  51. package/dist/integrity/constraint.ts +174 -22
  52. package/dist/integrity/generators/compiler.js +7 -1
  53. package/dist/integrity/generators/compiler.ts +10 -1
  54. package/dist/integrity/generators/gate.js +23 -18
  55. package/dist/integrity/generators/gate.ts +29 -9
  56. package/dist/integrity/generators/publish.js +14 -26
  57. package/dist/integrity/generators/publish.ts +14 -17
  58. package/dist/integrity/generators/trial.d.ts +2 -2
  59. package/dist/integrity/generators/trial.js +108 -50
  60. package/dist/integrity/generators/trial.ts +107 -48
  61. package/dist/integrity/lint.js +3 -3
  62. package/dist/integrity/lint.ts +3 -3
  63. package/dist/integrity/lsp/diagnostics.js +1 -1
  64. package/dist/integrity/lsp/diagnostics.ts +1 -1
  65. package/dist/integrity/lsp/lint.js +1 -1
  66. package/dist/integrity/lsp/lint.ts +1 -1
  67. package/dist/integrity/slopnator.d.ts +1 -14
  68. package/dist/integrity/slopnator.js +8 -40
  69. package/dist/integrity/slopnator.ts +9 -51
  70. package/dist/integrity/slopnators/sl_broken_lineage.js +4 -2
  71. package/dist/integrity/slopnators/sl_broken_lineage.ts +4 -2
  72. package/dist/integrity/slopnators/sl_declared_elsewhere.js +24 -3
  73. package/dist/integrity/slopnators/sl_declared_elsewhere.ts +24 -3
  74. package/dist/integrity/slopnators/sl_derived_drift.js +10 -4
  75. package/dist/integrity/slopnators/sl_derived_drift.ts +10 -4
  76. package/dist/integrity/slopnators/sl_inert_island.js +7 -13
  77. package/dist/integrity/slopnators/sl_inert_island.ts +7 -16
  78. package/dist/integrity/slopnators/sl_inert_slopnator.js +8 -8
  79. package/dist/integrity/slopnators/sl_inert_slopnator.ts +8 -8
  80. package/dist/integrity/slopnators/sl_missing_row.js +14 -9
  81. package/dist/integrity/slopnators/sl_missing_row.ts +14 -10
  82. package/dist/integrity/slopnators/sl_no_child_process.js +8 -8
  83. package/dist/integrity/slopnators/sl_no_child_process.ts +8 -8
  84. package/dist/integrity/slopnators/sl_no_import_cycle.js +1 -1
  85. package/dist/integrity/slopnators/sl_no_import_cycle.ts +1 -1
  86. package/dist/integrity/slopnators/sl_no_such_command.js +20 -5
  87. package/dist/integrity/slopnators/sl_no_such_command.ts +22 -5
  88. package/dist/integrity/slopnators/sl_only_takes_a_literal.js +8 -8
  89. package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +8 -8
  90. package/dist/integrity/slopnators/sl_stale_path.js +91 -18
  91. package/dist/integrity/slopnators/sl_stale_path.ts +97 -18
  92. package/dist/integrity/slopnators/sl_twice_meant.js +25 -2
  93. package/dist/integrity/slopnators/sl_twice_meant.ts +28 -2
  94. package/dist/integrity/slopnators/sl_twice_parented.js +26 -5
  95. package/dist/integrity/slopnators/sl_twice_parented.ts +26 -5
  96. package/dist/integrity/slopnators/sl_undeclared_node.js +18 -2
  97. package/dist/integrity/slopnators/sl_undeclared_node.ts +18 -2
  98. package/dist/integrity/slopnators/sl_undeclared_operation.js +3 -1
  99. package/dist/integrity/slopnators/sl_undeclared_operation.ts +3 -1
  100. package/dist/integrity/slopnators/sl_undeclared_variant.js +6 -2
  101. package/dist/integrity/slopnators/sl_undeclared_variant.ts +6 -2
  102. package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -4
  103. package/dist/integrity/slopnators/sl_ungoverned_island.ts +3 -4
  104. package/dist/integrity/slopnators/sl_unkept_rows.js +1 -1
  105. package/dist/integrity/slopnators/sl_unkept_rows.ts +1 -1
  106. package/dist/integrity/slopnators/sl_unpaired_member.js +13 -8
  107. package/dist/integrity/slopnators/sl_unpaired_member.ts +16 -8
  108. package/dist/integrity/slopnators/sl_unpaired_slopnator.js +16 -3
  109. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +16 -3
  110. package/dist/integrity/slopnators/sl_unthreaded.d.ts +1 -0
  111. package/dist/integrity/slopnators/sl_unthreaded.js +18 -10
  112. package/dist/integrity/slopnators/sl_unthreaded.ts +19 -11
  113. package/dist/integrity/slopnators/sl_untranslated_key.js +1 -1
  114. package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -1
  115. package/dist/integrity/slopnators/sl_unversioned.js +16 -2
  116. package/dist/integrity/slopnators/sl_unversioned.ts +16 -2
  117. package/dist/integrity/sweep.js +17 -3
  118. package/dist/integrity/sweep.ts +20 -3
  119. package/dist/knowledge/attach.js +0 -6
  120. package/dist/knowledge/attach.ts +0 -6
  121. package/dist/knowledge/generators/docsgen.js +12 -3
  122. package/dist/knowledge/generators/docsgen.ts +12 -2
  123. package/dist/knowledge/generators/glossary.js +1 -1
  124. package/dist/knowledge/generators/glossary.ts +1 -1
  125. package/dist/operations/attach.js +2 -26
  126. package/dist/operations/attach.ts +2 -27
  127. package/dist/operations/dispatch.js +2 -3
  128. package/dist/operations/dispatch.ts +2 -3
  129. package/dist/operations/generators/capability.js +4 -7
  130. package/dist/operations/generators/capability.ts +3 -6
  131. package/dist/operations/generators/compile.js +8 -13
  132. package/dist/operations/generators/compile.ts +8 -4
  133. package/dist/operations/generators/permits.js +4 -13
  134. package/dist/operations/generators/permits.ts +3 -3
  135. package/dist/operations/generators/seed.js +5 -2
  136. package/dist/operations/generators/seed.ts +6 -2
  137. package/dist/operations/generators/trial.js +10 -10
  138. package/dist/operations/generators/trial.ts +10 -10
  139. package/dist/operations/http.js +4 -1
  140. package/dist/operations/http.ts +6 -1
  141. package/package.json +2 -2
  142. package/dist/core/cli.d.ts +0 -2
  143. package/dist/core/cli.js +0 -1761
  144. package/dist/core/cli.ts +0 -2004
  145. package/dist/core/generators/meta.d.ts +0 -2
  146. package/dist/core/generators/meta.js +0 -829
  147. package/dist/core/generators/meta.ts +0 -1006
  148. package/dist/derivation/generators/derivation.d.ts +0 -3
  149. package/dist/derivation/generators/derivation.js +0 -273
  150. package/dist/derivation/generators/derivation.ts +0 -347
  151. package/dist/derivation/makers/release.d.ts +0 -1
  152. package/dist/derivation/makers/release.js +0 -107
  153. package/dist/derivation/makers/release.ts +0 -135
  154. package/dist/integrations/generators/secret.d.ts +0 -1
  155. package/dist/integrations/generators/secret.js +0 -233
  156. package/dist/integrations/generators/secret.ts +0 -289
  157. package/dist/integrations/generators/target.d.ts +0 -1
  158. package/dist/integrations/generators/target.js +0 -775
  159. package/dist/integrations/generators/target.ts +0 -944
  160. package/dist/integrity/generators/rule.d.ts +0 -2
  161. package/dist/integrity/generators/rule.js +0 -213
  162. package/dist/integrity/generators/rule.ts +0 -271
  163. package/dist/integrity/generators/seal.d.ts +0 -4
  164. package/dist/integrity/generators/seal.js +0 -101
  165. package/dist/integrity/generators/seal.ts +0 -117
  166. package/dist/integrity/sealed.d.ts +0 -1
  167. package/dist/integrity/sealed.js +0 -56
  168. package/dist/integrity/sealed.ts +0 -56
  169. package/dist/knowledge/generators/skills.d.ts +0 -1
  170. package/dist/knowledge/generators/skills.js +0 -222
  171. package/dist/knowledge/generators/skills.ts +0 -263
  172. package/dist/operations/generators/grant.d.ts +0 -1
  173. package/dist/operations/generators/grant.js +0 -267
  174. package/dist/operations/generators/grant.ts +0 -336
  175. package/dist/operations/generators/operation.d.ts +0 -1
  176. package/dist/operations/generators/operation.js +0 -811
  177. package/dist/operations/generators/operation.ts +0 -972
  178. package/dist/operations/makers/capability.d.ts +0 -1
  179. package/dist/operations/makers/capability.js +0 -117
  180. package/dist/operations/makers/capability.ts +0 -166
  181. package/dist/operations/makers/trial.d.ts +0 -1
  182. package/dist/operations/makers/trial.js +0 -413
  183. package/dist/operations/makers/trial.ts +0 -520
@@ -1,5 +1,5 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs';
2
+ import { attached, existsSync, kind, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "./files.js";
3
3
  import { dirname, join, relative, resolve } from 'node:path';
4
4
  import ts from 'typescript';
5
5
  import { ignored } from "./init.js";
@@ -26,12 +26,17 @@ function rooted() {
26
26
  export const ROOT = TREE_AT?.at ?? NOWHERE;
27
27
  export const TREE = `${TREE_AT?.name ?? NOWHERE}/`;
28
28
  export const THREAD_AT = `${TREE_AT?.name ?? NOWHERE}/thread.ts`;
29
- const OWN_IGNORES = (() => {
30
- if (spawnSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: ROOT, stdio: 'ignore' }).status === 0) {
31
- return undefined;
29
+ let ignores;
30
+ let readIgnores = false;
31
+ function ownIgnores() {
32
+ if (readIgnores)
33
+ return ignores;
34
+ readIgnores = true;
35
+ if (!attached() && spawnSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: ROOT, stdio: 'ignore' }).status === 0) {
36
+ return ignores;
32
37
  }
33
38
  try {
34
- return readFileSync(join(ROOT, '.gitignore'), 'utf8')
39
+ ignores = readFileSync(join(ROOT, '.gitignore'), 'utf8')
35
40
  .split('\n')
36
41
  .map((one) => one.trim())
37
42
  .filter((one) => one !== '' && !one.startsWith('#') && !one.startsWith('!'))
@@ -39,21 +44,24 @@ const OWN_IGNORES = (() => {
39
44
  .filter((one) => one !== '' && !one.includes('*'));
40
45
  }
41
46
  catch {
42
- return [];
47
+ ignores = [];
43
48
  }
44
- })();
49
+ return ignores;
50
+ }
45
51
  const asked = new Map();
46
52
  export function unkept(at) {
47
53
  const held = asked.get(at);
48
54
  if (held !== undefined)
49
55
  return held;
50
- const found = OWN_IGNORES === undefined
56
+ const own = ownIgnores();
57
+ const found = own === undefined
51
58
  ? spawnSync('git', ['check-ignore', '-q', '--', at], { cwd: ROOT, stdio: 'ignore' }).status === 0
52
- : OWN_IGNORES.some((one) => at === one || at.startsWith(`${one}/`) || at.split('/').includes(one));
59
+ : own.some((one) => at === one || at.startsWith(`${one}/`) || at.split('/').includes(one));
53
60
  asked.set(at, found);
54
61
  return found;
55
62
  }
56
63
  export const LISTS = ['declares', 'derives', 'seeds'];
64
+ export const ROW_LISTS = [...LISTS, 'holds'];
57
65
  const SOURCE = /\.(ts|tsx|mts|cts)$/;
58
66
  const RUNNER = /^(?:pnpm|npm|yarn) (?:run )?([\w:.-]+)/;
59
67
  export function under(pass, id, root, seen = new Set()) {
@@ -148,12 +156,67 @@ const INVARIANT = {
148
156
  allowJs: true,
149
157
  jsx: ts.JsxEmit.ReactJSX,
150
158
  };
159
+ const NEVER = ['node_modules', 'bower_components', 'jspm_packages'];
160
+ function bounds(from, one) {
161
+ const star = one.indexOf('*');
162
+ if (star === -1)
163
+ return { under: resolve(from, one), ending: '' };
164
+ const head = one.slice(0, star);
165
+ const cut = head.lastIndexOf('/');
166
+ const tail = one.slice(star);
167
+ const dot = tail.lastIndexOf('.');
168
+ const end = dot === -1 ? '' : tail.slice(dot);
169
+ return {
170
+ under: resolve(from, cut === -1 ? '' : head.slice(0, cut)),
171
+ ending: end.includes('/') || end.includes('*') ? '' : end,
172
+ };
173
+ }
174
+ function wanted(at, only) {
175
+ if (only.length === 0)
176
+ return true;
177
+ return only.some((one) => (at === one.under || at.startsWith(`${one.under}/`)) && at.endsWith(one.ending));
178
+ }
179
+ function listing(at, take, skip, only, deep) {
180
+ const found = [];
181
+ if (deep < 0 || !existsSync(at))
182
+ return found;
183
+ for (const one of readdirSync(at, { withFileTypes: true })) {
184
+ const full = join(at, one.name);
185
+ if (skip.some((each) => full === each || full.startsWith(`${each}/`)))
186
+ continue;
187
+ if (one.isDirectory()) {
188
+ if (NEVER.includes(one.name))
189
+ continue;
190
+ found.push(...listing(full, take, skip, only, deep - 1));
191
+ }
192
+ else if (take.some((each) => one.name.endsWith(each)) && wanted(full, only)) {
193
+ found.push(full);
194
+ }
195
+ }
196
+ return found;
197
+ }
198
+ function parsing(from) {
199
+ return {
200
+ useCaseSensitiveFileNames: true,
201
+ fileExists: (at) => existsSync(at),
202
+ readFile: (at) => {
203
+ try {
204
+ return readFileSync(at);
205
+ }
206
+ catch {
207
+ return undefined;
208
+ }
209
+ },
210
+ readDirectory: (at, take, skip, only, deep) => listing(at, take, (skip ?? []).map((one) => resolve(from, one.endsWith('/**') ? one.slice(0, -3) : one)), only.map((one) => bounds(from, one)), deep ?? Number.MAX_SAFE_INTEGER),
211
+ };
212
+ }
151
213
  export function configured() {
152
214
  for (const name of CONFIGS) {
153
215
  const at = join(ROOT, name);
154
- if (!ts.sys.fileExists(at))
216
+ if (!existsSync(at))
155
217
  continue;
156
- const held = ts.readConfigFile(at, ts.sys.readFile.bind(ts.sys));
218
+ const host = parsing(ROOT);
219
+ const held = ts.readConfigFile(at, host.readFile);
157
220
  if (held.error !== undefined) {
158
221
  return {
159
222
  options: { ...FALLBACK, ...INVARIANT },
@@ -161,7 +224,7 @@ export function configured() {
161
224
  trouble: `${name} is there and cannot be read: ${ts.flattenDiagnosticMessageText(held.error.messageText, ' ')}`,
162
225
  };
163
226
  }
164
- const parsed = ts.parseJsonConfigFileContent(held.config, ts.sys, dirname(at));
227
+ const parsed = ts.parseJsonConfigFileContent(held.config, host, dirname(at));
165
228
  const complained = parsed.errors[0];
166
229
  if (complained !== undefined) {
167
230
  return {
@@ -174,13 +237,37 @@ export function configured() {
174
237
  }
175
238
  return { options: { ...FALLBACK, ...INVARIANT }, from: '' };
176
239
  }
240
+ export function hosting(options) {
241
+ const host = ts.createCompilerHost(options, true);
242
+ const lib = host.getDefaultLibLocation?.();
243
+ const toolchain = (at) => lib !== undefined && (at === lib || at.startsWith(`${lib}/`));
244
+ return {
245
+ ...host,
246
+ fileExists: (at) => (toolchain(at) ? host.fileExists(at) : kind(at) === 'file'),
247
+ directoryExists: (at) => (toolchain(at) ? (host.directoryExists?.(at) ?? false) : kind(at) === 'folder'),
248
+ readFile: (at) => (toolchain(at) ? host.readFile(at) : kind(at) === 'file' ? readFileSync(at) : undefined),
249
+ getSourceFile: (at, version) => {
250
+ if (toolchain(at))
251
+ return host.getSourceFile(at, version);
252
+ if (kind(at) !== 'file')
253
+ return undefined;
254
+ return ts.createSourceFile(at, readFileSync(at), version, true);
255
+ },
256
+ getDirectories: (at) => kind(at) === 'folder' ? readdirSync(at, { withFileTypes: true }).filter((one) => one.isDirectory()).map((one) => one.name) : [],
257
+ realpath: (at) => (toolchain(at) ? (host.realpath?.(at) ?? at) : realpathSync(at)),
258
+ writeFile: (at, said) => {
259
+ writeFileSync(at, said);
260
+ },
261
+ };
262
+ }
177
263
  let built;
178
264
  function compiled() {
179
265
  rooted();
180
266
  if (built !== undefined)
181
267
  return built;
182
268
  const roots = [THREAD_AT, ...sources(), ...shipped()].filter((one) => SOURCE.test(one)).map((one) => resolve(ROOT, one));
183
- const program = ts.createProgram([...new Set(roots)], configured().options);
269
+ const options = configured().options;
270
+ const program = ts.createProgram([...new Set(roots)], options, hosting(options));
184
271
  built = { program, checker: program.getTypeChecker() };
185
272
  return built;
186
273
  }
@@ -585,6 +672,9 @@ export function forget() {
585
672
  scanned = undefined;
586
673
  commands = undefined;
587
674
  source.clear();
675
+ asked.clear();
676
+ ignores = undefined;
677
+ readIgnores = false;
588
678
  }
589
679
  export function descend() {
590
680
  rooted();
@@ -762,7 +852,10 @@ export function descend() {
762
852
  continue;
763
853
  }
764
854
  for (const one of value.elements) {
765
- if (!ts.isObjectLiteralExpression(one)) {
855
+ const declaration = ts.isIdentifier(one) ? checker.getSymbolAtLocation(one)?.valueDeclaration : undefined;
856
+ const initial = declaration !== undefined && ts.isVariableDeclaration(declaration) ? unwrap(declaration.initializer) : undefined;
857
+ const object = ts.isObjectLiteralExpression(one) ? one : initial !== undefined && ts.isObjectLiteralExpression(initial) ? initial : undefined;
858
+ if (object === undefined) {
766
859
  findings.push({
767
860
  file: at,
768
861
  detail: `\`${by}\` holds something in \`${list}\` that is not a row written out here`,
@@ -773,7 +866,7 @@ export function descend() {
773
866
  rows += 1;
774
867
  const wanted = checker.getContextualType(one);
775
868
  const kind = wanted === undefined ? undefined : elementOf(checker, wanted);
776
- const row = rowOf(one, kind === undefined ? type : checker.typeToString(kind), at, kind);
869
+ const row = rowOf(object, kind === undefined ? type : checker.typeToString(kind), at, kind);
777
870
  coin(row.id, row.describes, under, by);
778
871
  judge(row, by, list, false);
779
872
  if (row.at !== undefined) {
@@ -782,7 +875,7 @@ export function descend() {
782
875
  derived.set(row.id, row);
783
876
  }
784
877
  if (LISTS.some((name) => row.fields.has(name)))
785
- into(one, row.type, at, under, by, true);
878
+ into(object, row.type, at, under, by, true);
786
879
  if (row.at !== undefined)
787
880
  open(row.at, wanted === undefined ? undefined : elementOf(checker, wanted), under, row.id ?? by, by);
788
881
  }
@@ -1,5 +1,5 @@
1
1
  import { spawnSync } from 'node:child_process'
2
- import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs'
2
+ import { attached, existsSync, kind, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from './files.ts'
3
3
  import { dirname, join, relative, resolve } from 'node:path'
4
4
 
5
5
  import ts from 'typescript'
@@ -35,37 +35,46 @@ export const ROOT = TREE_AT?.at ?? NOWHERE
35
35
  export const TREE = `${TREE_AT?.name ?? NOWHERE}/`
36
36
  export const THREAD_AT = `${TREE_AT?.name ?? NOWHERE}/thread.ts`
37
37
 
38
- const OWN_IGNORES: readonly string[] | undefined = (() => {
39
- if (spawnSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: ROOT, stdio: 'ignore' }).status === 0) {
40
- return undefined
38
+ let ignores: readonly string[] | undefined
39
+ let readIgnores = false
40
+
41
+ function ownIgnores(): readonly string[] | undefined {
42
+ if (readIgnores) return ignores
43
+ readIgnores = true
44
+ if (!attached() && spawnSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: ROOT, stdio: 'ignore' }).status === 0) {
45
+ return ignores
41
46
  }
42
47
  try {
43
- return readFileSync(join(ROOT, '.gitignore'), 'utf8')
48
+ ignores = readFileSync(join(ROOT, '.gitignore'), 'utf8')
44
49
  .split('\n')
45
50
  .map((one) => one.trim())
46
51
  .filter((one) => one !== '' && !one.startsWith('#') && !one.startsWith('!'))
47
52
  .map((one) => one.replace(/^\/+|\/+$/g, ''))
48
53
  .filter((one) => one !== '' && !one.includes('*'))
49
54
  } catch {
50
- return []
55
+ ignores = []
51
56
  }
52
- })()
57
+ return ignores
58
+ }
53
59
 
54
60
  const asked = new Map<string, boolean>()
55
61
 
56
62
  export function unkept(at: string): boolean {
57
63
  const held = asked.get(at)
58
64
  if (held !== undefined) return held
65
+ const own = ownIgnores()
59
66
  const found =
60
- OWN_IGNORES === undefined
67
+ own === undefined
61
68
  ? spawnSync('git', ['check-ignore', '-q', '--', at], { cwd: ROOT, stdio: 'ignore' }).status === 0
62
- : OWN_IGNORES.some((one) => at === one || at.startsWith(`${one}/`) || at.split('/').includes(one))
69
+ : own.some((one) => at === one || at.startsWith(`${one}/`) || at.split('/').includes(one))
63
70
  asked.set(at, found)
64
71
  return found
65
72
  }
66
73
 
67
74
  export const LISTS = ['declares', 'derives', 'seeds'] as const
68
75
 
76
+ export const ROW_LISTS = [...LISTS, 'holds'] as const
77
+
69
78
  const SOURCE = /\.(ts|tsx|mts|cts)$/
70
79
  const RUNNER = /^(?:pnpm|npm|yarn) (?:run )?([\w:.-]+)/
71
80
 
@@ -198,12 +207,77 @@ const INVARIANT: ts.CompilerOptions = {
198
207
  jsx: ts.JsxEmit.ReactJSX,
199
208
  }
200
209
 
210
+ const NEVER: readonly string[] = ['node_modules', 'bower_components', 'jspm_packages']
211
+
212
+ interface Wanted {
213
+ readonly under: string
214
+ readonly ending: string
215
+ }
216
+
217
+ function bounds(from: string, one: string): Wanted {
218
+ const star = one.indexOf('*')
219
+ if (star === -1) return { under: resolve(from, one), ending: '' }
220
+ const head = one.slice(0, star)
221
+ const cut = head.lastIndexOf('/')
222
+ const tail = one.slice(star)
223
+ const dot = tail.lastIndexOf('.')
224
+ const end = dot === -1 ? '' : tail.slice(dot)
225
+ return {
226
+ under: resolve(from, cut === -1 ? '' : head.slice(0, cut)),
227
+ ending: end.includes('/') || end.includes('*') ? '' : end,
228
+ }
229
+ }
230
+
231
+ function wanted(at: string, only: readonly Wanted[]): boolean {
232
+ if (only.length === 0) return true
233
+ return only.some((one) => (at === one.under || at.startsWith(`${one.under}/`)) && at.endsWith(one.ending))
234
+ }
235
+
236
+ function listing(at: string, take: readonly string[], skip: readonly string[], only: readonly Wanted[], deep: number): readonly string[] {
237
+ const found: string[] = []
238
+ if (deep < 0 || !existsSync(at)) return found
239
+ for (const one of readdirSync(at, { withFileTypes: true })) {
240
+ const full = join(at, one.name)
241
+ if (skip.some((each) => full === each || full.startsWith(`${each}/`))) continue
242
+ if (one.isDirectory()) {
243
+ if (NEVER.includes(one.name)) continue
244
+ found.push(...listing(full, take, skip, only, deep - 1))
245
+ } else if (take.some((each) => one.name.endsWith(each)) && wanted(full, only)) {
246
+ found.push(full)
247
+ }
248
+ }
249
+ return found
250
+ }
251
+
252
+ function parsing(from: string): ts.ParseConfigHost {
253
+ return {
254
+ useCaseSensitiveFileNames: true,
255
+ fileExists: (at) => existsSync(at),
256
+ readFile: (at) => {
257
+ try {
258
+ return readFileSync(at)
259
+ } catch {
260
+ return undefined
261
+ }
262
+ },
263
+ readDirectory: (at, take, skip, only, deep) =>
264
+ listing(
265
+ at,
266
+ take,
267
+ (skip ?? []).map((one) => resolve(from, one.endsWith('/**') ? one.slice(0, -3) : one)),
268
+ only.map((one) => bounds(from, one)),
269
+ deep ?? Number.MAX_SAFE_INTEGER,
270
+ ),
271
+ }
272
+ }
273
+
201
274
  export function configured(): { readonly options: ts.CompilerOptions; readonly from: string; readonly trouble?: string } {
202
275
  for (const name of CONFIGS) {
203
276
  const at = join(ROOT, name)
204
- if (!ts.sys.fileExists(at)) continue
277
+ if (!existsSync(at)) continue
205
278
 
206
- const held = ts.readConfigFile(at, ts.sys.readFile.bind(ts.sys))
279
+ const host = parsing(ROOT)
280
+ const held = ts.readConfigFile(at, host.readFile)
207
281
  if (held.error !== undefined) {
208
282
  return {
209
283
  options: { ...FALLBACK, ...INVARIANT },
@@ -212,7 +286,7 @@ export function configured(): { readonly options: ts.CompilerOptions; readonly f
212
286
  }
213
287
  }
214
288
 
215
- const parsed = ts.parseJsonConfigFileContent(held.config as object, ts.sys, dirname(at))
289
+ const parsed = ts.parseJsonConfigFileContent(held.config as object, host, dirname(at))
216
290
  const complained = parsed.errors[0]
217
291
  if (complained !== undefined) {
218
292
  return {
@@ -227,13 +301,37 @@ export function configured(): { readonly options: ts.CompilerOptions; readonly f
227
301
  return { options: { ...FALLBACK, ...INVARIANT }, from: '' }
228
302
  }
229
303
 
304
+ export function hosting(options: ts.CompilerOptions): ts.CompilerHost {
305
+ const host = ts.createCompilerHost(options, true)
306
+ const lib = host.getDefaultLibLocation?.()
307
+ const toolchain = (at: string): boolean => lib !== undefined && (at === lib || at.startsWith(`${lib}/`))
308
+ return {
309
+ ...host,
310
+ fileExists: (at) => (toolchain(at) ? host.fileExists(at) : kind(at) === 'file'),
311
+ directoryExists: (at) => (toolchain(at) ? (host.directoryExists?.(at) ?? false) : kind(at) === 'folder'),
312
+ readFile: (at) => (toolchain(at) ? host.readFile(at) : kind(at) === 'file' ? readFileSync(at) : undefined),
313
+ getSourceFile: (at, version) => {
314
+ if (toolchain(at)) return host.getSourceFile(at, version)
315
+ if (kind(at) !== 'file') return undefined
316
+ return ts.createSourceFile(at, readFileSync(at), version, true)
317
+ },
318
+ getDirectories: (at) =>
319
+ kind(at) === 'folder' ? readdirSync(at, { withFileTypes: true }).filter((one) => one.isDirectory()).map((one) => one.name) : [],
320
+ realpath: (at) => (toolchain(at) ? (host.realpath?.(at) ?? at) : realpathSync(at)),
321
+ writeFile: (at, said) => {
322
+ writeFileSync(at, said)
323
+ },
324
+ }
325
+ }
326
+
230
327
  let built: { program: ts.Program; checker: ts.TypeChecker } | undefined
231
328
 
232
329
  function compiled(): { program: ts.Program; checker: ts.TypeChecker } {
233
330
  rooted()
234
331
  if (built !== undefined) return built
235
332
  const roots = [THREAD_AT, ...sources(), ...shipped()].filter((one) => SOURCE.test(one)).map((one) => resolve(ROOT, one))
236
- const program = ts.createProgram([...new Set(roots)], configured().options)
333
+ const options = configured().options
334
+ const program = ts.createProgram([...new Set(roots)], options, hosting(options))
237
335
  built = { program, checker: program.getTypeChecker() }
238
336
  return built
239
337
  }
@@ -659,6 +757,9 @@ export function forget(): void {
659
757
  scanned = undefined
660
758
  commands = undefined
661
759
  source.clear()
760
+ asked.clear()
761
+ ignores = undefined
762
+ readIgnores = false
662
763
  }
663
764
 
664
765
  export function descend(): Pass {
@@ -846,7 +947,10 @@ export function descend(): Pass {
846
947
  }
847
948
 
848
949
  for (const one of value.elements) {
849
- if (!ts.isObjectLiteralExpression(one)) {
950
+ const declaration = ts.isIdentifier(one) ? checker.getSymbolAtLocation(one)?.valueDeclaration : undefined
951
+ const initial = declaration !== undefined && ts.isVariableDeclaration(declaration) ? unwrap(declaration.initializer) : undefined
952
+ const object = ts.isObjectLiteralExpression(one) ? one : initial !== undefined && ts.isObjectLiteralExpression(initial) ? initial : undefined
953
+ if (object === undefined) {
850
954
  findings.push({
851
955
  file: at,
852
956
  detail: `\`${by}\` holds something in \`${list}\` that is not a row written out here`,
@@ -857,14 +961,14 @@ export function descend(): Pass {
857
961
  rows += 1
858
962
  const wanted = checker.getContextualType(one)
859
963
  const kind = wanted === undefined ? undefined : elementOf(checker, wanted)
860
- const row = rowOf(one, kind === undefined ? type : checker.typeToString(kind), at, kind)
964
+ const row = rowOf(object, kind === undefined ? type : checker.typeToString(kind), at, kind)
861
965
  coin(row.id, row.describes, under, by)
862
966
  judge(row, by, list, false)
863
967
  if (row.at !== undefined) {
864
968
  threaded.set(row.at, [...(threaded.get(row.at) ?? []), { list, by, row }])
865
969
  if (list === 'derives' && row.id !== undefined) derived.set(row.id, row)
866
970
  }
867
- if (LISTS.some((name) => row.fields.has(name))) into(one, row.type, at, under, by, true)
971
+ if (LISTS.some((name) => row.fields.has(name))) into(object, row.type, at, under, by, true)
868
972
  if (row.at !== undefined) open(row.at, wanted === undefined ? undefined : elementOf(checker, wanted), under, row.id ?? by, by)
869
973
  }
870
974
  }
@@ -0,0 +1,45 @@
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
+ }
20
+ export declare function answeredBy(one: Answering | undefined): void;
21
+ export declare function attached(): boolean;
22
+ export declare function kind(at: string): Kind;
23
+ export declare function existsSync(at: disk.PathLike): boolean;
24
+ export declare function readFileSync(at: disk.PathOrFileDescriptor, how?: BufferEncoding): string;
25
+ export declare function writeFileSync(at: disk.PathOrFileDescriptor, said: string, how?: BufferEncoding): void;
26
+ export interface Named {
27
+ readonly name: string;
28
+ readonly isFile: () => boolean;
29
+ readonly isDirectory: () => boolean;
30
+ }
31
+ export declare function readdirSync(at: disk.PathLike): string[];
32
+ export declare function readdirSync(at: disk.PathLike, how: {
33
+ readonly withFileTypes: true;
34
+ }): readonly Named[];
35
+ export interface Held extends Named, Measured {
36
+ readonly mtimeMs: number;
37
+ }
38
+ export declare function statSync(at: disk.PathLike): Held;
39
+ export declare function statSync(at: disk.PathLike, how: {
40
+ readonly throwIfNoEntry: false;
41
+ }): Held | undefined;
42
+ export declare function rmSync(at: disk.PathLike, how?: disk.RmOptions): void;
43
+ export declare function renameSync(from: disk.PathLike, to: disk.PathLike): void;
44
+ export declare function appendFileSync(at: disk.PathOrFileDescriptor, said: string): void;
45
+ export declare function realpathSync(at: disk.PathLike): string;
@@ -0,0 +1,145 @@
1
+ import * as disk from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ const CHANNEL = 'THREAD_CHANNEL';
4
+ const WAITING = 180000;
5
+ function overFiles(dir) {
6
+ const idle = new Int32Array(new SharedArrayBuffer(4));
7
+ let spun = 0;
8
+ const ask = (body) => {
9
+ spun += 1;
10
+ const name = `${dir}/${String(process.pid)}-${String(spun)}`;
11
+ disk.writeFileSync(`${name}.asking`, JSON.stringify(body));
12
+ disk.renameSync(`${name}.asking`, `${name}.order`);
13
+ const until = Date.now() + WAITING;
14
+ for (;;) {
15
+ let held;
16
+ try {
17
+ held = disk.readFileSync(`${name}.answer`, 'utf8');
18
+ }
19
+ catch {
20
+ held = undefined;
21
+ }
22
+ if (held !== undefined) {
23
+ disk.rmSync(`${name}.answer`, { force: true });
24
+ const said = JSON.parse(held);
25
+ if (said.answered === 'refused')
26
+ throw new Error(String(said.because));
27
+ return said;
28
+ }
29
+ if (Date.now() > until)
30
+ throw new Error('nothing answered ' +
31
+ String(body.order) +
32
+ ' ' +
33
+ String(body.at) +
34
+ ' for three minutes, so this stopped waiting rather than spinning forever. Whoever was answering is gone. It waits longer than a server waits before giving up on a run, on purpose: if an answer were still coming it would have arrived. Nothing here is half-written — one order is one file, and it was either answered or it was not.');
35
+ Atomics.wait(idle, 0, 0, 2);
36
+ }
37
+ };
38
+ return {
39
+ kind: (at) => ask({ order: 'kind', at }).held,
40
+ read: (at) => ask({ order: 'read', at }).said,
41
+ write: (at, said) => {
42
+ ask({ order: 'write', at, said });
43
+ },
44
+ list: (at) => ask({ order: 'list', at }).held,
45
+ stat: (at) => ask({ order: 'stat', at }).held,
46
+ remove: (at) => {
47
+ ask({ order: 'remove', at });
48
+ },
49
+ real: (at) => ask({ order: 'real', at }).at,
50
+ };
51
+ }
52
+ const WHERE = process.env[CHANNEL];
53
+ let answering = WHERE !== undefined && WHERE !== '' ? overFiles(WHERE) : undefined;
54
+ export function answeredBy(one) {
55
+ answering = one;
56
+ }
57
+ const asked = (at) => (typeof at === 'string' ? answering : undefined);
58
+ export function attached() {
59
+ return answering !== undefined;
60
+ }
61
+ export function kind(at) {
62
+ const one = answering;
63
+ if (one !== undefined)
64
+ return one.kind(at);
65
+ const held = disk.statSync(at, { throwIfNoEntry: false });
66
+ if (held === undefined)
67
+ return 'none';
68
+ return held.isFile() ? 'file' : held.isDirectory() ? 'folder' : 'other';
69
+ }
70
+ export function existsSync(at) {
71
+ return typeof at === 'string' ? kind(at) !== 'none' : disk.existsSync(at);
72
+ }
73
+ export function readFileSync(at, how) {
74
+ const one = asked(at);
75
+ return one === undefined ? disk.readFileSync(at, how ?? 'utf8') : one.read(at);
76
+ }
77
+ export function writeFileSync(at, said, how) {
78
+ const one = asked(at);
79
+ if (one !== undefined) {
80
+ one.write(at, said);
81
+ return;
82
+ }
83
+ if (typeof at === 'string')
84
+ disk.mkdirSync(dirname(at), { recursive: true });
85
+ disk.writeFileSync(at, said, how ?? 'utf8');
86
+ }
87
+ export function readdirSync(at, how) {
88
+ const one = asked(at);
89
+ if (one === undefined)
90
+ return how === undefined ? disk.readdirSync(at) : disk.readdirSync(at, how);
91
+ const held = one.list(at);
92
+ return how === undefined
93
+ ? held.map((each) => each.name)
94
+ : held.map((each) => ({ name: each.name, isFile: () => each.kind === 'file', isDirectory: () => each.kind === 'folder' }));
95
+ }
96
+ export function statSync(at, how) {
97
+ const one = asked(at);
98
+ if (one === undefined)
99
+ return (how === undefined ? disk.statSync(at) : disk.statSync(at, how));
100
+ const path = at;
101
+ const held = one.kind(path);
102
+ if (held === 'none') {
103
+ if (how !== undefined)
104
+ return undefined;
105
+ throw new Error(`${path} is not there, so nothing can be said about it.`);
106
+ }
107
+ const measured = one.stat(path);
108
+ return {
109
+ name: path,
110
+ size: measured.size,
111
+ modified: measured.modified,
112
+ mtimeMs: measured.modified,
113
+ isFile: () => held === 'file',
114
+ isDirectory: () => held === 'folder',
115
+ };
116
+ }
117
+ export function rmSync(at, how) {
118
+ const one = asked(at);
119
+ if (one === undefined)
120
+ disk.rmSync(at, how);
121
+ else
122
+ one.remove(at);
123
+ }
124
+ export function renameSync(from, to) {
125
+ const one = asked(from);
126
+ if (one === undefined) {
127
+ disk.renameSync(from, to);
128
+ return;
129
+ }
130
+ one.write(to, one.read(from));
131
+ one.remove(from);
132
+ }
133
+ export function appendFileSync(at, said) {
134
+ const one = asked(at);
135
+ if (one === undefined) {
136
+ disk.appendFileSync(at, said);
137
+ return;
138
+ }
139
+ const path = at;
140
+ one.write(path, `${one.kind(path) === 'none' ? '' : one.read(path)}${said}`);
141
+ }
142
+ export function realpathSync(at) {
143
+ const one = asked(at);
144
+ return one === undefined ? disk.realpathSync(at) : one.real(at);
145
+ }