t: string): Promise<boolean> {
const dryRunOutput = execSync('npm pack --dry-run 2>&1', { cwd: projectRoot, encoding: 'utf-8' });
const hasMissingFiles = dryRunOutput.includes('ENOENT') || dryRunOutput.includes('not found');
const hasStaleArtifacts = dryRunOutput.includes('node_modules') || dryRunOutput.includes('.env');
if (hasMissingFiles || hasStaleArtifacts) {
console.error('[Boundary] Package contains excluded or missing files.');
return false;
}
const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf-8'));
if (!pkg.files?.length && !pkg.main && !pkg.bin) {
console.warn('[Boundary] No explicit file inclusion rules detected. Defaulting to npm heuristics.');
}
return true;
}
**Why this approach:** `npm pack --dry-run` executes the exact same file resolution logic used during publication. Parsing the output catches missing binaries, accidental `node_modules` inclusion, and stale build directories. Explicit `files` arrays prevent heuristic drift across npm versions.
### Step 2: CI/README Parity Enforcement
Documentation promises must match automated verification. If the README instructs users to run `npm run test`, the CI pipeline must execute that exact command. Parity checks parse both surfaces and compare command signatures.
```typescript
import { readFileSync } from 'fs';
import { join } from 'path';
export function checkCiReadmeParity(projectRoot: string): boolean {
const readme = readFileSync(join(projectRoot, 'README.md'), 'utf-8');
const ciWorkflow = readFileSync(join(projectRoot, '.github', 'workflows', 'ci.yml'), 'utf-8');
const readmeCommands = readme.match(/npm run (\w+)/g) || [];
const ciCommands = ciWorkflow.match(/run:\s*npm run (\w+)/g) || [];
const missingInCi = readmeCommands.filter(cmd => !ciCommands.includes(cmd));
if (missingInCi.length > 0) {
console.error(`[Parity] README commands not validated in CI: ${missingInCi.join(', ')}`);
return false;
}
return true;
}
Why this approach: Regex-based command extraction is deterministic and fast. It catches documentation drift before users encounter broken instructions. The check runs in CI to prevent merges that desync docs from automation.
Step 3: Secret Hygiene & Environment Validation
Public repositories must never teach contributors to create production secrets. The pipeline verifies that environment templates exist, real secrets are ignored, and documentation avoids hardcoded values.
import { readFileSync, readdirSync } from 'fs';
import { join } from 'path';
export function validateEnvHygiene(projectRoot: string): boolean {
const gitignore = readFileSync(join(projectRoot, '.gitignore'), 'utf-8');
const requiredIgnores = ['.env', '.env.local', '*.secret', '*.key'];
const missingIgnores = requiredIgnores.filter(rule => !gitignore.includes(rule));
if (missingIgnores.length > 0) {
console.error(`[Hygiene] Missing gitignore rules: ${missingIgnores.join(', ')}`);
return false;
}
const hasExample = readdirSync(projectRoot).some(f => f.startsWith('.env.example'));
if (!hasExample) {
console.error('[Hygiene] No .env.example template found.');
return false;
}
return true;
}
Why this approach: Environment validation is binary: either the template exists and secrets are excluded, or the repository is a liability. The check runs early in the pipeline to fail fast. For npm publishing, OIDC/trusted publishing replaces long-lived tokens, eliminating credential storage entirely.
Distribution surfaces fragment across npm, GitHub Marketplace, MCP directories, and product sites. Metadata drift causes install failures and trust erosion. The validator cross-references critical fields.
import { readFileSync } from 'fs';
import { join } from 'path';
export function alignRegistryMetadata(projectRoot: string): boolean {
const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf-8'));
const requiredFields = ['name', 'description', 'license', 'repository', 'bugs', 'homepage'];
const missing = requiredFields.filter(f => !pkg[f]);
if (missing.length > 0) {
console.error(`[Metadata] Missing required fields: ${missing.join(', ')}`);
return false;
}
if (pkg.repository?.url && !pkg.repository.url.includes('github.com')) {
console.warn('[Metadata] Non-GitHub repository URL may break marketplace discovery.');
}
return true;
}
Why this approach: Registry crawlers and client SDKs parse these fields deterministically. Missing or inconsistent metadata breaks automated discovery, especially for MCP servers where the 2026 roadmap emphasizes crawler-based directory population. The validator enforces schema compliance before publication.
Step 5: Payment & Agent Execution Bounds
When demos involve financial transactions or autonomous agents, failure containment is non-negotiable. The pipeline verifies sandbox defaults, spend caps, human approval gates, and audit logging.
import { readFileSync } from 'fs';
import { join } from 'path';
export function verifyAgentSafetyBounds(projectRoot: string): boolean {
const configPath = join(projectRoot, 'agent-config.json');
if (!configPath) return true;
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
const safetyChecks = {
sandboxMode: config.environment === 'testnet' || config.environment === 'sandbox',
spendCap: typeof config.maxSpend === 'number' && config.maxSpend > 0,
humanApproval: config.requireApproval === true,
auditLog: config.enableReceipts === true
};
const failed = Object.entries(safetyChecks).filter(([, v]) => !v);
if (failed.length > 0) {
console.error(`[Safety] Missing bounds: ${failed.map(([k]) => k).join(', ')}`);
return false;
}
return true;
}
Why this approach: x402 and similar payment-required API frameworks shift the threat model. A bug in a payment demo can drain testnet funds or trigger real-world charges. Explicit bounds, sandbox defaults, and audit trails transform experimental code into production-safe artifacts.
Pitfall Guide
1. Local-Only Build Validation
Explanation: Developers assume npm run build success guarantees publication readiness. Local environments contain development dependencies, untracked files, and permissive filesystem access that disappear after packaging.
Fix: Run npm pack --dry-run in CI. Verify that the tarball contains only production artifacts. Strip devDependencies before packaging.
Explanation: package.json, MCP server manifests, GitHub Marketplace listings, and product sites often diverge. Crawlers and client SDKs fail when names, versions, or install instructions mismatch.
Fix: Centralize metadata in a single source of truth. Use a pre-publish script that syncs package.json fields to registry manifests. Validate alignment before every release.
3. Over-Provisioned Action Permissions
Explanation: GitHub Actions default to broad permissions (contents: write, packages: write). External consumers inherit these permissions, creating supply chain risk.
Fix: Declare explicit permissions per job. Use permissions: read by default. Validate SARIF outputs and fixture repos to ensure the Action behaves identically in third-party workflows.
4. Unbounded Agent-Commerce Demos
Explanation: Payment or agent demos without sandboxing, spend caps, or human approval gates treat financial operations like standard API calls. Bugs become irreversible fund drains.
Fix: Enforce testnet/sandbox defaults. Implement explicit spend limits. Require human confirmation for mainnet transitions. Log all transactions with cryptographic receipts.
5. Silent Lockfile Drift
Explanation: Applications and CLIs that rely on deterministic installs fail when package-lock.json or pnpm-lock.yaml is missing or ignored. CI and local environments diverge.
Fix: Commit lockfiles for all consumer-facing projects. Add a CI step that verifies npm ci succeeds without network fallback. Reject PRs that modify dependencies without updating the lockfile.
6. Hardcoded Credential Examples
Explanation: Documentation or .env.example files that include placeholder tokens, API keys, or connection strings encourage copy-paste security failures.
Fix: Use explicit placeholder syntax (YOUR_API_KEY_HERE). Never include real or realistic-looking credentials. Validate examples with a regex scanner that rejects hex strings or base64 patterns.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Shared npm Library | Strict boundary validation + metadata alignment | Consumers install directly; missing files or drift cause immediate failures | Low (CI overhead < 15s) |
| GitHub Action | Permission narrowing + fixture validation + SARIF check | Marketplace consumers inherit permissions; broad defaults create supply chain risk | Medium (requires test repo setup) |
| MCP Server | Registry metadata sync + smoke-test endpoint + auth boundary docs | 2026 roadmap emphasizes crawler discovery; clients fail on mismatched manifests | Low (automated sync script) |
| Payment/Agent Demo | Sandbox enforcement + spend caps + human approval + audit logs | Financial operations require irreversible failure containment | High (requires testnet infrastructure) |
Configuration Template
# .github/workflows/release-gate.yml
name: Release Pre-Flight Validation
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- name: Run Pre-Flight Validator
run: npx ts-node scripts/release-gate.ts
env:
NODE_ENV: validation
- name: Upload Validation Report
if: always()
uses: actions/upload-artifact@v4
with:
name: release-validation-report
path: .release-gate/report.json
// release-gate.config.json
{
"boundary": {
"dryRun": true,
"excludePatterns": ["node_modules", ".env*", "*.test.ts", "docs/"]
},
"parity": {
"readmePath": "README.md",
"ciPath": ".github/workflows/ci.yml",
"commandRegex": "npm run (\\w+)"
},
"hygiene": {
"requiredIgnores": [".env", ".env.local", "*.secret", "*.key"],
"templatePattern": ".env.example"
},
"metadata": {
"requiredFields": ["name", "description", "license", "repository", "bugs", "homepage"],
"syncTargets": ["package.json", "server.json"]
},
"safety": {
"enforceSandbox": true,
"maxSpendLimit": 100,
"requireApproval": true,
"enableAuditLog": true
}
}
Quick Start Guide
- Initialize the validator: Create a
scripts/release-gate.ts file in your repository and paste the TypeScript validation logic from the Core Solution section.
- Add configuration: Place
release-gate.config.json in the project root. Adjust excludePatterns and maxSpendLimit to match your project requirements.
- Wire into CI: Copy the GitHub Actions workflow template into
.github/workflows/release-gate.yml. Ensure npm ci runs before the validation step.
- Run locally: Execute
npx ts-node scripts/release-gate.ts to verify the pipeline catches expected issues. Review the generated .release-gate/report.json for evidence paths and command logs.
- Enforce on PRs: Merge the workflow. The pipeline will block merges that fail boundary, parity, hygiene, metadata, or safety checks until all validations pass.