Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions src/apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,28 @@ export async function runApply(): Promise<void> {
const pushArgs = allArgs.join(" ");

if (!env || !SLUG_RE.test(env)) {
console.error("Usage: npm run apply <org> [--force]");
console.error("Usage: npm run apply <org> [--force] [--allow-new-files]");
console.error("");
console.error(" Pull → Merge → Push (safe bidirectional sync)");
console.error("");
console.error(" Pulls latest platform state (preserving local changes),");
console.error(" then pushes the result back to the platform.");
console.error("");
console.error(
" --force Enable deletions: resources you deleted locally",
" --force Enable deletions: resources you deleted locally",
);
console.error(
" will also be deleted from the platform.",
);
console.error(
" --allow-new-files Bypass the orphan-YAML pre-flight gate (push stage).",
);
console.error(
" Use only after confirming every local file without a",
);
console.error(
" state entry is genuinely new — see src/new-file-gate.ts.",
);
console.error(" will also be deleted from the platform.");
process.exit(1);
}

Expand Down
30 changes: 15 additions & 15 deletions src/audit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import { readFile } from "fs/promises";
import { join } from "path";
import { matchesIgnore, RESOURCES_DIR } from "./config.ts";
import { findOrphanResourceIds } from "./new-file-gate.ts";
import {
extractBaseSlug,
fetchAllResources,
Expand Down Expand Up @@ -179,21 +180,20 @@ function checkOrphanYaml(
localIds: string[],
state: StateFile,
): AuditFinding[] {
const stateKeys = new Set(Object.keys(state[type]));
const findings: AuditFinding[] = [];
for (const localId of localIds) {
if (stateKeys.has(localId)) continue;
findings.push({
severity: "warn",
type,
rule: "orphan-yaml",
resourceIds: [localId],
message: `local file ${type}/${localId} has no state entry`,
suggestedAction:
"delete file OR run `npm run pull` to re-key it into state",
});
}
return findings;
// Single source of truth for "what counts as an orphan YAML" — shared with
// the push-time pre-flight gate in src/new-file-gate.ts. Both surfaces map
// the same predicate to different output shapes (here: AuditFinding[];
// there: OrphanFile[] + verbose halt message).
const orphanIds = findOrphanResourceIds(type, localIds, state);
return orphanIds.map((localId) => ({
severity: "warn",
type,
rule: "orphan-yaml",
resourceIds: [localId],
message: `local file ${type}/${localId} has no state entry`,
suggestedAction:
"delete file OR run `npm run pull` to re-key it into state",
}));
}

function checkStateGhosts(
Expand Down
12 changes: 10 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ function parseEnvironment(): Environment {
console.error(
" --type <type> (apply only specific resource type, repeatable)",
);
console.error(
" --allow-new-files (bypass orphan-YAML pre-flight gate)",
);
console.error(" -- <file...> (apply only specific files)");
process.exit(1);
}
Expand Down Expand Up @@ -88,6 +91,7 @@ function parseFlags(): {
dryRun: boolean;
strictValidation: boolean;
overwriteDrift: boolean;
allowNewFiles: boolean;
applyFilter: ApplyFilter;
} {
const args = process.argv.slice(3);
Expand All @@ -97,13 +101,15 @@ function parseFlags(): {
dryRun: boolean;
strictValidation: boolean;
overwriteDrift: boolean;
allowNewFiles: boolean;
applyFilter: ApplyFilter;
} = {
forceDelete: args.includes("--force"),
bootstrapSync: args.includes("--bootstrap"),
dryRun: args.includes("--dry-run"),
strictValidation: args.includes("--strict"),
overwriteDrift: args.includes("--overwrite"),
allowNewFiles: args.includes("--allow-new-files"),
applyFilter: {},
};

Expand All @@ -119,7 +125,8 @@ function parseFlags(): {
arg === "--bootstrap" ||
arg === "--dry-run" ||
arg === "--strict" ||
arg === "--overwrite"
arg === "--overwrite" ||
arg === "--allow-new-files"
)
continue;

Expand Down Expand Up @@ -184,7 +191,7 @@ function parseFlags(): {
console.error(
` Expected a resource type (e.g. assistants, tools), a folder path ` +
`(e.g. assistants/foo.yml or resources/<org>/assistants/foo.yml), or ` +
`a flag (--force, --bootstrap, --type, --id).`,
`a flag (--force, --bootstrap, --type, --id, --allow-new-files).`,
);
process.exit(1);
}
Expand Down Expand Up @@ -257,6 +264,7 @@ export const {
dryRun: DRY_RUN,
strictValidation: STRICT_VALIDATION,
overwriteDrift: OVERWRITE_DRIFT,
allowNewFiles: ALLOW_NEW_FILES,
applyFilter: APPLY_FILTER,
} = parseFlags();

Expand Down
Loading