69 lines
2.7 KiB
TypeScript
69 lines
2.7 KiB
TypeScript
/**
|
|
* Public API surface guard (v0.12.0+):
|
|
* - The CLIENT-REACHABLE source graph stays free of Node.js types: the
|
|
* shipped `Context` (TabComponentProps / the cordis augmentation) must
|
|
* not leak `node:*` imports or the `Buffer` global into browser-only
|
|
* consumer builds. context-types.ts is the shared file — a regression
|
|
* here breaks external consumers with `skipLibCheck: false`.
|
|
* - The version/features constants exist and are consistent (the detailed
|
|
* package.json lockstep assertion lives in service.spec.ts).
|
|
*/
|
|
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
import { join, resolve } from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { SIDEBAR_FEATURES, SIDEBAR_SERVICE_VERSION } from '../src/client/service.ts'
|
|
|
|
const ROOT = resolve(import.meta.dirname, '..')
|
|
|
|
/** The client-reachable source files (the declaration graph consumers pull). */
|
|
function clientGraphFiles(): string[] {
|
|
const files: string[] = []
|
|
const walk = (dir: string): void => {
|
|
for (const name of readdirSync(dir)) {
|
|
const path = join(dir, name)
|
|
if (statSync(path).isDirectory()) {
|
|
if (name !== 'chunks') walk(path)
|
|
} else if (/\.(ts|tsx)$/.test(name)) {
|
|
files.push(path)
|
|
}
|
|
}
|
|
}
|
|
walk(join(ROOT, 'src', 'client'))
|
|
for (const extra of ['src/context-types.ts', 'src/html-route.ts', 'src/prefs-shared.ts']) {
|
|
files.push(join(ROOT, extra))
|
|
}
|
|
return files
|
|
}
|
|
|
|
describe('public API surface (v0.12.0)', () => {
|
|
it('the client-reachable graph imports no node:* modules', () => {
|
|
const offenders = clientGraphFiles().filter(path => {
|
|
const text = readFileSync(path, 'utf8')
|
|
return /from\s+['"]node:/.test(text)
|
|
})
|
|
expect(offenders).toEqual([])
|
|
})
|
|
|
|
it('the client-reachable graph never references the Buffer global', () => {
|
|
const offenders = clientGraphFiles().filter(path => {
|
|
const text = readFileSync(path, 'utf8')
|
|
return text.split('\n').some(line =>
|
|
/\bBuffer\b/.test(line)
|
|
&& !/^\s*\*/.test(line) // doc comments
|
|
&& !/^\s*\/\//.test(line) // line comments
|
|
&& !/\/\*/.test(line), // block comment openers
|
|
)
|
|
})
|
|
expect(offenders).toEqual([])
|
|
})
|
|
|
|
it('advertises the capability list and version (values come from service.ts)', () => {
|
|
// Full releases are bare x.y.z; pre-releases (beta) carry -<tag>.<n>.
|
|
expect(SIDEBAR_SERVICE_VERSION).toMatch(/^\d+\.\d+\.\d+(-[a-z0-9.]+)?$/)
|
|
expect(SIDEBAR_FEATURES.length).toBeGreaterThanOrEqual(8)
|
|
for (const feature of ['badge', 'tabLifecycle', 'updateTab', 'openFile', 'targetedOpen', 'stateSubscription', 'tabMeta', 'pluginSettings']) {
|
|
expect(SIDEBAR_FEATURES).toContain(feature)
|
|
}
|
|
})
|
|
})
|