@prestack/thread 0.1.7 → 0.1.9
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 +181 -0
- package/dist/core/attach.js +40 -6
- package/dist/core/attach.ts +38 -6
- package/dist/core/descend.js +13 -17
- package/dist/core/descend.ts +13 -15
- package/dist/integrations/attach.js +5 -3
- package/dist/integrations/attach.ts +8 -4
- package/dist/integrations/cli.d.ts +2 -0
- package/dist/integrations/cli.js +55 -11
- package/dist/integrations/cli.ts +62 -11
- package/dist/integrations/disk.js +4 -3
- package/dist/integrations/disk.ts +4 -3
- package/dist/integrations/generators/vault.ts +0 -1
- package/dist/integrations/target.d.ts +1 -1
- package/dist/integrations/target.ts +1 -1
- package/dist/integrity/generators/gate.ts +1 -2
- package/dist/integrity/generators/publish.ts +0 -1
- package/dist/integrity/lint.js +0 -6
- package/dist/integrity/lint.ts +0 -10
- package/dist/integrity/slopnator.ts +1 -1
- package/dist/integrity/slopnators/sl_derived_drift.js +0 -2
- package/dist/integrity/slopnators/sl_derived_drift.ts +0 -3
- package/dist/integrity/slopnators/sl_unthreaded.js +0 -2
- package/dist/integrity/slopnators/sl_unthreaded.ts +0 -5
- package/dist/integrity/sweep.ts +0 -2
- package/dist/knowledge/generators/docsgen.ts +1 -1
- package/dist/operations/generators/capability.js +20 -9
- package/dist/operations/generators/capability.ts +19 -12
- package/dist/operations/generators/compile.js +41 -28
- package/dist/operations/generators/compile.ts +48 -33
- package/dist/operations/generators/permits.js +6 -3
- package/dist/operations/generators/permits.ts +7 -5
- package/dist/operations/generators/trial.ts +1 -1
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ const ARRIVES = 'Which header a value comes in on. A webhook says which one carr
|
|
|
15
15
|
const SIGNS = 'What a secret does to a body on its way out: its value is the key a signature is computed with, and the digest goes on the header named here. It is `sends` turned around — `sends` puts the value itself on a header, and this puts the value nowhere at all.';
|
|
16
16
|
const ADDRESS = 'Where a target reached over https is: the one absolute url every call on it is written from, stated on the row because a place is a fact anybody may read. Only the address of a database is sealed instead, because a connection string carries a password.';
|
|
17
17
|
const HEADERS = 'The headers every call on a target carries, stated on its row as name and value: the version of an API this app is written against, or a fixed way of asking. A credential never goes here, because anybody may read a row; a value that has to be withheld is a secret with a sends.';
|
|
18
|
+
const NEEDS = 'The packages that must be installed for this to work, each named with the versions it works with: a range from npm, a git link or a tarball. Attaching a package, or creating any row that states them, adds the ones the app does not already name to what it depends on and fetches them. A name the app already carries is left alone, so nothing overwrites a choice somebody made.';
|
|
18
19
|
const SSOT = { on: '2026-08-29', said: 'SSOT for the integrations, as a whole meta inside thread core' };
|
|
19
20
|
const RULED = { on: '2026-09-22', said: 'remove pub then as an acceptable thing, that\'s definitely slop and it\'s not the first time such a fucking thing has surfaced. Delete and redo it properly.' };
|
|
20
21
|
export const CONCEPTS = [
|
|
@@ -28,6 +29,7 @@ export const CONCEPTS = [
|
|
|
28
29
|
{ id: 'call', describes: CALL, cites: [SSOT] },
|
|
29
30
|
{ id: 'reach', describes: REACH, cites: [SSOT] },
|
|
30
31
|
{ id: 'integration', describes: INTEGRATION, cites: [SSOT] },
|
|
32
|
+
{ id: 'needs', describes: NEEDS, cites: [] },
|
|
31
33
|
];
|
|
32
34
|
import { quoted } from '../core/rows.js';
|
|
33
35
|
const concept = (one) => [
|
|
@@ -36,9 +38,9 @@ const concept = (one) => [
|
|
|
36
38
|
" at: '_threads/concepts.ts',",
|
|
37
39
|
` describes: ${quoted(one.describes)},`,
|
|
38
40
|
` version: ${String(VERSION)},`,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
' ],',
|
|
41
|
+
...(one.cites.length === 0
|
|
42
|
+
? [' cites: [],']
|
|
43
|
+
: [' cites: [', ...one.cites.map((each) => " { on: '" + each.on + "', said: " + quoted(each.said) + ' },'), ' ],']),
|
|
42
44
|
' },',
|
|
43
45
|
].join('\n');
|
|
44
46
|
const INTEGRATIONS_SOURCE = `import type { Threaded } from '${SHAPED}'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Attaching } from '../core/attach.ts'
|
|
2
2
|
import { SHAPED } from '../core/init.ts'
|
|
3
|
-
import { NAMED
|
|
3
|
+
import { NAMED } from '../core/scope.ts'
|
|
4
4
|
|
|
5
5
|
const VERSION = 1
|
|
6
6
|
|
|
@@ -46,6 +46,9 @@ const ADDRESS =
|
|
|
46
46
|
const HEADERS =
|
|
47
47
|
'The headers every call on a target carries, stated on its row as name and value: the version of an API this app is written against, or a fixed way of asking. A credential never goes here, because anybody may read a row; a value that has to be withheld is a secret with a sends.'
|
|
48
48
|
|
|
49
|
+
const NEEDS =
|
|
50
|
+
'The packages that must be installed for this to work, each named with the versions it works with: a range from npm, a git link or a tarball. Attaching a package, or creating any row that states them, adds the ones the app does not already name to what it depends on and fetches them. A name the app already carries is left alone, so nothing overwrites a choice somebody made.'
|
|
51
|
+
|
|
49
52
|
const SSOT = { on: '2026-08-29', said: 'SSOT for the integrations, as a whole meta inside thread core' }
|
|
50
53
|
|
|
51
54
|
const RULED = { on: '2026-09-22', said: 'remove pub then as an acceptable thing, that\'s definitely slop and it\'s not the first time such a fucking thing has surfaced. Delete and redo it properly.' }
|
|
@@ -61,6 +64,7 @@ export const CONCEPTS: readonly { readonly id: string; readonly describes: strin
|
|
|
61
64
|
{ id: 'call', describes: CALL, cites: [SSOT] },
|
|
62
65
|
{ id: 'reach', describes: REACH, cites: [SSOT] },
|
|
63
66
|
{ id: 'integration', describes: INTEGRATION, cites: [SSOT] },
|
|
67
|
+
{ id: 'needs', describes: NEEDS, cites: [] },
|
|
64
68
|
]
|
|
65
69
|
|
|
66
70
|
import { quoted } from '../core/rows.ts'
|
|
@@ -72,9 +76,9 @@ const concept = (one: (typeof CONCEPTS)[number]): string =>
|
|
|
72
76
|
" at: '_threads/concepts.ts',",
|
|
73
77
|
` describes: ${quoted(one.describes)},`,
|
|
74
78
|
` version: ${String(VERSION)},`,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
' ],',
|
|
79
|
+
...(one.cites.length === 0
|
|
80
|
+
? [' cites: [],']
|
|
81
|
+
: [' cites: [', ...one.cites.map((each) => " { on: '" + each.on + "', said: " + quoted(each.said) + ' },'), ' ],']),
|
|
78
82
|
' },',
|
|
79
83
|
].join('\n')
|
|
80
84
|
|
|
@@ -22,6 +22,8 @@ export declare function claims(root: string, runs: string): Held;
|
|
|
22
22
|
export declare function programFor(name: string, at: string): string;
|
|
23
23
|
export declare const SIGNING = "THREAD_RECEIPTS_KEY";
|
|
24
24
|
export declare function signing(): string;
|
|
25
|
+
export declare const PROGRAM: string;
|
|
26
|
+
export declare const COPIES: string;
|
|
25
27
|
export declare function running(name: string, subcommands: Readonly<Record<string, string>>, at: string): Running;
|
|
26
28
|
export interface Expansion {
|
|
27
29
|
readonly program: string;
|
package/dist/integrations/cli.js
CHANGED
|
@@ -6,8 +6,8 @@ import { allowedNodeEnvironmentFlags, env, execPath, kill, pid, stderr, stdout }
|
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { CHANNEL, FOLDER, across, attached, createFileSync, existsSync, here, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from '../integrations/disk.js';
|
|
8
8
|
import { VAULT_DIR } from '../integrations/vault.js';
|
|
9
|
-
import { CALL_PREFIX, LOCKFILES, PACKAGER, REACH, managerOf, programOf, unlisted } from '../integrations/target.js';
|
|
10
|
-
import { CLI } from '../core/invoked.js';
|
|
9
|
+
import { CALL_PREFIX, LOCKFILES, ORDERS, PACKAGER, REACH, managerOf, programOf, unlisted } from '../integrations/target.js';
|
|
10
|
+
import { BESIDE, CLI } from '../core/invoked.js';
|
|
11
11
|
import { valuesIn } from '../core/rows.js';
|
|
12
12
|
import { THING } from '../core/scope.js';
|
|
13
13
|
import { findTree, inTree } from '../core/tree.js';
|
|
@@ -168,11 +168,33 @@ export function signing() {
|
|
|
168
168
|
return signed;
|
|
169
169
|
}
|
|
170
170
|
const OWN = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
171
|
+
export const PROGRAM = REACH.cli + THING;
|
|
172
|
+
export const COPIES = THING + ' create copy';
|
|
173
|
+
const UNCOPIED = 'the thread command sits beside none of the framework code this server runs, because no package ships it, and the ' +
|
|
174
|
+
'folder this server was started from keeps no compiled copy of it. Run `' + COPIES + '` in that folder, and the next ' +
|
|
175
|
+
'command starts that copy.';
|
|
176
|
+
function startable() {
|
|
177
|
+
if (existsSync(CLI.at(-1) ?? ''))
|
|
178
|
+
return { words: CLI, own: OWN, reads: [OWN] };
|
|
179
|
+
let root;
|
|
180
|
+
try {
|
|
181
|
+
root = findTree().at;
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return UNCOPIED;
|
|
185
|
+
}
|
|
186
|
+
const copied = ((sheet(root).TARGETS ?? []).find((one) => one.id === PROGRAM)?.derives ?? []).find((one) => one.by === COPIES)?.at;
|
|
187
|
+
if (copied === undefined)
|
|
188
|
+
return UNCOPIED;
|
|
189
|
+
const own = resolve(root, copied);
|
|
190
|
+
const entry = join(own, 'cli', BESIDE('cli'));
|
|
191
|
+
return existsSync(entry) ? { words: [entry], own, reads: [own, join(root, 'node_modules')] } : UNCOPIED;
|
|
192
|
+
}
|
|
171
193
|
const GUARDS = ['--permission', '--experimental-permission'];
|
|
172
194
|
const HUSHES = '--disable-warning';
|
|
173
195
|
const swapped = (path) => [...path].map((one) => (one === one.toUpperCase() ? one.toLowerCase() : one.toUpperCase())).join('');
|
|
174
196
|
let unguarded = false;
|
|
175
|
-
function guarded() {
|
|
197
|
+
function guarded(reads) {
|
|
176
198
|
const before = env.NODE_OPTIONS ?? '';
|
|
177
199
|
const on = GUARDS.find((one) => allowedNodeEnvironmentFlags.has(one));
|
|
178
200
|
if (on === undefined) {
|
|
@@ -186,7 +208,7 @@ function guarded() {
|
|
|
186
208
|
const kept = [...new Set([tmpdir(), realpathSync(tmpdir())])];
|
|
187
209
|
const allowed = (flag, paths) => paths.map((one) => flag + '="' + one + '"');
|
|
188
210
|
const quiet = allowedNodeEnvironmentFlags.has(HUSHES) ? [HUSHES + '=SecurityWarning'] : [];
|
|
189
|
-
return [before, on, ...allowed('--allow-fs-read', [
|
|
211
|
+
return [before, on, ...allowed('--allow-fs-read', [...reads.flatMap((one) => [one, swapped(one)]), ...kept]), ...allowed('--allow-fs-write', kept), '--allow-child-process', '--allow-worker', ...quiet]
|
|
190
212
|
.filter((one) => one !== '')
|
|
191
213
|
.join(' ');
|
|
192
214
|
}
|
|
@@ -201,6 +223,11 @@ const still = (one, asks) => ({ there: true, ok: false, code: -1, out: '', err:
|
|
|
201
223
|
const nowhere = (why) => ({ there: false, ok: false, code: 1, out: '', err: '', killed: null, why });
|
|
202
224
|
const lost = (of) => ({ there: true, ok: false, code: -1, out: '', err: '', killed: null, why: '', of, lost: of });
|
|
203
225
|
const told = (one) => JSON.stringify([one.order, one.at, one.said ?? null, one.mode ?? null, one.words ?? null]);
|
|
226
|
+
const READS = new Set(Object.entries(ORDERS).filter(([order, answer]) => order === answer).map(([order]) => order));
|
|
227
|
+
function kept(one, asked, held) {
|
|
228
|
+
if (READS.has(asked.order) && held.answered !== 'refused')
|
|
229
|
+
one.known.set(told(asked), held);
|
|
230
|
+
}
|
|
204
231
|
const plainly = (one) => one.order + ' ' + (one.at === '' ? '.' : one.at);
|
|
205
232
|
const outside = (root, at) => {
|
|
206
233
|
const step = relative(root, at);
|
|
@@ -248,10 +275,18 @@ function watching(one) {
|
|
|
248
275
|
continue;
|
|
249
276
|
}
|
|
250
277
|
const asked = { ...order, at: relative(one.root, order.at) };
|
|
278
|
+
const known = one.known.get(told(asked));
|
|
279
|
+
if (known !== undefined) {
|
|
280
|
+
reply(one.dir, short, known);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (!READS.has(asked.order))
|
|
284
|
+
one.known.clear();
|
|
251
285
|
const due = one.replaying.shift();
|
|
252
286
|
if (due !== undefined) {
|
|
253
287
|
if (told(due.order) === told(asked)) {
|
|
254
288
|
reply(one.dir, short, due.answered);
|
|
289
|
+
kept(one, asked, due.answered);
|
|
255
290
|
continue;
|
|
256
291
|
}
|
|
257
292
|
one.replaying = [];
|
|
@@ -263,6 +298,7 @@ function watching(one) {
|
|
|
263
298
|
continue;
|
|
264
299
|
}
|
|
265
300
|
one.pending = short;
|
|
301
|
+
one.asking = asked;
|
|
266
302
|
hand(one, still(one, [asked]));
|
|
267
303
|
}
|
|
268
304
|
}, TICK);
|
|
@@ -291,15 +327,15 @@ function next(one, within) {
|
|
|
291
327
|
}, Math.max(1_000, within - MARGIN));
|
|
292
328
|
});
|
|
293
329
|
}
|
|
294
|
-
function begun(word, args, root, hears, called, recording = []) {
|
|
330
|
+
function begun(from, word, args, root, hears, called, recording = []) {
|
|
295
331
|
const of = called ?? randomBytes(6).toString('hex');
|
|
296
332
|
const dir = join(tmpdir(), SERVED, randomBytes(6).toString('hex'));
|
|
297
333
|
writeFileSync(join(dir, 'channel'), '', 'utf8');
|
|
298
|
-
const one = { of, dir, root, out: '', err: '', why: '', pending: '', waiting: [], parked: [], replaying: [...recording], handed: recording.length };
|
|
299
|
-
const child = spawn(execPath, [...
|
|
300
|
-
cwd:
|
|
334
|
+
const one = { of, dir, root, out: '', err: '', why: '', pending: '', waiting: [], parked: [], replaying: [...recording], handed: recording.length, known: new Map() };
|
|
335
|
+
const child = spawn(execPath, [...from.words, word, ...args], {
|
|
336
|
+
cwd: from.own,
|
|
301
337
|
shell: false,
|
|
302
|
-
env: { ...env, [CHANNEL]: dir, [FOLDER]: root, [SIGNING]: signing(), NODE_OPTIONS: guarded() },
|
|
338
|
+
env: { ...env, [CHANNEL]: dir, [FOLDER]: root, [SIGNING]: signing(), NODE_OPTIONS: guarded(from.reads) },
|
|
303
339
|
});
|
|
304
340
|
one.stop = () => child.kill();
|
|
305
341
|
child.stdout.on('data', (chunk) => (one.out += chunk.toString()));
|
|
@@ -337,7 +373,10 @@ async function served(word, args, within, how) {
|
|
|
337
373
|
const root = how?.at ?? '';
|
|
338
374
|
if (root === '')
|
|
339
375
|
return nowhere('nothing said which folder to run thread ' + word + UNPLACED);
|
|
340
|
-
const
|
|
376
|
+
const from = startable();
|
|
377
|
+
if (typeof from === 'string')
|
|
378
|
+
return nowhere(from);
|
|
379
|
+
const again = begun(from, word, args, root, how?.hears, of, recording);
|
|
341
380
|
return counted(again, await next(again, within));
|
|
342
381
|
}
|
|
343
382
|
const answering = how?.answering;
|
|
@@ -346,6 +385,8 @@ async function served(word, args, within, how) {
|
|
|
346
385
|
const given = (how?.answered ?? [])[0];
|
|
347
386
|
if (one.pending !== '' && given !== undefined) {
|
|
348
387
|
reply(one.dir, one.pending, given);
|
|
388
|
+
if (one.asking !== undefined)
|
|
389
|
+
kept(one, one.asking, given);
|
|
349
390
|
one.pending = '';
|
|
350
391
|
}
|
|
351
392
|
return counted(one, await next(one, within));
|
|
@@ -353,7 +394,10 @@ async function served(word, args, within, how) {
|
|
|
353
394
|
const root = how?.at ?? '';
|
|
354
395
|
if (root === '')
|
|
355
396
|
return nowhere('nothing said which folder to run thread ' + word + UNPLACED);
|
|
356
|
-
const
|
|
397
|
+
const from = startable();
|
|
398
|
+
if (typeof from === 'string')
|
|
399
|
+
return nowhere(from);
|
|
400
|
+
const fresh = begun(from, word, args, root, how?.hears);
|
|
357
401
|
return counted(fresh, await next(fresh, within));
|
|
358
402
|
}
|
|
359
403
|
export function running(name, subcommands, at) {
|
package/dist/integrations/cli.ts
CHANGED
|
@@ -8,8 +8,8 @@ import { fileURLToPath } from 'node:url'
|
|
|
8
8
|
import { CHANNEL, FOLDER, across, attached, createFileSync, existsSync, here, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from '../integrations/disk.ts'
|
|
9
9
|
import { VAULT_DIR } from '../integrations/vault.ts'
|
|
10
10
|
|
|
11
|
-
import { CALL_PREFIX, LOCKFILES, PACKAGER, REACH, managerOf, programOf, unlisted, type Answer, type Call, type How, type Order, type Ran, type Recorded, type Running, type Target } from '../integrations/target.ts'
|
|
12
|
-
import { CLI } from '../core/invoked.ts'
|
|
11
|
+
import { CALL_PREFIX, LOCKFILES, ORDERS, PACKAGER, REACH, managerOf, programOf, unlisted, type Answer, type Call, type How, type Order, type Ran, type Recorded, type Running, type Target } from '../integrations/target.ts'
|
|
12
|
+
import { BESIDE, CLI } from '../core/invoked.ts'
|
|
13
13
|
import { valuesIn } from '../core/rows.ts'
|
|
14
14
|
import { THING } from '../core/scope.ts'
|
|
15
15
|
import { findTree, inTree } from '../core/tree.ts'
|
|
@@ -213,6 +213,36 @@ export function signing(): string {
|
|
|
213
213
|
|
|
214
214
|
const OWN = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
215
215
|
|
|
216
|
+
export const PROGRAM = REACH.cli + THING
|
|
217
|
+
|
|
218
|
+
export const COPIES = THING + ' create copy'
|
|
219
|
+
|
|
220
|
+
interface Started {
|
|
221
|
+
readonly words: readonly string[]
|
|
222
|
+
readonly own: string
|
|
223
|
+
readonly reads: readonly string[]
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const UNCOPIED =
|
|
227
|
+
'the thread command sits beside none of the framework code this server runs, because no package ships it, and the ' +
|
|
228
|
+
'folder this server was started from keeps no compiled copy of it. Run `' + COPIES + '` in that folder, and the next ' +
|
|
229
|
+
'command starts that copy.'
|
|
230
|
+
|
|
231
|
+
function startable(): Started | string {
|
|
232
|
+
if (existsSync(CLI.at(-1) ?? '')) return { words: CLI, own: OWN, reads: [OWN] }
|
|
233
|
+
let root: string
|
|
234
|
+
try {
|
|
235
|
+
root = findTree().at
|
|
236
|
+
} catch {
|
|
237
|
+
return UNCOPIED
|
|
238
|
+
}
|
|
239
|
+
const copied = ((sheet(root).TARGETS ?? []).find((one) => one.id === PROGRAM)?.derives ?? []).find((one) => one.by === COPIES)?.at
|
|
240
|
+
if (copied === undefined) return UNCOPIED
|
|
241
|
+
const own = resolve(root, copied)
|
|
242
|
+
const entry = join(own, 'cli', BESIDE('cli'))
|
|
243
|
+
return existsSync(entry) ? { words: [entry], own, reads: [own, join(root, 'node_modules')] } : UNCOPIED
|
|
244
|
+
}
|
|
245
|
+
|
|
216
246
|
const GUARDS = ['--permission', '--experimental-permission']
|
|
217
247
|
|
|
218
248
|
const HUSHES = '--disable-warning'
|
|
@@ -222,7 +252,7 @@ const swapped = (path: string): string =>
|
|
|
222
252
|
|
|
223
253
|
let unguarded = false
|
|
224
254
|
|
|
225
|
-
function guarded(): string {
|
|
255
|
+
function guarded(reads: readonly string[]): string {
|
|
226
256
|
const before = env.NODE_OPTIONS ?? ''
|
|
227
257
|
const on = GUARDS.find((one) => allowedNodeEnvironmentFlags.has(one))
|
|
228
258
|
if (on === undefined) {
|
|
@@ -238,7 +268,7 @@ function guarded(): string {
|
|
|
238
268
|
const kept = [...new Set([tmpdir(), realpathSync(tmpdir())])]
|
|
239
269
|
const allowed = (flag: string, paths: readonly string[]): readonly string[] => paths.map((one) => flag + '="' + one + '"')
|
|
240
270
|
const quiet = allowedNodeEnvironmentFlags.has(HUSHES) ? [HUSHES + '=SecurityWarning'] : []
|
|
241
|
-
return [before, on, ...allowed('--allow-fs-read', [
|
|
271
|
+
return [before, on, ...allowed('--allow-fs-read', [...reads.flatMap((one) => [one, swapped(one)]), ...kept]), ...allowed('--allow-fs-write', kept), '--allow-child-process', '--allow-worker', ...quiet]
|
|
242
272
|
.filter((one) => one !== '')
|
|
243
273
|
.join(' ')
|
|
244
274
|
}
|
|
@@ -267,6 +297,8 @@ interface Serving {
|
|
|
267
297
|
parked: Ran[]
|
|
268
298
|
replaying: Recorded[]
|
|
269
299
|
handed: number
|
|
300
|
+
readonly known: Map<string, Answer>
|
|
301
|
+
asking?: Order
|
|
270
302
|
last?: Ran
|
|
271
303
|
stop?: () => void
|
|
272
304
|
watch?: ReturnType<typeof setInterval>
|
|
@@ -282,6 +314,12 @@ const lost = (of: string): Ran => ({ there: true, ok: false, code: -1, out: '',
|
|
|
282
314
|
|
|
283
315
|
const told = (one: Order): string => JSON.stringify([one.order, one.at, one.said ?? null, one.mode ?? null, one.words ?? null])
|
|
284
316
|
|
|
317
|
+
const READS: ReadonlySet<string> = new Set(Object.entries(ORDERS).filter(([order, answer]) => order === answer).map(([order]) => order))
|
|
318
|
+
|
|
319
|
+
function kept(one: Serving, asked: Order, held: Answer): void {
|
|
320
|
+
if (READS.has(asked.order) && held.answered !== 'refused') one.known.set(told(asked), held)
|
|
321
|
+
}
|
|
322
|
+
|
|
285
323
|
const plainly = (one: Order): string => one.order + ' ' + (one.at === '' ? '.' : one.at)
|
|
286
324
|
|
|
287
325
|
const outside = (root: string, at: string): boolean => {
|
|
@@ -328,10 +366,17 @@ function watching(one: Serving): void {
|
|
|
328
366
|
continue
|
|
329
367
|
}
|
|
330
368
|
const asked: Order = { ...order, at: relative(one.root, order.at) }
|
|
369
|
+
const known = one.known.get(told(asked))
|
|
370
|
+
if (known !== undefined) {
|
|
371
|
+
reply(one.dir, short, known)
|
|
372
|
+
continue
|
|
373
|
+
}
|
|
374
|
+
if (!READS.has(asked.order)) one.known.clear()
|
|
331
375
|
const due = one.replaying.shift()
|
|
332
376
|
if (due !== undefined) {
|
|
333
377
|
if (told(due.order) === told(asked)) {
|
|
334
378
|
reply(one.dir, short, due.answered)
|
|
379
|
+
kept(one, asked, due.answered)
|
|
335
380
|
continue
|
|
336
381
|
}
|
|
337
382
|
one.replaying = []
|
|
@@ -343,6 +388,7 @@ function watching(one: Serving): void {
|
|
|
343
388
|
continue
|
|
344
389
|
}
|
|
345
390
|
one.pending = short
|
|
391
|
+
one.asking = asked
|
|
346
392
|
hand(one, still(one, [asked]))
|
|
347
393
|
}
|
|
348
394
|
}, TICK)
|
|
@@ -369,15 +415,15 @@ function next(one: Serving, within?: number): Promise<Ran> {
|
|
|
369
415
|
})
|
|
370
416
|
}
|
|
371
417
|
|
|
372
|
-
function begun(word: string, args: readonly string[], root: string, hears?: string, called?: string, recording: readonly Recorded[] = []): Serving {
|
|
418
|
+
function begun(from: Started, word: string, args: readonly string[], root: string, hears?: string, called?: string, recording: readonly Recorded[] = []): Serving {
|
|
373
419
|
const of = called ?? randomBytes(6).toString('hex')
|
|
374
420
|
const dir = join(tmpdir(), SERVED, randomBytes(6).toString('hex'))
|
|
375
421
|
writeFileSync(join(dir, 'channel'), '', 'utf8')
|
|
376
|
-
const one: Serving = { of, dir, root, out: '', err: '', why: '', pending: '', waiting: [], parked: [], replaying: [...recording], handed: recording.length }
|
|
377
|
-
const child = spawn(execPath, [...
|
|
378
|
-
cwd:
|
|
422
|
+
const one: Serving = { of, dir, root, out: '', err: '', why: '', pending: '', waiting: [], parked: [], replaying: [...recording], handed: recording.length, known: new Map() }
|
|
423
|
+
const child = spawn(execPath, [...from.words, word, ...args], {
|
|
424
|
+
cwd: from.own,
|
|
379
425
|
shell: false,
|
|
380
|
-
env: { ...env, [CHANNEL]: dir, [FOLDER]: root, [SIGNING]: signing(), NODE_OPTIONS: guarded() },
|
|
426
|
+
env: { ...env, [CHANNEL]: dir, [FOLDER]: root, [SIGNING]: signing(), NODE_OPTIONS: guarded(from.reads) },
|
|
381
427
|
})
|
|
382
428
|
one.stop = () => child.kill()
|
|
383
429
|
child.stdout.on('data', (chunk: Buffer) => (one.out += chunk.toString()))
|
|
@@ -414,7 +460,9 @@ async function served(word: string, args: readonly string[], within?: number, ho
|
|
|
414
460
|
if (recording === undefined) return lost(of)
|
|
415
461
|
const root = how?.at ?? ''
|
|
416
462
|
if (root === '') return nowhere('nothing said which folder to run thread ' + word + UNPLACED)
|
|
417
|
-
const
|
|
463
|
+
const from = startable()
|
|
464
|
+
if (typeof from === 'string') return nowhere(from)
|
|
465
|
+
const again = begun(from, word, args, root, how?.hears, of, recording)
|
|
418
466
|
return counted(again, await next(again, within))
|
|
419
467
|
}
|
|
420
468
|
const answering = how?.answering
|
|
@@ -422,13 +470,16 @@ async function served(word: string, args: readonly string[], within?: number, ho
|
|
|
422
470
|
const given = (how?.answered ?? [])[0]
|
|
423
471
|
if (one.pending !== '' && given !== undefined) {
|
|
424
472
|
reply(one.dir, one.pending, given)
|
|
473
|
+
if (one.asking !== undefined) kept(one, one.asking, given)
|
|
425
474
|
one.pending = ''
|
|
426
475
|
}
|
|
427
476
|
return counted(one, await next(one, within))
|
|
428
477
|
}
|
|
429
478
|
const root = how?.at ?? ''
|
|
430
479
|
if (root === '') return nowhere('nothing said which folder to run thread ' + word + UNPLACED)
|
|
431
|
-
const
|
|
480
|
+
const from = startable()
|
|
481
|
+
if (typeof from === 'string') return nowhere(from)
|
|
482
|
+
const fresh = begun(from, word, args, root, how?.hears)
|
|
432
483
|
return counted(fresh, await next(fresh, within))
|
|
433
484
|
}
|
|
434
485
|
|
|
@@ -132,9 +132,9 @@ function overFiles(dir) {
|
|
|
132
132
|
reals.set(at, found);
|
|
133
133
|
return found;
|
|
134
134
|
},
|
|
135
|
-
fetch: (at) => {
|
|
135
|
+
fetch: (at, words) => {
|
|
136
136
|
try {
|
|
137
|
-
return String(took({ order: 'fetch', at }).said);
|
|
137
|
+
return String(took(words === undefined ? { order: 'fetch', at } : { order: 'fetch', at, words }).said);
|
|
138
138
|
}
|
|
139
139
|
finally {
|
|
140
140
|
unknown();
|
|
@@ -308,7 +308,8 @@ export const HERE = {
|
|
|
308
308
|
held = disk.statSync(at, { throwIfNoEntry: false });
|
|
309
309
|
}
|
|
310
310
|
catch (thrown) {
|
|
311
|
-
|
|
311
|
+
const code = thrown.code;
|
|
312
|
+
if (code === 'ERR_ACCESS_DENIED' || code === 'ENAMETOOLONG')
|
|
312
313
|
return 'none';
|
|
313
314
|
throw thrown;
|
|
314
315
|
}
|
|
@@ -146,9 +146,9 @@ function overFiles(dir: string): Attached {
|
|
|
146
146
|
reals.set(at, found)
|
|
147
147
|
return found
|
|
148
148
|
},
|
|
149
|
-
fetch: (at) => {
|
|
149
|
+
fetch: (at, words) => {
|
|
150
150
|
try {
|
|
151
|
-
return String(took({ order: 'fetch', at }).said)
|
|
151
|
+
return String(took(words === undefined ? { order: 'fetch', at } : { order: 'fetch', at, words }).said)
|
|
152
152
|
} finally {
|
|
153
153
|
unknown()
|
|
154
154
|
}
|
|
@@ -349,7 +349,8 @@ export const HERE: Attached = {
|
|
|
349
349
|
try {
|
|
350
350
|
held = disk.statSync(at, { throwIfNoEntry: false })
|
|
351
351
|
} catch (thrown) {
|
|
352
|
-
|
|
352
|
+
const code = (thrown as NodeJS.ErrnoException).code
|
|
353
|
+
if (code === 'ERR_ACCESS_DENIED' || code === 'ENAMETOOLONG') return 'none'
|
|
353
354
|
throw thrown
|
|
354
355
|
}
|
|
355
356
|
if (held === undefined) return 'none'
|
|
@@ -2,7 +2,6 @@ import { existsSync, readFileSync, writeFileSync } from '../disk.ts'
|
|
|
2
2
|
import { valuesIn } from '../../core/rows.ts'
|
|
3
3
|
import { dirname, join, relative } from 'node:path'
|
|
4
4
|
import { argv, stderr, stdout } from 'node:process'
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
5
|
|
|
7
6
|
import ts from 'typescript'
|
|
8
7
|
|
|
@@ -48,7 +48,7 @@ export interface Answering {
|
|
|
48
48
|
readonly real: (at: string) => string;
|
|
49
49
|
}
|
|
50
50
|
export interface Attached extends Answering {
|
|
51
|
-
readonly fetch: (at: string) => string;
|
|
51
|
+
readonly fetch: (at: string, words?: readonly string[]) => string;
|
|
52
52
|
readonly run: (at: string, words: readonly string[]) => Ran;
|
|
53
53
|
}
|
|
54
54
|
export declare const ORDERS: {
|
|
@@ -127,7 +127,7 @@ export interface Answering {
|
|
|
127
127
|
|
|
128
128
|
export interface Attached extends Answering {
|
|
129
129
|
|
|
130
|
-
readonly fetch: (at: string) => string
|
|
130
|
+
readonly fetch: (at: string, words?: readonly string[]) => string
|
|
131
131
|
|
|
132
132
|
readonly run: (at: string, words: readonly string[]) => Ran
|
|
133
133
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { starts } from '../../integrations/cli.ts'
|
|
2
2
|
import { createHash } from 'node:crypto'
|
|
3
|
-
import {
|
|
3
|
+
import { join } from 'node:path'
|
|
4
4
|
import { argv, execPath, stderr, stdout } from 'node:process'
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
5
|
import { descend } from '../../core/descend.ts'
|
|
7
6
|
import { attached, existsSync, here, readdirSync, readFileSync, rmSync, writeFileSync } from '../../integrations/disk.ts'
|
|
8
7
|
import { LOADER, exit } from '../../core/invoked.ts'
|
|
@@ -3,7 +3,6 @@ import { existsSync, readFileSync } from '../../integrations/disk.ts'
|
|
|
3
3
|
import { valuesIn } from '../../core/rows.ts'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
import { argv, env, execPath, stderr, stdout } from 'node:process'
|
|
6
|
-
import { pathToFileURL } from 'node:url'
|
|
7
6
|
import { CLI, exit } from '../../core/invoked.ts'
|
|
8
7
|
import { THING as TYPED, named } from '../../core/scope.ts'
|
|
9
8
|
import { findTree, inTree } from '../../core/tree.ts'
|
package/dist/integrity/lint.js
CHANGED
|
@@ -4,12 +4,6 @@ import tseslint from 'typescript-eslint';
|
|
|
4
4
|
import { THING } from '../core/scope.js';
|
|
5
5
|
import { CONSTRAINTS, shown } from './constraint.js';
|
|
6
6
|
import { NOT_WRITTEN, governs, subject, reads, visits, load, ROOT, rules as appRules, slopnatorName, } from './slopnator.js';
|
|
7
|
-
const PROBE = {
|
|
8
|
-
filename: 'probe.ts',
|
|
9
|
-
text: '',
|
|
10
|
-
ancestors: () => [],
|
|
11
|
-
comments: () => [],
|
|
12
|
-
};
|
|
13
7
|
export function ruleMessage(rule) {
|
|
14
8
|
const cites = (rule.cites ?? []).map((cite) => `${cite.on} — ${cite.said}`).join(' · ');
|
|
15
9
|
return cites === '' ? `${rule.bans} — ${rule.because}. Instead: ${rule.instead}.` : `${rule.bans} — ${rule.because}. Instead: ${rule.instead}. (${cites})`;
|
package/dist/integrity/lint.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { starts } from '../integrations/cli.ts'
|
|
2
|
-
import { fileURLToPath } from 'node:url'
|
|
3
2
|
|
|
4
3
|
import { Linter, type Rule } from 'eslint'
|
|
5
4
|
import tseslint from 'typescript-eslint'
|
|
@@ -20,15 +19,6 @@ import {
|
|
|
20
19
|
type SlopnatorContext,
|
|
21
20
|
} from './slopnator.ts'
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const PROBE: SlopnatorContext = {
|
|
26
|
-
filename: 'probe.ts',
|
|
27
|
-
text: '',
|
|
28
|
-
ancestors: () => [],
|
|
29
|
-
comments: () => [],
|
|
30
|
-
}
|
|
31
|
-
|
|
32
22
|
export function ruleMessage(rule: Constraint): string {
|
|
33
23
|
const cites = (rule.cites ?? []).map((cite) => `${cite.on} — ${cite.said}`).join(' · ')
|
|
34
24
|
return cites === '' ? `${rule.bans} — ${rule.because}. Instead: ${rule.instead}.` : `${rule.bans} — ${rule.because}. Instead: ${rule.instead}. (${cites})`
|
|
@@ -8,7 +8,7 @@ import { invoked } from '../core/invoked.ts'
|
|
|
8
8
|
|
|
9
9
|
import { listBounds, quoted, withSeed } from '../core/rows.ts'
|
|
10
10
|
|
|
11
|
-
import { CONSTRAINTS, type Constraint, type Proof
|
|
11
|
+
import { CONSTRAINTS, type Constraint, type Proof } from './constraint.ts'
|
|
12
12
|
import { findTree, inTree } from '../core/tree.ts'
|
|
13
13
|
|
|
14
14
|
export const NOT_WRITTEN = Symbol.for('thread.slopnator.not_written')
|
|
@@ -2,8 +2,6 @@ import { readFileSync } from '../../integrations/disk.js';
|
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { atsIn } from './sl_unthreaded.js';
|
|
4
4
|
import { reached } from '../../core/descend.js';
|
|
5
|
-
import { findTree } from '../../core/tree.js';
|
|
6
|
-
const ROOT = `${findTree().at}/`;
|
|
7
5
|
export function derivations(root) {
|
|
8
6
|
const found = [];
|
|
9
7
|
for (const at of reached()) {
|
|
@@ -4,9 +4,6 @@ import { resolve } from 'node:path'
|
|
|
4
4
|
import type { Slopnator } from '../slopnator.ts'
|
|
5
5
|
import { atsIn } from './sl_unthreaded.ts'
|
|
6
6
|
import { reached } from '../../core/descend.ts'
|
|
7
|
-
import { findTree } from '../../core/tree.ts'
|
|
8
|
-
|
|
9
|
-
const ROOT = `${findTree().at}/`
|
|
10
7
|
|
|
11
8
|
export interface Derivation {
|
|
12
9
|
readonly at: string
|
|
@@ -7,7 +7,6 @@ const TREE = TREE_AT.name;
|
|
|
7
7
|
const THREAD_AT = `${TREE_AT.name}/thread.ts`;
|
|
8
8
|
const FOLLOWABLE = /\.(ts|tsx|mts|cts|mjs|cjs|js)$/;
|
|
9
9
|
const LISTS = new Set(LINEAGE);
|
|
10
|
-
let threaders;
|
|
11
10
|
let tree;
|
|
12
11
|
const parsed = new Map();
|
|
13
12
|
const broke = new Map();
|
|
@@ -233,7 +232,6 @@ export function index() {
|
|
|
233
232
|
export function forget() {
|
|
234
233
|
parsed.clear();
|
|
235
234
|
broke.clear();
|
|
236
|
-
threaders = undefined;
|
|
237
235
|
tree = undefined;
|
|
238
236
|
forgetDescent();
|
|
239
237
|
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { readFileSync } from '../../integrations/disk.ts'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
|
|
4
1
|
import { parser } from 'typescript-eslint'
|
|
5
2
|
|
|
6
3
|
import type { Slopnator } from '../slopnator.ts'
|
|
@@ -15,7 +12,6 @@ const THREAD_AT = `${TREE_AT.name}/thread.ts`
|
|
|
15
12
|
const FOLLOWABLE = /\.(ts|tsx|mts|cts|mjs|cjs|js)$/
|
|
16
13
|
const LISTS: ReadonlySet<string> = new Set(LINEAGE)
|
|
17
14
|
|
|
18
|
-
let threaders: ReadonlySet<string> | undefined
|
|
19
15
|
let tree: Index | undefined
|
|
20
16
|
const parsed = new Map<string, readonly Reach[]>()
|
|
21
17
|
const broke = new Map<string, string>()
|
|
@@ -295,7 +291,6 @@ export function index(): Index {
|
|
|
295
291
|
export function forget(): void {
|
|
296
292
|
parsed.clear()
|
|
297
293
|
broke.clear()
|
|
298
|
-
threaders = undefined
|
|
299
294
|
tree = undefined
|
|
300
295
|
forgetDescent()
|
|
301
296
|
}
|
package/dist/integrity/sweep.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { starts } from '../integrations/cli.ts'
|
|
|
2
2
|
import { attached, existsSync, readFileSync, readdirSync } from '../integrations/disk.ts'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
import { argv, stdout } from 'node:process'
|
|
5
|
-
import { pathToFileURL } from 'node:url'
|
|
6
5
|
|
|
7
6
|
import { runChecker, type Finding } from '../core/finding.ts'
|
|
8
7
|
import { exit, invoked } from '../core/invoked.ts'
|
|
@@ -11,7 +10,6 @@ import { parser } from 'typescript-eslint'
|
|
|
11
10
|
|
|
12
11
|
import { CONSTRAINTS, type Constraint, type Proof } from './constraint.ts'
|
|
13
12
|
import { NOT_WRITTEN, ROOT, derivedHere, governs, load, proofsOf, reads, rules, slopnatorName, subject, sweeps, visits, type Reading, type Slopnator, type Spoken, type Visiting } from './slopnator.ts'
|
|
14
|
-
import { ruleMessage } from './lint.ts'
|
|
15
13
|
import { crossed, deriveTrials, runTrials } from './generators/trial.ts'
|
|
16
14
|
|
|
17
15
|
const PARSEABLE = /\.(ts|tsx|mts|cts|mjs|cjs|js|jsx)$/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from '../../integrations/disk.ts'
|
|
2
|
-
import {
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
3
|
import { argv, stderr, stdout } from 'node:process'
|
|
4
4
|
|
|
5
5
|
import { exit } from '../../core/invoked.ts'
|