Skip to content

Commit bd0351a

Browse files
authored
refactor: improve publish code (#796)
1 parent 260692e commit bd0351a

File tree

17 files changed

+303
-129
lines changed

17 files changed

+303
-129
lines changed

global-setup.e2e.ts

+22-11
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
1-
import { execSync } from 'child_process';
1+
import { execFileSync, execSync } from 'child_process';
22
import { setup as globalSetup } from './global-setup';
33
import { setupTestFolder, teardownTestFolder } from './testing/test-setup/src';
44
import startLocalRegistry from './tools/scripts/start-local-registry';
55
import stopLocalRegistry from './tools/scripts/stop-local-registry';
66

7+
const localRegistryNxTarget = '@code-pushup/cli-source:local-registry';
8+
79
export async function setup() {
810
await globalSetup();
9-
await startLocalRegistry();
10-
execSync('npm install -D @code-pushup/cli@e2e');
11-
execSync('npm install -D @code-pushup/nx-plugin@e2e');
12-
execSync('npm install -D @code-pushup/eslint-plugin@e2e');
13-
execSync('npm install -D @code-pushup/coverage-plugin@e2e');
14-
await setupTestFolder('tmp/e2e');
11+
try {
12+
await setupTestFolder('tmp/local-registry');
13+
await startLocalRegistry({ localRegistryTarget: localRegistryNxTarget });
14+
console.info('Installing packages');
15+
execFileSync(
16+
'npx',
17+
['nx', 'run-many', '--targets=npm-install', '--parallel=1'],
18+
{ env: process.env, stdio: 'inherit', shell: true },
19+
);
20+
await setupTestFolder('tmp/e2e');
21+
} catch (error) {
22+
console.info('setup error: ' + error.message);
23+
}
1524
}
1625

1726
export async function teardown() {
1827
stopLocalRegistry();
19-
execSync('npm uninstall @code-pushup/cli');
20-
execSync('npm uninstall @code-pushup/nx-plugin');
21-
execSync('npm uninstall @code-pushup/eslint-plugin');
22-
execSync('npm uninstall @code-pushup/coverage-plugin');
28+
console.info('Uninstalling packages');
29+
execFileSync(
30+
'npx',
31+
['nx', 'run-many', '--targets=npm-uninstall', '--parallel=1'],
32+
{ env: process.env, stdio: 'inherit', shell: true },
33+
);
2334
await teardownTestFolder('tmp/e2e');
2435
await teardownTestFolder('tmp/local-registry');
2536
}

nx.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,6 @@
109109
}
110110
},
111111
"releaseTagPattern": "v{version}"
112-
}
112+
},
113+
"plugins": ["./tools/scripts/publish.plugin.ts"]
113114
}

packages/cli/project.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
"esbuildConfig": "esbuild.config.js"
1616
}
1717
},
18-
"publish": {
19-
"command": "node tools/scripts/publish.mjs --name=cli --ver={args.ver} --tag={args.tag}",
20-
"dependsOn": ["build"]
21-
},
18+
"publish": {},
2219
"lint": {
2320
"executor": "@nx/linter:eslint",
2421
"outputs": ["{options.outputFile}"],

packages/core/project.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
"esbuildConfig": "esbuild.config.js"
1616
}
1717
},
18-
"publish": {
19-
"command": "node tools/scripts/publish.mjs --name=core --ver={args.ver} --tag={args.tag}",
20-
"dependsOn": ["build"]
21-
},
18+
"publish": {},
2219
"lint": {
2320
"executor": "@nx/linter:eslint",
2421
"outputs": ["{options.outputFile}"],

packages/models/project.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
"esbuildConfig": "esbuild.config.js"
1616
}
1717
},
18-
"publish": {
19-
"command": "node tools/scripts/publish.mjs --name=models --ver={args.ver} --tag={args.tag}",
20-
"dependsOn": ["build"]
21-
},
18+
"publish": {},
2219
"lint": {
2320
"executor": "@nx/linter:eslint",
2421
"outputs": ["{options.outputFile}"],

packages/nx-plugin/project.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@
3737
]
3838
}
3939
},
40-
"publish": {
41-
"command": "node tools/scripts/publish.mjs --name=nx-plugin --ver={args.ver} --tag={args.tag}",
42-
"dependsOn": ["build"]
43-
},
40+
"publish": {},
4441
"lint": {
4542
"executor": "@nx/linter:eslint",
4643
"outputs": ["{options.outputFile}"],

packages/plugin-coverage/project.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,7 @@
3838
"configFile": "packages/plugin-coverage/vite.config.integration.ts"
3939
}
4040
},
41-
"publish": {
42-
"command": "node tools/scripts/publish.mjs --name=plugin-coverage --ver={args.ver} --tag={args.tag}",
43-
"dependsOn": ["build"]
44-
}
41+
"publish": {}
4542
},
4643
"tags": ["scope:plugin", "type:feature"]
4744
}

