|
5 | 5 | */ |
6 | 6 |
|
7 | 7 | const { execFileSync } = require('node:child_process'); |
8 | | -const { readFileSync, readdirSync, appendFileSync } = require('node:fs'); |
| 8 | +const { |
| 9 | + readFileSync, |
| 10 | + existsSync, |
| 11 | + unlinkSync, |
| 12 | + appendFileSync, |
| 13 | +} = require('node:fs'); |
9 | 14 | const path = require('node:path'); |
10 | 15 |
|
11 | 16 | // Top-level dirs whose packages must carry a changeset when changed |
@@ -83,18 +88,28 @@ for (const dir of changedDirs) { |
83 | 88 | } |
84 | 89 | } |
85 | 90 |
|
86 | | -// Packages named across the frontmatter of every changeset file (the "covered" |
87 | | -// set) - read straight from disk so pending and uncommitted changesets count |
88 | | -const changesetDir = path.join(process.cwd(), '.changeset'); |
| 91 | +// Ask changesets which packages are marked for release (the "covered" set). |
| 92 | +// Going through the CLI means we never parse the changeset file format ourselves |
89 | 93 | const covered = new Set(); |
90 | | -for (const file of readdirSync(changesetDir)) { |
91 | | - if (!file.endsWith('.md') || file.toLowerCase() === 'readme.md') { |
92 | | - continue; |
93 | | - } |
94 | | - const contents = readFileSync(path.join(changesetDir, file), 'utf8'); |
95 | | - for (const name of changesetPackages(contents)) { |
96 | | - covered.add(name); |
| 94 | +const planFile = path.join(process.cwd(), '.changeset-status.json'); |
| 95 | +try { |
| 96 | + execFileSync('pnpm', ['exec', 'changeset', 'status', '--output', planFile], { |
| 97 | + stdio: ['ignore', 'pipe', 'pipe'], |
| 98 | + }); |
| 99 | +} catch { |
| 100 | + // status exits non-zero and writes nothing when packages changed but no |
| 101 | + // changesets exist yet - nothing is marked for release, covered stays empty |
| 102 | +} |
| 103 | +if (existsSync(planFile)) { |
| 104 | + const plan = JSON.parse(readFileSync(planFile, 'utf8')); |
| 105 | + // changesets[] = packages a changeset explicitly names. Not releases[], which |
| 106 | + // also includes transitive dependency bumps we don't want to treat as covered |
| 107 | + for (const changeset of plan.changesets) { |
| 108 | + for (const release of changeset.releases) { |
| 109 | + covered.add(release.name); |
| 110 | + } |
97 | 111 | } |
| 112 | + unlinkSync(planFile); |
98 | 113 | } |
99 | 114 |
|
100 | 115 | // Any changed package that no changeset accounts for |
@@ -140,22 +155,3 @@ if (process.env.GITHUB_STEP_SUMMARY) { |
140 | 155 | } |
141 | 156 |
|
142 | 157 | process.exit(1); |
143 | | - |
144 | | -// Package names listed in a changeset's frontmatter block |
145 | | -function changesetPackages(contents) { |
146 | | - const frontmatter = contents.match(/^---\r?\n([\s\S]*?)\r?\n---/); |
147 | | - if (!frontmatter) { |
148 | | - return []; |
149 | | - } |
150 | | - const names = []; |
151 | | - for (const line of frontmatter[1].split('\n')) { |
152 | | - // e.g. '@openfn/cli': minor or "@openfn/cli": patch |
153 | | - const entry = line.match( |
154 | | - /^\s*['"]?(@?[\w./-]+)['"]?\s*:\s*(patch|minor|major)\s*$/ |
155 | | - ); |
156 | | - if (entry) { |
157 | | - names.push(entry[1]); |
158 | | - } |
159 | | - } |
160 | | - return names; |
161 | | -} |
0 commit comments