Skip to content

Commit 6816103

Browse files
authored
Include khan packages to make independent (#2)
Final touches to create this action
1 parent cc494a0 commit 6816103

File tree

10 files changed

+133
-112
lines changed

10 files changed

+133
-112
lines changed

.changeset/cyan-insects-whisper.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'mock-workspace': patch
3+
---
4+
5+
Just testing the action!

.github/linters/.markdown-lint.yml

-11
This file was deleted.

.github/workflows/ci.yml

-12
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,3 @@ jobs:
5252
- name: Test Local Action
5353
id: test-action
5454
uses: ./
55-
with:
56-
changed_files: '[]'
57-
58-
- name: Print Success
59-
if: success()
60-
id: success
61-
run: echo "Success"
62-
63-
- name: Print Failure
64-
if: failure()
65-
id: failure
66-
run: echo "Failure"

.github/workflows/linter.yml

+1
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ jobs:
4343
TYPESCRIPT_DEFAULT_STYLE: prettier
4444
VALIDATE_JSCPD: false
4545
VALIDATE_NATURAL_LANGUAGE: false
46+
VALIDATE_MARKDOWN: false

__tests__/main.test.ts

+54-51
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const inputMock = jest
3131
}
3232
})
3333

34+
jest.spyOn(exec, 'exec').mockImplementation(async () => 0)
3435
const getExecOutputMock = jest.spyOn(exec, 'getExecOutput')
3536

3637
const originalContext = github.context
@@ -119,6 +120,58 @@ describe('action', () => {
119120
)
120121
})
121122

