Files
zhu1090093659 dc7e26b9f0 refactor(shared): drop dead shared artifacts and realign references
- Remove the sync-manifest entry for shared/host/run-guarded.ts and the three
  generated copies under packages/{dsh-usage,dsh-task-board,dsh-git-graph}/src/host/:
  nothing in this repository imported the module (the satellite repositories
  carry their own copies), so the entry only kept three unread files in sync.
  The shared source and its spec stay.
- Delete the unused mobileBundle helper and its node:module createRequire import
  from shared/tsdown.client.ts; the standalone mobile bundle has been gone since
  0.4.0.
- Update the sync composition guard in scripts/sync-shared.test.mjs (99 -> 96
  copies, 48 -> 45 host copies) and refresh scripts/lib-artifact-fingerprints.json.
- Re-point the notes, READMEs, CONTRIBUTING and docs that still linked the
  skin-center contract files to the dsh-skins repository, and prune the stale
  screenshots.
- Record the decision in
  .agents/notes/implemented/simplification/2026-09-26-dead-shared-artifacts-removed.md
  and correct the run-guarded facts in the aggregate fault-isolation note.
2026-09-27 20:47:07 +08:00

323 lines
13 KiB
JavaScript

#!/usr/bin/env node
'use strict'
/**
* sync-shared — mirror shared/client/settings and shared/host (the
* family-shared modules) into the consuming plugin packages.
*
* The trio lives exactly once under shared/client/settings and is copied,
* with a generated-file header, to each consumer's src/client/. The copies
* are committed (same policy as the dsh-skins built assets) so packages stay
* self-contained for typecheck, test, and publish. The --check mode fails on
* any drift and runs in CI through test:scripts.
*
* Usage:
* node scripts/sync-shared.mjs # rewrite every consumer copy
* node scripts/sync-shared.mjs --check # fail when any copy drifts
*/
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url))
export const REPO_ROOT = resolve(SCRIPT_DIR, '..')
/**
* The sync manifest: every shared-module copy this repo carries. Sources
* live exactly once under shared/; consumers import the committed copy.
*/
// Consumers of the settings card trio: one list, three derivations below.
const SETTINGS_CONSUMERS = ['dsh-task-board', 'dsh-remote-web-ui', 'dsh-market', 'dsh-liangshen']
const SETTINGS_CARD_CONSUMERS = [...SETTINGS_CONSUMERS]
// Consumers of the entry-bound form fallback: every package whose card binds a
// family settings namespace through the shared forms service. It is a superset
// of the card trio — dsh-usage and dsh-session-archive carry their own cards but
// bind the same way.
const SETTINGS_ENTRY_FORM_CONSUMERS = [...SETTINGS_CONSUMERS, 'dsh-usage', 'dsh-session-archive']
const MANIFEST = [
{
file: 'settings-form.ts',
source: 'shared/client/settings/settings-form.ts',
targets: SETTINGS_CONSUMERS.map(pkg => `packages/${pkg}/src/client/settings-form.ts`),
},
{
file: 'PluginSettingsCard.tsx',
source: 'shared/client/settings/PluginSettingsCard.tsx',
targets: SETTINGS_CONSUMERS.map(pkg => `packages/${pkg}/src/client/PluginSettingsCard.tsx`),
},
{
file: 'settings-card.module.css',
source: 'shared/client/settings/settings-card.module.css',
targets: SETTINGS_CARD_CONSUMERS.map(pkg => `packages/${pkg}/src/client/settings-card.module.css`),
},
{
// Family plugin-card seat: one card component, dispatched into whichever
// plugin-card seat the running host declares (the family group's list seat
// or the official keyed one), with the refusal logged instead of swallowed
// (issue #1589).
file: 'plugin-card-seat.ts',
source: 'shared/client/settings/plugin-card-seat.ts',
targets: [
'packages/dsh-remote-web-ui/src/client/plugin-card-seat.ts',
'packages/dsh-task-board/src/client/plugin-card-seat.ts',
'packages/dsh-liangshen/src/client/plugin-card-seat.ts',
],
},
{
// Fallback settings transport for a page that serves no family binder: the
// form is bound to the profile entry id the Host actually serves, and
// rebound when the shared describe mirror answers with a different one of
// this package's rows. A one-shot guess left the card on an entry the Host
// does not serve, so every save was rejected (issue: the LiangShen card
// reported "the deployment did not accept these values" for every edit).
file: 'settings-entry-form.ts',
source: 'shared/client/settings/settings-entry-form.ts',
targets: SETTINGS_ENTRY_FORM_CONSUMERS.map(pkg => `packages/${pkg}/src/client/settings-entry-form.ts`),
},
{
file: 'poll-guard.ts',
source: 'shared/host/poll-guard.ts',
targets: [
'packages/dsh-git-graph/src/host/poll-guard.ts',
],
},
{
file: 'dsh-home.ts',
source: 'shared/host/dsh-home.ts',
targets: [
'packages/dsh-liangshen/src/dsh-home.ts',
'packages/dsh-task-board/src/dsh-home.ts',
'packages/dsh-plugin-manager/src/host/dsh-home.ts',
'packages/dsh-remote-web-ui/src/dsh-home.ts',
'packages/dsh-ssh/src/dsh-home.ts',
'packages/dsh-web-settings/src/dsh-home.ts',
'packages/dsh-market/src/dsh-home.ts',
'packages/dsh-git-graph/src/host/dsh-home.ts',
'packages/dsh-usage/src/dsh-home.ts',
'packages/dsh-session-archive/src/dsh-home.ts',
],
},
{
file: 'git-runner.ts',
source: 'shared/host/git-runner.ts',
targets: ['packages/dsh-git-graph/src/host/git-runner.ts'],
},
{
file: 'legacy-migration.ts',
source: 'shared/host/legacy-migration.ts',
targets: [
'packages/dsh-plugin-manager/src/host/legacy-migration.ts',
],
},
{
file: 'mount-once.ts',
source: 'shared/host/mount-once.ts',
targets: [
'packages/dsh-ssh/src/mount-once.ts',
'packages/dsh-remote-web-ui/src/mount-once.ts',
'packages/dsh-liangshen/src/mount-once.ts',
'packages/dsh-task-board/src/mount-once.ts',
'packages/dsh-git-graph/src/mount-once.ts',
'packages/dsh-plugin-manager/src/mount-once.ts',
'packages/dsh-web-settings/src/mount-once.ts',
'packages/dsh-skill-explorer/src/mount-once.ts',
'packages/dsh-market/src/mount-once.ts',
'packages/dsh-usage/src/mount-once.ts',
'packages/dsh-session-archive/src/mount-once.ts',
'packages/dsh-model-capabilities/src/mount-once.ts',
'packages/dsh-update/src/mount-once.ts',
],
},
{
file: 'telemetry.ts',
source: 'shared/client/telemetry.ts',
targets: [
'packages/dsh-market/src/client/telemetry.ts',
'packages/dsh-git-graph/src/client/telemetry.ts',
'packages/dsh-plugin-manager/src/client/telemetry.ts',
'packages/dsh-remote-web-ui/src/client/telemetry.ts',
'packages/dsh-session-id/src/client/telemetry.ts',
'packages/dsh-skill-explorer/src/client/telemetry.ts',
'packages/dsh-ssh/src/client/telemetry.ts',
'packages/dsh-task-board/src/client/telemetry.ts',
'packages/dsh-web-settings/src/client/telemetry.ts',
'packages/dsh-update/src/client/telemetry.ts',
],
},
{
file: 'sse-leader.ts',
source: 'shared/client/sse-leader.ts',
targets: ['packages/dsh-git-graph/src/client/sse-leader.ts'],
},
{
// Main-view Session derivation: the Client Session Controller dropped its
// global `current` selection at 0.1.6-alpha.2 in favour of per-source
// ownership counts, so every plugin that needs "the session the main view
// shows" shares this one derivation instead of guessing at a replacement.
file: 'main-session.ts',
source: 'shared/client/main-session.ts',
targets: [
'packages/dsh-task-board/src/client/main-session.ts',
'packages/dsh-session-id/src/client/main-session.ts',
'packages/dsh-liangshen/src/client/main-session.ts',
'packages/dsh-git-graph/src/client/main-session.ts',
'packages/dsh-session-archive/src/client/main-session.ts',
],
},
{
file: 'pair-access.ts',
source: 'shared/host/pair-access.ts',
targets: ['packages/dsh-git-graph/src/host/pair-access.ts', 'packages/dsh-skill-explorer/src/pair-access.ts', 'packages/dsh-usage/src/host/pair-access.ts'],
},
{
file: 'loopback.ts',
source: 'shared/host/loopback.ts',
targets: ['packages/dsh-ssh/src/loopback.ts', 'packages/dsh-git-graph/src/host/loopback.ts', 'packages/dsh-remote-web-ui/src/loopback.ts', 'packages/dsh-task-board/src/loopback.ts', 'packages/dsh-skill-explorer/src/loopback.ts', 'packages/dsh-plugin-manager/src/host/loopback.ts', 'packages/dsh-market/src/loopback.ts', 'packages/dsh-usage/src/host/loopback.ts', 'packages/dsh-session-archive/src/host/loopback.ts', 'packages/dsh-update/src/loopback.ts'],
},
{
file: 'http.ts',
source: 'shared/host/http.ts',
targets: [
'packages/dsh-market/src/http.ts',
'packages/dsh-skill-explorer/src/http.ts',
'packages/dsh-web-settings/src/http.ts',
'packages/dsh-git-graph/src/host/http.ts',
'packages/dsh-ssh/src/http.ts',
'packages/dsh-plugin-manager/src/host/http.ts',
'packages/dsh-remote-web-ui/src/http.ts',
'packages/dsh-task-board/src/http.ts',
'packages/dsh-usage/src/host/http.ts',
'packages/dsh-session-archive/src/host/http.ts',
'packages/dsh-update/src/http.ts',
],
},
{
// The shared setup carries the browser-module loader and the storage
// repair Node 25 needs (see its header); a consumer of the shared vitest
// config runs this copy through setupFiles.
file: 'vitest.setup.ts',
source: 'shared/vitest.setup.ts',
targets: [
'packages/dsh-web-settings/vitest.setup.ts',
'packages/dsh-remote-web-ui/vitest.setup.ts',
'packages/dsh-git-graph/vitest.setup.ts',
'packages/dsh-task-board/vitest.setup.ts',
'packages/dsh-update/vitest.setup.ts',
],
},
{
// Page-wide body mutation hub: exactly one document.body childList
// observer shared by every family consumer (sidebar entries, center
// panels, the aggregate shell shims), instead of one per plugin.
file: 'body-mutations.ts',
source: 'shared/client/body-mutations.ts',
targets: [
'packages/dsh-web-all/src/client/body-mutations.ts',
'packages/dsh-usage/src/client/body-mutations.ts',
],
},
{
// Child-process output capture: byte accumulation with a single decode so
// a Windows console's code page (CP936/GBK) never becomes replacement
// characters, and a character split across two `data` reads is restored.
file: 'console-output.ts',
source: 'shared/host/console-output.ts',
targets: [
'packages/dsh-plugin-manager/src/host/console-output.ts',
'packages/dsh-update/src/console-output.ts',
],
},
]
/** One source-to-consumer copy step. */
export function copyEntries(root = REPO_ROOT) {
return MANIFEST.flatMap(entry => entry.targets.map(target => ({
source: join(root, entry.source),
target: join(root, target),
file: entry.file,
sourceRel: entry.source,
})))
}
/** Generated-file header prepended to every copy. */
export function headerFor(file, sourceRel = `shared/client/settings/${file}`) {
const body = `Generated by scripts/sync-shared.mjs from ${sourceRel}. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".`
return file.endsWith('.css') ? `/* ${body} */` : `// ${body}`
}
/** Render a consumer copy from its shared source. */
export function renderCopy(sourceText, file, sourceRel) {
return `${headerFor(file, sourceRel)}
${sourceText}`
}
/** Recover the shared source text from a rendered copy (undefined when the header is missing or mangled). */
export function stripHeader(rendered, file, sourceRel) {
const header = headerFor(file, sourceRel)
if (!rendered.startsWith(header)) return undefined
return rendered.slice(header.length + 1)
}
/** Apply every copy step. Returns the number of rewritten files. */
export async function applySync(root = REPO_ROOT) {
let written = 0
for (const { source, target, file, sourceRel } of copyEntries(root)) {
const sourceText = await readFile(source, 'utf8')
const rendered = renderCopy(sourceText, file, sourceRel)
await mkdir(dirname(target), { recursive: true })
await writeFile(target, rendered)
written += 1
}
return written
}
/** Compare every copy against its source. Returns the drifted entries. */
export async function checkSync(root = REPO_ROOT) {
const drift = []
for (const { source, target, file, sourceRel } of copyEntries(root)) {
const sourceText = await readFile(source, 'utf8')
let rendered
try {
rendered = await readFile(target, 'utf8')
} catch {
drift.push({ target, reason: 'missing' })
continue
}
const stripped = stripHeader(rendered, file, sourceRel)
if (stripped === undefined) {
drift.push({ target, reason: 'header missing or mangled' })
} else if (stripped !== sourceText) {
drift.push({ target, reason: 'content drifted from shared source' })
}
}
return drift
}
async function main() {
const mode = process.argv[2]
if (mode === '--check') {
const drift = await checkSync()
if (drift.length > 0) {
for (const entry of drift) {
console.error(`[sync-shared] drift: ${entry.target} (${entry.reason})`)
}
console.error(`[sync-shared] ${drift.length} consumer copy(ies) out of sync; run "node scripts/sync-shared.mjs"`)
process.exit(1)
}
console.log('[sync-shared] all consumer copies in sync')
return
}
if (mode !== undefined) {
console.error('usage: node scripts/sync-shared.mjs [--check]')
process.exit(2)
}
const written = await applySync()
console.log(`[sync-shared] wrote ${written} consumer copies`)
}
if (process.argv[1] !== undefined && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
await main()
}