@mannyc1/ts-release 0.0.2 → 0.0.5

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.
Files changed (181) hide show
  1. package/ARCHITECTURE.md +55 -15
  2. package/README.md +229 -75
  3. package/SPEC.md +53 -9
  4. package/dist/config/errors.d.ts +2 -1
  5. package/dist/config/errors.d.ts.map +1 -1
  6. package/dist/config/errors.js +4 -3
  7. package/dist/config/errors.js.map +1 -1
  8. package/dist/config/load.d.ts.map +1 -1
  9. package/dist/config/load.js +5 -3
  10. package/dist/config/load.js.map +1 -1
  11. package/dist/config/schema.d.ts +4 -0
  12. package/dist/config/schema.d.ts.map +1 -1
  13. package/dist/config/schema.js +13 -0
  14. package/dist/config/schema.js.map +1 -1
  15. package/dist/domain/artifact.d.ts +3 -3
  16. package/dist/domain/artifact.d.ts.map +1 -1
  17. package/dist/domain/artifact.js +1 -1
  18. package/dist/domain/artifact.js.map +1 -1
  19. package/dist/domain/evidence.d.ts +18 -14
  20. package/dist/domain/evidence.d.ts.map +1 -1
  21. package/dist/domain/evidence.js +8 -3
  22. package/dist/domain/evidence.js.map +1 -1
  23. package/dist/domain/operation.d.ts +15 -14
  24. package/dist/domain/operation.d.ts.map +1 -1
  25. package/dist/domain/operation.js +117 -1
  26. package/dist/domain/operation.js.map +1 -1
  27. package/dist/domain/release.d.ts +84 -9
  28. package/dist/domain/release.d.ts.map +1 -1
  29. package/dist/domain/release.js +76 -5
  30. package/dist/domain/release.js.map +1 -1
  31. package/dist/domain/remote-state.d.ts +21 -16
  32. package/dist/domain/remote-state.d.ts.map +1 -1
  33. package/dist/domain/remote-state.js +14 -8
  34. package/dist/domain/remote-state.js.map +1 -1
  35. package/dist/domain/status.d.ts +4 -4
  36. package/dist/domain/status.d.ts.map +1 -1
  37. package/dist/domain/status.js +3 -2
  38. package/dist/domain/status.js.map +1 -1
  39. package/dist/domain/target.d.ts +7 -7
  40. package/dist/domain/target.d.ts.map +1 -1
  41. package/dist/domain/target.js +1 -1
  42. package/dist/domain/target.js.map +1 -1
  43. package/dist/host/host.d.ts +1 -0
  44. package/dist/host/host.d.ts.map +1 -1
  45. package/dist/host/host.js +2 -1
  46. package/dist/host/host.js.map +1 -1
  47. package/dist/host/http-live.d.ts.map +1 -1
  48. package/dist/host/http-live.js +7 -3
  49. package/dist/host/http-live.js.map +1 -1
  50. package/dist/host/http.d.ts +4 -1
  51. package/dist/host/http.d.ts.map +1 -1
  52. package/dist/host/http.js +5 -2
  53. package/dist/host/http.js.map +1 -1
  54. package/dist/host/platform.d.ts.map +1 -1
  55. package/dist/host/platform.js +2 -2
  56. package/dist/host/platform.js.map +1 -1
  57. package/dist/host/test.d.ts +1 -0
  58. package/dist/host/test.d.ts.map +1 -1
  59. package/dist/host/test.js +67 -7
  60. package/dist/host/test.js.map +1 -1
  61. package/dist/internal/workspace-path.d.ts +16 -0
  62. package/dist/internal/workspace-path.d.ts.map +1 -0
  63. package/dist/internal/workspace-path.js +33 -0
  64. package/dist/internal/workspace-path.js.map +1 -0
  65. package/dist/planner/artifact-inventory.d.ts.map +1 -1
  66. package/dist/planner/artifact-inventory.js +28 -9
  67. package/dist/planner/artifact-inventory.js.map +1 -1
  68. package/dist/planner/create-release-plan.d.ts +1 -1
  69. package/dist/planner/errors.d.ts +12 -6
  70. package/dist/planner/errors.d.ts.map +1 -1
  71. package/dist/planner/errors.js +20 -12
  72. package/dist/planner/errors.js.map +1 -1
  73. package/dist/planner/evidence-recorder.d.ts.map +1 -1
  74. package/dist/planner/evidence-recorder.js +32 -10
  75. package/dist/planner/evidence-recorder.js.map +1 -1
  76. package/dist/planner/executor.d.ts +4 -4
  77. package/dist/planner/executor.d.ts.map +1 -1
  78. package/dist/planner/executor.js +36 -4
  79. package/dist/planner/executor.js.map +1 -1
  80. package/dist/planner/normalize-release.d.ts +11 -2
  81. package/dist/planner/normalize-release.d.ts.map +1 -1
  82. package/dist/planner/normalize-release.js +98 -19
  83. package/dist/planner/normalize-release.js.map +1 -1
  84. package/dist/planner/reconcile.d.ts +1 -1
  85. package/dist/planner/reconcile.d.ts.map +1 -1
  86. package/dist/planner/reconcile.js +75 -92
  87. package/dist/planner/reconcile.js.map +1 -1
  88. package/dist/planner/release-eligibility.d.ts +20 -14
  89. package/dist/planner/release-eligibility.d.ts.map +1 -1
  90. package/dist/planner/release-eligibility.js +431 -25
  91. package/dist/planner/release-eligibility.js.map +1 -1
  92. package/dist/planner/render-plan.d.ts +10 -0
  93. package/dist/planner/render-plan.d.ts.map +1 -1
  94. package/dist/planner/render-plan.js +159 -0
  95. package/dist/planner/render-plan.js.map +1 -1
  96. package/dist/planner/status.d.ts +1 -1
  97. package/dist/planner/status.d.ts.map +1 -1
  98. package/dist/planner/status.js +8 -2
  99. package/dist/planner/status.js.map +1 -1
  100. package/dist/targets/adapter-helpers.d.ts +20 -2
  101. package/dist/targets/adapter-helpers.d.ts.map +1 -1
  102. package/dist/targets/adapter-helpers.js +32 -1
  103. package/dist/targets/adapter-helpers.js.map +1 -1
  104. package/dist/targets/github-release.d.ts +25 -0
  105. package/dist/targets/github-release.d.ts.map +1 -0
  106. package/dist/targets/github-release.js +82 -0
  107. package/dist/targets/github-release.js.map +1 -0
  108. package/dist/targets/github.d.ts +2 -2
  109. package/dist/targets/github.d.ts.map +1 -1
  110. package/dist/targets/github.js +28 -66
  111. package/dist/targets/github.js.map +1 -1
  112. package/dist/targets/homebrew.d.ts +2 -2
  113. package/dist/targets/homebrew.d.ts.map +1 -1
  114. package/dist/targets/homebrew.js +15 -31
  115. package/dist/targets/homebrew.js.map +1 -1
  116. package/dist/targets/scoop.d.ts +2 -2
  117. package/dist/targets/scoop.d.ts.map +1 -1
  118. package/dist/targets/scoop.js +13 -28
  119. package/dist/targets/scoop.js.map +1 -1
  120. package/dist/workflows/config.d.ts +191 -18
  121. package/dist/workflows/config.d.ts.map +1 -1
  122. package/dist/workflows/config.js +339 -61
  123. package/dist/workflows/config.js.map +1 -1
  124. package/dist/workflows/diagnostics.d.ts +62 -0
  125. package/dist/workflows/diagnostics.d.ts.map +1 -0
  126. package/dist/workflows/diagnostics.js +536 -0
  127. package/dist/workflows/diagnostics.js.map +1 -0
  128. package/dist/workflows/evidence.d.ts +8 -3
  129. package/dist/workflows/evidence.d.ts.map +1 -1
  130. package/dist/workflows/evidence.js +9 -0
  131. package/dist/workflows/evidence.js.map +1 -1
  132. package/dist/workflows/index.d.ts +7 -0
  133. package/dist/workflows/index.d.ts.map +1 -0
  134. package/dist/workflows/index.js +6 -0
  135. package/dist/workflows/index.js.map +1 -0
  136. package/dist/workflows/init.d.ts +84 -0
  137. package/dist/workflows/init.d.ts.map +1 -0
  138. package/dist/workflows/init.js +451 -0
  139. package/dist/workflows/init.js.map +1 -0
  140. package/dist/workflows/live.d.ts +2 -0
  141. package/dist/workflows/live.d.ts.map +1 -1
  142. package/dist/workflows/live.js +2 -0
  143. package/dist/workflows/live.js.map +1 -1
  144. package/dist/workflows/options.d.ts +30 -0
  145. package/dist/workflows/options.d.ts.map +1 -0
  146. package/dist/workflows/options.js +16 -0
  147. package/dist/workflows/options.js.map +1 -0
  148. package/examples/README.md +16 -12
  149. package/examples/github-release/release.config.json +1 -0
  150. package/examples/homebrew-tap/release.config.json +1 -0
  151. package/examples/multi-target/release.config.json +5 -1
  152. package/examples/non-strict-skips/release.config.json +1 -0
  153. package/examples/npm-first-publish/release.config.json +1 -0
  154. package/examples/npm-only/release.config.json +5 -1
  155. package/examples/pypi-registry/release.config.json +1 -0
  156. package/examples/scoop-bucket/release.config.json +1 -0
  157. package/package.json +37 -26
  158. package/templates/README.md +59 -0
  159. package/templates/github-actions/plan-and-approved-execute.yml +58 -0
  160. package/templates/github-actions/plan-only.yml +33 -0
  161. package/templates/github-actions/trusted-publishing.yml +61 -0
  162. package/templates/multi-target-homebrew/release.config.json +70 -0
  163. package/templates/multi-target-scoop/release.config.json +72 -0
  164. package/templates/npm-github/release.config.json +50 -0
  165. package/templates/npm-only/release.config.json +39 -0
  166. package/dist/cli/command.d.ts +0 -4
  167. package/dist/cli/command.d.ts.map +0 -1
  168. package/dist/cli/command.js +0 -173
  169. package/dist/cli/command.js.map +0 -1
  170. package/dist/cli/main.d.ts +0 -3
  171. package/dist/cli/main.d.ts.map +0 -1
  172. package/dist/cli/main.js +0 -9
  173. package/dist/cli/main.js.map +0 -1
  174. package/dist/runtime/bun.d.ts +0 -9
  175. package/dist/runtime/bun.d.ts.map +0 -1
  176. package/dist/runtime/bun.js +0 -10
  177. package/dist/runtime/bun.js.map +0 -1
  178. package/dist/version.d.ts +0 -2
  179. package/dist/version.d.ts.map +0 -1
  180. package/dist/version.js +0 -2
  181. package/dist/version.js.map +0 -1
