cross-release-cli
Advanced tools
| import { ProjectCategory } from "cross-bump"; | ||
| import "cac"; | ||
| //#region src/types.d.ts | ||
| type DefineConfigOptions = Partial<Omit<ReleaseOptions, "config">>; | ||
| type ReleaseOptions = { | ||
| /** | ||
| * Indicates whether to commit the changes. | ||
| */ | ||
| commit: boolean | CommitOptions | ||
| /** | ||
| * Specifies the path to the configuration file. | ||
| */ | ||
| config: string | ||
| /** | ||
| * The directory path where the operation will be performed. | ||
| * @default process.cwd() | ||
| */ | ||
| cwd: string | ||
| /** | ||
| * Enable debug log | ||
| */ | ||
| debug: boolean | ||
| /** | ||
| * Whether the operation is being run in a dry-run mode (simulated execution). | ||
| */ | ||
| dry: boolean | ||
| /** | ||
| * The list of directories to exclude from the search. | ||
| * @default ["node_modules", ".git", "target", "build", "dist"] | ||
| */ | ||
| exclude: string[] | ||
| /** | ||
| * The command to execute before pushing. | ||
| */ | ||
| execute: string[] | ||
| /** | ||
| * Specifies the main project category. | ||
| */ | ||
| main: ProjectCategory | ||
| /** | ||
| * Whether push changes to remote and push options | ||
| * @default false | ||
| */ | ||
| push: boolean | PushOptions | ||
| /** | ||
| * Specifies whether the operation should be performed recursively. | ||
| * @default false | ||
| */ | ||
| recursive: boolean | ||
| /** | ||
| * Indicates whether to create a tag for a release. | ||
| * @default false | ||
| */ | ||
| tag: boolean | TagOptions | ||
| /** | ||
| * The version string associated with the command or operation. | ||
| */ | ||
| version: string | ||
| /** | ||
| * Whether all prompts requiring user input will be answered with "yes". | ||
| * @default false | ||
| */ | ||
| yes: boolean | ||
| }; | ||
| type CommitOptions = { | ||
| /** | ||
| * Whether to sign the commit. | ||
| * @default true | ||
| */ | ||
| signoff?: true | ||
| /** | ||
| * Whether to stage all files or only modified files. | ||
| * @default false | ||
| */ | ||
| stageAll?: boolean | ||
| /** | ||
| * The template string for the commit message. if the template contains any "%s" placeholders, | ||
| * then they are replaced with the version number; | ||
| * @default "chore: release v%s" | ||
| */ | ||
| template?: string | ||
| /** | ||
| * Whether to enable git pre-commit and commit-msg hook. | ||
| * @default true | ||
| */ | ||
| verify?: boolean | ||
| }; | ||
| type PushOptions = { | ||
| /** | ||
| * The branch name, Use the same branch name as the local if not specified. | ||
| */ | ||
| branch?: string | ||
| /** | ||
| * Whether to follow tags | ||
| * @default true | ||
| */ | ||
| followTags?: boolean | ||
| /** | ||
| * The remote name, defaults to the upstream defined in the Git repository if not specified. | ||
| */ | ||
| remote?: string | ||
| }; | ||
| type TagOptions = { | ||
| /** | ||
| * The template for tag name, same as @type {CommitOptions.template} | ||
| * if the template contains any "%s" placeholders, | ||
| * then they are replaced with the version number; | ||
| */ | ||
| template?: string | ||
| }; | ||
| //#endregion | ||
| export { DefineConfigOptions, ReleaseOptions }; |
@@ -6,3 +6,3 @@ #!/usr/bin/env node | ||
| const App = await import("../dist/app.js") | ||
| const app = await App.default.create() | ||
| const app = new App.default() | ||
| void app.run() |
+26
-25
@@ -1,29 +0,30 @@ | ||
| import { ProjectFile } from 'cross-bump'; | ||
| import { ReleaseOptions } from './types.js'; | ||
| import { ReleaseOptions } from "./types.d-PDBL5zNm.js"; | ||
| import { ProjectFile } from "cross-bump"; | ||
| //#region src/app.d.ts | ||
| declare class App { | ||
| #private; | ||
| private _currentVersion; | ||
| private _modifiedFiles; | ||
| private _nextVersion; | ||
| private _options; | ||
| private _projectFiles; | ||
| private _taskQueue; | ||
| private _taskStatus; | ||
| private constructor(); | ||
| static create(argv?: string[]): Promise<App>; | ||
| checkGitClean(): void; | ||
| confirmReleaseOptions(): Promise<void>; | ||
| executeTasks(): Promise<void>; | ||
| resolveExecutes(): void; | ||
| resolveNextVersion(): Promise<void>; | ||
| resolveProjectFiles(): void; | ||
| resolveProjects(): void; | ||
| run(): Promise<void>; | ||
| get currentVersion(): string; | ||
| get nextVersion(): string; | ||
| get options(): ReleaseOptions; | ||
| get projectFiles(): ProjectFile[]; | ||
| #private; | ||
| private _currentVersion; | ||
| private _modifiedFiles; | ||
| private _nextVersion; | ||
| private _options; | ||
| private _projectFiles; | ||
| private _taskQueue; | ||
| private _taskStatus; | ||
| constructor(argv?: string[]); | ||
| checkGitClean(): void; | ||
| confirmReleaseOptions(): Promise<void>; | ||
| executeTasks(): Promise<void>; | ||
| resolveExecutes(): void; | ||
| resolveNextVersion(): Promise<void>; | ||
| resolveProjectFiles(): void; | ||
| resolveProjects(): void; | ||
| run(): Promise<void>; | ||
| get currentVersion(): string; | ||
| get nextVersion(): string; | ||
| get options(): ReleaseOptions; | ||
| get projectFiles(): ProjectFile[]; | ||
| } | ||
| export { App as default }; | ||
| //#endregion | ||
| export { App as default }; |
+628
-573
@@ -1,619 +0,674 @@ | ||
| // src/app.ts | ||
| import process4 from "node:process"; | ||
| import { | ||
| cancel, | ||
| confirm, | ||
| intro, | ||
| isCancel, | ||
| log as log2, | ||
| outro | ||
| } from "@clack/prompts"; | ||
| import { | ||
| findProjectFiles, | ||
| getProjectVersion, | ||
| isVersionValid as isVersionValid2, | ||
| upgradeProjectVersion | ||
| } from "cross-bump"; | ||
| import process from "node:process"; | ||
| import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts"; | ||
| import { DEFAULT_IGNORED_GLOBS, findProjectFiles, getGitignores, getNextVersions, getProjectVersion, isVersionValid, parseVersion, upgradeProjectVersion } from "cross-bump"; | ||
| import { execaSync, parseCommandString } from "execa"; | ||
| import isUnicodeSupported from "is-unicode-supported"; | ||
| import color2 from "picocolors"; | ||
| // src/cli.ts | ||
| import color from "picocolors"; | ||
| import path from "node:path"; | ||
| import { toAbsolute as toAbsolute2 } from "@rainbowatcher/path-extra"; | ||
| import { Command } from "commander"; | ||
| import { DEFAULT_IGNORED_GLOBS as DEFAULT_IGNORED_GLOBS2, getGitignores } from "cross-bump"; | ||
| import { defu as defu2 } from "defu"; | ||
| // package.json | ||
| var version = "0.1.0-alpha.4"; | ||
| // src/constants.ts | ||
| import process from "node:process"; | ||
| import { DEFAULT_IGNORED_GLOBS } from "cross-bump"; | ||
| var CONFIG_DEFAULT = { | ||
| all: false, | ||
| commit: { | ||
| stageAll: false, | ||
| template: "chore: release v%s", | ||
| verify: true | ||
| }, | ||
| cwd: process.cwd(), | ||
| debug: false, | ||
| dry: false, | ||
| exclude: DEFAULT_IGNORED_GLOBS, | ||
| execute: [], | ||
| main: "javascript", | ||
| push: { | ||
| followTags: false | ||
| }, | ||
| recursive: false, | ||
| tag: { | ||
| template: "v%s" | ||
| }, | ||
| yes: false | ||
| }; | ||
| // src/util/config.ts | ||
| import process2 from "node:process"; | ||
| import { toAbsolute } from "@rainbowatcher/path-extra"; | ||
| import cac from "cac"; | ||
| import { isFileSync } from "@rainbowatcher/fs-extra"; | ||
| import { toAbsolute } from "@rainbowatcher/path-extra"; | ||
| import defu from "defu"; | ||
| import { loadConfig } from "unconfig"; | ||
| import debug from "debug"; | ||
| import { Objects } from "@rainbowatcher/common"; | ||
| import { z } from "zod"; | ||
| // src/util/debug.ts | ||
| import debug from "debug"; | ||
| //#region src/util/debug.ts | ||
| function createDebug(ns) { | ||
| return debug(`cross-release-cli:${ns}`); | ||
| return debug(`cross-release-cli:${ns}`); | ||
| } | ||
| function isDebugEnable(options) { | ||
| if (options.debug) { | ||
| debug.enable("cross-release-cli:*"); | ||
| } | ||
| function setupDebug(options) { | ||
| if (options.debug) debug.enable("cross-release-cli:*"); | ||
| } | ||
| // src/util/config.ts | ||
| var debug2 = createDebug("config"); | ||
| //#endregion | ||
| //#region src/config.ts | ||
| const debug$4 = createDebug("config"); | ||
| function resolveAltOptions(opts, key, defaultValue) { | ||
| const value = opts[key]; | ||
| const _defaultValue = defaultValue ?? {}; | ||
| return typeof value === "boolean" ? value ? _defaultValue : {} : { ..._defaultValue, ...value }; | ||
| const value = opts[key]; | ||
| const _defaultValue = defaultValue ?? {}; | ||
| return typeof value === "boolean" ? value ? _defaultValue : {} : { | ||
| ..._defaultValue, | ||
| ...value | ||
| }; | ||
| } | ||
| async function loadUserSpecifiedConfigFile(configPath, currentOpts) { | ||
| const absConfigPath = toAbsolute(configPath); | ||
| if (!isFileSync(absConfigPath)) { | ||
| throw new Error(`${absConfigPath} is not a valid file.`); | ||
| } | ||
| const { config, sources } = await loadConfig({ | ||
| sources: [{ | ||
| files: absConfigPath | ||
| }] | ||
| }); | ||
| debug2("load specified config file:", sources); | ||
| return defu({ config: toAbsolute(currentOpts.config ?? "") }, currentOpts, config); | ||
| function loadUserSpecifiedConfigFile(configPath) { | ||
| const absConfigPath = toAbsolute(configPath); | ||
| if (!isFileSync(absConfigPath)) throw new Error(`${absConfigPath} is not a valid file.`); | ||
| const { config, sources } = loadConfig.sync({ sources: [{ files: absConfigPath }] }); | ||
| debug$4("load specified config file: %O", sources); | ||
| return config; | ||
| } | ||
| async function loadUserConfig(cwd = process2.cwd()) { | ||
| const { config: userConfig, sources } = await loadConfig({ | ||
| cwd, | ||
| sources: [ | ||
| { files: "cross-release.config" }, | ||
| { | ||
| extensions: ["json"], | ||
| files: "package", | ||
| rewrite(config) { | ||
| return config["cross-release"]; | ||
| } | ||
| } | ||
| ] | ||
| }); | ||
| debug2("load user config", sources); | ||
| debug2("user config:", userConfig); | ||
| return userConfig; | ||
| function loadDefaultConfigFile(cwd = process.cwd()) { | ||
| const { config, sources } = loadConfig.sync({ | ||
| cwd, | ||
| sources: [{ files: "cross-release.config" }, { | ||
| extensions: ["json"], | ||
| files: "package", | ||
| rewrite(config$1) { | ||
| return config$1["cross-release"]; | ||
| } | ||
| }] | ||
| }) ?? {}; | ||
| debug$4("load user config", sources); | ||
| debug$4("user config: %O", config); | ||
| return config; | ||
| } | ||
| function loadUserConfig(opts) { | ||
| let userConfig; | ||
| if (opts.config) userConfig = loadUserSpecifiedConfigFile(opts.config); | ||
| else userConfig = loadDefaultConfigFile(opts.cwd); | ||
| return userConfig; | ||
| } | ||
| // src/cli.ts | ||
| var debug3 = createDebug("cli"); | ||
| function createCliProgram() { | ||
| const cli = new Command("cross-release"); | ||
| cli.configureHelp({ | ||
| subcommandTerm: (cmd) => `${cmd.name()} ${cmd.usage()}` | ||
| }); | ||
| cli.name("cross-release").version(version).description("A release tool that support multi programming language").usage("[version] [options]").option("-a, --all", "Add all changed files to staged", CONFIG_DEFAULT.commit.stageAll).option("-c, --config [file]", "Config file (auto detect by default)").option("-D, --dry", "Dry run", CONFIG_DEFAULT.dry).option("-d, --debug", "Enable debug mode", CONFIG_DEFAULT.debug).option("-e, --exclude [dir...]", "Folders to exclude from search", CONFIG_DEFAULT.exclude).option("-m, --main", "Base project language [e.g. java, rust, javascript]", CONFIG_DEFAULT.main).option("-r, --recursive", "Run the command for each project in the workspace", CONFIG_DEFAULT.recursive).option("-x, --execute [command...]", "Execute the command", CONFIG_DEFAULT.execute).option("-y, --yes", "Answer yes to all prompts", CONFIG_DEFAULT.yes).option("--cwd [dir]", "Set working directory", CONFIG_DEFAULT.cwd).option("--no-commit", "Skip committing changes").option("--no-push", "Skip pushing").option("--no-tag", "Skip tagging").option("-h, --help", "Display this message"); | ||
| return cli; | ||
| //#endregion | ||
| //#region src/constants.ts | ||
| /** | ||
| * CLI exit codes. | ||
| * | ||
| * @see https://nodejs.org/api/process.html#process_exit_codes | ||
| */ | ||
| let ExitCode = /* @__PURE__ */ function(ExitCode$1) { | ||
| ExitCode$1[ExitCode$1["Canceled"] = 2] = "Canceled"; | ||
| ExitCode$1[ExitCode$1["FatalError"] = 1] = "FatalError"; | ||
| ExitCode$1[ExitCode$1["GitDirty"] = 3] = "GitDirty"; | ||
| ExitCode$1[ExitCode$1["InvalidArgument"] = 9] = "InvalidArgument"; | ||
| ExitCode$1[ExitCode$1["Success"] = 0] = "Success"; | ||
| return ExitCode$1; | ||
| }({}); | ||
| const CONFIG_DEFAULT = { | ||
| commit: { | ||
| signoff: true, | ||
| stageAll: false, | ||
| template: "chore: release v%s", | ||
| verify: true | ||
| }, | ||
| cwd: process.cwd(), | ||
| debug: false, | ||
| dry: false, | ||
| exclude: DEFAULT_IGNORED_GLOBS, | ||
| execute: [], | ||
| main: "javascript", | ||
| push: { | ||
| branch: void 0, | ||
| followTags: true, | ||
| remote: void 0 | ||
| }, | ||
| recursive: false, | ||
| tag: { template: "v%s" }, | ||
| yes: false | ||
| }; | ||
| //#endregion | ||
| //#region src/util/array.ts | ||
| function toArray(maybeArr) { | ||
| if (!maybeArr) return []; | ||
| return Array.isArray(maybeArr) ? maybeArr : [maybeArr]; | ||
| } | ||
| function toCliReleaseOptions(cli) { | ||
| const { args } = cli; | ||
| const options = cli.opts(); | ||
| if (options.help) { | ||
| cli.help(); | ||
| } | ||
| return { | ||
| ...options, | ||
| // combine user cli exclude option with default | ||
| exclude: options.exclude?.length ? [...DEFAULT_IGNORED_GLOBS2, ...options.exclude] : DEFAULT_IGNORED_GLOBS2, | ||
| ...args.length > 0 ? { version: args[0] } : {} | ||
| }; | ||
| //#endregion | ||
| //#region src/util/merge.ts | ||
| function merge(target, ...sources) { | ||
| let result = target; | ||
| const composer = (left, right, key) => { | ||
| if ([ | ||
| "commit", | ||
| "push", | ||
| "tag" | ||
| ].includes(key) && right === true) return left; | ||
| }; | ||
| for (const source of sources) result = Objects.mergeWith(result, source, composer); | ||
| return result; | ||
| } | ||
| async function resolveOptions(cli) { | ||
| const cliOptions = toCliReleaseOptions(cli); | ||
| let userConfig; | ||
| if (cliOptions.config) { | ||
| userConfig = await loadUserSpecifiedConfigFile(cliOptions.config, cliOptions); | ||
| } else { | ||
| userConfig = await loadUserConfig(cliOptions.cwd); | ||
| } | ||
| const parsedArgs = defu2(cliOptions, userConfig); | ||
| isDebugEnable(parsedArgs); | ||
| const set = getGitignores(parsedArgs.cwd); | ||
| for (const i of parsedArgs.exclude) set.add(i); | ||
| parsedArgs.exclude = [...set]; | ||
| const shouldBeAbsolute = ["cwd", "config"]; | ||
| for (const key of shouldBeAbsolute) { | ||
| if (!parsedArgs[key]) continue; | ||
| if (key === "cwd") { | ||
| const { cwd } = parsedArgs; | ||
| parsedArgs.cwd = toAbsolute2(cwd); | ||
| } | ||
| parsedArgs[key] = path.resolve(parsedArgs.cwd, parsedArgs[key]); | ||
| } | ||
| debug3("parsedArgs:", parsedArgs); | ||
| return parsedArgs; | ||
| //#endregion | ||
| //#region src/zod.ts | ||
| const cliOptions = z.object({ | ||
| commit: z.union([z.object({ | ||
| signoff: z.boolean(), | ||
| stageAll: z.boolean(), | ||
| template: z.string(), | ||
| verify: z.boolean() | ||
| }), z.boolean()]).describe("Indicates whether to commit the changes."), | ||
| config: z.string().optional(), | ||
| cwd: z.string(), | ||
| debug: z.boolean(), | ||
| dry: z.boolean(), | ||
| exclude: z.array(z.string()), | ||
| execute: z.array(z.string()), | ||
| main: z.string(), | ||
| push: z.union([z.object({ | ||
| branch: z.string().optional(), | ||
| followTags: z.boolean(), | ||
| remote: z.string().optional() | ||
| }), z.boolean()]), | ||
| recursive: z.boolean(), | ||
| tag: z.union([z.object({ template: z.string() }), z.boolean()]), | ||
| version: z.string().optional(), | ||
| yes: z.boolean() | ||
| }); | ||
| //#endregion | ||
| //#region package.json | ||
| var version = "0.1.0"; | ||
| //#endregion | ||
| //#region src/cli.ts | ||
| const debug$3 = createDebug("cli"); | ||
| function createCliProgram(argv) { | ||
| const cli = cac("cross-release").usage("A release tool that support multi programming language").version(version).usage("[version] [options]").option("-c, --config [file]", "Config file (auto detect by default)").option("-D, --dry", "Dry run").option("-d, --debug", "Enable debug mode").option("-e, --exclude [dir...]", "Folders to exclude from search").option("-m, --main [lang]", "Base project language [e.g. java, rust, javascript]").option("-r, --recursive", "Run the command for each project in the workspace").option("-x, --execute [command...]", "Execute the command").option("-y, --yes", "Answer yes to all prompts").option("--cwd [dir]", "Set working directory").option("--commit", "Committing changes").option("--commit.signoff", "Pushing Commit with signoff").option("--commit.stageAll", "Stage all changes before pushing").option("--commit.template <template>", "Template for commit message").option("--commit.verify", "Verify commit message").option("--push", "Pushing Commit to remote").option("--push.followTags", "Pushing with follow tags").option("--push.branch <branch>", "Branch name to push").option("--push.followTags", "pushing with follow tags").option("--tag", "Tagging for release").option("--tag.template <template>", "Template for tag message").option("-h, --help", "Display this message").help(); | ||
| return cli.parse(argv); | ||
| } | ||
| function argvToReleaseOptions(cli) { | ||
| const { args, options } = cli; | ||
| const opts = { | ||
| commit: options.commit, | ||
| config: options.config, | ||
| cwd: options.cwd, | ||
| debug: options.debug, | ||
| dry: options.dry, | ||
| exclude: toArray(options.exclude), | ||
| execute: toArray(options.execute), | ||
| main: options.main, | ||
| push: options.push, | ||
| recursive: options.recursive, | ||
| tag: options.tag, | ||
| version: options.version, | ||
| yes: options.yes, | ||
| ...args.length > 0 ? { version: args[0] } : {} | ||
| }; | ||
| debug$3("cli options: %O", opts); | ||
| return opts; | ||
| } | ||
| function pathToAbs(opts) { | ||
| const shouldBeAbsolute = ["cwd", "config"]; | ||
| for (const key of shouldBeAbsolute) { | ||
| if (!opts[key]) continue; | ||
| if (key === "cwd") opts.cwd = toAbsolute(opts.cwd); | ||
| opts[key] = path.resolve(opts.cwd, opts[key]); | ||
| } | ||
| } | ||
| function resolveGitIgnore(opts) { | ||
| const ignoresSet = getGitignores(opts.cwd); | ||
| for (const i of opts.exclude) ignoresSet.add(i); | ||
| opts.exclude = [...ignoresSet]; | ||
| } | ||
| function validateOptions(cli) { | ||
| const result = cliOptions.safeParse(cli); | ||
| if (!result.success) { | ||
| const formatted = result.error.format(); | ||
| let errorMsg = ""; | ||
| for (const [key, val] of Object.entries(formatted)) { | ||
| if (key === "_errors") continue; | ||
| errorMsg = `${val._errors[0]} for key \`${key}\``; | ||
| } | ||
| console.error(errorMsg); | ||
| process.exit(ExitCode.FatalError); | ||
| } | ||
| } | ||
| function resolveAppOptions(cli) { | ||
| const opts = argvToReleaseOptions(cli); | ||
| const userConfig = loadUserConfig(opts); | ||
| const crOptions = merge(CONFIG_DEFAULT, userConfig, opts); | ||
| validateOptions(crOptions); | ||
| setupDebug(crOptions); | ||
| resolveGitIgnore(crOptions); | ||
| pathToAbs(crOptions); | ||
| debug$3("resolved app options: %O", crOptions); | ||
| return crOptions; | ||
| } | ||
| // src/git.ts | ||
| import process3 from "node:process"; | ||
| import { log, spinner } from "@clack/prompts"; | ||
| import { execaSync as createExeca } from "execa"; | ||
| import color from "picocolors"; | ||
| var debug4 = createDebug("git"); | ||
| var execa = createExeca({ all: true, reject: false }); | ||
| //#endregion | ||
| //#region src/git.ts | ||
| const debug$2 = createDebug("git"); | ||
| const execa = execaSync({ | ||
| all: true, | ||
| reject: false | ||
| }); | ||
| function gitTag(options) { | ||
| const { | ||
| cwd = process3.cwd(), | ||
| del = false, | ||
| dry = false, | ||
| force = false, | ||
| message: message2, | ||
| tagName: name | ||
| } = options ?? {}; | ||
| const s = spinner(); | ||
| s.start("creating tag..."); | ||
| const args = []; | ||
| if (del) { | ||
| args.push("--delete"); | ||
| } else { | ||
| if (!message2 || message2?.length === 0) { | ||
| log.warn("no message provided, is recommended to provide a message for create an annotated tag"); | ||
| } else { | ||
| args.push( | ||
| // Create an annotated tag, which is recommended for releases. | ||
| // See https://git-scm.com/docs/git-tag | ||
| "--annotate", | ||
| // Use the same commit message for the tag | ||
| "--message", | ||
| // formatMessageString(template, nextVersion), | ||
| message2 | ||
| ); | ||
| } | ||
| } | ||
| if (force) args.push("--force"); | ||
| args.push(name); | ||
| debug4(`command: git tag ${args.join(" ")}`); | ||
| if (!dry) { | ||
| const { all, exitCode, failed, shortMessage } = execa("git", ["tag", ...args], { cwd }); | ||
| debug4("git tag stdout:", all); | ||
| if (failed) { | ||
| s.stop(color.red(shortMessage), exitCode); | ||
| return false; | ||
| } | ||
| } | ||
| s.stop(`create git tag: ${color.blue(name)}`); | ||
| return true; | ||
| const { cwd = process.cwd(), del = false, dry = false, force = false, message: message$1, tagName: name } = options ?? {}; | ||
| const s = spinner(); | ||
| s.start("creating tag..."); | ||
| const args = []; | ||
| if (del) args.push("--delete"); | ||
| else if (!message$1 || message$1?.length === 0) log.warn("no message provided, is recommended to provide a message for create an annotated tag"); | ||
| else args.push( | ||
| // Create an annotated tag, which is recommended for releases. | ||
| // See https://git-scm.com/docs/git-tag | ||
| "--annotate", | ||
| // Use the same commit message for the tag | ||
| "--message", | ||
| // formatMessageString(template, nextVersion), | ||
| message$1 | ||
| ); | ||
| if (force) args.push("--force"); | ||
| args.push(name); | ||
| debug$2(`command: git tag ${args.join(" ")}`); | ||
| if (!dry) { | ||
| const { all, exitCode, failed, shortMessage } = execa("git", ["tag", ...args], { cwd }); | ||
| debug$2("git tag stdout:", all); | ||
| if (failed) { | ||
| s.stop(color.red(shortMessage), exitCode); | ||
| return false; | ||
| } | ||
| } | ||
| s.stop(`create git tag: ${color.blue(name)}`); | ||
| return true; | ||
| } | ||
| function gitCommit(options) { | ||
| const { | ||
| cwd = process3.cwd(), | ||
| dry = false, | ||
| message: message2, | ||
| modifiedFiles = [], | ||
| stageAll, | ||
| verify | ||
| } = options ?? {}; | ||
| const s = spinner(); | ||
| s.start("committing..."); | ||
| const args = []; | ||
| args.push("--message", message2); | ||
| !verify && args.push("--no-verify"); | ||
| if (!stageAll && modifiedFiles.length > 0) { | ||
| args.push("--", ...modifiedFiles); | ||
| } else { | ||
| args.push("--all"); | ||
| } | ||
| debug4(`command: git commit ${args.join(" ")}`); | ||
| if (!dry) { | ||
| const { all, exitCode, failed, shortMessage } = execa("git", ["commit", ...args], { cwd }); | ||
| debug4("git commit stdout: %s", all); | ||
| if (failed) { | ||
| s.stop(color.red(shortMessage), exitCode); | ||
| return false; | ||
| } | ||
| } | ||
| s.stop(`commit message: ${color.green(message2)}`); | ||
| return true; | ||
| const { cwd = process.cwd(), dry = false, message: message$1, modifiedFiles = [], signoff, stageAll, verify } = options ?? {}; | ||
| const s = spinner(); | ||
| s.start("committing..."); | ||
| const args = []; | ||
| args.push("--message", message$1); | ||
| !verify && args.push("--no-verify"); | ||
| if (!stageAll && modifiedFiles.length > 0) args.push("--", ...modifiedFiles); | ||
| else args.push("--all"); | ||
| if (signoff) args.push("--signoff"); | ||
| debug$2(`command: git commit ${args.join(" ")}`); | ||
| if (!dry) { | ||
| const { all, exitCode, failed, shortMessage } = execa("git", ["commit", ...args], { cwd }); | ||
| debug$2("git commit stdout: %s", all); | ||
| if (failed) { | ||
| s.stop(color.red(shortMessage), exitCode); | ||
| return false; | ||
| } | ||
| } | ||
| s.stop(`commit message: ${color.green(message$1)}`); | ||
| return true; | ||
| } | ||
| function gitPush(options = {}) { | ||
| const { | ||
| branch, | ||
| cwd = process3.cwd(), | ||
| dry, | ||
| followTags = true, | ||
| remote | ||
| } = options; | ||
| const s = spinner(); | ||
| s.start("pushing..."); | ||
| const args = []; | ||
| if (remote) { | ||
| args.push(remote); | ||
| if (branch) { | ||
| args.push(branch); | ||
| } | ||
| } | ||
| followTags && args.push("--follow-tags"); | ||
| debug4(`command: git push ${args.join(" ")}`); | ||
| if (!dry) { | ||
| const { all, exitCode, failed, shortMessage } = execa("git", ["push", ...args], { cwd }); | ||
| debug4("git push stdout: %s", all); | ||
| if (failed) { | ||
| s.stop(color.red(shortMessage), exitCode); | ||
| return false; | ||
| } | ||
| } | ||
| const originUrl = gitOriginUrl(); | ||
| s.stop(`pushed to repo: ${color.underline(originUrl)}`); | ||
| return true; | ||
| const { branch, cwd = process.cwd(), dry, followTags = true, remote } = options; | ||
| const s = spinner(); | ||
| s.start("pushing..."); | ||
| const args = []; | ||
| if (remote) { | ||
| args.push(remote); | ||
| if (branch) args.push(branch); | ||
| } | ||
| followTags && args.push("--follow-tags"); | ||
| debug$2(`command: git push ${args.join(" ")}`); | ||
| if (!dry) { | ||
| const { all, exitCode, failed, shortMessage } = execa("git", ["push", ...args], { cwd }); | ||
| debug$2("git push stdout: %s", all); | ||
| if (failed) { | ||
| s.stop(color.red(shortMessage), exitCode); | ||
| return false; | ||
| } | ||
| } | ||
| const originUrl = gitOriginUrl(); | ||
| s.stop(`pushed to repo: ${color.underline(originUrl)}`); | ||
| return true; | ||
| } | ||
| function gitOriginUrl() { | ||
| const command = execa("git", ["remote", "get-url", "origin"]); | ||
| return command.stdout.trim(); | ||
| const command = execa("git", [ | ||
| "remote", | ||
| "get-url", | ||
| "origin" | ||
| ]); | ||
| return command.stdout.trim(); | ||
| } | ||
| function gitAdd(options = {}) { | ||
| const { | ||
| all = false, | ||
| cwd = process3.cwd(), | ||
| dry = false, | ||
| files = [] | ||
| } = options; | ||
| const args = []; | ||
| if (all) { | ||
| args.push("--all"); | ||
| } else if (files.length > 0) { | ||
| args.push("--", ...files); | ||
| } | ||
| debug4("command: git add", args.join(" ")); | ||
| if (!dry) { | ||
| const { all: all2, failed } = execa("git", ["add", ...args], { cwd }); | ||
| debug4("git add stdout:", all2); | ||
| if (failed) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| const { all = false, cwd = process.cwd(), dry = false, files = [] } = options; | ||
| const args = []; | ||
| if (all) args.push("--all"); | ||
| else if (files.length > 0) args.push("--", ...files); | ||
| debug$2("command: git add", args.join(" ")); | ||
| if (!dry) { | ||
| const { all: all$1, failed } = execa("git", ["add", ...args], { cwd }); | ||
| debug$2("git add stdout:", all$1); | ||
| if (failed) return false; | ||
| } | ||
| return true; | ||
| } | ||
| function isGitClean(options = {}) { | ||
| const { cwd = process3.cwd() } = options; | ||
| const args = ["diff-index", "--quiet", "HEAD", "--"]; | ||
| const { failed, message: message2 } = execa("git", args, { cwd }); | ||
| if (message2?.includes("bad revision")) { | ||
| return true; | ||
| } | ||
| return !failed; | ||
| const { cwd = process.cwd() } = options; | ||
| const args = [ | ||
| "diff-index", | ||
| "--quiet", | ||
| "HEAD", | ||
| "--" | ||
| ]; | ||
| const { failed, message: message$1 } = execa("git", args, { cwd }); | ||
| if (message$1?.includes("bad revision")) return true; | ||
| return !failed; | ||
| } | ||
| /** | ||
| * `-z`: use NUL termination instead of newline | ||
| * @see https://git-scm.com/docs/diff | ||
| */ | ||
| function getStagedFiles(opts = {}) { | ||
| const { cwd } = opts; | ||
| let stagedArr = []; | ||
| const args = ["--name-only", "--staged", "-z", "--diff-filter=ACMR"]; | ||
| debug4("command: git diff", args.join(" ")); | ||
| const { all, failed } = execa("git", ["diff", ...args], { cwd }); | ||
| if (!failed) { | ||
| stagedArr = all.replace(/\0$/, "").split("\0"); | ||
| } | ||
| return stagedArr; | ||
| const { cwd } = opts; | ||
| let stagedArr = []; | ||
| const args = [ | ||
| "--name-only", | ||
| "--staged", | ||
| "-z", | ||
| "--diff-filter=ACMR" | ||
| ]; | ||
| debug$2("command: git diff", args.join(" ")); | ||
| const { all, failed } = execa("git", ["diff", ...args], { cwd }); | ||
| if (!failed) stagedArr = all.replace(/\0$/, "").split("\0"); | ||
| return stagedArr; | ||
| } | ||
| // src/prompt.ts | ||
| import { select, text } from "@clack/prompts"; | ||
| import { getNextVersions, isVersionValid, parseVersion } from "cross-bump"; | ||
| //#endregion | ||
| //#region src/prompt.ts | ||
| /** | ||
| * Generates the version to be chosen based on command line arguments and project version. | ||
| * | ||
| * @param argv - The command line arguments. | ||
| * @param currentVersion - The current project version. | ||
| * @return The chosen version. | ||
| */ | ||
| async function chooseVersion(currentVersion) { | ||
| const versionObj = parseVersion(currentVersion); | ||
| const { | ||
| nextMajor, | ||
| nextMinor, | ||
| nextPatch, | ||
| nextPreMajor, | ||
| nextPreMinor, | ||
| nextPrePatch, | ||
| nextRelease | ||
| } = getNextVersions(versionObj ?? void 0); | ||
| const C_CUSTOM = "custom"; | ||
| const versions = [ | ||
| { label: "custom...", value: C_CUSTOM }, | ||
| { label: `next (${nextRelease})`, value: nextRelease }, | ||
| { label: `keep (${currentVersion})`, value: currentVersion ?? "" }, | ||
| { label: `patch (${nextPatch})`, value: nextPatch }, | ||
| { label: `minor (${nextMinor})`, value: nextMinor }, | ||
| { label: `major (${nextMajor})`, value: nextMajor }, | ||
| { label: `pre-patch (${nextPrePatch})`, value: nextPrePatch }, | ||
| { label: `pre-minor (${nextPreMinor})`, value: nextPreMinor }, | ||
| { label: `pre-major (${nextPreMajor})`, value: nextPreMajor } | ||
| ]; | ||
| const selectedValue = await select({ | ||
| initialValue: versions[1].value ?? C_CUSTOM, | ||
| message: `Pick a project version. (current: ${currentVersion})`, | ||
| options: versions | ||
| }); | ||
| if (!selectedValue || selectedValue === C_CUSTOM) { | ||
| return await text({ | ||
| message: "Input your custom version number", | ||
| placeholder: "version number", | ||
| validate: (value) => { | ||
| if (!isVersionValid(value)) { | ||
| return "Invalid"; | ||
| } | ||
| } | ||
| }); | ||
| } else { | ||
| return selectedValue; | ||
| } | ||
| const versionObj = parseVersion(currentVersion); | ||
| const { nextMajor, nextMinor, nextPatch, nextPreMajor, nextPreMinor, nextPrePatch, nextRelease } = getNextVersions(versionObj ?? void 0); | ||
| const C_CUSTOM = "custom"; | ||
| const versions = [ | ||
| { | ||
| label: "custom...", | ||
| value: C_CUSTOM | ||
| }, | ||
| { | ||
| label: `next (${nextRelease})`, | ||
| value: nextRelease | ||
| }, | ||
| { | ||
| label: `keep (${currentVersion})`, | ||
| value: currentVersion ?? "" | ||
| }, | ||
| { | ||
| label: `patch (${nextPatch})`, | ||
| value: nextPatch | ||
| }, | ||
| { | ||
| label: `minor (${nextMinor})`, | ||
| value: nextMinor | ||
| }, | ||
| { | ||
| label: `major (${nextMajor})`, | ||
| value: nextMajor | ||
| }, | ||
| { | ||
| label: `pre-patch (${nextPrePatch})`, | ||
| value: nextPrePatch | ||
| }, | ||
| { | ||
| label: `pre-minor (${nextPreMinor})`, | ||
| value: nextPreMinor | ||
| }, | ||
| { | ||
| label: `pre-major (${nextPreMajor})`, | ||
| value: nextPreMajor | ||
| } | ||
| ]; | ||
| const selectedValue = await select({ | ||
| initialValue: versions[1].value ?? C_CUSTOM, | ||
| message: `Pick a project version. (current: ${currentVersion})`, | ||
| options: versions | ||
| }); | ||
| if (!selectedValue || selectedValue === C_CUSTOM) return await text({ | ||
| message: "Input your custom version number", | ||
| placeholder: "version number", | ||
| validate: (value) => { | ||
| if (!isVersionValid(value)) return "Invalid"; | ||
| } | ||
| }); | ||
| else return selectedValue; | ||
| } | ||
| // src/util/str.ts | ||
| //#endregion | ||
| //#region src/util/str.ts | ||
| /** | ||
| * Accepts a message string template (e.g. "release %s" or "This is the %s release"). | ||
| * If the template contains any "%s" placeholders, then they are replaced with the version number; | ||
| * otherwise, the version number is appended to the string. | ||
| */ | ||
| function formatMessageString(template, nextVersion) { | ||
| return template?.includes("%s") ? template.replaceAll("%s", nextVersion) : template + nextVersion; | ||
| return template?.includes("%s") ? template.replaceAll("%s", nextVersion) : template + nextVersion; | ||
| } | ||
| // src/app.ts | ||
| var debug5 = createDebug("app"); | ||
| //#endregion | ||
| //#region src/app.ts | ||
| const debug$1 = createDebug("app"); | ||
| function message(msg) { | ||
| const bar = isUnicodeSupported() ? "\u2502" : "|"; | ||
| console.log(`${color2.gray(bar)} ${msg}`); | ||
| const bar = isUnicodeSupported() ? "│" : "|"; | ||
| console.log(`${color.gray(bar)} ${msg}`); | ||
| } | ||
| /** | ||
| * Return the original result if it is not a cancellation symbol. exit process when detect cancel signal | ||
| */ | ||
| function handleUserCancel(result) { | ||
| if (isCancel(result)) { | ||
| cancel("User cancel"); | ||
| process4.exit(2 /* Canceled */); | ||
| } | ||
| return result; | ||
| if (isCancel(result)) { | ||
| cancel("User cancel"); | ||
| process.exit(ExitCode.Canceled); | ||
| } | ||
| return result; | ||
| } | ||
| var App = class _App { | ||
| _currentVersion = ""; | ||
| _modifiedFiles = []; | ||
| _nextVersion = ""; | ||
| _options; | ||
| _projectFiles = []; | ||
| _taskQueue = []; | ||
| _taskStatus = "pending"; | ||
| constructor(opts) { | ||
| this._options = opts; | ||
| } | ||
| static async create(argv = process4.argv) { | ||
| const cli = createCliProgram().parse(argv); | ||
| const opts = await resolveOptions(cli); | ||
| return new _App(opts); | ||
| } | ||
| #addTask(task, idx) { | ||
| const expect = this._taskQueue.length + 1; | ||
| if (idx) { | ||
| this._taskQueue.splice(idx, 0, task); | ||
| } else { | ||
| this._taskQueue.push(task); | ||
| } | ||
| return this._taskQueue.length === expect; | ||
| } | ||
| #check(status) { | ||
| if (Array.isArray(status)) { | ||
| if (status.some((s) => !s)) { | ||
| this._taskStatus = "failed"; | ||
| } | ||
| } else if (!status) { | ||
| this._taskStatus = "failed"; | ||
| } | ||
| } | ||
| #checkDryRun() { | ||
| if (this._options.dry) { | ||
| log2.message(color2.bgBlue(" DRY RUN ")); | ||
| process4.env.DRY = "true"; | ||
| } | ||
| } | ||
| #done() { | ||
| if (this._taskStatus === "failed") { | ||
| outro(color2.red("Error")); | ||
| process4.exit(1 /* FatalError */); | ||
| } else { | ||
| outro("Done"); | ||
| this._taskStatus = "finished"; | ||
| } | ||
| } | ||
| #start() { | ||
| intro("Cross release"); | ||
| this.#checkDryRun(); | ||
| this._taskStatus = "running"; | ||
| } | ||
| checkGitClean() { | ||
| const { cwd } = this._options; | ||
| if (!isGitClean({ cwd })) { | ||
| log2.warn("git is not clean, please commit or stash your changes before release"); | ||
| this.#done(); | ||
| process4.exit(3 /* GitDirty */); | ||
| } | ||
| } | ||
| async confirmReleaseOptions() { | ||
| const { all, cwd, dry, yes } = this._options; | ||
| const confirmTask = async (name, message2, exec) => { | ||
| if (yes) { | ||
| if (!this._options[name]) return; | ||
| this._options[name] = true; | ||
| } else if (this._options[name]) { | ||
| const confirmation = await confirm({ message: message2 }); | ||
| this._options[name] = handleUserCancel(confirmation); | ||
| } | ||
| if (this._options[name]) { | ||
| this.#addTask({ exec, name }); | ||
| } | ||
| }; | ||
| let commitMessage; | ||
| if (this._options.commit) { | ||
| const { | ||
| stageAll, | ||
| template, | ||
| verify | ||
| } = resolveAltOptions(this._options, "commit", { | ||
| ...CONFIG_DEFAULT.commit, | ||
| stageAll: all | ||
| }); | ||
| const _all = stageAll ?? all; | ||
| this.#addTask({ | ||
| exec: () => { | ||
| return gitAdd({ | ||
| all: _all, | ||
| cwd, | ||
| dry, | ||
| files: this._modifiedFiles | ||
| }); | ||
| }, | ||
| name: "add" | ||
| }); | ||
| commitMessage = formatMessageString(template, this._nextVersion); | ||
| await confirmTask("commit", "should commit?", () => { | ||
| debug5("staged files: %O", getStagedFiles({ cwd })); | ||
| return gitCommit({ | ||
| cwd, | ||
| dry, | ||
| message: commitMessage, | ||
| modifiedFiles: _all ? void 0 : this._modifiedFiles, | ||
| stageAll: _all, | ||
| verify | ||
| }); | ||
| }); | ||
| } | ||
| if (this._options.tag && commitMessage !== void 0) { | ||
| const { template: tagTpt } = resolveAltOptions(this._options, "tag", CONFIG_DEFAULT.tag); | ||
| await confirmTask("tag", "should create tag?", () => { | ||
| const tagName = formatMessageString(tagTpt, this._nextVersion); | ||
| return gitTag({ | ||
| cwd, | ||
| dry, | ||
| message: commitMessage, | ||
| tagName | ||
| }); | ||
| }); | ||
| } | ||
| if (this._options.push) { | ||
| const { followTags } = resolveAltOptions(this._options, "push", CONFIG_DEFAULT.push); | ||
| await confirmTask("push", "should push to remote?", () => { | ||
| return gitPush({ cwd, dry, followTags }); | ||
| }); | ||
| } | ||
| } | ||
| async executeTasks() { | ||
| debug5("taskQueue:", this._taskQueue); | ||
| for await (const task of this._taskQueue) { | ||
| if (this._taskStatus === "failed") break; | ||
| this.#check(await task.exec()); | ||
| } | ||
| } | ||
| resolveExecutes() { | ||
| const { cwd, execute } = this._options; | ||
| const indexBeforeCommit = this._taskQueue.findIndex((t) => t.name === "commit") - 1; | ||
| const index = indexBeforeCommit === -1 ? this._taskQueue.length : indexBeforeCommit; | ||
| for (const command of execute) { | ||
| if (!command) continue; | ||
| const [cmd, ...args] = parseCommandString(command); | ||
| if (!cmd) continue; | ||
| const exec = () => { | ||
| debug5("exec command: %s %s", cmd, args.join(" ")); | ||
| const { exitCode, failed, stdout } = execaSync(cmd, args, { cwd, reject: false }); | ||
| debug5("exec stdout:", stdout, exitCode); | ||
| if (failed) { | ||
| log2.error(`exec: ${command}`); | ||
| return false; | ||
| } else { | ||
| log2.success(`exec: ${command}`); | ||
| return true; | ||
| } | ||
| }; | ||
| this.#addTask({ exec, name: "anonymous" }, index); | ||
| } | ||
| } | ||
| async resolveNextVersion() { | ||
| const { main, version: version2 } = this._options; | ||
| const mainProjectFile = this._projectFiles.find((file) => file.category === main); | ||
| if (!mainProjectFile) { | ||
| throw new Error(`can't found ${main} project file in the project root`); | ||
| } | ||
| const projectVersion = await getProjectVersion(mainProjectFile); | ||
| this._currentVersion = projectVersion ?? ""; | ||
| if (isVersionValid2(version2)) { | ||
| this._nextVersion = version2; | ||
| log2.info(`current version: ${this._currentVersion}, next version: ${color2.blue(this._nextVersion)}`); | ||
| } else { | ||
| const nextVersion = await chooseVersion(this._currentVersion); | ||
| this._nextVersion = handleUserCancel(nextVersion); | ||
| } | ||
| } | ||
| resolveProjectFiles() { | ||
| const { cwd, exclude, recursive } = this._options; | ||
| const projectFiles = findProjectFiles(cwd, exclude, recursive); | ||
| if (projectFiles.length === 0) { | ||
| console.error("can't found any project file in the project root"); | ||
| process4.exit(1); | ||
| } | ||
| debug5(`found ${projectFiles.length} project files`); | ||
| this._projectFiles = projectFiles; | ||
| } | ||
| resolveProjects() { | ||
| const { _nextVersion, _projectFiles } = this; | ||
| this.#addTask({ | ||
| exec: async () => { | ||
| return await Promise.all(_projectFiles.map(async (projectFile) => { | ||
| try { | ||
| await upgradeProjectVersion(_nextVersion, projectFile); | ||
| this._modifiedFiles.push(projectFile.path); | ||
| message(`upgrade to ${color2.blue(_nextVersion)} for ${color2.gray(projectFile.path)}`); | ||
| } catch (error) { | ||
| log2.error(String(error)); | ||
| return false; | ||
| } | ||
| return true; | ||
| })); | ||
| }, | ||
| name: "upgradeVersion" | ||
| }); | ||
| } | ||
| async run() { | ||
| this.#start(); | ||
| this.checkGitClean(); | ||
| this.resolveProjectFiles(); | ||
| await this.resolveNextVersion(); | ||
| this.resolveProjects(); | ||
| await this.confirmReleaseOptions(); | ||
| this.resolveExecutes(); | ||
| await this.executeTasks(); | ||
| this.#done(); | ||
| } | ||
| get currentVersion() { | ||
| return this._currentVersion; | ||
| } | ||
| get nextVersion() { | ||
| return this._nextVersion; | ||
| } | ||
| get options() { | ||
| return this._options; | ||
| } | ||
| get projectFiles() { | ||
| return this._projectFiles; | ||
| } | ||
| var App = class { | ||
| _currentVersion = ""; | ||
| _modifiedFiles = []; | ||
| _nextVersion = ""; | ||
| _options; | ||
| _projectFiles = []; | ||
| _taskQueue = []; | ||
| _taskStatus = "pending"; | ||
| constructor(argv = process.argv) { | ||
| const cli = createCliProgram(argv); | ||
| const opts = resolveAppOptions(cli); | ||
| this._options = opts; | ||
| } | ||
| #addTask(task, idx) { | ||
| const expect = this._taskQueue.length + 1; | ||
| if (idx) this._taskQueue.splice(idx, 0, task); | ||
| else this._taskQueue.push(task); | ||
| return this._taskQueue.length === expect; | ||
| } | ||
| #check(status) { | ||
| if (Array.isArray(status)) { | ||
| if (status.some((s) => !s)) this._taskStatus = "failed"; | ||
| } else if (!status) this._taskStatus = "failed"; | ||
| } | ||
| #checkDryRun() { | ||
| if (this._options.dry) { | ||
| log.message(color.bgBlue(" DRY RUN ")); | ||
| process.env.DRY = "true"; | ||
| } | ||
| } | ||
| #done() { | ||
| if (this._taskStatus === "failed") { | ||
| outro(color.red("Error")); | ||
| process.exit(ExitCode.FatalError); | ||
| } else { | ||
| outro("Done"); | ||
| this._taskStatus = "finished"; | ||
| } | ||
| } | ||
| #start() { | ||
| intro("Cross release"); | ||
| this.#checkDryRun(); | ||
| this._taskStatus = "running"; | ||
| } | ||
| checkGitClean() { | ||
| const { cwd } = this._options; | ||
| const commit = resolveAltOptions(this._options, "commit"); | ||
| const isClean = isGitClean({ cwd }); | ||
| if (!isClean && !commit.stageAll) { | ||
| log.warn("git is not clean, please commit or stash your changes before release"); | ||
| this.#done(); | ||
| process.exit(ExitCode.GitDirty); | ||
| } | ||
| } | ||
| async confirmReleaseOptions() { | ||
| const { cwd, dry, yes } = this._options; | ||
| const confirmTask = async (name, message$1, exec) => { | ||
| if (yes) { | ||
| if (!this._options[name]) return; | ||
| this._options[name] = true; | ||
| } else if (this._options[name]) { | ||
| const confirmation = await confirm({ message: message$1 }); | ||
| this._options[name] = handleUserCancel(confirmation); | ||
| } | ||
| if (this._options[name]) this.#addTask({ | ||
| exec, | ||
| name | ||
| }); | ||
| }; | ||
| let commitMessage; | ||
| if (this._options.commit) { | ||
| const { stageAll, template, verify } = resolveAltOptions(this._options, "commit", { ...CONFIG_DEFAULT.commit }); | ||
| this.#addTask({ | ||
| exec: () => { | ||
| return gitAdd({ | ||
| all: stageAll, | ||
| cwd, | ||
| dry, | ||
| files: this._modifiedFiles | ||
| }); | ||
| }, | ||
| name: "add" | ||
| }); | ||
| commitMessage = formatMessageString(template, this._nextVersion); | ||
| await confirmTask("commit", "should commit?", () => { | ||
| debug$1("staged files: %O", getStagedFiles({ cwd })); | ||
| return gitCommit({ | ||
| cwd, | ||
| dry, | ||
| message: commitMessage, | ||
| modifiedFiles: stageAll ? void 0 : this._modifiedFiles, | ||
| stageAll, | ||
| verify | ||
| }); | ||
| }); | ||
| } | ||
| if (this._options.tag && commitMessage !== void 0) { | ||
| const { template: tagTpt } = resolveAltOptions(this.options, "tag"); | ||
| await confirmTask("tag", "should create tag?", () => { | ||
| const tagName = formatMessageString(tagTpt, this._nextVersion); | ||
| return gitTag({ | ||
| cwd, | ||
| dry, | ||
| message: commitMessage, | ||
| tagName | ||
| }); | ||
| }); | ||
| } | ||
| if (this._options.push) { | ||
| const { followTags } = resolveAltOptions(this._options, "push", CONFIG_DEFAULT.push); | ||
| await confirmTask("push", "should push to remote?", () => { | ||
| return gitPush({ | ||
| cwd, | ||
| dry, | ||
| followTags | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
| async executeTasks() { | ||
| debug$1("taskQueue:", this._taskQueue); | ||
| for (const task of this._taskQueue) { | ||
| if (this._taskStatus === "failed") break; | ||
| this.#check(await task.exec()); | ||
| } | ||
| } | ||
| resolveExecutes() { | ||
| const { cwd, execute } = this._options; | ||
| const indexBeforeCommit = this._taskQueue.findIndex((t) => t.name === "commit") - 1; | ||
| const index = indexBeforeCommit === -1 ? this._taskQueue.length : indexBeforeCommit; | ||
| for (const command of execute) { | ||
| if (!command) continue; | ||
| const [cmd, ...args] = parseCommandString(command); | ||
| if (!cmd) continue; | ||
| const exec = () => { | ||
| debug$1("exec command: %s %s", cmd, args.join(" ")); | ||
| const { exitCode, failed, stdout } = execaSync(cmd, args, { | ||
| cwd, | ||
| reject: false | ||
| }); | ||
| debug$1("exec stdout:", stdout, exitCode); | ||
| if (failed) { | ||
| log.error(`exec: ${command}`); | ||
| return false; | ||
| } else { | ||
| log.success(`exec: ${command}`); | ||
| return true; | ||
| } | ||
| }; | ||
| this.#addTask({ | ||
| exec, | ||
| name: "anonymous" | ||
| }, index); | ||
| } | ||
| } | ||
| async resolveNextVersion() { | ||
| const { main, version: version$1 } = this._options; | ||
| const mainProjectFile = this._projectFiles.find((file) => file.category === main); | ||
| if (!mainProjectFile) throw new Error(`can't found ${main} project file in the project root`); | ||
| const projectVersion = await getProjectVersion(mainProjectFile); | ||
| this._currentVersion = projectVersion ?? ""; | ||
| if (isVersionValid(version$1)) { | ||
| this._nextVersion = version$1; | ||
| log.info(`current version: ${this._currentVersion}, next version: ${color.blue(this._nextVersion)}`); | ||
| } else { | ||
| const nextVersion = await chooseVersion(this._currentVersion); | ||
| this._nextVersion = handleUserCancel(nextVersion); | ||
| } | ||
| } | ||
| resolveProjectFiles() { | ||
| const { cwd, exclude, recursive } = this._options; | ||
| const projectFiles = findProjectFiles(cwd, exclude, recursive); | ||
| if (projectFiles.length === 0) { | ||
| console.error("can't found any project file in the project root"); | ||
| process.exit(ExitCode.FatalError); | ||
| } | ||
| debug$1(`found ${projectFiles.length} project files`); | ||
| this._projectFiles = projectFiles; | ||
| } | ||
| resolveProjects() { | ||
| const { _nextVersion, _projectFiles } = this; | ||
| this.#addTask({ | ||
| exec: async () => { | ||
| return await Promise.all(_projectFiles.map(async (projectFile) => { | ||
| try { | ||
| await upgradeProjectVersion(_nextVersion, projectFile); | ||
| this._modifiedFiles.push(projectFile.path); | ||
| message(`upgrade to ${color.blue(_nextVersion)} for ${color.gray(projectFile.path)}`); | ||
| } catch (error) { | ||
| log.error(String(error)); | ||
| return false; | ||
| } | ||
| return true; | ||
| })); | ||
| }, | ||
| name: "upgradeVersion" | ||
| }); | ||
| } | ||
| async run() { | ||
| this.#start(); | ||
| this.checkGitClean(); | ||
| this.resolveProjectFiles(); | ||
| await this.resolveNextVersion(); | ||
| this.resolveProjects(); | ||
| await this.confirmReleaseOptions(); | ||
| this.resolveExecutes(); | ||
| await this.executeTasks(); | ||
| this.#done(); | ||
| } | ||
| get currentVersion() { | ||
| return this._currentVersion; | ||
| } | ||
| get nextVersion() { | ||
| return this._nextVersion; | ||
| } | ||
| get options() { | ||
| return this._options; | ||
| } | ||
| get projectFiles() { | ||
| return this._projectFiles; | ||
| } | ||
| }; | ||
| var app_default = App; | ||
| export { | ||
| app_default as default | ||
| }; | ||
| //#endregion | ||
| export { app_default as default }; |
+4
-3
@@ -1,6 +0,7 @@ | ||
| import { DefineConfigOptions } from './types.js'; | ||
| import 'cross-bump'; | ||
| import { DefineConfigOptions } from "./types.d-PDBL5zNm.js"; | ||
| //#region src/index.d.ts | ||
| declare function defineConfig(config: DefineConfigOptions): DefineConfigOptions; | ||
| export { defineConfig }; | ||
| //#endregion | ||
| export { defineConfig }; |
+5
-5
@@ -1,7 +0,7 @@ | ||
| // src/index.ts | ||
| //#region src/index.ts | ||
| function defineConfig(config) { | ||
| return config; | ||
| return config; | ||
| } | ||
| export { | ||
| defineConfig | ||
| }; | ||
| //#endregion | ||
| export { defineConfig }; |
+16
-11
| { | ||
| "name": "cross-release-cli", | ||
| "type": "module", | ||
| "version": "0.1.0", | ||
| "version": "0.2.0", | ||
| "description": "command line app for cross language bump utility", | ||
@@ -29,18 +29,23 @@ "author": { | ||
| "dependencies": { | ||
| "@clack/prompts": "^0.7.0", | ||
| "@rainbowatcher/fs-extra": "^0.2.3", | ||
| "@rainbowatcher/path-extra": "^0.2.3", | ||
| "commander": "^12.1.0", | ||
| "debug": "^4.3.7", | ||
| "@clack/prompts": "^0.10.1", | ||
| "@rainbowatcher/common": "^0.7.0", | ||
| "@rainbowatcher/fs-extra": "^0.7.0", | ||
| "@rainbowatcher/path-extra": "^0.7.0", | ||
| "cac": "^6.7.14", | ||
| "debug": "^4.4.0", | ||
| "defu": "^6.1.4", | ||
| "execa": "^9.3.1", | ||
| "execa": "^9.5.2", | ||
| "is-unicode-supported": "^2.1.0", | ||
| "picocolors": "^1.1.0", | ||
| "unconfig": "^0.5.5", | ||
| "cross-bump": "0.1.0" | ||
| "picocolors": "^1.1.1", | ||
| "unconfig": "^7.3.1", | ||
| "zod": "^3.24.3", | ||
| "cross-bump": "0.2.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@rainbowatcher/maybe": "^0.7.0" | ||
| }, | ||
| "scripts": { | ||
| "clean": "rimraf dist/*", | ||
| "build": "tsup" | ||
| "build": "tsdown" | ||
| } | ||
| } |
-127
| import { ProjectCategory } from 'cross-bump'; | ||
| type Arrayable<T> = T | T[]; | ||
| type CliPrimitive = boolean | string | string[]; | ||
| type ExcludeType<T, U> = { | ||
| [K in keyof T]: T[K] extends U ? T[K] : Exclude<T[K], U>; | ||
| }; | ||
| type KeysOf<T, KeyType = string> = keyof { | ||
| [K in keyof T as T[K] extends KeyType ? K : never]: T[K]; | ||
| }; | ||
| type ResolvedOptions<T> = T extends boolean ? never : NonNullable<T>; | ||
| type Status = "failed" | "finished" | "pending" | "running"; | ||
| type ExtractBooleanKeys<T> = keyof Pick<T, { | ||
| [K in keyof T]: T[K] extends boolean | Record<string, unknown> ? K : never; | ||
| }[keyof T]>; | ||
| type Task = { | ||
| exec: () => boolean | boolean[] | Promise<boolean | boolean[]>; | ||
| name: string; | ||
| }; | ||
| type ReleaseOptionsDefault = Omit<ExcludeType<ReleaseOptions, CliPrimitive>, "config" | "version">; | ||
| type DefineConfigOptions = Partial<Omit<ReleaseOptions, "config">>; | ||
| type CliReleaseOptions = ExcludeType<ReleaseOptions, Record<string, unknown>>; | ||
| type ReleaseOptions = { | ||
| /** | ||
| * Wethere add all changed files to staged, shorthand for @type {CommitOptions.stageAll} | ||
| */ | ||
| all: boolean; | ||
| /** | ||
| * Indicates whether to commit the changes. | ||
| * @default false | ||
| */ | ||
| commit: boolean | CommitOptions; | ||
| /** | ||
| * Specifies the path to the configuration file. | ||
| */ | ||
| config: string; | ||
| /** | ||
| * The directory path where the operation will be performed. | ||
| * @default process.cwd() | ||
| */ | ||
| cwd: string; | ||
| /** | ||
| * Enable debug log | ||
| */ | ||
| debug: boolean; | ||
| /** | ||
| * Whether the operation is being run in a dry-run mode (simulated execution). | ||
| */ | ||
| dry: boolean; | ||
| /** | ||
| * The list of directories to exclude from the search. | ||
| * @default ["node_modules", ".git", "target", "build", "dist"] | ||
| */ | ||
| exclude: string[]; | ||
| /** | ||
| * The command to execute before pushing. | ||
| */ | ||
| execute: string[]; | ||
| /** | ||
| * Specifies the main project category. | ||
| */ | ||
| main: ProjectCategory; | ||
| /** | ||
| * Whether push changes to remote and push options | ||
| * @default false | ||
| */ | ||
| push: boolean | PushOptions; | ||
| /** | ||
| * Specifies whether the operation should be performed recursively. | ||
| * @default false | ||
| */ | ||
| recursive: boolean; | ||
| /** | ||
| * Indicates whether to create a tag for a release. | ||
| * @default false | ||
| */ | ||
| tag: boolean | TagOptions; | ||
| /** | ||
| * The version string associated with the command or operation. | ||
| */ | ||
| version: string; | ||
| /** | ||
| * Whether all prompts requiring user input will be answered with "yes". | ||
| * @default false | ||
| */ | ||
| yes: boolean; | ||
| }; | ||
| type CommitOptions = { | ||
| /** | ||
| * Whether to stage all files or only modified files. | ||
| */ | ||
| stageAll?: boolean; | ||
| /** | ||
| * The template string for the commit message. if the template contains any "%s" placeholders, | ||
| * then they are replaced with the version number; | ||
| */ | ||
| template?: string; | ||
| /** | ||
| * Whether to enable git pre-commit and commit-msg hook. | ||
| * @default true | ||
| */ | ||
| verify?: boolean; | ||
| }; | ||
| type PushOptions = { | ||
| /** | ||
| * The branch name | ||
| */ | ||
| branch?: string; | ||
| /** | ||
| * Whether to follow tags | ||
| */ | ||
| followTags?: boolean; | ||
| /** | ||
| * The remote name | ||
| */ | ||
| remote?: string; | ||
| }; | ||
| type TagOptions = { | ||
| /** | ||
| * The template for tag name, same as @type {CommitOptions.template} | ||
| * if the template contains any "%s" placeholders, | ||
| * then they are replaced with the version number; | ||
| */ | ||
| template?: string; | ||
| }; | ||
| export type { Arrayable, CliPrimitive, CliReleaseOptions, CommitOptions, DefineConfigOptions, ExcludeType, ExtractBooleanKeys, KeysOf, PushOptions, ReleaseOptions, ReleaseOptionsDefault, ResolvedOptions, Status, TagOptions, Task }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
30377
1.31%816
4.75%13
18.18%1
Infinity%1
Infinity%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated
Updated
Updated