Skip to content

Commit 18fc8ef

Browse files
committed
Fix broken package names being used in commands
1 parent f767caf commit 18fc8ef

File tree

6 files changed

+121
-90
lines changed

6 files changed

+121
-90
lines changed

.github/actions/src/info/__tests__/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import pathlib from 'path';
44
import * as core from '@actions/core';
55
import { describe, expect, test, vi } from 'vitest';
66
import * as git from '../../commons.js';
7-
import * as lockfiles from '../../lockfiles.js';
7+
import * as lockfiles from '../../lockfiles/index.js';
88
import { getAllPackages, getRawPackages, main } from '../index.js';
99

1010
const mockedCheckChanges = vi.spyOn(git, 'checkDirForChanges');

.github/actions/src/info/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { SummaryTableRow } from '@actions/core/lib/summary.js';
66
import packageJson from '../../../../package.json' with { type: 'json' };
77
import { checkDirForChanges, type PackageRecord, type RawPackageRecord } from '../commons.js';
88
import { gitRoot } from '../gitRoot.js';
9-
import { getPackagesWithResolutionChanges, hasLockFileChanged } from '../lockfiles.js';
9+
import { getPackagesWithResolutionChanges, hasLockFileChanged } from '../lockfiles/index.js';
1010
import { topoSortPackages } from './sorter.js';
1111

