@xmoon76/dsh-pi-tui 0.1.8 → 0.2.0
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/config/agent-presets/code/agent.cordis.yml +6 -5
- package/config/agent-presets/cordis/agent.cordis.yml +6 -5
- package/config/agent-presets/cordis/skills/editing-cordis-compositions/SKILL.md +3 -3
- package/config/agent-presets/minimal/agent.cordis.yml +27 -1
- package/config/agent-presets/standard/agent.cordis.yml +6 -5
- package/cordis.patch.yml +14 -1
- package/dist/builtins.d.mts +18 -0
- package/dist/builtins.mjs +75 -0
- package/dist/extension/advanced.d.mts +372 -0
- package/dist/extension/advanced.mjs +46 -0
- package/dist/extension/unstable.d.mts +206 -0
- package/dist/extension/unstable.mjs +30 -0
- package/dist/extensions.d.mts +132 -0
- package/dist/extensions.mjs +3 -0
- package/dist/index.d.mts +63 -3
- package/dist/index.mjs +3 -22739
- package/dist/public-types-2omeXG8d.d.mts +740 -0
- package/dist/src-AgdnRnGA.mjs +30851 -0
- package/dist/theme-registry-CjbtRmhd.d.mts +1 -0
- package/package.json +81 -55
- package/scripts/advanced-plugin-smoke.mjs +314 -0
- package/scripts/bench.mts +40 -5
- package/scripts/examples-plugin-smoke.mjs +320 -0
- package/scripts/extension-fixture-smoke.mjs +219 -0
- package/scripts/phase4-plugin-smoke.mjs +318 -0
- package/scripts/tarball-smoke.mjs +69 -7
- package/scripts/unstable-plugin-smoke.mjs +300 -0
- package/scripts/vim-plugin-smoke.mjs +315 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* phase4-plugin-smoke — verify the PACKED Phase-4 advanced surface end
|
|
4
|
+
* to end with a real third-party-shaped plugin (Phase 4, plan §17):
|
|
5
|
+
*
|
|
6
|
+
* 1. the fixture imports ONLY the public `@xmoon76/dsh-pi-tui/
|
|
7
|
+
* extensions/advanced` (and the stable `extensions` entry for the
|
|
8
|
+
* service type) subpaths — importing `@xmoon76/pi-tui`, `src/tui-app`
|
|
9
|
+
* or any repository-relative internal path FAILS the gate (static
|
|
10
|
+
* imports, dynamic import() AND require() are all scanned);
|
|
11
|
+
* 2. the fixture typechecks against the packed `.d.mts`;
|
|
12
|
+
* 3. the fixture COMPILES to JS and RUNTIME-LOADS against the packed
|
|
13
|
+
* bytes (apply() registers through a mock service without throwing);
|
|
14
|
+
* 4. the fixture exercises the Phase-4 surface: the imperative UI
|
|
15
|
+
* broker (select/confirm/input/notify), custom interactive UI
|
|
16
|
+
* (ui.custom), and the host-state facade (theme/title/working/
|
|
17
|
+
* tools-expanded);
|
|
18
|
+
* 5. the fixture's module shape (name/inject/apply) matches the Loader;
|
|
19
|
+
* 6. no duplicate dsh runtime in the fixture tree.
|
|
20
|
+
*
|
|
21
|
+
* The probe mirrors the host install with SYMLINKS (no npm install — the
|
|
22
|
+
* gate is offline-safe; the packed package's runtime deps resolve from
|
|
23
|
+
* the repo's node_modules exactly like a real profile).
|
|
24
|
+
*
|
|
25
|
+
* Usage: node scripts/phase4-plugin-smoke.mjs [path-to-tgz]
|
|
26
|
+
* @module phase4-plugin-smoke
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { spawnSync } from 'node:child_process'
|
|
30
|
+
import {
|
|
31
|
+
existsSync,
|
|
32
|
+
mkdirSync,
|
|
33
|
+
mkdtempSync,
|
|
34
|
+
readdirSync,
|
|
35
|
+
readFileSync,
|
|
36
|
+
realpathSync,
|
|
37
|
+
rmSync,
|
|
38
|
+
statSync,
|
|
39
|
+
lstatSync,
|
|
40
|
+
symlinkSync,
|
|
41
|
+
writeFileSync,
|
|
42
|
+
} from 'node:fs'
|
|
43
|
+
import { tmpdir } from 'node:os'
|
|
44
|
+
import { dirname, join, resolve } from 'node:path'
|
|
45
|
+
import { fileURLToPath } from 'node:url'
|
|
46
|
+
|
|
47
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url))
|
|
48
|
+
const PACKAGE_ROOT = join(SCRIPT_DIR, '..')
|
|
49
|
+
const FIXTURE_ROOT = join(PACKAGE_ROOT, 'test', 'fixtures', 'phase4-plugin')
|
|
50
|
+
|
|
51
|
+
const failures = []
|
|
52
|
+
const checks = []
|
|
53
|
+
|
|
54
|
+
function check(name, ok, detail = '') {
|
|
55
|
+
checks.push(`${ok ? 'ok ' : 'FAIL'} ${name}${detail === '' ? '' : ` — ${detail}`}`)
|
|
56
|
+
if (!ok) failures.push(`${name}${detail === '' ? '' : `: ${detail}`}`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function run(command, args, options = {}) {
|
|
60
|
+
return spawnSync(command, args, { encoding: 'utf8', ...options })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveTarball(explicit) {
|
|
64
|
+
if (explicit !== undefined) {
|
|
65
|
+
const absolute = resolve(explicit)
|
|
66
|
+
if (!existsSync(absolute)) throw new Error(`tarball not found: ${explicit}`)
|
|
67
|
+
return absolute
|
|
68
|
+
}
|
|
69
|
+
const dir = PACKAGE_ROOT
|
|
70
|
+
const candidates = readdirSync(dir)
|
|
71
|
+
.filter(name => /xmoon76-dsh-pi-tui-.*\.tgz$/.test(name))
|
|
72
|
+
.map(name => join(dir, name))
|
|
73
|
+
.sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs)
|
|
74
|
+
if (candidates.length === 0) {
|
|
75
|
+
throw new Error(`no xmoon76-dsh-pi-tui-*.tgz in ${dir}; run pnpm pack:release first`)
|
|
76
|
+
}
|
|
77
|
+
return candidates[0]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function main() {
|
|
81
|
+
const tarball = resolveTarball(process.argv[2])
|
|
82
|
+
const workDir = mkdtempSync(join(tmpdir(), 'phase4-plugin-'))
|
|
83
|
+
try {
|
|
84
|
+
// 1. Extract the packed package.
|
|
85
|
+
const extractedDir = join(workDir, 'pkg')
|
|
86
|
+
mkdirSync(extractedDir)
|
|
87
|
+
const extract = run('tar', ['-xzf', tarball, '-C', extractedDir])
|
|
88
|
+
check('tarball extracts', extract.status === 0, extract.stderr)
|
|
89
|
+
const pkgDir = join(extractedDir, 'package')
|
|
90
|
+
check('packed package has dist/extension/advanced.mjs', existsSync(join(pkgDir, 'dist', 'extension', 'advanced.mjs')))
|
|
91
|
+
check('packed package has dist/extension/advanced.d.mts', existsSync(join(pkgDir, 'dist', 'extension', 'advanced.d.mts')))
|
|
92
|
+
|
|
93
|
+
// 2. IMPORT BAN (plan §15 CI gate): the fixture's IMPORT STATEMENTS
|
|
94
|
+
// (multi-line blocks), dynamic import() and require() specifiers
|
|
95
|
+
// must never reference private internals.
|
|
96
|
+
const fixtureSrc = readFileSync(join(FIXTURE_ROOT, 'src', 'index.ts'), 'utf8')
|
|
97
|
+
const lines = fixtureSrc.split('\n')
|
|
98
|
+
const importStatements = []
|
|
99
|
+
for (let index = 0; index < lines.length; index++) {
|
|
100
|
+
if (/^\s*import\b/.test(lines[index] ?? '')) {
|
|
101
|
+
let statement = lines[index] ?? ''
|
|
102
|
+
while (!statement.includes(';') && index + 1 < lines.length) {
|
|
103
|
+
index += 1
|
|
104
|
+
statement += lines[index]
|
|
105
|
+
}
|
|
106
|
+
importStatements.push(statement)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const importLines = importStatements.join('\n')
|
|
110
|
+
const dynamicSpecifiers = [...fixtureSrc.matchAll(/import\s*\(\s*['"]([^'"]+)['"]\s*\)/g)]
|
|
111
|
+
.map(match => match[1])
|
|
112
|
+
.concat([...fixtureSrc.matchAll(/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g)]
|
|
113
|
+
.map(match => match[1]))
|
|
114
|
+
const banned = [
|
|
115
|
+
// Word-boundary: `@xmoon76/pi-tui` must NOT match
|
|
116
|
+
// `@xmoon76/dsh-pi-tui` (a substring of the public package name).
|
|
117
|
+
['@xmoon76/pi-tui (the private fork, word-bounded)', /@xmoon76\/pi-tui(?![-a-z])/g],
|
|
118
|
+
['@moonshot-ai/pi-tui', /@moonshot-ai\/pi-tui/g],
|
|
119
|
+
['src/tui-app', /src\/tui-app/g],
|
|
120
|
+
['TuiApp', /\bTuiApp\b/g],
|
|
121
|
+
['TuiMainScreen', /\bTuiMainScreen\b/g],
|
|
122
|
+
['TuiAltScreen', /\bTuiAltScreen\b/g],
|
|
123
|
+
['repository-relative internal path', /\.\.\/\.\.\/src/g],
|
|
124
|
+
]
|
|
125
|
+
for (const [name, pattern] of banned) {
|
|
126
|
+
const staticMatches = importLines.match(pattern) ?? []
|
|
127
|
+
const dynamicMatches = dynamicSpecifiers.filter(specifier => pattern.test(specifier))
|
|
128
|
+
check(`fixture does not import ${name}`, staticMatches.length === 0 && dynamicMatches.length === 0,
|
|
129
|
+
[...staticMatches, ...dynamicMatches].join(', '))
|
|
130
|
+
}
|
|
131
|
+
// The ONLY @xmoon76/dsh-pi-tui imports are the public subpaths
|
|
132
|
+
// (extensions/advanced + extensions for the service type; cordis is a
|
|
133
|
+
// legitimate peer for the plugin's apply signature).
|
|
134
|
+
const dshImports = importLines.match(/@xmoon76\/dsh-pi-tui[^'"]*/g) ?? []
|
|
135
|
+
check('fixture imports ONLY the public extensions subpaths',
|
|
136
|
+
dshImports.length === 2
|
|
137
|
+
&& dshImports.includes('@xmoon76/dsh-pi-tui/extensions/advanced')
|
|
138
|
+
&& dshImports.includes('@xmoon76/dsh-pi-tui/extensions'),
|
|
139
|
+
dshImports.join(', '))
|
|
140
|
+
|
|
141
|
+
// 3. PHASE-4 SURFACE EXERCISE (plan §17 acceptance): the fixture
|
|
142
|
+
// exercises the Phase-4 advanced surface through the public entry.
|
|
143
|
+
const surface = [
|
|
144
|
+
['ui.ui.select', 'imperative select broker'],
|
|
145
|
+
['ui.ui.confirm', 'imperative confirm broker'],
|
|
146
|
+
['ui.ui.input', 'imperative input broker'],
|
|
147
|
+
['ui.ui.notify', 'imperative notify broker'],
|
|
148
|
+
['ui.ui.custom', 'custom interactive UI'],
|
|
149
|
+
['AdvancedCustomHost', 'custom host facade type'],
|
|
150
|
+
['ui.host.getTheme', 'host-state theme query'],
|
|
151
|
+
['ui.host.setTheme', 'host-state theme select'],
|
|
152
|
+
['ui.host.setTitle', 'host-state title override'],
|
|
153
|
+
['ui.host.setWorkingMessage', 'host-state working override'],
|
|
154
|
+
['ui.host.setToolsExpanded', 'host-state tool expansion'],
|
|
155
|
+
]
|
|
156
|
+
for (const [api, label] of surface) {
|
|
157
|
+
check(`fixture exercises ${label} (${api})`, fixtureSrc.includes(api))
|
|
158
|
+
}
|
|
159
|
+
check('fixture never parses raw terminal bytes (no CSI-u / escape matching in the plugin)',
|
|
160
|
+
!/\\x1b\[/.test(fixtureSrc) && !/escape\s*\(/.test(fixtureSrc.replace(/handleInput/g, '')))
|
|
161
|
+
|
|
162
|
+
// 4. The fixture tree: symlink the packed package + the host's
|
|
163
|
+
// node_modules (the offline-safe mirror of a real profile install).
|
|
164
|
+
const fixtureDir = join(workDir, 'fixture')
|
|
165
|
+
mkdirSync(join(fixtureDir, 'node_modules', '@xmoon76'), { recursive: true })
|
|
166
|
+
symlinkSync(pkgDir, join(fixtureDir, 'node_modules', '@xmoon76', 'dsh-pi-tui'), 'dir')
|
|
167
|
+
const hostModules = join(PACKAGE_ROOT, 'node_modules')
|
|
168
|
+
mkdirSync(join(pkgDir, 'node_modules'), { recursive: true })
|
|
169
|
+
for (const entry of readdirSync(hostModules)) {
|
|
170
|
+
const target = join(pkgDir, 'node_modules', entry)
|
|
171
|
+
if (existsSync(target)) continue
|
|
172
|
+
symlinkSync(join(hostModules, entry), target, 'dir')
|
|
173
|
+
}
|
|
174
|
+
mkdirSync(join(fixtureDir, 'node_modules'), { recursive: true })
|
|
175
|
+
for (const entry of readdirSync(hostModules)) {
|
|
176
|
+
const target = join(fixtureDir, 'node_modules', entry)
|
|
177
|
+
if (existsSync(target)) continue
|
|
178
|
+
symlinkSync(join(hostModules, entry), target, 'dir')
|
|
179
|
+
}
|
|
180
|
+
mkdirSync(join(fixtureDir, 'src'), { recursive: true })
|
|
181
|
+
writeFileSync(join(fixtureDir, 'src', 'index.ts'), fixtureSrc)
|
|
182
|
+
writeFileSync(join(fixtureDir, 'tsconfig.json'), JSON.stringify({
|
|
183
|
+
compilerOptions: {
|
|
184
|
+
target: 'ES2022',
|
|
185
|
+
module: 'NodeNext',
|
|
186
|
+
moduleResolution: 'NodeNext',
|
|
187
|
+
strict: true,
|
|
188
|
+
noEmit: true,
|
|
189
|
+
skipLibCheck: true,
|
|
190
|
+
types: [],
|
|
191
|
+
},
|
|
192
|
+
include: ['src'],
|
|
193
|
+
}, null, 2))
|
|
194
|
+
writeFileSync(join(fixtureDir, 'package.json'), JSON.stringify({
|
|
195
|
+
name: 'dsh-pi-phase4-fixture',
|
|
196
|
+
private: true,
|
|
197
|
+
type: 'module',
|
|
198
|
+
}, null, 2))
|
|
199
|
+
|
|
200
|
+
// 5. Typecheck against the packed .d.mts (the repo's tsc — no
|
|
201
|
+
// registry fetch).
|
|
202
|
+
const tsc = run('node', [join(PACKAGE_ROOT, 'node_modules', 'typescript', 'bin', 'tsc'), '-p', join(fixtureDir, 'tsconfig.json')], { cwd: fixtureDir })
|
|
203
|
+
check('fixture typechecks against the packed .d.mts', tsc.status === 0,
|
|
204
|
+
tsc.status === 0 ? '' : (tsc.stdout + tsc.stderr).split('\n').slice(-6).join(' '))
|
|
205
|
+
|
|
206
|
+
// 6. Module shape.
|
|
207
|
+
const moduleShape = /export const name = ['"][^'"]+['"]/s.test(fixtureSrc)
|
|
208
|
+
&& /export const inject = \[/s.test(fixtureSrc)
|
|
209
|
+
&& /export function apply\(ctx: Context\)/s.test(fixtureSrc)
|
|
210
|
+
check('fixture module shape (name/inject/apply) matches the Loader', moduleShape)
|
|
211
|
+
|
|
212
|
+
// 7. RUNTIME LOAD: compile the fixture to JS and apply() it with a
|
|
213
|
+
// mock service — the registrations must not throw and must resolve
|
|
214
|
+
// the PACKED advanced module.
|
|
215
|
+
const emitDir = join(fixtureDir, 'dist-out')
|
|
216
|
+
const emit = run('node', [join(PACKAGE_ROOT, 'node_modules', 'typescript', 'bin', 'tsc'),
|
|
217
|
+
'-p', join(fixtureDir, 'tsconfig.json'), '--noEmit', 'false', '--outDir', emitDir], { cwd: fixtureDir })
|
|
218
|
+
check('fixture compiles to JS for the runtime probe', emit.status === 0,
|
|
219
|
+
emit.status === 0 ? '' : (emit.stdout + emit.stderr).split('\n').slice(-4).join(' '))
|
|
220
|
+
if (emit.status === 0) {
|
|
221
|
+
writeFileSync(join(fixtureDir, 'runtime-probe.mjs'), `
|
|
222
|
+
const context = {
|
|
223
|
+
get: (name) => name === 'piTuiExtensions' ? mockService() : undefined,
|
|
224
|
+
on: () => {},
|
|
225
|
+
}
|
|
226
|
+
function mockService() {
|
|
227
|
+
const inertLease = {
|
|
228
|
+
id: 'inert', active: false, focused: false,
|
|
229
|
+
focus: () => {}, blur: () => {}, invalidate: () => {},
|
|
230
|
+
close: () => {}, hide: () => {}, show: () => {},
|
|
231
|
+
}
|
|
232
|
+
const inertControls = {
|
|
233
|
+
getEditorState: () => ({ text: '', cursor: 0, focused: false, composing: false }),
|
|
234
|
+
setEditorText: () => {}, setEditorCursor: () => {},
|
|
235
|
+
insertEditorText: () => {}, pasteToEditor: () => {}, requestEditorFocus: () => {},
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
api: () => ({
|
|
239
|
+
apiVersion: 1,
|
|
240
|
+
hostVersion: 'test',
|
|
241
|
+
capabilities: new Set(['advanced.input.capture', 'advanced.ui.interactive', 'advanced.editor.control']),
|
|
242
|
+
deprecations: new Map(),
|
|
243
|
+
}),
|
|
244
|
+
_advancedCaptureInput: (spec) => ({ id: spec.id, dispose: () => {} }),
|
|
245
|
+
_advancedShowInteractiveOverlay: () => inertLease,
|
|
246
|
+
_advancedEditorControls: () => inertControls,
|
|
247
|
+
_advancedUiSelect: () => Promise.resolve(undefined),
|
|
248
|
+
_advancedUiConfirm: () => Promise.resolve(false),
|
|
249
|
+
_advancedUiInput: () => Promise.resolve(undefined),
|
|
250
|
+
_advancedUiNotify: () => {},
|
|
251
|
+
_advancedUiCustom: () => Promise.resolve(undefined),
|
|
252
|
+
_advancedHostState: () => ({
|
|
253
|
+
getTheme: () => 'dark',
|
|
254
|
+
setTheme: () => {},
|
|
255
|
+
setTitle: () => {},
|
|
256
|
+
setWorkingMessage: () => {},
|
|
257
|
+
setToolsExpanded: () => {},
|
|
258
|
+
}),
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const plugin = await import(${JSON.stringify(join(emitDir, 'index.js').replaceAll('\\', '/'))})
|
|
262
|
+
plugin.apply(context)
|
|
263
|
+
if (plugin.name !== 'dsh-pi-phase4-fixture') throw new Error('bad name: ' + plugin.name)
|
|
264
|
+
console.log('fixture-runtime-ok')
|
|
265
|
+
`)
|
|
266
|
+
const runtime = run(process.execPath, [join(fixtureDir, 'runtime-probe.mjs')], { cwd: fixtureDir })
|
|
267
|
+
check('fixture runtime-loads and apply() registers without throwing',
|
|
268
|
+
runtime.status === 0 && runtime.stdout.includes('fixture-runtime-ok'),
|
|
269
|
+
runtime.status === 0 ? '' : (runtime.stderr || runtime.stdout).split('\n').slice(-8).join(' '))
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// 8. No duplicate dsh runtime: every @deepseek-ai entry in BOTH the
|
|
273
|
+
// packed package tree AND the fixture tree must be a SYMLINK whose
|
|
274
|
+
// realpath resolves into the host module tree (the packed tarball
|
|
275
|
+
// itself carries no node_modules — the scaffolding symlinks them,
|
|
276
|
+
// exactly like a real profile resolves in-box packages from the
|
|
277
|
+
// dsh installation). A REAL directory is a duplicate runtime copy.
|
|
278
|
+
const probeReal = resolve(workDir)
|
|
279
|
+
const auditTrees = [
|
|
280
|
+
['packed package', join(pkgDir, 'node_modules', '@deepseek-ai')],
|
|
281
|
+
['fixture tree', join(fixtureDir, 'node_modules', '@deepseek-ai')],
|
|
282
|
+
]
|
|
283
|
+
const realDirs = []
|
|
284
|
+
for (const [label, nm] of auditTrees) {
|
|
285
|
+
const entries = existsSync(nm) ? readdirSync(nm) : []
|
|
286
|
+
for (const name of entries) {
|
|
287
|
+
const entry = join(nm, name)
|
|
288
|
+
let isSymlink = false
|
|
289
|
+
try { isSymlink = lstatSync(entry).isSymbolicLink() } catch { /* missing */ }
|
|
290
|
+
if (!isSymlink) {
|
|
291
|
+
realDirs.push(`${label}:${name} (real dir)`)
|
|
292
|
+
continue
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const target = resolve(realpathSync(entry))
|
|
296
|
+
if (target.startsWith(probeReal)) realDirs.push(`${label}:${name}->${target}`)
|
|
297
|
+
} catch {
|
|
298
|
+
realDirs.push(`${label}:${name} (dangling)`)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
check('no duplicate @deepseek-ai dsh-runtime copy (symlinks only, both trees)',
|
|
303
|
+
realDirs.length === 0,
|
|
304
|
+
realDirs.join(', '))
|
|
305
|
+
} finally {
|
|
306
|
+
if (process.env.ADVANCED_SMOKE_KEEP !== '1') rmSync(workDir, { recursive: true, force: true })
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
for (const line of checks) console.log(line)
|
|
310
|
+
if (failures.length > 0) {
|
|
311
|
+
console.error(`\nphase4-plugin smoke FAILED (${failures.length}):`)
|
|
312
|
+
for (const failure of failures) console.error(` - ${failure}`)
|
|
313
|
+
process.exit(1)
|
|
314
|
+
}
|
|
315
|
+
console.log('\nphase4-plugin smoke passed')
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
main()
|
|
@@ -149,10 +149,12 @@ function main() {
|
|
|
149
149
|
// --- structure ---
|
|
150
150
|
const files = walkFiles(extracted)
|
|
151
151
|
const has = (path) => files.includes(path)
|
|
152
|
-
check('exports entry dist/
|
|
153
|
-
check('exports entry dist/
|
|
154
|
-
check('types dist/
|
|
155
|
-
check('types dist/
|
|
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'))
|
|
156
158
|
check('cordis.patch.yml included', has('cordis.patch.yml'))
|
|
157
159
|
check('config/ included', files.some(name => name.startsWith('config/')))
|
|
158
160
|
check('repair-session.mjs included', has('scripts/repair-session.mjs'))
|
|
@@ -225,6 +227,62 @@ function main() {
|
|
|
225
227
|
checks.push(`skip .d.mts parse — typescript unavailable`)
|
|
226
228
|
}
|
|
227
229
|
|
|
230
|
+
// --- public .d.mts leak gate (M3/E, round-1/2 review): the shipped
|
|
231
|
+
// declarations must not reference the vendored fork, TuiApp, private
|
|
232
|
+
// screens, or repository source paths. tsdown INLINES types into the
|
|
233
|
+
// .d.mts with `//#region <path>` markers; a fork leak shows up as a
|
|
234
|
+
// region pointing OUTSIDE the package's own src/ (`../pi-tui/...`,
|
|
235
|
+
// `node_modules/@xmoon76/pi-tui/...`) or as fork module names
|
|
236
|
+
// (`src/tui-main-screen.d.ts`), often inside an auto-generated SHARED
|
|
237
|
+
// CHUNK that the SDK entries only re-export. The gate therefore:
|
|
238
|
+
// (a) scans EVERY dist/*.d.mts (entries AND chunks);
|
|
239
|
+
// (b) treats any region marker whose path is not this package's own
|
|
240
|
+
// src/ as a leak (allowlist by construction);
|
|
241
|
+
// (c) bans fork imports and fork module names anywhere;
|
|
242
|
+
// (d) bans the bare private identifiers (TuiMainScreen/TuiAltScreen/
|
|
243
|
+
// TuiApp) line-scoped over every file, skipping comment lines.
|
|
244
|
+
const dtsAll = files.filter(name => name.startsWith('dist/') && name.endsWith('.d.mts'))
|
|
245
|
+
const sdkIdentifiers = [/\bTuiMainScreen\b/, /\bTuiAltScreen\b/, /\bTuiApp\b/]
|
|
246
|
+
const dtsLeaks = []
|
|
247
|
+
for (const name of dtsAll) {
|
|
248
|
+
const text = readFileSync(join(extracted, name), 'utf8')
|
|
249
|
+
const lines = text.split('\n')
|
|
250
|
+
// (b) region markers: allow only `//#region src/<public module>.d.ts`.
|
|
251
|
+
// The ROOT entry (dist/index.d.mts) is the runner's own surface: it
|
|
252
|
+
// legitimately inlines the TuiApp implementation (src/tui-app.d.ts)
|
|
253
|
+
// for the Loader row — it is NOT a third-party SDK entry. Its region
|
|
254
|
+
// is exempted; everything else (chunks, extensions, builtins) must
|
|
255
|
+
// name only the public modules below.
|
|
256
|
+
const isRoot = name === 'dist/index.d.mts'
|
|
257
|
+
for (const line of lines) {
|
|
258
|
+
const match = /\/\/#region\s+(\S+)/.exec(line)
|
|
259
|
+
if (match) {
|
|
260
|
+
const regionPath = match[1]
|
|
261
|
+
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|index|skill-catalog|startup|surface-catalog)\.d\.ts$/.test(regionPath)
|
|
262
|
+
const rootAllowed = isRoot && /^src\/tui-app\.d\.ts$/.test(regionPath)
|
|
263
|
+
if (!allowed && !rootAllowed) dtsLeaks.push(`${name}: region ${regionPath}`)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// (c) fork imports and fork module names anywhere.
|
|
267
|
+
if (/from\s*["']@xmoon76\/pi-tui["']/.test(text)) dtsLeaks.push(`${name}: fork import`)
|
|
268
|
+
if (/packages\/pi-tui/.test(text)) dtsLeaks.push(`${name}: packages/pi-tui path`)
|
|
269
|
+
if (/\b(tui-main-screen|tui-alt-screen)\.d\.ts\b/.test(text)) dtsLeaks.push(`${name}: fork screen module`)
|
|
270
|
+
// (d) bare private identifiers, line-scoped (comments skipped). No
|
|
271
|
+
// exemption: the clean dist has zero TuiApp identifiers anywhere
|
|
272
|
+
// (round-3 finding 1 — the old root exemption was dead code).
|
|
273
|
+
for (const line of lines) {
|
|
274
|
+
const trimmed = line.trim()
|
|
275
|
+
if (trimmed.startsWith('*') || trimmed.startsWith('//') || trimmed.startsWith('/*')) continue
|
|
276
|
+
for (const pattern of sdkIdentifiers) {
|
|
277
|
+
if (pattern.test(trimmed)) dtsLeaks.push(`${name}: bare ${pattern}`)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
check('every public .d.mts leaks no private pi-tui / internal paths', dtsLeaks.length === 0,
|
|
282
|
+
dtsLeaks.length === 0 ? '' : dtsLeaks.join('; '))
|
|
283
|
+
// The .mjs bodies must not import the fork externally either (it is
|
|
284
|
+
// bundled) — the existing dist check below covers every .mjs.
|
|
285
|
+
|
|
228
286
|
// --- install + runtime ---
|
|
229
287
|
if (skipInstall) {
|
|
230
288
|
checks.push('skip npm install (TARBALL_SMOKE_SKIP_INSTALL=1)')
|
|
@@ -242,10 +300,14 @@ function main() {
|
|
|
242
300
|
const installed = join(probeDir, 'node_modules', '@xmoon76', 'dsh-pi-tui')
|
|
243
301
|
check('installed package contains dist', existsSync(join(installed, 'dist', 'index.mjs')))
|
|
244
302
|
const importRun = run(process.execPath, ['--input-type=module', '-e',
|
|
245
|
-
"Promise.all([import('@xmoon76/dsh-pi-tui'), import('@xmoon76/dsh-pi-tui/startup')
|
|
246
|
-
+ "
|
|
303
|
+
"Promise.all([import('@xmoon76/dsh-pi-tui'), import('@xmoon76/dsh-pi-tui/startup'),"
|
|
304
|
+
+ "import('@xmoon76/dsh-pi-tui/extensions'), import('@xmoon76/dsh-pi-tui/builtins'),"
|
|
305
|
+
+ "import('@xmoon76/dsh-pi-tui/extensions/advanced'), import('@xmoon76/dsh-pi-tui/extensions/unstable')])"
|
|
306
|
+
+ ".then(m => { if (m[4].ADVANCED_API_LEVEL !== 1) throw new Error('ADVANCED_API_LEVEL');"
|
|
307
|
+
+ "if (m[5].UNSTABLE_API_LEVEL !== 1) throw new Error('UNSTABLE_API_LEVEL'); console.log('imports-ok') })"
|
|
308
|
+
+ ".catch(e => { console.error(e.message); process.exit(1) })",
|
|
247
309
|
], { cwd: probeDir })
|
|
248
|
-
check('
|
|
310
|
+
check('all exports entries import', importRun.status === 0 && importRun.stdout.includes('imports-ok'),
|
|
249
311
|
importRun.status === 0 ? '' : importRun.stderr.slice(0, 200))
|
|
250
312
|
const forkRun = run(process.execPath, ['--input-type=module', '-e',
|
|
251
313
|
"import('@xmoon76/pi-tui').then(() => process.exit(0)).catch(() => process.exit(3))",
|