153 lines
5.7 KiB
JavaScript
153 lines
5.7 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
const packageRoot = path.resolve(import.meta.dirname, '..', '..');
|
|
const repoRoot = path.resolve(packageRoot, '..', '..');
|
|
const EXPECTED_KIND = 'DeploymentProfileArtifact';
|
|
const EXPECTED_API_VERSION = 'reporting-governance/v1alpha1';
|
|
|
|
function readJsonFile(filePath) {
|
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
}
|
|
|
|
function assertNonEmptyString(value, label) {
|
|
if (typeof value !== 'string' || value.trim() === '') {
|
|
throw new Error(`${label} must be a non-empty string`);
|
|
}
|
|
return value.trim();
|
|
}
|
|
|
|
function assertObjectRecord(value, label) {
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
throw new Error(`${label} must be an object record`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function assertPathWithinRealRoot(candidatePath, label, { root, allowMissingLeaf = false }) {
|
|
const realRoot = fs.realpathSync(root);
|
|
let realCandidate;
|
|
|
|
if (allowMissingLeaf && !fs.existsSync(candidatePath)) {
|
|
let cursor = path.dirname(candidatePath);
|
|
while (cursor !== root && !fs.existsSync(cursor)) {
|
|
cursor = path.dirname(cursor);
|
|
}
|
|
|
|
if (!fs.existsSync(cursor)) {
|
|
realCandidate = realRoot;
|
|
} else {
|
|
realCandidate = path.resolve(fs.realpathSync(cursor), path.relative(cursor, candidatePath));
|
|
}
|
|
} else {
|
|
realCandidate = fs.realpathSync(candidatePath);
|
|
}
|
|
|
|
const relativeToRealRoot = path.relative(realRoot, realCandidate);
|
|
if (
|
|
relativeToRealRoot === '..'
|
|
|| relativeToRealRoot.startsWith(`..${path.sep}`)
|
|
|| path.isAbsolute(relativeToRealRoot)
|
|
) {
|
|
throw new Error(`${label} must stay within repo root: symlink resolution escapes realpath boundary`);
|
|
}
|
|
}
|
|
|
|
function assertRelativePathWithinRoot(relativePath, label, { root, allowMissingLeaf = false }) {
|
|
const normalizedPath = assertNonEmptyString(relativePath, label);
|
|
if (path.isAbsolute(normalizedPath)) {
|
|
throw new Error(`${label} must stay within repo root: absolute paths are not allowed`);
|
|
}
|
|
|
|
const resolvedPath = path.resolve(root, normalizedPath);
|
|
const relativeToRoot = path.relative(root, resolvedPath);
|
|
if (
|
|
relativeToRoot === '..'
|
|
|| relativeToRoot.startsWith(`..${path.sep}`)
|
|
|| path.isAbsolute(relativeToRoot)
|
|
) {
|
|
throw new Error(`${label} must stay within repo root: path escapes root boundary`);
|
|
}
|
|
|
|
assertPathWithinRealRoot(resolvedPath, label, { root, allowMissingLeaf });
|
|
return normalizedPath;
|
|
}
|
|
|
|
export function validateDeploymentProfileArtifact(artifact, { repoRootOverride } = {}) {
|
|
if (!artifact || typeof artifact !== 'object' || Array.isArray(artifact)) {
|
|
throw new Error('deployment profile artifact must be an object');
|
|
}
|
|
if (artifact.kind !== EXPECTED_KIND) {
|
|
throw new Error(`deployment profile artifact kind must be ${EXPECTED_KIND}`);
|
|
}
|
|
if (artifact.apiVersion !== EXPECTED_API_VERSION) {
|
|
throw new Error(`deployment profile artifact apiVersion must be ${EXPECTED_API_VERSION}`);
|
|
}
|
|
|
|
const bindings = artifact?.spec?.bindings;
|
|
if (!bindings || typeof bindings !== 'object' || Array.isArray(bindings)) {
|
|
throw new Error('deployment profile artifact bindings are required');
|
|
}
|
|
|
|
const root = path.resolve(repoRootOverride ?? repoRoot);
|
|
const scripts = assertObjectRecord(bindings.scripts, 'deployment profile artifact spec.bindings.scripts');
|
|
const artifactRoots = assertObjectRecord(bindings.artifact_roots, 'deployment profile artifact spec.bindings.artifact_roots');
|
|
|
|
assertRelativePathWithinRoot(bindings.entrypoint, 'deployment profile artifact spec.bindings.entrypoint', { root });
|
|
assertNonEmptyString(artifact?.spec?.package?.pluginVersion, 'deployment profile artifact spec.package.pluginVersion');
|
|
|
|
for (const [key, relativePath] of Object.entries(scripts)) {
|
|
assertRelativePathWithinRoot(relativePath, `deployment profile artifact spec.bindings.scripts.${key}`, { root });
|
|
}
|
|
for (const [key, relativePath] of Object.entries(artifactRoots)) {
|
|
assertRelativePathWithinRoot(relativePath, `deployment profile artifact spec.bindings.artifact_roots.${key}`, { root, allowMissingLeaf: true });
|
|
}
|
|
|
|
return artifact;
|
|
}
|
|
|
|
export function resolvePackageArtifactPath(...segments) {
|
|
return path.resolve(packageRoot, ...segments);
|
|
}
|
|
|
|
export function loadDeploymentProfileArtifact({ artifactPath, profileId } = {}) {
|
|
const resolvedPath = path.resolve(
|
|
artifactPath
|
|
?? resolvePackageArtifactPath('profiles', `${profileId ?? 'strict-manager-mode'}.profile.json`)
|
|
);
|
|
const artifact = validateDeploymentProfileArtifact(readJsonFile(resolvedPath));
|
|
return {
|
|
artifactPath: resolvedPath,
|
|
artifact,
|
|
};
|
|
}
|
|
|
|
export function createDeploymentBindingContract({ artifact, repoRootOverride } = {}) {
|
|
const validatedArtifact = validateDeploymentProfileArtifact(artifact, { repoRootOverride });
|
|
const root = path.resolve(repoRootOverride ?? repoRoot);
|
|
const scripts = Object.fromEntries(
|
|
Object.entries(validatedArtifact.spec.bindings.scripts).map(([key, relativePath]) => [key, path.resolve(root, relativePath)])
|
|
);
|
|
const artifactRoots = Object.fromEntries(
|
|
Object.entries(validatedArtifact.spec.bindings.artifact_roots).map(([key, relativePath]) => [key, path.resolve(root, relativePath)])
|
|
);
|
|
|
|
return {
|
|
runtime: validatedArtifact.spec.bindings.runtime ?? validatedArtifact.metadata?.runtime ?? 'unknown-runtime',
|
|
entrypoint: path.resolve(root, validatedArtifact.spec.bindings.entrypoint),
|
|
pluginVersion: validatedArtifact.spec.package.pluginVersion,
|
|
compatibilityMode: validatedArtifact.metadata?.compatibility_mode ?? 'strict_envelope',
|
|
scripts,
|
|
artifactRoots,
|
|
};
|
|
}
|
|
|
|
export const __testables = {
|
|
EXPECTED_KIND,
|
|
EXPECTED_API_VERSION,
|
|
packageRoot,
|
|
repoRoot,
|
|
readJsonFile,
|
|
validateDeploymentProfileArtifact,
|
|
};
|