Skip to content
Merged
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
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,45 @@ jobs:
category: apm-audit
```

## Private repo authentication

By default, `github-token` (which defaults to `${{ github.token }}`) is automatically forwarded to APM as `GITHUB_APM_PAT`. This means same-org private repos work with zero config.

```yaml
# Same-org private repos: zero config
- uses: microsoft/apm-action@v1
```

For cross-org private repos, pass a PAT with broader scope via the `github-token` input:

```yaml
# Cross-org private repos: pass a broader-scoped PAT
- uses: microsoft/apm-action@v1
with:
github-token: ${{ secrets.APM_PAT }}
```

For multi-org or multi-platform scenarios, use the `env:` block for full control. An explicit `GITHUB_APM_PAT` in `env:` always wins over the auto-forwarded value:

```yaml
# Multi-org / multi-platform: full control via env block
- uses: microsoft/apm-action@v1
env:
GITHUB_APM_PAT: ${{ secrets.APM_PAT }}
GITHUB_APM_PAT_CONTOSO: ${{ secrets.APM_PAT_CONTOSO }}
ADO_APM_PAT: ${{ secrets.ADO_PAT }}
ARTIFACTORY_APM_TOKEN: ${{ secrets.ARTIFACTORY_TOKEN }}
```

> **Note:** GitHub Actions forbids secrets named with the `GITHUB_` prefix, so you cannot create a secret called `GITHUB_APM_PAT` directly. The auto-forward from `github-token` covers the common case. For cross-org tokens, name your secret something like `APM_PAT` and pass it via `github-token` or `env: GITHUB_APM_PAT`.

## Inputs

| Input | Required | Default | Description |
|---|---|---|---|
| `working-directory` | No | `.` | Working directory for execution. Must exist in non-isolated mode (with your `apm.yml`). In `isolated`, `pack`, or `bundle` modes the directory is created automatically. |
| `apm-version` | No | `latest` | APM version to install |
| `github-token` | No | `${{ github.token }}` | GitHub token for API calls. Auto-forwarded as `GITHUB_APM_PAT` so same-org private repos work with zero config. Pass a broader-scoped PAT for cross-org access. |
| `script` | No | | APM script to run after install |
| `dependencies` | No | | YAML array of extra dependencies to install (additive to apm.yml) |
| `isolated` | No | `false` | Ignore apm.yml and clear pre-existing primitive dirs — install only inline dependencies |
Expand Down
7 changes: 4 additions & 3 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41181,15 +41181,16 @@ async function run() {
const packInput = getInput('pack') === 'true';
const isolated = getInput('isolated') === 'true';
const auditReportInput = getInput('audit-report').trim();
// Pass github-token input to APM subprocess as GITHUB_TOKEN.
// Pass github-token input to APM subprocess as GITHUB_TOKEN and GITHUB_APM_PAT.
// GitHub Actions does not auto-export input values as env vars —
// without this, APM runs unauthenticated (rate-limited, no private repo access).
// Use ??= so a GITHUB_TOKEN already in the environment (e.g., a PAT set via
// job-level `env:`) is not clobbered by the action's default github.token.
// Use ??= so values already in the environment (e.g., a PAT set via job-level
// `env:`) are not clobbered by the action's default github.token.
const githubToken = getInput('github-token');
if (githubToken) {
core_setSecret(githubToken);
process.env.GITHUB_TOKEN ??= githubToken;
process.env.GITHUB_APM_PAT ??= githubToken;
}
// Validate inputs before touching the filesystem.
if (bundleInput && packInput) {
Expand Down
60 changes: 57 additions & 3 deletions src/__tests__/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,13 +365,15 @@ describe('run', () => {
expect(mockSetOutput).not.toHaveBeenCalledWith('audit-report-path', expect.anything());
});

it('passes github-token input as GITHUB_TOKEN env var', async () => {
it('passes github-token input as GITHUB_TOKEN and GITHUB_APM_PAT env vars', async () => {
fs.writeFileSync(path.join(tmpDir, 'apm.yml'), 'name: test\nversion: 1.0.0\n');
fs.mkdirSync(path.join(tmpDir, '.github'), { recursive: true });
mockExec.mockResolvedValue(0);

const prevToken = process.env.GITHUB_TOKEN;
const prevApmPat = process.env.GITHUB_APM_PAT;
delete process.env.GITHUB_TOKEN;
delete process.env.GITHUB_APM_PAT;

try {
mockGetInput.mockImplementation((name: unknown) => {
Expand All @@ -394,6 +396,7 @@ describe('run', () => {
expect(mockSetFailed).not.toHaveBeenCalled();
// Token should be set in process.env for subprocess inheritance
expect(process.env.GITHUB_TOKEN).toBe('ghs_fakeToken123');
expect(process.env.GITHUB_APM_PAT).toBe('ghs_fakeToken123');
// Token should be masked in logs
expect(mockSetSecret).toHaveBeenCalledWith('ghs_fakeToken123');
} finally {
Expand All @@ -402,16 +405,23 @@ describe('run', () => {
} else {
process.env.GITHUB_TOKEN = prevToken;
}
if (prevApmPat === undefined) {
delete process.env.GITHUB_APM_PAT;
} else {
process.env.GITHUB_APM_PAT = prevApmPat;
}
}
});

it('does not set GITHUB_TOKEN when github-token input is empty', async () => {
it('does not set GITHUB_TOKEN or GITHUB_APM_PAT when github-token input is empty', async () => {
fs.writeFileSync(path.join(tmpDir, 'apm.yml'), 'name: test\nversion: 1.0.0\n');
fs.mkdirSync(path.join(tmpDir, '.github'), { recursive: true });
mockExec.mockResolvedValue(0);

const prevToken = process.env.GITHUB_TOKEN;
const prevApmPat = process.env.GITHUB_APM_PAT;
delete process.env.GITHUB_TOKEN;
delete process.env.GITHUB_APM_PAT;

try {
mockGetInput.mockImplementation((name: unknown) => {
Expand All @@ -432,15 +442,21 @@ describe('run', () => {
await run();

expect(mockSetFailed).not.toHaveBeenCalled();
// Token should NOT be set when input is empty
// Tokens should NOT be set when input is empty
expect(process.env.GITHUB_TOKEN).toBeUndefined();
expect(process.env.GITHUB_APM_PAT).toBeUndefined();
expect(mockSetSecret).not.toHaveBeenCalled();
} finally {
if (prevToken === undefined) {
delete process.env.GITHUB_TOKEN;
} else {
process.env.GITHUB_TOKEN = prevToken;
}
if (prevApmPat === undefined) {
delete process.env.GITHUB_APM_PAT;
} else {
process.env.GITHUB_APM_PAT = prevApmPat;
}
}
});

Expand Down Expand Up @@ -481,4 +497,42 @@ describe('run', () => {
}
}
});

it('does not clobber existing GITHUB_APM_PAT from job-level env', async () => {
fs.writeFileSync(path.join(tmpDir, 'apm.yml'), 'name: test\nversion: 1.0.0\n');
fs.mkdirSync(path.join(tmpDir, '.github'), { recursive: true });
mockExec.mockResolvedValue(0);

const prevApmPat = process.env.GITHUB_APM_PAT;
process.env.GITHUB_APM_PAT = 'ghp_userProvidedApmPAT';

try {
mockGetInput.mockImplementation((name: unknown) => {
switch (name) {
case 'working-directory': return tmpDir;
case 'dependencies': return '';
case 'isolated': return 'false';
case 'bundle': return '';
case 'pack': return 'false';
case 'compile': return 'false';
case 'script': return '';
case 'audit-report': return '';
case 'github-token': return 'ghs_defaultActionToken';
default: return '';
}
});

await run();

expect(mockSetFailed).not.toHaveBeenCalled();
// User's explicitly-set GITHUB_APM_PAT should be preserved
expect(process.env.GITHUB_APM_PAT).toBe('ghp_userProvidedApmPAT');
} finally {
if (prevApmPat === undefined) {
delete process.env.GITHUB_APM_PAT;
} else {
process.env.GITHUB_APM_PAT = prevApmPat;
}
}
});
});
7 changes: 4 additions & 3 deletions src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@ export async function run(): Promise<void> {
const isolated = core.getInput('isolated') === 'true';
const auditReportInput = core.getInput('audit-report').trim();

// Pass github-token input to APM subprocess as GITHUB_TOKEN.
// Pass github-token input to APM subprocess as GITHUB_TOKEN and GITHUB_APM_PAT.
// GitHub Actions does not auto-export input values as env vars —
// without this, APM runs unauthenticated (rate-limited, no private repo access).
// Use ??= so a GITHUB_TOKEN already in the environment (e.g., a PAT set via
// job-level `env:`) is not clobbered by the action's default github.token.
// Use ??= so values already in the environment (e.g., a PAT set via job-level
// `env:`) are not clobbered by the action's default github.token.
const githubToken = core.getInput('github-token');
if (githubToken) {
core.setSecret(githubToken);
process.env.GITHUB_TOKEN ??= githubToken;
process.env.GITHUB_APM_PAT ??= githubToken;
}

// Validate inputs before touching the filesystem.
Expand Down
Loading