PSA/services/workflow-worker/scripts/validate-runtime-imports.mjs
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

257 lines
6.7 KiB
JavaScript

#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const SERVICE_ROOT = path.resolve(__dirname, '..');
const REPO_ROOT = path.resolve(SERVICE_ROOT, '../..');
const DIST_ROOT = path.resolve(
process.env.WORKFLOW_WORKER_VALIDATE_DIST_ROOT || path.join(SERVICE_ROOT, 'dist'),
);
const ENTRY_CANDIDATES = [
path.join(DIST_ROOT, 'services/workflow-worker/src/index.js'),
path.join(DIST_ROOT, 'src/index.js'),
];
const EXTRA_ENTRY_CANDIDATES = [
// The worker imports @alga-psa/workflows/runtime/worker as a package export.
// That package dist is outside services/workflow-worker/dist, so scan it
// explicitly to catch bundled alias/server/UI leaks before Docker runtime.
path.join(REPO_ROOT, 'ee/packages/workflows/dist/runtime/worker.mjs'),
path.join(REPO_ROOT, 'ee/packages/workflows/dist/runtime/core.mjs'),
];
const FORBIDDEN_ROOT_IMPORTS = new Set([
'@alga-psa/auth',
'@alga-psa/documents',
'@alga-psa/integrations',
'@alga-psa/billing',
'@alga-psa/ui',
]);
function readFile(filePath) {
return fs.readFileSync(filePath, 'utf8');
}
function findEntry() {
for (const candidate of ENTRY_CANDIDATES) {
if (fs.existsSync(candidate)) {
return candidate;
}
}
return null;
}
function extractSpecifiers(source) {
const specs = [];
const staticImportRe = /(?:import|export)\s+(?:[^'"]*?\sfrom\s*)?['"]([^'"]+)['"]/g;
const dynamicImportRe = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
for (const re of [staticImportRe, dynamicImportRe]) {
let match;
while ((match = re.exec(source)) !== null) {
specs.push(match[1]);
}
}
return specs;
}
function resolveRelativeImport(fromFile, specifier) {
const fromDir = path.dirname(fromFile);
const rawPath = path.resolve(fromDir, specifier);
if (fs.existsSync(rawPath) && fs.statSync(rawPath).isFile()) {
return rawPath;
}
const candidates = [
`${rawPath}.js`,
`${rawPath}.mjs`,
`${rawPath}.cjs`,
`${rawPath}.json`,
path.join(rawPath, 'index.js'),
path.join(rawPath, 'index.mjs'),
path.join(rawPath, 'index.cjs'),
];
return candidates.find((candidate) => fs.existsSync(candidate)) ?? null;
}
function isRelative(specifier) {
return specifier.startsWith('./') || specifier.startsWith('../');
}
function isPathWithin(basePath, candidatePath) {
const rel = path.relative(basePath, candidatePath);
return rel && !rel.startsWith('..') && !path.isAbsolute(rel);
}
function addViolation(violations, filePath, specifier, reason) {
violations.push({
filePath: path.relative(SERVICE_ROOT, filePath),
specifier,
reason,
});
}
function isWorkerRuntimeEntrypoint(filePath) {
return filePath.endsWith(path.join('ee', 'packages', 'workflows', 'src', 'runtime', 'worker.js'));
}
function validate() {
if (!fs.existsSync(DIST_ROOT)) {
throw new Error(`dist not found at ${DIST_ROOT}`);
}
const entry = findEntry();
if (!entry) {
throw new Error('Unable to locate workflow-worker dist entrypoint');
}
const queue = [
entry,
...EXTRA_ENTRY_CANDIDATES.filter((candidate) => fs.existsSync(candidate)),
];
const visited = new Set();
const violations = [];
while (queue.length > 0) {
const current = queue.shift();
if (!current || visited.has(current)) continue;
visited.add(current);
const source = readFile(current);
const specifiers = extractSpecifiers(source);
for (const specifier of specifiers) {
if (FORBIDDEN_ROOT_IMPORTS.has(specifier)) {
addViolation(
violations,
current,
specifier,
'forbidden package root import in worker runtime graph'
);
}
if (specifier.startsWith('@alga-psa/ui/')) {
addViolation(
violations,
current,
specifier,
'ui package import is not allowed in worker runtime graph'
);
}
if (specifier.startsWith('@shared/')) {
addViolation(
violations,
current,
specifier,
'unresolved @shared alias is not allowed in worker runtime graph'
);
}
if (specifier.includes('/runtime/bootstrap')) {
addViolation(
violations,
current,
specifier,
'bootstrap/app-only runtime dependency leaked into worker runtime graph'
);
}
if (
(specifier.includes('workflowInferenceService') || specifier.includes('registerAiActions'))
&& !isWorkerRuntimeEntrypoint(current)
) {
addViolation(
violations,
current,
specifier,
'worker runtime graph may only reach AI runtime wiring through the dedicated runtime/worker entrypoint'
);
}
if (
specifier.includes('/components/') ||
specifier.endsWith('/components')
) {
addViolation(
violations,
current,
specifier,
'component import leaked into worker runtime graph'
);
}
if (!isRelative(specifier)) {
continue;
}
const ext = path.extname(specifier);
if (!ext) {
addViolation(
violations,
current,
specifier,
'relative import is missing explicit extension'
);
} else if (ext === '.jsx') {
addViolation(
violations,
current,
specifier,
'relative .jsx import is not allowed for Node worker runtime'
);
}
const resolved = resolveRelativeImport(current, specifier);
if (!resolved) {
addViolation(
violations,
current,
specifier,
'relative import does not resolve in dist output'
);
continue;
}
if (resolved.endsWith('.jsx')) {
addViolation(
violations,
current,
specifier,
'resolved path is .jsx, which is not executable by Node runtime here'
);
}
if (isPathWithin(DIST_ROOT, resolved)) {
queue.push(resolved);
}
}
}
if (violations.length > 0) {
console.error('\nWorkflow worker runtime import validation failed.\n');
for (const violation of violations) {
console.error(`- ${violation.filePath} -> "${violation.specifier}"`);
console.error(` ${violation.reason}`);
}
process.exit(1);
}
console.log('Workflow worker runtime import validation passed.');
}
try {
validate();
} catch (error) {
console.error('\nWorkflow worker runtime import validation failed.\n');
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}