Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
794f2b3
ci: implement reusable npm-release-ops workflow and publish script
fqjony Jan 16, 2026
4d28835
feat(cli): implement generator with auto-detection and version pinning
fqjony Jan 16, 2026
3cd6f3a
test(cli): add unit tests and automated UX test suite
fqjony Jan 16, 2026
647258a
chore: standardize workflow metadata and documentation
fqjony Jan 16, 2026
c11e6ca
ci: reorganize internal workflows and add cli release dispatcher
fqjony Jan 16, 2026
dfd3b2b
chore: add .gitignore for node and temporary artifacts
fqjony Jan 16, 2026
2c16138
refactor: flatten internal workflows and consolidate CLI ops
fqjony Jan 16, 2026
32b5070
docs: add development and release guides
fqjony Jan 16, 2026
eabccc7
feat: add OIDC (keyless) publishing and environment support
fqjony Jan 16, 2026
cd97c47
docs(cli): add changes.md for version tracking
fqjony Jan 16, 2026
cb27801
test(cli): refactor UX tests to use file-based fixtures
fqjony Jan 16, 2026
d3a29ef
docs(cli): remove recommended tag from npx usage
fqjony Jan 16, 2026
77d6f26
fix(cli): resolve global installation path and bundle templates
fqjony Jan 16, 2026
0b5c889
fix(cli): add missing existsSync import and bump to 1.1.2
fqjony Jan 16, 2026
ccbdf50
feat(cli): finalize marker-free auto-presets and global standardization
fqjony Jan 16, 2026
f7e97dc
test: update ux tests for new docker presets
fqjony Jan 16, 2026
d6813da
feat(cli): refined docker presets and prompt flow v1.1.4
fqjony Jan 16, 2026
1894624
feat(cli): generalize ux tests and finalize v1.1.5
fqjony Jan 16, 2026
d966b55
docs: finalize READMEs for v1.1.5
fqjony Jan 16, 2026
cd67653
cli: Smart Prompt Skipping and Improved Interactive Registry Selection
fqjony Jan 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/_release-cli.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: CLI Ops
on:
push:
paths:
- 'cli/**'
- 'scripts/**'
- '.github/workflows/_release-cli.yml'
workflow_dispatch:

jobs:
release:
uses: ./.github/workflows/npm-release-ops.yml
with:
registry_url: https://registry.npmjs.org/
build_command: "" # CLI doesn't need build
test_command: "npm test"
working_directory: "cli"
environment: "Master"
secrets:
npm_token: ${{ secrets.npm_token }}
Comment on lines +12 to +20

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI about 1 month ago

To fix the problem, explicitly set permissions in this workflow, so the GITHUB_TOKEN used by the release job (and any called workflows) is constrained to the least privileges needed. Since this workflow’s job only delegates to npm-release-ops.yml and uses an npm token secret for publishing, a conservative default is to set contents: read at the workflow level. If the called workflow requires additional scopes (for example, to create releases or tags), those should be added there, but with only the snippet given we should not assume extra requirements.

The single best change, without altering existing functionality, is to add a root-level permissions block (aligned with on: and jobs:) specifying contents: read. This establishes a minimal default for all jobs in this workflow, including release, while still allowing the called workflow to further reduce permissions if it wants. We do not change any other lines or behavior.

Concretely, in .github/workflows/_release-cli.yml, after the on: block and before jobs:, insert:

permissions:
  contents: read

No additional methods, imports, or definitions are required since this is purely a YAML configuration change for GitHub Actions.

Suggested changeset 1
.github/workflows/_release-cli.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/_release-cli.yml b/.github/workflows/_release-cli.yml
--- a/.github/workflows/_release-cli.yml
+++ b/.github/workflows/_release-cli.yml
@@ -7,6 +7,9 @@
       - '.github/workflows/_release-cli.yml'
   workflow_dispatch:
 
+permissions:
+  contents: read
+
 jobs:
   release:
     uses: ./.github/workflows/npm-release-ops.yml
