/** * DSH community-market compatibility guard: keeps the npm package inside the * "Host 接受什么" rules of dsh-community-market's * docs/install-and-uninstall.zh.md, so the catalog's verified npm install * target (repository_backlink) keeps passing for this package: * * - no `cordis` entry in dependencies / peerDependencies / * optionalDependencies (the market preview hard-rejects the legacy Cordis * runtime name in any of the three fields, even when optional), * - no install lifecycle scripts (preinstall / install / postinstall / * prepare) in the **packed** manifest — the repo manifest legitimately * keeps `prepare` for git-install builds; pnpm strips it from the published * surface, and this guard verifies that stripped surface stays clean, * - a safe, bund-visible `dsh.bundle.patch` path (`./cordis.patch.yml`), * - a repository identity that normalizes to a credential-free HTTPS GitHub * owner/repo URL (the catalog ↔ npm backlink comparison), * - an exact stable SemVer version (no ranges, no tags, no prerelease). * * The packed-manifest checks run `pnpm pack` into a temp dir (the `prepare` * script rebuilds the gitignored lib/, like the release flow does). */ import { execFileSync } from 'node:child_process' import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { describe, expect, it } from 'vitest' const ROOT = resolve(fileURLToPath(new URL('..', import.meta.url))) const pkg = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf8')) as { name: string version: string repository?: { type?: string; url?: string } engines?: { node?: string } dsh?: { bundle?: { patch?: string } } scripts?: Record dependencies?: Record peerDependencies?: Record optionalDependencies?: Record } /** The market's lifecycle-script reject list (install-and-uninstall.zh.md). */ const LIFECYCLE_SCRIPTS = ['preinstall', 'install', 'postinstall', 'prepare'] as const /** Stable exact SemVer: exactly three numeric segments (no tag / prerelease). */ const STABLE_SEMVER = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/u /** Read `package.json` from a fresh `pnpm pack` tarball (the publish surface). */ function packedManifest(): Record { const dir = mkdtempSync(join(tmpdir(), 'market-pack-')) try { // On Windows `pnpm` ships as a `.cmd` shim that `execFileSync` cannot // spawn directly (ENOENT). Run it through the shell so the same call // works on every platform; `tar` is available on all supported OSes. execFileSync('pnpm', ['pack', '--pack-destination', dir], { cwd: ROOT, stdio: 'pipe', shell: process.platform === 'win32' }) execFileSync('tar', ['-xzf', join(dir, `${pkg.name}-${pkg.version}.tgz`), '-C', dir, 'package/package.json'], { stdio: 'pipe' }) return JSON.parse(readFileSync(join(dir, 'package/package.json'), 'utf8')) as Record } finally { rmSync(dir, { recursive: true, force: true }) } } describe('DSH community-market manifest compatibility', () => { it('declares no cordis entry in any dependency field (dependencies / peerDependencies / optionalDependencies)', () => { for (const field of ['dependencies', 'peerDependencies', 'optionalDependencies'] as const) { const deps = pkg[field] if (deps === undefined) continue expect(Object.keys(deps), field).not.toContain('cordis') } }) it('declares no install lifecycle scripts in the PACKED manifest (the published surface)', () => { const packed = packedManifest() const scripts = (packed.scripts as Record | undefined) ?? {} for (const script of LIFECYCLE_SCRIPTS) { expect(scripts, script).not.toHaveProperty(script) } }, 180000) it('declares a safe bundle patch that exists inside the package', () => { const patch = pkg.dsh?.bundle?.patch expect(patch, 'dsh.bundle.patch').toBeDefined() const path = patch! expect(path.startsWith('./'), 'must be a relative path').toBe(true) const relative = path.slice(2) expect(relative).not.toBe('') expect(relative.startsWith('/')).toBe(false) expect(relative.includes('\\')).toBe(false) expect(relative.split('/').every(segment => segment.length > 0 && segment !== '.' && segment !== '..')) .toBe(true) expect(existsSync(resolve(ROOT, relative)), relative).toBe(true) }) it('declares a repository identity the market can normalize to a credential-free HTTPS GitHub owner/repo URL', () => { const repository = pkg.repository expect(repository, 'repository').toBeDefined() const url = new URL(repository!.url!) expect(url.protocol).toBe('https:') expect(url.username).toBe('') expect(url.password).toBe('') expect(url.search).toBe('') expect(url.hash).toBe('') expect(url.hostname.toLowerCase()).toBe('github.com') expect(url.pathname.split('/').filter(Boolean)).toHaveLength(2) }) it('declares an exact stable SemVer version (no range, tag, or prerelease)', () => { expect(pkg.version).toMatch(STABLE_SEMVER) }) })