Confirm company imports after preview

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
dotta
2026-03-23 13:47:22 -05:00
parent 06f5632d1a
commit c02dc73d3c
2 changed files with 93 additions and 1 deletions

View File

@@ -7,6 +7,7 @@ import {
buildSelectedFilesFromImportSelection,
renderCompanyImportPreview,
renderCompanyImportResult,
resolveCompanyImportApplyConfirmationMode,
resolveCompanyImportApiPath,
} from "../commands/client/company.js";
@@ -58,6 +59,48 @@ describe("resolveCompanyImportApiPath", () => {
});
});
describe("resolveCompanyImportApplyConfirmationMode", () => {
it("skips confirmation when --yes is set", () => {
expect(
resolveCompanyImportApplyConfirmationMode({
yes: true,
interactive: false,
json: false,
}),
).toBe("skip");
});
it("prompts in interactive text mode when --yes is not set", () => {
expect(
resolveCompanyImportApplyConfirmationMode({
yes: false,
interactive: true,
json: false,
}),
).toBe("prompt");
});
it("requires --yes for non-interactive apply", () => {
expect(() =>
resolveCompanyImportApplyConfirmationMode({
yes: false,
interactive: false,
json: false,
})
).toThrow(/non-interactive terminal requires --yes/i);
});
it("requires --yes for json apply", () => {
expect(() =>
resolveCompanyImportApplyConfirmationMode({
yes: false,
interactive: false,
json: true,
})
).toThrow(/with --json requires --yes/i);
});
});
describe("renderCompanyImportPreview", () => {
it("summarizes the preview with counts, selection info, and truncated examples", () => {
const preview: CompanyPortabilityPreviewResult = {

View File

@@ -704,6 +704,27 @@ export function resolveCompanyImportApiPath(input: {
return input.dryRun ? "/api/companies/import/preview" : "/api/companies/import";
}
export function resolveCompanyImportApplyConfirmationMode(input: {
yes?: boolean;
interactive: boolean;
json: boolean;
}): "skip" | "prompt" {
if (input.yes) {
return "skip";
}
if (input.json) {
throw new Error(
"Applying a company import with --json requires --yes. Use --dry-run first to inspect the preview.",
);
}
if (!input.interactive) {
throw new Error(
"Applying a company import from a non-interactive terminal requires --yes. Use --dry-run first to inspect the preview.",
);
}
return "prompt";
}
export function isHttpUrl(input: string): boolean {
return /^https?:\/\//i.test(input.trim());
}
@@ -1095,7 +1116,7 @@ export function registerCompanyCommands(program: Command): void {
.option("--collision <mode>", "Collision strategy: rename | skip | replace", "rename")
.option("--ref <value>", "Git ref to use for GitHub imports (branch, tag, or commit)")
.option("--paperclip-url <url>", "Alias for --api-base on this command")
.option("--yes", "Accept the default import selection without opening the TUI", false)
.option("--yes", "Accept default selection and skip the pre-import confirmation prompt", false)
.option("--dry-run", "Run preview only without applying", false)
.action(async (fromPathOrUrl: string, opts: CompanyImportOptions) => {
try {
@@ -1220,6 +1241,34 @@ export function registerCompanyCommands(program: Command): void {
return;
}
if (!ctx.json) {
printCompanyImportView(
"Import Preview",
renderCompanyImportPreview(preview, {
sourceLabel,
targetLabel: formatTargetLabel(targetPayload, preview),
infoMessages: adapterMessages,
}),
{ interactive: interactiveView },
);
}
const confirmationMode = resolveCompanyImportApplyConfirmationMode({
yes: opts.yes,
interactive: interactiveView,
json: ctx.json,
});
if (confirmationMode === "prompt") {
const confirmed = await p.confirm({
message: "Apply this import? (y/N)",
initialValue: false,
});
if (p.isCancel(confirmed) || !confirmed) {
p.log.warn("Import cancelled.");
return;
}
}
const importApiPath = resolveCompanyImportApiPath({
dryRun: false,
targetMode: targetPayload.mode,