@releasekit/release 0.7.48 → 0.8.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 CHANGED
@@ -223,6 +223,13 @@ The `ci` section controls automation behavior:
223
223
  "major": "release:major",
224
224
  "minor": "release:minor",
225
225
  "patch": "release:patch"
226
+ },
227
+
228
+ // Map PR labels to package filters for scoped releases
229
+ // Example: a PR with "scope:shared" label only releases matching packages
230
+ "scopeLabels": {
231
+ "scope:shared": "@myorg/shared-*",
232
+ "scope:frontend": "@myorg/web-*"
226
233
  }
227
234
  }
228
235
  }
@@ -245,6 +252,67 @@ Both modes support `release:stable` and `release:prerelease` as modifiers.
245
252
  | `standing-pr` | Changes accumulate in a standing release PR *(planned)* |
246
253
  | `scheduled` | Releases are triggered on a schedule *(planned)* |
247
254
 
255
+ #### Scope-Based Release
256
+
257
+ Use `scopeLabels` to filter which packages are released based on PR labels. This is useful for monorepos with distinct package groups.
258
+
259
+ When a PR has a matching scope label, only packages matching the pattern are included in the release:
260
+
261
+ ```json
262
+ {
263
+ "ci": {
264
+ "scopeLabels": {
265
+ "scope:shared": "@myorg/shared-*",
266
+ "scope:ui": "@myorg/ui-*"
267
+ },
268
+ "defaultScope": "scope:shared"
269
+ }
270
+ }
271
+ ```
272
+
273
+ **Options:**
274
+
275
+ | Option | Description |
276
+ |--------|-------------|
277
+ | `scopeLabels` | Map of PR label names to package patterns |
278
+ | `defaultScope` | Fallback scope to use when no scope label is found (must reference a key in `scopeLabels`) |
279
+
280
+ **Usage:**
281
+ - `scope:shared` + `release:minor` → Release only `@myorg/shared-*` packages with minor bump
282
+ - `scope:shared` + `scope:ui` → Release both matching scope groups
283
+ - `scope:shared` (no release label) → Release only shared packages, bump determined by conventional commits
284
+ - No scope label but `defaultScope` configured → Use default scope pattern
285
+
286
+ Multiple scope labels are combined with OR logic. Without a `release:*` label, conventional commits determine the version bump.
287
+
288
+ **Label conflicts:**
289
+
290
+ In label trigger mode, conflicting labels will block the release and post a comment explaining the issue:
291
+ - Multiple bump labels (`release:major` + `release:minor` + `release:patch`) → blocked
292
+ - Conflicting release type (`release:stable` + `release:prerelease`) → blocked (both modes)
293
+
294
+ **How it works:**
295
+
296
+ - **Preview mode**: Reads labels directly from the PR
297
+ - **Release mode**: When triggered via `workflow_run` after a PR merge, releasekit finds the merged PR(s) for the HEAD commit and reads their labels automatically
298
+
299
+ **Example workflow configuration:**
300
+
301
+ ```yaml
302
+ # Specify a fallback target - scope labels will filter to specific packages when found
303
+ jobs:
304
+ release:
305
+ runs-on: ubuntu-latest
306
+ steps:
307
+ - uses: goosewobbler/releasekit@v1
308
+ with:
309
+ mode: release
310
+ # Fallback target - used when no scope label is found
311
+ target: "@myorg/shared-*,@myorg/ui-*,@myorg/api-*"
312
+ ```
313
+
314
+ With this setup, releasekit will use the specified packages as fallback. When a PR has a scope label, it will be used instead to filter to just those packages.
315
+
248
316
  ### PR Preview
249
317
 
250
318
  The `releasekit preview` command posts a comment on pull requests showing what would be released. It reads PR labels from GitHub and adapts its messaging based on `releaseStrategy` and `releaseTrigger`.
package/dist/cli.js CHANGED
@@ -14508,7 +14508,7 @@ var init_baseError_DQHIJACF = __esm({
14508
14508
  }
14509
14509
  });
14510
14510
 
14511
- // ../version/dist/chunk-UBCKZYTO.js
14511
+ // ../version/dist/chunk-GHCENMVA.js
14512
14512
  import * as fs9 from "fs";
14513
14513
  import * as path12 from "path";
14514
14514
  import * as TOML2 from "smol-toml";
@@ -16297,8 +16297,8 @@ function createVersionCommand() {
16297
16297
  });
16298
16298
  }
