Move the 18 flat packages/<name> packages into role-grouped dirs: core/, llm/, bash/, session-persistence/, ui/, support/. Group dirs are pure containers; each package keeps its @deepseek-ai/dsh-* name. Collapse the per-package tsconfig paths maps (base + typecheck) into one @deepseek-ai/dsh-* wildcard with a candidate per group, and derive the publint list from the hierarchy. Update all depth-coupled globs/configs (workspace, tsdown, vitest, eslint, knip, tsconfig includes/refs, per-package tsconfigs, generators, doc-script scopes, type-equiv manifest) and the cross-package/script relative imports in tests. Fix doc-typecheck's workspacePaths() to parse tsconfig JSONC via the TypeScript API instead of a regex comment-strip, which corrupted the new wildcard `/*/` path candidates. WIP: doc cross-links and package/RFC docs still to update.
113 lines
3.4 KiB
TypeScript
113 lines
3.4 KiB
TypeScript
/**
|
|
* Workspace package invariant checks for package-manager-independent quality
|
|
* gates.
|
|
*
|
|
* Run: `tsx scripts/check-workspace-constraints.ts`.
|
|
*/
|
|
|
|
import { readdirSync, readFileSync } from 'node:fs'
|
|
import { join, relative, resolve } from 'node:path'
|
|
|
|
const root = resolve(import.meta.dirname, '..')
|
|
// vendor/* is single-level; packages/<group>/<pkg> nests one level deeper
|
|
// (the group dirs — core/llm/bash/… — are pure containers with no manifest).
|
|
const workspaceGlobs = [
|
|
{ dir: 'vendor', depth: 1 },
|
|
{ dir: 'packages', depth: 2 },
|
|
] as const
|
|
const vendoredPackages = new Set([
|
|
'cordis',
|
|
'cosmokit',
|
|
'schemastery',
|
|
'@cordisjs/plugin-loader',
|
|
'@cordisjs/plugin-include',
|
|
'@cordisjs/plugin-group',
|
|
'@cordisjs/plugin-timer',
|
|
'@cordisjs/plugin-hmr',
|
|
'@cordisjs/plugin-logger-console',
|
|
])
|
|
|
|
/** The subset of package.json fields this constraint check cares about. */
|
|
interface PackageManifest {
|
|
name?: string
|
|
version?: string
|
|
private?: boolean
|
|
type?: string
|
|
peerDependencies?: Record<string, string>
|
|
devDependencies?: Record<string, string>
|
|
}
|
|
|
|
/** One workspace manifest and its repo-relative path. */
|
|
interface WorkspaceManifest {
|
|
dir: string
|
|
manifest: PackageManifest
|
|
}
|
|
|
|
function readJson(path: string): PackageManifest {
|
|
return JSON.parse(readFileSync(path, 'utf8')) as PackageManifest
|
|
}
|
|
|
|
/** Repo-relative dirs holding a package.json, walked to the configured depth. */
|
|
function packageDirs(base: string, depth: number): string[] {
|
|
if (depth === 1) {
|
|
return readdirSync(join(root, base), { withFileTypes: true })
|
|
.filter(entry => entry.isDirectory())
|
|
.map(entry => join(base, entry.name))
|
|
}
|
|
return readdirSync(join(root, base), { withFileTypes: true })
|
|
.filter(entry => entry.isDirectory())
|
|
.flatMap(group => packageDirs(join(base, group.name), depth - 1))
|
|
}
|
|
|
|
function workspaceManifests(): WorkspaceManifest[] {
|
|
const manifests: WorkspaceManifest[] = [
|
|
{ dir: '.', manifest: readJson(join(root, 'package.json')) },
|
|
]
|
|
|
|
for (const { dir: base, depth } of workspaceGlobs) {
|
|
for (const dir of packageDirs(base, depth)) {
|
|
manifests.push({ dir, manifest: readJson(join(root, dir, 'package.json')) })
|
|
}
|
|
}
|
|
|
|
return manifests
|
|
}
|
|
|
|
function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
|
|
const errors: string[] = []
|
|
const label = manifest.name ?? dir
|
|
|
|
if (manifest.private !== true) {
|
|
errors.push(`${label}: package.json must set "private": true`)
|
|
}
|
|
|
|
if (manifest.name && vendoredPackages.has(manifest.name)) {
|
|
return errors
|
|
}
|
|
|
|
if (manifest.name?.startsWith('@deepseek-ai/dsh-') && manifest.name !== '@deepseek-ai/dsh-root') {
|
|
const peer = manifest.peerDependencies?.cordis
|
|
const dev = manifest.devDependencies?.cordis
|
|
|
|
if (!peer) errors.push(`${label}: cordis must be a peerDependency`)
|
|
if (!dev) errors.push(`${label}: cordis must also be a devDependency`)
|
|
if (peer && dev && peer !== dev) {
|
|
errors.push(`${label}: cordis peer (${peer}) and dev (${dev}) ranges must match`)
|
|
}
|
|
if (manifest.version !== '0.0.1') {
|
|
errors.push(`${label}: package.json must set "version": "0.0.1"`)
|
|
}
|
|
if (manifest.type !== 'module') {
|
|
errors.push(`${label}: package.json must set "type": "module"`)
|
|
}
|
|
}
|
|
|
|
return errors.map(error => `${relative(root, join(root, dir, 'package.json'))}: ${error}`)
|
|
}
|
|
|
|
const errors = workspaceManifests().flatMap(checkWorkspace)
|
|
if (errors.length > 0) {
|
|
console.error(errors.join('\n'))
|
|
process.exitCode = 1
|
|
}
|