packages/plugin-eslint/project.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@
1616
"esbuildConfig": "esbuild.config.js"
1717
}
1818
},
19-
"publish": {
20-
"command": "node tools/scripts/publish.mjs --name=plugin-eslint --ver={args.ver} --tag={args.tag}",
21-
"dependsOn": ["build"]
22-
},
19+
"publish": {},
2320
"lint": {
2421
"executor": "@nx/linter:eslint",
2522
"outputs": ["{options.outputFile}"],

packages/plugin-js-packages/project.json

+3-5
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,8 @@
3838
"configFile": "packages/plugin-js-packages/vite.config.integration.ts"
3939
}
4040
},
41-
"publish": {
42-
"command": "node tools/scripts/publish.mjs --name=plugin-js-packages --ver={args.ver} --tag={args.tag}",
43-
"dependsOn": ["build"]
44-
}
41+
"publish": {}
4542
},
46-
"tags": ["scope:plugin", "type:feature"]
43+
"tags": ["scope:plugin", "type:feature"],
44+
"description": "A plugin for JavaScript packages."
4745
}

packages/plugin-lighthouse/project.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
"options": {
3737
"configFile": "packages/plugin-lighthouse/vite.config.integration.ts"
3838
}
39-
}
39+
},
40+
"publish": {}
4041
},
4142
"tags": ["scope:plugin", "type:feature"]
4243
}

