Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
97 changes: 97 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: NPM Release Workflow

on:
workflow_call:
inputs:
ref:
description: "The branch or tag ref to checkout. Defaults to the default branch."
required: false
default: ${{ github.ref }}
type: string
sha:
description: "The commit SHA to checkout. Defaults to the SHA of the ref."
required: false
default: ${{ github.sha }}
type: string
repository:
description: "The repository to checkout. Defaults to the current repository."
required: false
default: ${{ github.repository }}
type: string
token:
description: "The token to use for authentication. Defaults to the token of the current workflow run."
required: false
default: ${{ github.token }}
type: string
secrets:
RELEASE_TOKEN:
required: true
repository_dispatch:
types: [semantic-release]

concurrency:
group: ci-${{ inputs.sha }}
cancel-in-progress: true

permissions:
actions: write
contents: write # to be able to publish a GitHub release
issues: write # to be able to comment on released issues
pull-requests: write # to be able to comment on released pull requests
id-token: write # to enable use of OIDC for npm provenance

jobs:
deploy:
name: Deploy NPM build
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
SKIP_PREFLIGHT_CHECK: true
steps:
- uses: actions/checkout@v6.0.1
with:
ref: ${{ inputs.sha }}
token: ${{ secrets.RELEASE_TOKEN }}

- name: Install compatible Nodejs version
id: setup-node
uses: ./.github/actions/setup-node

- name: Configure PATH
run: |
mkdir -p "$HOME/.local/bin"
echo "$HOME/.local/bin" >> "${GITHUB_PATH}"
echo "HOME=$HOME" >> "${GITHUB_ENV}"

- name: Configure Git
run: |
git config --global user.email "${{ github.event.pusher.email || 'stack@bitflight.io' }}"
git config --global user.name "${{ github.event.pusher.name || 'GitHub[bot]' }}"
git fetch --tags
git status --porcelain -u

- name: Install Deps
id: deps
run: |
npm ci

- name: Ensure dependencies are compatible with the version of node
run: npx --yes ls-engines

- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
run: npm audit signatures

- run: npm run build --if-present

- run: |
git add -f dist
npm run generate-docs
git commit -n -m 'build(release): bundle distribution files'

- name: Setup Node 24 for semantic-release
uses: actions/setup-node@v6.1.0
with:
node-version: "24.x"

- run: npx --yes semantic-release@latest
80 changes: 80 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Tag and Release Updated NPM Package

on:
pull_request_target:
push:
branches:
- main
- next
- beta
- "*.x"
repository_dispatch:
types: [semantic-release]

concurrency:
group: ci-${{ github.event.pull_request.number }}${{ github.ref }}${{ github.workflow }}
cancel-in-progress: true

permissions:
issues: write
pull-requests: write
statuses: write
checks: write
actions: write
id-token: write
contents: write

