code-foundry 1.9.13 → 1.10.1
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/.github/CONTRIBUTING.md +24 -3
- package/.github/workflows/draft-enforcement_self-ci.yml +79 -0
- package/.github/workflows/validation_self-ci.yml +26 -4
- package/.oxfmtrc.json +1 -1
- package/AGENTS.md +24 -1
- package/CHANGELOG.md +15 -0
- package/README.md +8 -5
- package/docs/INITIALIZATION.md +10 -1
- package/docs/WORKFLOWS.md +28 -17
- package/package.json +1 -1
- package/src/commands/doctor.mjs +14 -5
- package/src/commands/release.mjs +1 -0
- package/src/commands/sync.mjs +100 -13
- package/src/lib/github-doctor.mjs +1 -19
- package/src/lib/lockfiles.mjs +15 -0
package/.github/CONTRIBUTING.md
CHANGED
|
@@ -32,6 +32,27 @@ Agents must not:
|
|
|
32
32
|
- Change branch protections, secrets, deployments, or external systems unless that action is explicitly in scope.
|
|
33
33
|
- Claim completion when tests, deployment checks, or required reviews are still pending.
|
|
34
34
|
|
|
35
|
+
<!-- code-foundry-managed: pull-request-policy -->
|
|
36
|
+
|
|
37
|
+
### Pull request readiness (mandatory)
|
|
38
|
+
|
|
39
|
+
This repository uses the `direct` workflow. Topic pull requests target `main`.
|
|
40
|
+
|
|
41
|
+
- Open every ordinary pull request as a draft. Use `gh pr create --draft` or
|
|
42
|
+
set `draft: true` in the GitHub API; never create a ready ordinary pull
|
|
43
|
+
request as a shortcut.
|
|
44
|
+
- Keep ordinary pull requests in draft while making commits. The generated
|
|
45
|
+
Draft Guard resets a ready ordinary pull request to draft on creation,
|
|
46
|
+
reopening, or a new commit, and runner-heavy validation starts only after an
|
|
47
|
+
explicit `ready_for_review` transition.
|
|
48
|
+
- Run local validation and finish review preparation before marking an ordinary
|
|
49
|
+
pull request ready. After any later commit, mark it ready again only after
|
|
50
|
+
the current head is ready for review.
|
|
51
|
+
- Release Please version pull requests are managed by the Code Foundry release
|
|
52
|
+
workflow; do not manually change their draft state unless the workflow asks.
|
|
53
|
+
|
|
54
|
+
<!-- /code-foundry-managed: pull-request-policy -->
|
|
55
|
+
|
|
35
56
|
## Branching model
|
|
36
57
|
|
|
37
58
|
```text
|
|
@@ -160,11 +181,11 @@ Keep pull requests focused and reviewable. Include screenshots or recordings for
|
|
|
160
181
|
| Exact Release Please pull request targeting `main` | Full validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate` |
|
|
161
182
|
| Scheduled or manual validation | Full audit tier |
|
|
162
183
|
| Push to a working branch | Draft PR workflow |
|
|
163
|
-
| Push to `main` | Release workflow;
|
|
184
|
+
| Push to `main` | Release workflow plus default-branch CodeQL scan; validation ran on the merged PR |
|
|
164
185
|
|
|
165
|
-
Draft pull requests do not start validation. Marking a pull request ready for review starts the applicable validation tier. Convert it back to draft after an update, then mark it ready again after every update so the required checks attach to the current head. Converting it to draft runs only the lightweight cancellation control.
|
|
186
|
+
Draft pull requests do not start validation. The lightweight Draft Guard also converts ordinary pull requests opened, reopened, or updated while ready back to draft; it never checks out pull-request code and it excludes Release Please version heads, whose release workflow owns their state. Marking a pull request ready for review starts the applicable validation tier. Convert it back to draft after an update, then mark it ready again after every update so the required checks attach to the current head. Converting it to draft runs only the lightweight cancellation control.
|
|
166
187
|
|
|
167
|
-
Pull-request validation keys concurrency by event and pull-request head, so a newer update cancels its superseded run. Scheduled and manual audits use a separate caller pinned to the protected default branch; this prevents caller-selected runtime code from executing with default-branch cache access.
|
|
188
|
+
Pull-request validation keys concurrency by event and pull-request head, so a newer update cancels its superseded run. Scheduled and manual audits use a separate caller pinned to the protected default branch; this prevents caller-selected runtime code from executing with default-branch cache access. Pull-request and scheduled/manual callers use the mode-aware orchestrator, which fans out only the required jobs and concludes with the stable aggregate gate. Main pushes run the default-branch CodeQL lane separately.
|
|
168
189
|
|
|
169
190
|
Required checks are enforced by branch protection rulesets/branch protection. Do not duplicate their checklists in the pull request description; document validation commands and results instead.
|
|
170
191
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
name: Code Foundry Draft Guard
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
# This workflow runs from the trusted base branch and only reads pull-request
|
|
5
|
+
# metadata before changing draft state. It never checks out or executes PR code.
|
|
6
|
+
pull_request_target:
|
|
7
|
+
branches: [main, staging]
|
|
8
|
+
types:
|
|
9
|
+
- opened
|
|
10
|
+
- reopened
|
|
11
|
+
- synchronize
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
pull-requests: write
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: code-foundry-draft-guard-${{ github.repository }}-${{ github.event.pull_request.number }}
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
enforce:
|
|
22
|
+
name: Draft Guard / Enforce
|
|
23
|
+
if: vars.CI_BILLING_PAUSED != 'true'
|
|
24
|
+
runs-on: ubuntu-slim
|
|
25
|
+
timeout-minutes: 5
|
|
26
|
+
steps:
|
|
27
|
+
- name: Keep ordinary pull requests in draft
|
|
28
|
+
env:
|
|
29
|
+
GH_TOKEN: ${{ github.token }}
|
|
30
|
+
EVENT_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
31
|
+
EVENT_UPDATED_AT: ${{ github.event.pull_request.updated_at }}
|
|
32
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
33
|
+
run: |
|
|
34
|
+
set -euo pipefail
|
|
35
|
+
if [ -z "$EVENT_HEAD_SHA" ] || [ -z "$EVENT_UPDATED_AT" ] || [ -z "$PR_NUMBER" ]; then
|
|
36
|
+
echo '::error title=Draft guard cannot verify event freshness::The pull-request event did not include the required immutable metadata.'
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
read -r draft head_ref head_sha updated_at < <(
|
|
41
|
+
gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" \
|
|
42
|
+
--jq '[.draft, .head.ref, .head.sha, .updated_at] | @tsv'
|
|
43
|
+
)
|
|
44
|
+
if [ -z "$draft" ] || [ -z "$head_ref" ] || [ -z "$head_sha" ] || [ -z "$updated_at" ]; then
|
|
45
|
+
echo '::error title=Draft guard cannot verify current state::The GitHub API response did not include the required pull-request metadata.'
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Release Please owns the state of generated version PRs. Its release
|
|
50
|
+
# workflow deliberately resets and readies these PRs before merging.
|
|
51
|
+
case "$head_ref" in
|
|
52
|
+
release-please--branches--main|release-please--branches--main--*)
|
|
53
|
+
echo "Release Please head $head_ref is managed by the release workflow; leaving its draft state unchanged."
|
|
54
|
+
exit 0
|
|
55
|
+
;;
|
|
56
|
+
esac
|
|
57
|
+
|
|
58
|
+
# A newer state transition or commit superseded this event. Do not
|
|
59
|
+
# undo a user's later explicit readiness decision with a stale event.
|
|
60
|
+
if [ "$head_sha" != "$EVENT_HEAD_SHA" ] || [ "$updated_at" != "$EVENT_UPDATED_AT" ]; then
|
|
61
|
+
echo 'A newer pull-request state or head commit superseded this draft-guard event; leaving the current state unchanged.'
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
if [ "$draft" = true ]; then
|
|
66
|
+
echo "Pull request #$PR_NUMBER is already a draft; no validation state change is needed."
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
if [ "$draft" != false ]; then
|
|
70
|
+
echo "::error title=Draft guard cannot verify current state::Unexpected draft value: $draft"
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
gh api \
|
|
75
|
+
--method PATCH \
|
|
76
|
+
"repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" \
|
|
77
|
+
-F draft=true \
|
|
78
|
+
--silent >/dev/null
|
|
79
|
+
echo "Converted ordinary pull request #$PR_NUMBER to draft; runner-heavy validation waits for ready_for_review."
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
name: Code Foundry
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
4
6
|
pull_request:
|
|
5
7
|
branches: [main, staging]
|
|
6
8
|
types:
|
|
@@ -18,9 +20,9 @@ concurrency:
|
|
|
18
20
|
jobs:
|
|
19
21
|
mode:
|
|
20
22
|
name: Mode
|
|
21
|
-
# The
|
|
22
|
-
#
|
|
23
|
-
if: vars.CI_BILLING_PAUSED != 'true'
|
|
23
|
+
# The mode classifier is only needed for pull requests. Main pushes use
|
|
24
|
+
# the dedicated default-branch CodeQL lane below.
|
|
25
|
+
if: vars.CI_BILLING_PAUSED != 'true' && github.event_name == 'pull_request'
|
|
24
26
|
runs-on: ubuntu-slim
|
|
25
27
|
timeout-minutes: 10
|
|
26
28
|
permissions:
|
|
@@ -59,7 +61,7 @@ jobs:
|
|
|
59
61
|
validation:
|
|
60
62
|
name: Validation
|
|
61
63
|
needs: mode
|
|
62
|
-
if: vars.CI_BILLING_PAUSED != 'true'
|
|
64
|
+
if: vars.CI_BILLING_PAUSED != 'true' && github.event_name == 'pull_request'
|
|
63
65
|
# Reusable workflows can only maintain or reduce the caller job's scopes.
|
|
64
66
|
# The audit tier needs security-events: write for CodeQL uploads.
|
|
65
67
|
permissions:
|
|
@@ -87,3 +89,23 @@ jobs:
|
|
|
87
89
|
secrets:
|
|
88
90
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
|
89
91
|
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
|
|
92
|
+
|
|
93
|
+
# CodeQL requires a push analysis to populate default-branch code-scanning
|
|
94
|
+
# alerts. Keep the full validation gate pull-request-only so main pushes do
|
|
95
|
+
# not duplicate CI, test, or security work.
|
|
96
|
+
default-branch-codeql:
|
|
97
|
+
name: CodeQL (default branch)
|
|
98
|
+
if: vars.CI_BILLING_PAUSED != 'true' && github.event_name == 'push'
|
|
99
|
+
permissions:
|
|
100
|
+
actions: read
|
|
101
|
+
contents: read
|
|
102
|
+
packages: read
|
|
103
|
+
security-events: write
|
|
104
|
+
uses: ./.github/workflows/codeql.yml
|
|
105
|
+
with:
|
|
106
|
+
runtime-repository: 0xPlayerOne/code-foundry
|
|
107
|
+
runtime-ref: ${{ github.sha }}
|
|
108
|
+
runner: ubuntu-latest
|
|
109
|
+
rust-shards: '["all"]'
|
|
110
|
+
rust-threads: '1'
|
|
111
|
+
rust-max-parallel: 1
|
package/.oxfmtrc.json
CHANGED
package/AGENTS.md
CHANGED
|
@@ -94,6 +94,29 @@ them:
|
|
|
94
94
|
- Keep pull requests focused; merge with the canonical method only after
|
|
95
95
|
required checks pass.
|
|
96
96
|
|
|
97
|
+
<!-- code-foundry-managed: pull-request-policy -->
|
|
98
|
+
|
|
99
|
+
## Code Foundry workflow policy (mandatory)
|
|
100
|
+
|
|
101
|
+
This repository uses the `direct` workflow. Topic pull requests target `main`.
|
|
102
|
+
|
|
103
|
+
- Open every ordinary pull request as a draft. Use `gh pr create --draft` or
|
|
104
|
+
set `draft: true` in the GitHub API; never create a ready ordinary pull
|
|
105
|
+
request as a shortcut.
|
|
106
|
+
- Keep ordinary pull requests in draft while making commits. The generated
|
|
107
|
+
Draft Guard resets a ready ordinary pull request to draft on creation,
|
|
108
|
+
reopening, or a new commit, and runner-heavy validation starts only after an
|
|
109
|
+
explicit `ready_for_review` transition.
|
|
110
|
+
- Run local validation and finish review preparation before marking an ordinary
|
|
111
|
+
pull request ready. After any later commit, mark it ready again only after
|
|
112
|
+
the current head is ready for review.
|
|
113
|
+
- This contract is mandatory for every agent scope. Nested `AGENTS.md` files
|
|
114
|
+
may add stricter rules but must not weaken or replace it.
|
|
115
|
+
- Release Please version pull requests are managed by the Code Foundry release
|
|
116
|
+
workflow; do not manually change their draft state unless the workflow asks.
|
|
117
|
+
|
|
118
|
+
<!-- /code-foundry-managed: pull-request-policy -->
|
|
119
|
+
|
|
97
120
|
## Toolchain and dependencies
|
|
98
121
|
|
|
99
122
|
- Follow `toolchain: auto` in `.github/code-foundry.yml`; use native tools by
|
|
@@ -154,7 +177,7 @@ If a check cannot run, state the exact reason. A skipped check is not a passing
|
|
|
154
177
|
- Use per-workflow concurrency groups that cancel superseded runs while allowing independent workflows to run in parallel.
|
|
155
178
|
- Keep setup language-aware and cache dependency downloads by lockfile; do not cache secrets, `node_modules`, virtual environments, or broad build output without a measured reason.
|
|
156
179
|
- Use least-privilege permissions and pin action versions consistently with the template.
|
|
157
|
-
- Keep CI, Test, Security, CodeQL, Draft PR, Release PR, and Release concerns separated.
|
|
180
|
+
- Keep CI, Test, Security, CodeQL, Draft Guard, Draft PR, Release PR, and Release concerns separated.
|
|
158
181
|
- Security and CodeQL may skip when repository visibility or GitHub plan support does not permit them. Do not make an unavailable check required.
|
|
159
182
|
- Optional Turborepo Remote Caching uses `TURBO_TOKEN` and `TURBO_TEAM`; do not add Vercel deployment behavior just to enable caching.
|
|
160
183
|
- Update branch protection when adding or renaming required job checks; verify the actual GitHub status context.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.10.1](https://github.com/0xPlayerOne/code-foundry/compare/v1.10.0...v1.10.1) (2026-09-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **doctor:** honor shared config and correctly identify npm/Yarn lockfiles ([#533](https://github.com/0xPlayerOne/code-foundry/issues/533)) ([6464409](https://github.com/0xPlayerOne/code-foundry/commit/646440907ad3fc47f63e27b9621c8e0025459964))
|
|
9
|
+
* **validation:** scan default branch with CodeQL on push ([#540](https://github.com/0xPlayerOne/code-foundry/issues/540)) ([98d8241](https://github.com/0xPlayerOne/code-foundry/commit/98d8241522c78e1b6d3f4f29a44053d33888edba))
|
|
10
|
+
|
|
11
|
+
## [1.10.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.9.13...v1.10.0) (2026-09-08)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* **ci:** enforce draft pull request readiness ([#530](https://github.com/0xPlayerOne/code-foundry/issues/530)) ([fe62147](https://github.com/0xPlayerOne/code-foundry/commit/fe621477a46b49dee69a4c74c482e815a3e60b8d))
|
|
17
|
+
|
|
3
18
|
## [1.9.13](https://github.com/0xPlayerOne/code-foundry/compare/v1.9.12...v1.9.13) (2026-09-08)
|
|
4
19
|
|
|
5
20
|
|
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ contract. For normal updates, edit that file and run `npx code-foundry sync`.
|
|
|
36
36
|
## What it installs
|
|
37
37
|
|
|
38
38
|
- Short workflow callers for pull-request validation, protected scheduled/manual
|
|
39
|
-
audits, Draft PR, Release PR, and Release.
|
|
39
|
+
audits, Draft Guard, Draft PR, Release PR, and Release.
|
|
40
40
|
- A deterministic performance lane with ordered command support, stable result
|
|
41
41
|
artifacts, and an optional shared Node package budget profile.
|
|
42
42
|
- A small `.githooks/pre-commit` launcher with language-aware formatting and
|
|
@@ -70,14 +70,17 @@ when present and otherwise uses native language tooling; choose `native` or
|
|
|
70
70
|
|
|
71
71
|
The standard workflow triggers are:
|
|
72
72
|
|
|
73
|
-
- Pushes to `main` (and `staging` when `git_workflow: staging-release` is configured).
|
|
73
|
+
- Pushes to `main` (and `staging` when `git_workflow: staging-release` is configured); main pushes also run the default-branch CodeQL scan.
|
|
74
74
|
- Pull requests targeting `main` (and `staging` in the staging-release topology).
|
|
75
75
|
- Draft PR automation for supported feature/fix branches.
|
|
76
76
|
|
|
77
77
|
Automated feature/fix and staging-promotion pull requests open as drafts.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
The trusted Draft Guard is a fallback for manually created or reopened ready
|
|
79
|
+
PRs and for new commits to ordinary ready PRs: it converts them to drafts
|
|
80
|
+
without checking out PR code. It verifies the event head and update timestamp
|
|
81
|
+
before changing state, and excludes Release Please version PRs whose release
|
|
82
|
+
workflow owns readiness. Pull-request validation runs only on the
|
|
83
|
+
ready-for-review transition. Converting a PR to draft runs only the lightweight
|
|
81
84
|
cancellation control. Scheduled and manually dispatched audits are unaffected.
|
|
82
85
|
|
|
83
86
|
Jobs are language-aware and skip irrelevant setup inside the applicable
|
package/docs/INITIALIZATION.md
CHANGED
|
@@ -38,7 +38,16 @@ runtime, then run sync.
|
|
|
38
38
|
|
|
39
39
|
Sync updates standard Code Foundry files only. It preserves application code,
|
|
40
40
|
authored documentation, existing `.mise.toml` selections, and custom workflows
|
|
41
|
-
such as deployment, search, Slither, or monitoring workflows.
|
|
41
|
+
such as deployment, search, Slither, or monitoring workflows. A missing
|
|
42
|
+
`AGENTS.md` receives the baseline agent contract. If another initializer has
|
|
43
|
+
created an unmarked `AGENTS.md` or `.github/CONTRIBUTING.md`, sync preserves its
|
|
44
|
+
surrounding text and adds or refreshes only the marked Code Foundry policy block;
|
|
45
|
+
this lets later syncs restore mandatory pull-request rules without replacing
|
|
46
|
+
agent-specific instructions. Marked generated policy documents continue to
|
|
47
|
+
receive topology-aware baseline updates, and missing configuration keys are
|
|
48
|
+
added without changing existing values. The Oxfmt baseline also ignores
|
|
49
|
+
`plugin.json`, whose serialization is owned by Release Please; plugin manifest
|
|
50
|
+
semantics remain covered by repository tests rather than a formatter rewrite.
|
|
42
51
|
|
|
43
52
|
The environment bootstrap enables repository hooks and uses mise only when an
|
|
44
53
|
existing `.mise.toml` is present or `toolchain: mise` is selected. Otherwise it
|
package/docs/WORKFLOWS.md
CHANGED
|
@@ -4,20 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
Validation uses two callers so pull-request code and default-branch-capable
|
|
6
6
|
audit events never share a caller-selected runtime ref. The canonical
|
|
7
|
-
`validation.yml` caller handles pull requests
|
|
7
|
+
`validation.yml` caller handles pull requests and a lightweight default-branch
|
|
8
|
+
CodeQL lane:
|
|
8
9
|
|
|
9
10
|
```yaml
|
|
11
|
+
push:
|
|
12
|
+
branches: [main] # CodeQL only
|
|
10
13
|
pull_request:
|
|
11
14
|
branches: [main, staging] # staging-release topology
|
|
12
15
|
# direct topology: branches: [main]
|
|
13
16
|
```
|
|
14
17
|
|
|
15
|
-
The generated validation caller listens
|
|
16
|
-
pull requests register no validation checks and allocate no
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
The generated validation caller listens for `ready_for_review` and `push` to
|
|
19
|
+
`main`. Draft pull requests register no validation checks and allocate no
|
|
20
|
+
validation runner; main pushes run only the default-branch CodeQL lane, while
|
|
21
|
+
full validation remains pull-request-only.
|
|
22
|
+
A separate lightweight Draft Guard runs from the trusted base branch on
|
|
23
|
+
`opened`, `reopened`, and `synchronize`; it converts ordinary ready pull
|
|
24
|
+
requests back to draft without checking out pull-request code. It rechecks the
|
|
25
|
+
current head and update timestamp before mutating state, so a stale event
|
|
26
|
+
cannot undo a later draft or ready transition. Release Please version heads are
|
|
27
|
+
excluded because the release workflow owns their state. A separate draft-control
|
|
28
|
+
caller listens for `converted_to_draft` and cancels queued or running
|
|
29
|
+
pull-request workflows without creating skipped validation jobs. Marking a pull
|
|
30
|
+
request ready again starts validation for the current head.
|
|
21
31
|
|
|
22
32
|
The separate `validation-audit.yml` caller is pinned to the configured released
|
|
23
33
|
runtime and handles scheduled and manual audits:
|
|
@@ -39,7 +49,7 @@ Scheduled and manual runs select the audit tier in both topologies. Draft PR
|
|
|
39
49
|
automation separately listens to supported topic-branch pushes and always
|
|
40
50
|
opens those PRs as drafts. Promotion automation listens to `staging` pushes
|
|
41
51
|
(staging-release only) and also always opens its PR as a draft. Release
|
|
42
|
-
automation
|
|
52
|
+
automation and default-branch CodeQL listen to `main` pushes.
|
|
43
53
|
Custom deployment, indexing, search, Slither, or other workflows are
|
|
44
54
|
repository-owned extensions and should use the same ready-transition policy.
|
|
45
55
|
|
|
@@ -91,15 +101,16 @@ opt in or out without a code change.
|
|
|
91
101
|
|
|
92
102
|
## Standard workflow responsibilities
|
|
93
103
|
|
|
94
|
-
| Workflow
|
|
95
|
-
|
|
|
96
|
-
| CI
|
|
97
|
-
| Test
|
|
98
|
-
| Security
|
|
99
|
-
| CodeQL
|
|
100
|
-
| Draft PR
|
|
101
|
-
|
|
|
102
|
-
| Release
|
|
104
|
+
| Workflow | Responsibility |
|
|
105
|
+
| ----------- | ----------------------------------------------------------------- |
|
|
106
|
+
| CI | Format, lint, type-check, and build |
|
|
107
|
+
| Test | Unit, standardized performance, integration, E2E, and smoke tests |
|
|
108
|
+
| Security | Profile, audits, and public-only Dependency Review |
|
|
109
|
+
| CodeQL | GitHub-native code scanning, kept separate from CI |
|
|
110
|
+
| Draft PR | Create/update development pull requests |
|
|
111
|
+
| Draft Guard | Keep ordinary PRs draft until `ready_for_review` |
|
|
112
|
+
| Release PR | Promote `staging` into `main` (staging-release topology only) |
|
|
113
|
+
| Release | Release Please, GitHub release, and optional npm publication |
|
|
103
114
|
|
|
104
115
|
Use concise job names such as `CI / Format`, `Test / Unit`, and
|
|
105
116
|
`CodeQL / Analyze (Python)`. Per-language CodeQL analyzers (Rust shards
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-foundry",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.1",
|
|
4
4
|
"description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
|
|
5
5
|
"homepage": "https://github.com/0xPlayerOne/code-foundry#readme",
|
|
6
6
|
"bugs": {
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { join, resolve } from 'node:path'
|
|
|
5
5
|
import { spawnSync } from 'node:child_process'
|
|
6
6
|
import { includesValue, readConfig } from '../lib/config.mjs'
|
|
7
7
|
import { recommendRunners, resolveProfile } from '../lib/profile.mjs'
|
|
8
|
+
import { packageManagerForLockfile } from '../lib/lockfiles.mjs'
|
|
8
9
|
import { doctorGithub } from '../lib/github-doctor.mjs'
|
|
9
10
|
import { isGeneratedEventCaller } from './sync.mjs'
|
|
10
11
|
|
|
@@ -69,9 +70,7 @@ export function doctor(root, options = {}) {
|
|
|
69
70
|
if (lockfiles.length > 1) error('multiple JavaScript lockfiles found; keep one package manager')
|
|
70
71
|
if (packageJson.packageManager && lockfiles.length) {
|
|
71
72
|
const declared = String(packageJson.packageManager).split('@')[0]
|
|
72
|
-
const actual = lockfiles[0]
|
|
73
|
-
? 'bun'
|
|
74
|
-
: lockfiles[0]?.split('-')[0].replace('.yaml', '')
|
|
73
|
+
const actual = packageManagerForLockfile(lockfiles[0])
|
|
75
74
|
if (actual && declared !== actual)
|
|
76
75
|
error(`packageManager (${declared}) does not match ${actual} lockfile`)
|
|
77
76
|
}
|
|
@@ -150,9 +149,19 @@ export function doctor(root, options = {}) {
|
|
|
150
149
|
const validationEnabled =
|
|
151
150
|
includesValue(features, 'validation') ||
|
|
152
151
|
['ci', 'test', 'security', 'codeql'].some((legacy) => includesValue(features, legacy))
|
|
153
|
-
for (const name of [
|
|
152
|
+
for (const name of [
|
|
153
|
+
'validation',
|
|
154
|
+
'draft-control',
|
|
155
|
+
'draft-enforcement',
|
|
156
|
+
'draft-pr',
|
|
157
|
+
'release-pr',
|
|
158
|
+
'release',
|
|
159
|
+
]) {
|
|
154
160
|
if (name === 'release-pr' && workflow !== 'staging-release') continue
|
|
155
|
-
const enabled =
|
|
161
|
+
const enabled =
|
|
162
|
+
name === 'draft-control' || name === 'draft-enforcement'
|
|
163
|
+
? validationEnabled
|
|
164
|
+
: includesValue(features, name)
|
|
156
165
|
const candidates = [
|
|
157
166
|
join(target, `.github/workflows/${name}.yml`),
|
|
158
167
|
join(target, `.github/workflows/${name}_self-ci.yml`),
|
package/src/commands/release.mjs
CHANGED
package/src/commands/sync.mjs
CHANGED
|
@@ -44,6 +44,7 @@ const standardFiles = [
|
|
|
44
44
|
'.github/workflows/validation.yml',
|
|
45
45
|
'.github/workflows/validation-audit.yml',
|
|
46
46
|
'.github/workflows/draft-control.yml',
|
|
47
|
+
'.github/workflows/draft-enforcement.yml',
|
|
47
48
|
'.github/workflows/draft-pr.yml',
|
|
48
49
|
'.github/workflows/release-pr.yml',
|
|
49
50
|
'.github/workflows/release.yml',
|
|
@@ -221,10 +222,6 @@ export function syncRepository(options) {
|
|
|
221
222
|
const sourceFile = sourcePath(source, file)
|
|
222
223
|
if (!existsSync(sourceFile)) throw new Error(`Template file missing: ${file}`)
|
|
223
224
|
const destination = join(target, file)
|
|
224
|
-
if (!force && protectedFiles.has(file) && existsSync(destination)) {
|
|
225
|
-
const existing = readFileSync(destination, 'utf8')
|
|
226
|
-
if (!isLegacyManagedDoc(file, existing) && !isManagedConfigPolicy(file, existing)) continue
|
|
227
|
-
}
|
|
228
225
|
if (
|
|
229
226
|
(file === 'LICENSE' || file === 'NOTICE') &&
|
|
230
227
|
license === 'preserve' &&
|
|
@@ -267,6 +264,19 @@ export function syncRepository(options) {
|
|
|
267
264
|
mergeIgnorePatternsConfig(content.toString('utf8'), readFileSync(destination, 'utf8'))
|
|
268
265
|
)
|
|
269
266
|
}
|
|
267
|
+
if (!force && protectedFiles.has(file) && existsSync(destination)) {
|
|
268
|
+
const existing = readFileSync(destination, 'utf8')
|
|
269
|
+
if (!isLegacyManagedDoc(file, existing) && !isManagedConfigPolicy(file, existing)) {
|
|
270
|
+
if (configAwarePolicyFiles.has(file)) {
|
|
271
|
+
const merged = mergeManagedPolicyBlocks(existing, content.toString('utf8'))
|
|
272
|
+
if (merged !== existing) {
|
|
273
|
+
changed.push(file)
|
|
274
|
+
writeOrReport(destination, merged, dryRun)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
continue
|
|
278
|
+
}
|
|
279
|
+
}
|
|
270
280
|
if (!existsSync(destination) || !buffersEqual(content, readFileSync(destination))) {
|
|
271
281
|
changed.push(file)
|
|
272
282
|
writeOrReport(destination, content, dryRun)
|
|
@@ -478,7 +488,12 @@ function shouldInclude(file, languages, features, config) {
|
|
|
478
488
|
// the configuration or the variable enables it and the API key exists.
|
|
479
489
|
if (file === '.github/workflows/opencode-security.yml') return true
|
|
480
490
|
const workflow = file.match(/^\.github\/workflows\/([^/]+)\.yml$/)?.[1]
|
|
481
|
-
if (workflow === 'draft-control')
|
|
491
|
+
if (workflow === 'draft-control' || workflow === 'draft-enforcement') {
|
|
492
|
+
return (
|
|
493
|
+
includesValue(features, 'validation') ||
|
|
494
|
+
LEGACY_GENERATED_CALLERS.some((legacy) => includesValue(features, legacy))
|
|
495
|
+
)
|
|
496
|
+
}
|
|
482
497
|
// The staging promotion caller only exists in the staging-release topology;
|
|
483
498
|
// direct repositories open feature branches into main and need no promotion.
|
|
484
499
|
if (workflow === 'release-pr' && !isStagingRelease(config.git_workflow)) return false
|
|
@@ -522,12 +537,15 @@ function renderWorkflow(content, config, repository, ref, rustCodeql) {
|
|
|
522
537
|
let rendered = content.replaceAll(localPrefix, remotePrefix)
|
|
523
538
|
// An explicitly unavailable CodeQL capability selects an orchestrator that
|
|
524
539
|
// omits the job entirely, so GitHub does not register a misleading skipped
|
|
525
|
-
// check on every pull request. `auto` retains
|
|
540
|
+
// check on every pull request or an unused main-push run. `auto` retains
|
|
541
|
+
// runtime capability detection.
|
|
526
542
|
if (configured(config.codeql, 'auto') === 'false') {
|
|
527
543
|
rendered = rendered.replaceAll(
|
|
528
544
|
`${remotePrefix}validation.yml`,
|
|
529
545
|
`${remotePrefix}validation-no-codeql.yml`
|
|
530
546
|
)
|
|
547
|
+
rendered = removeWorkflowBlock(rendered, 'default-branch-codeql')
|
|
548
|
+
rendered = removeWorkflowBlock(rendered, 'push')
|
|
531
549
|
}
|
|
532
550
|
rendered = rendered.replace(
|
|
533
551
|
new RegExp(`${escapeRegExp(remotePrefix)}([^\\s@]+)`, 'g'),
|
|
@@ -625,6 +643,25 @@ function renderWorkflow(content, config, repository, ref, rustCodeql) {
|
|
|
625
643
|
return rendered
|
|
626
644
|
}
|
|
627
645
|
|
|
646
|
+
/**
|
|
647
|
+
* Remove a root-level YAML block by id while preserving the surrounding file.
|
|
648
|
+
* This is used for workflow jobs and triggers whose feature is disabled by
|
|
649
|
+
* configuration.
|
|
650
|
+
* @param {string} content
|
|
651
|
+
* @param {string} blockId
|
|
652
|
+
* @returns {string}
|
|
653
|
+
*/
|
|
654
|
+
function removeWorkflowBlock(content, blockId) {
|
|
655
|
+
const lines = content.split('\n')
|
|
656
|
+
const start = lines.findIndex((line) => line === ` ${blockId}:`)
|
|
657
|
+
if (start === -1) return content
|
|
658
|
+
let end = start + 1
|
|
659
|
+
while (end < lines.length && !/^ [A-Za-z0-9_-]+:\s*$/.test(lines[end])) end += 1
|
|
660
|
+
lines.splice(start, end - start)
|
|
661
|
+
if (content.endsWith('\n') && lines.at(-1) !== '') lines.push('')
|
|
662
|
+
return lines.join('\n')
|
|
663
|
+
}
|
|
664
|
+
|
|
628
665
|
/**
|
|
629
666
|
* Dependabot updates land on the repository's integration branch. Direct
|
|
630
667
|
* repositories have no staging branch, so every update targets main. Cargo,
|
|
@@ -719,8 +756,16 @@ const DIRECT_DOC_REPLACEMENTS = {
|
|
|
719
756
|
'This repository uses the `staging-release` workflow: topic branches **squash** into `staging`, a promotion PR **rebases** validated changes into `main` (`merge_strategy: rebase`), and the Release Please version PR **rebases** into `main` (`release_merge_strategy: rebase`). Feature PRs land on `staging` with squash merges; promotion and release PRs land on `main` with rebase merges. Re-align `staging` with `main` after a release when needed.',
|
|
720
757
|
'This repository uses the `direct` workflow: topic branches **squash** directly into `main`, and the Release Please version PR **squashes** into `main` (`release_merge_strategy: squash`). Feature and release PRs land on `main` with squash merges. No integration branch exists; all pull requests target `main`.',
|
|
721
758
|
],
|
|
759
|
+
[
|
|
760
|
+
'This repository uses the `staging-release` workflow. Topic pull requests target `staging`; promotion pull requests target `main`.',
|
|
761
|
+
'This repository uses the `direct` workflow. Topic pull requests target `main`.',
|
|
762
|
+
],
|
|
722
763
|
],
|
|
723
764
|
'.github/CONTRIBUTING.md': [
|
|
765
|
+
[
|
|
766
|
+
'This repository uses the `staging-release` workflow. Topic pull requests target `staging`; promotion pull requests target `main`.',
|
|
767
|
+
'This repository uses the `direct` workflow. Topic pull requests target `main`.',
|
|
768
|
+
],
|
|
724
769
|
[
|
|
725
770
|
'4. Branch from `staging` and target pull requests at `staging`; do not work directly on `main`.',
|
|
726
771
|
'4. Branch from `main` and target pull requests at `main`; do not push directly to `main`.',
|
|
@@ -756,16 +801,16 @@ const DIRECT_DOC_REPLACEMENTS = {
|
|
|
756
801
|
'7. Push to the fork and open a pull request targeting `main`.',
|
|
757
802
|
],
|
|
758
803
|
[
|
|
759
|
-
'| Event | Expected automation |\n| -------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| Draft pull request targeting `staging` | No runner-heavy validation; run local checks before requesting review |\n| Ready pull request targeting `staging` | Fast validation: CI plus unit tests, ending in `Validation / Gate` |\n| Draft ordinary pull request targeting `main` | No runner-heavy validation; run local checks before requesting review |\n| Ready ordinary pull request targeting `main` | Audit validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate` |\n| Exact Release Please pull request targeting `main` | Full validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate` |\n| Scheduled or manual validation | Full audit tier |\n| Push to a working branch | Draft PR workflow |\n| Push to `staging` | Promotion PR workflow; canonical validation waits for the PR event |\n| Push to `main` | Release workflow;
|
|
760
|
-
'| Event | Expected automation |\n| -------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| Draft pull request targeting `main` | No runner-heavy validation; run local checks before requesting review |\n| Ready pull request targeting `main` | Audit validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate` |\n| Exact Release Please pull request targeting `main` | Full validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate` |\n| Scheduled or manual validation | Full audit tier |\n| Push to a working branch | Draft PR workflow |\n| Push to `main` | Release workflow;
|
|
804
|
+
'| Event | Expected automation |\n| -------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| Draft pull request targeting `staging` | No runner-heavy validation; run local checks before requesting review |\n| Ready pull request targeting `staging` | Fast validation: CI plus unit tests, ending in `Validation / Gate` |\n| Draft ordinary pull request targeting `main` | No runner-heavy validation; run local checks before requesting review |\n| Ready ordinary pull request targeting `main` | Audit validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate` |\n| Exact Release Please pull request targeting `main` | Full validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate` |\n| Scheduled or manual validation | Full audit tier |\n| Push to a working branch | Draft PR workflow |\n| Push to `staging` | Promotion PR workflow; canonical validation waits for the PR event |\n| Push to `main` | Release workflow plus default-branch CodeQL scan; validation ran on the merged PR |\n',
|
|
805
|
+
'| Event | Expected automation |\n| -------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| Draft pull request targeting `main` | No runner-heavy validation; run local checks before requesting review |\n| Ready pull request targeting `main` | Audit validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate` |\n| Exact Release Please pull request targeting `main` | Full validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate` |\n| Scheduled or manual validation | Full audit tier |\n| Push to a working branch | Draft PR workflow |\n| Push to `main` | Release workflow plus default-branch CodeQL scan; validation ran on the merged PR |\n',
|
|
761
806
|
],
|
|
762
807
|
[
|
|
763
808
|
'| Change | Target | Merge method | Merge gate |\n| ---------------------------- | --------- | ----------------------------------------------- | --------------------------------------------------------- |\n| Working branch | `staging` | Squash | All applicable required checks pass |\n| `staging` → `main` promotion | `main` | Rebase (`merge_strategy`) | Current staging checks, release review, and rollout notes |\n| Release Please version PR | `main` | Rebase (`release_merge_strategy`, fails closed) | Validation gate and release policy pass |\n',
|
|
764
809
|
'| Change | Target | Merge method | Merge gate |\n| ------------------------- | ------ | --------------------------------- | --------------------------------------- |\n| Working branch | `main` | Squash | All applicable required checks pass |\n| Release Please version PR | `main` | Squash (`release_merge_strategy`) | Validation gate and release policy pass |\n',
|
|
765
810
|
],
|
|
766
811
|
[
|
|
767
|
-
'Draft pull requests do not start runner-heavy validation. Marking a pull request ready for review starts the applicable validation tier; converting it back to draft cancels in-flight validation, and no replacement starts until it is ready again.',
|
|
768
|
-
'Draft pull requests do not start validation. Marking a pull request ready for review starts the applicable validation tier. Convert it back to draft after an update, then mark it ready again after every update so the required checks attach to the current head. Converting it to draft runs only the lightweight cancellation control.',
|
|
812
|
+
'Draft pull requests do not start runner-heavy validation. The lightweight Draft Guard also converts ordinary pull requests opened, reopened, or updated while ready back to draft; it never checks out pull-request code and it excludes Release Please version heads, whose release workflow owns their state. Marking a pull request ready for review starts the applicable validation tier. Convert it back to draft after an update, then mark it ready again after every update so the required checks attach to the current head; converting it back to draft cancels in-flight validation, and no replacement starts until it is ready again.',
|
|
813
|
+
'Draft pull requests do not start validation. The lightweight Draft Guard also converts ordinary pull requests opened, reopened, or updated while ready back to draft; it never checks out pull-request code and it excludes Release Please version heads, whose release workflow owns their state. Marking a pull request ready for review starts the applicable validation tier. Convert it back to draft after an update, then mark it ready again after every update so the required checks attach to the current head. Converting it to draft runs only the lightweight cancellation control.',
|
|
769
814
|
],
|
|
770
815
|
['1. Create a focused branch from `staging`.', '1. Create a focused branch from `main`.'],
|
|
771
816
|
],
|
|
@@ -1116,9 +1161,12 @@ function isLegacyManagedDoc(file, content) {
|
|
|
1116
1161
|
|
|
1117
1162
|
/**
|
|
1118
1163
|
* Config-aware policy documents are generated contracts: a normal sync must
|
|
1119
|
-
* refresh
|
|
1120
|
-
*
|
|
1121
|
-
*
|
|
1164
|
+
* refresh generated copies after branch-topology or validation-policy edits.
|
|
1165
|
+
* Unmarked consumer-owned documents receive only the explicitly marked policy
|
|
1166
|
+
* blocks, so another agent's initializer cannot be overwritten. The top-level
|
|
1167
|
+
* marker owns generated copies. Exact scaffold signatures migrate older
|
|
1168
|
+
* generated copies without treating arbitrary repository documentation as
|
|
1169
|
+
* managed.
|
|
1122
1170
|
* @param {string} file
|
|
1123
1171
|
* @param {string} content
|
|
1124
1172
|
*/
|
|
@@ -1157,6 +1205,45 @@ function git(target, args) {
|
|
|
1157
1205
|
spawnSync('git', args, { cwd: target, stdio: 'ignore' })
|
|
1158
1206
|
}
|
|
1159
1207
|
|
|
1208
|
+
/**
|
|
1209
|
+
* Merge the marked policy blocks from a rendered baseline into a consumer-owned
|
|
1210
|
+
* policy document. Unmarked AGENTS.md/CONTRIBUTING.md files are commonly
|
|
1211
|
+
* generated by another agent initializer, so sync must add and refresh the
|
|
1212
|
+
* Code Foundry contract without replacing the user's surrounding instructions.
|
|
1213
|
+
* @param {string} existing
|
|
1214
|
+
* @param {string} baseline
|
|
1215
|
+
* @returns {string}
|
|
1216
|
+
*/
|
|
1217
|
+
function mergeManagedPolicyBlocks(existing, baseline) {
|
|
1218
|
+
const blockPattern =
|
|
1219
|
+
/<!-- code-foundry-managed: ([A-Za-z0-9_-]+) -->[\s\S]*?<!-- \/code-foundry-managed: \1 -->/g
|
|
1220
|
+
const blocks = [...baseline.matchAll(blockPattern)]
|
|
1221
|
+
if (!blocks.length) return existing
|
|
1222
|
+
|
|
1223
|
+
let merged = existing
|
|
1224
|
+
for (const match of blocks) {
|
|
1225
|
+
const id = match[1]
|
|
1226
|
+
const block = match[0]
|
|
1227
|
+
const start = `<!-- code-foundry-managed: ${id} -->`
|
|
1228
|
+
const end = `<!-- /code-foundry-managed: ${id} -->`
|
|
1229
|
+
const pattern = new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}`, 'g')
|
|
1230
|
+
const occurrences = merged.match(pattern) ?? []
|
|
1231
|
+
if (occurrences.length > 1) {
|
|
1232
|
+
throw new Error(`Managed policy block appears more than once in consumer document: ${id}`)
|
|
1233
|
+
}
|
|
1234
|
+
if (occurrences.length === 1) {
|
|
1235
|
+
merged = merged.replace(pattern, block)
|
|
1236
|
+
continue
|
|
1237
|
+
}
|
|
1238
|
+
if (merged.includes(start) || merged.includes(end)) {
|
|
1239
|
+
throw new Error(`Managed policy block is incomplete in consumer document: ${id}`)
|
|
1240
|
+
}
|
|
1241
|
+
const separator = merged.endsWith('\n\n') ? '' : merged.endsWith('\n') ? '\n' : '\n\n'
|
|
1242
|
+
merged = `${merged}${separator}${block}\n`
|
|
1243
|
+
}
|
|
1244
|
+
return merged
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1160
1247
|
/** @param {string} file @param {Buffer|string} content @param {boolean} dryRun */
|
|
1161
1248
|
function writeOrReport(file, content, dryRun) {
|
|
1162
1249
|
if (dryRun) {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { readFileSync, readdirSync } from 'node:fs'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
import { spawnSync } from 'node:child_process'
|
|
6
|
+
import { readConfig } from './config.mjs'
|
|
6
7
|
|
|
7
8
|
/** @param {string} root @returns {{ errors: string[], warnings: string[], details: Record<string, unknown> }} */
|
|
8
9
|
export function doctorGithub(root) {
|
|
@@ -306,22 +307,3 @@ function ghJson(args) {
|
|
|
306
307
|
function commandExists(command) {
|
|
307
308
|
return spawnSync(command, ['--version'], { stdio: 'ignore' }).status === 0
|
|
308
309
|
}
|
|
309
|
-
|
|
310
|
-
/** @param {string} root @returns {Record<string, string>} */
|
|
311
|
-
function readConfig(root) {
|
|
312
|
-
const file = join(root, '.github/code-foundry.yml')
|
|
313
|
-
try {
|
|
314
|
-
return Object.fromEntries(
|
|
315
|
-
readFileSync(file, 'utf8')
|
|
316
|
-
.split(/\r?\n/)
|
|
317
|
-
.flatMap((line) => {
|
|
318
|
-
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*?)\s*$/)
|
|
319
|
-
return match
|
|
320
|
-
? [[match[1], match[2].replace(/\s+#.*$/, '').replace(/^['"]|['"]$/g, '')]]
|
|
321
|
-
: []
|
|
322
|
-
})
|
|
323
|
-
)
|
|
324
|
-
} catch {
|
|
325
|
-
return {}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/** @type {Readonly<Record<string, string>>} */
|
|
4
|
+
const managers = Object.freeze({
|
|
5
|
+
'bun.lock': 'bun',
|
|
6
|
+
'bun.lockb': 'bun',
|
|
7
|
+
'pnpm-lock.yaml': 'pnpm',
|
|
8
|
+
'yarn.lock': 'yarn',
|
|
9
|
+
'package-lock.json': 'npm',
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
/** @param {string | undefined} file @returns {string | undefined} */
|
|
13
|
+
export function packageManagerForLockfile(file) {
|
|
14
|
+
return file && Object.hasOwn(managers, file) ? managers[file] : undefined
|
|
15
|
+
}
|