packages/utils/project.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
"esbuildConfig": "esbuild.config.js"
1616
}
1717
},
18-
"publish": {
19-
"command": "node tools/scripts/publish.mjs --name=utils --ver={args.ver} --tag={args.tag}",
20-
"dependsOn": ["build"]
21-
},
18+
"publish": {},
2219
"lint": {
2320
"executor": "@nx/linter:eslint",
2421
"outputs": ["{options.outputFile}"],

packages/utils/src/lib/execute-process.ts

+27-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { spawn } from 'node:child_process';
1+
import {
2+
ChildProcess,
3+
ChildProcessByStdio,
4+
SpawnOptionsWithStdioTuple,
5+
StdioPipe,
6+
spawn,
7+
} from 'node:child_process';
8+
import { Readable, Writable } from 'node:stream';
29
import { calcDuration } from './reports/utils';
310

411
/**
@@ -77,10 +84,12 @@ export class ProcessError extends Error {
7784
* args: ['--version']
7885
*
7986
*/
80-
export type ProcessConfig = {
87+
export type ProcessConfig = Omit<
88+
SpawnOptionsWithStdioTuple<StdioPipe, StdioPipe, StdioPipe>,
89+
'stdio'
90+
> & {
8191
command: string;
8292
args?: string[];
83-
cwd?: string;
8493
observer?: ProcessObserver;
8594
ignoreExitCode?: boolean;
8695
};
@@ -99,7 +108,8 @@ export type ProcessConfig = {
99108
* }
100109
*/
101110
export type ProcessObserver = {
102-
onStdout?: (stdout: string) => void;
111+
onStdout?: (stdout: string, sourceProcess?: ChildProcess) => void;
112+
onStderr?: (stderr: string, sourceProcess?: ChildProcess) => void;
103113
onError?: (error: ProcessError) => void;
104114
onComplete?: () => void;
105115
};
@@ -133,33 +143,38 @@ export type ProcessObserver = {
133143
* @param cfg - see {@link ProcessConfig}
134144
*/
135145
export function executeProcess(cfg: ProcessConfig): Promise<ProcessResult> {
136-
const { observer, cwd, command, args, ignoreExitCode = false } = cfg;
137-
const { onStdout, onError, onComplete } = observer ?? {};
146+
const { command, args, observer, ignoreExitCode = false, ...options } = cfg;
147+
const { onStdout, onStderr, onError, onComplete } = observer ?? {};
138148
const date = new Date().toISOString();
139149
const start = performance.now();
140150

141151
return new Promise((resolve, reject) => {
142152
// shell:true tells Windows to use shell command for spawning a child process
143-
const process = spawn(command, args, { cwd, shell: true });
153+
const spawnedProcess = spawn(command, args ?? [], {
154+
shell: true,
155+
...options,
156+
}) as ChildProcessByStdio<Writable, Readable, Readable>;
157+
144158
// eslint-disable-next-line functional/no-let
145159
let stdout = '';
146160
// eslint-disable-next-line functional/no-let
147161
let stderr = '';
148162

149-
process.stdout.on('data', data => {
163+
spawnedProcess.stdout.on('data', data => {
150164
stdout += String(data);
151-
onStdout?.(String(data));
165+
onStdout?.(String(data), spawnedProcess);
152166
});
153167

154-
process.stderr.on('data', data => {
168+
spawnedProcess.stderr.on('data', data => {
155169
stderr += String(data);
170+
onStderr?.(String(data), spawnedProcess);
156171
});
157172

158-
process.on('error', err => {
173+
spawnedProcess.on('error', err => {
159174
stderr += err.toString();
160175
});
161176

162-
process.on('close', code => {
177+
spawnedProcess.on('close', code => {
163178
const timings = { date, duration: calcDuration(start) };
164179
if (code === 0 || ignoreExitCode) {
165180
onComplete?.();

packages/utils/src/lib/execute-process.unit.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import { ChildProcess } from 'node:child_process';
12
import { describe, expect, it, vi } from 'vitest';
23
import { getAsyncProcessRunnerConfig } from '@code-pushup/test-utils';
34
import { ProcessObserver, executeProcess } from './execute-process';
45

56
describe('executeProcess', () => {
67
const spyObserver: ProcessObserver = {
78
onStdout: vi.fn(),
9+
onStderr: vi.fn(),
810
onError: vi.fn(),
911
onComplete: vi.fn(),
1012
};
@@ -66,6 +68,15 @@ describe('executeProcess', () => {
6668
expect(errorSpy).toHaveBeenCalledOnce();
6769
expect(processResult).toBeUndefined();
6870
expect(spyObserver.onStdout).toHaveBeenCalledTimes(2); // intro + 1 run before error
71+
expect(spyObserver.onStdout).toHaveBeenLastCalledWith(
72+
'process:update\n',
73+
expect.any(ChildProcess),
74+
);
75+
expect(spyObserver.onStderr).toHaveBeenCalled();
76+
expect(spyObserver.onStderr).toHaveBeenCalledWith(
77+
expect.stringContaining('dummy-error'),
78+
expect.any(ChildProcess),
79+
);
6980
expect(spyObserver.onError).toHaveBeenCalledOnce();
7081
expect(spyObserver.onComplete).not.toHaveBeenCalled();
7182
});
@@ -86,6 +97,7 @@ describe('executeProcess', () => {
8697
expect(processResult.stdout).toContain('process:update');
8798
expect(processResult.stderr).toContain('dummy-error');
8899
expect(spyObserver.onStdout).toHaveBeenCalledTimes(2); // intro + 1 run before error
100+
expect(spyObserver.onStderr).toHaveBeenCalled();
89101
expect(spyObserver.onError).not.toHaveBeenCalled();
90102
expect(spyObserver.onComplete).toHaveBeenCalledOnce();
91103
});

tools/scripts/publish.plugin.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { CreateNodes, CreateNodesContext, readJsonFile } from '@nx/devkit';
2+
import { dirname, join } from 'node:path';
3+
import { ProjectConfiguration } from 'nx/src/config/workspace-json-project-json';
4+
import { PackageJson } from 'nx/src/utils/package-json';
5+
6+
export const createNodes: CreateNodes = [
7+
'**/project.json',
8+
(projectConfigurationFile: string, opts, context: CreateNodesContext) => {
9+
const root = dirname(projectConfigurationFile);
10+
const projectConfiguration: ProjectConfiguration = readJsonFile(
11+
projectConfigurationFile,
12+
);
13+
14+
const isPublishable = Boolean(projectConfiguration?.targets?.publish);
15+
16+
if (!isPublishable) {
17+
return {};
18+
}
19+
20+
return {
21+
projects: {
22+
[root]: {
23+
targets: publishTargets(projectConfiguration, root),
24+
},
25+
},
26+
};
27+
},
28+
];
29+
30+
function publishTargets(projectConfig: ProjectConfiguration, root: string) {
31+
const { name: projectName } = projectConfig;
32+
const { name: packageName } = readJsonFile<PackageJson>(
33+
join(root, 'package.json'),
34+
);
35+
return {
36+
publish: {
37+
command: `node tools/scripts/publish.mjs --name=${projectName} --ver={args.ver} --tag={args.tag}`,
38+
dependsOn: ['build'],
39+
},
40+
'npm-install': {
41+
command: `npm install -D ${packageName}@e2e`,
42+
},
43+
'npm-uninstall': {
44+
command: `npm uninstall ${packageName}`,
45+
},
46+
};
47+
}

0 commit comments

Comments
 (0)