@pubm/plugin-brew 0.4.7 → 0.4.8

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.
@@ -1,3 +1,3 @@
1
- import type { PubmPlugin } from "@pubm/core";
1
+ import { type PubmPlugin } from "@pubm/core";
2
2
  import type { BrewCoreOptions } from "./types.js";
3
3
  export declare function brewCore(options: BrewCoreOptions): PubmPlugin;
@@ -1,3 +1,3 @@
1
- import type { PubmPlugin } from "@pubm/core";
1
+ import { type PubmPlugin } from "@pubm/core";
2
2
  import type { BrewTapOptions } from "./types.js";
3
3
  export declare function brewTap(options: BrewTapOptions): PubmPlugin;
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
5
5
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import { tmpdir } from "node:os";
7
7
  import { dirname, join, resolve } from "node:path";
8
+ import { resolvePhases } from "@pubm/core";
8
9
 
9
10
  // src/formula.ts
10
11
  function toClassName(name) {
@@ -204,8 +205,64 @@ function brewCore(options) {
204
205
  ]
205
206
  }
206
207
  ],
208
+ credentials: (ctx) => {
209
+ if (ctx.options.mode !== "ci")
210
+ return [];
211
+ return [
212
+ {
213
+ key: "brew-github-token",
214
+ env: "PUBM_BREW_GITHUB_TOKEN",
215
+ label: "GitHub PAT for Homebrew (homebrew-core)",
216
+ tokenUrl: "https://github.com/settings/tokens/new?scopes=repo,workflow",
217
+ tokenUrlLabel: "github.com",
218
+ ghSecretName: "PUBM_BREW_GITHUB_TOKEN",
219
+ required: true
220
+ }
221
+ ];
222
+ },
223
+ checks: (ctx) => {
224
+ const phases = resolvePhases(ctx.options);
225
+ if (!phases.includes("publish") && ctx.options.mode !== "ci")
226
+ return [];
227
+ if (ctx.options.mode === "ci") {
228
+ return [
229
+ {
230
+ title: "Checking Homebrew core token availability",
231
+ phase: "conditions",
232
+ task: async (ctx2, task) => {
233
+ const token = ctx2.runtime.pluginTokens?.["brew-github-token"];
234
+ if (!token) {
235
+ throw new Error("PUBM_BREW_GITHUB_TOKEN is required for homebrew-core publishing.");
236
+ }
237
+ task.output = "Homebrew core token verified";
238
+ }
239
+ }
240
+ ];
241
+ }
242
+ return [
243
+ {
244
+ title: "Checking GitHub CLI access for homebrew-core",
245
+ phase: "conditions",
246
+ task: async (_ctx, task) => {
247
+ const { execFileSync } = await import("node:child_process");
248
+ try {
249
+ execFileSync("gh", ["auth", "status"], { stdio: "pipe" });
250
+ task.output = "GitHub CLI authenticated";
251
+ } catch {
252
+ throw new Error("GitHub CLI is not authenticated. Run `gh auth login` first.");
253
+ }
254
+ try {
255
+ execFileSync("gh", ["repo", "view", "homebrew/homebrew-core", "--json", "name"], { stdio: "pipe" });
256
+ task.output = "Access to homebrew/homebrew-core verified";
257
+ } catch {
258
+ throw new Error("Cannot access homebrew/homebrew-core. Check your GitHub permissions.");
259
+ }
260
+ }
261
+ }
262
+ ];
263
+ },
207
264
  hooks: {
208
- afterRelease: async (_ctx, releaseCtx) => {
265
+ afterRelease: async (ctx, releaseCtx) => {
209
266
  if (options.packageName && releaseCtx.displayLabel !== options.packageName) {
210
267
  return;
211
268
  }
@@ -215,16 +272,26 @@ function brewCore(options) {
215
272
  const pkg = existsSync(pkgPath) ? JSON.parse(readFileSync(pkgPath, "utf-8")) : {};
216
273
  const name = pkg.name?.replace(/^@[^/]+\//, "") ?? "my-tool";
217
274
  const formulaAssets = releaseAssetsToFormulaAssets(releaseCtx.assets, options.assetPlatforms);
275
+ const token = ctx.runtime.pluginTokens?.["brew-github-token"];
276
+ const ghEnv = token ? { env: { ...process.env, GH_TOKEN: token } } : {};
218
277
  try {
219
278
  execSync2("gh repo fork homebrew/homebrew-core --clone=false", {
220
- stdio: "pipe"
279
+ stdio: "pipe",
280
+ ...ghEnv
221
281
  });
222
282
  } catch {}
223
283
  const username = execSync2("gh api user --jq .login", {
224
- encoding: "utf-8"
284
+ encoding: "utf-8",
285
+ ...ghEnv
225
286
  }).trim();
226
287
  const tmpDir = join(tmpdir(), `pubm-brew-core-${Date.now()}`);
227
- execSync2(`git clone --depth 1 https://github.com/${username}/homebrew-core.git ${tmpDir}`, { stdio: "inherit" });
288
+ let cloneUrl = `https://github.com/${username}/homebrew-core.git`;
289
+ if (token) {
290
+ cloneUrl = `https://x-access-token:${token}@github.com/${username}/homebrew-core.git`;
291
+ }
292
+ execSync2(`git clone --depth 1 ${cloneUrl} ${tmpDir}`, {
293
+ stdio: "inherit"
294
+ });
228
295
  ensureGitIdentity(tmpDir);
229
296
  const formulaPath = join(tmpDir, "Formula", `${name}.rb`);
230
297
  let content;
@@ -251,10 +318,21 @@ function brewCore(options) {
251
318
  `git commit -m "${name} ${releaseCtx.version}"`,
252
319
  `git push origin ${branchName}`
253
320
  ].join(" && "), { stdio: "inherit" });
254
- execSync2([
321
+ const prUrl = execSync2([
255
322
  `cd ${tmpDir}`,
256
323
  `gh pr create --repo homebrew/homebrew-core --title "${name} ${releaseCtx.version}" --body "Update ${name} formula to version ${releaseCtx.version}"`
257
- ].join(" && "), { stdio: "inherit" });
324
+ ].join(" && "), { encoding: "utf-8", ...ghEnv }).trim();
325
+ const prNumber = prUrl.match(/\/pull\/(\d+)/)?.[1];
326
+ if (prNumber) {
327
+ ctx.runtime.rollback.add({
328
+ label: `Close homebrew-core PR #${prNumber}`,
329
+ fn: async () => {
330
+ const { execSync: execSyncRb } = await import("node:child_process");
331
+ execSyncRb(`gh pr close ${prNumber} --repo homebrew/homebrew-core --comment "Closed by pubm rollback"`, { stdio: "inherit", ...ghEnv });
332
+ },
333
+ confirm: true
334
+ });
335
+ }
258
336
  console.log(`PR created to homebrew/homebrew-core for ${name} ${releaseCtx.version}`);
259
337
  }
260
338
  }
@@ -263,6 +341,7 @@ function brewCore(options) {
263
341
  // src/brew-tap.ts
264
342
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
265
343
  import { dirname as dirname2, resolve as resolve2 } from "node:path";
344
+ import { resolvePhases as resolvePhases2 } from "@pubm/core";
266
345
  function brewTap(options) {
267
346
  return {
268
347
  name: "@pubm/plugin-brew-tap",
@@ -299,8 +378,72 @@ function brewTap(options) {
299
378
  ]
300
379
  }
301
380
  ],
381
+ credentials: (ctx) => {
382
+ if (!options.repo || ctx.options.mode !== "ci")
383
+ return [];
384
+ return [
385
+ {
386
+ key: "brew-github-token",
387
+ env: "PUBM_BREW_GITHUB_TOKEN",
388
+ label: "GitHub PAT for Homebrew tap",
389
+ tokenUrl: "https://github.com/settings/tokens/new?scopes=repo",
390
+ tokenUrlLabel: "github.com",
391
+ ghSecretName: "PUBM_BREW_GITHUB_TOKEN",
392
+ required: true
393
+ }
394
+ ];
395
+ },
396
+ checks: (ctx) => {
397
+ const phases = resolvePhases2(ctx.options);
398
+ if (!phases.includes("publish") && ctx.options.mode !== "ci")
399
+ return [];
400
+ if (ctx.options.mode === "ci") {
401
+ if (!options.repo)
402
+ return [];
403
+ return [
404
+ {
405
+ title: "Checking Homebrew tap token availability",
406
+ phase: "conditions",
407
+ task: async (ctx2, task) => {
408
+ const token = ctx2.runtime.pluginTokens?.["brew-github-token"];
409
+ if (!token) {
410
+ throw new Error("PUBM_BREW_GITHUB_TOKEN is required for Homebrew tap publishing.");
411
+ }
412
+ task.output = "Homebrew tap token verified";
413
+ }
414
+ }
415
+ ];
416
+ }
417
+ if (!options.repo)
418
+ return [];
419
+ const targetRepo = options.repo;
420
+ return [
421
+ {
422
+ title: "Checking git/gh access for Homebrew tap",
423
+ phase: "conditions",
424
+ task: async (_ctx, task) => {
425
+ const { execFileSync } = await import("node:child_process");
426
+ try {
427
+ execFileSync("gh", ["auth", "status"], { stdio: "pipe" });
428
+ task.output = "GitHub CLI authenticated";
429
+ } catch {
430
+ throw new Error("GitHub CLI is not authenticated. Run `gh auth login` first.");
431
+ }
432
+ const repoName = /^[^/]+\/[^/]+$/.test(targetRepo) ? targetRepo : targetRepo.match(/github\.com[/:]([^/]+\/[^/.]+)/)?.[1];
433
+ if (repoName) {
434
+ try {
435
+ execFileSync("gh", ["repo", "view", repoName, "--json", "name"], { stdio: "pipe" });
436
+ task.output = `Access to ${repoName} verified`;
437
+ } catch {
438
+ throw new Error(`Cannot access tap repository '${targetRepo}'. Check your GitHub permissions.`);
439
+ }
440
+ }
441
+ }
442
+ }
443
+ ];
444
+ },
302
445
  hooks: {
303
- afterRelease: async (_ctx, releaseCtx) => {
446
+ afterRelease: async (ctx, releaseCtx) => {
304
447
  if (options.packageName && releaseCtx.displayLabel !== options.packageName) {
305
448
  return;
306
449
  }
@@ -338,7 +481,17 @@ function brewTap(options) {
338
481
  const branch = `pubm/brew-formula-v${releaseCtx.version}`;
339
482
  execSync2(`git checkout -b ${branch}`, { stdio: "inherit" });
340
483
  execSync2(`git push origin ${branch}`, { stdio: "inherit" });
341
- execSync2(`gh pr create --title "chore(brew): update formula to ${releaseCtx.version}" --body "Automated formula update by pubm"`, { stdio: "inherit" });
484
+ const prUrl = execSync2(`gh pr create --title "chore(brew): update formula to ${releaseCtx.version}" --body "Automated formula update by pubm"`, { encoding: "utf-8" }).trim();
485
+ const prNumber = prUrl.match(/\/pull\/(\d+)/)?.[1];
486
+ if (prNumber) {
487
+ ctx.runtime.rollback.add({
488
+ label: `Close Homebrew tap PR #${prNumber}`,
489
+ fn: async () => {
490
+ execSync2(`gh pr close ${prNumber} --comment "Closed by pubm rollback"`, { stdio: "inherit" });
491
+ },
492
+ confirm: true
493
+ });
494
+ }
342
495
  console.log(`Created PR on branch ${branch}`);
343
496
  }
344
497
  return;
@@ -347,7 +500,6 @@ function brewTap(options) {
347
500
  const { tmpdir: tmpdir2 } = await import("node:os");
348
501
  const { basename, join: join2 } = await import("node:path");
349
502
  const { execSync: execSync2 } = await import("node:child_process");
350
- const { resolveGitHubToken } = await import("@pubm/core");
351
503
  const tmpDir = join2(tmpdir2(), `pubm-brew-tap-${Date.now()}`);
352
504
  const formulaFile = basename(formulaPath);
353
505
  const isShorthand = /^[^/]+\/[^/]+$/.test(options.repo);
@@ -355,10 +507,11 @@ function brewTap(options) {
355
507
  const ownerRepoMatch = repoUrl.match(/github\.com[/:]([^/]+\/[^/.]+?)(?:\.git)?$/);
356
508
  const ownerRepo = ownerRepoMatch?.[1] ?? options.repo;
357
509
  let cloneUrl = repoUrl;
358
- const tokenResult = resolveGitHubToken();
359
- if (tokenResult?.token && repoUrl.startsWith("https://github.com/")) {
360
- cloneUrl = repoUrl.replace("https://github.com/", `https://x-access-token:${tokenResult.token}@github.com/`);
510
+ const token = ctx.runtime.pluginTokens?.["brew-github-token"];
511
+ if (token && repoUrl.startsWith("https://github.com/")) {
512
+ cloneUrl = repoUrl.replace("https://github.com/", `https://x-access-token:${token}@github.com/`);
361
513
  }
514
+ const ghEnv = token ? { env: { ...process.env, GH_TOKEN: token } } : {};
362
515
  execSync2(`git clone --depth 1 ${cloneUrl} ${tmpDir}`, {
363
516
  stdio: "inherit"
364
517
  });
@@ -381,7 +534,18 @@ function brewTap(options) {
381
534
  execSync2(`cd ${tmpDir} && git push origin ${branch}`, {
382
535
  stdio: "inherit"
383
536
  });
384
- execSync2(`gh pr create --repo ${ownerRepo} --title "chore(brew): update formula to ${releaseCtx.version}" --body "Automated formula update by pubm"`, { stdio: "inherit" });
537
+ const prUrl = execSync2(`gh pr create --repo ${ownerRepo} --title "chore(brew): update formula to ${releaseCtx.version}" --body "Automated formula update by pubm"`, { encoding: "utf-8", ...ghEnv }).trim();
538
+ const prNumber = prUrl.match(/\/pull\/(\d+)/)?.[1];
539
+ if (prNumber) {
540
+ const repoFlag = ownerRepo;
541
+ ctx.runtime.rollback.add({
542
+ label: `Close Homebrew tap PR #${prNumber} (${repoFlag})`,
543
+ fn: async () => {
544
+ execSync2(`gh pr close ${prNumber} --repo ${repoFlag} --comment "Closed by pubm rollback"`, { stdio: "inherit", ...ghEnv });
545
+ },
546
+ confirm: true
547
+ });
548
+ }
385
549
  console.log(`Created PR on branch ${branch}`);
386
550
  }
387
551
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pubm/plugin-brew",
3
- "version": "0.4.7",
3
+ "version": "0.4.8",
4
4
  "type": "module",
5
5
  "description": "pubm plugin for Homebrew formula publishing workflows",
6
6
  "main": "./dist/index.js",