@xmoon76/dsh-pi-tui 0.3.2 → 0.3.4
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/README.md +69 -7
- package/README.zh-CN.md +56 -6
- package/dist/builtins.mjs +1 -1
- package/dist/extensions.mjs +1 -1
- package/dist/index.d.mts +137 -2
- package/dist/index.mjs +2 -2
- package/dist/{src-oZjot5jQ.mjs → src-B1uP5j0d.mjs} +8535 -1713
- package/package.json +6 -2
- package/scripts/client-boundary-baseline.json +82 -0
- package/scripts/client-boundary-gate.mjs +165 -0
- package/scripts/e2e-session-lease.sh +388 -0
- package/scripts/pre-push-stages.mjs +116 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Pre-push gate stage derivation — the single source of truth for what the
|
|
4
|
+
* husky pre-push hook runs stays in package.json's `verify:*` scripts; this
|
|
5
|
+
* module turns one of them into the ordered per-command stage list the hook
|
|
6
|
+
* executes with progress reporting.
|
|
7
|
+
*
|
|
8
|
+
* A script like `a && b && c` yields `['a', 'b', 'c']`. Only top-level `&&`
|
|
9
|
+
* separators are split (a `&&` inside quotes, e.g. inside a node -e snippet,
|
|
10
|
+
* is not a separator). A trailing `&&` (or a `&&` followed by nothing but
|
|
11
|
+
* whitespace) is rejected — `sh -e` treats such a line as a syntax error, so
|
|
12
|
+
* a split would silently drop the intended tail.
|
|
13
|
+
*
|
|
14
|
+
* Supported shell grammar (the parsed script must stay within this subset):
|
|
15
|
+
* - commands chained with `&&` (the split points), each otherwise passed
|
|
16
|
+
* verbatim to `sh -c` for execution — the splitter does NOT interpret
|
|
17
|
+
* the commands themselves;
|
|
18
|
+
* - single- and double-quoted strings anywhere in the script (quotes are
|
|
19
|
+
* tracked so `&&` inside them is not a separator);
|
|
20
|
+
* - backslash escapes inside and outside quotes: an escaped `&&` is NOT
|
|
21
|
+
* treated as a separator (it stays inside the current stage, whose
|
|
22
|
+
* shell semantics are then preserved unchanged by `sh -c`);
|
|
23
|
+
* - anything the command segment may contain (redirects, env assignments,
|
|
24
|
+
* pipes, subshells, `node -e "..."`, ...) is preserved verbatim and
|
|
25
|
+
* executed by `sh -c` unchanged.
|
|
26
|
+
* NOT supported (the splitter rejects them loudly): unterminated quotes,
|
|
27
|
+
* dangling `&&`, empty commands (consecutive `&&`), empty scripts.
|
|
28
|
+
*
|
|
29
|
+
* Usage:
|
|
30
|
+
* node scripts/pre-push-stages.mjs <verify-script-name>
|
|
31
|
+
* # e.g. node scripts/pre-push-stages.mjs verify:prepush
|
|
32
|
+
*
|
|
33
|
+
* @module pre-push-stages
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { readFileSync } from 'node:fs'
|
|
37
|
+
import { join, dirname } from 'node:path'
|
|
38
|
+
import { fileURLToPath } from 'node:url'
|
|
39
|
+
|
|
40
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
41
|
+
|
|
42
|
+
function fail(message) {
|
|
43
|
+
console.error(`pre-push-stages: ${message}`)
|
|
44
|
+
process.exit(1)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const [name] = process.argv.slice(2)
|
|
48
|
+
if (!name || !name.startsWith('verify:')) {
|
|
49
|
+
fail(`usage: node scripts/pre-push-stages.mjs <verify-script-name> (got: ${name ?? 'none'})`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'))
|
|
53
|
+
const script = pkg.scripts?.[name]
|
|
54
|
+
if (typeof script !== 'string' || script === '') {
|
|
55
|
+
fail(`package.json has no non-empty script "${name}" (update .husky/pre-push)`)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Split on top-level `&&` only: walk the string tracking single-quote,
|
|
59
|
+
// double-quote and backslash escapes; a `&&` seen at depth 0 (no open
|
|
60
|
+
// quote) is a command separator. Escapes are honoured OUTSIDE quotes too
|
|
61
|
+
// (`\&&` is an escaped literal `&`, not a separator), and a script with
|
|
62
|
+
// an unterminated quote is rejected — sh would reject it as well.
|
|
63
|
+
const stages = []
|
|
64
|
+
let current = ''
|
|
65
|
+
let quote = null
|
|
66
|
+
for (let i = 0; i < script.length; i++) {
|
|
67
|
+
const ch = script[i]
|
|
68
|
+
if (quote === '"') {
|
|
69
|
+
if (ch === '\\') { current += ch + (script[i + 1] ?? ''); i++; continue }
|
|
70
|
+
if (ch === '"') quote = null
|
|
71
|
+
current += ch
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
if (quote === "'") {
|
|
75
|
+
if (ch === "'") quote = null
|
|
76
|
+
current += ch
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
if (ch === '\\') {
|
|
80
|
+
// Outside quotes: escape the next character (incl. `&`, so `\&&`
|
|
81
|
+
// stays literal). Keep the backslash in the fragment — it is
|
|
82
|
+
// meaningful to the shell that later runs the stage.
|
|
83
|
+
current += ch + (script[i + 1] ?? '')
|
|
84
|
+
i++
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
if (ch === '"' || ch === "'") {
|
|
88
|
+
quote = ch
|
|
89
|
+
current += ch
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
if (ch === '&' && script[i + 1] === '&') {
|
|
93
|
+
stages.push(current)
|
|
94
|
+
current = ''
|
|
95
|
+
i++
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
current += ch
|
|
99
|
+
}
|
|
100
|
+
stages.push(current)
|
|
101
|
+
|
|
102
|
+
if (quote !== null) {
|
|
103
|
+
fail(`script "${name}" has an unterminated ${quote === '"' ? 'double' : 'single'} quote — sh would reject it; fix package.json`)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const trimmed = stages.map((s) => s.trim())
|
|
107
|
+
if (trimmed[trimmed.length - 1] === '') {
|
|
108
|
+
fail(`script "${name}" ends with a dangling "&&" — sh -e would reject it; fix package.json`)
|
|
109
|
+
}
|
|
110
|
+
for (const [i, stage] of trimmed.entries()) {
|
|
111
|
+
if (stage === '') {
|
|
112
|
+
fail(`script "${name}" has an empty command at position ${i + 1} (consecutive "&&")`)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
process.stdout.write(trimmed.join('\n') + '\n')
|