tegami 0.0.0 → 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,11 +1,14 @@
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-BSE_dtB3.mjs";
4
- 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";
5
9
  import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
6
- import path, { basename, dirname, join, resolve } from "node:path";
10
+ import path, { basename, dirname, join } from "node:path";
7
11
  import { x } from "tinyexec";
8
- import { inc } from "semver";
9
12
  import { load } from "js-yaml";
10
13
  import { fromMarkdown } from "mdast-util-from-markdown";
11
14
  import { toMarkdown } from "mdast-util-to-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,195 +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/utils/semver.ts
182
- function maxBump(a, b) {
183
- if (a === "major" || b === "major") return "major";
184
- if (a === "minor" || b === "minor") return "minor";
185
- return "patch";
186
- }
187
- function bumpVersion(version, type) {
188
- const next = inc(version, type);
189
- if (!next) throw new Error(`Invalid semver version: ${version}`);
190
- return next;
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
+ };
191
217
  }
192
218
  //#endregion
193
- //#region src/draft.ts
219
+ //#region src/plans/draft.ts
194
220
  var DraftPlan = class {
195
- changelogs;
196
- packages;
197
221
  context;
198
- #created = false;
199
- constructor(changelogs, packages, context) {
200
- this.changelogs = changelogs;
201
- this.packages = packages;
222
+ #applied = false;
223
+ packages = /* @__PURE__ */ new Map();
224
+ changelogs = /* @__PURE__ */ new Map();
225
+ policies = [];
226
+ constructor(context) {
202
227
  this.context = context;
228
+ this.policies.push(groupPolicy(context));
203
229
  }
204
- getPackageIds() {
205
- return Array.from(this.packages.keys());
230
+ getPackagePlans() {
231
+ return this.packages;
206
232
  }
207
- getPackage(id) {
233
+ getPackagePlan(id) {
208
234
  return this.packages.get(id);
209
235
  }
210
- setPackage(id, plan = {}) {
211
- this.packages.set(id, {
212
- ...plan,
213
- changelogIds: plan.changelogIds ?? /* @__PURE__ */ new Set(),
214
- publish: plan.publish ?? true,
215
- 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
216
251
  });
252
+ return plan;
217
253
  }
218
- deletePackage(id) {
219
- 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;
220
261
  }
221
- getChangelogIds() {
222
- return Array.from(this.changelogs.keys());
262
+ getChangelogs() {
263
+ return Array.from(this.changelogs.values());
223
264
  }
224
265
  getChangelog(id) {
225
266
  return this.changelogs.get(id);
226
267
  }
227
- setChangelog(id, entry) {
228
- 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
+ }
229
278
  }
230
279
  deleteChangelog(id) {
231
280
  return this.changelogs.delete(id);
232
281
  }
233
- /** Write the publish plan, update package versions, and consume changelog files. */
234
- async createPublishPlan() {
235
- this.assertEditable();
236
- await this.assertPublishPlanFinished();
237
- this.#created = true;
238
- await this.applyVersionChanges();
239
- const plan = {
240
- id: `tegami-${Date.now().toString(36)}`,
241
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
242
- changelogs: Object.fromEntries(Array.from(this.changelogs, ([id, entry]) => [id, {
243
- filename: entry.filename,
244
- subject: entry.subject,
245
- packages: Array.from(entry.packages),
246
- type: entry.type,
247
- title: entry.title,
248
- content: entry.content
249
- }])),
250
- packages: Object.fromEntries(this.packages)
251
- };
252
- await mkdir(dirname(this.context.planPath), { recursive: true });
253
- await writeFile(this.context.planPath, planStoreSchema.encode(plan));
254
- await this.removeConsumedChangelogs();
255
- }
256
- async assertPublishPlanFinished() {
257
- const content = await readFile(this.context.planPath, "utf8").catch(() => void 0);
258
- if (!content) return;
259
- const parsed = planStoreSchema.safeDecode(content);
260
- if (!parsed.success) return;
261
- const status = await publishPlanStatus(this.context, parsed.data);
262
- if (status.state === "success") return;
263
- const message = `Publish plan already exists at ${this.context.planPath} and is ${status.state}. Publish it before creating a new plan.`;
264
- throw new Error(status.error ? `${message}\n${status.error}` : message);
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;
265
288
  }
266
- 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));
267
295
  const { graph } = this.context;
268
- const updatedPackages = /* @__PURE__ */ new Map();
269
296
  const writes = [];
270
- for (const [id, plan] of this.packages) {
297
+ for (const [id, packagePlan] of this.packages) {
271
298
  const pkg = graph.get(id);
272
299
  if (!pkg) continue;
273
- updatedPackages.set(id, {
274
- plan,
275
- version: bumpVersion(pkg.version, plan.type)
276
- });
277
- }
278
- for (const pkg of graph.getPackages()) {
279
- const updated = updatedPackages.get(pkg.id);
280
- for (const [id, updatedDep] of updatedPackages) {
281
- const target = graph.get(id);
282
- if (target) await pkg.updateDependency?.(target, updatedDep.version, this.context);
283
- }
284
- if (updated) {
285
- pkg.setVersion?.(updated.version);
286
- writes.push(this.appendChangelog(pkg, updated.plan));
287
- }
288
- const write = pkg.write?.();
289
- if (write) writes.push(write);
300
+ writes.push(this.appendChangelog(pkg, packagePlan));
290
301
  }
302
+ for (const entry of this.changelogs.values()) writes.push(rm(path.join(this.context.changelogDir, entry.filename), { force: true }));
291
303
  await Promise.all(writes);
304
+ await mkdir(dirname(this.context.planPath), { recursive: true });
305
+ await writeFile(this.context.planPath, createPlanStore(this, this.context));
292
306
  }
293
- async removeConsumedChangelogs() {
294
- const writes = [];
295
- for (const entry of this.changelogs.values()) {
296
- const file = path.resolve(this.context.cwd, this.context.changelogDir, entry.filename);
297
- writes.push(rm(file, { force: true }));
298
- }
299
- 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);
300
313
  }
301
- assertEditable() {
302
- if (this.#created) throw new Error("This draft has already created a publish plan.");
314
+ canApply() {
315
+ return !this.#applied;
303
316
  }
304
317
  async appendChangelog(pkg, plan) {
305
- if (plan.changelogIds.size === 0) return;
318
+ if (!plan.changelogs || plan.changelogs.length === 0) return;
306
319
  const { generator = simpleGenerator() } = this.context.options;
307
- const changelogs = [];
308
- for (const id of plan.changelogIds) {
309
- const entry = this.changelogs.get(id);
310
- if (entry) changelogs.push(entry);
311
- }
312
320
  const generated = await generator.generate.call(this.context, {
321
+ packageId: pkg.id,
313
322
  packageName: pkg.name,
314
323
  version: pkg.version,
315
- changelogs
324
+ npm: plan.npm,
325
+ plan,
326
+ changelogs: plan.changelogs,
327
+ _draft: this
316
328
  });
317
329
  const path = join(pkg.path, "CHANGELOG.md");
318
330
  const existing = await readFile(path, "utf8").catch(() => "");
319
331
  await writeFile(path, `${generated.trim()}\n\n${existing}`.trimEnd() + "\n");
320
332
  }
321
- /** {@link createPublishPlan} but for `await using` syntax */
333
+ /** {@link applyPlan} but for `await using` syntax */
322
334
  async [Symbol.asyncDispose]() {
323
- return this.createPublishPlan();
335
+ return this.applyPlan();
324
336
  }
325
337
  };
326
- async function publishPlanStatus(context, plan) {
327
- for (const [id, pkgPlan] of Object.entries(plan.packages)) {
328
- const pkg = context.graph.get(id);
329
- if (!pkg || !pkgPlan.publish) continue;
330
- if (!await context.getRegistryClient(pkg).packageVersionExists(pkg, pkg.version)) return { state: "pending" };
331
- }
332
- return { state: "success" };
333
- }
334
- function createDraftPlan(changelogs, context) {
335
- const changelogMap = /* @__PURE__ */ new Map();
336
- const byPackage = /* @__PURE__ */ new Map();
337
- for (const entry of changelogs) {
338
- changelogMap.set(entry.id, entry);
339
- for (const requestedPackage of entry.packages) for (const pkg of context.graph.getByName(requestedPackage)) {
340
- let entries = byPackage.get(pkg);
341
- if (!entries) {
342
- entries = [];
343
- byPackage.set(pkg, entries);
344
- }
345
- entries.push(entry);
346
- }
347
- }
348
- const packages = /* @__PURE__ */ new Map();
349
- for (const [pkg, entries] of byPackage.entries()) {
350
- const plan = createPackagePlan(pkg, entries, context);
351
- if (plan) packages.set(pkg.id, plan);
352
- }
353
- return new DraftPlan(changelogMap, packages, context);
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" };
354
350
  }
355
- function createPackagePlan(pkg, entries, context) {
356
- if (entries.length === 0) return null;
357
- const packageOptions = context.options.packages?.[pkg.id] ?? context.options.packages?.[pkg.name] ?? {};
358
- let type = entries[0].type;
359
- const changelogIds = /* @__PURE__ */ new Set();
360
- for (const entry of entries) {
361
- changelogIds.add(entry.id);
362
- type = maxBump(type, entry.type);
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;
363
356
  }
364
- return {
365
- type,
366
- changelogIds,
367
- distTag: packageOptions.distTag ?? pkg.distTag,
368
- publish: packageOptions.publish ?? pkg.publish
369
- };
357
+ for (const entry of changelogs) draft.addChangelog(entry);
358
+ return draft;
370
359
  }
371
360
  //#endregion
372
361
  //#region src/utils/frontmatter.ts
@@ -392,20 +381,16 @@ function frontmatter(input) {
392
381
  }
393
382
  //#endregion
394
383
  //#region src/changelog/parse.ts
395
- async function readChangelogEntries(cwd, changelogDir) {
396
- const directory = resolve(cwd, changelogDir);
397
- const files = await readdir(directory).catch((error) => {
398
- if (isNodeError(error) && error.code === "ENOENT") return [];
399
- throw error;
400
- });
401
- const entries = [];
402
- for (const file of files.sort()) {
403
- if (!file.endsWith(".md")) continue;
404
- const filePath = join(directory, file);
405
- const content = await readFile(filePath, "utf8");
406
- entries.push(...parseChangelogFile(filePath, content));
407
- }
408
- 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();
409
394
  }
410
395
  /** Parse one changelog markdown file into release entries. */
411
396
  function parseChangelogFile(file, content) {
@@ -470,23 +455,16 @@ function headingToBump(depth) {
470
455
  }
471
456
  //#endregion
472
457
  //#region src/publish.ts
473
- async function publishFromPlan(context, plan, options) {
474
- const packages = await publishStoredPlan(plan, context, options);
475
- return {
476
- planPath: context.planPath,
477
- state: packages.some((pkg) => pkg.state === "failed") ? "failed" : "success",
478
- packages,
479
- _rawPlan: plan
480
- };
481
- }
482
- async function publishStoredPlan(store, context, { dryRun = false }) {
483
- const results = [];
458
+ async function publishFromPlan(context, store, options) {
459
+ const { dryRun = false } = options;
460
+ const packages = [];
461
+ if ((await publishPlanStatus(store, context)).state !== "pending") return { state: "skipped" };
484
462
  for (const [id, plan] of Object.entries(store.packages)) {
485
463
  if (!plan.publish) continue;
486
464
  const pkg = context.graph.get(id);
487
465
  if (!pkg) continue;
488
466
  const changelogs = [];
489
- for (const id of plan.changelogIds) {
467
+ for (const id of plan.changelogIds ?? []) {
490
468
  const entry = store.changelogs[id];
491
469
  if (entry) changelogs.push({
492
470
  ...entry,
@@ -495,12 +473,12 @@ async function publishStoredPlan(store, context, { dryRun = false }) {
495
473
  });
496
474
  }
497
475
  if (!dryRun) {
498
- if (await context.getRegistryClient(pkg).packageVersionExists(pkg, pkg.version)) {
499
- results.push({
476
+ if (await context.getRegistryClient(pkg).isPackagePublished(pkg)) {
477
+ packages.push({
500
478
  id: pkg.id,
501
479
  name: pkg.name,
502
480
  version: pkg.version,
503
- distTag: plan.distTag,
481
+ npm: plan.npm,
504
482
  state: "success",
505
483
  changelogs
506
484
  });
@@ -508,60 +486,77 @@ async function publishStoredPlan(store, context, { dryRun = false }) {
508
486
  }
509
487
  }
510
488
  try {
511
- if (!dryRun) await context.getRegistryClient(pkg).publish(pkg, { distTag: plan.distTag });
512
- results.push({
489
+ if (!dryRun) await context.getRegistryClient(pkg).publish(pkg, {
490
+ packageStore: plan,
491
+ store
492
+ });
493
+ packages.push({
513
494
  id: pkg.id,
514
495
  name: pkg.name,
515
496
  version: pkg.version,
516
- distTag: plan.distTag,
497
+ npm: plan.npm,
517
498
  state: "success",
518
499
  changelogs
519
500
  });
520
501
  } catch (error) {
521
- results.push({
502
+ packages.push({
522
503
  id: pkg.id,
523
504
  name: pkg.name,
524
505
  version: pkg.version,
525
- distTag: plan.distTag,
506
+ npm: plan.npm,
526
507
  changelogs,
527
508
  state: "failed",
528
509
  error: error instanceof Error ? error.message : String(error)
529
510
  });
530
511
  }
531
512
  }
532
- return results;
513
+ if (packages.length === 0) return { state: "skipped" };
514
+ return {
515
+ state: packages.some((pkg) => pkg.state === "failed") ? "failed" : "created",
516
+ packages,
517
+ _rawPlan: store
518
+ };
533
519
  }
534
520
  //#endregion
535
521
  //#region src/index.ts
536
522
  /** Create a Tegami project handle. */
537
523
  function tegami(options = {}) {
524
+ const $context = init();
525
+ async function init() {
526
+ return createTegamiContext(options);
527
+ }
538
528
  return {
539
- async createChangelog(createOptions = {}) {
540
- return createChangelog(await createTegamiContext(options), createOptions);
529
+ async generateChangelog(createOptions = {}) {
530
+ return createChangelog(await $context, createOptions);
541
531
  },
542
- async draft() {
543
- const context = await createTegamiContext(options);
544
- let plan = createDraftPlan(await readChangelogEntries(context.cwd, context.changelogDir), context);
545
- for (const plugin of context.plugins) plan = await plugin.initPlan?.call(context, plan) ?? plan;
546
- return plan;
532
+ _internal: {
533
+ options,
534
+ context() {
535
+ return $context;
536
+ },
537
+ async graph() {
538
+ return (await $context).graph;
539
+ }
547
540
  },
548
- async graph() {
549
- return (await createTegamiContext(options)).graph;
541
+ async draft() {
542
+ const context = await $context;
543
+ return createDraftPlan(await readChangelogEntries(context), context);
550
544
  },
551
545
  async publish(publishOptions = {}) {
552
- const context = await createTegamiContext(options);
553
- const parsed = await readFile(context.planPath, "utf8").then((content) => planStoreSchema.decode(content)).catch((error) => {
554
- if (isNodeError(error) && error.code === "ENOENT") return void 0;
555
- throw error;
556
- });
557
- if (parsed === void 0) throw new Error(`No publish plan found at ${context.planPath}.`);
546
+ const context = await $context;
547
+ if ((await getChangelogFiles(context)).length > 0) return { state: "skipped" };
548
+ const parsed = await readPlanStore(context);
549
+ if (!parsed) return { state: "skipped" };
558
550
  let result = await publishFromPlan(context, parsed, publishOptions);
559
551
  const publishCtx = {
560
552
  ...context,
561
553
  publishOptions
562
554
  };
563
- 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;
564
556
  return result;
557
+ },
558
+ async cleanup() {
559
+ return cleanupPublishPlan(await $context);
565
560
  }
566
561
  };
567
562
  }
@@ -1,9 +1,11 @@
1
- import { o as TegamiPlugin } from "../types-DdIMewK9.mjs";
1
+ import { d as TegamiPlugin } from "../index-Bhh2dJZp.js";
2
2
 
3
3
  //#region src/plugins/git.d.ts
4
4
  interface GitPluginOptions {
5
5
  /** Set to false to skip creating git tags after all packages publish successfully. */
6
6
  createTags?: boolean;
7
+ /** Push created tags to origin. Defaults to true in CI. */
8
+ pushTags?: boolean;
7
9
  }
8
10
  /**
9
11
  * Basic Git integrations:
@@ -12,8 +14,5 @@ interface GitPluginOptions {
12
14
  * Note: you do not need this with `github` plugin enabled.
13
15
  */
14
16
  declare function git(options?: GitPluginOptions): TegamiPlugin;
15
- /** create a Git tag, ignored if already exists */
16
- declare function createGitTag(cwd: string, tag: string): Promise<void>;
17
- declare function gitTagExists(cwd: string, tag: string): Promise<boolean>;
18
17
  //#endregion
19
- export { GitPluginOptions, createGitTag, git, gitTagExists };
18
+ export { GitPluginOptions, git };
@@ -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 };