@xmoon76/dsh-pi-tui 0.3.4 → 0.3.5
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.en.md +648 -0
- package/README.md +557 -466
- package/dist/builtins.mjs +1 -13
- package/dist/extension/advanced.d.mts +1 -1
- package/dist/extension/unstable.d.mts +1 -1
- package/dist/extensions.d.mts +9 -6
- package/dist/extensions.mjs +1 -1
- package/dist/index.d.mts +2 -12
- package/dist/index.mjs +2 -2
- package/dist/{public-types-CZz5_arj.d.mts → public-types-_pe9ZH00.d.mts} +74 -8
- package/dist/{src-B1uP5j0d.mjs → src-B93JMbsv.mjs} +13497 -2012
- package/package.json +11 -6
- package/README.zh-CN.md +0 -469
- package/scripts/advanced-plugin-smoke.mjs +0 -314
- package/scripts/bench.mts +0 -302
- package/scripts/client-boundary-baseline.json +0 -82
- package/scripts/client-boundary-gate.mjs +0 -165
- package/scripts/e2e-session-lease.sh +0 -388
- package/scripts/examples-plugin-smoke.mjs +0 -320
- package/scripts/extension-fixture-smoke.mjs +0 -219
- package/scripts/naming-gate.mjs +0 -158
- package/scripts/phase4-plugin-smoke.mjs +0 -318
- package/scripts/pre-push-stages.mjs +0 -116
- package/scripts/preview-prompt.mts +0 -62
- package/scripts/preview-role-styles.mts +0 -97
- package/scripts/tarball-smoke.mjs +0 -393
- package/scripts/unstable-plugin-smoke.mjs +0 -300
- package/scripts/vim-plugin-smoke.mjs +0 -315
- /package/dist/{theme-registry-CjbtRmhd.d.mts → keybinding-registry-CjbtRmhd.d.mts} +0 -0
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Side-by-side preview of user-role visual schemes — repo code untouched,
|
|
3
|
-
* pure display. Run in a real TTY (tmux):
|
|
4
|
-
*
|
|
5
|
-
* env -u NO_COLOR node --import tsx/esm scripts/preview-role-styles.mts
|
|
6
|
-
*
|
|
7
|
-
* Shows the chosen scheme — background bubble with a brand-blue ❯ (dsh-web
|
|
8
|
-
* parity) — in both the dark palette and the light palette, next to the
|
|
9
|
-
* old kimi-style amber text for contrast, plus the editor-prompt colours.
|
|
10
|
-
* Ctrl+C exits.
|
|
11
|
-
* @module @xmoon76/dsh-pi-tui/preview-role-styles
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { Chalk } from 'chalk'
|
|
15
|
-
import {
|
|
16
|
-
ProcessTerminal, Text, TuiMainScreen, matchesKey,
|
|
17
|
-
visibleWidth, wrapTextWithAnsi, type TuiInputListenerResult,
|
|
18
|
-
} from '@xmoon76/pi-tui'
|
|
19
|
-
|
|
20
|
-
const chalk = new Chalk({ level: 3 })
|
|
21
|
-
const dim = (s: string): string => chalk.hex('#888888')(s)
|
|
22
|
-
const darkBlue = (s: string): string => chalk.bold.hex('#679EFE')(s)
|
|
23
|
-
const darkBubbleBg = (s: string): string => chalk.bgHex('#2C2C2F')(s)
|
|
24
|
-
const lightBlue = (s: string): string => chalk.bold.hex('#4177E6')(s)
|
|
25
|
-
const lightBubbleBg = (s: string): string => chalk.bgHex('#E4EDFD')(s)
|
|
26
|
-
const amber = (s: string): string => chalk.bold.hex('#FFCB6B')(s)
|
|
27
|
-
const border = (s: string): string => chalk.hex('#5A5A5A')(s)
|
|
28
|
-
|
|
29
|
-
const W = 100
|
|
30
|
-
const MSG_ONE = 'Check the config load order — ENOTFOUND at src/app.ts:42, likely a missing env var.'
|
|
31
|
-
const MSG_TWO = 'Also verify the .env.example keys against the docs, since the preset loader reads them in sequence.'
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Wrap `text` to `width - 2` cells. The first row carries the plain `❯ `
|
|
35
|
-
* marker, continuation rows indent 2 cells under it; every row is padded
|
|
36
|
-
* to the full inner width so a background paint covers the whole row.
|
|
37
|
-
*/
|
|
38
|
-
function wrapped(width: number, text: string): string[] {
|
|
39
|
-
const inner = Math.max(1, width - 2)
|
|
40
|
-
return wrapTextWithAnsi(text, inner).map((line, index) => {
|
|
41
|
-
const prefix = index === 0 ? '❯ ' : ' '
|
|
42
|
-
return prefix + line + ' '.repeat(Math.max(0, inner - visibleWidth(line)))
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Bubble rows: full-row background, coloured ❯, body in the terminal default. */
|
|
47
|
-
function bubble(width: number, text: string, bg: (s: string) => string, bullet: (s: string) => string): string[] {
|
|
48
|
-
return wrapped(width, text).map((row, index) => {
|
|
49
|
-
if (index === 0 && row.startsWith('❯ ')) {
|
|
50
|
-
return bg(bullet('❯') + ' ' + row.slice(2))
|
|
51
|
-
}
|
|
52
|
-
return bg(row)
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Old kimi look: the whole row (❯ + body + padding) in one amber paint. */
|
|
57
|
-
function amberRows(width: number, text: string): string[] {
|
|
58
|
-
return wrapped(width, text).map(amber)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const blocks: string[] = [
|
|
62
|
-
dim('dsh-pi-tui · user-role style preview (repo untouched) — Ctrl+C to exit'),
|
|
63
|
-
'',
|
|
64
|
-
dim('dark · bubble + brand-blue ❯ (chosen scheme)'),
|
|
65
|
-
...bubble(W, MSG_ONE, darkBubbleBg, darkBlue),
|
|
66
|
-
...bubble(W, MSG_TWO, darkBubbleBg, darkBlue),
|
|
67
|
-
'',
|
|
68
|
-
dim('light · bubble + deep brand-blue ❯ (light palette)'),
|
|
69
|
-
...bubble(W, MSG_ONE, lightBubbleBg, lightBlue),
|
|
70
|
-
...bubble(W, MSG_TWO, lightBubbleBg, lightBlue),
|
|
71
|
-
'',
|
|
72
|
-
dim('kimi amber text (rejected — kept for contrast)'),
|
|
73
|
-
...amberRows(W, MSG_ONE),
|
|
74
|
-
...amberRows(W, MSG_TWO),
|
|
75
|
-
'',
|
|
76
|
-
dim('editor prompt candidates:'),
|
|
77
|
-
border('─'.repeat(W)),
|
|
78
|
-
` ${darkBlue('❯')} git status ${lightBlue('❯')} git status ${amber('❯')} git status`,
|
|
79
|
-
border('─'.repeat(W)),
|
|
80
|
-
]
|
|
81
|
-
|
|
82
|
-
// Pad every line to the exact width so nothing wraps.
|
|
83
|
-
const preview = blocks
|
|
84
|
-
.map(line => line + ' '.repeat(Math.max(0, W - visibleWidth(line))))
|
|
85
|
-
.join('\n')
|
|
86
|
-
|
|
87
|
-
const terminal = new ProcessTerminal()
|
|
88
|
-
const tui = new TuiMainScreen(terminal)
|
|
89
|
-
tui.addChild(new Text(preview, 0, 0))
|
|
90
|
-
tui.addInputListener((data): TuiInputListenerResult => {
|
|
91
|
-
if (matchesKey(data, 'ctrl+c')) {
|
|
92
|
-
tui.stop()
|
|
93
|
-
process.exit(0)
|
|
94
|
-
}
|
|
95
|
-
return undefined
|
|
96
|
-
})
|
|
97
|
-
tui.start()
|
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* @xmoon76/dsh-pi-tui/scripts/tarball-smoke — verify a packed
|
|
4
|
-
* `@xmoon76/dsh-pi-tui` tarball end to end, so a fresh checkout can never
|
|
5
|
-
* publish a broken or leaky artifact:
|
|
6
|
-
*
|
|
7
|
-
* 1. structure — every `exports` file exists, config + cordis.patch.yml +
|
|
8
|
-
* repair scripts are included, nothing else leaks in (no test fixtures,
|
|
9
|
-
* no backups, no nested tarballs, no absolute paths);
|
|
10
|
-
* 2. content — no workspace absolute paths anywhere, and the bundle does
|
|
11
|
-
* NOT import `@xmoon76/pi-tui` externally (it is bundled);
|
|
12
|
-
* 3. parse — every `.mjs` passes `node --check`; every `.d.mts` parses via
|
|
13
|
-
* the TypeScript compiler when available;
|
|
14
|
-
* 4. install — the tarball installs standalone (`npm install --omit=dev`
|
|
15
|
-
* in a fresh temp project), both `exports` entries import, and
|
|
16
|
-
* `@xmoon76/pi-tui` is NOT resolvable from the installed package;
|
|
17
|
-
* 5. repair CLI — `--help` works and a read-only `--scan` over fixture
|
|
18
|
-
* sessions reports the damaged one and touches nothing.
|
|
19
|
-
*
|
|
20
|
-
* Usage: node scripts/tarball-smoke.mjs [path-to-tgz]
|
|
21
|
-
* The tarball path defaults to the newest `xmoon76-dsh-pi-tui-*.tgz` in
|
|
22
|
-
* the current directory (the `pnpm pack` / `npm pack` output location;
|
|
23
|
-
* npm/pnpm strip the `@` scope from tarball filenames).
|
|
24
|
-
*
|
|
25
|
-
* Env: TARBALL_SMOKE_SKIP_INSTALL=1 skips the npm install step (offline
|
|
26
|
-
* dev runs); TARBALL_SMOKE_KEEP=1 keeps the temp dirs for inspection.
|
|
27
|
-
* @module tarball-smoke
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
import { spawnSync } from 'node:child_process'
|
|
31
|
-
import {
|
|
32
|
-
existsSync,
|
|
33
|
-
mkdirSync,
|
|
34
|
-
mkdtempSync,
|
|
35
|
-
readdirSync,
|
|
36
|
-
readFileSync,
|
|
37
|
-
realpathSync,
|
|
38
|
-
rmSync,
|
|
39
|
-
statSync,
|
|
40
|
-
symlinkSync,
|
|
41
|
-
writeFileSync,
|
|
42
|
-
} from 'node:fs'
|
|
43
|
-
import { tmpdir } from 'node:os'
|
|
44
|
-
import { basename, dirname, join, resolve } from 'node:path'
|
|
45
|
-
import { fileURLToPath } from 'node:url'
|
|
46
|
-
import { createRequire } from 'node:module'
|
|
47
|
-
import { zstdCompressSync } from 'node:zlib'
|
|
48
|
-
|
|
49
|
-
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url))
|
|
50
|
-
/** The dsh-pi-tui package root (the workspace checkout). */
|
|
51
|
-
const PACKAGE_ROOT = join(SCRIPT_DIR, '..')
|
|
52
|
-
|
|
53
|
-
const failures = []
|
|
54
|
-
const checks = []
|
|
55
|
-
|
|
56
|
-
/** Record a passing check or a hard failure (failures exit non-zero at the end). */
|
|
57
|
-
function check(name, ok, detail = '') {
|
|
58
|
-
checks.push(`${ok ? 'ok ' : 'FAIL'} ${name}${detail === '' ? '' : ` — ${detail}`}`)
|
|
59
|
-
if (!ok) failures.push(`${name}${detail === '' ? '' : `: ${detail}`}`)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function run(command, args, options = {}) {
|
|
63
|
-
return spawnSync(command, args, { encoding: 'utf8', ...options })
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Resolve the tarball: explicit arg, else the newest matching tgz in the
|
|
67
|
-
* current directory or its parent (pnpm workspace packs may land either
|
|
68
|
-
* place depending on how the lifecycle was invoked). */
|
|
69
|
-
function resolveTarball(explicit) {
|
|
70
|
-
if (explicit !== undefined) {
|
|
71
|
-
// Absolutize BEFORE any subprocess uses it: `npm install` below runs
|
|
72
|
-
// with cwd=probeDir, so a caller-relative path (e.g. the CI artifact
|
|
73
|
-
// download `tarball/xmoon76-*.tgz`) would resolve against the temp
|
|
74
|
-
// probe and fail with ENOENT.
|
|
75
|
-
const absolute = resolve(explicit)
|
|
76
|
-
if (!existsSync(absolute)) throw new Error(`tarball not found: ${explicit}`)
|
|
77
|
-
return absolute
|
|
78
|
-
}
|
|
79
|
-
// npm/pnpm strip the `@` scope from tarball filenames, so the glob is
|
|
80
|
-
// `xmoon76-dsh-pi-tui-*.tgz` even though the package is @xmoon76/dsh-pi-tui.
|
|
81
|
-
const candidates = [process.cwd(), join(process.cwd(), '..')]
|
|
82
|
-
.flatMap(dir => {
|
|
83
|
-
try {
|
|
84
|
-
return readdirSync(dir).map(name => join(dir, name))
|
|
85
|
-
} catch {
|
|
86
|
-
return []
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
.filter(path => /xmoon76-dsh-pi-tui-.*\.tgz$/.test(path))
|
|
90
|
-
.sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs)
|
|
91
|
-
if (candidates.length === 0) throw new Error(`no xmoon76-dsh-pi-tui-*.tgz in the current directory (${process.cwd()}) or its parent (${join(process.cwd(), '..')}); pass the tarball path`)
|
|
92
|
-
return candidates[0]
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** Extract an npm tarball; returns the `package/` directory. */
|
|
96
|
-
function extract(tarball, workDir) {
|
|
97
|
-
const result = run('tar', ['-xzf', tarball, '-C', workDir])
|
|
98
|
-
if (result.status !== 0) throw new Error(`tar extraction failed: ${result.stderr}`)
|
|
99
|
-
const extracted = join(workDir, 'package')
|
|
100
|
-
if (!existsSync(extracted)) throw new Error('tarball has no package/ root (not an npm tarball?)')
|
|
101
|
-
return extracted
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Walk a directory tree, returning relative file paths. */
|
|
105
|
-
function walkFiles(root) {
|
|
106
|
-
const out = []
|
|
107
|
-
const walk = (dir, prefix) => {
|
|
108
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
109
|
-
const rel = prefix === '' ? entry.name : `${prefix}/${entry.name}`
|
|
110
|
-
if (entry.isDirectory()) walk(join(dir, entry.name), rel)
|
|
111
|
-
else out.push(rel)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
walk(root, '')
|
|
115
|
-
return out
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** Load the TypeScript compiler for parse-only validation, when available. */
|
|
119
|
-
function loadTypescript() {
|
|
120
|
-
try {
|
|
121
|
-
return createRequire(join(PACKAGE_ROOT, 'package.json'))('typescript')
|
|
122
|
-
} catch {
|
|
123
|
-
return undefined
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/** Parse-only check of a .d.mts file: no resolution, syntax errors only. */
|
|
128
|
-
function dtsParses(ts, file) {
|
|
129
|
-
const text = readFileSync(file, 'utf8')
|
|
130
|
-
const source = ts.createSourceFile(file, text, ts.ScriptTarget.ES2022, true, ts.ScriptKind.TS)
|
|
131
|
-
return source.parseDiagnostics.length === 0
|
|
132
|
-
? { ok: true }
|
|
133
|
-
: { ok: false, detail: source.parseDiagnostics[0].messageText }
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function main() {
|
|
137
|
-
const tarball = resolveTarball(process.argv[2])
|
|
138
|
-
const keep = process.env.TARBALL_SMOKE_KEEP === '1'
|
|
139
|
-
const skipInstall = process.env.TARBALL_SMOKE_SKIP_INSTALL === '1'
|
|
140
|
-
const workDir = mkdtempSync(join(tmpdir(), 'tarball-smoke-'))
|
|
141
|
-
const probeDir = join(workDir, 'probe')
|
|
142
|
-
try {
|
|
143
|
-
check('tarball exists', existsSync(tarball), basename(tarball))
|
|
144
|
-
const extracted = extract(tarball, workDir)
|
|
145
|
-
const pkg = JSON.parse(readFileSync(join(extracted, 'package.json'), 'utf8'))
|
|
146
|
-
check('package name', pkg.name === '@xmoon76/dsh-pi-tui', pkg.name)
|
|
147
|
-
check('published package is not private', pkg.private !== true, `private=${String(pkg.private)}`)
|
|
148
|
-
|
|
149
|
-
// --- structure ---
|
|
150
|
-
const files = walkFiles(extracted)
|
|
151
|
-
const has = (path) => files.includes(path)
|
|
152
|
-
check('exports entry dist/extension/advanced.mjs', has('dist/extension/advanced.mjs'))
|
|
153
|
-
check('exports entry dist/extension/unstable.mjs', has('dist/extension/unstable.mjs'))
|
|
154
|
-
check('types dist/extension/advanced.d.mts', has('dist/extension/advanced.d.mts'))
|
|
155
|
-
check('types dist/extension/unstable.d.mts', has('dist/extension/unstable.d.mts'))
|
|
156
|
-
check('exports entry dist/builtins.mjs', has('dist/builtins.mjs'))
|
|
157
|
-
check('types dist/builtins.d.mts', has('dist/builtins.d.mts'))
|
|
158
|
-
check('cordis.patch.yml included', has('cordis.patch.yml'))
|
|
159
|
-
check('config/ included', files.some(name => name.startsWith('config/')))
|
|
160
|
-
check('repair-session.mjs included', has('scripts/repair-session.mjs'))
|
|
161
|
-
check('repair-core.mjs included', has('scripts/repair-core.mjs'))
|
|
162
|
-
check('README included', has('README.md'))
|
|
163
|
-
const leaks = files.filter(name => name.includes('test/')
|
|
164
|
-
|| name.endsWith('.bak')
|
|
165
|
-
|| name.endsWith('.tgz')
|
|
166
|
-
|| /^\//.test(name)
|
|
167
|
-
|| name.includes('..'))
|
|
168
|
-
check('no test fixtures / backups / nested tarballs / absolute paths', leaks.length === 0,
|
|
169
|
-
leaks.length === 0 ? '' : leaks.join(', '))
|
|
170
|
-
|
|
171
|
-
// --- content ---
|
|
172
|
-
const textFiles = files
|
|
173
|
-
.filter(name => /\.(mjs|ts|yml|yaml|json|md)$/.test(name))
|
|
174
|
-
.map(name => join(extracted, name))
|
|
175
|
-
// Leak detection is DYNAMIC, not tied to this machine: the runtime-
|
|
176
|
-
// resolved repo/package/dist roots (realpath) catch THIS checkout's
|
|
177
|
-
// absolute paths on any machine, and the generic patterns catch the
|
|
178
|
-
// common absolute-path shapes (Unix home, macOS Users, CI workspaces,
|
|
179
|
-
// Windows drives) everywhere. The smoke script itself is exempt from
|
|
180
|
-
// the generic patterns: its source deliberately assembles the old
|
|
181
|
-
// /home/xmoon/ marker at runtime (the literal would otherwise trip the
|
|
182
|
-
// check on its own file). The dynamic-root checks still apply to it.
|
|
183
|
-
// After the root-package migration the repository root IS the package
|
|
184
|
-
// root, so both resolve from SCRIPT_DIR with a single `..`.
|
|
185
|
-
const repoRoot = realpathSync(join(SCRIPT_DIR, '..'))
|
|
186
|
-
const packageRoot = realpathSync(PACKAGE_ROOT)
|
|
187
|
-
// The bundle dist may be absent (a smoke run on a raw checkout); the
|
|
188
|
-
// structure checks below already fail loudly for that, so never crash
|
|
189
|
-
// here — realpathSync on a missing dir would throw ENOENT and turn the
|
|
190
|
-
// failure into a bare `tarball-smoke: ...` stderr line.
|
|
191
|
-
const distRoot = join(PACKAGE_ROOT, 'dist')
|
|
192
|
-
const leakRoots = [...new Set([
|
|
193
|
-
repoRoot,
|
|
194
|
-
packageRoot,
|
|
195
|
-
...(existsSync(distRoot) ? [realpathSync(distRoot)] : []),
|
|
196
|
-
])]
|
|
197
|
-
const absolutePathPatterns = [
|
|
198
|
-
/\/home\/[^/"']+/,
|
|
199
|
-
/\/Users\/[^/"']+/,
|
|
200
|
-
/\/home\/runner\/work\//,
|
|
201
|
-
/[A-Za-z]:\\(?:[^\\"']*\\)+/,
|
|
202
|
-
]
|
|
203
|
-
const workspaceLeaks = textFiles.filter(file => {
|
|
204
|
-
const text = readFileSync(file, 'utf8')
|
|
205
|
-
if (leakRoots.some(root => root.length > 1 && text.includes(root))) return true
|
|
206
|
-
if (basename(file) === 'tarball-smoke.mjs') return false
|
|
207
|
-
return absolutePathPatterns.some(pattern => pattern.test(text))
|
|
208
|
-
})
|
|
209
|
-
check('no workspace absolute paths in packaged files', workspaceLeaks.length === 0,
|
|
210
|
-
workspaceLeaks.length === 0 ? '' : workspaceLeaks.join(', '))
|
|
211
|
-
const dist = files.filter(name => name.startsWith('dist/') && name.endsWith('.mjs'))
|
|
212
|
-
.map(name => readFileSync(join(extracted, name), 'utf8'))
|
|
213
|
-
const externalFork = dist.filter(text => /from\s*["']@xmoon76\/pi-tui["']/.test(text) || /require\(\s*["']@xmoon76\/pi-tui["']/.test(text))
|
|
214
|
-
check('bundle does not import @xmoon76/pi-tui externally', externalFork.length === 0,
|
|
215
|
-
externalFork.length === 0 ? '' : `${externalFork.length} file(s) still import the fork`)
|
|
216
|
-
|
|
217
|
-
// --- parse ---
|
|
218
|
-
const mjsFiles = files.filter(name => /\.mjs$/.test(name)).map(name => join(extracted, name))
|
|
219
|
-
const badMjs = mjsFiles.filter(file => run(process.execPath, ['--check', file]).status !== 0)
|
|
220
|
-
check('every .mjs parses (node --check)', badMjs.length === 0,
|
|
221
|
-
badMjs.length === 0 ? '' : badMjs.join(', '))
|
|
222
|
-
const ts = loadTypescript()
|
|
223
|
-
const dtsFiles = files.filter(name => /\.d\.mts$/.test(name)).map(name => join(extracted, name))
|
|
224
|
-
if (ts !== undefined) {
|
|
225
|
-
const badDts = dtsFiles.filter(file => !dtsParses(ts, file).ok)
|
|
226
|
-
check('every .d.mts parses (typescript)', badDts.length === 0,
|
|
227
|
-
badDts.length === 0 ? '' : badDts.join(', '))
|
|
228
|
-
} else {
|
|
229
|
-
checks.push(`skip .d.mts parse — typescript unavailable`)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// --- public .d.mts leak gate (M3/E, round-1/2 review): the shipped
|
|
233
|
-
// declarations must not reference the vendored fork, TuiApp, private
|
|
234
|
-
// screens, or repository source paths. tsdown INLINES types into the
|
|
235
|
-
// .d.mts with `//#region <path>` markers; a fork leak shows up as a
|
|
236
|
-
// region pointing OUTSIDE the package's own src/ (`../pi-tui/...`,
|
|
237
|
-
// `node_modules/@xmoon76/pi-tui/...`) or as fork module names
|
|
238
|
-
// (`src/tui-main-screen.d.ts`), often inside an auto-generated SHARED
|
|
239
|
-
// CHUNK that the SDK entries only re-export. The gate therefore:
|
|
240
|
-
// (a) scans EVERY dist/*.d.mts (entries AND chunks);
|
|
241
|
-
// (b) treats any region marker whose path is not this package's own
|
|
242
|
-
// src/ as a leak (allowlist by construction);
|
|
243
|
-
// (c) bans fork imports and fork module names anywhere;
|
|
244
|
-
// (d) bans the bare private identifiers (TuiMainScreen/TuiAltScreen/
|
|
245
|
-
// TuiApp) line-scoped over every file, skipping comment lines.
|
|
246
|
-
const dtsAll = files.filter(name => name.startsWith('dist/') && name.endsWith('.d.mts'))
|
|
247
|
-
const sdkIdentifiers = [/\bTuiMainScreen\b/, /\bTuiAltScreen\b/, /\bTuiApp\b/]
|
|
248
|
-
const dtsLeaks = []
|
|
249
|
-
for (const name of dtsAll) {
|
|
250
|
-
const text = readFileSync(join(extracted, name), 'utf8')
|
|
251
|
-
const lines = text.split('\n')
|
|
252
|
-
// (b) region markers: allow only `//#region src/<public module>.d.ts`.
|
|
253
|
-
// The ROOT entry (dist/index.d.mts) is the runner's own surface: it
|
|
254
|
-
// legitimately inlines the TuiApp implementation (src/tui-app.d.ts)
|
|
255
|
-
// for the Loader row — it is NOT a third-party SDK entry. Its region
|
|
256
|
-
// is exempted; everything else (chunks, extensions, builtins) must
|
|
257
|
-
// name only the public modules below. The image modules
|
|
258
|
-
// (src/image/admission.d.ts, src/image/types.d.ts) are also
|
|
259
|
-
// legitimately inlined into the root entry: the public
|
|
260
|
-
// `commandRejectsImages` signature references `DraftImageStoreLike`,
|
|
261
|
-
// which pulls in the draft vocabulary (and the recalled-ref shape).
|
|
262
|
-
const isRoot = name === 'dist/index.d.mts'
|
|
263
|
-
for (const line of lines) {
|
|
264
|
-
const match = /\/\/#region\s+(\S+)/.exec(line)
|
|
265
|
-
if (match) {
|
|
266
|
-
const regionPath = match[1]
|
|
267
|
-
const allowed = /^src\/(builtins|commands|diag|extension\/advanced|extension\/advanced-types|extension\/public-types|extension\/service|extension\/slot-map|extension\/unstable|extension\/unstable-types|extensions|image\/admission|image\/types|index|skill-catalog|startup|surface-catalog)\.d\.ts$/.test(regionPath)
|
|
268
|
-
const rootAllowed = isRoot && /^src\/tui-app\.d\.ts$/.test(regionPath)
|
|
269
|
-
if (!allowed && !rootAllowed) dtsLeaks.push(`${name}: region ${regionPath}`)
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
// (c) fork imports and fork module names anywhere.
|
|
273
|
-
if (/from\s*["']@xmoon76\/pi-tui["']/.test(text)) dtsLeaks.push(`${name}: fork import`)
|
|
274
|
-
if (/packages\/pi-tui/.test(text)) dtsLeaks.push(`${name}: packages/pi-tui path`)
|
|
275
|
-
if (/\b(tui-main-screen|tui-alt-screen)\.d\.ts\b/.test(text)) dtsLeaks.push(`${name}: fork screen module`)
|
|
276
|
-
// (d) bare private identifiers, line-scoped (comments skipped). No
|
|
277
|
-
// exemption: the clean dist has zero TuiApp identifiers anywhere
|
|
278
|
-
// (round-3 finding 1 — the old root exemption was dead code).
|
|
279
|
-
for (const line of lines) {
|
|
280
|
-
const trimmed = line.trim()
|
|
281
|
-
if (trimmed.startsWith('*') || trimmed.startsWith('//') || trimmed.startsWith('/*')) continue
|
|
282
|
-
for (const pattern of sdkIdentifiers) {
|
|
283
|
-
if (pattern.test(trimmed)) dtsLeaks.push(`${name}: bare ${pattern}`)
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
check('every public .d.mts leaks no private pi-tui / internal paths', dtsLeaks.length === 0,
|
|
288
|
-
dtsLeaks.length === 0 ? '' : dtsLeaks.join('; '))
|
|
289
|
-
// The .mjs bodies must not import the fork externally either (it is
|
|
290
|
-
// bundled) — the existing dist check below covers every .mjs.
|
|
291
|
-
|
|
292
|
-
// --- install + runtime ---
|
|
293
|
-
if (skipInstall) {
|
|
294
|
-
checks.push('skip npm install (TARBALL_SMOKE_SKIP_INSTALL=1)')
|
|
295
|
-
} else {
|
|
296
|
-
mkdirSync(probeDir)
|
|
297
|
-
writeFileSync(join(probeDir, 'package.json'), JSON.stringify({ name: 'tarball-smoke-probe', private: true }))
|
|
298
|
-
const install = run('npm', [
|
|
299
|
-
'install', '--omit=dev', '--no-save',
|
|
300
|
-
'--cache', join(workDir, 'npm-cache'),
|
|
301
|
-
tarball,
|
|
302
|
-
], { cwd: probeDir, timeout: 180_000 })
|
|
303
|
-
check('tarball installs standalone (npm install --omit=dev)', install.status === 0,
|
|
304
|
-
install.status === 0 ? '' : (install.stderr.split('\n').slice(-3).join(' ')))
|
|
305
|
-
if (install.status === 0) {
|
|
306
|
-
const installed = join(probeDir, 'node_modules', '@xmoon76', 'dsh-pi-tui')
|
|
307
|
-
check('installed package contains dist', existsSync(join(installed, 'dist', 'index.mjs')))
|
|
308
|
-
const importRun = run(process.execPath, ['--input-type=module', '-e',
|
|
309
|
-
"Promise.all([import('@xmoon76/dsh-pi-tui'), import('@xmoon76/dsh-pi-tui/startup'),"
|
|
310
|
-
+ "import('@xmoon76/dsh-pi-tui/extensions'), import('@xmoon76/dsh-pi-tui/builtins'),"
|
|
311
|
-
+ "import('@xmoon76/dsh-pi-tui/extensions/advanced'), import('@xmoon76/dsh-pi-tui/extensions/unstable')])"
|
|
312
|
-
+ ".then(m => { if (m[4].ADVANCED_API_LEVEL !== 1) throw new Error('ADVANCED_API_LEVEL');"
|
|
313
|
-
+ "if (m[5].UNSTABLE_API_LEVEL !== 1) throw new Error('UNSTABLE_API_LEVEL'); console.log('imports-ok') })"
|
|
314
|
-
+ ".catch(e => { console.error(e.message); process.exit(1) })",
|
|
315
|
-
], { cwd: probeDir })
|
|
316
|
-
check('all exports entries import', importRun.status === 0 && importRun.stdout.includes('imports-ok'),
|
|
317
|
-
importRun.status === 0 ? '' : importRun.stderr.slice(0, 200))
|
|
318
|
-
const forkRun = run(process.execPath, ['--input-type=module', '-e',
|
|
319
|
-
"import('@xmoon76/pi-tui').then(() => process.exit(0)).catch(() => process.exit(3))",
|
|
320
|
-
], { cwd: probeDir })
|
|
321
|
-
check('@xmoon76/pi-tui is not resolvable from the installed package (self-contained)',
|
|
322
|
-
forkRun.status === 3, `status=${forkRun.status}`)
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// --- repair CLI from the installed package ---
|
|
326
|
-
const cli = join(probeDir, 'node_modules', '@xmoon76', 'dsh-pi-tui', 'scripts', 'repair-session.mjs')
|
|
327
|
-
if (existsSync(cli)) {
|
|
328
|
-
const help = run(process.execPath, [cli, '--help'])
|
|
329
|
-
check('installed repair CLI shows help', help.status === 0 && help.stdout.includes('usage:'),
|
|
330
|
-
help.status === 0 ? '' : help.stderr.slice(0, 200))
|
|
331
|
-
// A read-only scan over fixtures: healthy + torn sessions. The CLI
|
|
332
|
-
// needs a dsh stub whose node_modules resolves @deepseek-ai/dsh-session
|
|
333
|
-
// (npm installed the peer deps into the probe).
|
|
334
|
-
const dshSession = join(probeDir, 'node_modules', '@deepseek-ai', 'dsh-session')
|
|
335
|
-
if (existsSync(dshSession)) {
|
|
336
|
-
const home = join(workDir, 'home')
|
|
337
|
-
const stub = join(workDir, 'dsh-stub')
|
|
338
|
-
const torn = join(home, 'sessions', 'proj', 'torn-session')
|
|
339
|
-
const healthy = join(home, 'sessions', 'proj', 'ok-session')
|
|
340
|
-
mkdirSync(join(stub, 'node_modules', '@deepseek-ai'), { recursive: true })
|
|
341
|
-
writeFileSync(join(stub, 'package.json'), JSON.stringify({ name: '@deepseek-ai/dsh', version: '0.0.0' }))
|
|
342
|
-
symlinkSync(dshSession, join(stub, 'node_modules', '@deepseek-ai', 'dsh-session'), 'dir')
|
|
343
|
-
mkdirSync(torn, { recursive: true })
|
|
344
|
-
mkdirSync(healthy, { recursive: true })
|
|
345
|
-
const HEADER = JSON.stringify({
|
|
346
|
-
type: 'session', version: 0, id: 'torn-session', createdAt: 1,
|
|
347
|
-
cwd: '/work', delegationDepth: 0, agentPreset: 'standard',
|
|
348
|
-
})
|
|
349
|
-
const text = [HEADER,
|
|
350
|
-
JSON.stringify({ type: 'permission/preset', seq: 0, time: 1, data: { preset: 'workspace-write' } }),
|
|
351
|
-
JSON.stringify({ type: 'user/message', seq: 1, time: 2, data: { content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } } }),
|
|
352
|
-
].join('\n')
|
|
353
|
-
writeFileSync(join(healthy, 'session.jsonl'), `${text}\n`)
|
|
354
|
-
// A torn zstd artifact: complete frames + a truncated frame tail.
|
|
355
|
-
const full = Buffer.concat([
|
|
356
|
-
zstdCompressSync(Buffer.from(`${HEADER}\n`, 'utf8')),
|
|
357
|
-
zstdCompressSync(Buffer.from(`${text.slice(HEADER.length + 1)}\n`, 'utf8')),
|
|
358
|
-
zstdCompressSync(Buffer.from('{"type":"user/message","seq":2,"time":3,"data":{"content":[{"type":"text","text":"lost"}],"source":{"kind":"user"}}}\n', 'utf8')),
|
|
359
|
-
])
|
|
360
|
-
writeFileSync(join(torn, 'session.jsonl.zstd'), full.subarray(0, full.length - 9))
|
|
361
|
-
const scan = run(process.execPath, [cli, '--scan', '--dsh-dir', stub, '--dsh-home', home])
|
|
362
|
-
check('installed repair CLI --scan reports the torn session and ignores the healthy one',
|
|
363
|
-
scan.status === 1 && scan.stdout.includes('torn-session') && !scan.stdout.includes('ok-session'),
|
|
364
|
-
`status=${scan.status} stdout=${scan.stdout.trim().slice(0, 200)}`)
|
|
365
|
-
const before = readFileSync(join(torn, 'session.jsonl.zstd'))
|
|
366
|
-
const dry = run(process.execPath, [cli, 'torn-session', '--dsh-dir', stub, '--dsh-home', home])
|
|
367
|
-
const after = readFileSync(join(torn, 'session.jsonl.zstd'))
|
|
368
|
-
check('installed repair CLI dry run is read-only', dry.status === 1 && before.equals(after),
|
|
369
|
-
`status=${dry.status} ${dry.stdout.trim().slice(0, 120)}`)
|
|
370
|
-
} else {
|
|
371
|
-
checks.push('skip repair CLI scan — peer deps not installed in probe')
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
} finally {
|
|
376
|
-
if (!keep) rmSync(workDir, { recursive: true, force: true })
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
for (const line of checks) console.log(line)
|
|
380
|
-
if (failures.length > 0) {
|
|
381
|
-
console.error(`\ntarball smoke FAILED (${failures.length}):`)
|
|
382
|
-
for (const failure of failures) console.error(` - ${failure}`)
|
|
383
|
-
process.exit(1)
|
|
384
|
-
}
|
|
385
|
-
console.log('\ntarball smoke passed')
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
try {
|
|
389
|
-
await main()
|
|
390
|
-
} catch (error) {
|
|
391
|
-
console.error(`tarball-smoke: ${error instanceof Error ? error.message : String(error)}`)
|
|
392
|
-
process.exit(1)
|
|
393
|
-
}
|