🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

cross-release-cli

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cross-release-cli - npm Package Compare versions

Comparing version
0.1.0
to
0.2.0
+114
dist/types.d-PDBL5zNm.d.ts
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 };
+1
-1

@@ -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()

@@ -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 };

@@ -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 };

@@ -1,7 +0,7 @@

// src/index.ts
//#region src/index.ts
function defineConfig(config) {
return config;
return config;
}
export {
defineConfig
};
//#endregion
export { defineConfig };
{
"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"
}
}
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 };