tegami 0.0.1 → 0.1.0-beta.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.
@@ -1,10 +1,13 @@
1
- import { r as isNodeError, t as PackageGraph } from "./workspace-B5_i21S0.mjs";
2
- import { cargo } from "./providers/cargo.mjs";
3
- import { a as planStoreSchema, i as changelogFrontmatterSchema, r as npm } from "./npm-BpFlXy8I.mjs";
4
- import { i as maxBump, t as bumpVersion } from "./semver-FSWx3_34.mjs";
5
- import { simpleGenerator } from "./generators/simple.mjs";
1
+ import { n as handlePluginError } from "./error-DBK-9uBa.js";
2
+ import { i as maxBump } from "./semver-mWK2Khi2.js";
3
+ import { t as PackageGraph } from "./graph-CUgwuRW5.js";
4
+ import { cargo } from "./providers/cargo.js";
5
+ import { t as changelogFrontmatterSchema } from "./schemas-Cc4h6bq5.js";
6
+ import { npm } from "./providers/npm.js";
7
+ import { simpleGenerator } from "./generators/simple.js";
8
+ import { i as readPlanStore, n as publishPlanStatus, r as createPlanStore, t as assertPublishPlanFinished } from "./checks-CKBRRjgQ.js";
6
9
  import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
7
- import path, { basename, dirname, join, resolve } from "node:path";
10
+ import path, { basename, dirname, join } from "node:path";
8
11
  import { x } from "tinyexec";
9
12
  import { load } from "js-yaml";
10
13
  import { fromMarkdown } from "mdast-util-from-markdown";
@@ -19,7 +22,7 @@ async function createChangelog(context, options = {}) {
19
22
  if (group) group.push(commit);
20
23
  else groups.set(key, [commit]);
21
24
  }
22
- const directory = join(context.cwd, context.changelogDir);
25
+ const directory = context.changelogDir;
23
26
  await mkdir(directory, { recursive: true });
24
27
  const created = [];
25
28
  const stamp = Date.now().toString(36);
