@savvy-web/silk-effects 7.5.3 → 8.0.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/index.js CHANGED
@@ -1,22 +1,14 @@
1
- import { SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, savvyBasePreamble, savvyHooksHygiene, savvyToolSection, savvyToolchainCheck } from "./schemas/SavvySections.js";
2
- import { ChangesetConfigError } from "./errors/ChangesetConfigError.js";
3
1
  import { ChangesetConfigReader } from "./services/ChangesetConfigReader.js";
4
- import { PublishTargetBindingError } from "./errors/PublishTargetBindingError.js";
5
2
  import { ChangesetConfig } from "./services/ChangesetConfig.js";
6
3
  import { SilkPublishability, readTargetsBinding } from "./services/SilkPublishability.js";
7
4
  import { changesets_exports } from "./changesets/index.js";
8
5
  import { commitlint_exports } from "./commitlint/index.js";
9
- import { BiomeSyncError } from "./errors/BiomeSyncError.js";
10
- import { ConfigNotFoundError } from "./errors/ConfigNotFoundError.js";
11
- import { WorkspaceAnalysisError } from "./errors/WorkspaceAnalysisError.js";
12
6
  import { lint_exports } from "./lint/index.js";
13
- import { pr_body_exports } from "./pr-body/index.js";
14
7
  import { repos_exports } from "./repos/index.js";
15
- import { LIFECYCLE_SCRIPTS_CONFIG_KEY, SavvyInstallSection, publishesBuiltLinkDirectory, savvyInstallBlock, savvyInstallDeps } from "./schemas/SavvyInstallSection.js";
16
- import { AnalyzedWorkspace, SilkPublishConfig, WorkspaceAnalysis } from "./schemas/WorkspaceAnalysisSchemas.js";
17
8
  import { BiomeSchemaSync, buildSchemaUrl, extractSemver } from "./services/BiomeSchemaSync.js";
18
9
  import { ConfigDiscovery } from "./services/ConfigDiscovery.js";
19
10
  import { SilkWorkspaceAnalyzer } from "./services/SilkWorkspaceAnalyzer.js";
20
11
  import { turbo_exports } from "./turbo/index.js";
12
+ import { AnalyzedWorkspace, BiomeSyncError, ChangesetConfigError, ConfigNotFoundError, LIFECYCLE_SCRIPTS_CONFIG_KEY, PrBody, PublishTargetBindingError, SavvyBaseSection, SavvyHooksSection, SavvyInstallSection, SavvyToolchainSection, SilkPublishConfig, WorkspaceAnalysis, WorkspaceAnalysisError, publishesBuiltLinkDirectory, savvyBasePreamble, savvyHooksHygiene, savvyInstallBlock, savvyInstallDeps, savvyToolSection, savvyToolchainCheck } from "@savvy-web/silk-core";
21
13
 
22
- export { AnalyzedWorkspace, BiomeSchemaSync, BiomeSyncError, ChangesetConfig, ChangesetConfigError, ChangesetConfigReader, changesets_exports as Changesets, commitlint_exports as Commitlint, ConfigDiscovery, ConfigNotFoundError, LIFECYCLE_SCRIPTS_CONFIG_KEY, lint_exports as Lint, pr_body_exports as PrBody, PublishTargetBindingError, repos_exports as Repos, SavvyBaseSection, SavvyHooksSection, SavvyInstallSection, SavvyToolchainSection, SilkPublishConfig, SilkPublishability, SilkWorkspaceAnalyzer, turbo_exports as Turbo, WorkspaceAnalysis, WorkspaceAnalysisError, buildSchemaUrl, extractSemver, publishesBuiltLinkDirectory, readTargetsBinding, savvyBasePreamble, savvyHooksHygiene, savvyInstallBlock, savvyInstallDeps, savvyToolSection, savvyToolchainCheck };
14
+ export { AnalyzedWorkspace, BiomeSchemaSync, BiomeSyncError, ChangesetConfig, ChangesetConfigError, ChangesetConfigReader, changesets_exports as Changesets, commitlint_exports as Commitlint, ConfigDiscovery, ConfigNotFoundError, LIFECYCLE_SCRIPTS_CONFIG_KEY, lint_exports as Lint, PrBody, PublishTargetBindingError, repos_exports as Repos, SavvyBaseSection, SavvyHooksSection, SavvyInstallSection, SavvyToolchainSection, SilkPublishConfig, SilkPublishability, SilkWorkspaceAnalyzer, turbo_exports as Turbo, WorkspaceAnalysis, WorkspaceAnalysisError, buildSchemaUrl, extractSemver, publishesBuiltLinkDirectory, readTargetsBinding, savvyBasePreamble, savvyHooksHygiene, savvyInstallBlock, savvyInstallDeps, savvyToolSection, savvyToolchainCheck };
@@ -1,4 +1,4 @@
1
- import { savvyToolSection } from "../../schemas/SavvySections.js";
1
+ import { savvyToolSection } from "@savvy-web/silk-core";
2
2
  import { CommentStyle, SectionId } from "@effected/templates";
