diff --git a/fern/pages/changelogs/cli/2024-12-09.mdx b/fern/pages/changelogs/cli/2024-12-09.mdx new file mode 100644 index 00000000000..9097d84cbe8 --- /dev/null +++ b/fern/pages/changelogs/cli/2024-12-09.mdx @@ -0,0 +1,4 @@ +## 0.45.4-rc0 +**`(fix):`** The CLI prompts the user to confirm output directory overwrites on fern generate. + + diff --git a/fern/pages/changelogs/fastapi/2024-12-09.mdx b/fern/pages/changelogs/fastapi/2024-12-09.mdx new file mode 100644 index 00000000000..173d1fd7340 --- /dev/null +++ b/fern/pages/changelogs/fastapi/2024-12-09.mdx @@ -0,0 +1,15 @@ +## 1.6.0 +**`(fix):`** The FastAPI generator now supports a new mode for pydantic model generation, +to use pydantic v1 on v2 versions. + +```yml generators.yml +generators: + server: + - name: fernapi/fern-fastapi-server + version: 1.6.0 + config: + pydantic: + version: "v1_on_v2" +```` + + diff --git a/fern/pages/changelogs/python-sdk/2024-12-08.mdx b/fern/pages/changelogs/python-sdk/2024-12-08.mdx new file mode 100644 index 00000000000..8143b05e9fa --- /dev/null +++ b/fern/pages/changelogs/python-sdk/2024-12-08.mdx @@ -0,0 +1,4 @@ +## 4.3.9 +**`(fix):`** Fix indentation in generated README.md sections to ensure proper formatting and readability. + + diff --git a/packages/cli/cli/package.json b/packages/cli/cli/package.json index 5e467419eab..d262e217fa3 100644 --- a/packages/cli/cli/package.json +++ b/packages/cli/cli/package.json @@ -79,6 +79,7 @@ "@fern-fern/fiddle-sdk": "0.0.584", "@fern-fern/generators-sdk": "0.114.0-5745f9e74", "@fern-typescript/fetcher": "workspace:*", + "@inquirer/prompts": "^7.1.0", "@types/axios": "^0.14.0", "@types/boxen": "^3.0.1", "@types/get-port": "^4.2.0", @@ -116,5 +117,6 @@ "vitest": "^2.1.4", "yaml": "^2.4.5", "yargs": "^17.4.1" - } -} \ No newline at end of file + }, + "dependencies": {} +} diff --git a/packages/cli/cli/src/__test__/checkOutputDirectory.test.ts b/packages/cli/cli/src/__test__/checkOutputDirectory.test.ts new file mode 100644 index 00000000000..c9c4d2cda85 --- /dev/null +++ b/packages/cli/cli/src/__test__/checkOutputDirectory.test.ts @@ -0,0 +1,134 @@ +import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/fs-utils"; +import { mkdir, writeFile } from "fs/promises"; +import tmp from "tmp-promise"; +import { checkOutputDirectory } from "../commands/generate/checkOutputDirectory"; +import { getOutputDirectories } from "../persistence/output-directories/getOutputDirectories"; +import { storeOutputDirectories } from "../persistence/output-directories/storeOutputDirectories"; +import { describe, it, expect, beforeEach, vi, Mock, afterEach } from "vitest"; +import { CliContext } from "../cli-context/CliContext"; +import { isCI } from "../utils/isCI"; + +vi.mock("../utils/isCI", () => ({ + isCI: vi.fn().mockReturnValue(false) +})); + +describe("checkOutputDirectory", () => { + let mockCliContext: { + confirmPrompt: Mock & ((message: string, defaultValue?: boolean) => Promise); + }; + + beforeEach(() => { + mockCliContext = { + confirmPrompt: vi.fn().mockImplementation(async () => true) + }; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("doesn't prompt if directory doesn't exist", async () => { + const tmpDir = await tmp.dir(); + const nonExistentPath = join(AbsoluteFilePath.of(tmpDir.path), RelativeFilePath.of("non-existent")); + + const result = await checkOutputDirectory(nonExistentPath, mockCliContext as unknown as CliContext, false); + + expect(result).toEqual({ + shouldProceed: true + }); + expect(mockCliContext.confirmPrompt).not.toHaveBeenCalled(); + }); + + it("doesn't prompt if directory is empty", async () => { + const tmpDir = await tmp.dir(); + const emptyDir = join(AbsoluteFilePath.of(tmpDir.path), RelativeFilePath.of("empty")); + await mkdir(emptyDir); + + const result = await checkOutputDirectory(emptyDir, mockCliContext as unknown as CliContext, false); + + expect(result).toEqual({ + shouldProceed: true + }); + expect(mockCliContext.confirmPrompt).not.toHaveBeenCalled(); + }); + + it("prompts for confirmation if directory has files and not in safelist", async () => { + const tmpDir = await tmp.dir(); + const dirWithFiles = join(AbsoluteFilePath.of(tmpDir.path), RelativeFilePath.of("with-files")); + await mkdir(dirWithFiles); + await writeFile(join(dirWithFiles, RelativeFilePath.of("test.txt")), "test"); + + mockCliContext.confirmPrompt.mockResolvedValueOnce(true); + + const result = await checkOutputDirectory(dirWithFiles, mockCliContext as unknown as CliContext, false); + + expect(result).toEqual({ + shouldProceed: true + }); + expect(mockCliContext.confirmPrompt).toHaveBeenCalledTimes(1); + }); + + it("doesn't prompt if directory is in safelist", async () => { + const tmpDir = await tmp.dir(); + const safelistedDir = join(AbsoluteFilePath.of(tmpDir.path), RelativeFilePath.of("safelisted")); + await mkdir(safelistedDir); + await writeFile(join(safelistedDir, RelativeFilePath.of("test.txt")), "test"); + + // Add to safelist + await storeOutputDirectories([safelistedDir]); + + const result = await checkOutputDirectory(safelistedDir, mockCliContext as unknown as CliContext, false); + + expect(result).toEqual({ + shouldProceed: true + }); + expect(mockCliContext.confirmPrompt).not.toHaveBeenCalled(); + }); + + it("saves directory to safelist when requested", async () => { + const tmpDir = await tmp.dir(); + const dirToSafelist = join(AbsoluteFilePath.of(tmpDir.path), RelativeFilePath.of("to-safelist")); + await mkdir(dirToSafelist); + await writeFile(join(dirToSafelist, RelativeFilePath.of("test.txt")), "test"); + + mockCliContext.confirmPrompt.mockResolvedValueOnce(true); + + const result = await checkOutputDirectory(dirToSafelist, mockCliContext as unknown as CliContext, false); + + expect(result).toEqual({ + shouldProceed: true + }); + + // Verify directory was added to safelist + const savedDirectories = await getOutputDirectories(); + expect(savedDirectories).toContain(dirToSafelist); + }); + + it("doesn't proceed if user declines overwrite", async () => { + const tmpDir = await tmp.dir(); + const dirWithFiles = join(AbsoluteFilePath.of(tmpDir.path), RelativeFilePath.of("with-files")); + await mkdir(dirWithFiles); + await writeFile(join(dirWithFiles, RelativeFilePath.of("test.txt")), "test"); + + mockCliContext.confirmPrompt.mockResolvedValueOnce(false); // overwrite prompt + + const result = await checkOutputDirectory(dirWithFiles, mockCliContext as unknown as CliContext, false); + + expect(result).toEqual({ + shouldProceed: false + }); + expect(mockCliContext.confirmPrompt).toHaveBeenCalledTimes(1); + }); + + it("doesn't prompt if force is true", async () => { + const tmpDir = await tmp.dir(); + const dirWithFiles = join(AbsoluteFilePath.of(tmpDir.path), RelativeFilePath.of("with-files")); + await mkdir(dirWithFiles); + await writeFile(join(dirWithFiles, RelativeFilePath.of("test.txt")), "test"); + + const result = await checkOutputDirectory(dirWithFiles, mockCliContext as unknown as CliContext, true); + + expect(result).toEqual({ shouldProceed: true }); + expect(mockCliContext.confirmPrompt).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/cli/cli/src/__test__/checkOutputDirectoryCI.test.ts b/packages/cli/cli/src/__test__/checkOutputDirectoryCI.test.ts new file mode 100644 index 00000000000..d1152d7420d --- /dev/null +++ b/packages/cli/cli/src/__test__/checkOutputDirectoryCI.test.ts @@ -0,0 +1,39 @@ +import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/fs-utils"; +import { mkdir, writeFile } from "fs/promises"; +import tmp from "tmp-promise"; +import { checkOutputDirectory } from "../commands/generate/checkOutputDirectory"; +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import { CliContext } from "../cli-context/CliContext"; +import { isCI } from "../utils/isCI"; + +vi.mock("../utils/isCI", () => ({ + isCI: vi.fn().mockReturnValue(true) +})); + +describe("checkOutputDirectory in CI", () => { + let mockCliContext: Partial; + + beforeEach(() => { + mockCliContext = { + confirmPrompt: vi.fn() + }; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("doesn't prompt in CI environment even with files present", async () => { + const tmpDir = await tmp.dir(); + const dirWithFiles = join(AbsoluteFilePath.of(tmpDir.path), RelativeFilePath.of("with-files")); + await mkdir(dirWithFiles); + await writeFile(join(dirWithFiles, RelativeFilePath.of("test.txt")), "test"); + + const result = await checkOutputDirectory(dirWithFiles, mockCliContext as CliContext, false); + + expect(result).toEqual({ + shouldProceed: true + }); + expect(mockCliContext.confirmPrompt).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/cli/cli/src/cli-context/CliContext.ts b/packages/cli/cli/src/cli-context/CliContext.ts index f74c2de5015..88775645122 100644 --- a/packages/cli/cli/src/cli-context/CliContext.ts +++ b/packages/cli/cli/src/cli-context/CliContext.ts @@ -14,6 +14,7 @@ import { TtyAwareLogger } from "./TtyAwareLogger"; import { getFernUpgradeMessage } from "./upgrade-utils/getFernUpgradeMessage"; import { FernGeneratorUpgradeInfo, getProjectGeneratorUpgrades } from "./upgrade-utils/getGeneratorVersions"; import { getLatestVersionOfCli } from "./upgrade-utils/getLatestVersionOfCli"; +import { confirm } from "@inquirer/prompts"; const WORKSPACE_NAME_COLORS = ["#2E86AB", "#A23B72", "#F18F01", "#C73E1D", "#CCE2A3"]; @@ -306,6 +307,19 @@ export class CliContext { } return this._isUpgradeAvailable; } + + /** + * Prompts the user for confirmation with a yes/no question + * @param message The message to display to the user + * @param defaultValue Optional default value (defaults to false) + * @returns Promise representing the user's choice + */ + public async confirmPrompt(message: string, defaultValue = false): Promise { + return await confirm({ + message, + default: defaultValue + }); + } } function wrapWorkspaceNameForPrefix(workspaceName: string): string { diff --git a/packages/cli/cli/src/cli.ts b/packages/cli/cli/src/cli.ts index 159a84aa26f..7e2329b4371 100644 --- a/packages/cli/cli/src/cli.ts +++ b/packages/cli/cli/src/cli.ts @@ -381,6 +381,11 @@ function addGenerateCommand(cli: Argv, cliContext: CliContext) boolean: true, default: false, description: "Prevent auto-deletion of the Docker containers." + }) + .option("force", { + boolean: true, + default: false, + description: "Ignore prompts to confirm generation, defaults to false" }), async (argv) => { if (argv.api != null && argv.docs != null) { @@ -402,7 +407,8 @@ function addGenerateCommand(cli: Argv, cliContext: CliContext) keepDocker: argv.keepDocker, useLocalDocker: argv.local, preview: argv.preview, - mode: argv.mode + mode: argv.mode, + force: argv.force }); } if (argv.docs != null) { @@ -439,7 +445,8 @@ function addGenerateCommand(cli: Argv, cliContext: CliContext) keepDocker: argv.keepDocker, useLocalDocker: argv.local, preview: argv.preview, - mode: argv.mode + mode: argv.mode, + force: argv.force }); } ); diff --git a/packages/cli/cli/src/commands/generate/checkOutputDirectory.ts b/packages/cli/cli/src/commands/generate/checkOutputDirectory.ts new file mode 100644 index 00000000000..7c2f2918047 --- /dev/null +++ b/packages/cli/cli/src/commands/generate/checkOutputDirectory.ts @@ -0,0 +1,69 @@ +import { AbsoluteFilePath, doesPathExist } from "@fern-api/fs-utils"; +import { readdir } from "fs/promises"; +import { CliContext } from "../../cli-context/CliContext"; +import { getOutputDirectories } from "../../persistence/output-directories/getOutputDirectories"; +import { storeOutputDirectories } from "../../persistence/output-directories/storeOutputDirectories"; +import { isCI } from "../../utils/isCI"; + +export interface CheckOutputDirectoryResult { + shouldProceed: boolean; +} + +/** + * Checks if an output directory is safe to write to and handles user confirmations + * @param outputPath The path to check + * @param cliContext The CLI context for prompting + * @returns Object containing whether to proceed and if directory was saved + */ +export async function checkOutputDirectory( + outputPath: AbsoluteFilePath | undefined, + cliContext: CliContext, + force: boolean +): Promise { + if (!outputPath || isCI() || force) { + return { + shouldProceed: true + }; + } + + // First check if this is already a saved output directory + const savedDirectories = await getOutputDirectories(); + if (savedDirectories?.includes(outputPath)) { + return { + shouldProceed: true + }; + } + + // Check if directory exists and has files + const doesExist = await doesPathExist(outputPath); + if (!doesExist) { + return { + shouldProceed: true + }; + } + + const files = await readdir(outputPath); + if (files.length === 0) { + return { + shouldProceed: true + }; + } + + // Prompt user for confirmation since directory has files + const shouldOverwrite = await cliContext.confirmPrompt( + `Directory ${outputPath} contains existing files that may be overwritten. Continue?`, + false + ); + + if (!shouldOverwrite) { + return { + shouldProceed: false + }; + } + + await storeOutputDirectories([...(savedDirectories ?? []), outputPath]); + + return { + shouldProceed: true + }; +} diff --git a/packages/cli/cli/src/commands/generate/generateAPIWorkspaces.ts b/packages/cli/cli/src/commands/generate/generateAPIWorkspaces.ts index a7d949fca42..3ec5ad37a0f 100644 --- a/packages/cli/cli/src/commands/generate/generateAPIWorkspaces.ts +++ b/packages/cli/cli/src/commands/generate/generateAPIWorkspaces.ts @@ -6,6 +6,8 @@ import { Project } from "@fern-api/project-loader"; import { CliContext } from "../../cli-context/CliContext"; import { PREVIEW_DIRECTORY } from "../../constants"; import { generateWorkspace } from "./generateAPIWorkspace"; +import { checkOutputDirectory } from "./checkOutputDirectory"; +import { isCI } from "../../utils/isCI"; export const GenerationMode = { PullRequest: "pull-request" @@ -22,7 +24,8 @@ export async function generateAPIWorkspaces({ keepDocker, useLocalDocker, preview, - mode + mode, + force }: { project: Project; cliContext: CliContext; @@ -33,6 +36,7 @@ export async function generateAPIWorkspaces({ keepDocker: boolean; preview: boolean; mode: GenerationMode | undefined; + force: boolean; }): Promise { let token: FernToken | undefined = undefined; @@ -52,6 +56,19 @@ export async function generateAPIWorkspaces({ token = currentToken; } + for (const workspace of project.apiWorkspaces) { + for (const generator of workspace.generatorsConfiguration?.groups.flatMap((group) => group.generators) ?? []) { + const { shouldProceed } = await checkOutputDirectory( + generator.absolutePathToLocalOutput, + cliContext, + force + ); + if (!shouldProceed) { + cliContext.failAndThrow("Generation cancelled"); + } + } + } + await cliContext.instrumentPostHogEvent({ orgId: project.config.organization, command: "fern generate", diff --git a/packages/cli/cli/src/constants.ts b/packages/cli/cli/src/constants.ts index 3a0672452ce..afff9df0e4b 100644 --- a/packages/cli/cli/src/constants.ts +++ b/packages/cli/cli/src/constants.ts @@ -2,3 +2,5 @@ export const API_CLI_OPTION = "api"; export const GROUP_CLI_OPTION = "group"; export const TOKEN_STDIN_OPTION = "token-stdin"; export const PREVIEW_DIRECTORY = ".preview"; +export const APPROVED_DIRECTORIES_FILENAME = "approved-output-directories"; +export const LOCAL_STORAGE_FOLDER = process.env.LOCAL_STORAGE_FOLDER ?? ".fern"; diff --git a/packages/cli/cli/src/persistence/output-directories/getOutputDirectories.ts b/packages/cli/cli/src/persistence/output-directories/getOutputDirectories.ts new file mode 100644 index 00000000000..a22ca3ddc58 --- /dev/null +++ b/packages/cli/cli/src/persistence/output-directories/getOutputDirectories.ts @@ -0,0 +1,16 @@ +import { AbsoluteFilePath, doesPathExist } from "@fern-api/fs-utils"; +import { readFile } from "fs/promises"; +import { getPathToOutputDirectoriesFile } from "./getPathToOutputDirectoriesFile"; + +export async function getOutputDirectories(): Promise { + const pathToOutputDirectoriesFile = getPathToOutputDirectoriesFile(); + const doesOutputDirectoriesFileExist = await doesPathExist(pathToOutputDirectoriesFile); + if (!doesOutputDirectoriesFileExist) { + return []; + } + + const outputDirectoriesFileContents = await readFile(pathToOutputDirectoriesFile); + const outputDirectories: AbsoluteFilePath[] = JSON.parse(outputDirectoriesFileContents.toString()); + + return outputDirectories; +} diff --git a/packages/cli/cli/src/persistence/output-directories/getPathToOutputDirectoriesFile.ts b/packages/cli/cli/src/persistence/output-directories/getPathToOutputDirectoriesFile.ts new file mode 100644 index 00000000000..e5836b6766a --- /dev/null +++ b/packages/cli/cli/src/persistence/output-directories/getPathToOutputDirectoriesFile.ts @@ -0,0 +1,11 @@ +import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/fs-utils"; +import { homedir } from "os"; +import { APPROVED_DIRECTORIES_FILENAME, LOCAL_STORAGE_FOLDER } from "../../constants"; + +export function getPathToOutputDirectoriesFile(): AbsoluteFilePath { + return join( + AbsoluteFilePath.of(homedir()), + RelativeFilePath.of(LOCAL_STORAGE_FOLDER), + RelativeFilePath.of(APPROVED_DIRECTORIES_FILENAME) + ); +} diff --git a/packages/cli/cli/src/persistence/output-directories/storeOutputDirectories.ts b/packages/cli/cli/src/persistence/output-directories/storeOutputDirectories.ts new file mode 100644 index 00000000000..1d3402a9f01 --- /dev/null +++ b/packages/cli/cli/src/persistence/output-directories/storeOutputDirectories.ts @@ -0,0 +1,9 @@ +import { mkdir, writeFile } from "fs/promises"; +import { AbsoluteFilePath } from "@fern-api/fs-utils"; +import path from "path"; +import { getPathToOutputDirectoriesFile } from "./getPathToOutputDirectoriesFile"; + +export async function storeOutputDirectories(outputDirectories: AbsoluteFilePath[]): Promise { + await mkdir(path.dirname(getPathToOutputDirectoriesFile()), { recursive: true }); + await writeFile(getPathToOutputDirectoriesFile(), JSON.stringify(outputDirectories, null, 2)); +} diff --git a/packages/cli/cli/src/utils/isCI.ts b/packages/cli/cli/src/utils/isCI.ts new file mode 100644 index 00000000000..5759f20fb73 --- /dev/null +++ b/packages/cli/cli/src/utils/isCI.ts @@ -0,0 +1,10 @@ +export function isCI(): boolean { + return Boolean( + process.env.CI === "true" || // Generic CI + process.env.GITHUB_ACTIONS === "true" || // GitHub Actions + process.env.GITLAB_CI === "true" || // GitLab CI + process.env.CIRCLECI === "true" || // CircleCI + process.env.JENKINS_URL === "true" || // Jenkins + process.env.TRAVIS === "true" // Travis CI + ); +} diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index b5799dbfe10..e0f8259d0c6 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,3 +1,10 @@ +- changelogEntry: + - summary: | + The CLI prompts the user to confirm output directory overwrites on fern generate. + type: fix + irVersion: 53 + version: 0.45.4-rc0 + - changelogEntry: - summary: | Unknown schemas are no longer incorrectly marked as `additionalProperties: true`. diff --git a/packages/cli/ete-tests/src/tests/generate/output-directory-prompts.test.ts b/packages/cli/ete-tests/src/tests/generate/output-directory-prompts.test.ts new file mode 100644 index 00000000000..75e4f142b5b --- /dev/null +++ b/packages/cli/ete-tests/src/tests/generate/output-directory-prompts.test.ts @@ -0,0 +1,26 @@ +import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/fs-utils"; +import { cp, mkdir } from "fs/promises"; +import tmp from "tmp-promise"; +import { runFernCli } from "../../utils/runFernCli"; +import stripAnsi from "strip-ansi"; +import { init } from "../init/init"; +import { Options } from "execa"; + +const envWithCI = { + CI: "true" +}; + +describe("output directory prompts", () => { + it("doesn't show prompts for CI environment", async () => { + const pathOfDirectory = await init(); + + const { stdout } = await runFernCli(["generate", "--local", "--keepDocker"], { + cwd: pathOfDirectory, + env: envWithCI + }); + + const cleanOutput = stripAnsi(stdout).trim(); + expect(cleanOutput).not.toContain("contains existing files"); + expect(cleanOutput).not.toContain("Would you like to save this"); + }, 180_000); +}); diff --git a/packages/cli/generation/local-generation/local-workspace-runner/package.json b/packages/cli/generation/local-generation/local-workspace-runner/package.json index aaff184f172..75598a3754a 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/package.json +++ b/packages/cli/generation/local-generation/local-workspace-runner/package.json @@ -62,4 +62,4 @@ "prettier": "^2.7.1", "typescript": "4.6.4" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c58a9c6527..78dc0d32efc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3891,6 +3891,9 @@ importers: '@fern-typescript/fetcher': specifier: workspace:* version: link:../../../generators/typescript/utils/core-utilities/fetcher + '@inquirer/prompts': + specifier: ^7.1.0 + version: 7.1.0(@types/node@18.7.18) '@types/axios': specifier: ^0.14.0 version: 0.14.0 @@ -8212,10 +8215,90 @@ packages: resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} deprecated: Use @eslint/object-schema instead + '@inquirer/checkbox@4.0.2': + resolution: {integrity: sha512-+gznPl8ip8P8HYHYecDtUtdsh1t2jvb+sWCD72GAiZ9m45RqwrLmReDaqdC0umQfamtFXVRoMVJ2/qINKGm9Tg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@inquirer/confirm@5.0.2': + resolution: {integrity: sha512-KJLUHOaKnNCYzwVbryj3TNBxyZIrr56fR5N45v6K9IPrbT6B7DcudBMfylkV1A8PUdJE15mybkEQyp2/ZUpxUA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@inquirer/core@10.1.0': + resolution: {integrity: sha512-I+ETk2AL+yAVbvuKx5AJpQmoaWhpiTFOg/UJb7ZkMAK4blmtG8ATh5ct+T/8xNld0CZG/2UhtkdMwpgvld92XQ==} + engines: {node: '>=18'} + + '@inquirer/editor@4.1.0': + resolution: {integrity: sha512-K1gGWsxEqO23tVdp5MT3H799OZ4ER1za7Dlc8F4um0W7lwSv0KGR/YyrUEyimj0g7dXZd8XknM/5QA2/Uy+TbA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@inquirer/expand@4.0.2': + resolution: {integrity: sha512-WdgCX1cUtinz+syKyZdJomovULYlKUWZbVYZzhf+ZeeYf4htAQ3jLymoNs3koIAKfZZl3HUBb819ClCBfyznaw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + '@inquirer/figures@1.0.3': resolution: {integrity: sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw==} engines: {node: '>=18'} + '@inquirer/figures@1.0.8': + resolution: {integrity: sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==} + engines: {node: '>=18'} + + '@inquirer/input@4.0.2': + resolution: {integrity: sha512-yCLCraigU085EcdpIVEDgyfGv4vBiE4I+k1qRkc9C5dMjWF42ADMGy1RFU94+eZlz4YlkmFsiyHZy0W1wdhaNg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@inquirer/number@3.0.2': + resolution: {integrity: sha512-MKQhYofdUNk7eqJtz52KvM1dH6R93OMrqHduXCvuefKrsiMjHiMwjc3NZw5Imm2nqY7gWd9xdhYrtcHMJQZUxA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@inquirer/password@4.0.2': + resolution: {integrity: sha512-tQXGSu7IO07gsYlGy3VgXRVsbOWqFBMbqAUrJSc1PDTQQ5Qdm+QVwkP0OC0jnUZ62D19iPgXOMO+tnWG+HhjNQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@inquirer/prompts@7.1.0': + resolution: {integrity: sha512-5U/XiVRH2pp1X6gpNAjWOglMf38/Ys522ncEHIKT1voRUvSj/DQnR22OVxHnwu5S+rCFaUiPQ57JOtMFQayqYA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@inquirer/rawlist@4.0.2': + resolution: {integrity: sha512-3XGcskMoVF8H0Dl1S5TSZ3rMPPBWXRcM0VeNVsS4ByWeWjSeb0lPqfnBg6N7T0608I1B2bSVnbi2cwCrmOD1Yw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@inquirer/search@3.0.2': + resolution: {integrity: sha512-Zv4FC7w4dJ13BOJfKRQCICQfShinGjb1bCEIHxTSnjj2telu3+3RHwHubPG9HyD4aix5s+lyAMEK/wSFD75HLA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@inquirer/select@4.0.2': + resolution: {integrity: sha512-uSWUzaSYAEj0hlzxa1mUB6VqrKaYx0QxGBLZzU4xWFxaSyGaXxsSE4OSOwdU24j0xl8OajgayqFXW0l2bkl2kg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@inquirer/type@3.0.1': + resolution: {integrity: sha512-+ksJMIy92sOAiAccGpcKZUc3bYO07cADnscIxHBknEm3uNts3movSmBofc1908BNy5edKscxYeAdaX1NXkHS6A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -11800,6 +11883,10 @@ packages: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -13833,6 +13920,10 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} @@ -15239,8 +15330,114 @@ snapshots: '@humanwhocodes/object-schema@2.0.2': {} + '@inquirer/checkbox@4.0.2(@types/node@18.7.18)': + dependencies: + '@inquirer/core': 10.1.0(@types/node@18.7.18) + '@inquirer/figures': 1.0.8 + '@inquirer/type': 3.0.1(@types/node@18.7.18) + '@types/node': 18.7.18 + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + + '@inquirer/confirm@5.0.2(@types/node@18.7.18)': + dependencies: + '@inquirer/core': 10.1.0(@types/node@18.7.18) + '@inquirer/type': 3.0.1(@types/node@18.7.18) + '@types/node': 18.7.18 + + '@inquirer/core@10.1.0(@types/node@18.7.18)': + dependencies: + '@inquirer/figures': 1.0.8 + '@inquirer/type': 3.0.1(@types/node@18.7.18) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + transitivePeerDependencies: + - '@types/node' + + '@inquirer/editor@4.1.0(@types/node@18.7.18)': + dependencies: + '@inquirer/core': 10.1.0(@types/node@18.7.18) + '@inquirer/type': 3.0.1(@types/node@18.7.18) + '@types/node': 18.7.18 + external-editor: 3.1.0 + + '@inquirer/expand@4.0.2(@types/node@18.7.18)': + dependencies: + '@inquirer/core': 10.1.0(@types/node@18.7.18) + '@inquirer/type': 3.0.1(@types/node@18.7.18) + '@types/node': 18.7.18 + yoctocolors-cjs: 2.1.2 + '@inquirer/figures@1.0.3': {} + '@inquirer/figures@1.0.8': {} + + '@inquirer/input@4.0.2(@types/node@18.7.18)': + dependencies: + '@inquirer/core': 10.1.0(@types/node@18.7.18) + '@inquirer/type': 3.0.1(@types/node@18.7.18) + '@types/node': 18.7.18 + + '@inquirer/number@3.0.2(@types/node@18.7.18)': + dependencies: + '@inquirer/core': 10.1.0(@types/node@18.7.18) + '@inquirer/type': 3.0.1(@types/node@18.7.18) + '@types/node': 18.7.18 + + '@inquirer/password@4.0.2(@types/node@18.7.18)': + dependencies: + '@inquirer/core': 10.1.0(@types/node@18.7.18) + '@inquirer/type': 3.0.1(@types/node@18.7.18) + '@types/node': 18.7.18 + ansi-escapes: 4.3.2 + + '@inquirer/prompts@7.1.0(@types/node@18.7.18)': + dependencies: + '@inquirer/checkbox': 4.0.2(@types/node@18.7.18) + '@inquirer/confirm': 5.0.2(@types/node@18.7.18) + '@inquirer/editor': 4.1.0(@types/node@18.7.18) + '@inquirer/expand': 4.0.2(@types/node@18.7.18) + '@inquirer/input': 4.0.2(@types/node@18.7.18) + '@inquirer/number': 3.0.2(@types/node@18.7.18) + '@inquirer/password': 4.0.2(@types/node@18.7.18) + '@inquirer/rawlist': 4.0.2(@types/node@18.7.18) + '@inquirer/search': 3.0.2(@types/node@18.7.18) + '@inquirer/select': 4.0.2(@types/node@18.7.18) + '@types/node': 18.7.18 + + '@inquirer/rawlist@4.0.2(@types/node@18.7.18)': + dependencies: + '@inquirer/core': 10.1.0(@types/node@18.7.18) + '@inquirer/type': 3.0.1(@types/node@18.7.18) + '@types/node': 18.7.18 + yoctocolors-cjs: 2.1.2 + + '@inquirer/search@3.0.2(@types/node@18.7.18)': + dependencies: + '@inquirer/core': 10.1.0(@types/node@18.7.18) + '@inquirer/figures': 1.0.8 + '@inquirer/type': 3.0.1(@types/node@18.7.18) + '@types/node': 18.7.18 + yoctocolors-cjs: 2.1.2 + + '@inquirer/select@4.0.2(@types/node@18.7.18)': + dependencies: + '@inquirer/core': 10.1.0(@types/node@18.7.18) + '@inquirer/figures': 1.0.8 + '@inquirer/type': 3.0.1(@types/node@18.7.18) + '@types/node': 18.7.18 + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + + '@inquirer/type@3.0.1(@types/node@18.7.18)': + dependencies: + '@types/node': 18.7.18 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -16366,7 +16563,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.6 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -18285,7 +18482,7 @@ snapshots: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.6 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -18294,7 +18491,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.6 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -19863,6 +20060,8 @@ snapshots: mute-stream@1.0.0: {} + mute-stream@2.0.0: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -20708,7 +20907,7 @@ snapshots: dependencies: chokidar: 3.6.0 immutable: 4.3.5 - source-map-js: 1.2.0 + source-map-js: 1.2.1 sax@1.3.0: {} @@ -22231,6 +22430,8 @@ snapshots: yocto-queue@1.0.0: {} + yoctocolors-cjs@2.1.2: {} + zod@3.22.4: {} zod@3.23.8: {}