mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 02:27:44 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,985 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const ts = require('typescript');
|
||||
|
||||
const ALLOWED_CRITERIA = new Set([
|
||||
'authority',
|
||||
'dependency_isolation',
|
||||
'deployable',
|
||||
'replaceable_adapter',
|
||||
'shared_leaf',
|
||||
]);
|
||||
const THIN_PACKAGE_CRITERIA = new Set([
|
||||
'authority',
|
||||
'deployable',
|
||||
'shared_leaf',
|
||||
]);
|
||||
const ALLOWED_ROOT_SOURCE_ROLES = new Set([
|
||||
'binary_entry',
|
||||
'public_export',
|
||||
'shared_infrastructure',
|
||||
]);
|
||||
const ALLOWED_SHALLOW_LAYOUT_KINDS = new Set(['public_entrypoints']);
|
||||
const ALLOWED_DENSE_DIRECTORY_KINDS = new Set([
|
||||
'ordered_ledger',
|
||||
'ownership_review',
|
||||
]);
|
||||
|
||||
function readJson(filePath) {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
}
|
||||
|
||||
function sortedUniqueStrings(value) {
|
||||
if (
|
||||
!Array.isArray(value) ||
|
||||
value.some((item) => typeof item !== 'string' || item.length === 0)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
const sorted = [...new Set(value)].sort();
|
||||
return sorted.length === value.length ? sorted : undefined;
|
||||
}
|
||||
|
||||
function sameStrings(left, right) {
|
||||
return (
|
||||
left.length === right.length &&
|
||||
left.every((value, index) => value === right[index])
|
||||
);
|
||||
}
|
||||
|
||||
function sourceMetrics(directory) {
|
||||
const files = [];
|
||||
const directDirectories = [];
|
||||
const sourceRoot = path.join(directory, 'src');
|
||||
const walk = (current) => {
|
||||
if (!fs.existsSync(current)) return;
|
||||
const entries = fs.readdirSync(current, { withFileTypes: true });
|
||||
const directFiles = entries.filter(
|
||||
(entry) =>
|
||||
entry.isFile() &&
|
||||
(entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) &&
|
||||
!entry.name.endsWith('.d.ts'),
|
||||
);
|
||||
if (current !== sourceRoot && directFiles.length > 0) {
|
||||
directDirectories.push(
|
||||
Object.freeze({
|
||||
relativePath: path
|
||||
.relative(sourceRoot, current)
|
||||
.split(path.sep)
|
||||
.join('/'),
|
||||
files: directFiles.length,
|
||||
}),
|
||||
);
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const target = path.join(current, entry.name);
|
||||
if (entry.isDirectory()) walk(target);
|
||||
else if (
|
||||
entry.isFile() &&
|
||||
(entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) &&
|
||||
!entry.name.endsWith('.d.ts')
|
||||
) {
|
||||
files.push(target);
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(sourceRoot);
|
||||
files.sort();
|
||||
directDirectories.sort((left, right) =>
|
||||
left.relativePath.localeCompare(right.relativePath),
|
||||
);
|
||||
const rootFileNames = files
|
||||
.filter((file) => path.dirname(file) === sourceRoot)
|
||||
.map((file) => path.basename(file));
|
||||
const linesByFile = new Map(
|
||||
files.map((file) => [
|
||||
file,
|
||||
fs.readFileSync(file, 'utf8').split(/\r?\n/u).length,
|
||||
]),
|
||||
);
|
||||
return Object.freeze({
|
||||
files: files.length,
|
||||
rootFiles: rootFileNames.length,
|
||||
rootFileNames: Object.freeze(rootFileNames),
|
||||
rootLines: files
|
||||
.filter((file) => path.dirname(file) === sourceRoot)
|
||||
.reduce((total, file) => total + (linesByFile.get(file) ?? 0), 0),
|
||||
nestedFiles: files.filter((file) => path.dirname(file) !== sourceRoot)
|
||||
.length,
|
||||
directDirectories: Object.freeze(directDirectories),
|
||||
lines: files.reduce(
|
||||
(total, file) => total + (linesByFile.get(file) ?? 0),
|
||||
0,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
function internalSourceLayoutPolicy(value) {
|
||||
if (
|
||||
!value ||
|
||||
typeof value !== 'object' ||
|
||||
Array.isArray(value) ||
|
||||
!sameStrings(Object.keys(value), [
|
||||
'directSourceFileReviewThreshold',
|
||||
'reviewedDenseDirectories',
|
||||
]) ||
|
||||
!Number.isSafeInteger(value.directSourceFileReviewThreshold) ||
|
||||
value.directSourceFileReviewThreshold < 2 ||
|
||||
!Array.isArray(value.reviewedDenseDirectories)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
const reviewed = [];
|
||||
for (const entry of value.reviewedDenseDirectories) {
|
||||
if (
|
||||
!entry ||
|
||||
typeof entry !== 'object' ||
|
||||
Array.isArray(entry) ||
|
||||
!sameStrings(Object.keys(entry), [
|
||||
'kind',
|
||||
'maxDirectSourceFiles',
|
||||
'path',
|
||||
'rationale',
|
||||
]) ||
|
||||
!ALLOWED_DENSE_DIRECTORY_KINDS.has(entry.kind) ||
|
||||
!Number.isSafeInteger(entry.maxDirectSourceFiles) ||
|
||||
entry.maxDirectSourceFiles < value.directSourceFileReviewThreshold ||
|
||||
typeof entry.path !== 'string' ||
|
||||
!/^packages\/ql3-[a-z0-9-]+\/src\/[A-Za-z0-9][A-Za-z0-9/-]*$/u.test(
|
||||
entry.path,
|
||||
) ||
|
||||
entry.path.includes('//') ||
|
||||
entry.path.split('/').includes('..') ||
|
||||
typeof entry.rationale !== 'string' ||
|
||||
entry.rationale.length < 16
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
reviewed.push(
|
||||
Object.freeze({
|
||||
kind: entry.kind,
|
||||
maxDirectSourceFiles: entry.maxDirectSourceFiles,
|
||||
path: entry.path,
|
||||
rationale: entry.rationale,
|
||||
}),
|
||||
);
|
||||
}
|
||||
const paths = reviewed.map((entry) => entry.path);
|
||||
if (
|
||||
new Set(paths).size !== paths.length ||
|
||||
!sameStrings(paths, [...paths].sort())
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return Object.freeze({
|
||||
directSourceFileReviewThreshold: value.directSourceFileReviewThreshold,
|
||||
reviewedDenseDirectories: Object.freeze(reviewed),
|
||||
});
|
||||
}
|
||||
|
||||
function packageDirectories(root) {
|
||||
const packagesRoot = path.join(root, 'packages');
|
||||
return fs
|
||||
.readdirSync(packagesRoot, { withFileTypes: true })
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.isDirectory() &&
|
||||
entry.name.startsWith('ql3-') &&
|
||||
fs.existsSync(path.join(packagesRoot, entry.name, 'package.json')),
|
||||
)
|
||||
.map((entry) => `packages/${entry.name}`)
|
||||
.sort();
|
||||
}
|
||||
|
||||
function productionDependencies(manifest) {
|
||||
return Object.freeze({
|
||||
...(manifest.dependencies ?? {}),
|
||||
...(manifest.optionalDependencies ?? {}),
|
||||
...(manifest.peerDependencies ?? {}),
|
||||
});
|
||||
}
|
||||
|
||||
function finding(code, packagePath, message) {
|
||||
return Object.freeze({ code, packagePath, message });
|
||||
}
|
||||
|
||||
function stringTargets(value, targets = []) {
|
||||
if (typeof value === 'string') {
|
||||
targets.push(value.replace(/^\.\//u, ''));
|
||||
} else if (value && typeof value === 'object') {
|
||||
for (const nested of Object.values(value)) stringTargets(nested, targets);
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
function sourceOutputTarget(fileName) {
|
||||
return `dist/${fileName.replace(/\.tsx?$/u, '.js')}`;
|
||||
}
|
||||
|
||||
function publicExportIsReexportOnly(filePath) {
|
||||
try {
|
||||
const sourceFile = ts.createSourceFile(
|
||||
filePath,
|
||||
fs.readFileSync(filePath, 'utf8'),
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
filePath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
|
||||
);
|
||||
return (
|
||||
sourceFile.parseDiagnostics.length === 0 &&
|
||||
sourceFile.statements.length > 0 &&
|
||||
sourceFile.statements.every((statement) =>
|
||||
ts.isExportDeclaration(statement),
|
||||
)
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function rootSourceRoles(value) {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const fileNames = Object.keys(value);
|
||||
if (
|
||||
fileNames.some(
|
||||
(fileName) =>
|
||||
!/^[A-Za-z][A-Za-z0-9]*\.(?:ts|tsx)$/u.test(fileName) ||
|
||||
!ALLOWED_ROOT_SOURCE_ROLES.has(value[fileName]),
|
||||
) ||
|
||||
!sameStrings(fileNames, [...fileNames].sort())
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return Object.freeze(
|
||||
Object.fromEntries(
|
||||
fileNames.map((fileName) => [fileName, value[fileName]]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function shallowSourceLayout(value) {
|
||||
const keys =
|
||||
value && typeof value === 'object' && !Array.isArray(value)
|
||||
? Object.keys(value)
|
||||
: [];
|
||||
const baseShape = sameStrings(keys, ['kind', 'rationale']);
|
||||
const artifactShape = sameStrings(keys, [
|
||||
'artifactEntrypoints',
|
||||
'closureDelta',
|
||||
'kind',
|
||||
'rationale',
|
||||
]);
|
||||
if (
|
||||
!value ||
|
||||
typeof value !== 'object' ||
|
||||
Array.isArray(value) ||
|
||||
(!baseShape && !artifactShape) ||
|
||||
!ALLOWED_SHALLOW_LAYOUT_KINDS.has(value.kind) ||
|
||||
typeof value.rationale !== 'string' ||
|
||||
value.rationale.length < 16
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
if (baseShape) {
|
||||
return Object.freeze({ kind: value.kind, rationale: value.rationale });
|
||||
}
|
||||
const artifactEntrypoints = value.artifactEntrypoints;
|
||||
const artifactNames =
|
||||
artifactEntrypoints &&
|
||||
typeof artifactEntrypoints === 'object' &&
|
||||
!Array.isArray(artifactEntrypoints)
|
||||
? Object.keys(artifactEntrypoints)
|
||||
: [];
|
||||
const entrypointSpecifiers = artifactNames.map(
|
||||
(artifactName) => artifactEntrypoints[artifactName],
|
||||
);
|
||||
const closureDelta = value.closureDelta;
|
||||
const excludedDependencies = sortedUniqueStrings(
|
||||
closureDelta?.excludedDependencies,
|
||||
);
|
||||
if (
|
||||
artifactNames.length === 0 ||
|
||||
!sameStrings(artifactNames, [...artifactNames].sort()) ||
|
||||
artifactNames.some(
|
||||
(artifactName) => !/^[a-z][a-z0-9-]*$/u.test(artifactName),
|
||||
) ||
|
||||
entrypointSpecifiers.some(
|
||||
(specifier) =>
|
||||
typeof specifier !== 'string' ||
|
||||
!/^\.\/[a-z][a-z0-9-]*$/u.test(specifier),
|
||||
) ||
|
||||
new Set(entrypointSpecifiers).size !== entrypointSpecifiers.length ||
|
||||
!closureDelta ||
|
||||
typeof closureDelta !== 'object' ||
|
||||
Array.isArray(closureDelta) ||
|
||||
!sameStrings(Object.keys(closureDelta), [
|
||||
'comparedWith',
|
||||
'excludedDependencies',
|
||||
]) ||
|
||||
typeof closureDelta.comparedWith !== 'string' ||
|
||||
!closureDelta.comparedWith.startsWith('@qinglong/') ||
|
||||
!excludedDependencies ||
|
||||
excludedDependencies.length === 0
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return Object.freeze({
|
||||
artifactEntrypoints: Object.freeze({ ...artifactEntrypoints }),
|
||||
closureDelta: Object.freeze({
|
||||
comparedWith: closureDelta.comparedWith,
|
||||
excludedDependencies,
|
||||
}),
|
||||
kind: value.kind,
|
||||
rationale: value.rationale,
|
||||
});
|
||||
}
|
||||
|
||||
function runtimeExportTarget(value) {
|
||||
const targets = [
|
||||
...new Set(stringTargets(value).filter((target) => target.endsWith('.js'))),
|
||||
];
|
||||
return targets.length === 1 ? targets[0] : undefined;
|
||||
}
|
||||
|
||||
function productionDependencyClosure(manifest, manifestsByName) {
|
||||
const closure = new Set();
|
||||
const pending = Object.keys(productionDependencies(manifest));
|
||||
while (pending.length > 0) {
|
||||
const dependency = pending.pop();
|
||||
if (!dependency || closure.has(dependency)) continue;
|
||||
closure.add(dependency);
|
||||
const dependencyManifest = manifestsByName.get(dependency);
|
||||
if (dependencyManifest) {
|
||||
pending.push(...Object.keys(productionDependencies(dependencyManifest)));
|
||||
}
|
||||
}
|
||||
return closure;
|
||||
}
|
||||
|
||||
function auditPackageBoundaries(options = {}) {
|
||||
const root = path.resolve(options.root ?? path.join(__dirname, '..'));
|
||||
const ledgerPath = path.resolve(
|
||||
options.ledgerPath ?? path.join(root, 'docs/ql3-package-boundaries.json'),
|
||||
);
|
||||
const findings = [];
|
||||
let ledger;
|
||||
try {
|
||||
ledger = readJson(ledgerPath);
|
||||
} catch (error) {
|
||||
return Object.freeze({
|
||||
schemaVersion: 6,
|
||||
compatible: false,
|
||||
workspacePackageCount: 0,
|
||||
workspacePackageHardCap: 0,
|
||||
singleSourcePackages: Object.freeze([]),
|
||||
shallowSourcePackages: Object.freeze([]),
|
||||
denseSourceDirectories: Object.freeze([]),
|
||||
packages: Object.freeze([]),
|
||||
findings: Object.freeze([
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_LEDGER_INVALID',
|
||||
'docs/ql3-package-boundaries.json',
|
||||
error instanceof Error ? error.name : 'Error',
|
||||
),
|
||||
]),
|
||||
});
|
||||
}
|
||||
|
||||
const directories = packageDirectories(root);
|
||||
const declared = Array.isArray(ledger.packages) ? ledger.packages : [];
|
||||
if (ledger.schemaVersion !== 6) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_SCHEMA_INVALID',
|
||||
'docs/ql3-package-boundaries.json',
|
||||
'schemaVersion must be 6',
|
||||
),
|
||||
);
|
||||
}
|
||||
const sourceLayoutPolicy = internalSourceLayoutPolicy(
|
||||
ledger.internalSourceLayout,
|
||||
);
|
||||
if (!sourceLayoutPolicy) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_INTERNAL_LAYOUT_POLICY_INVALID',
|
||||
'docs/ql3-package-boundaries.json',
|
||||
'internalSourceLayout must declare one threshold and a sorted exact dense-directory review list',
|
||||
),
|
||||
);
|
||||
}
|
||||
if (
|
||||
!Number.isSafeInteger(ledger.workspacePackageHardCap) ||
|
||||
ledger.workspacePackageHardCap < 1
|
||||
) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_HARD_CAP_INVALID',
|
||||
'docs/ql3-package-boundaries.json',
|
||||
'workspacePackageHardCap must be a positive integer',
|
||||
),
|
||||
);
|
||||
} else if (directories.length > ledger.workspacePackageHardCap) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_HARD_CAP_EXCEEDED',
|
||||
'packages',
|
||||
`${directories.length} packages exceed cap ${ledger.workspacePackageHardCap}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const entriesByPath = new Map();
|
||||
for (const entry of declared) {
|
||||
const packagePath =
|
||||
entry && typeof entry.path === 'string' ? entry.path : '<invalid>';
|
||||
if (entriesByPath.has(packagePath)) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_DUPLICATE_PATH',
|
||||
packagePath,
|
||||
'package path is declared more than once',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
entriesByPath.set(packagePath, entry);
|
||||
}
|
||||
}
|
||||
for (const directory of directories) {
|
||||
if (!entriesByPath.has(directory)) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_UNDECLARED_PACKAGE',
|
||||
directory,
|
||||
'workspace package has no reviewed boundary decision',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const packagePath of entriesByPath.keys()) {
|
||||
if (!directories.includes(packagePath)) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_STALE_PACKAGE',
|
||||
packagePath,
|
||||
'boundary decision has no workspace package',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const manifests = new Map();
|
||||
for (const directory of directories) {
|
||||
manifests.set(
|
||||
directory,
|
||||
readJson(path.join(root, directory, 'package.json')),
|
||||
);
|
||||
}
|
||||
const actualNames = new Set(
|
||||
[...manifests.values()].map((manifest) => manifest.name),
|
||||
);
|
||||
const manifestsByName = new Map(
|
||||
[...manifests.values()].map((manifest) => [manifest.name, manifest]),
|
||||
);
|
||||
const reports = [];
|
||||
const directSourceDirectories = new Map();
|
||||
const denseSourceDirectories = [];
|
||||
const reviewedDenseDirectories = new Map(
|
||||
(sourceLayoutPolicy?.reviewedDenseDirectories ?? []).map((entry) => [
|
||||
entry.path,
|
||||
entry,
|
||||
]),
|
||||
);
|
||||
for (const directory of directories) {
|
||||
const entry = entriesByPath.get(directory);
|
||||
const manifest = manifests.get(directory);
|
||||
const metrics = sourceMetrics(path.join(root, directory));
|
||||
for (const directDirectory of metrics.directDirectories) {
|
||||
const directoryPath = `${directory}/src/${directDirectory.relativePath}`;
|
||||
directSourceDirectories.set(directoryPath, directDirectory.files);
|
||||
if (
|
||||
sourceLayoutPolicy &&
|
||||
directDirectory.files >=
|
||||
sourceLayoutPolicy.directSourceFileReviewThreshold
|
||||
) {
|
||||
const review = reviewedDenseDirectories.get(directoryPath);
|
||||
denseSourceDirectories.push(
|
||||
Object.freeze({
|
||||
path: directoryPath,
|
||||
directSourceFiles: directDirectory.files,
|
||||
reviewKind: review?.kind ?? null,
|
||||
maxDirectSourceFiles: review?.maxDirectSourceFiles ?? null,
|
||||
}),
|
||||
);
|
||||
if (!review) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_DENSE_DIRECTORY_UNREVIEWED',
|
||||
directory,
|
||||
`${directoryPath} has ${directDirectory.files} direct source files at review threshold ${sourceLayoutPolicy.directSourceFileReviewThreshold}`,
|
||||
),
|
||||
);
|
||||
} else if (directDirectory.files > review.maxDirectSourceFiles) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_DENSE_DIRECTORY_HARD_CAP_EXCEEDED',
|
||||
directory,
|
||||
`${directoryPath} has ${directDirectory.files} direct source files above reviewed cap ${review.maxDirectSourceFiles}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
const dependencies = productionDependencies(manifest);
|
||||
const consumers = [...manifests.entries()]
|
||||
.filter(([, candidate]) =>
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
productionDependencies(candidate),
|
||||
manifest.name,
|
||||
),
|
||||
)
|
||||
.map(([, candidate]) => candidate.name)
|
||||
.sort();
|
||||
reports.push(
|
||||
Object.freeze({
|
||||
path: directory,
|
||||
name: manifest.name,
|
||||
sourceFiles: metrics.files,
|
||||
rootSourceFiles: metrics.rootFiles,
|
||||
rootSourceLines: metrics.rootLines,
|
||||
nestedSourceFiles: metrics.nestedFiles,
|
||||
rootSourceFileHardCap: entry?.rootSourceFileHardCap ?? null,
|
||||
rootSourceLineHardCap: entry?.rootSourceLineHardCap ?? null,
|
||||
rootSourceFileRoles: Object.freeze({
|
||||
...(entry?.rootSourceFileRoles ?? {}),
|
||||
}),
|
||||
shallowSourceLayoutKind: entry?.shallowSourceLayout?.kind ?? null,
|
||||
sourceLines: metrics.lines,
|
||||
consumers: Object.freeze(consumers),
|
||||
}),
|
||||
);
|
||||
for (const dependency of Object.keys(dependencies)) {
|
||||
if (dependency.startsWith('@qinglong/') && !actualNames.has(dependency)) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_UNKNOWN_WORKSPACE_DEPENDENCY',
|
||||
directory,
|
||||
dependency,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!entry) continue;
|
||||
if (
|
||||
!Number.isSafeInteger(entry.rootSourceFileHardCap) ||
|
||||
entry.rootSourceFileHardCap < 0
|
||||
) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_ROOT_HARD_CAP_INVALID',
|
||||
directory,
|
||||
'rootSourceFileHardCap must be a non-negative integer',
|
||||
),
|
||||
);
|
||||
} else if (metrics.rootFiles > entry.rootSourceFileHardCap) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_ROOT_HARD_CAP_EXCEEDED',
|
||||
directory,
|
||||
`${metrics.rootFiles} root source files exceed cap ${entry.rootSourceFileHardCap}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (
|
||||
!Number.isSafeInteger(entry.rootSourceLineHardCap) ||
|
||||
entry.rootSourceLineHardCap < 0
|
||||
) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_ROOT_LINE_HARD_CAP_INVALID',
|
||||
directory,
|
||||
'rootSourceLineHardCap must be a non-negative integer',
|
||||
),
|
||||
);
|
||||
} else if (metrics.rootLines > entry.rootSourceLineHardCap) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_ROOT_LINE_HARD_CAP_EXCEEDED',
|
||||
directory,
|
||||
`${metrics.rootLines} root source lines exceed cap ${entry.rootSourceLineHardCap}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
const roles = rootSourceRoles(entry.rootSourceFileRoles);
|
||||
if (!roles) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_ROOT_ROLES_INVALID',
|
||||
directory,
|
||||
'rootSourceFileRoles must be a sorted exact file-to-role object',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
const declaredRootFiles = Object.keys(roles);
|
||||
if (!sameStrings(declaredRootFiles, metrics.rootFileNames)) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_ROOT_ROLE_DRIFT',
|
||||
directory,
|
||||
`expected ${JSON.stringify(
|
||||
declaredRootFiles,
|
||||
)}, found ${JSON.stringify(metrics.rootFileNames)}`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
const publicTargets = new Set([
|
||||
...stringTargets(manifest.main),
|
||||
...stringTargets(manifest.exports),
|
||||
]);
|
||||
const binaryTargets = new Set(stringTargets(manifest.bin));
|
||||
for (const [fileName, role] of Object.entries(roles)) {
|
||||
const outputTarget = sourceOutputTarget(fileName);
|
||||
const roleProven =
|
||||
(role === 'public_export' && publicTargets.has(outputTarget)) ||
|
||||
(role === 'binary_entry' && binaryTargets.has(outputTarget)) ||
|
||||
(role === 'shared_infrastructure' &&
|
||||
!publicTargets.has(outputTarget) &&
|
||||
!binaryTargets.has(outputTarget) &&
|
||||
metrics.nestedFiles > 0);
|
||||
if (!roleProven) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_ROOT_ROLE_UNPROVEN',
|
||||
directory,
|
||||
`${fileName} is not proven as ${role}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (
|
||||
role === 'public_export' &&
|
||||
entry.shallowSourceLayout === undefined &&
|
||||
!publicExportIsReexportOnly(
|
||||
path.join(root, directory, 'src', fileName),
|
||||
)
|
||||
) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_ROOT_PUBLIC_EXPORT_IMPLEMENTATION',
|
||||
directory,
|
||||
`${fileName} must contain only re-export declarations`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (
|
||||
Object.values(roles).includes('shared_infrastructure') &&
|
||||
(typeof entry.rootSourceLayoutRationale !== 'string' ||
|
||||
entry.rootSourceLayoutRationale.length < 16)
|
||||
) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_ROOT_INFRASTRUCTURE_UNPROVEN',
|
||||
directory,
|
||||
'shared infrastructure roots need a concrete layout rationale',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (entry.name !== manifest.name) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_NAME_DRIFT',
|
||||
directory,
|
||||
`expected ${entry.name}, found ${manifest.name}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
const criteria = sortedUniqueStrings(entry.criteria);
|
||||
if (
|
||||
!criteria ||
|
||||
criteria.length === 0 ||
|
||||
criteria.some((criterion) => !ALLOWED_CRITERIA.has(criterion))
|
||||
) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_CRITERIA_INVALID',
|
||||
directory,
|
||||
'criteria must be sorted, unique and reviewed',
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const declaredConsumers = sortedUniqueStrings(entry.consumers);
|
||||
if (!declaredConsumers || !sameStrings(declaredConsumers, consumers)) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_CONSUMER_DRIFT',
|
||||
directory,
|
||||
`expected ${JSON.stringify(
|
||||
declaredConsumers ?? entry.consumers,
|
||||
)}, found ${JSON.stringify(consumers)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (
|
||||
!Array.isArray(entry.profiles) ||
|
||||
entry.profiles.length === 0 ||
|
||||
typeof entry.rationale !== 'string' ||
|
||||
entry.rationale.length < 16
|
||||
) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_EVIDENCE_INCOMPLETE',
|
||||
directory,
|
||||
'profiles and a concrete rationale are required',
|
||||
),
|
||||
);
|
||||
}
|
||||
if (metrics.nestedFiles === 0) {
|
||||
const shallowLayout = shallowSourceLayout(entry.shallowSourceLayout);
|
||||
if (!shallowLayout) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_SHALLOW_LAYOUT_MISSING',
|
||||
directory,
|
||||
'an all-root package needs one reviewed shallow layout exception',
|
||||
),
|
||||
);
|
||||
} else if (roles) {
|
||||
const roleValues = Object.values(roles);
|
||||
const publicEntrypointsProven =
|
||||
shallowLayout.kind === 'public_entrypoints' &&
|
||||
criteria.includes('deployable') &&
|
||||
roleValues.every((role) =>
|
||||
['binary_entry', 'public_export'].includes(role),
|
||||
) &&
|
||||
(Boolean(manifest.bin) ||
|
||||
(Array.isArray(entry.artifacts) && entry.artifacts.length > 0));
|
||||
if (!publicEntrypointsProven) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_SHALLOW_LAYOUT_UNPROVEN',
|
||||
directory,
|
||||
`${shallowLayout.kind} lacks matching package boundary evidence`,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (!manifest.bin) {
|
||||
const artifactNames = sortedUniqueStrings(entry.artifacts);
|
||||
const artifactEntrypoints = shallowLayout.artifactEntrypoints;
|
||||
const exportSpecifiers =
|
||||
manifest.exports &&
|
||||
typeof manifest.exports === 'object' &&
|
||||
!Array.isArray(manifest.exports)
|
||||
? Object.keys(manifest.exports).sort()
|
||||
: [];
|
||||
const expectedSpecifiers = artifactEntrypoints
|
||||
? ['.', ...Object.values(artifactEntrypoints)].sort()
|
||||
: [];
|
||||
const runtimeTargets = exportSpecifiers.map((specifier) =>
|
||||
runtimeExportTarget(manifest.exports[specifier]),
|
||||
);
|
||||
const expectedRuntimeTargets = metrics.rootFileNames
|
||||
.map(sourceOutputTarget)
|
||||
.sort();
|
||||
const artifactEntrypointsProven =
|
||||
artifactNames !== undefined &&
|
||||
artifactNames.length >= 2 &&
|
||||
artifactEntrypoints !== undefined &&
|
||||
sameStrings(artifactNames, Object.keys(artifactEntrypoints)) &&
|
||||
sameStrings(exportSpecifiers, expectedSpecifiers) &&
|
||||
runtimeTargets.every(Boolean) &&
|
||||
sameStrings([...runtimeTargets].sort(), expectedRuntimeTargets);
|
||||
if (!artifactEntrypointsProven) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_SHALLOW_ARTIFACT_ENTRYPOINTS_UNPROVEN',
|
||||
directory,
|
||||
'artifact names, export specifiers and root source outputs must map one-to-one',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const closureDelta = shallowLayout.closureDelta;
|
||||
const comparedManifest = closureDelta
|
||||
? manifestsByName.get(closureDelta.comparedWith)
|
||||
: undefined;
|
||||
const ownClosure = productionDependencyClosure(
|
||||
manifest,
|
||||
manifestsByName,
|
||||
);
|
||||
const comparedClosure = comparedManifest
|
||||
? productionDependencyClosure(comparedManifest, manifestsByName)
|
||||
: new Set();
|
||||
const dependencyFirewallProven =
|
||||
closureDelta !== undefined &&
|
||||
consumers.includes(closureDelta.comparedWith) &&
|
||||
comparedManifest !== undefined &&
|
||||
closureDelta.excludedDependencies.every(
|
||||
(dependency) =>
|
||||
!ownClosure.has(dependency) && comparedClosure.has(dependency),
|
||||
);
|
||||
if (!dependencyFirewallProven) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_SHALLOW_DEPENDENCY_FIREWALL_UNPROVEN',
|
||||
directory,
|
||||
'a direct consumer must prove a non-empty production dependency closure delta',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (entry.shallowSourceLayout !== undefined) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_SHALLOW_LAYOUT_STALE',
|
||||
directory,
|
||||
'a package with nested sources must remove its shallow exception',
|
||||
),
|
||||
);
|
||||
}
|
||||
if (criteria.includes('shared_leaf') && consumers.length < 2) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_SHARED_LEAF_UNPROVEN',
|
||||
directory,
|
||||
'shared_leaf requires at least two production consumers',
|
||||
),
|
||||
);
|
||||
}
|
||||
if (
|
||||
criteria.includes('deployable') &&
|
||||
!manifest.bin &&
|
||||
(!Array.isArray(entry.artifacts) || entry.artifacts.length === 0)
|
||||
) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_DEPLOYABLE_UNPROVEN',
|
||||
directory,
|
||||
'deployable requires a bin or reviewed artifact names',
|
||||
),
|
||||
);
|
||||
}
|
||||
if (
|
||||
criteria.includes('authority') &&
|
||||
(!Array.isArray(entry.authorities) || entry.authorities.length === 0)
|
||||
) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_AUTHORITY_UNPROVEN',
|
||||
directory,
|
||||
'authority requires at least one explicit authority',
|
||||
),
|
||||
);
|
||||
}
|
||||
if (criteria.includes('dependency_isolation')) {
|
||||
const isolated = sortedUniqueStrings(entry.isolatedDependencies);
|
||||
if (
|
||||
!isolated ||
|
||||
isolated.length === 0 ||
|
||||
isolated.some(
|
||||
(dependency) =>
|
||||
dependency.startsWith('@qinglong/') ||
|
||||
!Object.prototype.hasOwnProperty.call(dependencies, dependency),
|
||||
)
|
||||
) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_DEPENDENCY_ISOLATION_UNPROVEN',
|
||||
directory,
|
||||
'isolatedDependencies must name installed external production dependencies',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (
|
||||
criteria.includes('replaceable_adapter') &&
|
||||
(typeof entry.adapterContract !== 'string' ||
|
||||
entry.adapterContract.length < 16)
|
||||
) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_ADAPTER_UNPROVEN',
|
||||
directory,
|
||||
'replaceable_adapter requires a concrete adapterContract',
|
||||
),
|
||||
);
|
||||
}
|
||||
if (
|
||||
metrics.files <= 2 &&
|
||||
!criteria.some((criterion) => THIN_PACKAGE_CRITERIA.has(criterion))
|
||||
) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_THIN_PACKAGE_UNJUSTIFIED',
|
||||
directory,
|
||||
'one-or-two-source-file package needs deployable, authority or shared_leaf evidence',
|
||||
),
|
||||
);
|
||||
}
|
||||
if (metrics.files === 0) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_BOUNDARY_SOURCE_MISSING',
|
||||
directory,
|
||||
'workspace package has no TypeScript source',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceLayoutPolicy) {
|
||||
for (const review of sourceLayoutPolicy.reviewedDenseDirectories) {
|
||||
const actualFiles = directSourceDirectories.get(review.path);
|
||||
if (
|
||||
actualFiles === undefined ||
|
||||
actualFiles < sourceLayoutPolicy.directSourceFileReviewThreshold
|
||||
) {
|
||||
findings.push(
|
||||
finding(
|
||||
'PACKAGE_SOURCE_DENSE_DIRECTORY_REVIEW_STALE',
|
||||
review.path.split('/src/')[0],
|
||||
`${review.path} no longer reaches review threshold ${sourceLayoutPolicy.directSourceFileReviewThreshold}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
denseSourceDirectories.sort((left, right) =>
|
||||
left.path.localeCompare(right.path),
|
||||
);
|
||||
|
||||
return Object.freeze({
|
||||
schemaVersion: 6,
|
||||
compatible: findings.length === 0,
|
||||
workspacePackageCount: directories.length,
|
||||
workspacePackageHardCap: ledger.workspacePackageHardCap,
|
||||
singleSourcePackages: Object.freeze(
|
||||
reports
|
||||
.filter((report) => report.sourceFiles === 1)
|
||||
.map((report) => report.name),
|
||||
),
|
||||
shallowSourcePackages: Object.freeze(
|
||||
reports
|
||||
.filter((report) => report.nestedSourceFiles === 0)
|
||||
.map((report) => report.name),
|
||||
),
|
||||
denseSourceDirectories: Object.freeze(denseSourceDirectories),
|
||||
packages: Object.freeze(reports),
|
||||
findings: Object.freeze(findings),
|
||||
});
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const report = auditPackageBoundaries();
|
||||
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
||||
if (!report.compatible) process.exitCode = 1;
|
||||
}
|
||||
|
||||
module.exports = Object.freeze({ auditPackageBoundaries });
|
||||
Reference in New Issue
Block a user