jobs:
run-tests:
name: Run unit tests (Node ${{ matrix.node-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: ["20.0.0", "20.17.0", "24.x"]
env:
SKIP_PREFLIGHT_CHECK: true
steps:
- uses: actions/checkout@v6.0.1
with:
ref: ${{ github.head_ref || github.ref }}

- name: Install compatible Nodejs version
id: setup-node
uses: ./.github/actions/setup-node
with:
version: ${{ matrix.node-version }}

- name: Configure PATH
run: |
mkdir -p "$HOME/.local/bin"
echo "$HOME/.local/bin" >> "${GITHUB_PATH}"
echo "HOME=$HOME" >> "${GITHUB_ENV}"

- run: npm install
- run: npm run test
- run: npm run coverage
- name: "Report Coverage"
if: always()
continue-on-error: true
uses: davelosert/vitest-coverage-report-action@v2.9.0
with:
json-summary-path: "./out/coverage-summary.json"
json-final-path: ./out/coverage-final.json
- run: npm run build
- run: npm run generate-docs
call-workflow-passing-data:
needs: run-tests
if: ${{ github.event_name == 'push' }}
permissions:
issues: write
pull-requests: write
statuses: write
checks: write
actions: write
id-token: write
contents: write
uses: ./.github/workflows/deploy.yml
with:
ref: ${{ github.head_ref || github.ref }}
secrets: inherit
14 changes: 10 additions & 4 deletions __tests__/inputs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ describe('inputs', () => {
vi.stubEnv('INPUT_ACTION', 'testaction');

expect(() => loadRequiredConfig(log, config)).toThrowError(
/Missing required keys: paths:action, paths:readme, owner, repo/,
/Missing required keys: owner, repo/,
);
});

Expand Down Expand Up @@ -242,22 +242,28 @@ describe('inputs', () => {
test('Inputs stringify', async ({ task }) => {
const log = new LogTask(task.name);
const { default: Action } = await import('../src/Action.js');

// Clear beforeEach env vars and set test-specific ones
vi.unstubAllEnvs();
vi.stubEnv('DEBUG', 'true');
const action = new Action(actTestYmlPath);
const sections = ['usage'] as ReadmeSection[];
vi.stubEnv('INPUT_OWNER', 'stringowner');
vi.stubEnv('INPUT_REPO', 'stringrepo');
vi.stubEnv('INPUT_README', 'stringreadme');
vi.stubEnv('INPUT_ACTION', 'stringaction');
vi.stubEnv('GITHUB_REPOSITORY', ''); // Prevent fallback
vi.stubEnv('GITHUB_EVENT_PATH', ''); // Prevent payload file read

const action = new Action(actTestYmlPath);
const sections = ['usage'] as ReadmeSection[];
const providedInputContext: InputContext = {
action,
sections,
configPath: '/tmp/nonexistent.json', // Prevent loading .ghadocs.json
};

const inputs = new Inputs(providedInputContext, log);
const result = inputs.stringify();
expect(typeof result).toBe('string');

expect(result).toMatch(/owner: stringowner/);
expect(result).toMatch(/repo: stringrepo/);
expect(result).toMatch(/sections:\n {2}- usage/);
Expand Down
35 changes: 23 additions & 12 deletions src/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ import { fileURLToPath } from 'node:url';

import * as core from '@actions/core';
import { Context } from '@actions/github/lib/context.js';
import { IOptions, Provider } from 'nconf';
import nconf from 'nconf';
import YAML from 'yaml';

import Action, { Input } from './Action.js';
import { configFileName, ConfigKeys, README_SECTIONS, ReadmeSection } from './constants.js';
import { repositoryFinder } from './helpers.js';
import LogTask from './logtask/index.js';
import ReadmeEditor from './readme-editor.js';
// import workingDirectory from './working-directory.js';

const { Provider } = nconf;
type IOptions = nconf.IOptions;

/**
* Get the filename from the import.meta.url
Expand Down Expand Up @@ -302,10 +304,6 @@ export function transformGitHubInputsToArgv(
log.debug(`Parsing input: ${obj.key} with ith value: ${obj.value}`);
const keyParsed = obj.key.replace(/^(INPUT|input)_/, '').toLocaleLowerCase();
const key = ConfigKeysInputsMap[keyParsed] || keyParsed;
// eslint-disable-next-line no-param-reassign
obj.key = key;
// TODO: This is a hack to get around the fact that nconf doesn't support just returning the new value like its documentation says.
config.set(key, obj.value);

log.debug(`New input is ${key} with the value ${obj.value}`);
return { key, value: obj.value };
Expand Down Expand Up @@ -356,6 +354,9 @@ export function collectAllDefaultValuesFromAction(
} = {},
): IOptions {
log.debug('Collecting default values from action.yml');
// This loads the defaults from THIS action's own action.yml file (github-action-readme-generator's action.yml)
// NOT the user's action.yml file (which is loaded separately via the 'action' input parameter)
// Therefore, we use __dirname to find this package's action.yml regardless of where it's installed
const thisActionPath = path.join(__dirname, providedMetaActionPath ?? metaActionPath);
try {
const defaultValues = {} as IOptions;
Expand All @@ -373,7 +374,12 @@ export function collectAllDefaultValuesFromAction(
log.debug(JSON.stringify(defaultValues, null, 2));
return defaultValues;
} catch (error) {
throw new Error(`failed to load defaults from this action's action.yml: ${error}`);
// When running as a CLI tool (e.g., via npx or yarn dlx), the tool's own action.yml
// may not be present in the node_modules. This is expected behavior, as the tool
// should still work to generate documentation for other actions.
log.debug(`Could not load defaults from this tool's action.yml at ${thisActionPath}: ${error}`);
log.debug('Continuing without default values from action.yml');
return {} as IOptions;
}
}

Expand All @@ -400,16 +406,17 @@ export function loadConfig(
log.debug(`Config file not found: ${configFilePath}`);
}
}

config
.env({
lowerCase: true,
parseValues: true,
match: /^(INPUT|input)_[A-Z_a-z]\w*$/,
transform: (obj: KVPairType): undefined | KVPairType => {
return transformGitHubInputsToArgv(log, config, obj);
},
})
.argv(argvOptions);

return config;
}

Expand All @@ -427,10 +434,14 @@ export function loadDefaultConfig(
log.debug('Loading default config');
const defaultValues = collectAllDefaultValuesFromAction(log);
const context = providedContext ?? new Context();
const repositoryDetail = repositoryFinder(
`${process.env.INPUT_OWNER ?? ''}/${process.env.INPUT_REPO ?? ''}`,
context,
);

// Get owner/repo from config (which includes CLI args), falling back to env vars for GitHub Actions
const ownerFromConfig = config.get('owner') as string | undefined;
const repoFromConfig = config.get('repo') as string | undefined;
const ownerInput = ownerFromConfig ?? process.env.INPUT_OWNER ?? '';
const repoInput = repoFromConfig ?? process.env.INPUT_REPO ?? '';

const repositoryDetail = repositoryFinder(`${ownerInput}/${repoInput}`, context);
log.debug(`repositoryDetail: ${repositoryDetail}`);
// Apply the default values from the action.yml file
return config.defaults({
Expand Down