@@ -1,33 +1,39 @@
1
1
  import * as Effect from "effect/Effect";
2
+ import * as FileSystem from "effect/FileSystem";
3
+ import * as Path from "effect/Path";
2
4
  import * as Schema from "effect/Schema";
3
5
  import { ReleaseEligibilityDecision, ReleaseEligibilityInput } from "../domain/remote-state.js";
4
- import { ReleaseIntent } from "../domain/release.js";
6
+ import { ReleaseIdentity, ReleaseIntent, ReleasePackageManifest } from "../domain/release.js";
7
+ import { GitHubReleaseTarget, NpmRegistryTarget } from "../domain/target.js";
5
8
  import { ReleaseCommandRunner } from "../host/host.js";
6
9
  import { ReleaseEligibilityCheckError } from "./errors.js";
7
10
  export type * from "../types/effect-internal.js";
8
- declare const ReleasePackageManifest_base: Schema.Class<ReleasePackageManifest, Schema.Struct<{
9
- readonly name: Schema.String;
10
- readonly version: Schema.String;
11
- }>, {}>;
12
- export declare class ReleasePackageManifest extends ReleasePackageManifest_base {
13
- }
11
+ export { ReleasePackageManifest } from "../domain/release.js";
14
12
  declare const ReleaseEligibilityRemoteCheck_base: Schema.Class<ReleaseEligibilityRemoteCheck, Schema.Struct<{
15
- readonly packageName: Schema.String;
16
- readonly packageVersion: Schema.String;
17
- readonly npmTargetId: Schema.String;
13
+ readonly packageName: Schema.NonEmptyString;
14
+ readonly packageVersion: Schema.NonEmptyString;
15
+ readonly npmTargetId: Schema.NonEmptyString;
18
16
  readonly npmRegistry: Schema.String;
19
- readonly githubTargetId: Schema.String;
17
+ readonly githubTargetId: Schema.NonEmptyString;
20
18
  readonly githubRepository: Schema.String;
19
+ readonly githubTag: Schema.NonEmptyString;
21
20
  readonly githubTokenEnv: Schema.optionalKey<Schema.String>;
22
21
  readonly expectedGithubDraft: Schema.Boolean;
23
22
  }>, {}>;
