dsh-git-worktree 0.9.2 → 0.10.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/CHANGELOG.md +418 -386
- package/LICENSE +21 -21
- package/NOTICE +16 -14
- package/README.en.md +169 -169
- package/README.md +169 -169
- package/cordis.patch.yml +11 -11
- package/docs/i18n.md +63 -63
- package/lib/adapters/initial-commit.js +3 -3
- package/lib/adapters/project-skill-provider.js +14 -4
- package/lib/adapters/project-skill-provider.js.map +1 -1
- package/lib/client/actions.d.ts +19 -3
- package/lib/client/actions.js +23 -3
- package/lib/client/actions.js.map +1 -1
- package/lib/client/console-remote/adapter.js +1 -1
- package/lib/client/console-remote/adapter.js.map +1 -1
- package/lib/client/console-remote/index.d.ts +10 -3
- package/lib/client/console-remote/index.js +56 -13
- package/lib/client/console-remote/index.js.map +1 -1
- package/lib/client/index.js +1 -0
- package/lib/client/index.js.map +1 -1
- package/lib/client/pre-session/controller.d.ts +1 -1
- package/lib/client/pre-session/controller.js +33 -18
- package/lib/client/pre-session/controller.js.map +1 -1
- package/lib/client/review-console/recovery-continuation.d.ts +1 -1
- package/lib/client/review-console/recovery-continuation.js +9 -4
- package/lib/client/review-console/recovery-continuation.js.map +1 -1
- package/lib/client/review-console/review-console.styles.js +190 -190
- package/lib/client/styles.js +146 -140
- package/lib/client/styles.js.map +1 -1
- package/lib/client/target-console/target-console.styles.js +155 -155
- package/lib/client/workspace-sidebar/index.d.ts +11 -2
- package/lib/client/workspace-sidebar/index.js +89 -9
- package/lib/client/workspace-sidebar/index.js.map +1 -1
- package/lib/client.js +10501 -1345
- package/lib/console-contract.d.ts +2 -0
- package/lib/console-contract.js.map +1 -1
- package/lib/console-host/service.js +4 -2
- package/lib/console-host/service.js.map +1 -1
- package/lib/console-remote/descriptors.js +10 -3
- package/lib/console-remote/descriptors.js.map +1 -1
- package/lib/console-remote/schemas.js +1 -0
- package/lib/console-remote/schemas.js.map +1 -1
- package/lib/host-compat.d.ts +4 -0
- package/lib/host-compat.js +25 -0
- package/lib/host-compat.js.map +1 -0
- package/lib/i18n/client-messages.d.ts +4 -0
- package/lib/i18n/client-messages.js +4 -0
- package/lib/i18n/client-messages.js.map +1 -1
- package/lib/index.d.ts +4 -1
- package/lib/index.js +9 -2
- package/lib/index.js.map +1 -1
- package/lib/workspace-provider-lifecycle.d.ts +16 -1
- package/lib/workspace-provider-lifecycle.js +69 -5
- package/lib/workspace-provider-lifecycle.js.map +1 -1
- package/package.json +153 -149
- package/scripts/check-publish.mjs +239 -238
- package/scripts/check-workspace-sidebar-version.mjs +18 -14
- package/scripts/clean-lib.mjs +7 -7
- package/scripts/dev-dsh-lib.mjs +547 -509
- package/scripts/dev-dsh.mjs +98 -95
- package/scripts/dsh-source-runner.mjs +20 -14
- package/scripts/workspace-sidebar-upstream-alpha.mjs +102 -0
- package/scripts/workspace-sidebar-upstream-next.mjs +134 -0
- package/scripts/workspace-sidebar-upstream.d.mts +8 -8
- package/scripts/workspace-sidebar-upstream.mjs +182 -162
|
@@ -1,238 +1,239 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pre-publish integrity gate. It validates every declared export, the Host
|
|
3
|
-
* mount patch, and the browser closure bundle instead of checking only the
|
|
4
|
-
* root entry. Set CHECK_PUBLISH_REQUIRE_CLEAN=1 in release CI to additionally
|
|
5
|
-
* require a clean Git checkout.
|
|
6
|
-
*/
|
|
7
|
-
import { execSync } from 'node:child_process'
|
|
8
|
-
import { createRequire } from 'node:module'
|
|
9
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
10
|
-
import { dirname, relative, resolve, sep } from 'node:path'
|
|
11
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
12
|
-
import vm from 'node:vm'
|
|
13
|
-
|
|
14
|
-
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
15
|
-
const require = createRequire(import.meta.url)
|
|
16
|
-
const fail = (message) => { console.error(`check-publish: FAIL: ${message}`); process.exit(1) }
|
|
17
|
-
const ok = (message) => console.log(`check-publish: ok: ${message}`)
|
|
18
|
-
const manifest = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'))
|
|
19
|
-
|
|
20
|
-
function exportTargets(value, label) {
|
|
21
|
-
if (typeof value === 'string') return [{ condition: 'default', target: value }]
|
|
22
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) fail(`${label} must be a path or condition map`)
|
|
23
|
-
return Object.entries(value).flatMap(([condition, nested]) => {
|
|
24
|
-
if (typeof nested === 'string') return [{ condition, target: nested }]
|
|
25
|
-
return exportTargets(nested, `${label}.${condition}`)
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function includedByFiles(target) {
|
|
30
|
-
const normalized = target.replace(/^\.\//, '').replaceAll('\\', '/')
|
|
31
|
-
if (normalized === 'package.json') return true
|
|
32
|
-
return (manifest.files ?? []).some((entry) => {
|
|
33
|
-
const normalizedEntry = String(entry).replace(/^\.\//, '').replaceAll('\\', '/').replace(/\/$/, '')
|
|
34
|
-
return normalized === normalizedEntry || normalized.startsWith(`${normalizedEntry}/`)
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// 1. Host singleton packages must resolve from DSH instead of a plugin-private dependency tree.
|
|
39
|
-
const hostSingletonPackages = [
|
|
40
|
-
'@deepseek-ai/cordis',
|
|
41
|
-
'@deepseek-ai/cordis-plugin-loader',
|
|
42
|
-
'@deepseek-ai/dsh-agent',
|
|
43
|
-
'@deepseek-ai/dsh-commands',
|
|
44
|
-
'@deepseek-ai/dsh-session',
|
|
45
|
-
'@deepseek-ai/dsh-fs',
|
|
46
|
-
'@deepseek-ai/dsh-skill',
|
|
47
|
-
'@deepseek-ai/dsh-skill-filesystem',
|
|
48
|
-
'@deepseek-ai/dsh-subprocess',
|
|
49
|
-
'@deepseek-ai/dsh-system-prompt',
|
|
50
|
-
'@deepseek-ai/dsh-tools',
|
|
51
|
-
'@deepseek-ai/dsh-typert-protocol',
|
|
52
|
-
'@deepseek-ai/dsh-typert-registry',
|
|
53
|
-
]
|
|
54
|
-
for (const packageName of hostSingletonPackages) {
|
|
55
|
-
if (manifest.dependencies?.[packageName] !== undefined) {
|
|
56
|
-
fail(`${packageName} must be a peerDependency; a private copy breaks Host service identity`)
|
|
57
|
-
}
|
|
58
|
-
if (typeof manifest.peerDependencies?.[packageName] !== 'string') {
|
|
59
|
-
fail(`${packageName} is missing from peerDependencies`)
|
|
60
|
-
}
|
|
61
|
-
if (typeof manifest.devDependencies?.[packageName] !== 'string') {
|
|
62
|
-
fail(`${packageName} is missing from devDependencies`)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
ok(`${hostSingletonPackages.length} Host singleton packages are peer-provided and locally testable`)
|
|
66
|
-
|
|
67
|
-
// 2. Host bundle declaration and patch.
|
|
68
|
-
const patchRel = manifest.dsh?.bundle?.patch
|
|
69
|
-
if (typeof patchRel !== 'string' || patchRel.length === 0) {
|
|
70
|
-
fail('package.json lacks "dsh".bundle.patch — dsh plugin add would install a plain dependency without mounting it')
|
|
71
|
-
}
|
|
72
|
-
const patchPath = resolve(root, patchRel)
|
|
73
|
-
if (!existsSync(patchPath)) fail(`declared patch file ${patchRel} does not exist`)
|
|
74
|
-
if (!includedByFiles(patchRel)) fail(`${patchRel} is not covered by package.json "files"`)
|
|
75
|
-
const patch = readFileSync(patchPath, 'utf8')
|
|
76
|
-
if (!patch.includes('- insert:') || !patch.includes(`name: ${manifest.name}`)) {
|
|
77
|
-
fail(`${patchRel} must insert one plugin row named ${manifest.name}`)
|
|
78
|
-
}
|
|
79
|
-
const { WORKSPACE_PROVIDER_CONDITION } = await import(pathToFileURL(resolve(root, 'lib/workspace-provider-lifecycle.js')).href)
|
|
80
|
-
if (!/- id: ui-workspace\s+name: ['"]?@deepseek-ai\/dsh-client-ui-workspace['"]?\s+disabled: !!js /u.test(patch)
|
|
81
|
-
|| !patch.includes(`disabled: !!js ${JSON.stringify(WORKSPACE_PROVIDER_CONDITION)}`)) {
|
|
82
|
-
fail(`${patchRel} must share the conditional Workspace selection used by the Host lifecycle`)
|
|
83
|
-
}
|
|
84
|
-
if (manifest.dsh?.client?.inject?.includes('@deepseek-ai/dsh-client-ui-workspace')) {
|
|
85
|
-
fail('dsh.client.inject must not request the disabled official ui-workspace Client module')
|
|
86
|
-
}
|
|
87
|
-
if (!manifest.files?.includes('NOTICE') || !existsSync(resolve(root, 'NOTICE'))) {
|
|
88
|
-
fail('the official Workspace Client derivative requires a published NOTICE')
|
|
89
|
-
}
|
|
90
|
-
ok(`Host mount patch ${patchRel} replaces official ui-workspace with one gated derivative`)
|
|
91
|
-
|
|
92
|
-
// 3. Every package export must exist and be included in the tarball.
|
|
93
|
-
const exportsMap = manifest.exports
|
|
94
|
-
if (!exportsMap || typeof exportsMap !== 'object' || Array.isArray(exportsMap)) fail('package.json lacks an exports map')
|
|
95
|
-
let exportCount = 0
|
|
96
|
-
for (const [subpath, value] of Object.entries(exportsMap)) {
|
|
97
|
-
for (const { condition, target } of exportTargets(value, `exports[${JSON.stringify(subpath)}]`)) {
|
|
98
|
-
exportCount += 1
|
|
99
|
-
if (!target.startsWith('./')) fail(`export ${subpath} (${condition}) must be package-relative: ${target}`)
|
|
100
|
-
const absolute = resolve(root, target)
|
|
101
|
-
const inside = relative(root, absolute)
|
|
102
|
-
if (inside.startsWith(`..${sep}`) || inside === '..') fail(`export ${subpath} escapes the package: ${target}`)
|
|
103
|
-
if (!existsSync(absolute)) fail(`export ${subpath} (${condition}) points to missing ${target} — run build first`)
|
|
104
|
-
if (!includedByFiles(target)) fail(`export ${subpath} (${condition}) target ${target} is not covered by package.json "files"`)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
ok(`${exportCount} export targets exist and are publishable`)
|
|
108
|
-
|
|
109
|
-
for (const stale of [
|
|
110
|
-
'lib/client/review-console/DiffViewer.js',
|
|
111
|
-
'lib/client/review-console/useReviewDiff.js',
|
|
112
|
-
]) {
|
|
113
|
-
if (existsSync(resolve(root, stale))) fail(`removed acceptance UI survived as stale build output: ${stale}`)
|
|
114
|
-
}
|
|
115
|
-
ok('removed Diff UI has no stale publish artifact')
|
|
116
|
-
|
|
117
|
-
// 4. Manual strict Typert artifacts: one descriptor identity feeds Host Loader and Client mount.
|
|
118
|
-
if (manifest.dependencies?.['@deepseek-ai/dsh-typert-generator'] || manifest.devDependencies?.['@deepseek-ai/dsh-typert-generator']) {
|
|
119
|
-
fail('manual contribution package must not depend on @deepseek-ai/dsh-typert-generator')
|
|
120
|
-
}
|
|
121
|
-
const typertExport = exportsMap['./typert']?.default
|
|
122
|
-
const remoteExport = exportsMap['./remote']?.default
|
|
123
|
-
if (typeof typertExport !== 'string' || typeof remoteExport !== 'string') {
|
|
124
|
-
fail('package must export both ./typert and ./remote artifacts')
|
|
125
|
-
}
|
|
126
|
-
let hostContribution
|
|
127
|
-
let remoteContribution
|
|
128
|
-
try {
|
|
129
|
-
hostContribution = (await import(pathToFileURL(resolve(root, typertExport)).href)).TYPERT
|
|
130
|
-
remoteContribution = (await import(pathToFileURL(resolve(root, remoteExport)).href)).default
|
|
131
|
-
} catch (error) {
|
|
132
|
-
fail(`Typert artifacts failed to import: ${error instanceof Error ? error.stack ?? error.message : String(error)}`)
|
|
133
|
-
}
|
|
134
|
-
if (hostContribution?.package !== manifest.name || hostContribution.face !== 'host') fail('./typert has invalid package/face identity')
|
|
135
|
-
if (remoteContribution?.package !== manifest.name) fail('./remote has invalid package identity')
|
|
136
|
-
if (hostContribution.invocations !== remoteContribution.descriptors) fail('./typert and ./remote must share one descriptor array instance')
|
|
137
|
-
const expectedRemoteMethods = ['sidebarTopology', 'current', 'list', 'preflightCreate', 'createWithInitialCommit', 'create', 'inspect', 'reviewDiff', 'preflight', 'previewRecoveryPreflight', 'preparePreviewRecoveryAnalysis', 'createPreviewRecoveryHandoff', 'preview', 'checkpoint', 'resumeRevision', 'prepareReviewRegeneration', 'rollbackPreview', 'discard', 'finalize', 'finalizePreview', 'setRetention', 'retryCleanup', 'beginNextIteration']
|
|
138
|
-
if (JSON.stringify(hostContribution.invocations.map(value => value.method)) !== JSON.stringify(expectedRemoteMethods)) {
|
|
139
|
-
fail(`manual Remote methods differ from the required surface: ${hostContribution.invocations.map(value => value.method).join(', ')}`)
|
|
140
|
-
}
|
|
141
|
-
for (const descriptor of hostContribution.invocations) {
|
|
142
|
-
if (descriptor.namespace !== 'gitWorktree' || descriptor.service !== 'gitWorktree') fail(`invalid Remote identity for ${descriptor.method}`)
|
|
143
|
-
if (descriptor.result?.mode !== 'strict' || !descriptor.result.schema?._zod) fail(`Remote ${descriptor.method} result lacks a Zod v4 strict codec`)
|
|
144
|
-
for (const parameter of descriptor.parameters ?? []) {
|
|
145
|
-
if (parameter.codec?.mode !== 'strict' || !parameter.codec.schema?._zod) fail(`Remote ${descriptor.method}/${parameter.wire} lacks a Zod v4 strict codec`)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
ok(`manual strict ./typert + ./remote contribution (${expectedRemoteMethods.length} methods)`)
|
|
149
|
-
|
|
150
|
-
// 5. Host entry metadata required by the Cordis loader.
|
|
151
|
-
const entryRel = exportsMap['.']?.default ?? manifest.main
|
|
152
|
-
if (typeof entryRel !== 'string') fail('exports["."].default is not a path')
|
|
153
|
-
const entry = readFileSync(resolve(root, entryRel), 'utf8')
|
|
154
|
-
if (!/export\s+(?:async\s+)?function\s+apply\b/.test(entry)) fail(`${entryRel} does not export function apply`)
|
|
155
|
-
if (!/export\s+const\s+inject\b/.test(entry)) fail(`${entryRel} does not export const inject`)
|
|
156
|
-
ok(`${entryRel} exports Host apply + inject`)
|
|
157
|
-
|
|
158
|
-
// 6. Browser ToolView declaration and executable ModuleLoader closure.
|
|
159
|
-
const client = manifest.dsh?.client
|
|
160
|
-
if (!client || client.platform !== 'web' || !Array.isArray(client.inject) || client.inject.length === 0) {
|
|
161
|
-
fail('package.json must declare dsh.client { platform: "web", inject: [...] }')
|
|
162
|
-
}
|
|
163
|
-
const requiredClientPackages = [
|
|
164
|
-
'@deepseek-ai/dsh-api-session-controller',
|
|
165
|
-
'@deepseek-ai/dsh-api-workspace-controller',
|
|
166
|
-
'@deepseek-ai/dsh-client-ui-conversation',
|
|
167
|
-
'@deepseek-ai/dsh-api-gateway',
|
|
168
|
-
]
|
|
169
|
-
const missingClientPackages = requiredClientPackages.filter(name => !client.inject.includes(name))
|
|
170
|
-
if (missingClientPackages.length > 0) {
|
|
171
|
-
fail(`package.json dsh.client.inject is missing: ${missingClientPackages.join(', ')}`)
|
|
172
|
-
}
|
|
173
|
-
const clientRel = exportsMap['./client']?.default
|
|
174
|
-
if (typeof clientRel !== 'string') fail('exports["./client"].default is required for dsh.client')
|
|
175
|
-
const clientSource = readFileSync(resolve(root, clientRel), 'utf8')
|
|
176
|
-
const browserRequires = [...clientSource.matchAll(/require\(["']([^"']+)["']\)/gu)].map(match => match[1])
|
|
177
|
-
const allowedBrowserRequires = new Set([
|
|
178
|
-
'react',
|
|
179
|
-
'react/jsx-runtime',
|
|
180
|
-
'@deepseek-ai/dsh-client-ui-primitives',
|
|
181
|
-
'@deepseek-ai/cordis',
|
|
182
|
-
'@deepseek-ai/dsh-client-store',
|
|
183
|
-
])
|
|
184
|
-
const unsupportedBrowserRequires = browserRequires.filter(specifier => !allowedBrowserRequires.has(specifier))
|
|
185
|
-
if (unsupportedBrowserRequires.length > 0) {
|
|
186
|
-
fail(`${clientRel} requires modules absent from the browser ModuleLoader table: ${[...new Set(unsupportedBrowserRequires)].join(', ')}`)
|
|
187
|
-
}
|
|
188
|
-
const sandbox = {}
|
|
189
|
-
sandbox.window = sandbox
|
|
190
|
-
sandbox.globalThis = sandbox
|
|
191
|
-
let handoff
|
|
192
|
-
sandbox.__ModuleLoader__ = {
|
|
193
|
-
load(value) {
|
|
194
|
-
if (handoff !== undefined) fail('client bundle registered more than one ModuleLoader handoff')
|
|
195
|
-
handoff = value
|
|
196
|
-
},
|
|
197
|
-
}
|
|
198
|
-
try {
|
|
199
|
-
vm.runInNewContext(clientSource, sandbox, { filename: clientRel })
|
|
200
|
-
} catch (error) {
|
|
201
|
-
fail(`client bundle script failed before ModuleLoader registration: ${error instanceof Error ? error.message : String(error)}`)
|
|
202
|
-
}
|
|
203
|
-
if (!handoff || handoff.id !== manifest.name || typeof handoff.factory !== 'function') {
|
|
204
|
-
fail(`client bundle must call window.__ModuleLoader__.load with id ${JSON.stringify(manifest.name)} and a factory`)
|
|
205
|
-
}
|
|
206
|
-
const browserRequire = (specifier) => {
|
|
207
|
-
if (specifier === '@deepseek-ai/dsh-client-ui-primitives') return { Modal: () => null, Menu: () => null }
|
|
208
|
-
return require(specifier)
|
|
209
|
-
}
|
|
210
|
-
let clientExports
|
|
211
|
-
try {
|
|
212
|
-
clientExports = handoff.factory(browserRequire)
|
|
213
|
-
} catch (error) {
|
|
214
|
-
fail(`client bundle factory failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`)
|
|
215
|
-
}
|
|
216
|
-
if (!clientExports || typeof clientExports.apply !== 'function' || !Array.isArray(clientExports.inject)) {
|
|
217
|
-
fail('client bundle factory did not return apply + inject')
|
|
218
|
-
}
|
|
219
|
-
if (!clientExports.inject.includes('remote')) fail('client bundle must inject the official Remote service before $mount')
|
|
220
|
-
if (
|
|
221
|
-
if (
|
|
222
|
-
if (clientExports.inject.includes('
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Pre-publish integrity gate. It validates every declared export, the Host
|
|
3
|
+
* mount patch, and the browser closure bundle instead of checking only the
|
|
4
|
+
* root entry. Set CHECK_PUBLISH_REQUIRE_CLEAN=1 in release CI to additionally
|
|
5
|
+
* require a clean Git checkout.
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from 'node:child_process'
|
|
8
|
+
import { createRequire } from 'node:module'
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
10
|
+
import { dirname, relative, resolve, sep } from 'node:path'
|
|
11
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
12
|
+
import vm from 'node:vm'
|
|
13
|
+
|
|
14
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
15
|
+
const require = createRequire(import.meta.url)
|
|
16
|
+
const fail = (message) => { console.error(`check-publish: FAIL: ${message}`); process.exit(1) }
|
|
17
|
+
const ok = (message) => console.log(`check-publish: ok: ${message}`)
|
|
18
|
+
const manifest = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'))
|
|
19
|
+
|
|
20
|
+
function exportTargets(value, label) {
|
|
21
|
+
if (typeof value === 'string') return [{ condition: 'default', target: value }]
|
|
22
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) fail(`${label} must be a path or condition map`)
|
|
23
|
+
return Object.entries(value).flatMap(([condition, nested]) => {
|
|
24
|
+
if (typeof nested === 'string') return [{ condition, target: nested }]
|
|
25
|
+
return exportTargets(nested, `${label}.${condition}`)
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function includedByFiles(target) {
|
|
30
|
+
const normalized = target.replace(/^\.\//, '').replaceAll('\\', '/')
|
|
31
|
+
if (normalized === 'package.json') return true
|
|
32
|
+
return (manifest.files ?? []).some((entry) => {
|
|
33
|
+
const normalizedEntry = String(entry).replace(/^\.\//, '').replaceAll('\\', '/').replace(/\/$/, '')
|
|
34
|
+
return normalized === normalizedEntry || normalized.startsWith(`${normalizedEntry}/`)
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 1. Host singleton packages must resolve from DSH instead of a plugin-private dependency tree.
|
|
39
|
+
const hostSingletonPackages = [
|
|
40
|
+
'@deepseek-ai/cordis',
|
|
41
|
+
'@deepseek-ai/cordis-plugin-loader',
|
|
42
|
+
'@deepseek-ai/dsh-agent',
|
|
43
|
+
'@deepseek-ai/dsh-commands',
|
|
44
|
+
'@deepseek-ai/dsh-session',
|
|
45
|
+
'@deepseek-ai/dsh-fs',
|
|
46
|
+
'@deepseek-ai/dsh-skill',
|
|
47
|
+
'@deepseek-ai/dsh-skill-filesystem',
|
|
48
|
+
'@deepseek-ai/dsh-subprocess',
|
|
49
|
+
'@deepseek-ai/dsh-system-prompt',
|
|
50
|
+
'@deepseek-ai/dsh-tools',
|
|
51
|
+
'@deepseek-ai/dsh-typert-protocol',
|
|
52
|
+
'@deepseek-ai/dsh-typert-registry',
|
|
53
|
+
]
|
|
54
|
+
for (const packageName of hostSingletonPackages) {
|
|
55
|
+
if (manifest.dependencies?.[packageName] !== undefined) {
|
|
56
|
+
fail(`${packageName} must be a peerDependency; a private copy breaks Host service identity`)
|
|
57
|
+
}
|
|
58
|
+
if (typeof manifest.peerDependencies?.[packageName] !== 'string') {
|
|
59
|
+
fail(`${packageName} is missing from peerDependencies`)
|
|
60
|
+
}
|
|
61
|
+
if (typeof manifest.devDependencies?.[packageName] !== 'string') {
|
|
62
|
+
fail(`${packageName} is missing from devDependencies`)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
ok(`${hostSingletonPackages.length} Host singleton packages are peer-provided and locally testable`)
|
|
66
|
+
|
|
67
|
+
// 2. Host bundle declaration and patch.
|
|
68
|
+
const patchRel = manifest.dsh?.bundle?.patch
|
|
69
|
+
if (typeof patchRel !== 'string' || patchRel.length === 0) {
|
|
70
|
+
fail('package.json lacks "dsh".bundle.patch — dsh plugin add would install a plain dependency without mounting it')
|
|
71
|
+
}
|
|
72
|
+
const patchPath = resolve(root, patchRel)
|
|
73
|
+
if (!existsSync(patchPath)) fail(`declared patch file ${patchRel} does not exist`)
|
|
74
|
+
if (!includedByFiles(patchRel)) fail(`${patchRel} is not covered by package.json "files"`)
|
|
75
|
+
const patch = readFileSync(patchPath, 'utf8')
|
|
76
|
+
if (!patch.includes('- insert:') || !patch.includes(`name: ${manifest.name}`)) {
|
|
77
|
+
fail(`${patchRel} must insert one plugin row named ${manifest.name}`)
|
|
78
|
+
}
|
|
79
|
+
const { WORKSPACE_PROVIDER_CONDITION } = await import(pathToFileURL(resolve(root, 'lib/workspace-provider-lifecycle.js')).href)
|
|
80
|
+
if (!/- id: ui-workspace\s+name: ['"]?@deepseek-ai\/dsh-client-ui-workspace['"]?\s+disabled: !!js /u.test(patch)
|
|
81
|
+
|| !patch.includes(`disabled: !!js ${JSON.stringify(WORKSPACE_PROVIDER_CONDITION)}`)) {
|
|
82
|
+
fail(`${patchRel} must share the conditional Workspace selection used by the Host lifecycle`)
|
|
83
|
+
}
|
|
84
|
+
if (manifest.dsh?.client?.inject?.includes('@deepseek-ai/dsh-client-ui-workspace')) {
|
|
85
|
+
fail('dsh.client.inject must not request the disabled official ui-workspace Client module')
|
|
86
|
+
}
|
|
87
|
+
if (!manifest.files?.includes('NOTICE') || !existsSync(resolve(root, 'NOTICE'))) {
|
|
88
|
+
fail('the official Workspace Client derivative requires a published NOTICE')
|
|
89
|
+
}
|
|
90
|
+
ok(`Host mount patch ${patchRel} replaces official ui-workspace with one gated derivative`)
|
|
91
|
+
|
|
92
|
+
// 3. Every package export must exist and be included in the tarball.
|
|
93
|
+
const exportsMap = manifest.exports
|
|
94
|
+
if (!exportsMap || typeof exportsMap !== 'object' || Array.isArray(exportsMap)) fail('package.json lacks an exports map')
|
|
95
|
+
let exportCount = 0
|
|
96
|
+
for (const [subpath, value] of Object.entries(exportsMap)) {
|
|
97
|
+
for (const { condition, target } of exportTargets(value, `exports[${JSON.stringify(subpath)}]`)) {
|
|
98
|
+
exportCount += 1
|
|
99
|
+
if (!target.startsWith('./')) fail(`export ${subpath} (${condition}) must be package-relative: ${target}`)
|
|
100
|
+
const absolute = resolve(root, target)
|
|
101
|
+
const inside = relative(root, absolute)
|
|
102
|
+
if (inside.startsWith(`..${sep}`) || inside === '..') fail(`export ${subpath} escapes the package: ${target}`)
|
|
103
|
+
if (!existsSync(absolute)) fail(`export ${subpath} (${condition}) points to missing ${target} — run build first`)
|
|
104
|
+
if (!includedByFiles(target)) fail(`export ${subpath} (${condition}) target ${target} is not covered by package.json "files"`)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
ok(`${exportCount} export targets exist and are publishable`)
|
|
108
|
+
|
|
109
|
+
for (const stale of [
|
|
110
|
+
'lib/client/review-console/DiffViewer.js',
|
|
111
|
+
'lib/client/review-console/useReviewDiff.js',
|
|
112
|
+
]) {
|
|
113
|
+
if (existsSync(resolve(root, stale))) fail(`removed acceptance UI survived as stale build output: ${stale}`)
|
|
114
|
+
}
|
|
115
|
+
ok('removed Diff UI has no stale publish artifact')
|
|
116
|
+
|
|
117
|
+
// 4. Manual strict Typert artifacts: one descriptor identity feeds Host Loader and Client mount.
|
|
118
|
+
if (manifest.dependencies?.['@deepseek-ai/dsh-typert-generator'] || manifest.devDependencies?.['@deepseek-ai/dsh-typert-generator']) {
|
|
119
|
+
fail('manual contribution package must not depend on @deepseek-ai/dsh-typert-generator')
|
|
120
|
+
}
|
|
121
|
+
const typertExport = exportsMap['./typert']?.default
|
|
122
|
+
const remoteExport = exportsMap['./remote']?.default
|
|
123
|
+
if (typeof typertExport !== 'string' || typeof remoteExport !== 'string') {
|
|
124
|
+
fail('package must export both ./typert and ./remote artifacts')
|
|
125
|
+
}
|
|
126
|
+
let hostContribution
|
|
127
|
+
let remoteContribution
|
|
128
|
+
try {
|
|
129
|
+
hostContribution = (await import(pathToFileURL(resolve(root, typertExport)).href)).TYPERT
|
|
130
|
+
remoteContribution = (await import(pathToFileURL(resolve(root, remoteExport)).href)).default
|
|
131
|
+
} catch (error) {
|
|
132
|
+
fail(`Typert artifacts failed to import: ${error instanceof Error ? error.stack ?? error.message : String(error)}`)
|
|
133
|
+
}
|
|
134
|
+
if (hostContribution?.package !== manifest.name || hostContribution.face !== 'host') fail('./typert has invalid package/face identity')
|
|
135
|
+
if (remoteContribution?.package !== manifest.name) fail('./remote has invalid package identity')
|
|
136
|
+
if (hostContribution.invocations !== remoteContribution.descriptors) fail('./typert and ./remote must share one descriptor array instance')
|
|
137
|
+
const expectedRemoteMethods = ['sidebarTopology', 'current', 'list', 'preflightCreate', 'createWithInitialCommit', 'create', 'inspect', 'reviewDiff', 'preflight', 'previewRecoveryPreflight', 'preparePreviewRecoveryAnalysis', 'createPreviewRecoveryHandoff', 'preview', 'checkpoint', 'resumeRevision', 'prepareReviewRegeneration', 'rollbackPreview', 'discard', 'finalize', 'finalizePreview', 'setRetention', 'retryCleanup', 'beginNextIteration']
|
|
138
|
+
if (JSON.stringify(hostContribution.invocations.map(value => value.method)) !== JSON.stringify(expectedRemoteMethods)) {
|
|
139
|
+
fail(`manual Remote methods differ from the required surface: ${hostContribution.invocations.map(value => value.method).join(', ')}`)
|
|
140
|
+
}
|
|
141
|
+
for (const descriptor of hostContribution.invocations) {
|
|
142
|
+
if (descriptor.namespace !== 'gitWorktree' || descriptor.service !== 'gitWorktree') fail(`invalid Remote identity for ${descriptor.method}`)
|
|
143
|
+
if (descriptor.result?.mode !== 'strict' || !descriptor.result.schema?._zod) fail(`Remote ${descriptor.method} result lacks a Zod v4 strict codec`)
|
|
144
|
+
for (const parameter of descriptor.parameters ?? []) {
|
|
145
|
+
if (parameter.codec?.mode !== 'strict' || !parameter.codec.schema?._zod) fail(`Remote ${descriptor.method}/${parameter.wire} lacks a Zod v4 strict codec`)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
ok(`manual strict ./typert + ./remote contribution (${expectedRemoteMethods.length} methods)`)
|
|
149
|
+
|
|
150
|
+
// 5. Host entry metadata required by the Cordis loader.
|
|
151
|
+
const entryRel = exportsMap['.']?.default ?? manifest.main
|
|
152
|
+
if (typeof entryRel !== 'string') fail('exports["."].default is not a path')
|
|
153
|
+
const entry = readFileSync(resolve(root, entryRel), 'utf8')
|
|
154
|
+
if (!/export\s+(?:async\s+)?function\s+apply\b/.test(entry)) fail(`${entryRel} does not export function apply`)
|
|
155
|
+
if (!/export\s+const\s+inject\b/.test(entry)) fail(`${entryRel} does not export const inject`)
|
|
156
|
+
ok(`${entryRel} exports Host apply + inject`)
|
|
157
|
+
|
|
158
|
+
// 6. Browser ToolView declaration and executable ModuleLoader closure.
|
|
159
|
+
const client = manifest.dsh?.client
|
|
160
|
+
if (!client || client.platform !== 'web' || !Array.isArray(client.inject) || client.inject.length === 0) {
|
|
161
|
+
fail('package.json must declare dsh.client { platform: "web", inject: [...] }')
|
|
162
|
+
}
|
|
163
|
+
const requiredClientPackages = [
|
|
164
|
+
'@deepseek-ai/dsh-api-session-controller',
|
|
165
|
+
'@deepseek-ai/dsh-api-workspace-controller',
|
|
166
|
+
'@deepseek-ai/dsh-client-ui-conversation',
|
|
167
|
+
'@deepseek-ai/dsh-api-gateway',
|
|
168
|
+
]
|
|
169
|
+
const missingClientPackages = requiredClientPackages.filter(name => !client.inject.includes(name))
|
|
170
|
+
if (missingClientPackages.length > 0) {
|
|
171
|
+
fail(`package.json dsh.client.inject is missing: ${missingClientPackages.join(', ')}`)
|
|
172
|
+
}
|
|
173
|
+
const clientRel = exportsMap['./client']?.default
|
|
174
|
+
if (typeof clientRel !== 'string') fail('exports["./client"].default is required for dsh.client')
|
|
175
|
+
const clientSource = readFileSync(resolve(root, clientRel), 'utf8')
|
|
176
|
+
const browserRequires = [...clientSource.matchAll(/require\(["']([^"']+)["']\)/gu)].map(match => match[1])
|
|
177
|
+
const allowedBrowserRequires = new Set([
|
|
178
|
+
'react',
|
|
179
|
+
'react/jsx-runtime',
|
|
180
|
+
'@deepseek-ai/dsh-client-ui-primitives',
|
|
181
|
+
'@deepseek-ai/cordis',
|
|
182
|
+
'@deepseek-ai/dsh-client-store',
|
|
183
|
+
])
|
|
184
|
+
const unsupportedBrowserRequires = browserRequires.filter(specifier => !allowedBrowserRequires.has(specifier))
|
|
185
|
+
if (unsupportedBrowserRequires.length > 0) {
|
|
186
|
+
fail(`${clientRel} requires modules absent from the browser ModuleLoader table: ${[...new Set(unsupportedBrowserRequires)].join(', ')}`)
|
|
187
|
+
}
|
|
188
|
+
const sandbox = {}
|
|
189
|
+
sandbox.window = sandbox
|
|
190
|
+
sandbox.globalThis = sandbox
|
|
191
|
+
let handoff
|
|
192
|
+
sandbox.__ModuleLoader__ = {
|
|
193
|
+
load(value) {
|
|
194
|
+
if (handoff !== undefined) fail('client bundle registered more than one ModuleLoader handoff')
|
|
195
|
+
handoff = value
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
vm.runInNewContext(clientSource, sandbox, { filename: clientRel })
|
|
200
|
+
} catch (error) {
|
|
201
|
+
fail(`client bundle script failed before ModuleLoader registration: ${error instanceof Error ? error.message : String(error)}`)
|
|
202
|
+
}
|
|
203
|
+
if (!handoff || handoff.id !== manifest.name || typeof handoff.factory !== 'function') {
|
|
204
|
+
fail(`client bundle must call window.__ModuleLoader__.load with id ${JSON.stringify(manifest.name)} and a factory`)
|
|
205
|
+
}
|
|
206
|
+
const browserRequire = (specifier) => {
|
|
207
|
+
if (specifier === '@deepseek-ai/dsh-client-ui-primitives') return { Modal: () => null, Menu: () => null }
|
|
208
|
+
return require(specifier)
|
|
209
|
+
}
|
|
210
|
+
let clientExports
|
|
211
|
+
try {
|
|
212
|
+
clientExports = handoff.factory(browserRequire)
|
|
213
|
+
} catch (error) {
|
|
214
|
+
fail(`client bundle factory failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`)
|
|
215
|
+
}
|
|
216
|
+
if (!clientExports || typeof clientExports.apply !== 'function' || !Array.isArray(clientExports.inject)) {
|
|
217
|
+
fail('client bundle factory did not return apply + inject')
|
|
218
|
+
}
|
|
219
|
+
if (!clientExports.inject.includes('remote')) fail('client bundle must inject the official Remote service before $mount')
|
|
220
|
+
if (clientExports.inject.includes('remote.directoryPicker')) fail('client bundle entry must not wait for the directory-picker Remote: its provider entry waits for uiWorkspace, so requiring it here deadlocks the 0.2.x web boot')
|
|
221
|
+
if (!clientSource.includes("'remote.directoryPicker'") && !clientSource.includes('"remote.directoryPicker"')) fail('client bundle must resolve the directory-picker lazily at call time through the managed context (deferred facade)')
|
|
222
|
+
if (clientExports.inject.includes('conversation')) fail('client bundle root must not wait for conversation: uiConversation depends on the uiWorkspace restored by this plugin')
|
|
223
|
+
if (clientExports.inject.includes('connection')) fail('client bundle root must keep connection-dependent Worktree views in the deferred child fiber')
|
|
224
|
+
ok(`${clientRel} registers ${manifest.name} through the browser ModuleLoader contract without a uiWorkspace/uiConversation boot cycle`)
|
|
225
|
+
|
|
226
|
+
// 7. Release CI and npm's prepublish lifecycle require committed source identity;
|
|
227
|
+
// local review builds intentionally run against a dirty managed Worktree.
|
|
228
|
+
const requireClean = process.env.CHECK_PUBLISH_REQUIRE_CLEAN === '1'
|
|
229
|
+
|| process.argv.includes('--require-clean')
|
|
230
|
+
if (requireClean) {
|
|
231
|
+
let dirty = ''
|
|
232
|
+
try { dirty = execSync('git status --porcelain --untracked-files=all', { cwd: root, encoding: 'utf8' }) } catch {}
|
|
233
|
+
if (dirty.trim() !== '') fail(`working tree differs from HEAD:\n${dirty}`)
|
|
234
|
+
ok('working tree matches HEAD')
|
|
235
|
+
} else {
|
|
236
|
+
ok('Git cleanliness check deferred (set CHECK_PUBLISH_REQUIRE_CLEAN=1 for release CI)')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
console.log('check-publish: all checks passed')
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { createRequire } from 'node:module'
|
|
2
|
-
import {
|
|
3
|
-
readOfficialWorkspaceClient,
|
|
4
|
-
WORKSPACE_LOCALE_VERSION,
|
|
5
|
-
WORKSPACE_UI_VERSION,
|
|
6
|
-
} from './workspace-sidebar-upstream.mjs'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import {
|
|
3
|
+
readOfficialWorkspaceClient,
|
|
4
|
+
WORKSPACE_LOCALE_VERSION,
|
|
5
|
+
WORKSPACE_UI_VERSION,
|
|
6
|
+
} from './workspace-sidebar-upstream.mjs'
|
|
7
|
+
import { readNextWorkspaceClient, NEXT_WORKSPACE_VERSION } from './workspace-sidebar-upstream-next.mjs'
|
|
8
|
+
import { readAlphaWorkspaceClient, ALPHA_WORKSPACE_VERSION } from './workspace-sidebar-upstream-alpha.mjs'
|
|
9
|
+
|
|
10
|
+
const require = createRequire(import.meta.url)
|
|
11
|
+
const locale = require('@deepseek-ai/dsh-client-locale/package.json').version
|
|
12
|
+
if (locale !== WORKSPACE_LOCALE_VERSION) {
|
|
13
|
+
throw new Error(`Workspace Sidebar compatibility gate failed: locale=${locale}, expected ${WORKSPACE_LOCALE_VERSION}`)
|
|
14
|
+
}
|
|
15
|
+
readOfficialWorkspaceClient()
|
|
16
|
+
readAlphaWorkspaceClient()
|
|
17
|
+
readNextWorkspaceClient()
|
|
18
|
+
console.log(`✓ Workspace Sidebar compatibility gate: ui-workspace ${WORKSPACE_UI_VERSION} + ${ALPHA_WORKSPACE_VERSION} + ${NEXT_WORKSPACE_VERSION} SHA-256, locale ${locale}`)
|
package/scripts/clean-lib.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { rmSync } from 'node:fs'
|
|
2
|
-
import { fileURLToPath } from 'node:url'
|
|
3
|
-
|
|
4
|
-
// TypeScript does not delete outputs for removed source files. Always rebuild
|
|
5
|
-
// the publishable tree from an empty lib/ directory so stale UI modules cannot
|
|
6
|
-
// survive into a tarball.
|
|
7
|
-
rmSync(fileURLToPath(new URL('../lib/', import.meta.url)), { recursive: true, force: true })
|
|
1
|
+
import { rmSync } from 'node:fs'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
|
|
4
|
+
// TypeScript does not delete outputs for removed source files. Always rebuild
|
|
5
|
+
// the publishable tree from an empty lib/ directory so stale UI modules cannot
|
|
6
|
+
// survive into a tarball.
|
|
7
|
+
rmSync(fileURLToPath(new URL('../lib/', import.meta.url)), { recursive: true, force: true })
|