@precisa-saude/cli 1.0.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +41 -12
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +28 -9
- package/dist/index.js.map +1 -1
- package/dist/templates/.claude/agents/debugger.md +130 -0
- package/dist/templates/.claude/agents/docs-sync.md +97 -0
- package/dist/templates/.claude/agents/pr-review-responder.md +117 -0
- package/dist/templates/.claude/agents/pre-commit-check.md +85 -0
- package/dist/templates/.claude/agents/refactor-scout.md +139 -0
- package/dist/templates/AGENTS.md +45 -0
- package/dist/templates/CLAUDE.md +9 -0
- package/dist/templates/commitlintrc.cjs +10 -0
- package/dist/templates/eslint.config.js +19 -0
- package/dist/templates/github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/dist/templates/github/ISSUE_TEMPLATE/config.yml +4 -4
- package/dist/templates/github/ISSUE_TEMPLATE/feature_request.md +18 -0
- package/dist/templates/github/PULL_REQUEST_TEMPLATE.md +10 -10
- package/dist/templates/github/workflows/_checks.yml +102 -0
- package/dist/templates/github/workflows/_deploy-site.yml +168 -0
- package/dist/templates/github/workflows/_publish.yml +77 -0
- package/dist/templates/github/workflows/_release.yml +156 -0
- package/dist/templates/github/workflows/ci.yml +63 -27
- package/dist/templates/github/workflows/doctor.yml +71 -0
- package/dist/templates/github/workflows/publish-tag.yml +71 -0
- package/dist/templates/github/workflows/release.yml +22 -8
- package/dist/templates/github/workflows/review.yml +75 -23
- package/dist/templates/governance/CITATION.cff +1 -1
- package/dist/templates/governance/CONTRIBUTING.md +14 -14
- package/dist/templates/governance/CONVENTIONS.md +21 -19
- package/dist/templates/governance/SECURITY.md +13 -13
- package/dist/templates/governance/SUPPORT.md +10 -10
- package/dist/templates/husky/commit-msg +4 -2
- package/dist/templates/husky/pre-commit +10 -0
- package/dist/templates/husky/pre-push +24 -10
- package/dist/templates/package.json +71 -0
- package/dist/templates/pnpm-workspace.yaml +3 -0
- package/dist/templates/scripts/worktree.sh +5 -0
- package/dist/templates/templates.manifest.yml +127 -7
- package/dist/templates/tsconfig.json +4 -0
- package/dist/templates/turbo.json +32 -0
- package/package.json +2 -2
- package/dist/templates/github/ISSUE_TEMPLATE/bug.md +0 -33
- package/dist/templates/github/ISSUE_TEMPLATE/feature.md +0 -18
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Reusable workflow — semantic-release with packages_changed guard.
|
|
4
|
+
# Produces outputs so downstream jobs (publish, deploy-site) can gate
|
|
5
|
+
# on whether a release happened and which commit carries the bump.
|
|
6
|
+
#
|
|
7
|
+
# Guards release on `packages/**` or root `package.json` changing in
|
|
8
|
+
# the pushed range. Site-only or docs-only pushes skip the release
|
|
9
|
+
# cycle. Set `require_package_changes: false` to release regardless.
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
workflow_call:
|
|
13
|
+
inputs:
|
|
14
|
+
require_package_changes:
|
|
15
|
+
description: 'Only run semantic-release if packages/** or package.json changed'
|
|
16
|
+
type: boolean
|
|
17
|
+
default: true
|
|
18
|
+
outputs:
|
|
19
|
+
released:
|
|
20
|
+
description: 'Whether a new version was released'
|
|
21
|
+
value: ${{ jobs.release.outputs.released }}
|
|
22
|
+
version:
|
|
23
|
+
description: 'The released version number (without leading v)'
|
|
24
|
+
value: ${{ jobs.release.outputs.version }}
|
|
25
|
+
release_sha:
|
|
26
|
+
description: 'The commit SHA that was released (chore(release) commit, or github.sha on no-op)'
|
|
27
|
+
value: ${{ jobs.release.outputs.release_sha }}
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
release:
|
|
31
|
+
name: semantic-release
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
permissions:
|
|
34
|
+
contents: write
|
|
35
|
+
issues: write
|
|
36
|
+
pull-requests: write
|
|
37
|
+
outputs:
|
|
38
|
+
released: ${{ steps.semantic.outputs.released }}
|
|
39
|
+
version: ${{ steps.semantic.outputs.version }}
|
|
40
|
+
# Fallback to github.sha on no-op — consumers without an `if: released == 'true'`
|
|
41
|
+
# guard won't checkout an empty ref.
|
|
42
|
+
release_sha: ${{ steps.semantic.outputs.release_sha || github.sha }}
|
|
43
|
+
steps:
|
|
44
|
+
- name: Check for package changes across pushed commits
|
|
45
|
+
id: changes
|
|
46
|
+
if: inputs.require_package_changes
|
|
47
|
+
uses: actions/github-script@v7
|
|
48
|
+
with:
|
|
49
|
+
# Compares `before..after` from the push payload. Paginates explicitly
|
|
50
|
+
# because `compareCommits` truncates at 300 files per page — a large
|
|
51
|
+
# push could otherwise silently miss detection.
|
|
52
|
+
script: |
|
|
53
|
+
const before = context.payload.before;
|
|
54
|
+
const after = context.sha;
|
|
55
|
+
const isInitial = !before || /^0+$/.test(before);
|
|
56
|
+
let filenames = [];
|
|
57
|
+
if (isInitial) {
|
|
58
|
+
const { data: commit } = await github.rest.repos.getCommit({
|
|
59
|
+
owner: context.repo.owner, repo: context.repo.repo, ref: after,
|
|
60
|
+
});
|
|
61
|
+
filenames = (commit.files ?? []).map(f => f.filename);
|
|
62
|
+
} else {
|
|
63
|
+
const pages = await github.paginate(
|
|
64
|
+
github.rest.repos.compareCommitsWithBasehead,
|
|
65
|
+
{
|
|
66
|
+
owner: context.repo.owner,
|
|
67
|
+
repo: context.repo.repo,
|
|
68
|
+
basehead: `${before}...${after}`,
|
|
69
|
+
per_page: 100,
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
filenames = pages.flatMap(p => (p.files ?? []).map(f => f.filename));
|
|
73
|
+
}
|
|
74
|
+
const packageChanged = filenames.some(
|
|
75
|
+
f => f.startsWith('packages/') || f === 'package.json'
|
|
76
|
+
);
|
|
77
|
+
core.setOutput('packages_changed', packageChanged ? 'true' : 'false');
|
|
78
|
+
core.info(`inspected ${filenames.length} files, packages_changed=${packageChanged}`);
|
|
79
|
+
|
|
80
|
+
- name: Generate GitHub App token
|
|
81
|
+
id: app-token
|
|
82
|
+
if: "!inputs.require_package_changes || steps.changes.outputs.packages_changed == 'true'"
|
|
83
|
+
uses: actions/create-github-app-token@v1
|
|
84
|
+
with:
|
|
85
|
+
app-id: ${{ secrets.RELEASE_APP_ID }}
|
|
86
|
+
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
|
87
|
+
|
|
88
|
+
- uses: actions/checkout@v6
|
|
89
|
+
if: "!inputs.require_package_changes || steps.changes.outputs.packages_changed == 'true'"
|
|
90
|
+
with:
|
|
91
|
+
fetch-depth: 0
|
|
92
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
93
|
+
|
|
94
|
+
- uses: pnpm/action-setup@v4
|
|
95
|
+
if: "!inputs.require_package_changes || steps.changes.outputs.packages_changed == 'true'"
|
|
96
|
+
|
|
97
|
+
- uses: actions/setup-node@v6
|
|
98
|
+
if: "!inputs.require_package_changes || steps.changes.outputs.packages_changed == 'true'"
|
|
99
|
+
with:
|
|
100
|
+
node-version: 22
|
|
101
|
+
cache: 'pnpm'
|
|
102
|
+
registry-url: 'https://registry.npmjs.org'
|
|
103
|
+
|
|
104
|
+
- name: Install dependencies
|
|
105
|
+
if: "!inputs.require_package_changes || steps.changes.outputs.packages_changed == 'true'"
|
|
106
|
+
run: pnpm install --frozen-lockfile
|
|
107
|
+
|
|
108
|
+
- name: Build
|
|
109
|
+
if: "!inputs.require_package_changes || steps.changes.outputs.packages_changed == 'true'"
|
|
110
|
+
run: pnpm turbo run build
|
|
111
|
+
|
|
112
|
+
- name: Run semantic-release
|
|
113
|
+
id: semantic
|
|
114
|
+
if: "!inputs.require_package_changes || steps.changes.outputs.packages_changed == 'true'"
|
|
115
|
+
env:
|
|
116
|
+
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
117
|
+
HUSKY: 0
|
|
118
|
+
# Three cases:
|
|
119
|
+
# (a) success with release: stdout contains 'Published release' →
|
|
120
|
+
# we take the version from `git describe --tags` (more robust
|
|
121
|
+
# to format changes in semantic-release's stdout).
|
|
122
|
+
# (b) no-op: exit 0 AND stdout mentions 'no relevant changes' →
|
|
123
|
+
# released=false. Requiring BOTH conditions avoids masking
|
|
124
|
+
# silent failures where the tool exits 0 without publishing
|
|
125
|
+
# or explaining why.
|
|
126
|
+
# (c) real error: any other combination → non-zero exit.
|
|
127
|
+
run: |
|
|
128
|
+
set +e
|
|
129
|
+
OUTPUT=$(npx semantic-release 2>&1)
|
|
130
|
+
EXIT=$?
|
|
131
|
+
set -e
|
|
132
|
+
echo "$OUTPUT"
|
|
133
|
+
|
|
134
|
+
if echo "$OUTPUT" | grep -q "Published release"; then
|
|
135
|
+
RAW_TAG=$(git describe --tags --abbrev=0)
|
|
136
|
+
VERSION="${RAW_TAG#v}"
|
|
137
|
+
if [ -z "$VERSION" ]; then
|
|
138
|
+
echo "::error::semantic-release published but git describe found no tag"
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
RELEASE_SHA=$(git rev-parse HEAD)
|
|
142
|
+
{
|
|
143
|
+
echo "released=true"
|
|
144
|
+
echo "version=$VERSION"
|
|
145
|
+
echo "release_sha=$RELEASE_SHA"
|
|
146
|
+
} >> "$GITHUB_OUTPUT"
|
|
147
|
+
elif [ "$EXIT" -eq 0 ] && echo "$OUTPUT" | grep -qE "no relevant changes|There are no relevant changes"; then
|
|
148
|
+
echo "released=false" >> "$GITHUB_OUTPUT"
|
|
149
|
+
else
|
|
150
|
+
echo "::error::semantic-release exited $EXIT with no recognized release or no-op marker"
|
|
151
|
+
exit "${EXIT:-1}"
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
- name: Skip release (no package changes)
|
|
155
|
+
if: inputs.require_package_changes && steps.changes.outputs.packages_changed != 'true'
|
|
156
|
+
run: echo "No changes under packages/** — release skipped"
|
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
name: CI
|
|
1
|
+
name: CI/CD
|
|
2
|
+
|
|
3
|
+
# Canonical CI/CD orchestrator — mirrors platform's split-workflow shape.
|
|
4
|
+
# Each concern lives in its own reusable workflow under `.github/workflows/`:
|
|
5
|
+
#
|
|
6
|
+
# _checks.yml — parallel CI checks (lint, typecheck, format, build, test)
|
|
7
|
+
# _release.yml — semantic-release with packages_changed guard
|
|
8
|
+
# _publish.yml — npm publish pinned to release_sha
|
|
9
|
+
# _deploy-site.yml — Cloudflare Pages deploy + Slack notify (repos with sites)
|
|
10
|
+
# review.yml — automated Anthropic → OpenAI code review
|
|
11
|
+
# publish-tag.yml — manual recovery (workflow_dispatch)
|
|
12
|
+
#
|
|
13
|
+
# This orchestrator wires the pieces together. Each repo adapts:
|
|
14
|
+
# - Which reusable workflows to call (comment out what doesn't apply)
|
|
15
|
+
# - The publish `packages` list
|
|
16
|
+
# - The site `project_name` and `site_filter`
|
|
17
|
+
# - Extra repo-specific workflows (e.g. fhir's publish-ig.yml)
|
|
2
18
|
|
|
3
19
|
on:
|
|
4
20
|
push:
|
|
@@ -7,38 +23,58 @@ on:
|
|
|
7
23
|
branches: [main]
|
|
8
24
|
workflow_dispatch:
|
|
9
25
|
|
|
26
|
+
permissions:
|
|
27
|
+
contents: read
|
|
28
|
+
issues: write
|
|
29
|
+
pull-requests: write
|
|
30
|
+
deployments: write
|
|
31
|
+
id-token: write
|
|
32
|
+
|
|
10
33
|
concurrency:
|
|
11
34
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
35
|
cancel-in-progress: true
|
|
13
36
|
|
|
14
37
|
jobs:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- uses: actions/checkout@v4
|
|
20
|
-
|
|
21
|
-
- uses: pnpm/action-setup@v4
|
|
22
|
-
|
|
23
|
-
- uses: actions/setup-node@v4
|
|
24
|
-
with:
|
|
25
|
-
node-version: '{{NODE_VERSION}}'
|
|
26
|
-
cache: pnpm
|
|
27
|
-
|
|
28
|
-
- name: Install dependencies
|
|
29
|
-
run: pnpm install --frozen-lockfile
|
|
30
|
-
|
|
31
|
-
- name: Build
|
|
32
|
-
run: pnpm -r build
|
|
38
|
+
# ── CI checks (every PR and push) ──────────────────────────────
|
|
39
|
+
checks:
|
|
40
|
+
uses: ./.github/workflows/_checks.yml
|
|
41
|
+
secrets: inherit
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
# ── Automated code review (PR only, after checks) ──────────────
|
|
44
|
+
review:
|
|
45
|
+
needs: checks
|
|
46
|
+
if: github.event_name == 'pull_request'
|
|
47
|
+
uses: ./.github/workflows/review.yml
|
|
48
|
+
with:
|
|
49
|
+
pr_number: ${{ github.event.pull_request.number }}
|
|
50
|
+
secrets: inherit
|
|
36
51
|
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
# ── Release (main only, guarded on package changes) ────────────
|
|
53
|
+
release:
|
|
54
|
+
needs: checks
|
|
55
|
+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
56
|
+
uses: ./.github/workflows/_release.yml
|
|
57
|
+
secrets: inherit
|
|
39
58
|
|
|
40
|
-
|
|
41
|
-
|
|
59
|
+
# ── Publish to npm (when release created a new version) ────────
|
|
60
|
+
publish:
|
|
61
|
+
needs: release
|
|
62
|
+
if: needs.release.outputs.released == 'true'
|
|
63
|
+
uses: ./.github/workflows/_publish.yml
|
|
64
|
+
with:
|
|
65
|
+
release_sha: ${{ needs.release.outputs.release_sha }}
|
|
66
|
+
# Newline-separated list of package directories. One per line.
|
|
67
|
+
packages: |
|
|
68
|
+
packages/core
|
|
69
|
+
packages/calculators
|
|
70
|
+
secrets: inherit
|
|
42
71
|
|
|
43
|
-
|
|
44
|
-
|
|
72
|
+
# ── Deploy site (repos with a site — remove if not applicable) ──
|
|
73
|
+
deploy-site:
|
|
74
|
+
needs: checks
|
|
75
|
+
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
|
76
|
+
uses: ./.github/workflows/_deploy-site.yml
|
|
77
|
+
with:
|
|
78
|
+
project_name: '{{PROJECT_NAME}}'
|
|
79
|
+
site_filter: '@{{SITE_FILTER}}'
|
|
80
|
+
secrets: inherit
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: Template drift audit
|
|
2
|
+
|
|
3
|
+
# Monthly audit — runs `precisa doctor` to detect drift between this
|
|
4
|
+
# repo's templated files (workflows, dotfiles, configs) and the current
|
|
5
|
+
# `@precisa-saude/cli` templates. When drift shows up, open a PR running
|
|
6
|
+
# `pnpm exec precisa sync` to absorb the changes.
|
|
7
|
+
#
|
|
8
|
+
# Runs on the 1st of each month at 06:00 America/Sao_Paulo (09:00 UTC).
|
|
9
|
+
# `workflow_dispatch` is wired so you can trigger it ad-hoc.
|
|
10
|
+
#
|
|
11
|
+
# This workflow catches the class of incident that motivated it:
|
|
12
|
+
# silent drift between canonical workflow templates and the real files
|
|
13
|
+
# in each repo. See fhir-brasil#26 (PAT_TOKEN → GitHub App) for the
|
|
14
|
+
# concrete failure mode — drift went undetected until a conditional
|
|
15
|
+
# job finally fired in production.
|
|
16
|
+
|
|
17
|
+
on:
|
|
18
|
+
schedule:
|
|
19
|
+
- cron: '0 9 1 * *'
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
issues: write
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
doctor:
|
|
28
|
+
name: precisa doctor
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
timeout-minutes: 10
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v6
|
|
33
|
+
- uses: pnpm/action-setup@v4
|
|
34
|
+
- uses: actions/setup-node@v6
|
|
35
|
+
with:
|
|
36
|
+
node-version: 22
|
|
37
|
+
cache: 'pnpm'
|
|
38
|
+
- run: pnpm install --frozen-lockfile
|
|
39
|
+
- name: Audit templates
|
|
40
|
+
id: doctor
|
|
41
|
+
run: pnpm exec precisa doctor
|
|
42
|
+
continue-on-error: true
|
|
43
|
+
- name: Open issue on drift
|
|
44
|
+
if: steps.doctor.outcome == 'failure'
|
|
45
|
+
uses: actions/github-script@v7
|
|
46
|
+
with:
|
|
47
|
+
script: |
|
|
48
|
+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
49
|
+
const body = [
|
|
50
|
+
'`precisa doctor` detected drift between this repo and the canonical templates.',
|
|
51
|
+
'',
|
|
52
|
+
`Full log: ${runUrl}`,
|
|
53
|
+
'',
|
|
54
|
+
'To fix:',
|
|
55
|
+
'',
|
|
56
|
+
'```bash',
|
|
57
|
+
'pnpm exec precisa sync --dry-run # review the diff',
|
|
58
|
+
'pnpm exec precisa sync # apply',
|
|
59
|
+
'```',
|
|
60
|
+
'',
|
|
61
|
+
'Drift typically shows up when `@precisa-saude/cli` publishes',
|
|
62
|
+
'new templates (workflow changes, dotfile updates) and this',
|
|
63
|
+
'repo has not run `precisa sync` since.',
|
|
64
|
+
].join('\n');
|
|
65
|
+
await github.rest.issues.create({
|
|
66
|
+
owner: context.repo.owner,
|
|
67
|
+
repo: context.repo.repo,
|
|
68
|
+
title: 'precisa doctor: template drift detected',
|
|
69
|
+
body,
|
|
70
|
+
labels: ['dependencies', 'tooling-drift'],
|
|
71
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: Publish tag to npm
|
|
2
|
+
|
|
3
|
+
# Recuperação manual: publica os pacotes de uma tag git específica no npm.
|
|
4
|
+
# Usado quando semantic-release criou a tag + commit chore(release) mas o
|
|
5
|
+
# step de publish foi interrompido (cancel de concorrência, npm fora do ar,
|
|
6
|
+
# rotação de token mid-release, etc.). Idempotente — só publica se a versão
|
|
7
|
+
# não estiver no registry.
|
|
8
|
+
#
|
|
9
|
+
# Uso: gh workflow run publish-tag.yml -f tag=v1.1.0
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
inputs:
|
|
14
|
+
tag:
|
|
15
|
+
description: 'Tag Git para publicar (ex.: v1.1.0)'
|
|
16
|
+
required: true
|
|
17
|
+
type: string
|
|
18
|
+
|
|
19
|
+
permissions:
|
|
20
|
+
contents: read
|
|
21
|
+
# `id-token: write` is for Sigstore attestations (`--provenance`),
|
|
22
|
+
# not for npm auth — see _publish.yml for the full rationale.
|
|
23
|
+
id-token: write
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
publish:
|
|
27
|
+
name: Publish tag
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v6
|
|
31
|
+
with:
|
|
32
|
+
ref: ${{ inputs.tag }}
|
|
33
|
+
|
|
34
|
+
- uses: pnpm/action-setup@v4
|
|
35
|
+
|
|
36
|
+
- uses: actions/setup-node@v6
|
|
37
|
+
with:
|
|
38
|
+
node-version: 22
|
|
39
|
+
cache: 'pnpm'
|
|
40
|
+
registry-url: 'https://registry.npmjs.org'
|
|
41
|
+
|
|
42
|
+
- name: Install dependencies
|
|
43
|
+
run: pnpm install --frozen-lockfile
|
|
44
|
+
|
|
45
|
+
- name: Build
|
|
46
|
+
run: pnpm turbo run build
|
|
47
|
+
|
|
48
|
+
- name: Publish packages (idempotent)
|
|
49
|
+
# Auto-discovers publishable packages via pnpm's workspace filter
|
|
50
|
+
# (`./packages/**`, skipping those with `private: true`). No
|
|
51
|
+
# per-repo list to keep in sync; adding a new package means
|
|
52
|
+
# adding it to `packages/` and the next tag picks it up.
|
|
53
|
+
env:
|
|
54
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
55
|
+
run: |
|
|
56
|
+
set -e
|
|
57
|
+
pnpm -r --filter "./packages/**" --if-present exec sh -c '
|
|
58
|
+
pkg_name=$(node -p "require(\"./package.json\").name")
|
|
59
|
+
pkg_version=$(node -p "require(\"./package.json\").version")
|
|
60
|
+
pkg_private=$(node -p "Boolean(require(\"./package.json\").private)")
|
|
61
|
+
if [ "$pkg_private" = "true" ]; then
|
|
62
|
+
echo "Skipping $pkg_name (private)"
|
|
63
|
+
exit 0
|
|
64
|
+
fi
|
|
65
|
+
if npm view "$pkg_name@$pkg_version" version 2>/dev/null; then
|
|
66
|
+
echo "Skipping $pkg_name@$pkg_version (already published)"
|
|
67
|
+
else
|
|
68
|
+
echo "Publishing $pkg_name@$pkg_version..."
|
|
69
|
+
pnpm publish --provenance --access public --no-git-checks
|
|
70
|
+
fi
|
|
71
|
+
'
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
name: Release
|
|
2
2
|
|
|
3
|
+
# Release standalone para repos que publicam pacotes SEM site associado
|
|
4
|
+
# (ex.: tooling). Repos que têm site costumam integrar release+publish no
|
|
5
|
+
# ci.yml — veja ci.yml do template.
|
|
6
|
+
|
|
3
7
|
on:
|
|
4
8
|
push:
|
|
5
9
|
branches: [main]
|
|
10
|
+
# Weekly cadence — picks up any docs/CI/chore commits that semantic-
|
|
11
|
+
# release normally wouldn't promote. Ensures consumers stay within
|
|
12
|
+
# one minor of the current HEAD even during low-commit weeks.
|
|
13
|
+
# semantic-release is a no-op when there are no releasable commits,
|
|
14
|
+
# so this is safe to run unconditionally.
|
|
15
|
+
schedule:
|
|
16
|
+
- cron: '0 9 * * 1'
|
|
6
17
|
workflow_dispatch:
|
|
7
18
|
|
|
8
19
|
concurrency:
|
|
@@ -13,23 +24,25 @@ jobs:
|
|
|
13
24
|
release:
|
|
14
25
|
name: semantic-release
|
|
15
26
|
runs-on: ubuntu-latest
|
|
27
|
+
# `id-token: write` is for Sigstore attestations (`--provenance`),
|
|
28
|
+
# not for npm auth. npm auth uses the `NPM_TOKEN` org-secret.
|
|
16
29
|
permissions:
|
|
17
30
|
contents: write
|
|
18
31
|
issues: write
|
|
19
32
|
pull-requests: write
|
|
20
33
|
id-token: write
|
|
21
34
|
steps:
|
|
22
|
-
- uses: actions/checkout@
|
|
35
|
+
- uses: actions/checkout@v6
|
|
23
36
|
with:
|
|
24
37
|
fetch-depth: 0
|
|
25
38
|
persist-credentials: false
|
|
26
39
|
|
|
27
40
|
- uses: pnpm/action-setup@v4
|
|
28
41
|
|
|
29
|
-
- uses: actions/setup-node@
|
|
42
|
+
- uses: actions/setup-node@v6
|
|
30
43
|
with:
|
|
31
44
|
node-version: 22
|
|
32
|
-
cache: pnpm
|
|
45
|
+
cache: 'pnpm'
|
|
33
46
|
registry-url: 'https://registry.npmjs.org'
|
|
34
47
|
|
|
35
48
|
- name: Install dependencies
|
|
@@ -39,11 +52,12 @@ jobs:
|
|
|
39
52
|
run: pnpm -r build
|
|
40
53
|
|
|
41
54
|
- name: Configure npm auth
|
|
42
|
-
# actions/setup-node writes
|
|
43
|
-
# that `npm`
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# so
|
|
55
|
+
# `actions/setup-node` writes `~/.npmrc` with a literal
|
|
56
|
+
# `${NODE_AUTH_TOKEN}` placeholder that `npm` expands at publish
|
|
57
|
+
# time, but `pnpm publish` (used by semantic-release's
|
|
58
|
+
# `publishCmd`) doesn't always expand it. Write the resolved
|
|
59
|
+
# token directly so pnpm reads it unambiguously. Lives in $HOME
|
|
60
|
+
# so it doesn't pollute the working tree.
|
|
47
61
|
env:
|
|
48
62
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
49
63
|
run: |
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
name: Code Review
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
3
|
+
# Reusable workflow chamado pelo ci.yml para rodar o review automatizado.
|
|
4
|
+
# Tenta providers em ordem (Anthropic → OpenAI → GitHub Models) e usa o
|
|
5
|
+
# primeiro que retorna 200. Qualquer não-200 dispara fallback para o
|
|
6
|
+
# próximo. GitHub Models é o último degrau: usa o GITHUB_TOKEN built-in
|
|
7
|
+
# + permissão `models: read`, então funciona sem nenhum segredo externo
|
|
8
|
+
# — repos que não configuram ANTHROPIC_API_KEY nem OPENAI_API_KEY ainda
|
|
9
|
+
# têm review automatizado. Se todos falham (ou nenhum está configurado),
|
|
10
|
+
# sai sem postar review e sem emitir marker de round, de modo que a
|
|
11
|
+
# próxima atividade no PR retenta fresco.
|
|
8
12
|
|
|
9
13
|
on:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
workflow_call:
|
|
15
|
+
inputs:
|
|
16
|
+
pr_number:
|
|
17
|
+
description: 'PR number to review'
|
|
18
|
+
type: number
|
|
19
|
+
required: true
|
|
16
20
|
|
|
17
21
|
jobs:
|
|
18
22
|
check-review-status:
|
|
@@ -26,7 +30,7 @@ jobs:
|
|
|
26
30
|
should_run: ${{ steps.check.outputs.should_run }}
|
|
27
31
|
review_round: ${{ steps.check.outputs.review_round }}
|
|
28
32
|
steps:
|
|
29
|
-
- uses: actions/checkout@
|
|
33
|
+
- uses: actions/checkout@v6
|
|
30
34
|
|
|
31
35
|
- name: Decide whether to run
|
|
32
36
|
id: check
|
|
@@ -34,10 +38,12 @@ jobs:
|
|
|
34
38
|
with:
|
|
35
39
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
36
40
|
script: |
|
|
37
|
-
const prNumber =
|
|
41
|
+
const prNumber = ${{ inputs.pr_number }};
|
|
38
42
|
|
|
39
|
-
//
|
|
40
|
-
|
|
43
|
+
// Paginamos listFiles porque em PRs grandes o .tsx/.ts real pode
|
|
44
|
+
// cair em página 2+ e a detecção de "código mudou" dava falso
|
|
45
|
+
// negativo, pulando o review.
|
|
46
|
+
const files = await github.paginate(github.rest.pulls.listFiles, {
|
|
41
47
|
owner: context.repo.owner,
|
|
42
48
|
repo: context.repo.repo,
|
|
43
49
|
pull_number: prNumber,
|
|
@@ -78,12 +84,18 @@ jobs:
|
|
|
78
84
|
permissions:
|
|
79
85
|
contents: read
|
|
80
86
|
pull-requests: write
|
|
87
|
+
# `models: read` lets this job call GitHub Models via the built-in
|
|
88
|
+
# GITHUB_TOKEN — no external API key needed for the fallback
|
|
89
|
+
# provider. Repos that don't configure ANTHROPIC_API_KEY or
|
|
90
|
+
# OPENAI_API_KEY still get a review via GitHub Models.
|
|
91
|
+
models: read
|
|
81
92
|
env:
|
|
82
93
|
ANTHROPIC_MODEL: claude-haiku-4-5-20251001
|
|
83
94
|
OPENAI_MODEL: gpt-4o-mini
|
|
95
|
+
GITHUB_MODELS_MODEL: openai/gpt-4o-mini
|
|
84
96
|
MAX_DIFF_SIZE: '150000'
|
|
85
97
|
steps:
|
|
86
|
-
- uses: actions/checkout@
|
|
98
|
+
- uses: actions/checkout@v6
|
|
87
99
|
with:
|
|
88
100
|
fetch-depth: 0
|
|
89
101
|
|
|
@@ -92,8 +104,8 @@ jobs:
|
|
|
92
104
|
env:
|
|
93
105
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
94
106
|
REVIEW_ROUND: ${{ needs.check-review-status.outputs.review_round }}
|
|
107
|
+
PR_NUMBER: ${{ inputs.pr_number }}
|
|
95
108
|
run: |
|
|
96
|
-
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
97
109
|
HEAD_SHA=$(gh pr view "$PR_NUMBER" --json headRefOid -q .headRefOid)
|
|
98
110
|
echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT
|
|
99
111
|
echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
|
@@ -120,6 +132,9 @@ jobs:
|
|
|
120
132
|
env:
|
|
121
133
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
122
134
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
135
|
+
# GitHub Models uses the built-in GITHUB_TOKEN; `models: read`
|
|
136
|
+
# on the job permissions is what actually authorizes the call.
|
|
137
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
123
138
|
DIFF_TRUNCATED: ${{ steps.pr.outputs.diff_truncated }}
|
|
124
139
|
REVIEW_ROUND: ${{ needs.check-review-status.outputs.review_round }}
|
|
125
140
|
run: |
|
|
@@ -163,8 +178,9 @@ jobs:
|
|
|
163
178
|
'- If no issues, return an empty feedback array.',
|
|
164
179
|
].join('\n');
|
|
165
180
|
|
|
166
|
-
//
|
|
167
|
-
//
|
|
181
|
+
// Pricing por 1M tokens USD. Atualizar quando providers mudarem rates.
|
|
182
|
+
// Entradas faltando silenciosamente pulam exibição de custo (tokens
|
|
183
|
+
// ainda são mostrados).
|
|
168
184
|
const PRICING = {
|
|
169
185
|
'claude-haiku-4-5-20251001': { in: 1.00, out: 5.00 },
|
|
170
186
|
'claude-sonnet-4-6': { in: 3.00, out: 15.00 },
|
|
@@ -235,10 +251,46 @@ jobs:
|
|
|
235
251
|
}),
|
|
236
252
|
extractError: (r) => r?.error?.message ?? 'see workflow logs',
|
|
237
253
|
},
|
|
254
|
+
// GitHub Models — OpenAI-compatible API authenticated via the
|
|
255
|
+
// built-in GITHUB_TOKEN + `models: read` permission. No
|
|
256
|
+
// external API key needed, so this is the fallback when
|
|
257
|
+
// neither Anthropic nor OpenAI secrets are configured.
|
|
258
|
+
// Rate limits apply per-repo; the models catalog lists what's
|
|
259
|
+
// available. `openai/gpt-4o-mini` is a safe default.
|
|
260
|
+
'github-models': {
|
|
261
|
+
label: 'GitHub Models',
|
|
262
|
+
envKey: 'GITHUB_TOKEN',
|
|
263
|
+
modelEnv: 'GITHUB_MODELS_MODEL',
|
|
264
|
+
defaultModel: 'openai/gpt-4o-mini',
|
|
265
|
+
build: (model, key) => ({
|
|
266
|
+
url: 'https://models.github.ai/inference/chat/completions',
|
|
267
|
+
headers: {
|
|
268
|
+
'content-type': 'application/json',
|
|
269
|
+
Authorization: `Bearer ${key}`,
|
|
270
|
+
},
|
|
271
|
+
body: JSON.stringify({
|
|
272
|
+
model,
|
|
273
|
+
max_tokens: 8192,
|
|
274
|
+
messages: [
|
|
275
|
+
{ role: 'system', content: system },
|
|
276
|
+
{ role: 'user', content: user },
|
|
277
|
+
],
|
|
278
|
+
response_format: { type: 'json_object' },
|
|
279
|
+
}),
|
|
280
|
+
}),
|
|
281
|
+
extractText: (r) => r?.choices?.[0]?.message?.content ?? '',
|
|
282
|
+
extractUsage: (r) => ({
|
|
283
|
+
prompt_tokens: r?.usage?.prompt_tokens ?? 0,
|
|
284
|
+
completion_tokens: r?.usage?.completion_tokens ?? 0,
|
|
285
|
+
}),
|
|
286
|
+
extractError: (r) => r?.error?.message ?? 'see workflow logs',
|
|
287
|
+
},
|
|
238
288
|
};
|
|
239
289
|
|
|
240
|
-
// Provider order:
|
|
241
|
-
|
|
290
|
+
// Provider order: prefer Anthropic (best output quality), then
|
|
291
|
+
// OpenAI, then GitHub Models as a keyless fallback. Each
|
|
292
|
+
// provider is skipped if its auth env var isn't set.
|
|
293
|
+
const order = ['anthropic', 'openai', 'github-models'];
|
|
242
294
|
|
|
243
295
|
const setOutput = (k, v) => {
|
|
244
296
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${k}<<EOF\n${String(v)}\nEOF\n`);
|
|
@@ -325,7 +377,6 @@ jobs:
|
|
|
325
377
|
setOutput('completion_tokens', String(completionTokens));
|
|
326
378
|
setOutput('cost_usd', cost == null ? '' : cost.toFixed(4));
|
|
327
379
|
|
|
328
|
-
// Emit fallback details to the workflow log (not the PR comment).
|
|
329
380
|
const failed = attempts.filter((a) => !a.ok && !a.skip);
|
|
330
381
|
const fellBack = failed.length > 0;
|
|
331
382
|
setOutput('fell_back', fellBack ? 'true' : 'false');
|
|
@@ -363,7 +414,8 @@ jobs:
|
|
|
363
414
|
: '';
|
|
364
415
|
const fallbackBadge = fellBack ? ' (fallback)' : '';
|
|
365
416
|
|
|
366
|
-
//
|
|
417
|
+
// Caminhos de falha — sem marker, de modo que o próximo push
|
|
418
|
+
// retenta como Round 1.
|
|
367
419
|
if (status !== 'ok') {
|
|
368
420
|
const note = status === 'skipped'
|
|
369
421
|
? `Review skipped — ${errorReason}. No round marker emitted; the next PR activity will retry as Round 1.`
|