24
23
  export declare class ReleaseEligibilityRemoteCheck extends ReleaseEligibilityRemoteCheck_base {
25
24
  }
25
+ interface ReleaseEligibilityMetadata {
26
+ readonly strategy?: string | undefined;
27
+ readonly source?: string | undefined;
28
+ }
26
29
  export declare const decideReleaseEligibility: (input: ReleaseEligibilityInput) => ReleaseEligibilityDecision;
30
+ export declare const releaseEligibilityRemoteCheckFromIdentity: (identity: ReleaseIdentity, targets: readonly (NpmRegistryTarget | GitHubReleaseTarget | import("../domain/target.js").HomebrewTapTarget | import("../domain/target.js").PyPiRegistryTarget | import("../domain/target.js").ScoopBucketTarget)[]) => Effect.Effect<ReleaseEligibilityRemoteCheck, ReleaseEligibilityCheckError, never>;
27
31
  export declare const releaseEligibilityRemoteCheckFromIntent: (manifest: ReleasePackageManifest, intent: ReleaseIntent) => Effect.Effect<ReleaseEligibilityRemoteCheck, ReleaseEligibilityCheckError, never>;
28
- export declare const classifyNpmRemoteState: (input: ReleaseEligibilityRemoteCheck) => Effect.Effect<"missing" | "published", ReleaseEligibilityCheckError | import("../host/host.js").CommandRunnerError, ReleaseCommandRunner>;
29
- export declare const classifyGitHubReleaseAvailability: (input: ReleaseEligibilityRemoteCheck) => Effect.Effect<"missing" | "published" | "draft", ReleaseEligibilityCheckError | import("../host/host.js").CommandRunnerError, ReleaseCommandRunner>;
30
- export declare const checkReleaseEligibility: (input: ReleaseEligibilityRemoteCheck) => Effect.Effect<ReleaseEligibilityDecision, ReleaseEligibilityCheckError | import("../host/host.js").CommandRunnerError, ReleaseCommandRunner>;
32
+ export declare const classifyNpmRemoteState: (input: ReleaseEligibilityRemoteCheck) => Effect.Effect<"missing" | "published", import("../host/host.js").CommandRunnerError | ReleaseEligibilityCheckError, ReleaseCommandRunner>;
33
+ export declare const classifyGitHubReleaseAvailability: (input: ReleaseEligibilityRemoteCheck) => Effect.Effect<"missing" | "published" | "draft", import("../host/host.js").CommandRunnerError | ReleaseEligibilityCheckError, ReleaseCommandRunner>;
34
+ export declare const checkReleaseEligibility: (input: ReleaseEligibilityRemoteCheck, metadata?: ReleaseEligibilityMetadata | undefined) => Effect.Effect<ReleaseEligibilityDecision, import("../host/host.js").CommandRunnerError | ReleaseEligibilityCheckError, ReleaseCommandRunner>;
35
+ export declare const checkReleaseDecision: (intent: ReleaseIntent, root?: string | undefined) => Effect.Effect<ReleaseEligibilityDecision, import("../host/host.js").CommandRunnerError | import("./errors.js").ReleaseNormalizationError | ReleaseEligibilityCheckError, FileSystem.FileSystem | Path.Path | ReleaseCommandRunner>;
36
+ export declare const checkReleaseIntentRequirement: (intent: ReleaseIntent, root?: string | undefined) => Effect.Effect<ReleaseEligibilityDecision, import("./errors.js").ReleaseNormalizationError | ReleaseEligibilityCheckError, FileSystem.FileSystem | Path.Path>;
31
37
  export declare const renderReleaseEligibilityText: (decision: ReleaseEligibilityDecision) => string;
32
38
  export declare const renderReleaseEligibilityJson: (decision: ReleaseEligibilityDecision) => string;
33
39
  //# sourceMappingURL=release-eligibility.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"release-eligibility.d.ts","sourceRoot":"","sources":["../../src/planner/release-eligibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,EAGL,0BAA0B,EAC1B,uBAAuB,EACxB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACtD,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAA;AAE1D,mBAAmB,6BAA6B,CAAA;;;;;AAEhD,qBAAa,sBAAuB,SAAQ,2BAG1C;CAAG;;;;;;;;;;;AAEL,qBAAa,6BAA8B,SAAQ,kCAWjD;CAAG;AAgBL,eAAO,MAAM,wBAAwB,GACnC,OAAO,uBAAuB,KAC7B,0BAwCF,CAAA;AAsBD,eAAO,MAAM,uCAAuC,gJA2BlD,CAAA;AAkEF,eAAO,MAAM,sBAAsB,qLAiBjC,CAAA;AAEF,eAAO,MAAM,iCAAiC,+LAiB5C,CAAA;AAEF,eAAO,MAAM,uBAAuB,wLAclC,CAAA;AAEF,eAAO,MAAM,4BAA4B,GAAI,UAAU,0BAA0B,KAAG,MACkD,CAAA;AAEtI,eAAO,MAAM,4BAA4B,GAAI,UAAU,0BAA0B,KAAG,MAC1C,CAAA"}
