forked from wangziqi/gongxue-base
26 lines
1.3 KiB
JavaScript
26 lines
1.3 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
const repoRoot = process.cwd();
|
|
const dockerfile = fs.readFileSync(path.join(repoRoot, 'apps', 'api', 'Dockerfile'), 'utf8');
|
|
|
|
assert.match(
|
|
dockerfile,
|
|
/ARG NODE_IMAGE=node:20\.20\.2-alpine3\.23@sha256:fb4cd12c85ee03686f6af5362a0b0d56d50c58a04632e6c0fb8363f609372293/,
|
|
'API image must pin the reviewed multi-architecture Node/Alpine manifest',
|
|
);
|
|
assert.match(dockerfile, /FROM deps AS production-deps/);
|
|
assert.match(dockerfile, /npm prune --omit=dev --workspaces --include-workspace-root/);
|
|
assert.match(dockerfile, /COPY --from=production-deps --chown=node:node \/app\/node_modules \.\/node_modules/);
|
|
assert.match(dockerfile, /COPY --from=build --chown=node:node \/app\/apps\/api\/dist \.\/apps\/api\/dist/);
|
|
assert.match(dockerfile, /\nUSER node\n/);
|
|
assert.match(dockerfile, /CMD \["node", "apps\/api\/dist\/apps\/api\/src\/server\.js"\]/);
|
|
assert.doesNotMatch(
|
|
dockerfile.slice(dockerfile.lastIndexOf(`FROM \${NODE_IMAGE} AS runner`)),
|
|
/package-lock\.json|package\.json|scripts\/import-pocketbase|COPY --from=deps \/app\/node_modules/,
|
|
'runtime stage must contain only production dependencies and compiled API files',
|
|
);
|
|
|
|
console.log('[PASS] API Docker runtime least-privilege contract');
|