123+
it('fails when there are packages that need changeset entries', async () => {
124+
// Arrange
125+
Object.defineProperty(github, 'context', {
126+
value: {
127+
...originalContext,
128+
eventName: 'pull_request',
129+
payload: {
130+
pull_request: {
131+
head: {
132+
sha: '1234567890'
133+
},
134+
base: {
135+
ref: 'main'
136+
}
137+
}
138+
}
139+
}
140+
})
141+
getExecOutputMock
142+
// workspaces info
143+
.mockImplementationOnce(async () => ({
144+
stdout: JSON.stringify({
145+
type: 'log',
146+
data: JSON.stringify({
147+
'@owner/pkg1': {
148+
location: './packages/pkg1'
149+
},
150+
'@owner/pkgB': {
151+
location: './packages/pkgB'
152+
}
153+
})
154+
}),
155+
stderr: '',
156+
exitCode: 0
157+
}))
158+
// changeset info
159+
.mockImplementationOnce(async () => ({
160+
stdout: '',
161+
stderr: '',
162+
exitCode: 1
163+
}))
164+
165+
// Act
166+
await main.run()
167+
168+
// Assert
169+
expect(runMock).toHaveReturned()
170+
expect(setFailedMock).toHaveBeenCalledWith(
171+
'Changeset entry required for @owner/pkg1, @owner/pkgB because there have been changes since the last release.'
172+
)
173+
})
174+
122175
it('skips without failure when there are no changed files', async () => {
123176
// Arrange
124177
inputMock.mockImplementationOnce(() => JSON.stringify([]))
@@ -205,56 +258,6 @@ describe('action', () => {
205258
expect(infoMock).toHaveBeenCalledWith('No packages to verify. Skipping.')
206259
})
207260

208-
it('fails when changeset CLI call fails', async () => {
209-
// Arrange
210-
Object.defineProperty(github, 'context', {
211-
value: {
212-
...originalContext,
213-
eventName: 'pull_request',
214-
payload: {
215-
pull_request: {
216-
head: {
217-
sha: '1234567890'
218-
},
219-
base: {
220-
ref: 'main'
221-
}
222-
}
223-
}
224-
}
225-
})
226-
getExecOutputMock
227-
// workspaces info
228-
.mockImplementationOnce(async () => ({
229-
stdout: JSON.stringify({
230-
type: 'log',
231-
data: JSON.stringify({
232-
'@owner/pkg1': {
233-
location: './packages/pkg1'
234-
},
235-
'@owner/pkgB': {
236-
location: './packages/pkgB'
237-
}
238-
})
239-
}),
240-
stderr: '',
241-
exitCode: 0
242-
}))
243-
// changeset info
244-
.mockImplementationOnce(async () => ({
245-
stdout: '',
246-
stderr: 'Oopsie!',
247-
exitCode: 1
248-
}))
249-
250-
// Act
251-
await main.run()
252-
253-
// Assert
254-
expect(runMock).toHaveReturned()
255-
expect(setFailedMock).toHaveBeenCalledWith('Oopsie!')
256-
})
257-
258261
it('fails when workspaces CLI call fails', async () => {
259262
// Arrange
260263
Object.defineProperty(github, 'context', {
@@ -286,7 +289,7 @@ describe('action', () => {
286289

287290
// Assert
288291
expect(runMock).toHaveReturned()
289-
expect(setFailedMock).toHaveBeenCalledWith('Oopsie!')
292+
expect(setFailedMock).toHaveBeenCalled()
290293
})
291294

292295
it('fails when there is no base ref', async () => {

action.yml

+35-4
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,48 @@ description: 'Fail if there is not a valid changeset entry for each package.'
33
author: 'Ned Redmond'
44

55
inputs:
6-
changed_files:
7-
description: JSON list of changed files
8-
required: true
6+
exclude_files_and_dirs:
7+
description:
8+
list of comma-separated paths (files or directories ending in /) that
9+
don't need a changeset when changed. Default .github/
10+
required: false
11+
default: .github/
12+
exclude_extensions:
13+
description:
14+
a list of comma-separated extensions that don't need a changeset when
15+
changed. Default <empty>
16+
required: false
17+
default: ''
18+
exclude_globs:
19+
description:
20+
a list of comma-separated globs (using picomatch syntax) that don't need a
21+
changeset when changed. Default <empty>
22+
required: false
23+
default: ''
924

1025
runs:
1126
using: 'composite'
1227
steps:
1328
- name: Checkout repository
1429
uses: actions/checkout@v4
30+
with:
31+
fetch-depth: 0
32+
33+
- name: Get changed files
34+
uses: Khan/actions@get-changed-files-v1
35+
id: changed
36+
37+
- name: Filter out files that don't need a changeset
38+
uses: Khan/actions@filter-files-v0
39+
id: match
40+
with:
41+
changed-files: ${{ steps.changed.outputs.files }}
42+
invert: true
43+
files: ${{ inputs.exclude_files_and_dirs }}
44+
extensions: ${{ inputs.exclude_extensions }}
45+
globs: ${{ inputs.exclude_globs }}
1546

1647
- name: Veryify changeset entries
1748
uses: ./actions/verify-per-package
1849
with:
19-
changed_files: ${{ inputs.changed_files }}
50+
changed_files: ${{ steps.match.outputs.filtered }}

actions/verify-per-package/dist/index.js

+12-14
Original file line numberDiff line numberDiff line change
@@ -10916,9 +10916,6 @@ async function run() {
1091610916
// relies on the repo having yarn...
1091710917
// a future enhancement would be to determine the package manager and use that
1091810918
const workspacesResult = await exec.getExecOutput(`yarn workspaces --json info`);
10919-
if (workspacesResult.exitCode !== 0) {
10920-
core.setFailed(workspacesResult.stderr);
10921-
}
1092210919
const workspacesInfo = JSON.parse(JSON.parse(workspacesResult.stdout).data);
1092310920
const workspaces = Object.keys(workspacesInfo).map(name => ({
1092410921
name,
@@ -10954,22 +10951,23 @@ async function run() {
1095410951
}
1095510952
// right now, the only way to access JSON output is to create a file,
1095610953
// so we are just going to work with the pretty-printed output
10957-
const changesetResult = await exec.getExecOutput(`yarn changeset status --since ${base}`);
10958-
if (changesetResult.exitCode !== 0) {
10959-
core.setFailed(changesetResult.stderr);
10960-
}
10961-
if (!changesetResult.stdout) {
10962-
core.setFailed(`Changeset entries are required for the following packages: ${packageNamesArray.join(', ')}`);
10954+
await exec.exec(`yarn add @changesets/cli@latest -W`);
10955+
const changesetResult = await exec.getExecOutput(`yarn changeset status --since origin/${base}`, undefined, { ignoreReturnCode: true });
10956+
if (changesetResult.exitCode === 1) {
10957+
core.debug(`Changeset status failed; that could mean there is no changeset file, or that there was an error.`);
1096310958
}
1096410959
// parse out the package names from the pretty-printed changeset output
10965-
const changesetEntries = changesetResult.stdout
10966-
.split('\n')
10967-
.map((line) => line.trim())
10968-
.filter((line) => line.startsWith('🦋 - '))
10969-
.map((line) => line.replace('🦋 - ', ''));
10960+
const changesetEntries = changesetResult.exitCode === 1
10961+
? []
10962+
: changesetResult.stdout
10963+
.split('\n')
10964+
.map((line) => line.trim())
10965+
.filter((line) => line.startsWith('🦋 - '))
10966+
.map((line) => line.replace('🦋 - ', ''));
1097010967
const changesetEntriesNeeded = packageNamesArray.filter(packageName => !changesetEntries.includes(packageName));
1097110968
if (changesetEntriesNeeded.length) {
1097210969
core.setFailed(`Changeset entry required for ${changesetEntriesNeeded.join(', ')} because there have been changes since the last release.`);
10970+
return;
1097310971
}
1097410972
core.info('All packages have changeset entries');
1097510973
}

mock-workspace/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "mock-workspace",
3+
"version": "1.0.0",
4+
"description": "Just here for workflow test. See `.github/workflows/ci.yml`"
5+
}

package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"format:write": "prettier --write **/*.ts",
3030
"format:check": "prettier --check **/*.ts",
3131
"lint": "npx eslint . -c ./.github/linters/.eslintrc.yml",
32-
"package": "ncc build src/index.ts --out sub-actions/verify-per-package/dist --license licenses.txt",
32+
"package": "ncc build src/index.ts -o actions/verify-per-package/dist --license licenses.txt",
3333
"package:watch": "yarn package -- --watch",
3434
"test": "(jest && make-coverage-badge --output-path ./badges/coverage.svg) || make-coverage-badge --output-path ./badges/coverage.svg",
3535
"all": "yarn format:write && yarn lint && yarn test && yarn package"
@@ -88,5 +88,8 @@
8888
"prettier-eslint": "^15.0.1",
8989
"ts-jest": "^29.1.1",
9090
"typescript": "^5.2.2"
91-
}
91+
},
92+
"workspaces": [
93+
"mock-workspace"
94+
]
9295
}

src/main.ts

+16-18
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ export async function run(): Promise<void> {
3535
`yarn workspaces --json info`
3636
)
3737

38-
if (workspacesResult.exitCode !== 0) {
39-
core.setFailed(workspacesResult.stderr)
40-
}
41-
4238
const workspacesInfo = JSON.parse(JSON.parse(workspacesResult.stdout).data)
4339
const workspaces = Object.keys(workspacesInfo).map(name => ({
4440
name,
@@ -81,27 +77,28 @@ export async function run(): Promise<void> {
8177

8278
// right now, the only way to access JSON output is to create a file,
8379
// so we are just going to work with the pretty-printed output
80+
await exec.exec(`yarn add @changesets/cli@latest -W`)
8481
const changesetResult = await exec.getExecOutput(
85-
`yarn changeset status --since ${base}`
82+
`yarn changeset status --since origin/${base}`,
83+
undefined,
84+
{ ignoreReturnCode: true }
8685
)
8786

88-
if (changesetResult.exitCode !== 0) {
89-
core.setFailed(changesetResult.stderr)
90-
}
91-
if (!changesetResult.stdout) {
92-
core.setFailed(
93-
`Changeset entries are required for the following packages: ${packageNamesArray.join(
94-
', '
95-
)}`
87+
if (changesetResult.exitCode === 1) {
88+
core.debug(
89+
`Changeset status failed; that could mean there is no changeset file, or that there was an error.`
9690
)
9791
}
9892

9993
// parse out the package names from the pretty-printed changeset output
100-
const changesetEntries = changesetResult.stdout
101-
.split('\n')
102-
.map((line: string) => line.trim())
103-
.filter((line: string) => line.startsWith('🦋 - '))
104-
.map((line: string) => line.replace('🦋 - ', ''))
94+
const changesetEntries =
95+
changesetResult.exitCode === 1
96+
? []
97+
: changesetResult.stdout
98+
.split('\n')
99+
.map((line: string) => line.trim())
100+
.filter((line: string) => line.startsWith('🦋 - '))
101+
.map((line: string) => line.replace('🦋 - ', ''))
105102

106103
const changesetEntriesNeeded = packageNamesArray.filter(
107104
packageName => !changesetEntries.includes(packageName)
@@ -113,6 +110,7 @@ export async function run(): Promise<void> {
113110
', '
114111
)} because there have been changes since the last release.`
115112
)
113+
return
116114
}
117115

118116
core.info('All packages have changeset entries')

0 commit comments

Comments
 (0)