1
+ {"version":3,"file":"release-eligibility.d.ts","sourceRoot":"","sources":["../../src/planner/release-eligibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AACnC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,EAGL,0BAA0B,EAC1B,uBAAuB,EACxB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EASL,eAAe,EACf,aAAa,EAGb,sBAAsB,EAIvB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAA0B,MAAM,qBAAqB,CAAA;AACpG,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACtD,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAA;AAS1D,mBAAmB,6BAA6B,CAAA;AAEhD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;;;;;;;;;;;;AAE7D,qBAAa,6BAA8B,SAAQ,kCAYjD;CAAG;AAQL,UAAU,0BAA0B;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACrC;AAuED,eAAO,MAAM,wBAAwB,GACnC,OAAO,uBAAuB,KAC7B,0BAwCF,CAAA;AAgCD,eAAO,MAAM,yCAAyC,wUAqCpD,CAAA;AAEF,eAAO,MAAM,uCAAuC,gJAalD,CAAA;AAgGF,eAAO,MAAM,sBAAsB,qLAiBjC,CAAA;AAEF,eAAO,MAAM,iCAAiC,+LAiB5C,CAAA;AAEF,eAAO,MAAM,uBAAuB,2OAmBlC,CAAA;AAscF,eAAO,MAAM,oBAAoB,0RAW/B,CAAA;AAEF,eAAO,MAAM,6BAA6B,oNA4BxC,CAAA;AAEF,eAAO,MAAM,4BAA4B,GAAI,UAAU,0BAA0B,KAAG,MAQnF,CAAA;AAED,eAAO,MAAM,4BAA4B,GAAI,UAAU,0BAA0B,KAAG,MAC1C,CAAA"}
@@ -1,21 +1,24 @@
1
1
  import * as Effect from "effect/Effect";
2
+ import * as FileSystem from "effect/FileSystem";
3
+ import * as Path from "effect/Path";
2
4
  import * as Schema from "effect/Schema";
5
+ import * as Semver from "semver";
3
6
  import { CommandSpec } from "../domain/operation.js";
4
7
  import { ReleaseEligibilityDecision, ReleaseEligibilityInput } from "../domain/remote-state.js";
8
+ import { ConventionalCommitReleaseRule, GitTag, IntentFilesReleaseDecision, PackageManifestReleaseIdentitySource, ReleaseIdentity, ReleaseIntentFile, ReleaseName, ReleaseVersion, RemoteStateReleaseDecision, StaticReleaseIdentitySource } from "../domain/release.js";
9
+ import { TargetId } from "../domain/target.js";
5
10
  import { ReleaseCommandRunner } from "../host/host.js";
6
11
  import { ReleaseEligibilityCheckError } from "./errors.js";
7
- export class ReleasePackageManifest extends Schema.Class("ReleasePackageManifest")({
8
- name: Schema.String,
9
- version: Schema.String
10
- }) {
11
- }
12
+ import { readReleasePackageManifest, renderReleaseVersionTemplate, resolveIdentityCommit, resolveReleaseIdentitySource, validateNonEmptySafeRelativePath } from "./normalize-release.js";
13
+ export { ReleasePackageManifest } from "../domain/release.js";
12
14
  export class ReleaseEligibilityRemoteCheck extends Schema.Class("ReleaseEligibilityRemoteCheck")({
13
- packageName: Schema.String,
14
- packageVersion: Schema.String,
15
- npmTargetId: Schema.String,
15
+ packageName: ReleaseName,
16
+ packageVersion: ReleaseVersion,
17
+ npmTargetId: TargetId,
16
18
  npmRegistry: Schema.String,
17
- githubTargetId: Schema.String,
19
+ githubTargetId: TargetId,
18
20
  githubRepository: Schema.String,
21
+ githubTag: GitTag,
19
22
  githubTokenEnv: Schema.optionalKey(Schema.String),
20
23
  expectedGithubDraft: Schema.Boolean
21
24
  }) {
@@ -25,8 +28,35 @@ class GitHubCliReleaseViewResponse extends Schema.Class("GitHubCliReleaseViewRes
25
28
  }) {
26
29
  }
27
30
  const decodeGitHubCliReleaseViewResponse = Schema.decodeUnknownEffect(GitHubCliReleaseViewResponse);
31
+ const decodeReleaseIntentFile = Schema.decodeUnknownEffect(ReleaseIntentFile);
32
+ const defaultReleaseRules = [
33
+ ConventionalCommitReleaseRule.make({ breaking: true, release: "major" }),
34
+ ConventionalCommitReleaseRule.make({ type: "feat", release: "minor" }),
35
+ ConventionalCommitReleaseRule.make({ type: "fix", release: "patch" })
36
+ ];
28
37
  const releaseLabel = (input) => `${input.packageName}@${input.packageVersion}`;
29
38
  const expectedGithubState = (input) => input.expectedGithubDraft ? "draft" : "published";
39
+ const decisionStrategy = (metadata) => metadata?.strategy;
40
+ const enrichDecision = (decision, input, metadata) => {
41
+ const strategy = decisionStrategy(metadata);
42
+ return ReleaseEligibilityDecision.make({
43
+ shouldRelease: decision.shouldRelease,
44
+ status: decision.status,
45
+ reason: decision.reason,
46
+ ...(strategy === undefined ? {} : { strategy }),
47
+ packageName: input.packageName,
48
+ packageVersion: input.packageVersion,
49
+ githubTag: input.githubTag,
50
+ ...(metadata?.source === undefined ? {} : { source: metadata.source })
51
+ });
52
+ };
53
+ const skippedDecision = (reason, metadata) => ReleaseEligibilityDecision.make({
54
+ shouldRelease: false,
55
+ status: "skipped",
56
+ reason,
57
+ ...(metadata.strategy === undefined ? {} : { strategy: metadata.strategy }),
58
+ ...(metadata.source === undefined ? {} : { source: metadata.source })
59
+ });
30
60
  export const decideReleaseEligibility = (input) => {
31
61
  if (input.npm === "missing" && input.github === "missing") {
32
62
  return ReleaseEligibilityDecision.make({
@@ -63,32 +93,48 @@ export const decideReleaseEligibility = (input) => {
63
93
  reason: `${releaseLabel(input)} has partial remote state: npm=${input.npm}, github=${input.github}, expected-github=${expectedGithub}.`
64
94
  });
65
95
  };
66
- const eligibilityError = (reason, targetId) => ReleaseEligibilityCheckError.make({
96
+ const eligibilityError = (reason, targetId, cause) => ReleaseEligibilityCheckError.make({
67
97
  ...(targetId === undefined ? {} : { targetId }),
68
- reason
98
+ reason,
99
+ ...(cause === undefined ? {} : { cause })
69
100
  });
70
- const findNpmTarget = (intent, packageName) => intent.targets.find((target) => target._tag === "NpmRegistryTarget" && (target.packageName === packageName || target.id === "npm"));
71
- const findGitHubTarget = (intent) => intent.targets.find((target) => target._tag === "GitHubReleaseTarget");
72
- export const releaseEligibilityRemoteCheckFromIntent = Effect.fn("releaseEligibilityRemoteCheckFromIntent")(function* (manifest, intent) {
73
- const npmTarget = findNpmTarget(intent, manifest.name);
101
+ const findNpmTarget = (targets, packageName) => targets.find((target) => target._tag === "NpmRegistryTarget" && target.packageName === packageName);
102
+ const findNpmTargetById = (targets, targetId) => targets.find((target) => target._tag === "NpmRegistryTarget" && target.id === targetId);
103
+ const findGitHubTarget = (targets) => targets.find((target) => target._tag === "GitHubReleaseTarget");
104
+ export const releaseEligibilityRemoteCheckFromIdentity = Effect.fn("releaseEligibilityRemoteCheckFromIdentity")(function* (identity, targets) {
105
+ const npmTarget = findNpmTarget(targets, identity.name);
74
106
  if (npmTarget === undefined) {
75
- return yield* Effect.fail(eligibilityError(`release config must include an npm target for ${manifest.name}`));
107
+ const npmTargetById = findNpmTargetById(targets, "npm");
108
+ if (npmTargetById !== undefined) {
109
+ return yield* Effect.fail(eligibilityError(`npm target ${npmTargetById.id} packageName ${npmTargetById.packageName} does not match release identity ${identity.name}.`, npmTargetById.id));
110
+ }
111
+ return yield* Effect.fail(eligibilityError(`release config must include an npm target for ${identity.name}`));
76
112
  }
77
- const githubTarget = findGitHubTarget(intent);
113
+ const githubTarget = findGitHubTarget(targets);
78
114
  if (githubTarget === undefined) {
79
115
  return yield* Effect.fail(eligibilityError("release config must include a GitHub release target"));
80
116
  }
81
117
  return ReleaseEligibilityRemoteCheck.make({
82
- packageName: manifest.name,
83
- packageVersion: manifest.version,
118
+ packageName: identity.name,
119
+ packageVersion: identity.version,
84
120
  npmTargetId: npmTarget.id,
85
121
  npmRegistry: npmTarget.registry,
86
122
  githubTargetId: githubTarget.id,
87
123
  githubRepository: githubTarget.repository,
124
+ githubTag: identity.tag ?? identity.version,
88
125
  ...(githubTarget.tokenEnv === undefined ? {} : { githubTokenEnv: githubTarget.tokenEnv }),
89
126
  expectedGithubDraft: githubTarget.draft ?? false
90
127
  });
91
128
  });
129
+ export const releaseEligibilityRemoteCheckFromIntent = Effect.fn("releaseEligibilityRemoteCheckFromIntent")(function* (manifest, intent) {
130
+ const staticTag = intent.identity instanceof StaticReleaseIdentitySource ? intent.identity.tag : undefined;
131
+ return yield* releaseEligibilityRemoteCheckFromIdentity(ReleaseIdentity.make({
132
+ name: manifest.name,
133
+ version: manifest.version,
134
+ commit: "HEAD",
135
+ ...(staticTag === undefined ? {} : { tag: staticTag })
136
+ }), intent.targets);
137
+ });
92
138
  const looksMissing = (stdout, stderr) => {
93
139
  const text = `${stdout}\n${stderr}`.toLowerCase();
94
140
  return text.includes("not found") || text.includes("404") || text.includes("e404");
@@ -117,7 +163,7 @@ const githubReleaseViewCommand = (input) => CommandSpec.make({
117
163
  args: [
118
164
  "release",
119
165
  "view",
120
- `v${input.packageVersion}`,
166
+ input.githubTag,
121
167
  "--repo",
122
168
  input.githubRepository,
123
169
  "--json",
@@ -126,10 +172,33 @@ const githubReleaseViewCommand = (input) => CommandSpec.make({
126
172
  requiredEnv: input.githubTokenEnv === undefined ? [] : [input.githubTokenEnv],
127
173
  redactedEnv: input.githubTokenEnv === undefined ? [] : [input.githubTokenEnv]
128
174
  });
175
+ const gitTagsAtHeadCommand = (root) => CommandSpec.make({
176
+ executable: "git",
177
+ args: ["tag", "--points-at", "HEAD"],
178
+ cwd: root,
179
+ requiredEnv: [],
180
+ redactedEnv: []
181
+ });
182
+ const gitListTagsCommand = (root) => CommandSpec.make({
183
+ executable: "git",
184
+ args: ["tag", "--list", "--merged", "HEAD"],
185
+ cwd: root,
186
+ requiredEnv: [],
187
+ redactedEnv: []
188
+ });
189
+ const gitLogCommand = (root, sinceTag) => CommandSpec.make({
190
+ executable: "git",
191
+ args: sinceTag === undefined
192
+ ? ["log", "--format=%B%x1e"]
193
+ : ["log", `${sinceTag}..HEAD`, "--format=%B%x1e"],
194
+ cwd: root,
195
+ requiredEnv: [],
196
+ redactedEnv: []
197
+ });
129
198
  const parseGitHubCliReleaseView = Effect.fn("parseGitHubCliReleaseView")(function* (input, stdout) {
130
199
  const parsed = yield* Effect.try({
131
200
  try: () => JSON.parse(stdout),
132
- catch: (cause) => eligibilityError(cause instanceof Error ? cause.message : String(cause), input.githubTargetId)
201
+ catch: (cause) => eligibilityError("gh release view returned invalid JSON.", input.githubTargetId, cause)
133
202
  });
134
203
  const response = yield* decodeGitHubCliReleaseViewResponse(parsed).pipe(Effect.mapError((error) => eligibilityError(`gh release view returned malformed JSON: ${error.message}`, input.githubTargetId)));
135
204
  return response.isDraft ? "draft" : "published";
@@ -156,17 +225,354 @@ export const classifyGitHubReleaseAvailability = Effect.fn("classifyGitHubReleas
156
225
  }
157
226
  return yield* Effect.fail(eligibilityError(`gh release view failed while checking release state: ${firstDiagnosticLine(result)}`, input.githubTargetId));
158
227
  });
159
- export const checkReleaseEligibility = Effect.fn("checkReleaseEligibility")(function* (input) {
228
+ export const checkReleaseEligibility = Effect.fn("checkReleaseEligibility")(function* (input, metadata = undefined) {
160
229
  const npm = yield* classifyNpmRemoteState(input);
161
230
  const github = yield* classifyGitHubReleaseAvailability(input);
162
- return decideReleaseEligibility(ReleaseEligibilityInput.make({
231
+ return enrichDecision(decideReleaseEligibility(ReleaseEligibilityInput.make({
163
232
  packageName: input.packageName,
164
233
  packageVersion: input.packageVersion,
165
234
  expectedGithubDraft: input.expectedGithubDraft,
166
235
  npm,
167
236
  github
168
- }));
237
+ })), input, metadata);
169
238
  });
170
- export const renderReleaseEligibilityText = (decision) => `release eligibility status=${decision.status} should-release=${decision.shouldRelease} reason=${JSON.stringify(decision.reason)}\n`;
239
+ const strategyFromIntent = (intent) => intent.releaseDecision ?? RemoteStateReleaseDecision.make({});
240
+ const identitySourceName = (intent) => intent.identity instanceof PackageManifestReleaseIdentitySource
241
+ ? "PackageManifestReleaseIdentitySource"
242
+ : "StaticReleaseIdentitySource";
243
+ const checkRemoteStateForIdentity = Effect.fn("checkRemoteStateForIdentity")(function* (identity, targets, metadata) {
244
+ const remoteCheck = yield* releaseEligibilityRemoteCheckFromIdentity(identity, targets);
245
+ return yield* checkReleaseEligibility(remoteCheck, metadata);
246
+ });
247
+ const skippedReleaseResolution = (decision) => ({
248
+ _tag: "Skipped",
249
+ decision
250
+ });
251
+ const resolvedReleaseResolution = (identity, targets, metadata) => ({
252
+ _tag: "Resolved",
253
+ identity,
254
+ targets,
255
+ metadata
256
+ });
257
+ const resolveRemoteStateDecision = Effect.fn("resolveRemoteStateDecision")(function* (strategy, intent, root) {
258
+ const identity = yield* resolveReleaseIdentitySource(intent.identity, root);
259
+ return resolvedReleaseResolution(identity, intent.targets, {
260
+ strategy: strategy._tag,
261
+ source: identitySourceName(intent)
262
+ });
263
+ });
264
+ const templateParts = (template, field) => {
265
+ const placeholder = "{version}";
266
+ const firstIndex = template.indexOf(placeholder);
267
+ const lastIndex = template.lastIndexOf(placeholder);
268
+ if (firstIndex >= 0 && firstIndex === lastIndex) {
269
+ return Effect.succeed({
270
+ prefix: template.slice(0, firstIndex),
271
+ suffix: template.slice(firstIndex + placeholder.length)
272
+ });
273
+ }
274
+ return Effect.fail(eligibilityError(`${field} must contain exactly one {version} placeholder.`));
275
+ };
276
+ const versionFromTag = (tag, parts) => {
277
+ if (!tag.startsWith(parts.prefix) || !tag.endsWith(parts.suffix)) {
278
+ return undefined;
279
+ }
280
+ const suffixStart = tag.length - parts.suffix.length;
281
+ const version = tag.slice(parts.prefix.length, suffixStart);
282
+ return version.length === 0 ? undefined : version;
283
+ };
284
+ const stdoutLines = (stdout) => stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
285
+ const readMatchingCurrentTags = Effect.fn("readMatchingCurrentTags")(function* (root, template, providedTag, requireCurrentRef) {
286
+ const parts = yield* templateParts(template, "releaseDecision.tagTemplate");
287
+ const tags = providedTag !== undefined && !requireCurrentRef
288
+ ? [providedTag]
289
+ : yield* Effect.gen(function* () {
290
+ const commandRunner = yield* ReleaseCommandRunner;
291
+ const result = yield* commandRunner.runCommand(gitTagsAtHeadCommand(root));
292
+ if (result.exitCode !== 0) {
293
+ return yield* Effect.fail(eligibilityError(`git tag --points-at HEAD failed: ${firstDiagnosticLine(result)}`));
294
+ }
295
+ const currentTags = stdoutLines(result.stdout);
296
+ return providedTag === undefined
297
+ ? currentTags
298
+ : currentTags.filter((tag) => tag === providedTag);
299
+ });
300
+ return tags.flatMap((tag) => {
301
+ const version = versionFromTag(tag, parts);
302
+ return version === undefined ? [] : [{ tag, version }];
303
+ });
304
+ });
305
+ const resolveTagIdentity = Effect.fn("resolveTagIdentity")(function* (root, manifest, tag) {
306
+ return yield* resolveIdentityCommit(ReleaseIdentity.make({
307
+ name: manifest.name,
308
+ version: tag.version,
309
+ commit: "HEAD",
310
+ tag: tag.tag
311
+ }), root);
312
+ });
313
+ const resolveGitTagDecision = Effect.fn("resolveGitTagDecision")(function* (strategy, intent, root) {
314
+ const metadata = {
315
+ strategy: strategy._tag,
316
+ source: strategy.tag === undefined ? "git tag --points-at HEAD" : `tag ${strategy.tag}`
317
+ };
318
+ const manifest = yield* readReleasePackageManifest(root, strategy.packagePath ?? "package.json", "releaseDecision.packagePath");
319
+ const tagTemplate = strategy.tagTemplate ?? "v{version}";
320
+ const requireCurrentRef = strategy.requireCurrentRef ?? true;
321
+ const matchingTags = yield* readMatchingCurrentTags(root, tagTemplate, strategy.tag, requireCurrentRef);
322
+ if (matchingTags.length === 0) {
323
+ if (strategy.tag !== undefined && requireCurrentRef) {
324
+ return skippedReleaseResolution(skippedDecision(`Release tag ${strategy.tag} does not point at HEAD.`, metadata));
325
+ }
326
+ return skippedReleaseResolution(skippedDecision("No release tag matching releaseDecision.tagTemplate points at HEAD.", metadata));
327
+ }
328
+ if (matchingTags.length > 1) {
329
+ return yield* Effect.fail(eligibilityError(`Multiple release tags match ${tagTemplate}: ${matchingTags.map((tag) => tag.tag).join(", ")}.`));
330
+ }
331
+ const tag = matchingTags[0];
332
+ if (tag === undefined) {
333
+ return skippedReleaseResolution(skippedDecision("No release tag matching releaseDecision.tagTemplate points at HEAD.", metadata));
334
+ }
335
+ const identity = yield* resolveTagIdentity(root, manifest, tag);
336
+ return resolvedReleaseResolution(identity, intent.targets, metadata);
337
+ });
338
+ const matchingTagsFromList = Effect.fn("matchingTagsFromList")(function* (stdout, template) {
339
+ const parts = yield* templateParts(template, "releaseDecision.tagTemplate");
340
+ const candidates = [];
341
+ for (const tag of stdoutLines(stdout)) {
342
+ const version = versionFromTag(tag, parts);
343
+ if (version === undefined) {
344
+ continue;
345
+ }
346
+ const validVersion = Semver.valid(version);
347
+ if (validVersion !== null) {
348
+ candidates.push({ tag, version: validVersion });
349
+ }
350
+ }
351
+ return candidates.sort((left, right) => Semver.rcompare(left.version, right.version));
352
+ });
353
+ const latestMatchingTag = Effect.fn("latestMatchingTag")(function* (root, template) {
354
+ const commandRunner = yield* ReleaseCommandRunner;
355
+ const result = yield* commandRunner.runCommand(gitListTagsCommand(root));
356
+ if (result.exitCode !== 0) {
357
+ return yield* Effect.fail(eligibilityError(`git tag --list failed: ${firstDiagnosticLine(result)}`));
358
+ }
359
+ const matching = yield* matchingTagsFromList(result.stdout, template);
360
+ return matching[0];
361
+ });
362
+ const parseCommit = (message) => {
363
+ const firstLine = message.split(/\r?\n/)[0]?.trim() ?? "";
364
+ const match = /^([A-Za-z][A-Za-z0-9-]*)(?:\([^)]+\))?(!)?:/.exec(firstLine);
365
+ const type = match?.[1];
366
+ const breakingSubject = match?.[2] === "!";
367
+ const breakingBody = /^BREAKING[ -]CHANGE:/m.test(message);
368
+ return {
369
+ type,
370
+ breaking: breakingSubject || breakingBody
371
+ };
372
+ };
373
+ const bumpRank = (bump) => {
374
+ switch (bump) {
375
+ case "major":
376
+ return 3;
377
+ case "minor":
378
+ return 2;
379
+ case "patch":
380
+ return 1;
381
+ case "none":
382
+ return 0;
383
+ }
384
+ };
385
+ const higherBump = (left, right) => bumpRank(right) > bumpRank(left) ? right : left;
386
+ const ruleMatches = (rule, commit) => {
387
+ if (rule.breaking !== undefined && rule.breaking !== commit.breaking) {
388
+ return false;
389
+ }
390
+ if (rule.type !== undefined && rule.type !== commit.type) {
391
+ return false;
392
+ }
393
+ return rule.breaking !== undefined || rule.type !== undefined;
394
+ };
395
+ const classifyConventionalCommitBump = (message, rules) => {
396
+ const commit = parseCommit(message);
397
+ let bump = "none";
398
+ for (const rule of rules) {
399
+ if (ruleMatches(rule, commit)) {
400
+ bump = higherBump(bump, rule.release);
401
+ }
402
+ }
403
+ return bump;
404
+ };
405
+ const readCommitMessages = Effect.fn("readCommitMessages")(function* (root, sinceTag) {
406
+ const commandRunner = yield* ReleaseCommandRunner;
407
+ const result = yield* commandRunner.runCommand(gitLogCommand(root, sinceTag));
408
+ if (result.exitCode !== 0) {
409
+ return yield* Effect.fail(eligibilityError(`git log failed: ${firstDiagnosticLine(result)}`));
410
+ }
411
+ return result.stdout
412
+ .split("\x1e")
413
+ .map((message) => message.trim())
414
+ .filter((message) => message.length > 0);
415
+ });
416
+ const nextSemverVersion = (version, bump, field) => {
417
+ const validVersion = Semver.valid(version);
418
+ if (validVersion === null) {
419
+ return Effect.fail(eligibilityError(`${field} must be a valid SemVer version.`));
420
+ }
421
+ const nextVersion = Semver.inc(validVersion, bump);
422
+ if (nextVersion !== null) {
423
+ return Effect.succeed(nextVersion);
424
+ }
425
+ return Effect.fail(eligibilityError(`Unable to increment ${validVersion} as ${bump}.`));
426
+ };
427
+ const highestConventionalBump = (messages, rules) => {
428
+ let bump = "none";
429
+ for (const message of messages) {
430
+ bump = higherBump(bump, classifyConventionalCommitBump(message, rules));
431
+ }
432
+ return bump;
433
+ };
434
+ const resolveConventionalCommitsDecision = Effect.fn("resolveConventionalCommitsDecision")(function* (strategy, intent, root) {
435
+ const metadata = {
436
+ strategy: strategy._tag,
437
+ source: "git commits"
438
+ };
439
+ const tagTemplate = strategy.tagTemplate ?? "v{version}";
440
+ const manifest = yield* readReleasePackageManifest(root, strategy.packagePath ?? "package.json", "releaseDecision.packagePath");
441
+ const latest = yield* latestMatchingTag(root, tagTemplate);
442
+ const messages = yield* readCommitMessages(root, latest?.tag);
443
+ if (messages.length === 0) {
444
+ return skippedReleaseResolution(skippedDecision("No commits were found for conventional commit analysis.", metadata));
445
+ }
446
+ const bump = highestConventionalBump(messages, strategy.releaseRules ?? defaultReleaseRules);
447
+ if (bump === "none") {
448
+ return skippedReleaseResolution(skippedDecision("No releasable conventional commits were found.", metadata));
449
+ }
450
+ const baseVersion = latest?.version ?? manifest.version;
451
+ const nextVersion = yield* nextSemverVersion(baseVersion, bump, latest === undefined ? "package.json version" : "release tag version");
452
+ const identity = yield* resolveIdentityCommit(ReleaseIdentity.make({
453
+ name: manifest.name,
454
+ version: nextVersion,
455
+ commit: "HEAD",
456
+ tag: renderReleaseVersionTemplate(tagTemplate, nextVersion)
457
+ }), root);
458
+ return resolvedReleaseResolution(identity, intent.targets, metadata);
459
+ });
460
+ const readIntentFiles = Effect.fn("readIntentFiles")(function* (root, strategy) {
461
+ const directory = strategy.directory ?? ".release/intents";
462
+ yield* validateNonEmptySafeRelativePath("releaseDecision.directory", directory);
463
+ const manifest = yield* readReleasePackageManifest(root, strategy.packagePath ?? "package.json", "releaseDecision.packagePath");
464
+ const fs = yield* FileSystem.FileSystem;
465
+ const path = yield* Path.Path;
466
+ const absoluteDirectory = path.resolve(root, directory);
467
+ const entries = yield* fs.readDirectory(absoluteDirectory).pipe(Effect.catch(() => Effect.succeed([])));
468
+ const files = [];
469
+ for (const entry of entries.filter((item) => item.endsWith(".json")).sort()) {
470
+ yield* validateNonEmptySafeRelativePath("releaseDecision.intentFile", entry);
471
+ const intentPath = path.join(absoluteDirectory, entry);
472
+ const contents = yield* fs.readFileString(intentPath).pipe(Effect.mapError((error) => eligibilityError(`Unable to read release intent file ${entry}: ${error.message}`, undefined, error)));
473
+ const parsed = yield* Effect.try({
474
+ try: () => JSON.parse(contents),
475
+ catch: (cause) => eligibilityError(`Release intent file ${entry} is not valid JSON.`, undefined, cause)
476
+ });
477
+ const decoded = yield* decodeReleaseIntentFile(parsed).pipe(Effect.mapError((error) => eligibilityError(`Release intent file ${entry} is invalid: ${error.message}`)));
478
+ if (decoded.package === manifest.name) {
479
+ files.push(decoded);
480
+ }
481
+ }
482
+ return {
483
+ manifest,
484
+ packageName: manifest.name,
485
+ directory,
486
+ files
487
+ };
488
+ });
489
+ const highestIntentBump = (files) => {
490
+ let bump = "none";
491
+ for (const file of files) {
492
+ const release = file.empty === true ? "none" : file.release;
493
+ bump = higherBump(bump, release);
494
+ }
495
+ return bump;
496
+ };
497
+ const resolveIntentFilesDecision = Effect.fn("resolveIntentFilesDecision")(function* (strategy, intent, root) {
498
+ const metadata = {
499
+ strategy: strategy._tag,
500
+ source: strategy.directory ?? ".release/intents"
501
+ };
502
+ const intentFiles = yield* readIntentFiles(root, strategy);
503
+ const manifest = intentFiles.manifest;
504
+ if (intentFiles.files.length === 0) {
505
+ return skippedReleaseResolution(skippedDecision(`No release intent files were found for ${manifest.name}.`, metadata));
506
+ }
507
+ const bump = highestIntentBump(intentFiles.files);
508
+ if (bump === "none") {
509
+ return skippedReleaseResolution(skippedDecision(`Release intent files for ${manifest.name} request no release.`, metadata));
510
+ }
511
+ const nextVersion = yield* nextSemverVersion(manifest.version, bump, "package.json version");
512
+ const tagTemplate = strategy.tagTemplate ?? "v{version}";
513
+ yield* templateParts(tagTemplate, "releaseDecision.tagTemplate");
514
+ const identity = yield* resolveIdentityCommit(ReleaseIdentity.make({
515
+ name: manifest.name,
516
+ version: nextVersion,
517
+ commit: "HEAD",
518
+ tag: renderReleaseVersionTemplate(tagTemplate, nextVersion)
519
+ }), root);
520
+ return resolvedReleaseResolution(identity, intent.targets, metadata);
521
+ });
522
+ const resolveReleaseDecision = Effect.fn("resolveReleaseDecision")(function* (intent, root = ".") {
523
+ const strategy = strategyFromIntent(intent);
524
+ switch (strategy._tag) {
525
+ case "RemoteStateReleaseDecision":
526
+ return yield* resolveRemoteStateDecision(strategy, intent, root);
527
+ case "GitTagReleaseDecision":
528
+ return yield* resolveGitTagDecision(strategy, intent, root);
529
+ case "ConventionalCommitsReleaseDecision":
530
+ return yield* resolveConventionalCommitsDecision(strategy, intent, root);
531
+ case "IntentFilesReleaseDecision":
532
+ return yield* resolveIntentFilesDecision(strategy, intent, root);
533
+ }
534
+ });
535
+ export const checkReleaseDecision = Effect.fn("checkReleaseDecision")(function* (intent, root = ".") {
536
+ const resolution = yield* resolveReleaseDecision(intent, root);
537
+ switch (resolution._tag) {
538
+ case "Skipped":
539
+ return resolution.decision;
540
+ case "Resolved":
541
+ return yield* checkRemoteStateForIdentity(resolution.identity, resolution.targets, resolution.metadata);
542
+ }
543
+ });
544
+ export const checkReleaseIntentRequirement = Effect.fn("checkReleaseIntentRequirement")(function* (intent, root = ".") {
545
+ const strategy = strategyFromIntent(intent);
546
+ if (!(strategy instanceof IntentFilesReleaseDecision)) {
547
+ return skippedDecision("Release config is not using IntentFilesReleaseDecision.", {
548
+ strategy: strategy._tag,
549
+ source: "releaseDecision"
550
+ });
551
+ }
552
+ const intentFiles = yield* readIntentFiles(root, strategy);
553
+ if (intentFiles.files.length === 0 && (strategy.requireIntent ?? true)) {
554
+ return yield* Effect.fail(eligibilityError(`release intent files are required for ${intentFiles.packageName} in ${intentFiles.directory}.`));
555
+ }
556
+ const bump = highestIntentBump(intentFiles.files);
557
+ return ReleaseEligibilityDecision.make({
558
+ shouldRelease: bump !== "none",
559
+ status: bump === "none" ? "skipped" : "ready",
560
+ reason: bump === "none"
561
+ ? `Release intent files for ${intentFiles.packageName} request no release.`
562
+ : `Release intent files for ${intentFiles.packageName} request a ${bump} release.`,
563
+ strategy: strategy._tag,
564
+ packageName: intentFiles.packageName,
565
+ source: intentFiles.directory
566
+ });
567
+ });
568
+ export const renderReleaseEligibilityText = (decision) => {
569
+ const strategy = decision.strategy === undefined ? "" : ` strategy=${decision.strategy}`;
570
+ const source = decision.source === undefined ? "" : ` source=${JSON.stringify(decision.source)}`;
571
+ const release = decision.packageName === undefined || decision.packageVersion === undefined
572
+ ? ""
573
+ : ` release=${decision.packageName}@${decision.packageVersion}`;
574
+ const tag = decision.githubTag === undefined ? "" : ` github-tag=${decision.githubTag}`;
575
+ return `release eligibility status=${decision.status} should-release=${decision.shouldRelease}${strategy}${source}${release}${tag} reason=${JSON.stringify(decision.reason)}\n`;
576
+ };
171
577
  export const renderReleaseEligibilityJson = (decision) => `${JSON.stringify(decision, null, 2)}\n`;
172
578
  //# sourceMappingURL=release-eligibility.js.map