EOF
@@ -7,6 +7,9 @@
- '.github/workflows/_release-cli.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
release:
uses: ./.github/workflows/npm-release-ops.yml
Copilot is powered by AI and may make mistakes. Always verify output.
36 changes: 18 additions & 18 deletions .github/workflows/docker-ops.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,47 @@ on:
workflow_call:
inputs:
image_name:
description: "Primary identifier; must be lowercase, URL-safe, and matches your registry repository name"
description: "Docker image name (lowercase, URL-safe)"
required: true
type: string
gcp_region:
description: "GCP region for Artifact Registry (Required if publishing to GCP)"
description: "GCP: Region for Artifact Registry (Required if publishing to GCP)"
required: false
type: string
gcp_project_id:
description: "GCP project ID (Required if publishing to GCP)"
description: "GCP: Project ID (Required if publishing to GCP)"
required: false
type: string
gcp_repo:
description: "GCP Artifact Registry repository name (Required if publishing to GCP)"
description: "GCP: Artifact Registry repository name (Required if publishing to GCP)"
required: false
type: string
gcp_workload_identity_provider:
description: "GCP Workload Identity Provider (format: projects/NUM/locations/global/workloadIdentityPools/POOL/providers/PROV) (Required if publishing to GCP)"
description: "GCP: Workload Identity Provider (format: projects/NUM/locations/global/workloadIdentityPools/POOL/providers/PROV) (Required if publishing to GCP)"
required: false
type: string
gcp_service_account:
description: "GCP Service Account email for Workload Identity Federation (Required if publishing to GCP)"
description: "GCP: Service Account email for Workload Identity Federation (Required if publishing to GCP)"
required: false
type: string
acr_registry:
description: "Azure Container Registry name (e.g., myregistry.azurecr.io) (Required if publishing to ACR)"
description: "ACR: Registry name (e.g., myregistry.azurecr.io) (Required if publishing to ACR)"
required: false
type: string
acr_repository:
description: "ACR repository name (Required if publishing to ACR)"
description: "ACR: Repository name (Required if publishing to ACR)"
required: false
type: string
azure_client_id:
description: "Azure Client ID for OIDC authentication (Required if publishing to ACR)"
description: "ACR: Azure Client ID for OIDC authentication (Required if publishing to ACR)"
required: false
type: string
azure_tenant_id:
description: "Azure Tenant ID for OIDC authentication (Required if publishing to ACR)"
description: "ACR: Azure Tenant ID for OIDC authentication (Required if publishing to ACR)"
required: false
type: string
azure_subscription_id:
description: "Azure Subscription ID for OIDC authentication (Required if publishing to ACR)"
description: "ACR: Azure Subscription ID for OIDC authentication (Required if publishing to ACR)"
required: false
type: string
release_branch:
Expand All @@ -59,12 +59,12 @@ on:
default: "./Dockerfile"
type: string
build_platforms:
description: "Platforms to build for (comma-separated, e.g., linux/amd64,linux/arm64)"
description: "Platforms to build for (e.g., linux/amd64,linux/arm64). Note: multi-platform is primarily used for Docker Hub."
required: false
default: "linux/amd64,linux/arm64"
type: string
version_config_path:
description: "Path to GitVersion config file (only used if package.json is absent)"
description: "Path to GitVersion config file (used if package.json NOT defined)"
required: false
default: "ci/git-version.yml"
type: string
Expand All @@ -89,23 +89,23 @@ on:
default: "true"
type: string
docker_login:
description: "Docker Hub username (Required if publishing to Docker Hub)"
description: "Docker Hub: Username (Required if publishing to Docker Hub)"
required: false
type: string
docker_org:
description: "Docker Hub organization name (Required if publishing to Docker Hub)"
description: "Docker Hub: Organization name (Required if publishing to Docker Hub)"
required: false
type: string
docker_repo:
description: "Docker Hub repository name (Required if publishing to Docker Hub)"
description: "Docker Hub: Repository name (Required if publishing to Docker Hub)"
required: false
type: string
secrets:
docker_token:
description: "Docker Hub token for authentication (Required if publishing to Docker Hub)"
description: "Docker Hub: Token for authentication (Required if publishing to Docker Hub)"
required: false
slack_webhook_url:
description: "Slack webhook URL for notifications (Optional, for release notifications)"
description: "Slack: Webhook URL for notifications (Optional, for release notifications)"
required: false

jobs:
Expand Down
223 changes: 223 additions & 0 deletions .github/workflows/npm-release-ops.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
name: NPM Release

on:
workflow_call:
inputs:
node_version:
description: "Node.js: Version to use (default: 20)"
required: false
default: "20"
type: string
build_command:
description: "Build: Command to run before publishing (default: npm run build, set to empty to skip)"
required: false
default: "npm run build"
type: string
test_command:
description: "Test: Command to run before building (default: npm test, set to empty to skip)"
required: false
default: "npm test"
type: string
registry_url:
description: "NPM: Registry URL (default: https://registry.npmjs.org/)"
required: false
default: "https://registry.npmjs.org/"
type: string
provenance:
description: "NPM: Enable provenance for the release (default: true)"
required: false
default: true
type: boolean
release_branch:
description: "Branch: Deployment branch that triggers releases (default: latest)"
required: false
default: "latest"
type: string
enable_gh_release:
description: "GitHub: Whether to create a GitHub release (default: true)"
required: false
default: true
type: boolean
check_version_bump:
description: "NPM: Whether to check if the version is already published (default: true)"
required: false
default: true
type: boolean
working_directory:
description: "Common: Directory containing package.json"
required: false
type: string
default: "."
environment:
description: "GitHub: Environment to use for the release job (Optional)"
required: false
type: string
secrets:
npm_token:
description: "NPM: Token for publishing (Optional if OIDC/Keyless is configured)"
required: false
slack_webhook_url:
description: "Slack: Webhook URL for notifications (Optional)"
required: false

