Files
workdsh/scripts/verify-layout.mjs
T

99 lines
4.5 KiB
JavaScript

import { execFileSync } from 'node:child_process'
import { existsSync, lstatSync, readFileSync, readlinkSync } from 'node:fs'
import { basename, resolve } from 'node:path'
const root = resolve(import.meta.dirname, '..')
const readJson = path => JSON.parse(readFileSync(resolve(root, path), 'utf8'))
const run = (command, args, cwd = root) => execFileSync(command, args, {
cwd,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
}).trim()
const fail = message => { throw new Error(`verify-layout: ${message}`) }
const workspace = readJson('package.json')
const upstream = readJson('upstream.json')
const plugin = readJson('dsh-plugin-desktop/package.json')
const upstreamPackage = readJson('deepseek-harness/package.json')
const noteDirectory = '.agents/notes/implemented/process'
const noteName = '2026-08-15-pinned-upstream-and-isolated-yarn-workspace'
const notePaths = [`${noteDirectory}/${noteName}.md`, `${noteDirectory}/${noteName}.zh.md`]
const noteRecordPath = `${noteDirectory}/${noteName}.i18n.yaml`
if (workspace.packageManager !== '[email protected]') {
fail('the product workspace must pin [email protected]')
}
if (JSON.stringify(workspace.workspaces) !== JSON.stringify(['dsh-plugin-desktop'])) {
fail('the root Yarn workspace must contain only dsh-plugin-desktop')
}
if (plugin.packageManager !== undefined) {
fail('dsh-plugin-desktop must inherit the root Yarn release')
}
const claudePath = resolve(root, 'CLAUDE.md')
if (!lstatSync(claudePath).isSymbolicLink() || readlinkSync(claudePath) !== 'AGENTS.md') {
fail('CLAUDE.md must link to the outer repository AGENTS.md')
}
for (const legacyFile of [
'pnpm-lock.yaml',
'pnpm-workspace.yaml',
'dsh-plugin-desktop/pnpm-lock.yaml',
'dsh-plugin-desktop/pnpm-workspace.yaml',
]) {
if (existsSync(resolve(root, legacyFile))) fail(`${legacyFile} must not exist`)
}
if (run('git', ['config', '-f', '.gitmodules', '--get', 'submodule.deepseek-harness.path']) !== 'deepseek-harness') {
fail('the upstream submodule path must be deepseek-harness')
}
if (run('git', ['config', '-f', '.gitmodules', '--get', 'submodule.deepseek-harness.url']) !== upstream.repository) {
fail('the upstream submodule URL differs from upstream.json')
}
if (typeof upstreamPackage.packageManager !== 'string' || !upstreamPackage.packageManager.startsWith('pnpm@')) {
fail('the upstream checkout must retain its pnpm package manager')
}
for (const [owner, manifest] of [['root', workspace], ['plugin', plugin]]) {
for (const field of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies', 'resolutions']) {
for (const [name, range] of Object.entries(manifest[field] ?? {})) {
if (typeof range !== 'string') continue
if (/^(?:workspace|portal|link):/u.test(range)
|| (range.startsWith('file:') && range.includes('deepseek-harness'))) {
fail(`${owner} ${field}.${name} bypasses the published DSH package boundary`)
}
}
}
}
const [mode, object] = run('git', ['ls-files', '--stage', '--', 'deepseek-harness']).split(/\s+/u)
if (mode !== '160000') fail('deepseek-harness must be tracked as a Git submodule')
if (object !== upstream.commit) fail(`submodule index is ${object}, expected ${upstream.commit}`)
const upstreamDir = resolve(root, 'deepseek-harness')
if (run('git', ['rev-parse', 'HEAD'], upstreamDir) !== upstream.commit) {
fail('checked-out upstream commit differs from upstream.json')
}
if (run('git', ['status', '--porcelain'], upstreamDir) !== '') {
fail('deepseek-harness contains local changes')
}
if (run('git', ['remote', 'get-url', 'origin'], upstreamDir) !== upstream.repository) {
fail('deepseek-harness origin differs from upstream.json')
}
if (upstreamPackage.version !== upstream.sourceVersion) {
fail('deepseek-harness package version differs from upstream.json')
}
for (const name of Object.keys(plugin.dependencies).filter(name => name === '@deepseek-ai/dsh' || name.startsWith('@deepseek-ai/dsh-'))) {
if (plugin.dependencies[name] !== upstream.runtimePackageVersion) {
fail(`${name} must use the recorded DSH runtime package family`)
}
}
const noteRecord = readFileSync(resolve(root, noteRecordPath), 'utf8')
for (const notePath of notePaths) {
const expected = run('git', ['hash-object', '--', notePath])
const recordLine = `${basename(notePath)}: ${expected}`
if (!noteRecord.split('\n').includes(recordLine)) {
fail(`${noteRecordPath} is stale for ${notePath}`)
}
}
process.stdout.write(`verify-layout: Yarn workspace and upstream ${upstream.commit.slice(0, 10)} are consistent\n`)