16299
16299
  var import_semver3, import_semver4, import_semver5, ConfigError2, MAX_JSONC_LENGTH2, GitConfigSchema2, MonorepoConfigSchema2, BranchPatternSchema2, VersionCargoConfigSchema2, VersionConfigSchema2, NpmConfigSchema2, CargoPublishConfigSchema2, PublishGitConfigSchema2, GitHubReleaseConfigSchema2, VerifyRegistryConfigSchema2, VerifyConfigSchema2, PublishConfigSchema2, TemplateConfigSchema2, LocationModeSchema2, ChangelogConfigSchema2, LLMOptionsSchema2, LLMRetryConfigSchema2, LLMTasksConfigSchema2, LLMCategorySchema2, ScopeRulesSchema2, ScopeConfigSchema2, LLMPromptOverridesSchema2, LLMPromptsConfigSchema2, LLMConfigSchema2, ReleaseNotesConfigSchema2, NotesInputConfigSchema2, NotesConfigSchema2, CILabelsConfigSchema2, CIConfigSchema2, ReleaseCIConfigSchema2, ReleaseConfigSchema2, ReleaseKitConfigSchema2, MAX_INPUT_LENGTH2, SOLE_REFERENCE_PATTERN2, AUTH_DIR2, AUTH_FILE2, CONFIG_FILE2, VersionError, VersionErrorCode, _jsonOutputMode, _pendingWrites, _jsonData, STANDARD_BUMP_TYPES, VersionMismatchError, CONVENTIONAL_COMMIT_REGEX, BREAKING_CHANGE_REGEX, PackageProcessor, GitError, VersionEngine;
