dsh-tui-theme 0.3.2 → 0.4.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/README.md +47 -22
- package/cordis.patch.yml +3 -3
- package/lib/types/autoTheme.d.ts +16 -50
- package/lib/types/autoTheme.d.ts.map +1 -1
- package/lib/types/autoTheme.js +40 -176
- package/lib/types/index.d.ts +2 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +13 -28
- package/lib/types/settingsSection.d.ts +1 -1
- package/lib/types/settingsSection.d.ts.map +1 -1
- package/lib/types/settingsSection.js +4 -4
- package/lib/types/statusLine.js +2 -2
- package/lib/types/themeAssets.d.ts.map +1 -1
- package/lib/types/themeAssets.js +14 -8
- package/package.json +12 -8
- package/scripts/headless-order-test.mjs +67 -0
- package/scripts/validate-themes-against-host.mjs +115 -0
- package/scripts/verify-package.mjs +79 -0
- package/scripts/verify.mjs +452 -0
|
@@ -84,11 +84,11 @@ function sectionDefinition(cordis) {
|
|
|
84
84
|
fields: [
|
|
85
85
|
{
|
|
86
86
|
path: ['followSystem'],
|
|
87
|
-
label: '
|
|
88
|
-
descriptions: { zh: '
|
|
89
|
-
hint: '
|
|
87
|
+
label: 'Use cached terminal background',
|
|
88
|
+
descriptions: { zh: '使用已缓存的终端背景' },
|
|
89
|
+
hint: 'Apply a previously saved terminal background result at startup. dsh-TUI 0.9.2 does not expose a safe plugin query, so this plugin does not refresh the cache.',
|
|
90
90
|
hintDescriptions: {
|
|
91
|
-
zh: '
|
|
91
|
+
zh: '启动时应用此前保存的终端背景结果。dsh-TUI 0.9.2 未提供安全的插件查询接缝,因此本插件不会刷新该缓存。',
|
|
92
92
|
},
|
|
93
93
|
kind: 'boolean',
|
|
94
94
|
format: (value) => String(value ?? cordis.followSystem),
|
package/lib/types/statusLine.js
CHANGED
|
@@ -92,14 +92,14 @@ export function startStatusLine(ctx, getEffective) {
|
|
|
92
92
|
// Display garnish only: a rendering hiccup must never travel upward.
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
|
-
|
|
95
|
+
statusCtx.on('session/event', (session, event) => {
|
|
96
96
|
current = session;
|
|
97
97
|
if (event?.type === 'turn/end') {
|
|
98
98
|
turns.set(session, (turns.get(session) ?? 0) + 1);
|
|
99
99
|
}
|
|
100
100
|
render();
|
|
101
101
|
});
|
|
102
|
-
|
|
102
|
+
statusCtx.on('session/disposed', session => {
|
|
103
103
|
turns.delete(session);
|
|
104
104
|
if (current === session)
|
|
105
105
|
current = undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"themeAssets.d.ts","sourceRoot":"","sources":["../../src/themeAssets.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,MAAM,WAAW,kBAAkB;IACjC,qDAAqD;IACrD,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CACnC;AAID,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,2DAA2D;AAC3D,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,GAAE,MAA0B,EACrC,SAAS,GAAE,MAA2B,GACrC,kBAAkB,
|
|
1
|
+
{"version":3,"file":"themeAssets.d.ts","sourceRoot":"","sources":["../../src/themeAssets.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,MAAM,WAAW,kBAAkB;IACjC,qDAAqD;IACrD,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CACnC;AAID,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,2DAA2D;AAC3D,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,GAAE,MAA0B,EACrC,SAAS,GAAE,MAA2B,GACrC,kBAAkB,CA+BpB"}
|
package/lib/types/themeAssets.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* theme garnish must never break the TUI's boot.
|
|
8
8
|
* @module dsh-tui-pink-theme/themeAssets
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
10
|
+
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { homedir } from 'node:os';
|
|
12
12
|
import { dirname, join } from 'node:path';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
@@ -42,17 +42,23 @@ export function installBundledThemes(targetDir = themesTargetDir(), sourceDir =
|
|
|
42
42
|
return { installed, skipped, failed: [sourceDir] };
|
|
43
43
|
}
|
|
44
44
|
for (const file of files) {
|
|
45
|
+
const target = join(targetDir, file);
|
|
45
46
|
try {
|
|
46
|
-
const target = join(targetDir, file);
|
|
47
|
-
if (existsSync(target)) {
|
|
48
|
-
skipped.push(file);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
47
|
const text = readFileSync(join(sourceDir, file), 'utf8');
|
|
52
48
|
JSON.parse(text); // our own asset, but never write a corrupt file out
|
|
53
49
|
mkdirSync(targetDir, { recursive: true });
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
try {
|
|
51
|
+
writeFileSync(target, text, { flag: 'wx' });
|
|
52
|
+
installed.push(file);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (error.code === 'EEXIST') {
|
|
56
|
+
skipped.push(file);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
failed.push(file);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
56
62
|
}
|
|
57
63
|
catch {
|
|
58
64
|
failed.push(file);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tui-theme",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Sakura-pink themes for dsh-TUI with
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Sakura-pink themes for dsh-TUI with optional cached background follow (pink-day/pink-night), a blossom status line, and a /settings section. No shortcuts, no commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/types/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"lib",
|
|
18
18
|
"themes",
|
|
19
19
|
"docs",
|
|
20
|
+
"scripts",
|
|
20
21
|
"cordis.patch.yml"
|
|
21
22
|
],
|
|
22
23
|
"engines": {
|
|
@@ -24,8 +25,11 @@
|
|
|
24
25
|
},
|
|
25
26
|
"scripts": {
|
|
26
27
|
"build": "tsc -p tsconfig.json",
|
|
27
|
-
"
|
|
28
|
-
"verify": "node scripts/verify.mjs"
|
|
28
|
+
"verify": "node scripts/verify.mjs",
|
|
29
|
+
"verify:package": "node scripts/verify-package.mjs",
|
|
30
|
+
"verify:host": "node scripts/headless-order-test.mjs && node --import tsx/esm scripts/validate-themes-against-host.mjs",
|
|
31
|
+
"release:check": "npm run build && npm run verify && npm run verify:package",
|
|
32
|
+
"prepack": "npm run release:check"
|
|
29
33
|
},
|
|
30
34
|
"license": "MIT",
|
|
31
35
|
"author": "xiaoxiaohaigui",
|
|
@@ -51,19 +55,19 @@
|
|
|
51
55
|
"patch": "./cordis.patch.yml"
|
|
52
56
|
}
|
|
53
57
|
},
|
|
54
|
-
"dependencies": {
|
|
55
|
-
"@deepseek-ai/schemastery": "^3.18.1"
|
|
56
|
-
},
|
|
57
58
|
"peerDependencies": {
|
|
58
59
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
59
60
|
"@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-rc.1",
|
|
60
|
-
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6 || ^0.1.1-rc.1"
|
|
61
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6 || ^0.1.1-rc.1",
|
|
62
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
61
63
|
},
|
|
62
64
|
"devDependencies": {
|
|
63
65
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
64
66
|
"@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-rc.1",
|
|
65
67
|
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6 || ^0.1.1-rc.1",
|
|
68
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
66
69
|
"@types/node": "^22.0.0",
|
|
70
|
+
"tsx": "^4.23.12",
|
|
67
71
|
"typescript": "^6.0.3"
|
|
68
72
|
}
|
|
69
73
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end order test with the installed dsh-TUI services. Composes this
|
|
3
|
+
* plugin before the extension services to exercise late service injection.
|
|
4
|
+
*
|
|
5
|
+
* DSH_TUI_ADAPTER_DIR must point at dsh-TUI's lib/types/dsh-adapter directory.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdtempSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
8
|
+
import { tmpdir } from 'node:os'
|
|
9
|
+
import { join } from 'node:path'
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
11
|
+
import { createRequire } from 'node:module'
|
|
12
|
+
import assert from 'node:assert/strict'
|
|
13
|
+
|
|
14
|
+
const pluginRoot = fileURLToPath(new URL('..', import.meta.url))
|
|
15
|
+
const adapter = process.env.DSH_TUI_ADAPTER_DIR
|
|
16
|
+
if (adapter === undefined || adapter === '') {
|
|
17
|
+
throw new Error('DSH_TUI_ADAPTER_DIR must point at dsh-TUI lib/types/dsh-adapter for this host integration test.')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!existsSync(join(adapter, 'extensions.js'))) {
|
|
21
|
+
throw new Error(`dsh-TUI adapter not found at ${adapter}`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const hostPackagePath = join(adapter, '..', '..', '..', 'package.json')
|
|
25
|
+
if (!existsSync(hostPackagePath)) {
|
|
26
|
+
throw new Error(`dsh-TUI package metadata not found above adapter at ${adapter}`)
|
|
27
|
+
}
|
|
28
|
+
const hostPackage = JSON.parse(readFileSync(hostPackagePath, 'utf8'))
|
|
29
|
+
assert.equal(hostPackage.version, '0.9.2', `expected dsh-TUI adapter 0.9.2, received ${hostPackage.version}`)
|
|
30
|
+
|
|
31
|
+
const sandbox = mkdtempSync(join(tmpdir(), 'pink-order-'))
|
|
32
|
+
process.env.USERPROFILE = sandbox
|
|
33
|
+
process.env.HOME = sandbox
|
|
34
|
+
|
|
35
|
+
const dataDir = join(sandbox, '.dsh-tui')
|
|
36
|
+
mkdirSync(dataDir, { recursive: true })
|
|
37
|
+
writeFileSync(join(dataDir, 'theme.json'), JSON.stringify({ theme: 'pink-night' }, null, 2))
|
|
38
|
+
|
|
39
|
+
const req = createRequire(join(adapter, 'extensions.js'))
|
|
40
|
+
const { Context } = await import(pathToFileURL(req.resolve('@deepseek-ai/cordis')).href)
|
|
41
|
+
const extensions = await import(pathToFileURL(join(adapter, 'extensions.js')).href)
|
|
42
|
+
const ledgerModule = await import(pathToFileURL(join(adapter, 'effect-ledger.js')).href)
|
|
43
|
+
const statusModule = await import(pathToFileURL(join(adapter, 'status.js')).href)
|
|
44
|
+
const pink = await import(pathToFileURL(join(pluginRoot, 'lib', 'types', 'index.js')).href)
|
|
45
|
+
|
|
46
|
+
const app = new Context()
|
|
47
|
+
await app.plugin(ledgerModule.default)
|
|
48
|
+
await app.plugin(pink)
|
|
49
|
+
await app.plugin(extensions.default ?? extensions)
|
|
50
|
+
await new Promise(resolve => setTimeout(resolve, 300))
|
|
51
|
+
|
|
52
|
+
const ledger = join(dataDir, 'effect-ledger.jsonl')
|
|
53
|
+
const pinkBinds = existsSync(ledger)
|
|
54
|
+
? readFileSync(ledger, 'utf8').trim().split('\n')
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.map(line => JSON.parse(line))
|
|
57
|
+
.filter(entry => entry.resource?.id === 'dsh-tui-theme')
|
|
58
|
+
: []
|
|
59
|
+
const runtime = app.get('tuiStatus')
|
|
60
|
+
const snapshot = statusModule.getHostStatusStore(runtime)?.getSnapshot()
|
|
61
|
+
|
|
62
|
+
assert.ok(pinkBinds.length > 0, 'plugin must bind through the late status service')
|
|
63
|
+
assert.ok(
|
|
64
|
+
snapshot?.some?.(entry => entry.key === 'dsh-tui-theme'),
|
|
65
|
+
'status store must contain the plugin contribution',
|
|
66
|
+
)
|
|
67
|
+
console.log(`✓ headless order: ${pinkBinds.length} ledger bind(s), visible status contribution`)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate bundled themes against the real dsh-TUI source implementation.
|
|
3
|
+
* DSH_TUI_SOURCE_ROOT must point at a dsh-TUI source checkout.
|
|
4
|
+
*
|
|
5
|
+
* Run with: npx -y tsx scripts/validate-themes-against-host.mjs
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdtempSync, readFileSync, readdirSync } from 'node:fs'
|
|
8
|
+
import { tmpdir } from 'node:os'
|
|
9
|
+
import { join, resolve } from 'node:path'
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
11
|
+
import assert from 'node:assert/strict'
|
|
12
|
+
|
|
13
|
+
const pluginRoot = fileURLToPath(new URL('..', import.meta.url))
|
|
14
|
+
const sourceRoot = process.env.DSH_TUI_SOURCE_ROOT
|
|
15
|
+
if (sourceRoot === undefined || sourceRoot === '') {
|
|
16
|
+
throw new Error('DSH_TUI_SOURCE_ROOT must point at a dsh-TUI source checkout for this host theme validation.')
|
|
17
|
+
}
|
|
18
|
+
const hostRoot = resolve(sourceRoot)
|
|
19
|
+
const customThemePath = join(hostRoot, 'src', 'customTheme.ts')
|
|
20
|
+
const themePath = join(hostRoot, 'src', 'theme.ts')
|
|
21
|
+
|
|
22
|
+
if (!existsSync(customThemePath) || !existsSync(themePath)) {
|
|
23
|
+
throw new Error(`dsh-TUI sources not found at ${hostRoot}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const hostPackagePath = join(hostRoot, 'package.json')
|
|
27
|
+
if (!existsSync(hostPackagePath)) {
|
|
28
|
+
throw new Error(`dsh-TUI package metadata not found at ${hostPackagePath}`)
|
|
29
|
+
}
|
|
30
|
+
const hostPackage = JSON.parse(readFileSync(hostPackagePath, 'utf8'))
|
|
31
|
+
assert.equal(hostPackage.version, '0.9.2', `expected dsh-TUI source 0.9.2, received ${hostPackage.version}`)
|
|
32
|
+
|
|
33
|
+
const sandboxHome = mkdtempSync(join(tmpdir(), 'pink-theme-host-validate-'))
|
|
34
|
+
process.env.USERPROFILE = sandboxHome
|
|
35
|
+
process.env.HOME = sandboxHome
|
|
36
|
+
|
|
37
|
+
const themesDir = join(pluginRoot, 'themes')
|
|
38
|
+
const { parseCustomTheme, buildTheme } = await import(pathToFileURL(customThemePath).href)
|
|
39
|
+
const { getTheme, isLightThemeActive, registerCustomThemeResolver } = await import(
|
|
40
|
+
pathToFileURL(themePath).href
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const allKeys = Object.keys(getTheme('dark'))
|
|
44
|
+
assert.ok(allKeys.length >= 90, 'host Theme key count drifted; re-check coverage')
|
|
45
|
+
|
|
46
|
+
function parseColor(value) {
|
|
47
|
+
if (/^#[0-9A-Fa-f]{6}$/.test(value)) {
|
|
48
|
+
const n = Number.parseInt(value.slice(1), 16)
|
|
49
|
+
return [(n >> 16) & 255, (n >> 8) & 255, n & 255]
|
|
50
|
+
}
|
|
51
|
+
const match = /^rgb\((\d+),(\d+),(\d+)\)$/.exec(value)
|
|
52
|
+
return match === null ? undefined : [Number(match[1]), Number(match[2]), Number(match[3])]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function luminance(color) {
|
|
56
|
+
const channel = value => {
|
|
57
|
+
const normalized = value / 255
|
|
58
|
+
return normalized <= 0.03928
|
|
59
|
+
? normalized / 12.92
|
|
60
|
+
: ((normalized + 0.055) / 1.055) ** 2.4
|
|
61
|
+
}
|
|
62
|
+
return 0.2126 * channel(color[0]) + 0.7152 * channel(color[1]) + 0.0722 * channel(color[2])
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function contrast(foreground, background) {
|
|
66
|
+
const [high, low] = [luminance(foreground), luminance(background)].sort((a, b) => b - a)
|
|
67
|
+
return (high + 0.05) / (low + 0.05)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const expectedLight = { 'pink-night': false, 'pink-day': true, 'pink-ansi': false }
|
|
71
|
+
const built = {}
|
|
72
|
+
registerCustomThemeResolver(name => built[name])
|
|
73
|
+
|
|
74
|
+
for (const file of readdirSync(themesDir).filter(name => name.endsWith('.json'))) {
|
|
75
|
+
const warnings = []
|
|
76
|
+
const originalWarn = console.warn
|
|
77
|
+
console.warn = message => warnings.push(String(message))
|
|
78
|
+
let spec
|
|
79
|
+
try {
|
|
80
|
+
spec = parseCustomTheme(readFileSync(join(themesDir, file), 'utf8'), file)
|
|
81
|
+
} finally {
|
|
82
|
+
console.warn = originalWarn
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
assert.notEqual(spec, undefined, `${file} must parse`)
|
|
86
|
+
assert.deepEqual(warnings, [], `${file} must produce zero host warnings`)
|
|
87
|
+
const missing = allKeys.filter(key => key !== 'userMessageBackground' && !(key in spec.colors))
|
|
88
|
+
assert.deepEqual(missing, [], `${file} must cover every Theme key`)
|
|
89
|
+
|
|
90
|
+
const theme = buildTheme(spec)
|
|
91
|
+
built[spec.name] = theme
|
|
92
|
+
assert.equal(isLightThemeActive(spec.name), expectedLight[spec.name], `${spec.name} identity`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const [name, light] of Object.entries(expectedLight)) {
|
|
96
|
+
assert.equal(isLightThemeActive(name), light, `${name} identity via resolver`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const cases = [
|
|
100
|
+
['pink-night', 'text', '#1E1E1E', 4.5],
|
|
101
|
+
['pink-night', 'claude', '#1E1E1E', 3.0],
|
|
102
|
+
['pink-night', 'inactive', '#1E1E1E', 2.5],
|
|
103
|
+
['pink-day', 'text', '#F6F3ED', 4.5],
|
|
104
|
+
['pink-day', 'claude', '#F6F3ED', 3.0],
|
|
105
|
+
['pink-day', 'inactive', '#F6F3ED', 2.5],
|
|
106
|
+
]
|
|
107
|
+
for (const [theme, key, background, minimum] of cases) {
|
|
108
|
+
const foregroundRgb = parseColor(built[theme][key])
|
|
109
|
+
const backgroundRgb = parseColor(background)
|
|
110
|
+
assert.notEqual(foregroundRgb, undefined, `${theme}.${key} must be parseable`)
|
|
111
|
+
const ratio = contrast(foregroundRgb, backgroundRgb)
|
|
112
|
+
assert.ok(ratio >= minimum, `${theme}.${key} contrast ${ratio.toFixed(2)} >= ${minimum}`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log(`✓ host theme validation: ${Object.keys(built).length} themes, ${allKeys.length - 1} keys each`)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release artifact contract: inspect npm's dry-run manifest without creating
|
|
3
|
+
* a tarball, then ensure consumers receive every advertised runtime asset.
|
|
4
|
+
*/
|
|
5
|
+
import { execFileSync } from 'node:child_process'
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
import assert from 'node:assert/strict'
|
|
9
|
+
|
|
10
|
+
const pluginRoot = fileURLToPath(new URL('..', import.meta.url))
|
|
11
|
+
const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
|
|
12
|
+
const lockfile = JSON.parse(readFileSync(new URL('../package-lock.json', import.meta.url), 'utf8'))
|
|
13
|
+
const packCommand = process.platform === 'win32'
|
|
14
|
+
? { file: 'cmd.exe', args: ['/d', '/s', '/c', 'npm pack --dry-run --json --ignore-scripts'] }
|
|
15
|
+
: { file: 'npm', args: ['pack', '--dry-run', '--json', '--ignore-scripts'] }
|
|
16
|
+
const manifest = JSON.parse(execFileSync(packCommand.file, packCommand.args, {
|
|
17
|
+
cwd: pluginRoot,
|
|
18
|
+
encoding: 'utf8',
|
|
19
|
+
}))
|
|
20
|
+
const packed = manifest[0]
|
|
21
|
+
assert.ok(packed, 'npm pack must report one package')
|
|
22
|
+
assert.equal(packed.name, 'dsh-tui-theme')
|
|
23
|
+
assert.equal(packed.version, packageJson.version)
|
|
24
|
+
|
|
25
|
+
const files = new Set(packed.files.map(entry => entry.path))
|
|
26
|
+
for (const required of [
|
|
27
|
+
'package.json',
|
|
28
|
+
'README.md',
|
|
29
|
+
'LICENSE',
|
|
30
|
+
'cordis.patch.yml',
|
|
31
|
+
'lib/types/index.js',
|
|
32
|
+
'lib/types/index.d.ts',
|
|
33
|
+
'lib/types/autoTheme.js',
|
|
34
|
+
'lib/types/autoTheme.d.ts',
|
|
35
|
+
'lib/types/settingsSection.js',
|
|
36
|
+
'lib/types/settingsSection.d.ts',
|
|
37
|
+
'lib/types/statusLine.js',
|
|
38
|
+
'lib/types/statusLine.d.ts',
|
|
39
|
+
'lib/types/themeAssets.js',
|
|
40
|
+
'lib/types/themeAssets.d.ts',
|
|
41
|
+
'themes/pink-night.json',
|
|
42
|
+
'themes/pink-day.json',
|
|
43
|
+
'themes/pink-ansi.json',
|
|
44
|
+
'docs/screenshots/pink-day.png',
|
|
45
|
+
'docs/screenshots/pink-night.png',
|
|
46
|
+
'docs/screenshots/settings.png',
|
|
47
|
+
'scripts/verify.mjs',
|
|
48
|
+
'scripts/verify-package.mjs',
|
|
49
|
+
'scripts/headless-order-test.mjs',
|
|
50
|
+
'scripts/validate-themes-against-host.mjs',
|
|
51
|
+
]) {
|
|
52
|
+
assert.ok(files.has(required), `published package must include ${required}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const exportedPaths = [packageJson.main, packageJson.types, packageJson.dsh.bundle.patch]
|
|
56
|
+
for (const entry of Object.values(packageJson.exports)) {
|
|
57
|
+
if (typeof entry === 'string') exportedPaths.push(entry)
|
|
58
|
+
else if (entry !== null && typeof entry === 'object') exportedPaths.push(...Object.values(entry))
|
|
59
|
+
}
|
|
60
|
+
for (const entry of exportedPaths) {
|
|
61
|
+
assert.equal(typeof entry, 'string')
|
|
62
|
+
assert.ok(files.has(entry.replace(/^\.\//, '')), `published package must include declared entry ${entry}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const name of Object.keys(packageJson.peerDependencies)) {
|
|
66
|
+
if (!name.startsWith('@deepseek-ai/')) continue
|
|
67
|
+
assert.equal(packageJson.dependencies?.[name], undefined, `${name} must not be a runtime dependency`)
|
|
68
|
+
assert.equal(packageJson.devDependencies?.[name], packageJson.peerDependencies[name], `${name} peer and dev ranges must match`)
|
|
69
|
+
}
|
|
70
|
+
assert.equal(packageJson.dependencies?.['@deepseek-ai/schemastery'], undefined)
|
|
71
|
+
const rootLock = lockfile.packages?.['']
|
|
72
|
+
assert.ok(rootLock, 'lockfile must have root metadata')
|
|
73
|
+
assert.equal(lockfile.version, packageJson.version)
|
|
74
|
+
assert.deepEqual(rootLock.devDependencies, packageJson.devDependencies)
|
|
75
|
+
assert.deepEqual(rootLock.peerDependencies, packageJson.peerDependencies)
|
|
76
|
+
assert.equal(rootLock.dependencies, undefined)
|
|
77
|
+
assert.equal(existsSync(new URL('../lib/types/index.js', import.meta.url)), true)
|
|
78
|
+
|
|
79
|
+
console.log(`✓ package manifest: ${packed.name}@${packed.version}, ${files.size} files`)
|