@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.
- package/CHANGELOG.md +310 -0
- package/dist/core/{asking.d.ts → answers.d.ts} +6 -14
- package/dist/core/{asking.js → answers.js} +13 -332
- package/dist/core/{asking.ts → answers.ts} +14 -357
- package/dist/core/attach.js +2 -2
- package/dist/core/attach.ts +2 -3
- package/dist/core/chain.js +4 -11
- package/dist/core/chain.ts +4 -3
- package/dist/core/client.d.ts +57 -0
- package/dist/core/client.js +243 -0
- package/dist/core/client.ts +279 -0
- package/dist/core/descend.d.ts +2 -0
- package/dist/core/descend.js +109 -16
- package/dist/core/descend.ts +120 -16
- package/dist/core/files.d.ts +45 -0
- package/dist/core/files.js +145 -0
- package/dist/core/files.ts +193 -0
- package/dist/core/init.d.ts +1 -0
- package/dist/core/init.js +10 -2
- package/dist/core/init.ts +8 -2
- package/dist/core/invoked.js +1 -1
- package/dist/core/invoked.ts +1 -1
- package/dist/core/rows.d.ts +2 -0
- package/dist/core/rows.js +65 -0
- package/dist/core/rows.ts +58 -0
- package/dist/core/tree.js +1 -1
- package/dist/core/tree.ts +1 -1
- package/dist/derivation/attach.js +0 -12
- package/dist/derivation/attach.ts +0 -12
- package/dist/derivation/generators/build.js +8 -7
- package/dist/derivation/generators/build.ts +8 -7
- package/dist/derivation/generators/release.d.ts +1 -1
- package/dist/derivation/generators/release.js +15 -23
- package/dist/derivation/generators/release.ts +15 -15
- package/dist/integrations/attach.js +1 -19
- package/dist/integrations/attach.ts +1 -19
- package/dist/integrations/generators/read.js +2 -2
- package/dist/integrations/generators/read.ts +4 -4
- package/dist/integrations/generators/vault.js +4 -12
- package/dist/integrations/generators/vault.ts +4 -4
- package/dist/integrations/module.js +5 -3
- package/dist/integrations/module.ts +5 -3
- package/dist/integrations/vault.js +5 -5
- package/dist/integrations/vault.ts +5 -5
- package/dist/integrity/attach.js +0 -12
- package/dist/integrity/attach.ts +0 -12
- package/dist/integrity/check-threads.js +1 -1
- package/dist/integrity/check-threads.ts +1 -1
- package/dist/integrity/constraint.d.ts +11 -2
- package/dist/integrity/constraint.js +163 -20
- package/dist/integrity/constraint.ts +174 -22
- package/dist/integrity/generators/compiler.js +7 -1
- package/dist/integrity/generators/compiler.ts +10 -1
- package/dist/integrity/generators/gate.js +23 -18
- package/dist/integrity/generators/gate.ts +29 -9
- package/dist/integrity/generators/publish.js +14 -26
- package/dist/integrity/generators/publish.ts +14 -17
- package/dist/integrity/generators/trial.d.ts +2 -2
- package/dist/integrity/generators/trial.js +108 -50
- package/dist/integrity/generators/trial.ts +107 -48
- package/dist/integrity/lint.js +3 -3
- package/dist/integrity/lint.ts +3 -3
- package/dist/integrity/lsp/diagnostics.js +1 -1
- package/dist/integrity/lsp/diagnostics.ts +1 -1
- package/dist/integrity/lsp/lint.js +1 -1
- package/dist/integrity/lsp/lint.ts +1 -1
- package/dist/integrity/slopnator.d.ts +1 -14
- package/dist/integrity/slopnator.js +8 -40
- package/dist/integrity/slopnator.ts +9 -51
- package/dist/integrity/slopnators/sl_broken_lineage.js +4 -2
- package/dist/integrity/slopnators/sl_broken_lineage.ts +4 -2
- package/dist/integrity/slopnators/sl_declared_elsewhere.js +24 -3
- package/dist/integrity/slopnators/sl_declared_elsewhere.ts +24 -3
- package/dist/integrity/slopnators/sl_derived_drift.js +10 -4
- package/dist/integrity/slopnators/sl_derived_drift.ts +10 -4
- package/dist/integrity/slopnators/sl_inert_island.js +7 -13
- package/dist/integrity/slopnators/sl_inert_island.ts +7 -16
- package/dist/integrity/slopnators/sl_inert_slopnator.js +8 -8
- package/dist/integrity/slopnators/sl_inert_slopnator.ts +8 -8
- package/dist/integrity/slopnators/sl_missing_row.js +14 -9
- package/dist/integrity/slopnators/sl_missing_row.ts +14 -10
- package/dist/integrity/slopnators/sl_no_child_process.js +8 -8
- package/dist/integrity/slopnators/sl_no_child_process.ts +8 -8
- package/dist/integrity/slopnators/sl_no_import_cycle.js +1 -1
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +1 -1
- package/dist/integrity/slopnators/sl_no_such_command.js +20 -5
- package/dist/integrity/slopnators/sl_no_such_command.ts +22 -5
- package/dist/integrity/slopnators/sl_only_takes_a_literal.js +8 -8
- package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +8 -8
- package/dist/integrity/slopnators/sl_stale_path.js +91 -18
- package/dist/integrity/slopnators/sl_stale_path.ts +97 -18
- package/dist/integrity/slopnators/sl_twice_meant.js +25 -2
- package/dist/integrity/slopnators/sl_twice_meant.ts +28 -2
- package/dist/integrity/slopnators/sl_twice_parented.js +26 -5
- package/dist/integrity/slopnators/sl_twice_parented.ts +26 -5
- package/dist/integrity/slopnators/sl_undeclared_node.js +18 -2
- package/dist/integrity/slopnators/sl_undeclared_node.ts +18 -2
- package/dist/integrity/slopnators/sl_undeclared_operation.js +3 -1
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +3 -1
- package/dist/integrity/slopnators/sl_undeclared_variant.js +6 -2
- package/dist/integrity/slopnators/sl_undeclared_variant.ts +6 -2
- package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -4
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +3 -4
- package/dist/integrity/slopnators/sl_unkept_rows.js +1 -1
- package/dist/integrity/slopnators/sl_unkept_rows.ts +1 -1
- package/dist/integrity/slopnators/sl_unpaired_member.js +13 -8
- package/dist/integrity/slopnators/sl_unpaired_member.ts +16 -8
- package/dist/integrity/slopnators/sl_unpaired_slopnator.js +16 -3
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +16 -3
- package/dist/integrity/slopnators/sl_unthreaded.d.ts +1 -0
- package/dist/integrity/slopnators/sl_unthreaded.js +18 -10
- package/dist/integrity/slopnators/sl_unthreaded.ts +19 -11
- package/dist/integrity/slopnators/sl_untranslated_key.js +1 -1
- package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -1
- package/dist/integrity/slopnators/sl_unversioned.js +16 -2
- package/dist/integrity/slopnators/sl_unversioned.ts +16 -2
- package/dist/integrity/sweep.js +17 -3
- package/dist/integrity/sweep.ts +20 -3
- package/dist/knowledge/attach.js +0 -6
- package/dist/knowledge/attach.ts +0 -6
- package/dist/knowledge/generators/docsgen.js +12 -3
- package/dist/knowledge/generators/docsgen.ts +12 -2
- package/dist/knowledge/generators/glossary.js +1 -1
- package/dist/knowledge/generators/glossary.ts +1 -1
- package/dist/operations/attach.js +2 -26
- package/dist/operations/attach.ts +2 -27
- package/dist/operations/dispatch.js +2 -3
- package/dist/operations/dispatch.ts +2 -3
- package/dist/operations/generators/capability.js +4 -7
- package/dist/operations/generators/capability.ts +3 -6
- package/dist/operations/generators/compile.js +8 -13
- package/dist/operations/generators/compile.ts +8 -4
- package/dist/operations/generators/permits.js +4 -13
- package/dist/operations/generators/permits.ts +3 -3
- package/dist/operations/generators/seed.js +5 -2
- package/dist/operations/generators/seed.ts +6 -2
- package/dist/operations/generators/trial.js +10 -10
- package/dist/operations/generators/trial.ts +10 -10
- package/dist/operations/http.js +4 -1
- package/dist/operations/http.ts +6 -1
- package/package.json +2 -2
- package/dist/core/cli.d.ts +0 -2
- package/dist/core/cli.js +0 -1761
- package/dist/core/cli.ts +0 -2004
- package/dist/core/generators/meta.d.ts +0 -2
- package/dist/core/generators/meta.js +0 -829
- package/dist/core/generators/meta.ts +0 -1006
- package/dist/derivation/generators/derivation.d.ts +0 -3
- package/dist/derivation/generators/derivation.js +0 -273
- package/dist/derivation/generators/derivation.ts +0 -347
- package/dist/derivation/makers/release.d.ts +0 -1
- package/dist/derivation/makers/release.js +0 -107
- package/dist/derivation/makers/release.ts +0 -135
- package/dist/integrations/generators/secret.d.ts +0 -1
- package/dist/integrations/generators/secret.js +0 -233
- package/dist/integrations/generators/secret.ts +0 -289
- package/dist/integrations/generators/target.d.ts +0 -1
- package/dist/integrations/generators/target.js +0 -775
- package/dist/integrations/generators/target.ts +0 -944
- package/dist/integrity/generators/rule.d.ts +0 -2
- package/dist/integrity/generators/rule.js +0 -213
- package/dist/integrity/generators/rule.ts +0 -271
- package/dist/integrity/generators/seal.d.ts +0 -4
- package/dist/integrity/generators/seal.js +0 -101
- package/dist/integrity/generators/seal.ts +0 -117
- package/dist/integrity/sealed.d.ts +0 -1
- package/dist/integrity/sealed.js +0 -56
- package/dist/integrity/sealed.ts +0 -56
- package/dist/knowledge/generators/skills.d.ts +0 -1
- package/dist/knowledge/generators/skills.js +0 -222
- package/dist/knowledge/generators/skills.ts +0 -263
- package/dist/operations/generators/grant.d.ts +0 -1
- package/dist/operations/generators/grant.js +0 -267
- package/dist/operations/generators/grant.ts +0 -336
- package/dist/operations/generators/operation.d.ts +0 -1
- package/dist/operations/generators/operation.js +0 -811
- package/dist/operations/generators/operation.ts +0 -972
- package/dist/operations/makers/capability.d.ts +0 -1
- package/dist/operations/makers/capability.js +0 -117
- package/dist/operations/makers/capability.ts +0 -166
- package/dist/operations/makers/trial.d.ts +0 -1
- package/dist/operations/makers/trial.js +0 -413
- package/dist/operations/makers/trial.ts +0 -520
package/dist/core/descend.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
56
|
+
const own = ownIgnores();
|
|
57
|
+
const found = own === undefined
|
|
51
58
|
? spawnSync('git', ['check-ignore', '-q', '--', at], { cwd: ROOT, stdio: 'ignore' }).status === 0
|
|
52
|
-
:
|
|
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 (!
|
|
216
|
+
if (!existsSync(at))
|
|
155
217
|
continue;
|
|
156
|
-
const
|
|
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,
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
}
|
package/dist/core/descend.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process'
|
|
2
|
-
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from '
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
+
own === undefined
|
|
61
68
|
? spawnSync('git', ['check-ignore', '-q', '--', at], { cwd: ROOT, stdio: 'ignore' }).status === 0
|
|
62
|
-
:
|
|
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 (!
|
|
277
|
+
if (!existsSync(at)) continue
|
|
205
278
|
|
|
206
|
-
const
|
|
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,
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
+
}
|