3
3
 
4
4
  //#region src/lint/cli/sections.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/silk-effects",
3
- "version": "7.5.3",
3
+ "version": "8.0.0",
4
4
  "private": false,
5
5
  "description": "Shared Effect library for Silk Suite conventions",
6
6
  "homepage": "https://github.com/savvy-web/systems/tree/main/packages/silk-effects",
@@ -44,6 +44,7 @@
44
44
  "@effected/walker": "^0.8.0",
45
45
  "@effected/yaml": "^0.15.0",
46
46
  "@manypkg/get-packages": "^3.1.0",
47
+ "@savvy-web/silk-core": "0.1.0",
47
48
  "mdast-util-heading-range": "^4.0.0",
48
49
  "mdast-util-to-string": "^4.0.0",
49
50
  "remark-gfm": "^4.0.1",
@@ -1,4 +1,4 @@
1
- import { BiomeSyncError } from "../errors/BiomeSyncError.js";
1
+ import { BiomeSyncError } from "@savvy-web/silk-core";
2
2
  import { Context, Effect, FileSystem, Layer } from "effect";
3
3
  import { Jsonc, JsoncEdit, JsoncModifier } from "@effected/jsonc";
4
4
 
@@ -60,7 +60,7 @@ function findBiomeConfigs(cwd, fs) {
60
60
  * const result = await Effect.runPromise(
61
61
  * Effect.gen(function* () {
62
62
  * const syncer = yield* BiomeSchemaSync;
63
- * return yield* syncer.sync("^1.9.3");
63
+ * return yield* syncer.sync("^1.9.3", { cwd: "/path/to/repo" });
64
64
  * }).pipe(
65
65
  * Effect.provide(BiomeSchemaSync.layer),
66
66
  * Effect.provide(NodeServices.layer),
@@ -85,7 +85,7 @@ var BiomeSchemaSync = class extends Context.Service()("@savvy-web/silk-effects/B
85
85
  static layer = Layer.effect(this, Effect.gen(function* () {
86
86
  const fs = yield* FileSystem.FileSystem;
87
87
  const run = (version, options, write) => Effect.gen(function* () {
88
- const cwd = options?.cwd ?? process.cwd();
88
+ const { cwd } = options;
89
89
  const expectedUrl = buildSchemaUrl(extractSemver(version));
90
90
  const configs = yield* findBiomeConfigs(cwd, fs);
91
91
  const updated = [];
@@ -1,5 +1,4 @@
1
- import { ChangesetConfigError } from "../errors/ChangesetConfigError.js";
2
- import { ChangesetConfigFile, SilkChangesetConfigFile } from "../schemas/VersioningSchemas.js";
1
+ import { ChangesetConfigError, ChangesetConfigFile, SilkChangesetConfigFile } from "@savvy-web/silk-core";
3
2
  import { Context, Effect, FileSystem, Layer, Schema } from "effect";
4
3
 
5
4
  //#region src/services/ChangesetConfigReader.ts
@@ -31,8 +30,8 @@ function isSilkChangelog(changelog) {
31
30
  *
32
31
  * @remarks
33
32
  * Automatically detects whether the config uses the Silk changelog adapter
34
- * (`@savvy-web/changesets`) and decodes as {@link (SilkChangesetConfigFile:type)} or the
35
- * standard {@link (ChangesetConfigFile:type)} accordingly.
33
+ * (`@savvy-web/changesets`) and decodes as `SilkChangesetConfigFile` or the
34
+ * standard `ChangesetConfigFile` accordingly.
36
35
  *
37
36
  * @example
38
37
  * ```typescript
@@ -22,7 +22,7 @@ function safeExists(fs, path) {
22
22
  * const result = await Effect.runPromise(
23
23
  * Effect.gen(function* () {
24
24
  * const discovery = yield* ConfigDiscovery;
25
- * return yield* discovery.find("biome.json");
25
+ * return yield* discovery.find("biome.json", { cwd: "/path/to/repo" });
26
26
  * }).pipe(
27
27
  * Effect.provide(ConfigDiscovery.layer),
28
28
  * Effect.provide(NodeServices.layer),
@@ -47,7 +47,7 @@ var ConfigDiscovery = class extends Context.Service()("@savvy-web/silk-effects/C
47
47
  static layer = Layer.effect(this, Effect.gen(function* () {
48
48
  const fs = yield* FileSystem.FileSystem;
49
49
  const findAll = (name, options) => Effect.gen(function* () {
50
- const cwd = options?.cwd ?? process.cwd();
50
+ const { cwd } = options;
51
51
  const results = [];
52
52
  const libPath = `${cwd}/lib/configs/${name}`;
53
53
  if (yield* safeExists(fs, libPath)) results.push({
@@ -1,6 +1,5 @@
1
- import { PublishTargetBindingError } from "../errors/PublishTargetBindingError.js";
2
- import { trimTrailingSlashes } from "../utils/TrailingSlash.js";
3
1
  import { ChangesetConfig } from "./ChangesetConfig.js";
2
+ import { PublishTargetBindingError, trimTrailingSlashes } from "@savvy-web/silk-core";
4
3
  import { Effect, FileSystem, Layer } from "effect";
5
4
  import { isAbsolute, join, relative } from "node:path";
6
5
  import { PublishTarget, PublishabilityDetector, WorkspaceDiscovery } from "@effected/workspaces";
@@ -127,7 +126,7 @@ var SilkPublishability = class SilkPublishability {
127
126
  * directories it names. A directory outside the binding means detection did
128
127
  * not select the prod output — the `yaml-effect@0.7.1` shape, where a dev
129
128
  * manifest carrying `catalog:` specifiers was packed and published. Rather
130
- * than ship those bytes, fail with {@link PublishTargetBindingError}.
129
+ * than ship those bytes, fail with `PublishTargetBindingError`.
131
130
  *
132
131
  * Before the prod build runs there is no binding, and the detector's
133
132
  * placeholder directories are left alone.
@@ -1,8 +1,7 @@
1
1
  import { ChangesetConfigReader } from "./ChangesetConfigReader.js";
2
2
  import { ChangesetConfig } from "./ChangesetConfig.js";
3
3
  import { SilkPublishability, readTargetsBinding } from "./SilkPublishability.js";
4
- import { WorkspaceAnalysisError } from "../errors/WorkspaceAnalysisError.js";
5
- import { AnalyzedWorkspace, WorkspaceAnalysis } from "../schemas/WorkspaceAnalysisSchemas.js";
4
+ import { AnalyzedWorkspace, WorkspaceAnalysis, WorkspaceAnalysisError } from "@savvy-web/silk-core";
6
5
  import { Context, Effect, FileSystem, Layer, Option } from "effect";
7
6
  import { DependencyGraph, PackageManagerDetector, VersioningStrategy, WorkspaceDiscovery } from "@effected/workspaces";
8
7
 
@@ -16,7 +15,7 @@ import { DependencyGraph, PackageManagerDetector, VersioningStrategy, WorkspaceD
16
15
  * Orchestrates `WorkspaceDiscovery`, `PackageManagerDetector` and
17
16
  * {@link ChangesetConfigReader}, then classifies the result with
18
17
  * `@effected/workspaces`' pure `VersioningStrategy` value class, to produce a
19
- * complete {@link WorkspaceAnalysis} for a given workspace root.
18
+ * complete `WorkspaceAnalysis` for a given workspace root.
20
19
  *
21
20
  * @example
22
21
  * ```typescript
@@ -1,22 +0,0 @@
1
- import { Data } from "effect";
2
-
3
- //#region src/errors/BiomeSyncError.ts
4
- /**
5
- * Raised when a Biome config file cannot be read or its `$schema` URL cannot be updated.
6
- *
7
- * @remarks
8
- * Returned by `BiomeSchemaSync.sync` and `BiomeSchemaSync.check` when
9
- * a `biome.json` or `biome.jsonc` file exists but cannot be read, contains invalid JSON,
10
- * or cannot be written back to disk.
11
- *
12
- * @since 0.1.0
13
- * @public
14
- */
15
- var BiomeSyncError = class extends Data.TaggedError("BiomeSyncError") {
16
- get message() {
17
- return `Failed to sync biome schema in ${this.path}: ${this.reason}`;
18
- }
19
- };
20
-
21
- //#endregion
22
- export { BiomeSyncError };
@@ -1,21 +0,0 @@
1
- import { Data } from "effect";
2
-
3
- //#region src/errors/ChangesetConfigError.ts
4
- /**
5
- * Raised when the `.changeset/config.json` file cannot be read or decoded.
6
- *
7
- * @remarks
8
- * Returned by `ChangesetConfigReader.read` when the file is missing,
9
- * contains invalid JSON, or fails Effect Schema validation.
10
- *
11
- * @since 0.1.0
12
- * @public
13
- */
14
- var ChangesetConfigError = class extends Data.TaggedError("ChangesetConfigError") {
15
- get message() {
16
- return `Failed to read changeset config at ${this.path}: ${this.reason}`;
17
- }
18
- };
19
-
20
- //#endregion
21
- export { ChangesetConfigError };
@@ -1,22 +0,0 @@
1
- import { Data } from "effect";
2
-
3
- //#region src/errors/ConfigNotFoundError.ts
4
- /**
5
- * Raised when a config file cannot be located in any of the expected locations.
6
- *
7
- * @remarks
8
- * Returned by consumers that require a config file to exist. `ConfigDiscovery.find`
9
- * itself returns `null` instead of failing — callers that need a hard failure should
10
- * map `null` to this error.
11
- *
12
- * @since 0.1.0
13
- * @public
14
- */
15
- var ConfigNotFoundError = class extends Data.TaggedError("ConfigNotFoundError") {
16
- get message() {
17
- return `Config '${this.name}' not found. Searched: ${this.searchedPaths.join(", ")}`;
18
- }
19
- };
20
-
21
- //#endregion
22
- export { ConfigNotFoundError };
@@ -1,30 +0,0 @@
1
- import { Data } from "effect";
2
-
3
- //#region src/errors/PublishTargetBindingError.ts
4
- /**
5
- * Raised when publishability detection selects a directory that the package's
6
- * `dist/prod/targets.json` binding does not describe.
7
- *
8
- * @remarks
9
- * The bundler's prod build writes `targets.json` naming every byte-group
10
- * directory it produced. Once that binding exists it is authoritative: the only
11
- * directories whose bytes may be published are the ones it lists. A detector
12
- * that returns anything else — most often `publishConfig.directory` pointing at
13
- * a **dev** build, because silk mode was misdetected — is about to pack an
14
- * unresolved workspace manifest and ship it to a registry.
15
- *
16
- * That is the `yaml-effect@0.7.1` failure: detection picked `dist/dev/pkg`, the
17
- * dev manifest still carried `catalog:` specifiers, and the published package
18
- * was uninstallable (`EUNSUPPORTEDPROTOCOL`).
19
- *
20
- * @since 3.1.0
21
- * @public
22
- */
23
- var PublishTargetBindingError = class extends Data.TaggedError("PublishTargetBindingError") {
24
- get message() {
25
- return `Package ${this.pkg} resolved publish directory "${this.directory}", which is not one of the directories bound by dist/prod/targets.json (${this.boundDirectories.join(", ")}). This means publishability detection did not select the prod build output — refusing to publish before packing an unresolved manifest.`;
26
- }
27
- };
28
-
29
- //#endregion
30
- export { PublishTargetBindingError };
@@ -1,22 +0,0 @@
1
- import { Data } from "effect";
2
-
3
- //#region src/errors/WorkspaceAnalysisError.ts
4
- /**
5
- * Raised when workspace analysis fails for a given root directory.
6
- *
7
- * @remarks
8
- * Returned by `SilkWorkspaceAnalyzer.analyze` when the analysis pipeline
9
- * encounters an unrecoverable error — e.g. workspace discovery failure,
10
- * package manager detection failure, or publishability detection errors.
11
- *
12
- * @since 0.2.0
13
- * @public
14
- */
15
- var WorkspaceAnalysisError = class extends Data.TaggedError("WorkspaceAnalysisError") {
16
- get message() {
17
- return `Workspace analysis failed at ${this.root}: ${this.reason}`;
18
- }
19
- };
20
-
21
- //#endregion
22
- export { WorkspaceAnalysisError };
package/pr-body/body.js DELETED
@@ -1,145 +0,0 @@
1
- import { Region } from "./region.js";
2
- import { Markers } from "./markers.js";
3
- import { ClosingReferences, OwnedAttribute } from "./references.js";
4
-
5
- //#region src/pr-body/body.ts
6
- const FENCE = "```";
7
- /**
8
- * The marker-delimited reference region, merging this run's references with
9
- * any an agent added.
10
- *
11
- * @remarks
12
- * Module-private: the region is always emitted, empty or not, so an agent
13
- * always has a target to write into — the same reservation the summary
14
- * region makes.
15
- */
16
- const buildReferencesRegion = (args) => {
17
- const known = new Set(args.linkedIssues.map((issue) => issue.number));
18
- const ownedNow = ClosingReferences.fromIssues(args.linkedIssues).dedupe();
19
- const agentAdded = [...new Set(ClosingReferences.parseBare(args.carriedReferences))].filter((id) => !args.previouslyOwned.has(id) && !known.has(id)).sort((a, b) => a - b);
20
- const lines = ClosingReferences.make({ ids: [...ownedNow.ids, ...agentAdded] }).renderBareLines();
21
- return `${`${Markers.REFERENCES_START_PREFIX} ${OwnedAttribute.render(ownedNow.ids)} -->`}\n${lines === "" ? "" : `${lines}\n`}${Markers.REFERENCES_END}`;
22
- };
23
- /**
24
- * The proposed squash-commit message, fenced for an AI integration to
25
- * rewrite.
26
- *
27
- * @remarks
28
- * Module-private. Carries its own `Closes` references because the squash
29
- * commit needs them too — a reference inside a fenced block is inert to
30
- * GitHub's linker, which is precisely why the plain-text lines outside it
31
- * are separate rather than shared.
32
- */
33
- const buildSquashBlock = (args) => {
34
- const closing = ClosingReferences.fromIssues(args.linkedIssues).renderTrailer();
35
- const message = [
36
- args.subject,
37
- closing,
38
- args.signoff
39
- ].filter((part) => part !== "").join("\n\n");
40
- return `${FENCE}${Markers.SQUASH_FENCE_LANGUAGE}\n${message}\n${FENCE}`;
41
- };
42
- /**
43
- * The shared managed-PR-body renderer and its carry-through readers — the
44
- * single implementation of the contract `silk-release-action` dogfooded at
45
- * `src/utils/pr-body.ts` (savvy-web/systems#419).
46
- *
47
- * @remarks
48
- * Every operation is pure and total: markers absent, regions broken, or
49
- * attributes malformed all degrade to the documented fail-safe result
50
- * (preserve too much rather than delete someone's work) instead of failing —
51
- * a regenerating action must still produce a body when the prior one is
52
- * malformed. Use `PrBodyDiagnostic.scan` where a writer wants to be told
53
- * about a broken pair instead of tolerating it.
54
- *
55
- * @public
56
- */
57
- var ManagedPrBody = class ManagedPrBody {
58
- constructor() {}
59
- /**
60
- * Build the region of the PR description the generating run owns.
61
- *
62
- * @remarks
63
- * Delimited by `Markers.MANAGED_START` / `Markers.MANAGED_END` so
64
- * {@link ManagedPrBody.upsert} can regenerate it without touching
65
- * anything a human wrote around it. Layout, in order: the reserved
66
- * summary region (nothing may sit above it — a reader meets the prose
67
- * before the machinery), the proposed-squash-commit fence, and the
68
- * bare-reference region. No preamble, no file listing, no linked-issues
69
- * list, no run attribution — each said something already on the page.
70
- *
71
- * @public
72
- */
73
- static build(options) {
74
- const parts = [];
75
- parts.push(`${Markers.SUMMARY_START}\n${options.summary === "" ? "" : `${options.summary}\n`}${Markers.SUMMARY_END}`);
76
- parts.push(buildSquashBlock({
77
- subject: options.subject,
78
- linkedIssues: options.linkedIssues,
79
- signoff: options.signoff
80
- }));
81
- const priorBody = options.priorBody ?? "";
82
- parts.push(buildReferencesRegion({
83
- linkedIssues: options.linkedIssues,
84
- carriedReferences: ManagedPrBody.extractReferences(priorBody),
85
- previouslyOwned: OwnedAttribute.parse(priorBody)
86
- }));
87
- return `${Markers.MANAGED_START}\n${parts.join("\n\n")}\n${Markers.MANAGED_END}`;
88
- }
89
- /**
90
- * Put `managed` (a full {@link ManagedPrBody.build} result) into
91
- * `existing`, replacing a previous managed region and leaving everything
92
- * else alone.
93
- *
94
- * @remarks
95
- * Human edits outside the markers survive; a body with no markers keeps
96
- * its content and gains the region below it. See `Region.upsert` for the
97
- * splice semantics.
98
- *
99
- * @public
100
- */
101
- static upsert(existing, managed) {
102
- return Region.upsert(existing, "silk-release", managed);
103
- }
104
- /**
105
- * The summary region's current content, or `""` when it is empty or
106
- * absent.
107
- *
108
- * @remarks
109
- * Extraction exists because the managed region is REGENERATED on every
110
- * run. Re-emitting the region empty would delete a summary the moment
111
- * any commit landed — destructive and silent, with no signal back to the
112
- * summariser whose work was discarded. Feed the result to
113
- * {@link ManagedPrBody.build}'s `summary` option.
114
- *
115
- * @public
116
- */
117
- static extractSummary(existing) {
118
- const from = existing.indexOf(Markers.SUMMARY_START);
119
- const to = existing.indexOf(Markers.SUMMARY_END);
120
- if (from === -1 || to === -1 || to < from) return "";
121
- return existing.slice(from + Markers.SUMMARY_START.length, to).trim();
122
- }
123
- /**
124
- * The reference region's current content, or `""` when it is empty or
125
- * absent.
126
- *
127
- * @remarks
128
- * Symmetric to {@link ManagedPrBody.extractSummary}, and for the same
129
- * reason. Located by `Markers.REFERENCES_START_PREFIX` — never the plain
130
- * opening constant — because a generating run emits the attributed form.
131
- *
132
- * @public
133
- */
134
- static extractReferences(existing) {
135
- const from = existing.indexOf(Markers.REFERENCES_START_PREFIX);
136
- const to = existing.indexOf(Markers.REFERENCES_END);
137
- if (from === -1 || to === -1 || to < from) return "";
138
- const openEnd = existing.indexOf("-->", from);
139
- if (openEnd === -1 || openEnd > to) return "";
140
- return existing.slice(openEnd + 3, to).trim();
141
- }
142
- };
143
-
144
- //#endregion
145
- export { ManagedPrBody };
@@ -1,104 +0,0 @@
1
- import { Region } from "./region.js";
2
- import { Markers } from "./markers.js";
3
- import { Schema } from "effect";
4
-
5
- //#region src/pr-body/diagnostics.ts
6
- /**
7
- * The problems {@link PrBodyDiagnostic.scan} can report about a body's
8
- * markers.
9
- *
10
- * @public
11
- */
12
- const PrBodyDiagnosticCode = Schema.Literals(["unpairedMarker", "duplicateMarker"]);
13
- /**
14
- * One problem with a body's silk-release markers.
15
- *
16
- * @remarks
17
- * Diagnostics are advisory VALUES, not a typed error channel: every parse and
18
- * render operation in this namespace is deliberately total (a regenerating
19
- * action must still produce a body when the prior one is malformed, and the
20
- * fail-safe direction is to preserve too much rather than delete someone's
21
- * work). `scan` exists for the writer that wants to be told about a broken
22
- * pair before editing — the `pr-body` skill instructs an agent that finds a
23
- * region missing its pair to stop and report rather than guess, and this is
24
- * the check that instruction points at. A misplaced marker pair is worse than
25
- * none: it makes the next regeneration rewrite content it does not own.
26
- *
27
- * @public
28
- */
29
- var PrBodyDiagnostic = class PrBodyDiagnostic extends Schema.Class("PrBodyDiagnostic")({
30
- code: PrBodyDiagnosticCode,
31
- /** The region token the problem is about, e.g. `silk-release:summary`. */
32
- token: Schema.String
33
- }) {
34
- /**
35
- * A human-readable description, derived from the structured fields.
36
- *
37
- * @public
38
- */
39
- get message() {
40
- return this.code === "unpairedMarker" ? `region "${this.token}" has an unpaired or out-of-order marker — its :start and :end must both be present, in order` : `region "${this.token}" appears more than once — each silk-release region must be a single pair`;
41
- }
42
- /**
43
- * Every marker problem in `body`, or an empty array when the markers are
44
- * well-formed (including entirely absent — an unmanaged body is not a
45
- * defect).
46
- *
47
- * @remarks
48
- * The references region is located by its attributed opening prefix, so a
49
- * marker carrying an `owned="…"` attribute counts as present.
50
- *
51
- * @public
52
- */
53
- static scan(body) {
54
- const diagnostics = [];
55
- const tokens = [
56
- ["silk-release", Markers.MANAGED_START],
57
- ["silk-release:summary", Markers.SUMMARY_START],
58
- ["silk-release:references", Markers.REFERENCES_START_PREFIX]
59
- ];
60
- for (const [token, openMarker] of tokens) {
61
- const opens = countOccurrences(body, openMarker);
62
- const closes = countOccurrences(body, Region.end(token));
63
- if (opens === 0 && closes === 0) continue;
64
- if (opens !== closes) {
65
- diagnostics.push(PrBodyDiagnostic.make({
66
- code: "unpairedMarker",
67
- token
68
- }));
69
- continue;
70
- }
71
- if (opens > 1) {
72
- diagnostics.push(PrBodyDiagnostic.make({
73
- code: "duplicateMarker",
74
- token
75
- }));
76
- continue;
77
- }
78
- if (body.indexOf(Region.end(token)) < body.indexOf(openMarker)) diagnostics.push(PrBodyDiagnostic.make({
79
- code: "unpairedMarker",
80
- token
81
- }));
82
- }
83
- return diagnostics;
84
- }
85
- };
86
- /**
87
- * Non-overlapping occurrences of `needle` in `haystack`.
88
- *
89
- * @remarks
90
- * Module-private on purpose — it exists only to keep `scan` readable.
91
- */
92
- const countOccurrences = (haystack, needle) => {
93
- let count = 0;
94
- let from = 0;
95
- for (;;) {
96
- const at = haystack.indexOf(needle, from);
97
- if (at === -1) return count;
98
- count += 1;
99
- from = at + needle.length;
100
- }
101
- };
102
-
103
- //#endregion
104
- export { PrBodyDiagnostic, PrBodyDiagnosticCode };
package/pr-body/index.js DELETED
@@ -1,22 +0,0 @@
1
- import { __exportAll } from "../_virtual/_rolldown/runtime.js";
2
- import { Region } from "./region.js";
3
- import { Markers } from "./markers.js";
4
- import { LinkedIssueRef } from "./linked-issue.js";
5
- import { ClosingReferences, OwnedAttribute } from "./references.js";
6
- import { ManagedPrBody } from "./body.js";
7
- import { PrBodyDiagnostic, PrBodyDiagnosticCode } from "./diagnostics.js";
8
-
9
- //#region src/pr-body/index.ts
10
- var pr_body_exports = /* @__PURE__ */ __exportAll({
11
- ClosingReferences: () => ClosingReferences,
12
- LinkedIssueRef: () => LinkedIssueRef,
13
- ManagedPrBody: () => ManagedPrBody,
14
- Markers: () => Markers,
15
- OwnedAttribute: () => OwnedAttribute,
16
- PrBodyDiagnostic: () => PrBodyDiagnostic,
17
- PrBodyDiagnosticCode: () => PrBodyDiagnosticCode,
18
- Region: () => Region
19
- });
20
-
21
- //#endregion
22
- export { ClosingReferences, LinkedIssueRef, ManagedPrBody, Markers, OwnedAttribute, PrBodyDiagnostic, PrBodyDiagnosticCode, Region, pr_body_exports };
@@ -1,44 +0,0 @@
1
- import { Schema } from "effect";
2
-
3
- //#region src/pr-body/linked-issue.ts
4
- /**
5
- * The minimum an issue must carry to appear in a managed PR body.
6
- *
7
- * @remarks
8
- * `state` is deliberately a tolerant `Schema.String` rather than a literal
9
- * union: GitHub's REST API reports `"open"`/`"closed"` while GraphQL reports
10
- * `"OPEN"`/`"CLOSED"`, and both actions pass their existing issue shapes
11
- * through unchanged. **`LinkedIssueRef.isClosed` is the ONLY sanctioned way
12
- * to test closedness** — it lowercases before comparing, so both spellings
13
- * classify correctly. A hand-written `issue.state === "closed"` comparison
14
- * silently misclassifies GraphQL's `"CLOSED"` as open, which re-links (and on
15
- * merge auto-closes) an issue the release deliberately dropped.
16
- *
17
- * The class carries no instance members, so a plain
18
- * `{ number, title, state }` literal satisfies the type structurally — both
19
- * actions' existing `LinkedIssue` shapes are accepted without mapping.
20
- *
21
- * @public
22
- */
23
- var LinkedIssueRef = class extends Schema.Class("LinkedIssueRef")({
24
- number: Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)),
25
- title: Schema.String,
26
- state: Schema.String
27
- }) {
28
- /**
29
- * Whether an issue is closed, case-insensitively.
30
- *
31
- * @remarks
32
- * The only sanctioned closedness test — see the class remarks for why a
33
- * bare `state === "closed"` comparison is a silent bug against GraphQL
34
- * payloads.
35
- *
36
- * @public
37
- */
38
- static isClosed(issue) {
39
- return issue.state.toLowerCase() === "closed";
40
- }
41
- };
42
-
43
- //#endregion
44
- export { LinkedIssueRef };