16300
- var init_chunk_UBCKZYTO = __esm({
16301
- "../version/dist/chunk-UBCKZYTO.js"() {
16300
+ var init_chunk_GHCENMVA = __esm({
16301
+ "../version/dist/chunk-GHCENMVA.js"() {
16302
16302
  "use strict";
16303
16303
  init_chunk_Q3FHZORY();
16304
16304
  init_chunk_LMPZV35Z();
@@ -16584,7 +16584,18 @@ var init_chunk_UBCKZYTO = __esm({
16584
16584
  major: "release:major",
16585
16585
  minor: "release:minor",
16586
16586
  patch: "release:patch"
16587
- })
16587
+ }),
16588
+ /**
16589
+ * Map of scope labels to package patterns.
16590
+ * When a PR has a label matching a key, only packages matching the corresponding pattern will be released.
16591
+ */
16592
+ scopeLabels: z3.record(z3.string(), z3.string()).optional(),
16593
+ /**
16594
+ * Default scope to use when no scope label is found on the merged PR.
16595
+ * Must reference a key from scopeLabels (e.g., "scope:shared").
16596
+ * Only applies when scopeLabels is configured.
16597
+ */
16598
+ defaultScope: z3.string().optional()
16588
16599
  });
16589
16600
  ReleaseCIConfigSchema2 = z3.object({
16590
16601
  skipPatterns: z3.array(z3.string().min(1)).optional(),
@@ -17039,7 +17050,7 @@ __export(dist_exports3, {
17039
17050
  var init_dist13 = __esm({
17040
17051
  "../version/dist/index.js"() {
17041
17052
  "use strict";
17042
- init_chunk_UBCKZYTO();
17053
+ init_chunk_GHCENMVA();
17043
17054
  init_chunk_Q3FHZORY();
17044
17055
  init_chunk_LMPZV35Z();
17045
17056
  }
@@ -41918,7 +41929,7 @@ var init_aggregator_IUQUAVJC = __esm({
41918
41929
  }
41919
41930
  });
41920
41931
 
41921
- // ../notes/dist/chunk-Y4S5UWCL.js
41932
+ // ../notes/dist/chunk-5B2RYLOK.js
41922
41933
  import * as TOML3 from "smol-toml";
41923
41934
  import * as fs33 from "fs";
41924
41935
  import * as path33 from "path";
@@ -43250,8 +43261,8 @@ function handleError(err) {
43250
43261
  process.exit(EXIT_CODES2.GENERAL_ERROR);
43251
43262
  }
43252
43263
  var import_handlebars, ConfigError3, MAX_JSONC_LENGTH3, GitConfigSchema3, MonorepoConfigSchema3, BranchPatternSchema3, VersionCargoConfigSchema3, VersionConfigSchema3, NpmConfigSchema3, CargoPublishConfigSchema3, PublishGitConfigSchema3, GitHubReleaseConfigSchema3, VerifyRegistryConfigSchema3, VerifyConfigSchema3, PublishConfigSchema3, TemplateConfigSchema3, LocationModeSchema3, ChangelogConfigSchema3, LLMOptionsSchema3, LLMRetryConfigSchema3, LLMTasksConfigSchema3, LLMCategorySchema3, ScopeRulesSchema3, ScopeConfigSchema3, LLMPromptOverridesSchema3, LLMPromptsConfigSchema3, LLMConfigSchema3, ReleaseNotesConfigSchema3, NotesInputConfigSchema3, NotesConfigSchema3, CILabelsConfigSchema3, CIConfigSchema3, ReleaseCIConfigSchema3, ReleaseConfigSchema3, ReleaseKitConfigSchema3, MAX_INPUT_LENGTH3, SOLE_REFERENCE_PATTERN3, AUTH_DIR3, AUTH_FILE3, CONFIG_FILE3, NotesError, InputParseError, TemplateError, LLMError, GitHubError, ConfigError22, LLM_DEFAULTS, BaseLLMProvider, AnthropicProvider, OllamaProvider, OpenAIProvider, OpenAICompatibleProvider, DEFAULT_CATEGORIZE_PROMPT, DEFAULT_ENHANCE_PROMPT, DEFAULT_RELEASE_NOTES_PROMPT, DEFAULT_SUMMARIZE_PROMPT;
43253
- var init_chunk_Y4S5UWCL = __esm({
43254
- "../notes/dist/chunk-Y4S5UWCL.js"() {
43264
+ var init_chunk_5B2RYLOK = __esm({
43265
+ "../notes/dist/chunk-5B2RYLOK.js"() {
43255
43266
  "use strict";
43256
43267
  init_chunk_7TJSPQPW();
43257
43268
  init_sdk();
@@ -43533,7 +43544,18 @@ var init_chunk_Y4S5UWCL = __esm({
43533
43544
  major: "release:major",
43534
43545
  minor: "release:minor",
43535
43546
  patch: "release:patch"
43536
- })
43547
+ }),
43548
+ /**
43549
+ * Map of scope labels to package patterns.
43550
+ * When a PR has a label matching a key, only packages matching the corresponding pattern will be released.
43551
+ */
43552
+ scopeLabels: z4.record(z4.string(), z4.string()).optional(),
43553
+ /**
43554
+ * Default scope to use when no scope label is found on the merged PR.
43555
+ * Must reference a key from scopeLabels (e.g., "scope:shared").
43556
+ * Only applies when scopeLabels is configured.
43557
+ */
43558
+ defaultScope: z4.string().optional()
43537
43559
  });
43538
43560
  ReleaseCIConfigSchema3 = z4.object({
43539
43561
  skipPatterns: z4.array(z4.string().min(1)).optional(),
@@ -43915,13 +43937,13 @@ function writeJson(outputPath, contexts, dryRun) {
43915
43937
  var init_dist14 = __esm({
43916
43938
  "../notes/dist/index.js"() {
43917
43939
  "use strict";
43918
- init_chunk_Y4S5UWCL();
43940
+ init_chunk_5B2RYLOK();
43919
43941
  init_chunk_F7MUVHZ2();
43920
43942
  init_chunk_7TJSPQPW();
43921
43943
  }
43922
43944
  });
43923
43945
 
43924
- // ../publish/dist/chunk-OZHNJUFW.js
43946
+ // ../publish/dist/chunk-UARK5H2F.js
43925
43947
  import chalk5 from "chalk";
43926
43948
  import * as fs25 from "fs";
43927
43949
  import * as TOML4 from "smol-toml";
@@ -45458,8 +45480,8 @@ function createPublishCommand() {
45458
45480
  });
45459
45481
  }
45460
45482
  var import_semver6, LOG_LEVELS4, PREFIXES4, COLORS4, currentLevel4, quietMode4, ReleaseKitError4, EXIT_CODES3, ConfigError4, MAX_JSONC_LENGTH4, GitConfigSchema4, MonorepoConfigSchema4, BranchPatternSchema4, VersionCargoConfigSchema4, VersionConfigSchema4, NpmConfigSchema4, CargoPublishConfigSchema4, PublishGitConfigSchema4, GitHubReleaseConfigSchema4, VerifyRegistryConfigSchema4, VerifyConfigSchema4, PublishConfigSchema4, TemplateConfigSchema4, LocationModeSchema4, ChangelogConfigSchema4, LLMOptionsSchema4, LLMRetryConfigSchema4, LLMTasksConfigSchema4, LLMCategorySchema4, ScopeRulesSchema4, ScopeConfigSchema4, LLMPromptOverridesSchema4, LLMPromptsConfigSchema4, LLMConfigSchema4, ReleaseNotesConfigSchema4, NotesInputConfigSchema4, NotesConfigSchema4, CILabelsConfigSchema4, CIConfigSchema4, ReleaseCIConfigSchema4, ReleaseConfigSchema4, ReleaseKitConfigSchema4, MAX_INPUT_LENGTH4, SOLE_REFERENCE_PATTERN4, AUTH_DIR4, AUTH_FILE4, CONFIG_FILE4, BasePublishError, PublishError, PipelineError, PublishErrorCode, VersionChangelogEntrySchema, VersionPackageChangelogSchema, VersionPackageUpdateSchema, VersionOutputSchema;
45461
- var init_chunk_OZHNJUFW = __esm({
45462
- "../publish/dist/chunk-OZHNJUFW.js"() {
45483
+ var init_chunk_UARK5H2F = __esm({
45484
+ "../publish/dist/chunk-UARK5H2F.js"() {
45463
45485
  "use strict";
45464
45486
  import_semver6 = __toESM(require_semver2(), 1);
45465
45487
  LOG_LEVELS4 = {
@@ -45788,7 +45810,18 @@ var init_chunk_OZHNJUFW = __esm({
45788
45810
  major: "release:major",
45789
45811
  minor: "release:minor",
45790
45812
  patch: "release:patch"
45791
- })
45813
+ }),
45814
+ /**
45815
+ * Map of scope labels to package patterns.
45816
+ * When a PR has a label matching a key, only packages matching the corresponding pattern will be released.
45817
+ */
45818
+ scopeLabels: z5.record(z5.string(), z5.string()).optional(),
45819
+ /**
45820
+ * Default scope to use when no scope label is found on the merged PR.
45821
+ * Must reference a key from scopeLabels (e.g., "scope:shared").
45822
+ * Only applies when scopeLabels is configured.
45823
+ */
45824
+ defaultScope: z5.string().optional()
45792
45825
  });
45793
45826
  ReleaseCIConfigSchema4 = z5.object({
45794
45827
  skipPatterns: z5.array(z5.string().min(1)).optional(),
@@ -45921,7 +45954,7 @@ __export(dist_exports5, {
45921
45954
  var init_dist15 = __esm({
45922
45955
  "../publish/dist/index.js"() {
45923
45956
  "use strict";
45924
- init_chunk_OZHNJUFW();
45957
+ init_chunk_UARK5H2F();
45925
45958
  }
45926
45959
  });
45927
45960
 
@@ -46327,7 +46360,18 @@ var CIConfigSchema = z.object({
46327
46360
  major: "release:major",
46328
46361
  minor: "release:minor",
46329
46362
  patch: "release:patch"
46330
- })
46363
+ }),
46364
+ /**
46365
+ * Map of scope labels to package patterns.
46366
+ * When a PR has a label matching a key, only packages matching the corresponding pattern will be released.
46367
+ */
46368
+ scopeLabels: z.record(z.string(), z.string()).optional(),
46369
+ /**
46370
+ * Default scope to use when no scope label is found on the merged PR.
46371
+ * Must reference a key from scopeLabels (e.g., "scope:shared").
46372
+ * Only applies when scopeLabels is configured.
46373
+ */
46374
+ defaultScope: z.string().optional()
46331
46375
  });
46332
46376
  var ReleaseCIConfigSchema = z.object({
46333
46377
  skipPatterns: z.array(z.string().min(1)).optional(),
@@ -46430,6 +46474,34 @@ function loadCIConfig(options) {
46430
46474
  return config.ci;
46431
46475
  }
46432
46476
 
46477
+ // src/label-utils.ts
46478
+ var DEFAULT_LABELS = {
46479
+ stable: "release:stable",
46480
+ prerelease: "release:prerelease",
46481
+ skip: "release:skip",
46482
+ major: "release:major",
46483
+ minor: "release:minor",
46484
+ patch: "release:patch"
46485
+ };
46486
+ function detectLabelConflicts(prLabels, labels = DEFAULT_LABELS) {
46487
+ const bumpLabelsPresent = [
46488
+ prLabels.includes(labels.major) && "major",
46489
+ prLabels.includes(labels.minor) && "minor",
46490
+ prLabels.includes(labels.patch) && "patch"
46491
+ ].filter(Boolean);
46492
+ const bumpConflict = bumpLabelsPresent.length > 1;
46493
+ const hasStable = prLabels.includes(labels.stable);
46494
+ const hasPrerelease = prLabels.includes(labels.prerelease);
46495
+ const prereleaseConflict = hasStable && hasPrerelease;
46496
+ return {
46497
+ bumpConflict,
46498
+ bumpLabelsPresent,
46499
+ prereleaseConflict,
46500
+ hasStable,
46501
+ hasPrerelease
46502
+ };
46503
+ }
46504
+
46433
46505
  // src/preview-context.ts
46434
46506
  import * as fs4 from "fs";
46435
46507
  function resolvePreviewContext(opts) {
@@ -46546,25 +46618,54 @@ function getIntroMessage(strategy, standingPrNumber) {
46546
46618
  }
46547
46619
  function getLabelBanner(labelContext) {
46548
46620
  if (!labelContext) return [];
46621
+ const lines = [];
46622
+ if (labelContext.scopeLabels && labelContext.scopeLabels.length > 0) {
46623
+ lines.push(`> **Scope:** ${labelContext.scopeLabels.join(", ")}`, "");
46624
+ }
46549
46625
  if (labelContext.trigger === "commit") {
46550
46626
  if (labelContext.skip) {
46551
- return ["> **Warning:** This PR is marked to skip release.", ""];
46627
+ lines.push("> **Warning:** This PR is marked to skip release.", "");
46628
+ return lines;
46552
46629
  }
46553
46630
  if (labelContext.bumpLabel === "major") {
46554
- return ["> **Important:** This PR is labeled for a **major** release.", ""];
46555
- }
46631
+ lines.push("> **Important:** This PR is labeled for a **major** release.", "");
46632
+ return lines;
46633
+ }
46634
+ }
46635
+ if (labelContext.prereleaseConflict) {
46636
+ const labels = labelContext.labels;
46637
+ const stableLabel = labels?.stable ?? "release:stable";
46638
+ const prereleaseLabel = labels?.prerelease ?? "release:prerelease";
46639
+ lines.push(
46640
+ "> **Error:** Conflicting release type labels detected.",
46641
+ `> **Note:** Please use only one of \`${stableLabel}\` or \`${prereleaseLabel}\` at a time.`,
46642
+ ""
46643
+ );
46644
+ return lines;
46556
46645
  }
46557
46646
  if (labelContext.trigger === "label") {
46647
+ if (labelContext.bumpConflict) {
46648
+ const labels = labelContext.labels;
46649
+ const labelExamples = labels ? `\`${labels.patch}\`, \`${labels.minor}\`, or \`${labels.major}\`` : "a release label (e.g., `release:patch`, `release:minor`, `release:major`)";
46650
+ lines.push(
46651
+ "> **Error:** Conflicting bump labels detected.",
46652
+ `> **Note:** Please use only one release label at a time. Use ${labelExamples}.`,
46653
+ ""
46654
+ );
46655
+ return lines;
46656
+ }
46558
46657
  if (labelContext.noBumpLabel) {
46559
46658
  const labels = labelContext.labels;
46560
46659
  const labelExamples = labels ? `\`${labels.patch}\`, \`${labels.minor}\`, or \`${labels.major}\`` : "a release label (e.g., `release:patch`, `release:minor`, `release:major`)";
46561
- return ["> No release label detected.", `> **Note:** Add ${labelExamples} to trigger a release.`, ""];
46660
+ lines.push("> No release label detected.", `> **Note:** Add ${labelExamples} to trigger a release.`, "");
46661
+ return lines;
46562
46662
  }
46563
46663
  if (labelContext.bumpLabel) {
46564
- return [`> This PR is labeled for a **${labelContext.bumpLabel}** release.`, ""];
46664
+ lines.push(`> This PR is labeled for a **${labelContext.bumpLabel}** release.`, "");
46665
+ return lines;
46565
46666
  }
46566
46667
  }
46567
- return [];
46668
+ return lines;
46568
46669
  }
46569
46670
  function formatPreviewComment(result, options) {
46570
46671
  const strategy = options?.strategy ?? "direct";
@@ -46673,6 +46774,18 @@ import { Octokit } from "@octokit/rest";
46673
46774
  function createOctokit(token) {
46674
46775
  return new Octokit({ auth: token });
46675
46776
  }
46777
+ async function findMergedPRsForCommit(octokit, owner, repo, commitSha) {
46778
+ try {
46779
+ const { data: prs } = await octokit.rest.repos.listPullRequestsAssociatedWithCommit({
46780
+ owner,
46781
+ repo,
46782
+ commit_sha: commitSha
46783
+ });
46784
+ return prs.filter((pr) => pr.merged_at !== null).map((pr) => pr.number);
46785
+ } catch {
46786
+ return [];
46787
+ }
46788
+ }
46676
46789
  async function findPreviewComment(octokit, owner, repo, prNumber) {
46677
46790
  const iterator = octokit.paginate.iterator(octokit.rest.issues.listComments, {
46678
46791
  owner,
@@ -46725,6 +46838,82 @@ function getHeadCommitMessage(cwd3) {
46725
46838
  return null;
46726
46839
  }
46727
46840
  }
46841
+ function getGitHubContext() {
46842
+ const repo = process.env.GITHUB_REPOSITORY;
46843
+ const sha = process.env.GITHUB_SHA;
46844
+ if (!repo || !sha) {
46845
+ return null;
46846
+ }
46847
+ const [owner, repoName] = repo.split("/");
46848
+ if (!owner || !repoName) {
46849
+ return null;
46850
+ }
46851
+ return { owner, repo: repoName, sha };
46852
+ }
46853
+ async function applyScopeLabelsFromPR(ciConfig, options) {
46854
+ const scopeLabels = ciConfig?.scopeLabels ?? {};
46855
+ const defaultScope = ciConfig?.defaultScope;
46856
+ const githubContext = getGitHubContext();
46857
+ if (!githubContext) {
46858
+ return { target: options.target, scopeLabels: [], labels: [] };
46859
+ }
46860
+ const token = process.env.GITHUB_TOKEN;
46861
+ if (!token) {
46862
+ warn("No GITHUB_TOKEN available \u2014 skipping scope label detection");
46863
+ return { target: options.target, scopeLabels: [], labels: [] };
46864
+ }
46865
+ const octokit = createOctokit(token);
46866
+ const prNumbers = await findMergedPRsForCommit(octokit, githubContext.owner, githubContext.repo, githubContext.sha);
46867
+ const allLabels = [];
46868
+ const perPRLabels = /* @__PURE__ */ new Map();
46869
+ for (const prNumber of prNumbers) {
46870
+ const labels = await fetchPRLabels(octokit, githubContext.owner, githubContext.repo, prNumber);
46871
+ allLabels.push(...labels);
46872
+ perPRLabels.set(prNumber, labels);
46873
+ }
46874
+ for (const [prNumber, labels] of perPRLabels) {
46875
+ const conflict = detectLabelConflicts(labels, ciConfig?.labels ?? DEFAULT_LABELS);
46876
+ if (conflict.prereleaseConflict) {
46877
+ warn(
46878
+ `PR #${prNumber} has conflicting labels "${ciConfig?.labels?.stable ?? "release:stable"}" and "${ciConfig?.labels?.prerelease ?? "release:prerelease"}" \u2014 release blocked`
46879
+ );
46880
+ return { target: options.target, scopeLabels: [], labels: [], blocked: true };
46881
+ }
46882
+ if (conflict.bumpConflict && (ciConfig?.releaseTrigger ?? "label") === "label") {
46883
+ warn(`PR #${prNumber} has conflicting bump labels (${conflict.bumpLabelsPresent.join(", ")}) \u2014 release blocked`);
46884
+ return { target: options.target, scopeLabels: [], labels: [], blocked: true };
46885
+ }
46886
+ }
46887
+ if (prNumbers.length === 0) {
46888
+ if (defaultScope && Object.keys(scopeLabels).length > 0) {
46889
+ const defaultPattern = scopeLabels[defaultScope];
46890
+ if (defaultPattern) {
46891
+ info(`No merged PRs found \u2014 using default scope "${defaultScope}" (${defaultPattern})`);
46892
+ return { target: defaultPattern, scopeLabels: [], labels: allLabels };
46893
+ }
46894
+ }
46895
+ info("No merged PRs found for HEAD commit \u2014 releasing all packages");
46896
+ return { target: options.target, scopeLabels: [], labels: allLabels };
46897
+ }
46898
+ const matchedScopePatterns = [];
46899
+ for (const [labelName, packagePattern] of Object.entries(scopeLabels)) {
46900
+ if (allLabels.includes(labelName)) {
46901
+ info(`Scope label "${labelName}" detected \u2014 limiting release to packages matching "${packagePattern}"`);
46902
+ matchedScopePatterns.push(packagePattern);
46903
+ }
46904
+ }
46905
+ let finalTarget = options.target;
46906
+ if (matchedScopePatterns.length > 0) {
46907
+ finalTarget = matchedScopePatterns.join(", ");
46908
+ } else if (defaultScope && Object.keys(scopeLabels).length > 0) {
46909
+ const defaultPattern = scopeLabels[defaultScope];
46910
+ if (defaultPattern) {
46911
+ info(`No scope label found \u2014 using default scope "${defaultScope}" (${defaultPattern})`);
46912
+ finalTarget = defaultPattern;
46913
+ }
46914
+ }
46915
+ return { target: finalTarget, scopeLabels: matchedScopePatterns, labels: allLabels };
46916
+ }
46728
46917
  async function runRelease(inputOptions) {
46729
46918
  const options = { ...inputOptions };
46730
46919
  if (options.verbose) setLogLevel("debug");
@@ -46738,6 +46927,26 @@ async function runRelease(inputOptions) {
46738
46927
  throw err;
46739
46928
  }
46740
46929
  const releaseConfig = releaseKitConfig.release;
46930
+ const ciConfig = loadCIConfig({ cwd: options.projectDir, configPath: options.config });
46931
+ if (!options.dryRun) {
46932
+ const scopeResult = await applyScopeLabelsFromPR(ciConfig, options);
46933
+ if (scopeResult.blocked) {
46934
+ info("Release blocked due to conflicting PR labels");
46935
+ return null;
46936
+ }
46937
+ if (scopeResult.target !== options.target) {
46938
+ options.target = scopeResult.target;
46939
+ }
46940
+ } else if (ciConfig?.scopeLabels) {
46941
+ const scopeResult = await applyScopeLabelsFromPR(ciConfig, options);
46942
+ if (scopeResult.blocked) {
46943
+ info("Release blocked due to conflicting PR labels");
46944
+ return null;
46945
+ }
46946
+ if (scopeResult.target !== options.target) {
46947
+ options.target = scopeResult.target;
46948
+ }
46949
+ }
46741
46950
  if (releaseConfig?.ci?.skipPatterns?.length) {
46742
46951
  const headCommit = getHeadCommitMessage(options.projectDir);
46743
46952
  if (headCommit) {
@@ -46867,14 +47076,6 @@ async function runPublishStep(versionOutput, options, releaseNotes, additionalFi
46867
47076
  }
46868
47077
 
46869
47078
  // src/preview.ts
46870
- var DEFAULT_LABELS = {
46871
- stable: "release:stable",
46872
- prerelease: "release:prerelease",
46873
- skip: "release:skip",
46874
- major: "release:major",
46875
- minor: "release:minor",
46876
- patch: "release:patch"
46877
- };
46878
47079
  async function runPreview(options) {
46879
47080
  const ciConfig = loadCIConfig({ cwd: options.projectDir, configPath: options.config });
46880
47081
  if (ciConfig?.prPreview === false) {
@@ -46908,6 +47109,7 @@ async function runPreview(options) {
46908
47109
  sync: false,
46909
47110
  bump: effectiveOptions.bump,
46910
47111
  prerelease: prereleaseFlag,
47112
+ stable: effectiveOptions.stable,
46911
47113
  skipNotes: true,
46912
47114
  skipPublish: true,
46913
47115
  skipGit: true,
@@ -46916,7 +47118,8 @@ async function runPreview(options) {
46916
47118
  json: false,
46917
47119
  verbose: false,
46918
47120
  quiet: true,
46919
- projectDir: effectiveOptions.projectDir
47121
+ projectDir: effectiveOptions.projectDir,
47122
+ target: effectiveOptions.target
46920
47123
  });
46921
47124
  } else {
46922
47125
  info("No release label detected \u2014 skipping version analysis");
@@ -46947,7 +47150,14 @@ function resolvePrerelease(options, packagePaths, projectDir) {
46947
47150
  async function applyLabelOverrides(options, ciConfig, context, existingOctokit) {
46948
47151
  const trigger = ciConfig?.releaseTrigger ?? "label";
46949
47152
  const labels = ciConfig?.labels ?? DEFAULT_LABELS;
46950
- const defaultLabelContext = { trigger, skip: false, noBumpLabel: false };
47153
+ const scopeLabels = ciConfig?.scopeLabels ?? {};
47154
+ const defaultLabelContext = {
47155
+ trigger,
47156
+ skip: false,
47157
+ noBumpLabel: false,
47158
+ bumpConflict: false,
47159
+ prereleaseConflict: false
47160
+ };
46951
47161
  if (!context) {
46952
47162
  return {
46953
47163
  options,
@@ -46966,7 +47176,41 @@ async function applyLabelOverrides(options, ciConfig, context, existingOctokit)
46966
47176
  };
46967
47177
  }
46968
47178
  const result = { ...options };
46969
- const labelContext = { trigger, skip: false, noBumpLabel: false, labels };
47179
+ const labelContext = {
47180
+ trigger,
47181
+ skip: false,
47182
+ noBumpLabel: false,
47183
+ bumpConflict: false,
47184
+ prereleaseConflict: false,
47185
+ labels,
47186
+ scopeLabels: []
47187
+ };
47188
+ const matchedScopePatterns = [];
47189
+ for (const [labelName, packagePattern] of Object.entries(scopeLabels)) {
47190
+ if (prLabels.includes(labelName)) {
47191
+ info(`PR label "${labelName}" detected \u2014 limiting release to packages matching "${packagePattern}"`);
47192
+ matchedScopePatterns.push(packagePattern);
47193
+ }
47194
+ }
47195
+ labelContext.scopeLabels = matchedScopePatterns;
47196
+ if (matchedScopePatterns.length > 0) {
47197
+ result.target = matchedScopePatterns.join(", ");
47198
+ } else if (ciConfig?.defaultScope && scopeLabels[ciConfig.defaultScope]) {
47199
+ const defaultPattern = scopeLabels[ciConfig.defaultScope];
47200
+ info(`No scope label found \u2014 using default scope "${ciConfig.defaultScope}" (${defaultPattern})`);
47201
+ result.target = defaultPattern;
47202
+ }
47203
+ const conflict = detectLabelConflicts(prLabels, labels);
47204
+ if (trigger === "label" && conflict.bumpConflict) {
47205
+ warn(`Conflicting bump labels detected (${conflict.bumpLabelsPresent.join(", ")}) \u2014 release blocked`);
47206
+ labelContext.noBumpLabel = true;
47207
+ labelContext.bumpConflict = true;
47208
+ }
47209
+ if (conflict.prereleaseConflict) {
47210
+ warn(`Conflicting labels "${labels.stable}" and "${labels.prerelease}" detected \u2014 release blocked`);
47211
+ labelContext.noBumpLabel = true;
47212
+ labelContext.prereleaseConflict = true;
47213
+ }
46970
47214
  if (trigger === "commit") {
46971
47215
  if (prLabels.includes(labels.skip)) {
46972
47216
  info(`PR label "${labels.skip}" detected \u2014 release will be skipped`);
@@ -46978,7 +47222,8 @@ async function applyLabelOverrides(options, ciConfig, context, existingOctokit)
46978
47222
  result.bump = "major";
46979
47223
  }
46980
47224
  } else {
46981
- if (prLabels.includes(labels.major)) {
47225
+ if (conflict.bumpConflict || conflict.prereleaseConflict) {
47226
+ } else if (prLabels.includes(labels.major)) {
46982
47227
  info(`PR label "${labels.major}" detected \u2014 major release`);
46983
47228
  labelContext.bumpLabel = "major";
46984
47229
  result.bump = "major";
@@ -46990,17 +47235,26 @@ async function applyLabelOverrides(options, ciConfig, context, existingOctokit)
46990
47235
  info(`PR label "${labels.patch}" detected \u2014 patch release`);
46991
47236
  labelContext.bumpLabel = "patch";
46992
47237
  result.bump = "patch";
46993
- } else {
46994
- labelContext.noBumpLabel = true;
47238
+ } else if (matchedScopePatterns.length === 0) {
47239
+ const hasStableOrPrerelease = conflict.hasStable || conflict.hasPrerelease;
47240
+ if (!hasStableOrPrerelease) {
47241
+ labelContext.noBumpLabel = true;
47242
+ }
46995
47243
  }
46996
47244
  }
46997
47245
  if (!options.stable && options.prerelease === void 0) {
46998
- if (prLabels.includes(labels.stable)) {
47246
+ if (conflict.hasStable && conflict.hasPrerelease) {
47247
+ } else if (conflict.hasStable) {
46999
47248
  info(`PR label "${labels.stable}" detected \u2014 using stable release preview`);
47000
47249
  result.stable = true;
47001
- } else if (prLabels.includes(labels.prerelease)) {
47250
+ } else if (conflict.hasPrerelease) {
47002
47251
  info(`PR label "${labels.prerelease}" detected \u2014 using prerelease preview`);
47003
47252
  result.prerelease = true;
47253
+ if (trigger === "label" && !result.bump) {
47254
+ info("No bump label found \u2014 defaulting to patch bump for prerelease release");
47255
+ result.bump = "patch";
47256
+ labelContext.bumpLabel = "patch";
47257
+ }
47004
47258
  }
47005
47259
  }
47006
47260
  return { options: result, labelContext: { ...labelContext, labels } };
@@ -47008,7 +47262,7 @@ async function applyLabelOverrides(options, ciConfig, context, existingOctokit)
47008
47262
 
47009
47263
  // src/preview-command.ts
47010
47264
  function createPreviewCommand() {
47011
- return new Command4("preview").description("Post a release preview comment on the current pull request").option("-c, --config <path>", "Path to config file").option("--project-dir <path>", "Project directory", process.cwd()).option("--pr <number>", "PR number (auto-detected from GitHub Actions)").option("--repo <owner/repo>", "Repository (auto-detected from GITHUB_REPOSITORY)").option("-p, --prerelease [identifier]", "Force prerelease preview (auto-detected by default)").option("--stable", "Force stable release preview (graduation from prerelease)", false).option(
47265
+ return new Command4("preview").description("Post a release preview comment on the current pull request").option("-c, --config <path>", "Path to config file").option("--project-dir <path>", "Project directory", process.cwd()).option("--pr <number>", "PR number (auto-detected from GitHub Actions)").option("--repo <owner/repo>", "Repository (auto-detected from GITHUB_REPOSITORY)").option("-p, --prerelease [identifier]", "Force prerelease preview (auto-detected by default)").option("--stable", "Force stable release preview (graduation from prerelease)", false).option("-t, --target <packages>", "Target specific packages (comma-separated)").option(
47012
47266
  "-d, --dry-run",
47013
47267
  "Print the comment to stdout without posting (GitHub context not available in dry-run mode)",
47014
47268
  false
@@ -47021,7 +47275,8 @@ function createPreviewCommand() {
47021
47275
  repo: opts.repo,
47022
47276
  prerelease: opts.prerelease,
47023
47277
  stable: opts.stable,
47024
- dryRun: opts.dryRun
47278
+ dryRun: opts.dryRun,
47279
+ target: opts.target
47025
47280
  });
47026
47281
  } catch (error3) {
47027
47282
  console.error(error3 instanceof Error ? error3.message : String(error3));
@@ -47070,7 +47325,7 @@ function createReleaseCommand() {
47070
47325
 
47071
47326
  // src/cli.ts
47072
47327
  function createReleaseProgram() {
47073
- return new Command6().name("releasekit-release").description("Unified release pipeline: version, changelog, and publish").version(readPackageVersion(import.meta.url)).addCommand(createReleaseCommand(), { isDefault: true }).addCommand(createPreviewCommand());
47328
+ return new Command6().name("releasekit-release").description("Unified release pipeline: version, changelog, and publish").version(readPackageVersion(import.meta.url)).addCommand(createPreviewCommand(), { isDefault: true }).addCommand(createReleaseCommand());
47074
47329
  }
47075
47330
  var isMain = (() => {
47076
47331
  try {