-
Notifications
You must be signed in to change notification settings - Fork 332
feat(cli): rename --pack to --package and add --package-only #17307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| - summary: | | ||
| Rename the `--pack` and `--pack-mode` flags on `fern generate` to `--package` and | ||
| `--package-mode`, and add `--package-only`, which builds the distributable artifact into | ||
| `fern-dist/` and removes the generated SDK source, leaving only the package in the output | ||
| directory. | ||
| type: feat | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -875,17 +875,23 @@ function addGenerateCommand(cli: Argv<GlobalCliOptions>, cliContext: CliContext) | |
| description: | ||
| "Generate test files even when generating to a local file system (tests are normally only generated for GitHub output modes)" | ||
| }) | ||
| .option("pack", { | ||
| .option("package", { | ||
| boolean: true, | ||
| default: false, | ||
| description: | ||
| "After generating to the local file system, build distributable package artifacts (npm tarball, wheel, JAR, NuGet package, gem, etc.) into a fern-dist/ folder inside the output directory." | ||
| }) | ||
| .option("pack-mode", { | ||
| .option("package-mode", { | ||
| choices: ["host", "docker"] as const, | ||
| default: "host" as const, | ||
| description: | ||
| "Where --pack runs the packaging toolchain: 'host' uses toolchains installed on this machine; 'docker' runs each toolchain inside an official Docker image (node, python, gradle, dotnet/sdk, ruby, composer, rust) with the output directory mounted, so no local toolchains are needed." | ||
| "Where --package runs the packaging toolchain: 'host' uses toolchains installed on this machine; 'docker' runs each toolchain inside an official Docker image (node, python, gradle, dotnet/sdk, ruby, composer, rust) with the output directory mounted, so no local toolchains are needed." | ||
| }) | ||
| .option("package-only", { | ||
| boolean: true, | ||
| default: false, | ||
| description: | ||
| "Like --package, but only the fern-dist/ artifact is kept in the output directory — the generated SDK source is removed after the package is built." | ||
| }), | ||
| async (argv) => { | ||
| if (argv.api != null && argv.api.length > 0 && argv.docs != null) { | ||
|
|
@@ -961,22 +967,25 @@ function addGenerateCommand(cli: Argv<GlobalCliOptions>, cliContext: CliContext) | |
| { code: CliError.Code.ConfigError } | ||
| ); | ||
| } | ||
| if (argv.pack && argv.preview) { | ||
| return cliContext.failWithoutThrowing("The --pack flag cannot be used with --preview.", undefined, { | ||
| const shouldPackage = argv.package || argv.packageOnly; | ||
| if (shouldPackage && argv.preview) { | ||
| return cliContext.failWithoutThrowing("The --package flag cannot be used with --preview.", undefined, { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 suggestion When the user passed |
||
| code: CliError.Code.ConfigError | ||
| }); | ||
| } | ||
| if (argv.pack && argv.docs != null) { | ||
| if (shouldPackage && argv.docs != null) { | ||
| return cliContext.failWithoutThrowing( | ||
| "The --pack flag can only be used for API generation, not docs generation.", | ||
| "The --package flag can only be used for API generation, not docs generation.", | ||
| undefined, | ||
| { code: CliError.Code.ConfigError } | ||
| ); | ||
| } | ||
| if (argv.packMode !== "host" && !argv.pack) { | ||
| return cliContext.failWithoutThrowing("The --pack-mode flag can only be used with --pack.", undefined, { | ||
| code: CliError.Code.ConfigError | ||
| }); | ||
| if (argv.packageMode !== "host" && !shouldPackage) { | ||
| return cliContext.failWithoutThrowing( | ||
| "The --package-mode flag can only be used with --package.", | ||
| undefined, | ||
| { code: CliError.Code.ConfigError } | ||
| ); | ||
| } | ||
| if (argv.output != null && argv.docs != null) { | ||
| return cliContext.failWithoutThrowing( | ||
|
|
@@ -1018,8 +1027,9 @@ function addGenerateCommand(cli: Argv<GlobalCliOptions>, cliContext: CliContext) | |
| requireEnvVars: argv["require-env-vars"], | ||
| skipIfNoDiff: argv["skip-if-no-diff"], | ||
| generateTests: argv["generate-tests"], | ||
| pack: argv.pack, | ||
| packMode: argv.packMode | ||
| pack: shouldPackage, | ||
| packMode: argv.packageMode, | ||
| packOnly: argv.packageOnly | ||
| }); | ||
| } | ||
| if (argv.docs != null) { | ||
|
|
@@ -1083,8 +1093,9 @@ function addGenerateCommand(cli: Argv<GlobalCliOptions>, cliContext: CliContext) | |
| requireEnvVars: argv["require-env-vars"], | ||
| skipIfNoDiff: argv["skip-if-no-diff"], | ||
| generateTests: argv["generate-tests"], | ||
| pack: argv.pack, | ||
| packMode: argv.packMode | ||
| pack: shouldPackage, | ||
| packMode: argv.packageMode, | ||
| packOnly: argv.packageOnly | ||
| }); | ||
| } | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,7 +4,7 @@ import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath } from "@fern-a | |||||||||||||||||||||||||||||
| import { loggingExeca } from "@fern-api/logging-execa"; | ||||||||||||||||||||||||||||||
| import { TaskContext } from "@fern-api/task-context"; | ||||||||||||||||||||||||||||||
| import { createWriteStream } from "fs"; | ||||||||||||||||||||||||||||||
| import { copyFile, mkdir, readdir, readFile, rename, rmdir } from "fs/promises"; | ||||||||||||||||||||||||||||||
| import { copyFile, mkdir, readdir, readFile, rename, rm, rmdir } from "fs/promises"; | ||||||||||||||||||||||||||||||
| import { basename } from "path"; | ||||||||||||||||||||||||||||||
| import { ZipFile } from "yazl"; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -14,7 +14,7 @@ export const PACK_OUTPUT_DIRECTORY = "fern-dist"; | |||||||||||||||||||||||||||||
| /** Where the packaging toolchain runs: on the host machine, or inside a Docker toolchain image. */ | ||||||||||||||||||||||||||||||
| export type PackMode = "host" | "docker"; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| /** Official toolchain images used when packing with `--pack-mode docker`. */ | ||||||||||||||||||||||||||||||
| /** Official toolchain images used when packing with `--package-mode docker`. */ | ||||||||||||||||||||||||||||||
| const PACK_DOCKER_IMAGES: Record<string, string> = { | ||||||||||||||||||||||||||||||
| typescript: "node:22", | ||||||||||||||||||||||||||||||
| python: "python:3.12", | ||||||||||||||||||||||||||||||
|
|
@@ -39,12 +39,15 @@ export async function packLocalOutputForGroup({ | |||||||||||||||||||||||||||||
| group, | ||||||||||||||||||||||||||||||
| context, | ||||||||||||||||||||||||||||||
| mode = "host", | ||||||||||||||||||||||||||||||
| runner | ||||||||||||||||||||||||||||||
| runner, | ||||||||||||||||||||||||||||||
| packOnly = false | ||||||||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||||||||
| group: generatorsYml.GeneratorGroup; | ||||||||||||||||||||||||||||||
| context: TaskContext; | ||||||||||||||||||||||||||||||
| mode?: PackMode; | ||||||||||||||||||||||||||||||
| runner?: ContainerRunner; | ||||||||||||||||||||||||||||||
| /** Keep only the fern-dist/ artifact in each output directory, removing the generated SDK source. */ | ||||||||||||||||||||||||||||||
| packOnly?: boolean; | ||||||||||||||||||||||||||||||
| }): Promise<void> { | ||||||||||||||||||||||||||||||
| const failures: string[] = []; | ||||||||||||||||||||||||||||||
| for (const generator of group.generators) { | ||||||||||||||||||||||||||||||
|
|
@@ -62,6 +65,9 @@ export async function packLocalOutputForGroup({ | |||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
| await packOutputForLanguage({ language, outputPath, context, mode, runner: runner ?? "docker" }); | ||||||||||||||||||||||||||||||
| if (packOnly) { | ||||||||||||||||||||||||||||||
| await removeEverythingExceptDist({ outputPath, context }); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+74
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 warning The cleanup is inside the packaging
Suggested change
(Note: this suggestion assumes the
Comment on lines
+74
to
+82
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Swift SDK output is completely erased when only the package is requested The whole generated Swift output folder is wiped ( Swift packaging is a no-op warning, so nothing is created in fern-dist before deletionIn A fix would be to make packaging report whether an artifact was actually produced (or to skip the cleanup for languages with no package format), and only remove the source when Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in a1bfc44: |
||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||
| const message = error instanceof Error ? error.message : String(error); | ||||||||||||||||||||||||||||||
| context.logger.error(`Failed to package ${generator.name} output at ${outputPath}: ${message}`); | ||||||||||||||||||||||||||||||
|
|
@@ -262,6 +268,26 @@ async function zipDirectory({ | |||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||
| * Removes every entry in the output directory except the fern-dist/ artifact folder, so only the | ||||||||||||||||||||||||||||||
| * distributable package remains (used by --package-only). | ||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||
| async function removeEverythingExceptDist({ | ||||||||||||||||||||||||||||||
| outputPath, | ||||||||||||||||||||||||||||||
| context | ||||||||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||||||||
| outputPath: AbsoluteFilePath; | ||||||||||||||||||||||||||||||
| context: TaskContext; | ||||||||||||||||||||||||||||||
| }): Promise<void> { | ||||||||||||||||||||||||||||||
| for (const entry of await readdir(outputPath)) { | ||||||||||||||||||||||||||||||
| if (entry === PACK_OUTPUT_DIRECTORY) { | ||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| await rm(join(outputPath, RelativeFilePath.of(entry)), { recursive: true, force: true }); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+310
to
+315
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 critical This recursively force-deletes every entry in a user-configured At minimum, refuse to run when the directory looks like it contains non-generated content (e.g. a |
||||||||||||||||||||||||||||||
| context.logger.info(`Removed generated SDK source from ${outputPath}; only ${PACK_OUTPUT_DIRECTORY}/ remains.`); | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Hand-written and version-control files in the output directory are deleted along with generated code Every entry in the output directory is deleted ( Mechanism: cleanup does not exclude preserved or VCS entries
Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in a1bfc44: the cleanup now always preserves |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| async function removeDistDirIfEmpty(outputPath: AbsoluteFilePath): Promise<void> { | ||||||||||||||||||||||||||||||
| const distDir = join(outputPath, RelativeFilePath.of(PACK_OUTPUT_DIRECTORY)); | ||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 warning
Removing
--pack/--pack-modewith no alias is a breaking CLI change;type: featwill bury it in a minor release note. Use the breaking type (break) so users who scripted against--packactually see it — or keep hidden aliases for one release.