jobs:
config:
runs-on: ubuntu-latest
outputs:
current_branch: ${{ steps.branches.outputs.branch_name }}
is_release_branch: ${{ steps.branches.outputs.is_release_branch }}
release_version: ${{ steps.package_version.outputs.version }}
package_name: ${{ steps.package_version.outputs.name }}
has_slack_webhook: ${{ steps.check_slack.outputs.has_webhook }}
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Get Package Info
id: package_version
run: |
VERSION=$(node -p "require('./package.json').version")
NAME=$(node -p "require('./package.json').name")
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "name=$NAME" >> $GITHUB_OUTPUT
echo "📦 Package: $NAME@$VERSION"
working-directory: ${{ inputs.working_directory }}

- name: Determine Branch Information
id: branches
run: |
BRANCH_NAME=${GITHUB_REF#refs/heads/}
echo "branch_name=${BRANCH_NAME}" >> $GITHUB_OUTPUT

IS_RELEASE_BRANCH=$([[ "${BRANCH_NAME}" == "${{ inputs.release_branch }}" ]] && echo "true" || echo "false")
echo "is_release_branch=${IS_RELEASE_BRANCH}" >> $GITHUB_OUTPUT
echo "📌 Branch: ${BRANCH_NAME} (Release: ${IS_RELEASE_BRANCH})"

- name: Check Slack Webhook
id: check_slack
run: |
if [ -n "${{ secrets.slack_webhook_url }}" ]; then
echo "has_webhook=true" >> $GITHUB_OUTPUT
else
echo "has_webhook=false" >> $GITHUB_OUTPUT
fi

test:
Comment on lines +65 to +105

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI about 1 month ago

To fix the problem, add an explicit permissions block at the top level of the workflow so that all jobs without their own permissions (here: config and test) run with least-privilege GITHUB_TOKEN access. The release job already has its own permissions block, which will override the top-level one, so we leave it unchanged.

The best minimal, non‑functional change is:

  • Add permissions: contents: read near the top of .github/workflows/npm-release-ops.yml, alongside name and on.
  • This will ensure that config and test can still check out code (checkout requires contents: read) but won’t have unnecessary write permissions.
  • No imports, additional methods, or other file changes are required.

Concretely, insert:

permissions:
  contents: read

after the name: NPM Release line (or anywhere at the root level before jobs:).

Suggested changeset 1
.github/workflows/npm-release-ops.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/npm-release-ops.yml b/.github/workflows/npm-release-ops.yml
--- a/.github/workflows/npm-release-ops.yml
+++ b/.github/workflows/npm-release-ops.yml
@@ -1,5 +1,8 @@
 name: NPM Release
 
+permissions:
+  contents: read
+
 on:
   workflow_call:
     inputs:
EOF
@@ -1,5 +1,8 @@
name: NPM Release

permissions:
contents: read

on:
workflow_call:
inputs:
Copilot is powered by AI and may make mistakes. Always verify output.
needs: config
runs-on: ubuntu-latest
if: inputs.test_command != ''
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node_version }}
cache: 'npm'
cache-dependency-path: ${{ inputs.working_directory }}/package-lock.json

- name: Install dependencies
run: npm ci
working-directory: ${{ inputs.working_directory }}

- name: Run Tests
run: ${{ inputs.test_command }}
working-directory: ${{ inputs.working_directory }}

release:
Comment on lines +106 to +128

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI about 1 month ago

In general, the fix is to explicitly declare permissions for the workflow or for each job, granting only what is required. Here, the release job already has a tailored permissions block (contents: write, id-token: write). The config and test jobs only read repository contents and use secrets; they do not push changes or modify GitHub resources. So we should add a restrictive permissions block to those jobs (or at the workflow root) that limits GITHUB_TOKEN to read-only. To avoid altering existing behavior for the release job, we will set permissions: contents: read at the workflow root, so it applies to all jobs by default, while the existing release job permissions block will override this as needed.