@@ -132,17 +135,18 @@ function commandOutput(result) {
132
135
  //#endregion
133
136
  //#region src/context.ts
134
137
  async function createTegamiContext(options = {}) {
135
- const cwd = resolve(options.cwd ?? process.cwd());
138
+ const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
139
+ const changelogDir = path.resolve(cwd, options.changelogDir ?? ".tegami");
136
140
  const graph = new PackageGraph();
137
141
  const registryClients = /* @__PURE__ */ new Map();
138
142
  const ctx = {
139
143
  cwd,
140
- changelogDir: options.changelogDir ?? ".tegami",
141
- planPath: resolve(cwd, options.planPath ?? join(".tegami", "publish-plan.json")),
144
+ changelogDir,
145
+ planPath: options.planPath ? path.resolve(cwd, options.planPath) : path.join(changelogDir, "publish-plan"),
142
146
  options,
143
147
  plugins: resolvePlugins([
144
- npm(options.npmClient),
145
- cargo(),
148
+ npm(options.npm),
149
+ cargo(options.cargo),
146
150
  ...options.plugins ?? []
147
151
  ]),
148
152
  graph,
@@ -160,10 +164,25 @@ async function createTegamiContext(options = {}) {
160
164
  return client;
161
165
  }
162
166
  };
163
- for (const plugin of ctx.plugins) await plugin.init?.call(ctx);
164
- for (const plugin of ctx.plugins) await plugin.resolve?.call(ctx);
167
+ for (const plugin of ctx.plugins) await handlePluginError(plugin, "init", () => plugin.init?.call(ctx));
168
+ for (const plugin of ctx.plugins) await handlePluginError(plugin, "resolve", () => plugin.resolve?.call(ctx));
169
+ const ignoreMatchers = options.ignore?.map((pattern) => {
170
+ if (pattern instanceof RegExp) return (pkg) => pattern.test(pkg.name) || pattern.test(pkg.id);
171
+ return (pkg) => pkg.name === pattern || pkg.id === pattern;
172
+ });
173
+ for (const [name, groupOptions] of Object.entries(options.groups ?? {})) graph.registerGroup(name, groupOptions);
174
+ for (const pkg of graph.getPackages()) {
175
+ if (ignoreMatchers?.length && ignoreMatchers.some((matcher) => matcher(pkg))) {
176
+ graph.delete(pkg.id);
177
+ continue;
178
+ }
179
+ const packageOptions = options.packages?.[pkg.id] ?? options.packages?.[pkg.name];
180
+ if (!packageOptions) continue;
181
+ pkg.setPackageOptions(packageOptions);
182
+ if (packageOptions.group) graph.addGroupMember(packageOptions.group, pkg.id);
183
+ }
165
184
  for (const plugin of ctx.plugins) {
166
- const clients = await plugin.createRegistryClient?.call(ctx);
185
+ const clients = await handlePluginError(plugin, "createRegistryClient", () => plugin.createRegistryClient?.call(ctx));
167
186
  if (!clients) continue;
168
187
  for (const client of Array.isArray(clients) ? clients : [clients]) registryClients.set(client.id, client);
169
188
  }
@@ -178,187 +197,165 @@ function resolvePlugins(plugins = []) {
178
197
  return plugins.flat(Infinity).sort((a, b) => PLUGIN_ORDER[a.enforce ?? "default"] - PLUGIN_ORDER[b.enforce ?? "default"]);
179
198
  }
180
199
  //#endregion
181
- //#region src/draft.ts
200
+ //#region src/plans/policy.ts
201
+ function groupPolicy({ graph }) {
202
+ return {
203
+ id: "group",
204
+ onUpdate({ pkg, plan }) {
205
+ if (!plan.type) return;
206
+ const group = graph.getPackageGroup(pkg.id);
207
+ if (!group || !group.options.syncBump) return;
208
+ for (const member of group.packages) {
209
+ if (member === pkg) continue;
210
+ this.bumpPackage(member, {
211
+ type: plan.type,
212
+ reason: `sync "${group.name}" group package versions`
213
+ });
214
+ }
215
+ }
216
+ };
217
+ }
218
+ //#endregion
219
+ //#region src/plans/draft.ts
182
220
  var DraftPlan = class {
183
- changelogs;
184
- packages;
185
221
  context;
186
- #created = false;
187
- constructor(changelogs, packages, context) {
188
- this.changelogs = changelogs;
189
- this.packages = packages;
222
+ #applied = false;
223
+ packages = /* @__PURE__ */ new Map();
224
+ changelogs = /* @__PURE__ */ new Map();
225
+ policies = [];
226
+ constructor(context) {
190
227
  this.context = context;
228
+ this.policies.push(groupPolicy(context));
191
229
  }
192
- getPackageIds() {
193
- return Array.from(this.packages.keys());
230
+ getPackagePlans() {
231
+ return this.packages;
194
232
  }
195
- getPackage(id) {
233
+ getPackagePlan(id) {
196
234
  return this.packages.get(id);
197
235
  }
198
- setPackage(id, plan = {}) {
199
- this.packages.set(id, {
200
- ...plan,
201
- changelogIds: plan.changelogIds ?? /* @__PURE__ */ new Set(),
202
- publish: plan.publish ?? true,
203
- type: plan.type ?? "patch"
236
+ bumpPackage(pkg, { type, reason }) {
237
+ let plan = this.packages.get(pkg.id);
238
+ if (!plan) {
239
+ plan = this.initPackagePlan(pkg);
240
+ this.packages.set(pkg.id, plan);
241
+ }
242
+ const prevVersion = plan.bumpVersion(pkg);
243
+ plan.type = plan.type ? maxBump(plan.type, type) : type;
244
+ if (reason) {
245
+ plan.bumpReasons ??= /* @__PURE__ */ new Set();
246
+ plan.bumpReasons.add(reason);
247
+ }
248
+ if (prevVersion !== plan.bumpVersion(pkg)) for (const policy of this.policies) policy.onUpdate?.call(this, {
249
+ plan,
250
+ pkg
204
251
  });
252
+ return plan;
205
253
  }
206
- deletePackage(id) {
207
- return this.packages.delete(id);
254
+ hasPending() {
255
+ const { graph } = this.context;
256
+ for (const [id, plan] of this.packages) {
257
+ const pkg = graph.get(id);
258
+ if (pkg && plan.bumpVersion(pkg) !== pkg.version) return true;
259
+ }
260
+ return false;
208
261
  }
209
- getChangelogIds() {
210
- return Array.from(this.changelogs.keys());
262
+ getChangelogs() {
263
+ return Array.from(this.changelogs.values());
211
264
  }
212
265
  getChangelog(id) {
213
266
  return this.changelogs.get(id);
214
267
  }
215
- setChangelog(id, entry) {
216
- this.changelogs.set(id, entry);
268
+ addChangelog(entry) {
269
+ this.changelogs.set(entry.id, entry);
270
+ const { graph } = this.context;
271
+ const groupPackages = /* @__PURE__ */ new Set();
272
+ for (const name of entry.packages) for (const pkg of graph.getByName(name)) groupPackages.add(pkg);
273
+ for (const pkg of groupPackages) {
274
+ const plan = this.bumpPackage(pkg, { type: entry.type });
275
+ plan.changelogs ??= [];
276
+ plan.changelogs.push(entry);
277
+ }
217
278
  }
218
279
  deleteChangelog(id) {
219
280
  return this.changelogs.delete(id);
220
281
  }
221
- /** Write the publish plan, update package versions, and consume changelog files. */
222
- async createPublishPlan() {
223
- this.assertEditable();
224
- await this.assertPublishPlanFinished();
225
- this.#created = true;
226
- await this.applyVersionChanges();
227
- const plan = {
228
- id: `tegami-${Date.now().toString(36)}`,
229
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
230
- changelogs: Object.fromEntries(Array.from(this.changelogs, ([id, entry]) => [id, {
231
- filename: entry.filename,
232
- subject: entry.subject,
233
- packages: Array.from(entry.packages),
234
- type: entry.type,
235
- title: entry.title,
236
- content: entry.content
237
- }])),
238
- packages: Object.fromEntries(this.packages)
239
- };
240
- await mkdir(dirname(this.context.planPath), { recursive: true });
241
- await writeFile(this.context.planPath, planStoreSchema.encode(plan));
242
- await this.removeConsumedChangelogs();
243
- }
244
- editable() {
245
- return !this.#created;
282
+ initPackagePlan(pkg) {
283
+ const context = this.context;
284
+ const plan = pkg.onPlan(context);
285
+ const group = context.graph.getPackageGroup(pkg.id);
286
+ if (group) plan.prerelease ??= group.options.prerelease;
287
+ return plan;
246
288
  }
247
- async assertPublishPlanFinished() {
248
- const content = await readFile(this.context.planPath, "utf8").catch(() => void 0);
249
- if (!content) return;
250
- const parsed = planStoreSchema.safeDecode(content);
251
- if (!parsed.success) return;
252
- const status = await publishPlanStatus(this.context, parsed.data);
253
- if (status.state === "success") return;
254
- const message = `Publish plan already exists at ${this.context.planPath} and is ${status.state}. Publish it before creating a new plan.`;
255
- throw new Error(status.error ? `${message}\n${status.error}` : message);
256
- }
257
- async applyVersionChanges() {
289
+ /** Apply the publish plan: update package versions, write the plan file, and consume changelog files. */
290
+ async applyPlan() {
291
+ if (this.#applied) throw new Error("This draft has already applied a publish plan.");
292
+ await assertPublishPlanFinished(this.context);
293
+ this.#applied = true;
294
+ for (const plugin of this.context.plugins) await handlePluginError(plugin, "applyPlan", () => plugin.applyPlan?.call(this.context, this));
258
295
  const { graph } = this.context;
259
- const updatedPackages = /* @__PURE__ */ new Map();
260
296
  const writes = [];
261
- for (const [id, plan] of this.packages) {
297
+ for (const [id, packagePlan] of this.packages) {
262
298
  const pkg = graph.get(id);
263
299
  if (!pkg) continue;
264
- updatedPackages.set(id, {
265
- plan,
266
- version: bumpVersion(pkg.version, plan.type)
267
- });
268
- }
269
- for (const pkg of graph.getPackages()) {
270
- const updated = updatedPackages.get(pkg.id);
271
- for (const [id, updatedDep] of updatedPackages) {
272
- const target = graph.get(id);
273
- if (target) await pkg.updateDependency?.(target, updatedDep.version, this.context);
274
- }
275
- if (updated) {
276
- pkg.setVersion?.(updated.version);
277
- writes.push(this.appendChangelog(pkg, updated.plan));
278
- }
279
- const write = pkg.write?.();
280
- if (write) writes.push(write);
300
+ writes.push(this.appendChangelog(pkg, packagePlan));
281
301
  }
302
+ for (const entry of this.changelogs.values()) writes.push(rm(path.join(this.context.changelogDir, entry.filename), { force: true }));
282
303
  await Promise.all(writes);
304
+ await mkdir(dirname(this.context.planPath), { recursive: true });
305
+ await writeFile(this.context.planPath, createPlanStore(this, this.context));
283
306
  }
284
- async removeConsumedChangelogs() {
285
- const writes = [];
286
- for (const entry of this.changelogs.values()) {
287
- const file = path.resolve(this.context.cwd, this.context.changelogDir, entry.filename);
288
- writes.push(rm(file, { force: true }));
289
- }
290
- await Promise.all(writes);
307
+ addPolicy(policy) {
308
+ this.policies.push(policy);
309
+ }
310
+ removePolicy(policy) {
311
+ const idx = this.policies.indexOf(policy);
312
+ if (idx !== -1) this.policies.splice(idx, 1);
291
313
  }
292
- assertEditable() {
293
- if (this.#created) throw new Error("This draft has already created a publish plan.");
314
+ canApply() {
315
+ return !this.#applied;
294
316
  }
295
317
  async appendChangelog(pkg, plan) {
296
- if (plan.changelogIds.size === 0) return;
318
+ if (!plan.changelogs || plan.changelogs.length === 0) return;
297
319
  const { generator = simpleGenerator() } = this.context.options;
298
- const changelogs = [];
299
- for (const id of plan.changelogIds) {
300
- const entry = this.changelogs.get(id);
301
- if (entry) changelogs.push(entry);
302
- }
303
320
  const generated = await generator.generate.call(this.context, {
321
+ packageId: pkg.id,
304
322
  packageName: pkg.name,
305
323
  version: pkg.version,
306
- distTag: plan.distTag,
307
- changelogs
324
+ npm: plan.npm,
325
+ plan,
326
+ changelogs: plan.changelogs,
327
+ _draft: this
308
328
  });
309
329
  const path = join(pkg.path, "CHANGELOG.md");
310
330
  const existing = await readFile(path, "utf8").catch(() => "");
311
331
  await writeFile(path, `${generated.trim()}\n\n${existing}`.trimEnd() + "\n");
312
332
  }
313
- /** {@link createPublishPlan} but for `await using` syntax */
333
+ /** {@link applyPlan} but for `await using` syntax */
314
334
  async [Symbol.asyncDispose]() {
315
- return this.createPublishPlan();
335
+ return this.applyPlan();
316
336
  }
317
337
  };
318
- async function publishPlanStatus(context, plan) {
319
- for (const [id, pkgPlan] of Object.entries(plan.packages)) {
320
- const pkg = context.graph.get(id);
321
- if (!pkg || !pkgPlan.publish) continue;
322
- if (!await context.getRegistryClient(pkg).packageVersionExists(pkg, pkg.version)) return { state: "pending" };
323
- }
324
- return { state: "success" };
338
+ async function cleanupPublishPlan(context) {
339
+ const store = await readPlanStore(context);
340
+ if (!store) return {
341
+ state: "skipped",
342
+ reason: "missing"
343
+ };
344
+ if ((await publishPlanStatus(store, context)).state !== "success") return {
345
+ state: "skipped",
346
+ reason: "pending"
347
+ };
348
+ await rm(context.planPath, { force: true });
349
+ return { state: "removed" };
325
350
  }
326
- function createDraftPlan(changelogs, context) {
327
- const changelogMap = /* @__PURE__ */ new Map();
328
- const byPackage = /* @__PURE__ */ new Map();
329
- for (const entry of changelogs) {
330
- changelogMap.set(entry.id, entry);
331
- for (const requestedPackage of entry.packages) for (const pkg of context.graph.getByName(requestedPackage)) {
332
- let entries = byPackage.get(pkg);
333
- if (!entries) {
334
- entries = [];
335
- byPackage.set(pkg, entries);
336
- }
337
- entries.push(entry);
338
- }
351
+ async function createDraftPlan(changelogs, context) {
352
+ let draft = new DraftPlan(context);
353
+ for (const plugin of context.plugins) {
354
+ const result = await handlePluginError(plugin, "initPlan", () => plugin.initPlan?.call(context, draft));
355
+ if (result) draft = result;
339
356
  }
340
- const packages = /* @__PURE__ */ new Map();
341
- for (const [pkg, entries] of byPackage.entries()) {
342
- const plan = createPackagePlan(pkg, entries, context);
343
- if (plan) packages.set(pkg.id, plan);
344
- }
345
- return new DraftPlan(changelogMap, packages, context);
346
- }
347
- function createPackagePlan(pkg, entries, context) {
348
- if (entries.length === 0) return null;
349
- const packageOptions = context.options.packages?.[pkg.id] ?? context.options.packages?.[pkg.name] ?? {};
350
- let type = entries[0].type;
351
- const changelogIds = /* @__PURE__ */ new Set();
352
- for (const entry of entries) {
353
- changelogIds.add(entry.id);
354
- type = maxBump(type, entry.type);
355
- }
356
- return {
357
- type,
358
- changelogIds,
359
- distTag: packageOptions.distTag ?? pkg.distTag,
360
- publish: packageOptions.publish ?? pkg.publish
361
- };
357
+ for (const entry of changelogs) draft.addChangelog(entry);
358
+ return draft;
362
359
  }
363
360
  //#endregion
364
361
  //#region src/utils/frontmatter.ts
@@ -384,20 +381,16 @@ function frontmatter(input) {
384
381
  }
385
382
  //#endregion
386
383
  //#region src/changelog/parse.ts
387
- async function readChangelogEntries(cwd, changelogDir) {
388
- const directory = resolve(cwd, changelogDir);
389
- const files = await readdir(directory).catch((error) => {
390
- if (isNodeError(error) && error.code === "ENOENT") return [];
391
- throw error;
392
- });
393
- const entries = [];
394
- for (const file of files.sort()) {
395
- if (!file.endsWith(".md")) continue;
396
- const filePath = join(directory, file);
397
- const content = await readFile(filePath, "utf8");
398
- entries.push(...parseChangelogFile(filePath, content));
399
- }
400
- return entries;
384
+ async function getChangelogFiles(context) {
385
+ return (await readdir(context.changelogDir).catch(() => [])).filter((file) => file.endsWith(".md"));
386
+ }
387
+ async function readChangelogEntries(context) {
388
+ const dir = context.changelogDir;
389
+ const files = await getChangelogFiles(context);
390
+ return (await Promise.all(files.map(async (file) => {
391
+ const filePath = join(dir, file);
392
+ return parseChangelogFile(filePath, await readFile(filePath, "utf8"));
393
+ }))).flat();
401
394
  }
402
395
  /** Parse one changelog markdown file into release entries. */
403
396
  function parseChangelogFile(file, content) {
@@ -465,12 +458,13 @@ function headingToBump(depth) {
465
458
  async function publishFromPlan(context, store, options) {
466
459
  const { dryRun = false } = options;
467
460
  const packages = [];
461
+ if ((await publishPlanStatus(store, context)).state !== "pending") return { state: "skipped" };
468
462
  for (const [id, plan] of Object.entries(store.packages)) {
469
463
  if (!plan.publish) continue;
470
464
  const pkg = context.graph.get(id);
471
465
  if (!pkg) continue;
472
466
  const changelogs = [];
473
- for (const id of plan.changelogIds) {
467
+ for (const id of plan.changelogIds ?? []) {
474
468
  const entry = store.changelogs[id];
475
469
  if (entry) changelogs.push({
476
470
  ...entry,
@@ -479,12 +473,12 @@ async function publishFromPlan(context, store, options) {
479
473
  });
480
474
  }
481
475
  if (!dryRun) {
482
- if (await context.getRegistryClient(pkg).packageVersionExists(pkg, pkg.version)) {
476
+ if (await context.getRegistryClient(pkg).isPackagePublished(pkg)) {
483
477
  packages.push({
484
478
  id: pkg.id,
485
479
  name: pkg.name,
486
480
  version: pkg.version,
487
- distTag: plan.distTag,
481
+ npm: plan.npm,
488
482
  state: "success",
489
483
  changelogs
490
484
  });
@@ -492,12 +486,15 @@ async function publishFromPlan(context, store, options) {
492
486
  }
493
487
  }
494
488
  try {
495
- if (!dryRun) await context.getRegistryClient(pkg).publish(pkg, { distTag: plan.distTag });
489
+ if (!dryRun) await context.getRegistryClient(pkg).publish(pkg, {
490
+ packageStore: plan,
491
+ store
492
+ });
496
493
  packages.push({
497
494
  id: pkg.id,
498
495
  name: pkg.name,
499
496
  version: pkg.version,
500
- distTag: plan.distTag,
497
+ npm: plan.npm,
501
498
  state: "success",
502
499
  changelogs
503
500
  });
@@ -506,19 +503,15 @@ async function publishFromPlan(context, store, options) {
506
503
  id: pkg.id,
507
504
  name: pkg.name,
508
505
  version: pkg.version,
509
- distTag: plan.distTag,
506
+ npm: plan.npm,
510
507
  changelogs,
511
508
  state: "failed",
512
509
  error: error instanceof Error ? error.message : String(error)
513
510
  });
514
511
  }
515
512
  }
516
- if (packages.length === 0) return {
517
- state: "skipped",
518
- planPath: context.planPath
519
- };
513
+ if (packages.length === 0) return { state: "skipped" };
520
514
  return {
521
- planPath: context.planPath,
522
515
  state: packages.some((pkg) => pkg.state === "failed") ? "failed" : "created",
523
516
  packages,
524
517
  _rawPlan: store
@@ -547,31 +540,23 @@ function tegami(options = {}) {
547
540
  },
548
541
  async draft() {
549
542
  const context = await $context;
550
- let plan = createDraftPlan(await readChangelogEntries(context.cwd, context.changelogDir), context);
551
- for (const plugin of context.plugins) plan = await plugin.initPlan?.call(context, plan) ?? plan;
552
- return plan;
543
+ return createDraftPlan(await readChangelogEntries(context), context);
553
544
  },
554
545
  async publish(publishOptions = {}) {
555
546
  const context = await $context;
556
- if ((await readChangelogEntries(context.cwd, context.changelogDir)).length > 0) return {
557
- state: "skipped",
558
- planPath: context.planPath
559
- };
560
- const parsed = await readFile(context.planPath, "utf8").then((content) => planStoreSchema.decode(content)).catch((error) => {
561
- if (isNodeError(error) && error.code === "ENOENT") return void 0;
562
- throw error;
563
- });
564
- if (parsed === void 0) return {
565
- state: "skipped",
566
- planPath: context.planPath
567
- };
547
+ if ((await getChangelogFiles(context)).length > 0) return { state: "skipped" };
548
+ const parsed = await readPlanStore(context);
549
+ if (!parsed) return { state: "skipped" };
568
550
  let result = await publishFromPlan(context, parsed, publishOptions);
569
551
  const publishCtx = {
570
552
  ...context,
571
553
  publishOptions
572
554
  };
573
- for (const plugin of context.plugins) result = await plugin.afterPublish?.call(publishCtx, result) ?? result;
555
+ for (const plugin of context.plugins) result = await handlePluginError(plugin, "afterPublish", () => plugin.afterPublish?.call(publishCtx, result)) ?? result;
574
556
  return result;
557
+ },
558
+ async cleanup() {
559
+ return cleanupPublishPlan(await $context);
575
560
  }
576
561
  };
577
562
  }
@@ -1,4 +1,4 @@
1
- import { s as TegamiPlugin } from "../context-CC36jtz1.mjs";
1
+ import { d as TegamiPlugin } from "../index-Bhh2dJZp.js";
2
2
 
3
3
  //#region src/plugins/git.d.ts
4
4
  interface GitPluginOptions {
@@ -0,0 +1,84 @@
1
+ import { t as execFailure } from "../error-DBK-9uBa.js";
2
+ import { t as isCI } from "../constants-B9qjNfvr.js";
3
+ import { x } from "tinyexec";
4
+ //#region src/plugins/git.ts
5
+ /**
6
+ * Basic Git integrations:
7
+ * - auto tags.
8
+ *
9
+ * Note: you do not need this with `github` plugin enabled.
10
+ */
11
+ function git(options = {}) {
12
+ const { createTags = true, pushTags = isCI() } = options;
13
+ function resolveGitTag(context, result) {
14
+ const { graph } = context;
15
+ const pkg = graph.get(result.id);
16
+ if (!pkg) return `${result.name}@${result.version}`;
17
+ const group = graph.getPackageGroup(pkg.id);
18
+ if (group?.options.syncGitTag) return `${group.name}@${result.version}`;
19
+ return `${result.name}@${result.version}`;
20
+ }
21
+ return {
22
+ name: "git",
23
+ enforce: "pre",
24
+ cli: { async init() {
25
+ if (!isCI()) return;
26
+ const gitOptions = { nodeOptions: { cwd: this.cwd } };
27
+ for (const args of [[
28
+ "config",
29
+ "user.name",
30
+ "github-actions[bot]"
31
+ ], [
32
+ "config",
33
+ "user.email",
34
+ "41898282+github-actions[bot]@users.noreply.github.com"
35
+ ]]) {
36
+ const result = await x("git", [...args], gitOptions);
37
+ if (result.exitCode !== 0) throw execFailure("Failed to configure git user for GitHub Actions.", result);
38
+ }
39
+ } },
40
+ async afterPublish(result) {
41
+ const { cwd, publishOptions: { dryRun = false } } = this;
42
+ if (dryRun || !createTags || result.state !== "created") return result;
43
+ const pendingTags = /* @__PURE__ */ new Set();
44
+ for (const pkg of result.packages) {
45
+ pkg.gitTag = resolveGitTag(this, pkg);
46
+ pendingTags.add(pkg.gitTag);
47
+ }
48
+ try {
49
+ const createdTags = [];
50
+ await Promise.all(Array.from(pendingTags).map(async (tag) => {
51
+ if (await gitTagExists(cwd, tag)) return;
52
+ const gitOut = await x("git", ["tag", tag], { nodeOptions: { cwd } });
53
+ if (gitOut.exitCode !== 0) throw execFailure(`Failed to create Git tag "${tag}" for release`, gitOut);
54
+ createdTags.push(tag);
55
+ }));
56
+ if (pushTags && createdTags.length > 0) {
57
+ const gitOut = await x("git", [
58
+ "push",
59
+ "origin",
60
+ ...createdTags
61
+ ], { nodeOptions: { cwd } });
62
+ if (gitOut.exitCode !== 0) throw execFailure(`Failed to push Git tags to origin: ${createdTags.join(", ")}`, gitOut);
63
+ }
64
+ } catch (error) {
65
+ return {
66
+ ...result,
67
+ state: "failed",
68
+ error: error instanceof Error ? error.message : String(error)
69
+ };
70
+ }
71
+ return result;
72
+ }
73
+ };
74
+ }
75
+ async function gitTagExists(cwd, tag) {
76
+ return (await x("git", [
77
+ "rev-parse",
78
+ "-q",
79
+ "--verify",
80
+ `refs/tags/${tag}`
81
+ ], { nodeOptions: { cwd } })).exitCode === 0;
82
+ }
83
+ //#endregion
84
+ export { git };
@@ -1,5 +1,5 @@
1
- import { h as PackagePublishResult, n as Awaitable, s as TegamiPlugin } from "../context-CC36jtz1.mjs";
2
- import { GitPluginOptions } from "./git.mjs";
1
+ import { a as Awaitable, d as TegamiPlugin, w as DraftPlan, x as PackagePublishResult } from "../index-Bhh2dJZp.js";
2
+ import { GitPluginOptions } from "./git.js";
3
3
 
4
4
  //#region src/plugins/github.d.ts
5
5
  interface GithubRelease {
@@ -10,29 +10,43 @@ interface GithubRelease {
10
10
  /** Whether to mark release as prerelease */
11
11
  prerelease?: boolean;
12
12
  }
13
- interface VersionPullRequestOptions {
14
- /** Pull request branch. */
15
- branch?: string;
16
- /** Pull request base branch. */
17
- base?: string;
13
+ interface VersionPullRequest {
18
14
  /** Pull request title. */
19
15
  title?: string;
20
16
  /** Pull request body. */
21
17
  body?: string;
22
18
  }
19
+ interface VersionPullRequestOptions {
20
+ /**
21
+ * Pull request branch.
22
+ *
23
+ * @default "tegami/version-packages"
24
+ */
25
+ branch?: string;
26
+ /**
27
+ * Pull request base branch.
28
+ *
29
+ * @default "main"
30
+ */
31
+ base?: string;
32
+ }
23
33
  /** Options for creating GitHub releases after a successful publish. */
24
34
  interface GitHubPluginOptions extends GitPluginOptions {
25
35
  /** GitHub repository. */
26
36
  repo?: string;
27
- /** override release details, return `false` to skip */
37
+ /** Override release details for a single package, return `false` to skip. */
28
38
  onCreateRelease?: (result: PackagePublishResult) => Awaitable<GithubRelease | false>;
39
+ /** Override release details when multiple packages share a git tag, return `false` to skip. */
40
+ onCreateGroupedRelease?: (packages: PackagePublishResult[]) => Awaitable<GithubRelease | false>;
41
+ /** Override details for "Version Packages" PR. */
42
+ onCreateVersionPullRequest?: (publishPlan: DraftPlan) => Awaitable<VersionPullRequest | false>;
29
43
  cli?: {
30
44
  /**
31
45
  * Open a version pull request after versioning.
32
46
  * Defaults to enabled in CI and disabled locally.
33
47
  * Set to `true` to always create the pull request.
34
48
  */
35
- createVersionPR?: boolean | VersionPullRequestOptions;
49
+ versionPr?: boolean | VersionPullRequestOptions;
36
50
  };
37
51
  }
38
52
  /** Create GitHub releases for successfully published packages after the whole plan succeeds. */