-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpublish.ts
221 lines (209 loc) · 6.79 KB
/
publish.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
import { colors } from "./util";
import { parseArgs } from "node:util";
const { config, quietSpawnOptions } = getConfig();
await main();
/**
* this script is used to do the following:
*
* 1. build the project
* 2. update the version in package.json
* 3. run tests
* 4. update the tests badge in README.md
* 5. commit and push changes to github
* 6. publish to npm
*
* ## usage:
*
* patch version:
* ```sh
* bun publish.ts --patch
* # or
* bun publish.ts -p
* ```
*
* minor version:
* ```sh
* bun publish.ts --minor
* # or
* bun publish.ts -m
* ```
*
* major version:
* ```sh
* # major version
* bun publish.ts --major
* # or
* bun publish.ts -M
*```
*
* no version change (still runs tests and pushes to github):
* ```sh
* bun publish.ts # no args
* ```
*/
async function main() {
const promiseResults = await Promise.all([build(), updatePkgVersion(), runTests()]);
const [buildSuccessful, [oldVersion, newVersion], testsPassed] = promiseResults;
const readmeUpdated = await updateReadmeTestsBadge(testsPassed);
const changedFiles = await getChangedFiles();
await commitAndPush(changedFiles);
await publish(oldVersion, newVersion);
}
async function getChangedFiles(): Promise<string[]> {
console.log("getting changed files...");
const process = Bun.spawn(["git", "diff", "--name-only"], quietSpawnOptions);
const stdout = await Bun.readableStreamToText(process.stdout);
const changed = stdout.split("\n").filter(Boolean);
if (changed.length) {
console.log("changed files:");
changed.forEach((file) => console.log(`- ${file}`));
}
return changed;
}
async function build(): Promise<boolean> {
console.log("building...");
const { exited } = Bun.spawn(["bun", "run", "build"], quietSpawnOptions);
const exitCode = await exited;
const success = exitCode === 0;
const message = success ? colors.green("build successful!") : colors.red("build failed!");
console.log(message);
return success;
}
async function publish(oldVersion: SemVer, newVersion: SemVer) {
if (oldVersion === newVersion) {
console.log(
`${colors.yellow("not publishing to npm")} ${colors.gray(
`(v${newVersion} is the same as npm)`,
)}`,
);
return;
}
console.log("publishing to npm...");
const process = Bun.spawn(["npm", "publish"], quietSpawnOptions);
const exitCode = await process.exited;
if (exitCode !== 0) {
const stderr = await Bun.readableStreamToText(process.stderr);
throw new Error(`npm publish failed: ${stderr}`);
}
console.log(
`${colors.green("published to npm!")} 🎉 ${colors.gray(oldVersion)} → ${colors.gray(
newVersion,
)}`,
);
}
async function commitAndPush(changedFiles: string[]): Promise<void> {
if (!changedFiles.length) {
console.log("no changes to commit");
return;
}
console.log("committing and pushing...");
let process = Bun.spawn(["git", "add", "."], quietSpawnOptions);
let exitCode = await process.exited;
if (exitCode !== 0) {
const stderr = await Bun.readableStreamToText(process.stderr);
throw new Error(`git add failed: ${stderr}`);
}
process = Bun.spawn(["git", "commit", "-m", "'updated, tested'"], quietSpawnOptions);
exitCode = await process.exited;
if (exitCode !== 0) {
const stderr = await Bun.readableStreamToText(process.stderr);
throw new Error(`git commit failed: ${stderr}`);
}
process = Bun.spawn(["git", "push"], quietSpawnOptions);
exitCode = await process.exited;
if (exitCode !== 0) {
const stderr = await Bun.readableStreamToText(process.stderr);
throw new Error(`git push failed: ${stderr}`);
}
console.log("committed and pushed to github");
}
async function updatePkgVersion(): Promise<[SemVer, SemVer]> {
console.log("updating version...");
const parsed = parseArgs(config);
const file = Bun.file("package.json");
const pkg = await file.json<Package>();
const version = pkg.version;
const newVersion = getNewVersion(version, parsed.values);
pkg.version = newVersion;
const writer = file.writer();
writer.write(JSON.stringify(pkg, null, 4));
await writer.end();
if (version === newVersion) {
console.log("package version is already up to date");
return [version, newVersion];
}
console.log(
`${colors.green("updated version")} ${colors.gray(version)} → ${colors.gray(newVersion)}`,
);
return [version, newVersion];
}
function getNewVersion(version: SemVer, args: ParsedArgs["values"]): SemVer {
const [major, minor, patch] = version.split(".").map(Number);
if (args.major) return `${major + 1}.0.0`;
if (args.minor) return `${major}.${minor + 1}.0`;
if (args.patch) return `${major}.${minor}.${patch + 1}`;
return version;
}
async function runTests(): Promise<boolean> {
console.log("running tests...");
const process = Bun.spawn("bun test".split(" "), quietSpawnOptions);
const exitCode = await process.exited;
const passed = exitCode === 0;
let message = passed ? colors.green("tests passed!") : colors.red("tests failed!");
console.log(message);
return passed;
}
async function updateReadmeTestsBadge(testsPassed: boolean): Promise<boolean> {
const color = testsPassed ? "green" : "red";
const text = testsPassed ? "passing" : "failing";
console.log("updating README.md tests badge...");
const link = `![tests](https://img.shields.io/badge/tests-${text}-${color}?style=for-the-badge)`;
const file = Bun.file("README.md");
const contents = await file.text();
const updated = contents.replace(/!\[tests\].+/, link);
if (contents === updated) {
console.log("README.md tests badge is already up to date");
return false;
}
const writer = file.writer();
writer.write(updated);
await writer.end();
return true;
}
function getConfig() {
const options = {
patch: {
type: "boolean",
short: "p",
},
minor: {
type: "boolean",
short: "m",
},
major: {
type: "boolean",
short: "M",
},
} as const;
const args = process.argv.slice(2);
const config = { args, options };
const quietSpawnOptions = {
stdout: "pipe",
stderr: "pipe",
} as const;
return { config, quietSpawnOptions };
}
type Package = {
name: string;
version: SemVer;
module: string;
main: string;
type: string;
types: string;
scripts: JsonRecord;
devDependencies: JsonRecord;
peerDependencies: JsonRecord;
};
type SemVer = `${number}.${number}.${number}`;
type JsonRecord = Record<string, string>;
type ParsedArgs = ReturnType<typeof parseArgs<typeof config>>;