diff --git a/AI_AUTOMATION.md b/AI_AUTOMATION.md new file mode 100644 index 0000000..cf62daf --- /dev/null +++ b/AI_AUTOMATION.md @@ -0,0 +1,100 @@ +# AI Automation Support + +This document describes the AI automation features added to quick-publish. + +## New CLI Options + +### `--dry-run` +Shows what commands would be executed without actually running them. Perfect for AI agents to understand what will happen before execution. + +```bash +# See what would happen with a patch release +quick-publish --dry-run --release-type patch + +# Preview a custom version release +quick-publish --dry-run --release-type custom --version 1.2.3 --tag beta +``` + +### `--yes` +Skips all interactive prompts and uses provided or default values. Enables fully automated execution. + +```bash +# Automated patch release +quick-publish --yes --release-type patch + +# Automated custom release +quick-publish --yes --release-type custom --version 1.2.3 --tag latest +``` + +### Version and Tag Options + +- `--release-type `: `patch`, `minor`, `major`, `prerelease`, or `custom` +- `--version `: Custom version (required when `--release-type=custom`) +- `--tag `: NPM tag (`latest`, `next`, `beta`, or custom tag name) + +## AI Usage Examples + +### 1. Preview Changes (Dry Run) +```bash +# AI can run this to see what would happen +quick-publish --dry-run --release-type patch +``` + +Output: +``` +[DRY RUN] Would select version: 0.7.2, tag: latest +[DRY RUN] Would ask: Continue to publish `0.7.2` with tag `latest`? (auto-yes in automated mode) + +[DRY RUN] Publish workflow for version 0.7.2 with tag latest: +[DRY RUN] Would execute: npm version 0.7.2 +[DRY RUN] Would execute: /path/to/conventional-changelog -p angular -r 2 -i CHANGELOG.md -s +[DRY RUN] Would execute: npm publish --tag=latest +[DRY RUN] Would execute: git add CHANGELOG.md +[DRY RUN] Would execute: git commit -m chore%3A%20changelog%200.7.2 +[DRY RUN] Would execute: git push +[DRY RUN] Would execute: git push origin refs/tags/v0.7.2 + +[DRY RUN] Publish workflow completed. Use --yes to execute. +``` + +### 2. Automated Execution +```bash +# AI can run this after confirming with dry-run +quick-publish --yes --release-type patch +``` + +### 3. Custom Version Release +```bash +# For specific version requirements +quick-publish --yes --release-type custom --version 1.0.0 --tag latest +``` + +### 4. Beta Release +```bash +# For pre-release versions +quick-publish --yes --release-type prerelease --tag beta +``` + +## AI Workflow Recommendation + +1. **Analysis Phase**: AI runs with `--dry-run` to understand what will happen +2. **Confirmation Phase**: AI presents the planned actions to user +3. **Execution Phase**: AI runs with `--yes` and appropriate parameters + +```bash +# Step 1: Analyze +quick-publish --dry-run --release-type patch + +# Step 2: Execute (after user confirmation) +quick-publish --yes --release-type patch +``` + +## Error Handling + +- Missing `--version` with `--release-type=custom` will throw an error +- Invalid release types will be rejected +- All validation happens before any commands are executed + +## Backward Compatibility + +All existing functionality remains unchanged. The new options are additive and don't affect the interactive mode when not specified. diff --git a/src/cli.ts b/src/cli.ts old mode 100755 new mode 100644 index 11650d3..1e7c224 --- a/src/cli.ts +++ b/src/cli.ts @@ -18,6 +18,26 @@ export function bootstrapCli() { '--push', 'Execute git push & tag push to remote git origin, defaults to `true`', ) + .option( + '--dry-run', + 'Show what would be executed without actually running commands', + ) + .option( + '--yes', + 'Skip all prompts and use default values for automated execution', + ) + .option( + '--release-type ', + 'Release type: patch, minor, major, prerelease, or custom', + ) + .option( + '--version ', + 'Custom version (required when --release-type=custom)', + ) + .option( + '--tag ', + 'NPM tag: latest, next, beta, or custom tag name', + ) .action(opts => { publish(opts); }); diff --git a/src/index.ts b/src/index.ts old mode 100755 new mode 100644 index e83af23..bdfb1b2 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,8 @@ export async function publish(opts: IOptions = {}) { opts = { depcost: false, push: true, + dryRun: false, + yes: false, ...opts, }; @@ -24,36 +26,61 @@ export async function publish(opts: IOptions = {}) { */ const { version, tag } = await selectVersionAndTag( require(`${process.cwd()}/package.json`).version, + { + yes: opts.yes, + dryRun: opts.dryRun, + releaseType: opts.releaseType, + version: opts.version, + tag: opts.tag, + }, ); /** - * 2. Double check. + * 2. Double check (skip in automated mode). */ - const { continueTo } = await inquirer.prompt<{ - continueTo: boolean; - }>({ - type: 'confirm', - name: 'continueTo', - message: `Continue to publish \`${version}\` with tag \`${tag}\`?`, - }); + let continueTo = true; + if (!opts.yes && !opts.dryRun) { + const result = await inquirer.prompt<{ + continueTo: boolean; + }>({ + type: 'confirm', + name: 'continueTo', + message: `Continue to publish \`${version}\` with tag \`${tag}\`?`, + }); + continueTo = result.continueTo; + } else if (opts.dryRun) { + console.log(`[DRY RUN] Would ask: Continue to publish \`${version}\` with tag \`${tag}\`? (auto-yes in automated mode)`); + } /** * 3. Publish workflow. */ if (continueTo) { - await exec(COMMANDS.bumpVersion(version)); - await exec(COMMANDS.changelog()); - await exec(COMMANDS.npmPublish(tag)); - await exec(COMMANDS.gitAdd('CHANGELOG.md')); - await exec(COMMANDS.gitCommit(`chore: changelog ${version}`)); + if (opts.dryRun) { + console.log(`\n[DRY RUN] Publish workflow for version ${version} with tag ${tag}:`); + } + + await exec(COMMANDS.bumpVersion(version), opts.dryRun); + await exec(COMMANDS.changelog(), opts.dryRun); + await exec(COMMANDS.npmPublish(tag), opts.dryRun); + await exec(COMMANDS.gitAdd('CHANGELOG.md'), opts.dryRun); + await exec(COMMANDS.gitCommit(`chore: changelog ${version}`), opts.dryRun); + if (opts.depcost) { - await exec(COMMANDS.depcost()); - await exec(COMMANDS.gitAdd('DEPCOST.md')); - await exec(COMMANDS.gitCommit(`chore: DEPCOST.md ${version}`)); + await exec(COMMANDS.depcost(), opts.dryRun); + await exec(COMMANDS.gitAdd('DEPCOST.md'), opts.dryRun); + await exec(COMMANDS.gitCommit(`chore: DEPCOST.md ${version}`), opts.dryRun); } + if (opts.push) { - await exec(COMMANDS.gitPush()); - await exec(COMMANDS.gitPushTag(`v${version}`)); + await exec(COMMANDS.gitPush(), opts.dryRun); + await exec(COMMANDS.gitPushTag(`v${version}`), opts.dryRun); + } + + if (opts.dryRun) { + console.log(`\n[DRY RUN] Publish workflow completed. Use --yes to execute.`); + } else { + console.log(`\nPublish completed: ${version} with tag ${tag}`); } } else { console.log('Publish cancelled'); diff --git a/src/select-version.ts b/src/select-version.ts old mode 100755 new mode 100644 index 737b9c5..96fd8c6 --- a/src/select-version.ts +++ b/src/select-version.ts @@ -10,6 +10,13 @@ import type { ISelectVersionAndTagResult, VersionCandidate, IPromptAnswers, Rele */ export async function selectVersionAndTag( currentVersion: string, + opts: { + yes?: boolean; + dryRun?: boolean; + releaseType?: string; + version?: string; + tag?: string; + } = {}, ): Promise { const customItem = { name: 'Custom', value: 'custom' }; @@ -56,6 +63,34 @@ export async function selectVersionAndTag( return Boolean(semver.prerelease(version)); } + // Handle automated execution + if (opts.yes || opts.dryRun) { + const releaseType = (opts.releaseType as ReleaseType) || (isPreRelease(currentVersion) ? 'prerelease' : 'patch'); + let version: string; + + if (releaseType === 'custom') { + if (!opts.version) { + throw new Error('--version is required when --release-type=custom'); + } + version = opts.version; + } else { + version = versionCandidate[releaseType]!; + } + + const npmTags = getNpmTags(version); + let tag = opts.tag; + if (!tag) { + const firstTag = npmTags[0]; + tag = typeof firstTag === 'string' ? firstTag : firstTag.value; + } + + if (opts.dryRun) { + console.log(`[DRY RUN] Would select version: ${version}, tag: ${tag}`); + } + + return { version, tag }; + } + const bumpChoices = releaseTypes.map(b => ({ name: `${b} (${versionCandidate[b]})`, value: b, diff --git a/src/shared.ts b/src/shared.ts index a5c9eb9..4bfd724 100644 --- a/src/shared.ts +++ b/src/shared.ts @@ -7,9 +7,15 @@ import execa from 'execa'; * Execute a inline command. * * @param command + * @param dryRun - If true, only log the command without executing * @returns */ -export function exec(command: string) { +export function exec(command: string, dryRun: boolean = false) { + if (dryRun) { + console.log(`[DRY RUN] Would execute: ${command}`); + return Promise.resolve({ stdout: '', stderr: '', exitCode: 0 }); + } + const splits = command.split(' '); return execa( splits[0], diff --git a/src/types.ts b/src/types.ts index 6a00944..f91c5e5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,6 +23,26 @@ export interface IOptions { * Execute git push & tag push to remote git origin, defaults to `true` */ push?: boolean; + /** + * Show what would be executed without actually running commands + */ + dryRun?: boolean; + /** + * Skip all prompts and use default values for automated execution + */ + yes?: boolean; + /** + * Release type: patch, minor, major, prerelease, or custom + */ + releaseType?: ReleaseType | CustomReleaseType; + /** + * Custom version (required when releaseType=custom) + */ + version?: string; + /** + * NPM tag: latest, next, beta, or custom tag name + */ + tag?: string; } /** * Definition of versions candidate. diff --git a/tsconfig.json b/tsconfig.json old mode 100755 new mode 100644 index d9f3d4c..6f54dd1 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,8 +29,10 @@ "downlevelIteration": true, "lib": [ "es5", - "es6" - ] + "es6", + "dom" + ], + "types": ["node"] }, "include": [ "src", @@ -39,4 +41,4 @@ "exclude": [ "node_modules" ] -} \ No newline at end of file +}