1212
const packageNameRE = /^@sourceacademy\/(.+?)-(.+)$/u;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
import * as utils from '../utils.js';
3+
4+
vi.mock(import('../../gitRoot.js'), () => ({
5+
gitRoot: 'root'
6+
}));
7+
8+
describe(utils.extractPackageName, () => {
9+
it('works with packages that start with @', () => {
10+
expect(utils.extractPackageName('@sourceacademy/tab-Rune@workspace:^'))
11+
.toEqual('@sourceacademy/tab-Rune');
12+
});
13+
14+
it('works with regular package names', () => {
15+
expect(utils.extractPackageName('lodash@npm:^4.17.20'))
16+
.toEqual('lodash');
17+
});
18+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { getExecOutput } from '@actions/exec';
2+
import memoize from 'lodash/memoize.js';
3+
import uniq from 'lodash/uniq.js';
4+
import { getPackageDiffs, getPackageReason } from './utils.js';
5+
6+
/**
7+
* Returns `true` if there are changes present in the given directory relative to
8+
* the master branch\
9+
* Used to determine, particularly for libraries, if running tests and tsc are necessary
10+
*/
11+
export const hasLockFileChanged = memoize(async () => {
12+
const { exitCode } = await getExecOutput(
13+
'git --no-pager diff --quiet origin/master -- yarn.lock',
14+
[],
15+
{
16+
failOnStdErr: false,
17+
ignoreReturnCode: true
18+
}
19+
);
20+
return exitCode !== 0;
21+
});
22+
23+
/**
24+
* Gets a list of packages that have had some dependency of theirs
25+
* change according to the lockfile but not their package.json
26+
*/
27+
export async function getPackagesWithResolutionChanges() {
28+
const packages = [...await getPackageDiffs()];
29+
const workspaces = await Promise.all(packages.map(getPackageReason));
30+
return uniq(workspaces.flat());
31+
}
Lines changed: 63 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -2,75 +2,61 @@ import fs from 'fs/promises';
22
import pathlib from 'path';
33
import * as core from '@actions/core';
44
import { getExecOutput } from '@actions/exec';
5-
import memoize from 'lodash/memoize.js';
6-
import uniq from 'lodash/uniq.js';
75
import { extractPkgsFromYarnLockV2 } from 'snyk-nodejs-lockfile-parser';
8-
import { gitRoot } from './gitRoot.js';
6+
import { gitRoot } from '../gitRoot.js';
7+
8+
const packageNameRE = /^(.+)@.+$/;
99

1010
/**
11-
* Parses and lockfile's contents and extracts all the different dependencies and
12-
* versions
11+
* Lockfile specifications come in the form of package_name@resolution, but
12+
* we only want the package name. This function extracts that package name,
13+
* accounting for the fact that package names might start with '@'
1314
*/
14-
function processLockFileText(contents: string) {
15-
const lockFile = extractPkgsFromYarnLockV2(contents);
16-
return Object.entries(lockFile).reduce<Record<string, Set<string>>>((res, [key, { version }]) => {
17-
const newKey = key.split('@')[0];
18-
19-
if (!(newKey in res)) {
20-
res[newKey] = new Set();
21-
}
15+
export function extractPackageName(raw: string) {
16+
const match = packageNameRE.exec(raw);
17+
if (!match) {
18+
throw new Error(`Invalid package name: ${raw}`);
19+
}
2220

23-
res[newKey].add(version);
24-
return res;
25-
}, {});
21+
return match[1];
2622
}
2723

2824
/**
29-
* Retrieves the contents of the lockfile on the master branch
25+
* Using `yarn why`, determine why the given package is present in the
26+
* lockfile.
3027
*/
31-
async function getMasterLockFile() {
32-
const { stdout, exitCode } = await getExecOutput(
33-
'git',
34-
[
35-
'--no-pager',
36-
'show',
37-
'origin/master:yarn.lock'
38-
],
39-
);
40-
28+
export async function getPackageReason(pkg: string) {
29+
const { stdout, stderr, exitCode } = await getExecOutput('yarn why', [pkg, '-R', '--json'], { silent: true });
4130
if (exitCode !== 0) {
42-
throw new Error('git show exited with non-zero error-code');
31+
core.error(stderr);
32+
throw new Error(`yarn why for '${pkg}' exited with non-zero exit code!`);
4333
}
4434

45-
return processLockFileText(stdout);
46-
}
47-
48-
/**
49-
* Retrieves the contents of the lockfile in the repo
50-
*/
51-
async function getCurrentLockFile() {
52-
const lockFilePath = pathlib.join(gitRoot, 'yarn.lock');
53-
const contents = await fs.readFile(lockFilePath, 'utf-8');
54-
return processLockFileText(contents);
35+
return stdout.trim().split('\n').map(each => {
36+
const entry = JSON.parse(each).value;
37+
return extractPackageName(entry);
38+
});
5539
}
5640

5741
/**
5842
* Determines the names of the packages that have changed versions
5943
*/
60-
async function getPackageDiffs() {
44+
export async function getPackageDiffs() {
6145
const [currentLockFile, masterLockFile] = await Promise.all([
6246
getCurrentLockFile(),
6347
getMasterLockFile()
6448
]);
6549

66-
const packages = [];
50+
const packages = new Set<string>();
51+
6752
for (const [depName, versions] of Object.entries(currentLockFile)) {
53+
if (packages.has(depName)) continue;
54+
6855
if (!(depName in masterLockFile)) {
6956
core.info(`${depName} in current lockfile, not in master lock file`);
7057
continue;
7158
}
7259

73-
const args = depName.split('@');
7460
let needToAdd = false;
7561
for (const version of versions) {
7662
if (!masterLockFile[depName].has(version)) {
@@ -86,65 +72,56 @@ async function getPackageDiffs() {
8672
}
8773
}
8874

89-
if (needToAdd) {
90-
if (depName.startsWith('@')) {
91-
packages.push(args[1]);
92-
} else {
93-
packages.push(args[0]);
94-
}
95-
}
75+
if (needToAdd) packages.add(depName);
9676
}
9777

98-
return uniq(packages);
78+
return packages;
9979
}
10080

10181
/**
102-
* Using `yarn why`, determine why the given package is present in the
103-
* lockfile.
82+
* Retrieves the contents of the lockfile in the repo
10483
*/
105-
async function getPackageReason(pkg: string) {
106-
const packageNameRE = /^(@?sourceacademy\/.+)@.+$/;
84+
export async function getCurrentLockFile() {
85+
const lockFilePath = pathlib.join(gitRoot, 'yarn.lock');
86+
const contents = await fs.readFile(lockFilePath, 'utf-8');
87+
return processLockFileText(contents);
88+
}
89+
90+
/**
91+
* Retrieves the contents of the lockfile on the master branch
92+
*/
93+
export async function getMasterLockFile() {
94+
const { stdout, exitCode } = await getExecOutput(
95+
'git',
96+
[
97+
'--no-pager',
98+
'show',
99+
'origin/master:yarn.lock'
100+
],
101+
{ silent: true }
102+
);
107103

108-
const { stdout, stderr, exitCode } = await getExecOutput('yarn why', [pkg, '-R', '--json'], { silent: true });
109104
if (exitCode !== 0) {
110-
core.error(stderr);
111-
throw new Error(`yarn why for ${pkg} exited with non-zero exit code!`);
105+
throw new Error('git show exited with non-zero error-code');
112106
}
113107

114-
return stdout.trim().split('\n').map(each => {
115-
const entry = JSON.parse(each).value;
116-
const match = packageNameRE.exec(entry);
117-
if (!match) {
118-
throw new Error('failed to identify a package!');
119-
}
120-
121-
return match[1];
122-
});
108+
return processLockFileText(stdout);
123109
}
124110

125111
/**
126-
* Returns `true` if there are changes present in the given directory relative to
127-
* the master branch\
128-
* Used to determine, particularly for libraries, if running tests and tsc are necessary
112+
* Parses and lockfile's contents and extracts all the different dependencies and
113+
* versions
129114
*/
130-
export const hasLockFileChanged = memoize(async () => {
131-
const { exitCode } = await getExecOutput(
132-
'git',
133-
['--no-pager', 'diff', '--quiet', 'origin/master', '--', 'yarn.lock'],
134-
{
135-
failOnStdErr: false,
136-
ignoreReturnCode: true
115+
export function processLockFileText(contents: string) {
116+
const lockFile = extractPkgsFromYarnLockV2(contents);
117+
return Object.entries(lockFile).reduce<Record<string, Set<string>>>((res, [key, { version }]) => {
118+
const newKey = extractPackageName(key);
119+
120+
if (!(newKey in res)) {
121+
res[newKey] = new Set();
137122
}
138-
);
139-
return exitCode !== 0;
140-
});
141123

142-
/**
143-
* Gets a list of packages that have had some dependency of theirs
144-
* change according to the lockfile but not their package.json
145-
*/
146-
export async function getPackagesWithResolutionChanges() {
147-
const packages = await getPackageDiffs();
148-
const workspaces = await Promise.all(packages.map(getPackageReason));
149-
return uniq(workspaces.flat());
124+
res[newKey].add(version);
125+
return res;
126+
}, {});
150127
}

.github/actions/tsconfig.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
{
2-
"include": ["src/**/*.ts", "vitest.setup.ts"],
2+
"include": [
3+
"src/**/*.ts",
4+
"vitest.setup.ts"
5+
],
36
"compilerOptions": {
47
"forceConsistentCasingInFileNames": true,
5-
"lib": ["ES2020"],
8+
"lib": [
9+
"ES2020"
10+
],
611
"module": "esnext",
712
"moduleResolution": "bundler",
813
"noEmit": true,

0 commit comments

Comments
 (0)