@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.
- package/ARCHITECTURE.md +55 -15
- package/README.md +229 -75
- package/SPEC.md +53 -9
- package/dist/config/errors.d.ts +2 -1
- package/dist/config/errors.d.ts.map +1 -1
- package/dist/config/errors.js +4 -3
- package/dist/config/errors.js.map +1 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +5 -3
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +4 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +13 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/domain/artifact.d.ts +3 -3
- package/dist/domain/artifact.d.ts.map +1 -1
- package/dist/domain/artifact.js +1 -1
- package/dist/domain/artifact.js.map +1 -1
- package/dist/domain/evidence.d.ts +18 -14
- package/dist/domain/evidence.d.ts.map +1 -1
- package/dist/domain/evidence.js +8 -3
- package/dist/domain/evidence.js.map +1 -1
- package/dist/domain/operation.d.ts +15 -14
- package/dist/domain/operation.d.ts.map +1 -1
- package/dist/domain/operation.js +117 -1
- package/dist/domain/operation.js.map +1 -1
- package/dist/domain/release.d.ts +84 -9
- package/dist/domain/release.d.ts.map +1 -1
- package/dist/domain/release.js +76 -5
- package/dist/domain/release.js.map +1 -1
- package/dist/domain/remote-state.d.ts +21 -16
- package/dist/domain/remote-state.d.ts.map +1 -1
- package/dist/domain/remote-state.js +14 -8
- package/dist/domain/remote-state.js.map +1 -1
- package/dist/domain/status.d.ts +4 -4
- package/dist/domain/status.d.ts.map +1 -1
- package/dist/domain/status.js +3 -2
- package/dist/domain/status.js.map +1 -1
- package/dist/domain/target.d.ts +7 -7
- package/dist/domain/target.d.ts.map +1 -1
- package/dist/domain/target.js +1 -1
- package/dist/domain/target.js.map +1 -1
- package/dist/host/host.d.ts +1 -0
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +2 -1
- package/dist/host/host.js.map +1 -1
- package/dist/host/http-live.d.ts.map +1 -1
- package/dist/host/http-live.js +7 -3
- package/dist/host/http-live.js.map +1 -1
- package/dist/host/http.d.ts +4 -1
- package/dist/host/http.d.ts.map +1 -1
- package/dist/host/http.js +5 -2
- package/dist/host/http.js.map +1 -1
- package/dist/host/platform.d.ts.map +1 -1
- package/dist/host/platform.js +2 -2
- package/dist/host/platform.js.map +1 -1
- package/dist/host/test.d.ts +1 -0
- package/dist/host/test.d.ts.map +1 -1
- package/dist/host/test.js +67 -7
- package/dist/host/test.js.map +1 -1
- package/dist/internal/workspace-path.d.ts +16 -0
- package/dist/internal/workspace-path.d.ts.map +1 -0
- package/dist/internal/workspace-path.js +33 -0
- package/dist/internal/workspace-path.js.map +1 -0
- package/dist/planner/artifact-inventory.d.ts.map +1 -1
- package/dist/planner/artifact-inventory.js +28 -9
- package/dist/planner/artifact-inventory.js.map +1 -1
- package/dist/planner/create-release-plan.d.ts +1 -1
- package/dist/planner/errors.d.ts +12 -6
- package/dist/planner/errors.d.ts.map +1 -1
- package/dist/planner/errors.js +20 -12
- package/dist/planner/errors.js.map +1 -1
- package/dist/planner/evidence-recorder.d.ts.map +1 -1
- package/dist/planner/evidence-recorder.js +32 -10
- package/dist/planner/evidence-recorder.js.map +1 -1
- package/dist/planner/executor.d.ts +4 -4
- package/dist/planner/executor.d.ts.map +1 -1
- package/dist/planner/executor.js +36 -4
- package/dist/planner/executor.js.map +1 -1
- package/dist/planner/normalize-release.d.ts +11 -2
- package/dist/planner/normalize-release.d.ts.map +1 -1
- package/dist/planner/normalize-release.js +98 -19
- package/dist/planner/normalize-release.js.map +1 -1
- package/dist/planner/reconcile.d.ts +1 -1
- package/dist/planner/reconcile.d.ts.map +1 -1
- package/dist/planner/reconcile.js +75 -92
- package/dist/planner/reconcile.js.map +1 -1
- package/dist/planner/release-eligibility.d.ts +20 -14
- package/dist/planner/release-eligibility.d.ts.map +1 -1
- package/dist/planner/release-eligibility.js +431 -25
- package/dist/planner/release-eligibility.js.map +1 -1
- package/dist/planner/render-plan.d.ts +10 -0
- package/dist/planner/render-plan.d.ts.map +1 -1
- package/dist/planner/render-plan.js +159 -0
- package/dist/planner/render-plan.js.map +1 -1
- package/dist/planner/status.d.ts +1 -1
- package/dist/planner/status.d.ts.map +1 -1
- package/dist/planner/status.js +8 -2
- package/dist/planner/status.js.map +1 -1
- package/dist/targets/adapter-helpers.d.ts +20 -2
- package/dist/targets/adapter-helpers.d.ts.map +1 -1
- package/dist/targets/adapter-helpers.js +32 -1
- package/dist/targets/adapter-helpers.js.map +1 -1
- package/dist/targets/github-release.d.ts +25 -0
- package/dist/targets/github-release.d.ts.map +1 -0
- package/dist/targets/github-release.js +82 -0
- package/dist/targets/github-release.js.map +1 -0
- package/dist/targets/github.d.ts +2 -2
- package/dist/targets/github.d.ts.map +1 -1
- package/dist/targets/github.js +28 -66
- package/dist/targets/github.js.map +1 -1
- package/dist/targets/homebrew.d.ts +2 -2
- package/dist/targets/homebrew.d.ts.map +1 -1
- package/dist/targets/homebrew.js +15 -31
- package/dist/targets/homebrew.js.map +1 -1
- package/dist/targets/scoop.d.ts +2 -2
- package/dist/targets/scoop.d.ts.map +1 -1
- package/dist/targets/scoop.js +13 -28
- package/dist/targets/scoop.js.map +1 -1
- package/dist/workflows/config.d.ts +191 -18
- package/dist/workflows/config.d.ts.map +1 -1
- package/dist/workflows/config.js +339 -61
- package/dist/workflows/config.js.map +1 -1
- package/dist/workflows/diagnostics.d.ts +62 -0
- package/dist/workflows/diagnostics.d.ts.map +1 -0
- package/dist/workflows/diagnostics.js +536 -0
- package/dist/workflows/diagnostics.js.map +1 -0
- package/dist/workflows/evidence.d.ts +8 -3
- package/dist/workflows/evidence.d.ts.map +1 -1
- package/dist/workflows/evidence.js +9 -0
- package/dist/workflows/evidence.js.map +1 -1
- package/dist/workflows/index.d.ts +7 -0
- package/dist/workflows/index.d.ts.map +1 -0
- package/dist/workflows/index.js +6 -0
- package/dist/workflows/index.js.map +1 -0
- package/dist/workflows/init.d.ts +84 -0
- package/dist/workflows/init.d.ts.map +1 -0
- package/dist/workflows/init.js +451 -0
- package/dist/workflows/init.js.map +1 -0
- package/dist/workflows/live.d.ts +2 -0
- package/dist/workflows/live.d.ts.map +1 -1
- package/dist/workflows/live.js +2 -0
- package/dist/workflows/live.js.map +1 -1
- package/dist/workflows/options.d.ts +30 -0
- package/dist/workflows/options.d.ts.map +1 -0
- package/dist/workflows/options.js +16 -0
- package/dist/workflows/options.js.map +1 -0
- package/examples/README.md +16 -12
- package/examples/github-release/release.config.json +1 -0
- package/examples/homebrew-tap/release.config.json +1 -0
- package/examples/multi-target/release.config.json +5 -1
- package/examples/non-strict-skips/release.config.json +1 -0
- package/examples/npm-first-publish/release.config.json +1 -0
- package/examples/npm-only/release.config.json +5 -1
- package/examples/pypi-registry/release.config.json +1 -0
- package/examples/scoop-bucket/release.config.json +1 -0
- package/package.json +37 -26
- package/templates/README.md +59 -0
- package/templates/github-actions/plan-and-approved-execute.yml +58 -0
- package/templates/github-actions/plan-only.yml +33 -0
- package/templates/github-actions/trusted-publishing.yml +61 -0
- package/templates/multi-target-homebrew/release.config.json +70 -0
- package/templates/multi-target-scoop/release.config.json +72 -0
- package/templates/npm-github/release.config.json +50 -0
- package/templates/npm-only/release.config.json +39 -0
- package/dist/cli/command.d.ts +0 -4
- package/dist/cli/command.d.ts.map +0 -1
- package/dist/cli/command.js +0 -173
- package/dist/cli/command.js.map +0 -1
- package/dist/cli/main.d.ts +0 -3
- package/dist/cli/main.d.ts.map +0 -1
- package/dist/cli/main.js +0 -9
- package/dist/cli/main.js.map +0 -1
- package/dist/runtime/bun.d.ts +0 -9
- package/dist/runtime/bun.d.ts.map +0 -1
- package/dist/runtime/bun.js +0 -10
- package/dist/runtime/bun.js.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js +0 -2
- 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
|
-
|
|
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.
|
|
16
|
-
readonly packageVersion: Schema.
|
|
17
|
-
readonly npmTargetId: Schema.
|
|
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.
|
|
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",
|
|
29
|
-
export declare const classifyGitHubReleaseAvailability: (input: ReleaseEligibilityRemoteCheck) => Effect.Effect<"missing" | "published" | "draft",
|
|
30
|
-
export declare const checkReleaseEligibility: (input: ReleaseEligibilityRemoteCheck) => Effect.Effect<ReleaseEligibilityDecision,
|
|
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;
|
|
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
|
-
|
|
8
|
-
|
|
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:
|
|
14
|
-
packageVersion:
|
|
15
|
-
npmTargetId:
|
|
15
|
+
packageName: ReleaseName,
|
|
16
|
+
packageVersion: ReleaseVersion,
|
|
17
|
+
npmTargetId: TargetId,
|
|
16
18
|
npmRegistry: Schema.String,
|
|
17
|
-
githubTargetId:
|
|
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 = (
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
83
|
-
packageVersion:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|