pubz 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/cli.js +645 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# `pubz`
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
bunx pubz
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
`pubz` publishes multiple packages in one command, with some useful steps:
|
|
8
|
+
|
|
9
|
+
1. Prompts you to bump version number of packages.
|
|
10
|
+
2. Prompts you for where you want to publish (e.g. `npm` or private registry).
|
|
11
|
+
3. Prompts you to create a `git tag` and push it.
|
|
12
|
+
|
|
13
|
+
# Example
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bunx pubz
|
|
17
|
+
```
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
|
+
|
|
5
|
+
// src/discovery.ts
|
|
6
|
+
import { readFile, readdir as readdir2, stat as stat2 } from "node:fs/promises";
|
|
7
|
+
import { join as join2, resolve } from "node:path";
|
|
8
|
+
|
|
9
|
+
// src/glob.ts
|
|
10
|
+
import { readdir, stat } from "node:fs/promises";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
async function glob(pattern, cwd) {
|
|
13
|
+
const results = [];
|
|
14
|
+
const basePattern = pattern.replace(/\/\*\*?$/, "");
|
|
15
|
+
const isRecursive = pattern.endsWith("/**");
|
|
16
|
+
const basePath = join(cwd, basePattern);
|
|
17
|
+
try {
|
|
18
|
+
const entries = await readdir(basePath, { withFileTypes: true });
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (entry.isDirectory()) {
|
|
21
|
+
const entryPath = join(basePattern, entry.name);
|
|
22
|
+
const fullPath = join(cwd, entryPath);
|
|
23
|
+
try {
|
|
24
|
+
await stat(join(fullPath, "package.json"));
|
|
25
|
+
results.push(entryPath);
|
|
26
|
+
} catch {
|
|
27
|
+
if (isRecursive) {
|
|
28
|
+
const subResults = await glob(`${entryPath}/*`, cwd);
|
|
29
|
+
results.push(...subResults);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch {}
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/discovery.ts
|
|
39
|
+
async function findRootPackageJson(cwd) {
|
|
40
|
+
const packageJsonPath = join2(cwd, "package.json");
|
|
41
|
+
try {
|
|
42
|
+
await stat2(packageJsonPath);
|
|
43
|
+
return packageJsonPath;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function readPackageJson(path) {
|
|
49
|
+
const content = await readFile(path, "utf-8");
|
|
50
|
+
return JSON.parse(content);
|
|
51
|
+
}
|
|
52
|
+
function getWorkspacePatterns(packageJson) {
|
|
53
|
+
if (!packageJson.workspaces) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(packageJson.workspaces)) {
|
|
57
|
+
return packageJson.workspaces;
|
|
58
|
+
}
|
|
59
|
+
return packageJson.workspaces.packages || [];
|
|
60
|
+
}
|
|
61
|
+
async function discoverPackages(cwd) {
|
|
62
|
+
const rootPackageJsonPath = await findRootPackageJson(cwd);
|
|
63
|
+
if (!rootPackageJsonPath) {
|
|
64
|
+
throw new Error("No package.json found in current directory");
|
|
65
|
+
}
|
|
66
|
+
const rootPackageJson = await readPackageJson(rootPackageJsonPath);
|
|
67
|
+
const workspacePatterns = getWorkspacePatterns(rootPackageJson);
|
|
68
|
+
let packageDirs = [];
|
|
69
|
+
if (workspacePatterns.length > 0) {
|
|
70
|
+
for (const pattern of workspacePatterns) {
|
|
71
|
+
const matches = await glob(pattern, cwd);
|
|
72
|
+
packageDirs.push(...matches);
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
const packagesDir = join2(cwd, "packages");
|
|
76
|
+
try {
|
|
77
|
+
const entries = await readdir2(packagesDir, { withFileTypes: true });
|
|
78
|
+
packageDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => join2("packages", entry.name));
|
|
79
|
+
} catch {
|
|
80
|
+
if (!rootPackageJson.private) {
|
|
81
|
+
return [
|
|
82
|
+
await packageFromPath(cwd, rootPackageJsonPath, rootPackageJson, [])
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const packages = [];
|
|
89
|
+
const packageNames = new Set;
|
|
90
|
+
for (const dir of packageDirs) {
|
|
91
|
+
const pkgPath = resolve(cwd, dir);
|
|
92
|
+
const pkgJsonPath = join2(pkgPath, "package.json");
|
|
93
|
+
try {
|
|
94
|
+
const pkgJson = await readPackageJson(pkgJsonPath);
|
|
95
|
+
packageNames.add(pkgJson.name);
|
|
96
|
+
packages.push(await packageFromPath(pkgPath, pkgJsonPath, pkgJson, []));
|
|
97
|
+
} catch {}
|
|
98
|
+
}
|
|
99
|
+
for (const pkg of packages) {
|
|
100
|
+
pkg.localDependencies = findLocalDependencies(pkg, packageNames);
|
|
101
|
+
}
|
|
102
|
+
return packages;
|
|
103
|
+
}
|
|
104
|
+
async function packageFromPath(path, packageJsonPath, packageJson, localDependencies) {
|
|
105
|
+
return {
|
|
106
|
+
name: packageJson.name,
|
|
107
|
+
version: packageJson.version,
|
|
108
|
+
path,
|
|
109
|
+
packageJsonPath,
|
|
110
|
+
isPrivate: packageJson.private === true,
|
|
111
|
+
localDependencies
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function findLocalDependencies(pkg, packageNames) {
|
|
115
|
+
const deps = [];
|
|
116
|
+
const pkgJson = JSON.parse(__require("node:fs").readFileSync(pkg.packageJsonPath, "utf-8"));
|
|
117
|
+
const allDeps = {
|
|
118
|
+
...pkgJson.dependencies,
|
|
119
|
+
...pkgJson.devDependencies,
|
|
120
|
+
...pkgJson.peerDependencies
|
|
121
|
+
};
|
|
122
|
+
for (const depName of Object.keys(allDeps)) {
|
|
123
|
+
if (packageNames.has(depName)) {
|
|
124
|
+
deps.push(depName);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return deps;
|
|
128
|
+
}
|
|
129
|
+
function sortByDependencyOrder(packages) {
|
|
130
|
+
const packageMap = new Map(packages.map((p) => [p.name, p]));
|
|
131
|
+
const sorted = [];
|
|
132
|
+
const visited = new Set;
|
|
133
|
+
function visit(pkg) {
|
|
134
|
+
if (visited.has(pkg.name))
|
|
135
|
+
return;
|
|
136
|
+
visited.add(pkg.name);
|
|
137
|
+
for (const depName of pkg.localDependencies) {
|
|
138
|
+
const dep = packageMap.get(depName);
|
|
139
|
+
if (dep) {
|
|
140
|
+
visit(dep);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
sorted.push(pkg);
|
|
144
|
+
}
|
|
145
|
+
for (const pkg of packages) {
|
|
146
|
+
visit(pkg);
|
|
147
|
+
}
|
|
148
|
+
return sorted;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/prompts.ts
|
|
152
|
+
import * as readline from "node:readline";
|
|
153
|
+
var rl = readline.createInterface({
|
|
154
|
+
input: process.stdin,
|
|
155
|
+
output: process.stdout
|
|
156
|
+
});
|
|
157
|
+
function prompt(question) {
|
|
158
|
+
return new Promise((resolve2) => {
|
|
159
|
+
rl.question(question, (answer) => {
|
|
160
|
+
resolve2(answer.trim());
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function closePrompt() {
|
|
165
|
+
rl.close();
|
|
166
|
+
}
|
|
167
|
+
async function confirm(message, defaultNo = true) {
|
|
168
|
+
const hint = defaultNo ? "[y/N]" : "[Y/n]";
|
|
169
|
+
const answer = await prompt(`${message} ${hint} `);
|
|
170
|
+
if (answer === "") {
|
|
171
|
+
return !defaultNo;
|
|
172
|
+
}
|
|
173
|
+
return answer.toLowerCase() === "y";
|
|
174
|
+
}
|
|
175
|
+
async function select(message, options, defaultIndex = 0) {
|
|
176
|
+
console.log(message);
|
|
177
|
+
console.log("");
|
|
178
|
+
for (let i = 0;i < options.length; i++) {
|
|
179
|
+
const marker = i === defaultIndex ? ">" : " ";
|
|
180
|
+
console.log(` ${marker} ${i + 1}) ${options[i].label}`);
|
|
181
|
+
}
|
|
182
|
+
console.log("");
|
|
183
|
+
const answer = await prompt(`Enter choice [1-${options.length}] (default: ${defaultIndex + 1}): `);
|
|
184
|
+
if (answer === "") {
|
|
185
|
+
return options[defaultIndex].value;
|
|
186
|
+
}
|
|
187
|
+
const index = Number.parseInt(answer, 10) - 1;
|
|
188
|
+
if (index >= 0 && index < options.length) {
|
|
189
|
+
return options[index].value;
|
|
190
|
+
}
|
|
191
|
+
console.log(`Invalid choice. Using default: ${options[defaultIndex].label}`);
|
|
192
|
+
return options[defaultIndex].value;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/publish.ts
|
|
196
|
+
import { spawn } from "node:child_process";
|
|
197
|
+
import { readFile as readFile2, stat as stat3 } from "node:fs/promises";
|
|
198
|
+
import { join as join3 } from "node:path";
|
|
199
|
+
function run(command, args, cwd) {
|
|
200
|
+
return new Promise((resolve2) => {
|
|
201
|
+
const proc = spawn(command, args, {
|
|
202
|
+
cwd,
|
|
203
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
204
|
+
shell: true
|
|
205
|
+
});
|
|
206
|
+
let output = "";
|
|
207
|
+
proc.stdout?.on("data", (data) => {
|
|
208
|
+
output += data.toString();
|
|
209
|
+
process.stdout.write(data);
|
|
210
|
+
});
|
|
211
|
+
proc.stderr?.on("data", (data) => {
|
|
212
|
+
output += data.toString();
|
|
213
|
+
process.stderr.write(data);
|
|
214
|
+
});
|
|
215
|
+
proc.on("close", (code) => {
|
|
216
|
+
resolve2({ code: code ?? 1, output });
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async function runBuild(cwd, dryRun) {
|
|
221
|
+
if (dryRun) {
|
|
222
|
+
console.log("[DRY RUN] Would run: bun run build");
|
|
223
|
+
return { success: true };
|
|
224
|
+
}
|
|
225
|
+
console.log("Running build...");
|
|
226
|
+
console.log("");
|
|
227
|
+
const result = await run("bun", ["run", "build"], cwd);
|
|
228
|
+
if (result.code !== 0) {
|
|
229
|
+
return { success: false, error: "Build failed" };
|
|
230
|
+
}
|
|
231
|
+
console.log("");
|
|
232
|
+
console.log("Build completed successfully");
|
|
233
|
+
return { success: true };
|
|
234
|
+
}
|
|
235
|
+
async function verifyBuild(pkg) {
|
|
236
|
+
const content = await readFile2(pkg.packageJsonPath, "utf-8");
|
|
237
|
+
const packageJson = JSON.parse(content);
|
|
238
|
+
const filesToCheck = [];
|
|
239
|
+
if (packageJson.main) {
|
|
240
|
+
filesToCheck.push(packageJson.main);
|
|
241
|
+
}
|
|
242
|
+
if (packageJson.bin) {
|
|
243
|
+
if (typeof packageJson.bin === "string") {
|
|
244
|
+
filesToCheck.push(packageJson.bin);
|
|
245
|
+
} else {
|
|
246
|
+
filesToCheck.push(...Object.values(packageJson.bin));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (packageJson.exports) {
|
|
250
|
+
if (typeof packageJson.exports === "string") {
|
|
251
|
+
filesToCheck.push(packageJson.exports);
|
|
252
|
+
} else if (typeof packageJson.exports["."] === "string") {
|
|
253
|
+
filesToCheck.push(packageJson.exports["."]);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (filesToCheck.length === 0) {
|
|
257
|
+
filesToCheck.push("./dist/index.js");
|
|
258
|
+
}
|
|
259
|
+
for (const file of filesToCheck) {
|
|
260
|
+
const filePath = join3(pkg.path, file);
|
|
261
|
+
try {
|
|
262
|
+
await stat3(filePath);
|
|
263
|
+
} catch {
|
|
264
|
+
return { success: false, error: `Missing ${file} in ${pkg.name}` };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return { success: true };
|
|
268
|
+
}
|
|
269
|
+
async function publishPackage(pkg, registry, otp, dryRun) {
|
|
270
|
+
if (dryRun) {
|
|
271
|
+
console.log(` [DRY RUN] Would publish ${pkg.name}@${pkg.version} to ${registry}`);
|
|
272
|
+
return { success: true };
|
|
273
|
+
}
|
|
274
|
+
console.log(`Publishing ${pkg.name}@${pkg.version}...`);
|
|
275
|
+
const args = ["publish", "--registry", registry, "--access", "public"];
|
|
276
|
+
if (otp) {
|
|
277
|
+
args.push("--otp", otp);
|
|
278
|
+
}
|
|
279
|
+
const result = await run("bun", args, pkg.path);
|
|
280
|
+
if (result.code !== 0) {
|
|
281
|
+
return { success: false, error: `Failed to publish ${pkg.name}` };
|
|
282
|
+
}
|
|
283
|
+
console.log(` ${pkg.name} published successfully`);
|
|
284
|
+
return { success: true };
|
|
285
|
+
}
|
|
286
|
+
async function createGitTag(version, cwd, dryRun) {
|
|
287
|
+
const tagName = `v${version}`;
|
|
288
|
+
if (dryRun) {
|
|
289
|
+
console.log(`[DRY RUN] Would create git tag: ${tagName}`);
|
|
290
|
+
return { success: true };
|
|
291
|
+
}
|
|
292
|
+
const statusResult = await run("git", ["status", "--porcelain"], cwd);
|
|
293
|
+
if (statusResult.output.trim()) {
|
|
294
|
+
console.log("Uncommitted changes detected. Committing...");
|
|
295
|
+
await run("git", ["add", "-A"], cwd);
|
|
296
|
+
await run("git", ["commit", "-m", `chore: release ${tagName}`], cwd);
|
|
297
|
+
console.log(" Changes committed");
|
|
298
|
+
}
|
|
299
|
+
const tagResult = await run("git", ["tag", tagName], cwd);
|
|
300
|
+
if (tagResult.code !== 0) {
|
|
301
|
+
return {
|
|
302
|
+
success: false,
|
|
303
|
+
error: `Failed to create tag ${tagName} (may already exist)`
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
console.log(` Tag ${tagName} created`);
|
|
307
|
+
return { success: true };
|
|
308
|
+
}
|
|
309
|
+
async function pushGitTag(version, cwd, dryRun) {
|
|
310
|
+
const tagName = `v${version}`;
|
|
311
|
+
if (dryRun) {
|
|
312
|
+
console.log(`[DRY RUN] Would push git tag: ${tagName}`);
|
|
313
|
+
return { success: true };
|
|
314
|
+
}
|
|
315
|
+
const result = await run("git", ["push", "origin", tagName], cwd);
|
|
316
|
+
if (result.code !== 0) {
|
|
317
|
+
return { success: false, error: `Failed to push tag ${tagName}` };
|
|
318
|
+
}
|
|
319
|
+
console.log(` Tag ${tagName} pushed to origin`);
|
|
320
|
+
return { success: true };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/version.ts
|
|
324
|
+
import { readFile as readFile3, writeFile } from "node:fs/promises";
|
|
325
|
+
function bumpVersion(version, type) {
|
|
326
|
+
if (type === "none")
|
|
327
|
+
return version;
|
|
328
|
+
const [major, minor, patch] = version.split(".").map(Number);
|
|
329
|
+
switch (type) {
|
|
330
|
+
case "major":
|
|
331
|
+
return `${major + 1}.0.0`;
|
|
332
|
+
case "minor":
|
|
333
|
+
return `${major}.${minor + 1}.0`;
|
|
334
|
+
case "patch":
|
|
335
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function previewBump(version, type) {
|
|
339
|
+
const newVersion = bumpVersion(version, type);
|
|
340
|
+
return `${version} -> ${newVersion}`;
|
|
341
|
+
}
|
|
342
|
+
async function updatePackageVersion(pkg, newVersion, dryRun) {
|
|
343
|
+
const content = await readFile3(pkg.packageJsonPath, "utf-8");
|
|
344
|
+
const packageJson = JSON.parse(content);
|
|
345
|
+
packageJson.version = newVersion;
|
|
346
|
+
if (dryRun) {
|
|
347
|
+
console.log(` [DRY RUN] Would update ${pkg.name}: ${pkg.version} -> ${newVersion}`);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
await writeFile(pkg.packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
|
|
351
|
+
`);
|
|
352
|
+
console.log(` Updated ${pkg.name}: ${pkg.version} -> ${newVersion}`);
|
|
353
|
+
}
|
|
354
|
+
async function updateLocalDependencyVersions(packages, newVersion, dryRun) {
|
|
355
|
+
const packageNames = new Set(packages.map((p) => p.name));
|
|
356
|
+
for (const pkg of packages) {
|
|
357
|
+
const content = await readFile3(pkg.packageJsonPath, "utf-8");
|
|
358
|
+
const packageJson = JSON.parse(content);
|
|
359
|
+
let modified = false;
|
|
360
|
+
for (const depType of [
|
|
361
|
+
"dependencies",
|
|
362
|
+
"devDependencies",
|
|
363
|
+
"peerDependencies"
|
|
364
|
+
]) {
|
|
365
|
+
const deps = packageJson[depType];
|
|
366
|
+
if (!deps)
|
|
367
|
+
continue;
|
|
368
|
+
for (const depName of Object.keys(deps)) {
|
|
369
|
+
if (packageNames.has(depName)) {
|
|
370
|
+
const oldVersion = deps[depName];
|
|
371
|
+
const newVersionSpec = oldVersion.startsWith("^") ? `^${newVersion}` : oldVersion.startsWith("~") ? `~${newVersion}` : newVersion;
|
|
372
|
+
if (deps[depName] !== newVersionSpec) {
|
|
373
|
+
if (dryRun) {
|
|
374
|
+
console.log(` [DRY RUN] Would update ${pkg.name} ${depType}.${depName}: ${oldVersion} -> ${newVersionSpec}`);
|
|
375
|
+
} else {
|
|
376
|
+
deps[depName] = newVersionSpec;
|
|
377
|
+
modified = true;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (modified && !dryRun) {
|
|
384
|
+
await writeFile(pkg.packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
|
|
385
|
+
`);
|
|
386
|
+
console.log(` Updated local dependency versions in ${pkg.name}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/cli.ts
|
|
392
|
+
var REGISTRIES = {
|
|
393
|
+
npm: "https://registry.npmjs.org",
|
|
394
|
+
github: "https://npm.pkg.github.com"
|
|
395
|
+
};
|
|
396
|
+
function printUsage() {
|
|
397
|
+
console.log(`
|
|
398
|
+
pubz - Interactive npm package publisher
|
|
399
|
+
|
|
400
|
+
Usage: pubz [options]
|
|
401
|
+
|
|
402
|
+
Options:
|
|
403
|
+
--dry-run Show what would be published without actually publishing
|
|
404
|
+
--registry <url> Specify npm registry URL (default: public npm)
|
|
405
|
+
--otp <code> One-time password for 2FA
|
|
406
|
+
--skip-build Skip the build step
|
|
407
|
+
--yes, -y Skip confirmation prompts (use defaults)
|
|
408
|
+
-h, --help Show this help message
|
|
409
|
+
|
|
410
|
+
Examples:
|
|
411
|
+
pubz # Interactive publish
|
|
412
|
+
pubz --dry-run # Preview what would happen
|
|
413
|
+
pubz --registry https://npm.pkg.github.com # Publish to GitHub Packages
|
|
414
|
+
`);
|
|
415
|
+
}
|
|
416
|
+
function parseArgs(args) {
|
|
417
|
+
const options = {
|
|
418
|
+
dryRun: false,
|
|
419
|
+
registry: "",
|
|
420
|
+
otp: "",
|
|
421
|
+
skipBuild: false,
|
|
422
|
+
skipPrompts: false,
|
|
423
|
+
help: false
|
|
424
|
+
};
|
|
425
|
+
for (let i = 0;i < args.length; i++) {
|
|
426
|
+
const arg = args[i];
|
|
427
|
+
switch (arg) {
|
|
428
|
+
case "--dry-run":
|
|
429
|
+
options.dryRun = true;
|
|
430
|
+
break;
|
|
431
|
+
case "--registry":
|
|
432
|
+
options.registry = args[++i] || "";
|
|
433
|
+
break;
|
|
434
|
+
case "--otp":
|
|
435
|
+
options.otp = args[++i] || "";
|
|
436
|
+
break;
|
|
437
|
+
case "--skip-build":
|
|
438
|
+
options.skipBuild = true;
|
|
439
|
+
break;
|
|
440
|
+
case "--yes":
|
|
441
|
+
case "-y":
|
|
442
|
+
options.skipPrompts = true;
|
|
443
|
+
break;
|
|
444
|
+
case "-h":
|
|
445
|
+
case "--help":
|
|
446
|
+
options.help = true;
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return options;
|
|
451
|
+
}
|
|
452
|
+
async function main() {
|
|
453
|
+
const options = parseArgs(process.argv.slice(2));
|
|
454
|
+
if (options.help) {
|
|
455
|
+
printUsage();
|
|
456
|
+
process.exit(0);
|
|
457
|
+
}
|
|
458
|
+
const cwd = process.cwd();
|
|
459
|
+
if (options.dryRun) {
|
|
460
|
+
console.log("DRY RUN MODE - No actual changes will be made");
|
|
461
|
+
console.log("");
|
|
462
|
+
}
|
|
463
|
+
console.log("pubz - npm package publisher");
|
|
464
|
+
console.log("=============================");
|
|
465
|
+
console.log("");
|
|
466
|
+
console.log("Discovering packages...");
|
|
467
|
+
console.log("");
|
|
468
|
+
let packages = await discoverPackages(cwd);
|
|
469
|
+
const publishablePackages = packages.filter((p) => !p.isPrivate);
|
|
470
|
+
if (publishablePackages.length === 0) {
|
|
471
|
+
console.log("No publishable packages found.");
|
|
472
|
+
console.log("");
|
|
473
|
+
console.log("Make sure your packages:");
|
|
474
|
+
console.log(' - Have a package.json with a "name" field');
|
|
475
|
+
console.log(' - Do not have "private": true');
|
|
476
|
+
console.log("");
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
packages = sortByDependencyOrder(publishablePackages);
|
|
480
|
+
console.log(`Found ${packages.length} publishable package(s):`);
|
|
481
|
+
console.log("");
|
|
482
|
+
for (const pkg of packages) {
|
|
483
|
+
const deps = pkg.localDependencies.length > 0 ? ` (depends on: ${pkg.localDependencies.join(", ")})` : "";
|
|
484
|
+
console.log(` - ${pkg.name}@${pkg.version}${deps}`);
|
|
485
|
+
}
|
|
486
|
+
console.log("");
|
|
487
|
+
const currentVersion = packages[0].version;
|
|
488
|
+
console.log("Step 1: Version Management");
|
|
489
|
+
console.log("--------------------------");
|
|
490
|
+
console.log("");
|
|
491
|
+
console.log(`Current version: ${currentVersion}`);
|
|
492
|
+
console.log("");
|
|
493
|
+
let newVersion = currentVersion;
|
|
494
|
+
if (!options.skipPrompts) {
|
|
495
|
+
const shouldBump = await confirm("Bump version before publishing?");
|
|
496
|
+
if (shouldBump) {
|
|
497
|
+
const bumpType = await select("Select version bump type:", [
|
|
498
|
+
{
|
|
499
|
+
label: `patch (${previewBump(currentVersion, "patch")})`,
|
|
500
|
+
value: "patch"
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
label: `minor (${previewBump(currentVersion, "minor")})`,
|
|
504
|
+
value: "minor"
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
label: `major (${previewBump(currentVersion, "major")})`,
|
|
508
|
+
value: "major"
|
|
509
|
+
}
|
|
510
|
+
]);
|
|
511
|
+
newVersion = bumpVersion(currentVersion, bumpType);
|
|
512
|
+
console.log("");
|
|
513
|
+
console.log(`Updating version to ${newVersion} in all packages...`);
|
|
514
|
+
console.log("");
|
|
515
|
+
for (const pkg of packages) {
|
|
516
|
+
await updatePackageVersion(pkg, newVersion, options.dryRun);
|
|
517
|
+
}
|
|
518
|
+
await updateLocalDependencyVersions(packages, newVersion, options.dryRun);
|
|
519
|
+
for (const pkg of packages) {
|
|
520
|
+
pkg.version = newVersion;
|
|
521
|
+
}
|
|
522
|
+
console.log("");
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
let registry = options.registry;
|
|
526
|
+
if (!registry && !options.skipPrompts) {
|
|
527
|
+
registry = await select("Select publish target:", [
|
|
528
|
+
{
|
|
529
|
+
label: "Public npm registry (https://registry.npmjs.org)",
|
|
530
|
+
value: REGISTRIES.npm
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
label: "GitHub Packages (https://npm.pkg.github.com)",
|
|
534
|
+
value: REGISTRIES.github
|
|
535
|
+
}
|
|
536
|
+
]);
|
|
537
|
+
}
|
|
538
|
+
registry = registry || REGISTRIES.npm;
|
|
539
|
+
console.log("");
|
|
540
|
+
console.log(`Publishing to: ${registry}`);
|
|
541
|
+
console.log("");
|
|
542
|
+
if (!options.skipBuild) {
|
|
543
|
+
console.log("Step 2: Building Packages");
|
|
544
|
+
console.log("-------------------------");
|
|
545
|
+
console.log("");
|
|
546
|
+
const buildResult = await runBuild(cwd, options.dryRun);
|
|
547
|
+
if (!buildResult.success) {
|
|
548
|
+
console.error(`Build failed: ${buildResult.error}`);
|
|
549
|
+
closePrompt();
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
console.log("");
|
|
553
|
+
console.log("Verifying builds...");
|
|
554
|
+
console.log("");
|
|
555
|
+
let allBuildsVerified = true;
|
|
556
|
+
for (const pkg of packages) {
|
|
557
|
+
const result = await verifyBuild(pkg);
|
|
558
|
+
if (result.success) {
|
|
559
|
+
console.log(` ${pkg.name} build verified`);
|
|
560
|
+
} else {
|
|
561
|
+
console.error(` ${pkg.name}: ${result.error}`);
|
|
562
|
+
allBuildsVerified = false;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
console.log("");
|
|
566
|
+
if (!allBuildsVerified) {
|
|
567
|
+
console.error("Build verification failed. Please fix the issues and try again.");
|
|
568
|
+
closePrompt();
|
|
569
|
+
process.exit(1);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
console.log("Step 3: Publishing to npm");
|
|
573
|
+
console.log("-------------------------");
|
|
574
|
+
console.log("");
|
|
575
|
+
if (options.dryRun) {
|
|
576
|
+
console.log(`[DRY RUN] Would publish the following packages to ${registry}:`);
|
|
577
|
+
console.log("");
|
|
578
|
+
for (const pkg of packages) {
|
|
579
|
+
console.log(` ${pkg.name}@${newVersion}`);
|
|
580
|
+
}
|
|
581
|
+
console.log("");
|
|
582
|
+
console.log("Run without --dry-run to actually publish.");
|
|
583
|
+
} else {
|
|
584
|
+
console.log("About to publish the following packages:");
|
|
585
|
+
console.log("");
|
|
586
|
+
for (const pkg of packages) {
|
|
587
|
+
console.log(` ${pkg.name}@${newVersion}`);
|
|
588
|
+
}
|
|
589
|
+
console.log("");
|
|
590
|
+
console.log(`Registry: ${registry}`);
|
|
591
|
+
console.log("");
|
|
592
|
+
if (!options.skipPrompts) {
|
|
593
|
+
const shouldContinue = await confirm("Continue?");
|
|
594
|
+
if (!shouldContinue) {
|
|
595
|
+
console.log("Publish cancelled.");
|
|
596
|
+
closePrompt();
|
|
597
|
+
process.exit(0);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
console.log("");
|
|
601
|
+
console.log("Publishing packages...");
|
|
602
|
+
console.log("");
|
|
603
|
+
for (const pkg of packages) {
|
|
604
|
+
const result = await publishPackage(pkg, registry, options.otp, options.dryRun);
|
|
605
|
+
if (!result.success) {
|
|
606
|
+
console.error(`Failed to publish ${pkg.name}: ${result.error}`);
|
|
607
|
+
console.log("");
|
|
608
|
+
console.log("Stopping publish process.");
|
|
609
|
+
closePrompt();
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
console.log("");
|
|
615
|
+
console.log("==================================");
|
|
616
|
+
console.log("Publishing complete!");
|
|
617
|
+
console.log("");
|
|
618
|
+
console.log(`Published version: ${newVersion}`);
|
|
619
|
+
console.log("");
|
|
620
|
+
if (!options.dryRun && !options.skipPrompts) {
|
|
621
|
+
const shouldTag = await confirm(`Create a git tag for v${newVersion}?`);
|
|
622
|
+
if (shouldTag) {
|
|
623
|
+
console.log("");
|
|
624
|
+
const tagResult = await createGitTag(newVersion, cwd, options.dryRun);
|
|
625
|
+
if (tagResult.success) {
|
|
626
|
+
const shouldPush = await confirm("Push tag to origin?");
|
|
627
|
+
if (shouldPush) {
|
|
628
|
+
await pushGitTag(newVersion, cwd, options.dryRun);
|
|
629
|
+
} else {
|
|
630
|
+
console.log(`Tag created locally. Push manually with: git push origin v${newVersion}`);
|
|
631
|
+
}
|
|
632
|
+
} else {
|
|
633
|
+
console.error(tagResult.error);
|
|
634
|
+
}
|
|
635
|
+
console.log("");
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
console.log("Done!");
|
|
639
|
+
closePrompt();
|
|
640
|
+
}
|
|
641
|
+
main().catch((error) => {
|
|
642
|
+
console.error("Error:", error.message);
|
|
643
|
+
closePrompt();
|
|
644
|
+
process.exit(1);
|
|
645
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pubz",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Interactive CLI for publishing npm packages (single or monorepo)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pubz": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "bun build src/cli.ts --outdir dist --target node",
|
|
14
|
+
"dev": "bun run src/cli.ts",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"lint": "bunx biome check src/",
|
|
17
|
+
"lint:fix": "bunx biome check --write src/"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"npm",
|
|
21
|
+
"publish",
|
|
22
|
+
"monorepo",
|
|
23
|
+
"cli",
|
|
24
|
+
"release"
|
|
25
|
+
],
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@biomejs/biome": "^1.9.4",
|
|
30
|
+
"@types/bun": "^1.1.14",
|
|
31
|
+
"typescript": "^5.7.2"
|
|
32
|
+
}
|
|
33
|
+
}
|