Skip to content

Commit 1416712

Browse files
authored
release plan helper command (#808)
* wip * wip * finish up * tweaks * wip * finish up
1 parent c4a54d3 commit 1416712

File tree

65 files changed

+393
-58
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+393
-58
lines changed

.github/bin/modified-workspaces/list-workspaces.mjs renamed to .github/bin/list-workspaces/list-workspaces.mjs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import path from 'path';
22
import { promises as fsp } from 'fs';
33
import glob from 'glob';
4-
import { toposort } from './toposort.mjs';
4+
import { toposort } from '../util/toposort.mjs';
55

66
export async function listWorkspaces() {
77
try {
@@ -32,9 +32,11 @@ export async function listWorkspaces() {
3232
result.push({
3333
path: packagePath,
3434
name: packageJSON.name,
35+
private: packageJSON.private,
3536
dependencies: [
36-
...Object.keys(Object(packageJSON.devDependencies)),
3737
...Object.keys(Object(packageJSON.dependencies)),
38+
...Object.keys(Object(packageJSON.devDependencies)),
39+
...Object.keys(Object(packageJSON.peerDependencies)),
3840
],
3941
});
4042
}

.github/bin/modified-workspaces/modified-workspaces.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { listModifiedFilesInPullRequest } from './list-modified-files.mjs';
2-
import { listWorkspaces } from './list-workspaces.mjs';
2+
import { listWorkspaces } from '../list-workspaces/list-workspaces.mjs';
33

44
const privateRootDependencies = [
55
'packages/postcss-tape',

.github/bin/release-plan/commit.mjs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { spawn } from 'child_process';
2+
3+
export async function commitAfterPackageRelease(newVersion, packageDirectory, packageName) {
4+
await new Promise((resolve, reject) => {
5+
const commitCmd = spawn(
6+
'git',
7+
[
8+
'commit',
9+
'-am',
10+
`${packageName} v${newVersion}` // "@csstools/css-tokenizer v1.0.0"
11+
],
12+
{
13+
cwd: packageDirectory
14+
}
15+
);
16+
17+
let stdoutBuffer = '';
18+
let stderrBuffer = '';
19+
20+
commitCmd.stdout.on('data', (data) => {
21+
stdoutBuffer += data.toString();
22+
});
23+
24+
commitCmd.stderr.on('data', (data) => {
25+
stderrBuffer += data.toString();
26+
});
27+
28+
commitCmd.on('close', (code) => {
29+
if (0 !== code) {
30+
if (stderrBuffer) {
31+
reject(new Error(`'git commit -am' exited with code ${code} and error message: ${stderrBuffer}`));
32+
return;
33+
}
34+
35+
reject(new Error(`'git commit -am' exited with code ${code}`));
36+
return;
37+
}
38+
39+
resolve(stdoutBuffer);
40+
});
41+
});
42+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export function nowFormatted() {
2+
const now = new Date();
3+
4+
switch (now.getMonth()) {
5+
case 0:
6+
return `January ${now.getUTCDate()}, ${now.getUTCFullYear()}`;
7+
case 1:
8+
return `February ${now.getUTCDate()}, ${now.getUTCFullYear()}`;
9+
case 2:
10+
return `March ${now.getUTCDate()}, ${now.getUTCFullYear()}`;
11+
case 3:
12+
return `April ${now.getUTCDate()}, ${now.getUTCFullYear()}`;
13+
case 4:
14+
return `May ${now.getUTCDate()}, ${now.getUTCFullYear()}`;
15+
case 5:
16+
return `June ${now.getUTCDate()}, ${now.getUTCFullYear()}`;
17+
case 6:
18+
return `July ${now.getUTCDate()}, ${now.getUTCFullYear()}`;
19+
case 7:
20+
return `August ${now.getUTCDate()}, ${now.getUTCFullYear()}`;
21+
case 8:
22+
return `September ${now.getUTCDate()}, ${now.getUTCFullYear()}`;
23+
case 9:
24+
return `October ${now.getUTCDate()}, ${now.getUTCFullYear()}`;
25+
case 10:
26+
return `November ${now.getUTCDate()}, ${now.getUTCFullYear()}`;
27+
case 11:
28+
return `December ${now.getUTCDate()}, ${now.getUTCFullYear()}`;
29+
default:
30+
break;
31+
}
32+
}

.github/bin/release-plan/docs.mjs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { spawn } from 'child_process';
2+
3+
export async function updateDocumentation(packageDirectory) {
4+
await new Promise((resolve, reject) => {
5+
const docsCmd = spawn(
6+
'npm',
7+
[
8+
'run',
9+
'docs',
10+
'--if-present'
11+
],
12+
{
13+
cwd: packageDirectory
14+
}
15+
);
16+
17+
let stdoutBuffer = '';
18+
let stderrBuffer = '';
19+
20+
docsCmd.stdout.on('data', (data) => {
21+
stdoutBuffer += data.toString();
22+
});
23+
24+
docsCmd.stderr.on('data', (data) => {
25+
stderrBuffer += data.toString();
26+
});
27+
28+
docsCmd.on('close', (code) => {
29+
if (0 !== code) {
30+
if (stderrBuffer) {
31+
reject(new Error(`'npm run docs' exited with code ${code} and error message: ${stderrBuffer}`));
32+
return;
33+
}
34+
35+
reject(new Error(`'npm run docs' exited with code ${code}`));
36+
return;
37+
}
38+
39+
resolve(stdoutBuffer);
40+
});
41+
});
42+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { spawn } from 'child_process';
2+
3+
export async function npmInstall() {
4+
await new Promise((resolve, reject) => {
5+
const publishCmd = spawn(
6+
'npm',
7+
[
8+
'install'
9+
],
10+
);
11+
12+
publishCmd.on('close', (code) => {
13+
if (0 !== code) {
14+
reject(new Error(`'npm install' exited with code ${code}`));
15+
return;
16+
}
17+
18+
resolve(true);
19+
});
20+
});
21+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { spawn } from 'child_process';
2+
3+
export async function npmPublish(packageDirectory, packageName) {
4+
await new Promise((resolve, reject) => {
5+
const publishCmd = spawn(
6+
'npm',
7+
[
8+
'publish'
9+
],
10+
{
11+
stdio: 'inherit',
12+
cwd: packageDirectory
13+
}
14+
);
15+
16+
publishCmd.on('close', (code) => {
17+
if (0 !== code) {
18+
reject(new Error(`'npm publish' exited with code ${code} for ${packageName}`));
19+
return;
20+
}
21+
22+
resolve(true);
23+
});
24+
});
25+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { spawn } from 'child_process';
2+
import fs from 'fs/promises';
3+
import path from 'path';
4+
5+
export async function npmVersion(increment, packageDirectory) {
6+
await new Promise((resolve, reject) => {
7+
const versionCmd = spawn(
8+
'npm',
9+
[
10+
'version',
11+
increment
12+
],
13+
{
14+
cwd: packageDirectory
15+
}
16+
);
17+
18+
let stdoutBuffer = '';
19+
let stderrBuffer = '';
20+
21+
versionCmd.stdout.on('data', (data) => {
22+
stdoutBuffer += data.toString();
23+
});
24+
25+
versionCmd.stderr.on('data', (data) => {
26+
stderrBuffer += data.toString();
27+
});
28+
29+
versionCmd.on('close', (code) => {
30+
if (0 !== code) {
31+
if (stderrBuffer) {
32+
reject(new Error(`'npm version ${increment}' exited with code ${code} and error message: ${stderrBuffer}`));
33+
return;
34+
}
35+
36+
reject(new Error(`'npm version ${increment}' exited with code ${code}`));
37+
return;
38+
}
39+
40+
resolve(stdoutBuffer);
41+
});
42+
});
43+
44+
const packageInfo = JSON.parse(await fs.readFile(path.join(packageDirectory, 'package.json')));
45+
return packageInfo.version;
46+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { listWorkspaces } from '../list-workspaces/list-workspaces.mjs';
2+
import fs from 'fs/promises'
3+
import path from 'path'
4+
import { npmVersion } from './npm-version.mjs';
5+
import { nowFormatted } from './date-format.mjs';
6+
import { commitAfterPackageRelease } from './commit.mjs';
7+
import { npmPublish } from './npm-publish.mjs';
8+
import { updateDocumentation } from './docs.mjs';
9+
import { npmInstall } from './npm-install.mjs';
10+
11+
const workspaces = await listWorkspaces();
12+
const needsRelease = new Map();
13+
const waitingOnDependencies = new Map();
14+
15+
const isDryRun = process.argv.slice(2).includes('--dry-run');
16+
17+
WORKSPACES_LOOP:
18+
for (const workspace of workspaces) {
19+
if (workspace.private) {
20+
continue;
21+
}
22+
23+
for (const dependency of workspace.dependencies) {
24+
if (needsRelease.has(dependency) || waitingOnDependencies.has(dependency)) {
25+
waitingOnDependencies.set(workspace.name, workspace);
26+
// Can not be released before all modified dependencies have been released.
27+
continue WORKSPACES_LOOP;
28+
}
29+
}
30+
31+
let changelog = (await fs.readFile(path.join(workspace.path, 'CHANGELOG.md'))).toString();
32+
if (changelog.includes('Unreleased')) {
33+
let increment = '';
34+
if (changelog.includes('Unreleased (patch)')) {
35+
increment = 'patch';
36+
} else if (changelog.includes('Unreleased (minor)')) {
37+
increment = 'minor';
38+
} else if (changelog.includes('Unreleased (major)')) {
39+
increment = 'major';
40+
} else {
41+
console.warn("Invalid CHANGELOG.md in", workspace.name);
42+
waitingOnDependencies.set(workspace.name, workspace);
43+
continue WORKSPACES_LOOP;
44+
}
45+
46+
workspace.increment = increment;
47+
workspace.changelog = changelog;
48+
needsRelease.set(workspace.name, workspace);
49+
}
50+
}
51+
52+
if (needsRelease.size === 0) {
53+
console.log('Nothing to release');
54+
process.exit(0);
55+
}
56+
57+
console.log('Release plan:');
58+
for (const workspace of needsRelease.values()) {
59+
console.log(` - ${workspace.name} (${workspace.increment})`);
60+
}
61+
console.log(''); // empty line
62+
63+
if (isDryRun) {
64+
process.exit(0);
65+
}
66+
67+
for (const workspace of needsRelease.values()) {
68+
console.log(`Releasing : ${workspace.name}`);
69+
// Increment the version
70+
workspace.newVersion = await npmVersion(workspace.increment, workspace.path);
71+
72+
// Update the changelog
73+
workspace.changelog = workspace.changelog.replace(`Unreleased (${workspace.increment})`, `${workspace.newVersion} (${nowFormatted()})`)
74+
await fs.writeFile(path.join(workspace.path, 'CHANGELOG.md'), workspace.changelog);
75+
76+
// Update the documentation
77+
await updateDocumentation(workspace.path);
78+
79+
// Publish to npm
80+
await npmPublish(workspace.path, workspace.name);
81+
82+
// Commit changes
83+
await commitAfterPackageRelease(workspace.newVersion, workspace.path, workspace.name);
84+
85+
// TODO : remove this after the script proves to work ok.
86+
process.exit(0);
87+
}
88+
89+
console.log(''); // empty line
90+
console.log('Preparing next batch');
91+
92+
for (const workspace of waitingOnDependencies.values()) {
93+
const packageInfo = JSON.parse(await fs.readFile(path.join(workspace.path, 'package.json')));
94+
let didChange = false;
95+
96+
for (const dependency of workspace.dependencies) {
97+
if (needsRelease.has(dependency)) {
98+
const updated = needsRelease.get(dependency);
99+
100+
if (packageInfo.dependencies && packageInfo.dependencies[updated.name] && updated.newVersion) {
101+
packageInfo.dependencies[updated.name] = '^' + updated.newVersion;
102+
didChange = true;
103+
}
104+
if (packageInfo.devDependencies && packageInfo.devDependencies[updated.name] && updated.newVersion) {
105+
packageInfo.devDependencies[updated.name] = '^' + updated.newVersion;
106+
didChange = true;
107+
}
108+
if (packageInfo.peerDependencies && packageInfo.peerDependencies[updated.name] && updated.newVersion) {
109+
packageInfo.peerDependencies[updated.name] = '^' + updated.newVersion;
110+
didChange = true;
111+
}
112+
}
113+
}
114+
115+
if (didChange) {
116+
await fs.writeFile(path.join(workspace.path, 'package.json'), JSON.stringify(packageInfo, null, '\t'));
117+
}
118+
}
119+
120+
console.log('\nUpdating lock file');
121+
await npmInstall();
122+
123+
console.log('\nDone 🎉');

cli/csstools-cli/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changes to CSSTools CLI
22

3-
### Unreleased
3+
### Unreleased (patch)
44

55
- Updated `postcss-preset-env` to`8.0.0`
66

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
"lint:licenses": "node .github/bin/license/check-license.mjs",
4747
"lint:rollup-config": "eslint ./rollup --ext .js --ext .ts --ext .mjs --no-error-on-unmatched-pattern",
4848
"new-plugin": "node ./.github/bin/new-plugin.mjs",
49+
"release-plan": "node .github/bin/release-plan/release-plan.mjs",
50+
"release-plan:dry-run": "node .github/bin/release-plan/release-plan.mjs --dry-run",
4951
"test": "npm run test --workspaces --if-present",
5052
"test:browser": "npm run test:browser --workspaces --if-present"
5153
},

packages/cascade-layer-name-parser/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changes to Cascade Layer Name Parser
22

3-
### Unreleased
3+
### Unreleased (patch)
44

55
- Improve `types` declaration in `package.json`
66

packages/css-parser-algorithms/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changes to CSS Parser Algorithms
22

3-
### Unreleased
3+
### Unreleased (patch)
44

55
- Improve `types` declaration in `package.json`
66

0 commit comments

Comments
 (0)