Concretely, in .github/workflows/npm-release-ops.yml, insert a top-level permissions: block right after the name: NPM Release line. This block will specify contents: read, which is the minimal permission required for actions/checkout and reading the repository during config and test. We do not need to modify the release job’s permissions because job-level permissions override the workflow-level defaults. No additional imports or external libraries are needed since this is a YAML configuration change only.

Suggested changeset 1
.github/workflows/npm-release-ops.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/npm-release-ops.yml b/.github/workflows/npm-release-ops.yml
--- a/.github/workflows/npm-release-ops.yml
+++ b/.github/workflows/npm-release-ops.yml
@@ -1,4 +1,6 @@
 name: NPM Release
+permissions:
+  contents: read
 
 on:
   workflow_call:
EOF
@@ -1,4 +1,6 @@
name: NPM Release
permissions:
contents: read

on:
workflow_call:
Copilot is powered by AI and may make mistakes. Always verify output.
needs: [config, test]
# Only run release on the designated release branch
if: |
always() &&
needs.config.outputs.is_release_branch == 'true' &&
(needs.test.result == 'success' || needs.test.result == 'skipped')
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
permissions:
contents: write
id-token: write # Required for provenance
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node_version }}
registry-url: ${{ inputs.registry_url }}
cache: 'npm'
cache-dependency-path: ${{ inputs.working_directory }}/package-lock.json

- name: Publish to NPM
env:
NODE_AUTH_TOKEN: ${{ secrets.npm_token }}
REGISTRY_URL: ${{ inputs.registry_url }}
PROVENANCE: ${{ inputs.provenance }}
BUILD_COMMAND: ${{ inputs.build_command }}
CHECK_VERSION_BUMP: ${{ inputs.check_version_bump }}
NPM_TAG: ${{ inputs.release_branch }}
WORKING_DIR: ${{ inputs.working_directory }}
run: ./scripts/npm-publish.sh

- name: Parse Changelog
if: inputs.enable_gh_release == true
id: changelog
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const version = '${{ needs.config.outputs.release_version }}';
let description = 'Release ' + version;

if (fs.existsSync('./changes.md')) {
const content = fs.readFileSync('./changes.md', 'utf8');
const lines = content.split('\n');
const startMarker = `### ${version}`;
let found = false;
let body = [];

for (const line of lines) {
if (line.startsWith(startMarker)) {
found = true;
continue;
}
if (found) {
if (line.startsWith('### ')) break;
body.push(line);
}
}
if (body.length > 0) {
description = body.join('\n').trim();
}
}
core.setOutput('description', description);

- name: Create GitHub Release
if: inputs.enable_gh_release == true
uses: softprops/action-gh-release@v2
with:
name: "Release ${{ needs.config.outputs.release_version }}"
tag_name: "v${{ needs.config.outputs.release_version }}"
body: ${{ steps.changelog.outputs.description }}
token: ${{ github.token }}

- name: Notify Slack
if: always() && needs.config.outputs.has_slack_webhook == 'true'
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
"text": "NPM Release for ${{ needs.config.outputs.package_name }}: ${{ job.status == 'success' && '✅ SUCCESS' || '❌ FAILED' }}",
"attachments": [
{
"color": "${{ job.status == 'success' && '#36a64f' || '#ec000c' }}",
"fields": [
{ "title": "Version", "value": "${{ needs.config.outputs.release_version }}", "short": true },
{ "title": "Commit", "value": "${{ github.sha }}", "short": true }
]
}
]
}
env:
SLACK_WEB_HOOK_URL: ${{ secrets.slack_webhook_url }}
7 changes: 4 additions & 3 deletions .github/workflows/wp-gh-release-ops.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ on:
workflow_call:
inputs:
tag:
description: 'Release tag (e.g. 1.2.3a)'
description: 'GitHub: Release tag (e.g. 1.2.3a)'
required: true
default: '1.2.3'
type: string
version:
description: 'Release version (e.g. 1.2.3), default: latest'
description: 'WordPress: Release version (e.g. 1.2.3), default: latest'
required: false
type: string
prerelease:
description: 'Pre-release version (e.g. RC1, beta, etc...)'
description: 'GitHub: Pre-release version (e.g. RC1, beta, etc...)'
required: false
type: string

Expand Down
26 changes: 26 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
.npm/

# OS
.DS_Store
Thumbs.db

# IDE
.vscode/
.idea/
*.swp
*.swo

# Project
cli/test/ux-temp/
SETUP-*.md

# CLI bundled assets (copied during publish)
cli/.github/
cli/docs/
cli/examples/
Loading