@molecule/api-ai-tools 1.8.1 → 1.8.2
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/dist/command-position.d.ts +42 -0
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +108 -39
- package/package.json +1 -1
|
@@ -58,6 +58,48 @@ export declare function durationSeconds(word: string): number | null;
|
|
|
58
58
|
* @returns One entry per statement or pipeline stage that runs something, in order.
|
|
59
59
|
*/
|
|
60
60
|
export declare function commandSegments(command: string): CommandSegment[];
|
|
61
|
+
/**
|
|
62
|
+
* One statement of a shell command, with where it sits and the variables it sets for itself.
|
|
63
|
+
*
|
|
64
|
+
* `commandSegments` answers WHAT runs; this answers where, and what the shell binds to it — the
|
|
65
|
+
* two things a caller that WRITES into a command needs. The platform composes a run from the
|
|
66
|
+
* executor's own command in more than one place, and a value written for the runner belongs in
|
|
67
|
+
* the runner's own statement: `APP_BASE=https://… cd app && npx playwright test` sets the variable
|
|
68
|
+
* for `cd` alone, so the Playwright config falls back to the local app while the command line
|
|
69
|
+
* still names the deployed host (xr-molecule-flash-m103 +28:08).
|
|
70
|
+
*/
|
|
71
|
+
export interface LocatedCommandSegment {
|
|
72
|
+
/**
|
|
73
|
+
* What the statement runs, or null when it only sets variables (`BASE=http://…` on its own):
|
|
74
|
+
* that shape persists in the shell, so a caller following assignments across `;`/`&&` has to
|
|
75
|
+
* see it.
|
|
76
|
+
*/
|
|
77
|
+
segment: CommandSegment | null;
|
|
78
|
+
/** The statement's own leading `NAME=value` words, as written (`TEST_MODE=fullstack`). */
|
|
79
|
+
assignments: readonly string[];
|
|
80
|
+
/**
|
|
81
|
+
* The offset in the command of the statement's first word, or null for a segment inside a
|
|
82
|
+
* nested `sh -c '<command>'` string — its positions are offsets in THAT string, not in this
|
|
83
|
+
* command, so nothing may be spliced at them.
|
|
84
|
+
*/
|
|
85
|
+
start: number | null;
|
|
86
|
+
/**
|
|
87
|
+
* The offset in the command just before the word the statement runs — after its own leading
|
|
88
|
+
* `NAME=value` words and their whitespace. A `NAME=value` written here is in the environment of
|
|
89
|
+
* the program the statement runs (`… TEST_MODE=fullstack <here>npx playwright test`). Null
|
|
90
|
+
* inside a nested `sh -c` string, for the same reason as {@link start}.
|
|
91
|
+
*/
|
|
92
|
+
assignmentOffset: number | null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Every statement of a command, with its own assignments and where it sits — assignment-only
|
|
96
|
+
* statements included, because the variables those persist are exactly what a later statement of
|
|
97
|
+
* the same command reads.
|
|
98
|
+
*
|
|
99
|
+
* @param command - The shell command.
|
|
100
|
+
* @returns One entry per statement or pipeline stage, in order.
|
|
101
|
+
*/
|
|
102
|
+
export declare function locatedCommandSegments(command: string): LocatedCommandSegment[];
|
|
61
103
|
/**
|
|
62
104
|
* The bound a command puts on its own run: the largest `timeout <N>` any of its segments runs
|
|
63
105
|
* under (`timeout 280 hurl --test …` → 280), or null when it carries none.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AA2MD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;
|
|
1
|
+
{"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AA2MD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AAkVD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAIjE;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;IAC9B,0FAA0F;IAC1F,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,EAAE,CAE/E;AAqDD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK/E"}
|
package/dist/command-position.js
CHANGED
|
@@ -400,11 +400,14 @@ function commandStart(words) {
|
|
|
400
400
|
* no runner, and every run of it reads as nothing).
|
|
401
401
|
*
|
|
402
402
|
* @param command - The shell command.
|
|
403
|
-
* @returns Each segment's text
|
|
403
|
+
* @returns Each segment's text and the offset in the command its first character sits at, in
|
|
404
|
+
* order, with empty ones dropped.
|
|
404
405
|
*/
|
|
405
406
|
function splitSegments(command) {
|
|
406
407
|
const parts = [];
|
|
407
408
|
let buffer = '';
|
|
409
|
+
/** The offset in the command of the current buffer's first character. */
|
|
410
|
+
let bufferStart = 0;
|
|
408
411
|
/** The nestings this position sits inside, outermost first. */
|
|
409
412
|
const stack = [];
|
|
410
413
|
for (let i = 0; i < command.length; i++) {
|
|
@@ -466,25 +469,33 @@ function splitSegments(command) {
|
|
|
466
469
|
continue;
|
|
467
470
|
}
|
|
468
471
|
if ((char === '&' && next === '&') || (char === '|' && next === '|')) {
|
|
469
|
-
parts.push(buffer);
|
|
472
|
+
parts.push({ text: buffer, start: bufferStart });
|
|
470
473
|
buffer = '';
|
|
471
474
|
i += 1;
|
|
475
|
+
bufferStart = i + 1;
|
|
472
476
|
continue;
|
|
473
477
|
}
|
|
474
478
|
if (char === ';' || char === '|' || char === '\n') {
|
|
475
|
-
parts.push(buffer);
|
|
479
|
+
parts.push({ text: buffer, start: bufferStart });
|
|
476
480
|
buffer = '';
|
|
481
|
+
bufferStart = i + 1;
|
|
477
482
|
continue;
|
|
478
483
|
}
|
|
479
484
|
if (char === '&' && command[i - 1] !== '>' && next !== '>') {
|
|
480
|
-
parts.push(buffer);
|
|
485
|
+
parts.push({ text: buffer, start: bufferStart });
|
|
481
486
|
buffer = '';
|
|
487
|
+
bufferStart = i + 1;
|
|
482
488
|
continue;
|
|
483
489
|
}
|
|
484
490
|
buffer += char;
|
|
485
491
|
}
|
|
486
|
-
parts.push(buffer);
|
|
487
|
-
return parts
|
|
492
|
+
parts.push({ text: buffer, start: bufferStart });
|
|
493
|
+
return parts
|
|
494
|
+
.map((part) => ({
|
|
495
|
+
text: part.text.trim(),
|
|
496
|
+
start: part.start + (part.text.length - part.text.trimStart().length),
|
|
497
|
+
}))
|
|
498
|
+
.filter((part) => part.text !== '');
|
|
488
499
|
}
|
|
489
500
|
/**
|
|
490
501
|
* One segment's words: quotes kept whole, grouping parentheses dropped, and a `(` written
|
|
@@ -506,6 +517,38 @@ function segmentWords(text) {
|
|
|
506
517
|
}
|
|
507
518
|
return cleaned;
|
|
508
519
|
}
|
|
520
|
+
/** {@link WORD} anchored at an offset, for walking a statement's raw words. */
|
|
521
|
+
const WORD_AT = new RegExp(WORD.source, 'y');
|
|
522
|
+
/**
|
|
523
|
+
* A statement's own leading `NAME=value` words, and where they end.
|
|
524
|
+
*
|
|
525
|
+
* The shell binds these to the command the statement runs, so they are the environment THAT
|
|
526
|
+
* program sees — which is why a caller writing a variable for a run has to write it here and not
|
|
527
|
+
* at the start of the whole command.
|
|
528
|
+
*
|
|
529
|
+
* @param text - One statement's text.
|
|
530
|
+
* @returns The assignments as written, and the offset in `text` just before the word that runs.
|
|
531
|
+
*/
|
|
532
|
+
function leadingAssignments(text) {
|
|
533
|
+
const words = [];
|
|
534
|
+
let at = 0;
|
|
535
|
+
for (let guard = 0; guard < MAX_WRAPPERS; guard++) {
|
|
536
|
+
while (at < text.length && /\s/.test(text[at] ?? ''))
|
|
537
|
+
at += 1;
|
|
538
|
+
// Grouping in front of the statement (`(npx …)`, `{ …`): not a word the shell assigns with.
|
|
539
|
+
if (text[at] === '(' || text[at] === '{') {
|
|
540
|
+
at += 1;
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
WORD_AT.lastIndex = at;
|
|
544
|
+
const match = WORD_AT.exec(text);
|
|
545
|
+
if (!match || !ENV_ASSIGNMENT.test(match[0]))
|
|
546
|
+
return { words, end: at };
|
|
547
|
+
words.push(match[0]);
|
|
548
|
+
at += match[0].length;
|
|
549
|
+
}
|
|
550
|
+
return { words, end: at };
|
|
551
|
+
}
|
|
509
552
|
/**
|
|
510
553
|
* The segments of a command, each with its program and arguments — the answer every classifier
|
|
511
554
|
* that reads a command as "this runs the suite" is asking for.
|
|
@@ -514,66 +557,92 @@ function segmentWords(text) {
|
|
|
514
557
|
* @returns One entry per statement or pipeline stage that runs something, in order.
|
|
515
558
|
*/
|
|
516
559
|
export function commandSegments(command) {
|
|
517
|
-
return
|
|
560
|
+
return locatedSegmentsOf(command, 0).flatMap((located) => located.segment === null ? [] : [located.segment]);
|
|
518
561
|
}
|
|
519
562
|
/**
|
|
520
|
-
*
|
|
521
|
-
*
|
|
522
|
-
*
|
|
523
|
-
* This is the bound `man timeout` enforces by KILLING the command — so a run that used it did not
|
|
524
|
-
* finish, whatever the pipeline's own exit status says. It is read here, from the same parse every
|
|
525
|
-
* other command reader uses, rather than matched as text: `timeout` in an argument
|
|
526
|
-
* (`grep -n 'timeout 280' notes.md`) bounds nothing, and `-k 5` is the grace before the kill, not
|
|
527
|
-
* the bound ({@link WRAPPER_VALUE_FLAGS}).
|
|
563
|
+
* Every statement of a command, with its own assignments and where it sits — assignment-only
|
|
564
|
+
* statements included, because the variables those persist are exactly what a later statement of
|
|
565
|
+
* the same command reads.
|
|
528
566
|
*
|
|
529
567
|
* @param command - The shell command.
|
|
530
|
-
* @returns
|
|
568
|
+
* @returns One entry per statement or pipeline stage, in order.
|
|
531
569
|
*/
|
|
532
|
-
export function
|
|
533
|
-
|
|
534
|
-
for (const segment of segmentsOf(command, 0)) {
|
|
535
|
-
if (segment.timeoutSeconds === null)
|
|
536
|
-
continue;
|
|
537
|
-
bound = bound === null ? segment.timeoutSeconds : Math.max(bound, segment.timeoutSeconds);
|
|
538
|
-
}
|
|
539
|
-
return bound;
|
|
570
|
+
export function locatedCommandSegments(command) {
|
|
571
|
+
return locatedSegmentsOf(command, 0);
|
|
540
572
|
}
|
|
541
573
|
/**
|
|
542
|
-
* {@link
|
|
543
|
-
* {@link MAX_NESTING} allows.
|
|
574
|
+
* {@link locatedCommandSegments}'s recursion, so a `sh -c '<command>'` is opened as far as
|
|
575
|
+
* {@link MAX_NESTING} allows. A nested statement's offsets are cleared: they are positions in
|
|
576
|
+
* the nested string, not in the command this walk started from.
|
|
544
577
|
*
|
|
545
578
|
* @param command - The shell command.
|
|
546
579
|
* @param depth - How many `sh -c` strings this command already sits inside.
|
|
547
|
-
* @returns The
|
|
580
|
+
* @returns The located statements.
|
|
548
581
|
*/
|
|
549
|
-
function
|
|
582
|
+
function locatedSegmentsOf(command, depth) {
|
|
550
583
|
if (typeof command !== 'string' || depth > MAX_NESTING)
|
|
551
584
|
return [];
|
|
552
585
|
const out = [];
|
|
553
|
-
for (const
|
|
554
|
-
const words = segmentWords(text);
|
|
555
|
-
if (words.length === 0)
|
|
556
|
-
continue;
|
|
586
|
+
for (const part of splitSegments(command)) {
|
|
587
|
+
const words = segmentWords(part.text);
|
|
557
588
|
const start = commandStart(words);
|
|
589
|
+
const leading = leadingAssignments(part.text);
|
|
558
590
|
if (start.nested !== null) {
|
|
559
|
-
|
|
591
|
+
for (const inner of locatedSegmentsOf(start.nested, depth + 1))
|
|
592
|
+
out.push({ ...inner, start: null, assignmentOffset: null });
|
|
560
593
|
continue;
|
|
561
594
|
}
|
|
562
595
|
const word = words[start.at];
|
|
563
|
-
|
|
564
|
-
|
|
596
|
+
if (word === undefined) {
|
|
597
|
+
// Only wrappers and assignments: nothing this statement runs, but the variables it sets
|
|
598
|
+
// persist in the shell the rest of the command runs in.
|
|
599
|
+
if (leading.words.length > 0)
|
|
600
|
+
out.push({
|
|
601
|
+
segment: null,
|
|
602
|
+
assignments: leading.words,
|
|
603
|
+
start: part.start,
|
|
604
|
+
assignmentOffset: part.start + leading.end,
|
|
605
|
+
});
|
|
565
606
|
continue;
|
|
607
|
+
}
|
|
566
608
|
const program = programName(word);
|
|
567
609
|
const args = words.slice(start.at + 1);
|
|
568
610
|
out.push({
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
611
|
+
segment: {
|
|
612
|
+
program,
|
|
613
|
+
args,
|
|
614
|
+
text: [program, ...args].join(' '),
|
|
615
|
+
timeoutSeconds: start.timeoutSeconds,
|
|
616
|
+
},
|
|
617
|
+
assignments: leading.words,
|
|
618
|
+
start: part.start,
|
|
619
|
+
assignmentOffset: part.start + leading.end,
|
|
573
620
|
});
|
|
574
621
|
}
|
|
575
622
|
return out;
|
|
576
623
|
}
|
|
624
|
+
/**
|
|
625
|
+
* The bound a command puts on its own run: the largest `timeout <N>` any of its segments runs
|
|
626
|
+
* under (`timeout 280 hurl --test …` → 280), or null when it carries none.
|
|
627
|
+
*
|
|
628
|
+
* This is the bound `man timeout` enforces by KILLING the command — so a run that used it did not
|
|
629
|
+
* finish, whatever the pipeline's own exit status says. It is read here, from the same parse every
|
|
630
|
+
* other command reader uses, rather than matched as text: `timeout` in an argument
|
|
631
|
+
* (`grep -n 'timeout 280' notes.md`) bounds nothing, and `-k 5` is the grace before the kill, not
|
|
632
|
+
* the bound ({@link WRAPPER_VALUE_FLAGS}).
|
|
633
|
+
*
|
|
634
|
+
* @param command - The shell command.
|
|
635
|
+
* @returns The seconds the command allows itself, or null.
|
|
636
|
+
*/
|
|
637
|
+
export function commandTimeoutSeconds(command) {
|
|
638
|
+
let bound = null;
|
|
639
|
+
for (const segment of commandSegments(command)) {
|
|
640
|
+
if (segment.timeoutSeconds === null)
|
|
641
|
+
continue;
|
|
642
|
+
bound = bound === null ? segment.timeoutSeconds : Math.max(bound, segment.timeoutSeconds);
|
|
643
|
+
}
|
|
644
|
+
return bound;
|
|
645
|
+
}
|
|
577
646
|
/**
|
|
578
647
|
* Whether any segment of the command runs a program whose command-position text matches
|
|
579
648
|
* `pattern` — the one question a runner-kind table asks.
|