@savvy-web/silk-effects 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,10 @@
1
1
  import { ContentStructureRule } from "../remark/rules/content-structure.js";
2
+ import { DependencyTableFormatRule } from "../remark/rules/dependency-table-format.js";
2
3
  import { HeadingHierarchyRule } from "../remark/rules/heading-hierarchy.js";
3
4
  import { RequiredSectionsRule } from "../remark/rules/required-sections.js";
4
5
  import { UncategorizedContentRule } from "../remark/rules/uncategorized-content.js";
5
6
  import { stripFrontmatter } from "../utils/strip-frontmatter.js";
7
+ import remarkGfm from "remark-gfm";
6
8
  import remarkParse from "remark-parse";
7
9
  import remarkStringify from "remark-stringify";
8
10
  import { unified } from "unified";
@@ -13,7 +15,7 @@ import { join } from "node:path";
13
15
  /**
14
16
  * Class-based API wrapper for changeset linting.
15
17
  *
16
- * Provides a static class interface that runs all remark-lint rules
18
+ * Provides a static class interface that runs all five remark-lint rules
17
19
  * against changeset markdown files and returns structured diagnostics.
18
20
  *
19
21
  * @internal
@@ -21,9 +23,9 @@ import { join } from "node:path";
21
23
  /**
22
24
  * Static class for linting changeset markdown files.
23
25
  *
24
- * Runs the four remark-lint rules (heading-hierarchy, required-sections,
25
- * content-structure, uncategorized-content) against changeset markdown
26
- * and returns structured {@link LintMessage} diagnostics.
26
+ * Runs the five remark-lint rules (heading-hierarchy, required-sections,
27
+ * content-structure, dependency-table-format, uncategorized-content) against
28
+ * changeset markdown and returns structured {@link LintMessage} diagnostics.
27
29
  *
28
30
  * @remarks
29
31
  * This class implements the pre-validation layer of the three-layer
@@ -109,7 +111,7 @@ var ChangesetLinter = class ChangesetLinter {
109
111
  *
110
112
  * @remarks
111
113
  * Reads the file synchronously, strips YAML frontmatter, and runs all
112
- * four lint rules. The file path is preserved in each returned
114
+ * five lint rules. The file path is preserved in each returned
113
115
  * {@link LintMessage} for error reporting.
114
116
  *
115
117
  * @param filePath - Absolute or relative path to the changeset `.md` file
@@ -123,7 +125,7 @@ var ChangesetLinter = class ChangesetLinter {
123
125
  * Validate a markdown string directly.
124
126
  *
125
127
  * @remarks
126
- * Strips YAML frontmatter (if present) and runs all four lint rules
128
+ * Strips YAML frontmatter (if present) and runs all five lint rules
127
129
  * against the remaining content. This method is useful for validating
128
130
  * changeset content that is already in memory, such as in test suites
129
131
  * or editor integrations.
@@ -135,7 +137,7 @@ var ChangesetLinter = class ChangesetLinter {
135
137
  */
136
138
  static validateContent(content, filePath = "<input>") {
137
139
  const body = stripFrontmatter(content);
138
- return unified().use(remarkParse).use(remarkStringify).use(HeadingHierarchyRule).use(RequiredSectionsRule).use(ContentStructureRule).use(UncategorizedContentRule).processSync(body).messages.map((msg) => ({
140
+ return unified().use(remarkParse).use(remarkGfm).use(remarkStringify).use(HeadingHierarchyRule).use(RequiredSectionsRule).use(ContentStructureRule).use(DependencyTableFormatRule).use(UncategorizedContentRule).processSync(body).messages.map((msg) => ({
139
141
  file: filePath,
140
142
  /* v8 ignore next 3 -- ruleId/line/column fallbacks; remark-lint always provides these */
141
143
  rule: msg.ruleId ?? msg.source ?? "unknown",
@@ -7,13 +7,14 @@ import { ChangesetOptionsSchema, RepoSchema } from "./schemas/options.js";
7
7
  import { GitHubLive, GitHubService, GitHubServiceBase, makeGitHubTest } from "./services/github.js";
8
8
  import { MarkdownLive, MarkdownService, MarkdownServiceBase } from "./services/markdown.js";
9
9
  import { NonEmptyString, PositiveInteger } from "./schemas/primitives.js";
10
- import { DependencyActionSchema, DependencyTableRowSchema, DependencyTableSchema, DependencyTableTypeSchema, VersionOrEmptySchema } from "./schemas/dependency-table.js";
10
+ import { DependencyActionSchema, DependencyTableRowSchema, DependencyTableSchema, DependencyTableTypeSchema, VERSION_RE, VersionOrEmptySchema } from "./schemas/dependency-table.js";
11
11
  import { serializeDependencyTableToMarkdown } from "./utils/dependency-table.js";
12
12
  import { GitHubInfoSchema, IssueNumberSchema, UrlOrMarkdownLinkSchema, UsernameSchema } from "./schemas/github.js";
13
13
  import changelogFunctions from "./changelog/index.js";
14
14
  import { Changelog } from "./api/changelog.js";
15
15
  import { DependencyTable } from "./api/dependency-table.js";
16
16
  import { ContentStructureRule } from "./remark/rules/content-structure.js";
17
+ import { DependencyTableFormatRule } from "./remark/rules/dependency-table-format.js";
17
18
  import { HeadingHierarchyRule } from "./remark/rules/heading-hierarchy.js";
18
19
  import { RequiredSectionsRule } from "./remark/rules/required-sections.js";
19
20
  import { UncategorizedContentRule } from "./remark/rules/uncategorized-content.js";
@@ -28,24 +29,24 @@ import { ChangelogTransformer } from "./api/transformer.js";
28
29
  import { ClassificationReasonSchema, ClassificationSchema, ConfigInspector, ConfigInspectorBase, ConfigInspectorLive, InspectedConfigSchema, ResolvedPackageScopeSchema, ResolvedVersionFileSchema, makeConfigInspectorTest } from "./services/config-inspector.js";
29
30
  import { BranchAnalysisSchema, BranchAnalyzer, BranchAnalyzerBase, BranchAnalyzerLive, BranchFileEntrySchema, FileStatusSchema, makeBranchAnalyzerTest } from "./services/branch-analyzer.js";
30
31
  import { ChangelogService, ChangelogServiceBase } from "./services/changelog.js";
32
+ import { computeWorkspaceDependencyDiffs } from "./utils/dep-diff.js";
33
+ import { listPublishablePackageNames } from "./utils/publishability.js";
34
+ import { gitMergeBase, snapshotFromWorktree } from "./utils/worktree-snapshot.js";
35
+ import { WorkspaceSnapshotReader, WorkspaceSnapshotReaderBase, WorkspaceSnapshotReaderLive } from "./services/workspace-snapshot.js";
36
+ import { DepsRegen, DepsRegenBase, DepsRegenLive, isPureDependencyChangeset, resolveDiffRows } from "./services/deps-regen.js";
31
37
  import { VersionFiles } from "./utils/version-files.js";
32
38
  import { ReleasePlanner, ReleasePlannerBase, ReleasePlannerLive, makeReleasePlannerTest } from "./services/release-planner.js";
33
- import { WorkspaceSnapshotReader, WorkspaceSnapshotReaderBase, WorkspaceSnapshotReaderLive } from "./services/workspace-snapshot.js";
34
39
  import { SectionCategorySchema } from "./categories/types.js";
35
40
  import { CommitHashSchema, VersionTypeSchema } from "./schemas/git.js";
36
41
  import { ChangesetSchema, ChangesetSummarySchema, DependencyTypeSchema, DependencyUpdateSchema } from "./schemas/changeset.js";
37
42
  import { AppliedReleaseEntrySchema, AppliedReleaseSchema, BumpTypeSchema, ChangesetPreviewSchema, PendingChangesetSchema, PreviewReleaseSchema, VersionFileUpdateRecordSchema } from "./schemas/release-plan.js";
38
- import { computeWorkspaceDependencyDiffs } from "./utils/dep-diff.js";
39
- import { listPublishablePackageNames } from "./utils/publishability.js";
40
- import { gitMergeBase, snapshotFromWorktree } from "./utils/worktree-snapshot.js";
41
43
  import { ContentStructureRule as ContentStructureRule$1 } from "./markdownlint/rules/content-structure.js";
42
- import { DependencyTableFormatRule } from "./markdownlint/rules/dependency-table-format.js";
44
+ import { DependencyTableFormatRule as DependencyTableFormatRule$1 } from "./markdownlint/rules/dependency-table-format.js";
43
45
  import { HeadingHierarchyRule as HeadingHierarchyRule$1 } from "./markdownlint/rules/heading-hierarchy.js";
44
46
  import { RequiredSectionsRule as RequiredSectionsRule$1 } from "./markdownlint/rules/required-sections.js";
45
47
  import { UncategorizedContentRule as UncategorizedContentRule$1 } from "./markdownlint/rules/uncategorized-content.js";
46
48
  import SilkChangesetsRules from "./markdownlint/index.js";
47
49
  import { AggregateDependencyTablesPlugin } from "./remark/plugins/aggregate-dependency-tables.js";
48
- import { DependencyTableFormatRule as DependencyTableFormatRule$1 } from "./remark/rules/dependency-table-format.js";
49
50
  import { SilkChangesetPreset, SilkChangesetTransformPreset } from "./remark/presets.js";
50
51
 
51
52
  //#region src/changesets/index.ts
@@ -84,12 +85,15 @@ var changesets_exports = /* @__PURE__ */ __exportAll({
84
85
  DeduplicateItemsPlugin: () => DeduplicateItemsPlugin,
85
86
  DependencyActionSchema: () => DependencyActionSchema,
86
87
  DependencyTable: () => DependencyTable,
87
- DependencyTableFormatRule: () => DependencyTableFormatRule$1,
88
+ DependencyTableFormatRule: () => DependencyTableFormatRule,
88
89
  DependencyTableRowSchema: () => DependencyTableRowSchema,
89
90
  DependencyTableSchema: () => DependencyTableSchema,
90
91
  DependencyTableTypeSchema: () => DependencyTableTypeSchema,
91
92
  DependencyTypeSchema: () => DependencyTypeSchema,
92
93
  DependencyUpdateSchema: () => DependencyUpdateSchema,
94
+ DepsRegen: () => DepsRegen,
95
+ DepsRegenBase: () => DepsRegenBase,
96
+ DepsRegenLive: () => DepsRegenLive,
93
97
  FileStatusSchema: () => FileStatusSchema,
94
98
  GitError: () => GitError,
95
99
  GitErrorBase: () => GitErrorBase,
@@ -113,7 +117,7 @@ var changesets_exports = /* @__PURE__ */ __exportAll({
113
117
  MarkdownService: () => MarkdownService,
114
118
  MarkdownServiceBase: () => MarkdownServiceBase,
115
119
  MarkdownlintContentStructureRule: () => ContentStructureRule$1,
116
- MarkdownlintDependencyTableFormatRule: () => DependencyTableFormatRule,
120
+ MarkdownlintDependencyTableFormatRule: () => DependencyTableFormatRule$1,
117
121
  MarkdownlintHeadingHierarchyRule: () => HeadingHierarchyRule$1,
118
122
  MarkdownlintRequiredSectionsRule: () => RequiredSectionsRule$1,
119
123
  MarkdownlintUncategorizedContentRule: () => UncategorizedContentRule$1,
@@ -142,6 +146,7 @@ var changesets_exports = /* @__PURE__ */ __exportAll({
142
146
  UncategorizedContentRule: () => UncategorizedContentRule,
143
147
  UrlOrMarkdownLinkSchema: () => UrlOrMarkdownLinkSchema,
144
148
  UsernameSchema: () => UsernameSchema,
149
+ VERSION_RE: () => VERSION_RE,
145
150
  VersionFileConfigSchema: () => VersionFileConfigSchema,
146
151
  VersionFileError: () => VersionFileError,
147
152
  VersionFileErrorBase: () => VersionFileErrorBase,
@@ -156,14 +161,16 @@ var changesets_exports = /* @__PURE__ */ __exportAll({
156
161
  changelogFunctions: () => changelogFunctions,
157
162
  computeWorkspaceDependencyDiffs: () => computeWorkspaceDependencyDiffs,
158
163
  gitMergeBase: () => gitMergeBase,
164
+ isPureDependencyChangeset: () => isPureDependencyChangeset,
159
165
  listPublishablePackageNames: () => listPublishablePackageNames,
160
166
  makeBranchAnalyzerTest: () => makeBranchAnalyzerTest,
161
167
  makeConfigInspectorTest: () => makeConfigInspectorTest,
162
168
  makeGitHubTest: () => makeGitHubTest,
163
169
  makeReleasePlannerTest: () => makeReleasePlannerTest,
170
+ resolveDiffRows: () => resolveDiffRows,
164
171
  serializeDependencyTableToMarkdown: () => serializeDependencyTableToMarkdown,
165
172
  snapshotFromWorktree: () => snapshotFromWorktree
166
173
  });
167
174
 
168
175
  //#endregion
169
- export { AggregateDependencyTablesPlugin, AppliedReleaseEntrySchema, AppliedReleaseSchema, BranchAnalysisSchema, BranchAnalyzer, BranchAnalyzerBase, BranchAnalyzerLive, BranchFileEntrySchema, BumpTypeSchema, Categories, Changelog, ChangelogService, ChangelogServiceBase, ChangelogTransformer, ChangesetLinter, ChangesetOptionsSchema, ChangesetPreviewSchema, ChangesetSchema, ChangesetSummarySchema, ChangesetValidationError, ChangesetValidationErrorBase, ClassificationReasonSchema, ClassificationSchema, CommitHashSchema, ConfigInspector, ConfigInspectorBase, ConfigInspectorLive, ConfigurationError, ConfigurationErrorBase, ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, DependencyActionSchema, DependencyTable, DependencyTableFormatRule$1 as DependencyTableFormatRule, DependencyTableRowSchema, DependencyTableSchema, DependencyTableTypeSchema, DependencyTypeSchema, DependencyUpdateSchema, FileStatusSchema, GitError, GitErrorBase, GitHubApiError, GitHubApiErrorBase, GitHubInfoSchema, GitHubLive, GitHubService, GitHubServiceBase, GlobSchema, HeadingHierarchyRule, InspectedConfigSchema, IssueLinkRefsPlugin, IssueNumberSchema, JsonPathSchema, LegacyVersionFileConfigSchema, LegacyVersionFilesSchema, MarkdownLive, MarkdownParseError, MarkdownParseErrorBase, MarkdownService, MarkdownServiceBase, ContentStructureRule$1 as MarkdownlintContentStructureRule, DependencyTableFormatRule as MarkdownlintDependencyTableFormatRule, HeadingHierarchyRule$1 as MarkdownlintHeadingHierarchyRule, RequiredSectionsRule$1 as MarkdownlintRequiredSectionsRule, UncategorizedContentRule$1 as MarkdownlintUncategorizedContentRule, MergeSectionsPlugin, NonEmptyString, NormalizeFormatPlugin, PackageScopeSchema, PackagesRecordSchema, PendingChangesetSchema, PositiveInteger, PreviewReleaseSchema, ReleasePlanError, ReleasePlanErrorBase, ReleasePlanner, ReleasePlannerBase, ReleasePlannerLive, ReorderSectionsPlugin, RepoSchema, RequiredSectionsRule, ResolvedPackageScopeSchema, ResolvedVersionFileSchema, SectionCategorySchema, SilkChangesetPreset, SilkChangesetTransformPreset, SilkChangesetsRules, UncategorizedContentRule, UrlOrMarkdownLinkSchema, UsernameSchema, VersionFileConfigSchema, VersionFileError, VersionFileErrorBase, VersionFileUpdateRecordSchema, VersionFiles, VersionFilesSchema, VersionOrEmptySchema, VersionTypeSchema, WorkspaceSnapshotReader, WorkspaceSnapshotReaderBase, WorkspaceSnapshotReaderLive, changelogFunctions, changesets_exports, computeWorkspaceDependencyDiffs, gitMergeBase, listPublishablePackageNames, makeBranchAnalyzerTest, makeConfigInspectorTest, makeGitHubTest, makeReleasePlannerTest, serializeDependencyTableToMarkdown, snapshotFromWorktree };
176
+ export { AggregateDependencyTablesPlugin, AppliedReleaseEntrySchema, AppliedReleaseSchema, BranchAnalysisSchema, BranchAnalyzer, BranchAnalyzerBase, BranchAnalyzerLive, BranchFileEntrySchema, BumpTypeSchema, Categories, Changelog, ChangelogService, ChangelogServiceBase, ChangelogTransformer, ChangesetLinter, ChangesetOptionsSchema, ChangesetPreviewSchema, ChangesetSchema, ChangesetSummarySchema, ChangesetValidationError, ChangesetValidationErrorBase, ClassificationReasonSchema, ClassificationSchema, CommitHashSchema, ConfigInspector, ConfigInspectorBase, ConfigInspectorLive, ConfigurationError, ConfigurationErrorBase, ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, DependencyActionSchema, DependencyTable, DependencyTableFormatRule, DependencyTableRowSchema, DependencyTableSchema, DependencyTableTypeSchema, DependencyTypeSchema, DependencyUpdateSchema, DepsRegen, DepsRegenBase, DepsRegenLive, FileStatusSchema, GitError, GitErrorBase, GitHubApiError, GitHubApiErrorBase, GitHubInfoSchema, GitHubLive, GitHubService, GitHubServiceBase, GlobSchema, HeadingHierarchyRule, InspectedConfigSchema, IssueLinkRefsPlugin, IssueNumberSchema, JsonPathSchema, LegacyVersionFileConfigSchema, LegacyVersionFilesSchema, MarkdownLive, MarkdownParseError, MarkdownParseErrorBase, MarkdownService, MarkdownServiceBase, ContentStructureRule$1 as MarkdownlintContentStructureRule, DependencyTableFormatRule$1 as MarkdownlintDependencyTableFormatRule, HeadingHierarchyRule$1 as MarkdownlintHeadingHierarchyRule, RequiredSectionsRule$1 as MarkdownlintRequiredSectionsRule, UncategorizedContentRule$1 as MarkdownlintUncategorizedContentRule, MergeSectionsPlugin, NonEmptyString, NormalizeFormatPlugin, PackageScopeSchema, PackagesRecordSchema, PendingChangesetSchema, PositiveInteger, PreviewReleaseSchema, ReleasePlanError, ReleasePlanErrorBase, ReleasePlanner, ReleasePlannerBase, ReleasePlannerLive, ReorderSectionsPlugin, RepoSchema, RequiredSectionsRule, ResolvedPackageScopeSchema, ResolvedVersionFileSchema, SectionCategorySchema, SilkChangesetPreset, SilkChangesetTransformPreset, SilkChangesetsRules, UncategorizedContentRule, UrlOrMarkdownLinkSchema, UsernameSchema, VERSION_RE, VersionFileConfigSchema, VersionFileError, VersionFileErrorBase, VersionFileUpdateRecordSchema, VersionFiles, VersionFilesSchema, VersionOrEmptySchema, VersionTypeSchema, WorkspaceSnapshotReader, WorkspaceSnapshotReaderBase, WorkspaceSnapshotReaderLive, changelogFunctions, changesets_exports, computeWorkspaceDependencyDiffs, gitMergeBase, isPureDependencyChangeset, listPublishablePackageNames, makeBranchAnalyzerTest, makeConfigInspectorTest, makeGitHubTest, makeReleasePlannerTest, resolveDiffRows, serializeDependencyTableToMarkdown, snapshotFromWorktree };
@@ -1,3 +1,4 @@
1
+ import { VERSION_RE } from "../../schemas/dependency-table.js";
1
2
  import { RULE_DOCS } from "../../constants.js";
2
3
  import { getHeadingLevel, getHeadingText } from "./utils.js";
3
4
 
@@ -23,7 +24,6 @@ const EXPECTED_HEADERS = [
23
24
  "from",
24
25
  "to"
25
26
  ];
26
- const VERSION_RE = /^(\u2014|[~^]?\d+\.\d+\.\d+(?:[-+.][\w.+-]*)?)$/;
27
27
  /**
28
28
  * Extract text from a `tableHeader` or `tableData` cell token.
29
29
  *
@@ -1,4 +1,5 @@
1
1
  import { ContentStructureRule } from "./rules/content-structure.js";
2
+ import { DependencyTableFormatRule } from "./rules/dependency-table-format.js";
2
3
  import { HeadingHierarchyRule } from "./rules/heading-hierarchy.js";
3
4
  import { RequiredSectionsRule } from "./rules/required-sections.js";
4
5
  import { UncategorizedContentRule } from "./rules/uncategorized-content.js";
@@ -9,7 +10,6 @@ import { MergeSectionsPlugin } from "./plugins/merge-sections.js";
9
10
  import { NormalizeFormatPlugin } from "./plugins/normalize-format.js";
10
11
  import { ReorderSectionsPlugin } from "./plugins/reorder-sections.js";
11
12
  import { AggregateDependencyTablesPlugin } from "./plugins/aggregate-dependency-tables.js";
12
- import { DependencyTableFormatRule } from "./rules/dependency-table-format.js";
13
13
 
14
14
  //#region src/changesets/remark/presets.ts
15
15
  /**
@@ -68,6 +68,16 @@ const DependencyActionSchema = Schema.Literal("added", "updated", "removed");
68
68
  */
69
69
  const DependencyTableTypeSchema = Schema.Literal("dependency", "devDependency", "peerDependency", "optionalDependency", "workspace", "config");
70
70
  /**
71
+ * The canonical accepted-value pattern for a dependency-table From/To cell:
72
+ * the em-dash sentinel (U+2014), a bare/`~`/`^` semver, or — as a last-resort
73
+ * fallback when a `catalog:`/`workspace:` specifier could not be resolved to a
74
+ * concrete version — a pnpm protocol string. Non-overlapping alternatives keep
75
+ * this free of polynomial backtracking (CodeQL).
76
+ *
77
+ * @public
78
+ */
79
+ const VERSION_RE = /^(\u2014|[~^]?\d+\.\d+\.\d+(?:[-+.][\w.+-]+)?|(?:catalog|workspace|npm|jsr|file|link|portal):[^\s|]+)$/;
80
+ /**
71
81
  * Version string or em dash (U+2014) sentinel for added/removed entries.
72
82
  *
73
83
  * @remarks
@@ -93,7 +103,7 @@ const DependencyTableTypeSchema = Schema.Literal("dependency", "devDependency",
93
103
  *
94
104
  * @public
95
105
  */
96
- const VersionOrEmptySchema = Schema.String.pipe(Schema.pattern(/^(\u2014|[~^]?\d+\.\d+\.\d+(?:[-+.][\w.+-]*)?)$/));
106
+ const VersionOrEmptySchema = Schema.String.pipe(Schema.pattern(VERSION_RE));
97
107
  /**
98
108
  * Schema for a single dependency table row.
99
109
  *
@@ -185,4 +195,4 @@ const DependencyTableRowSchema = Schema.Struct({
185
195
  const DependencyTableSchema = Schema.Array(DependencyTableRowSchema).pipe(Schema.minItems(1));
186
196
 
187
197
  //#endregion
188
- export { DependencyActionSchema, DependencyTableRowSchema, DependencyTableSchema, DependencyTableTypeSchema, VersionOrEmptySchema };
198
+ export { DependencyActionSchema, DependencyTableRowSchema, DependencyTableSchema, DependencyTableTypeSchema, VERSION_RE, VersionOrEmptySchema };
@@ -0,0 +1,328 @@
1
+ import { serializeDependencyTableToMarkdown, sortDependencyRows } from "../utils/dependency-table.js";
2
+ import { ConfigInspector } from "./config-inspector.js";
3
+ import { computeWorkspaceDependencyDiffs } from "../utils/dep-diff.js";
4
+ import { listPublishablePackageNames } from "../utils/publishability.js";
5
+ import { gitMergeBase, snapshotFromWorktree } from "../utils/worktree-snapshot.js";
6
+ import { WorkspaceSnapshotReader } from "./workspace-snapshot.js";
7
+ import { Context, Effect, Layer, Option } from "effect";
8
+ import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
9
+ import { join, resolve } from "node:path";
10
+ import { CatalogResolver, PublishabilityDetector, WorkspaceDiscovery } from "workspaces-effect";
11
+
12
+ //#region src/changesets/services/deps-regen.ts
13
+ /**
14
+ * `Changesets.DepsRegen` service — lift the `deps regen` / `deps detect`
15
+ * orchestration out of the CLI into a `Context.Tag` service with a
16
+ * `plan()` / `execute()` split.
17
+ *
18
+ * @remarks
19
+ * `plan()` computes the cumulative dependency diff (merge-base → working
20
+ * tree by default, or between two explicit refs), resolves `catalog:` /
21
+ * `workspace:` specifiers to concrete versions, drops `devDependency`
22
+ * rows (unless `includeDevDeps`), and returns a complete {@link RegenPlan}
23
+ * (target filenames + stale-changeset deletes) WITHOUT touching the
24
+ * filesystem. `execute()` applies a plan — writing the fresh changesets
25
+ * first, then deleting the stale pure-dependency ones (so an interrupted
26
+ * run loses nothing and is safely re-runnable).
27
+ *
28
+ * This is the single source of truth for regen/detect: the CLI commands
29
+ * and MCP tools are thin adapters over this service.
30
+ *
31
+ * @see {@link DepsRegen} for the service tag
32
+ * @see {@link DepsRegenLive} for the production layer
33
+ *
34
+ */
35
+ /** The em-dash sentinel (U+2014) used for added ("from") / removed ("to") cells. */
36
+ const EM_DASH = "—";
37
+ /** Whether a From/To cell holds a pnpm protocol specifier that could resolve to a version. */
38
+ const isProtocol = (v) => /^(?:catalog|workspace|npm|jsr|file|link|portal):/.test(v);
39
+ /**
40
+ * Resolve protocol From/To cells to concrete versions (raw-string fallback
41
+ * when unresolved or on resolver error), leave em-dash sentinels untouched,
42
+ * then optionally drop `devDependency` rows, and re-sort.
43
+ *
44
+ * @param diff - One workspace package's dependency-table rows.
45
+ * @param keepDevDeps - When `true`, retain `devDependency` rows; otherwise
46
+ * drop them unconditionally (the regen default).
47
+ * @returns An Effect yielding the transformed {@link WorkspaceDependencyDiff}.
48
+ *
49
+ * @public
50
+ */
51
+ const resolveDiffRows = (diff, keepDevDeps = false) => Effect.gen(function* () {
52
+ const resolver = yield* CatalogResolver;
53
+ const resolveCell = (dep, value) => isProtocol(value) ? resolver.resolveSpecifier(dep, value).pipe(Effect.map((opt) => Option.getOrElse(opt, () => value)), Effect.catchAll((error) => Effect.logWarning(`DepsRegen: catalog resolution failed for "${dep}" (${value}); keeping raw specifier: ${String(error)}`).pipe(Effect.as(value)))) : Effect.succeed(value);
54
+ const rows = [];
55
+ for (const row of diff.rows) {
56
+ if (!keepDevDeps && row.type === "devDependency") continue;
57
+ const from = row.from === EM_DASH ? EM_DASH : yield* resolveCell(row.dependency, row.from);
58
+ const to = row.to === EM_DASH ? EM_DASH : yield* resolveCell(row.dependency, row.to);
59
+ rows.push({
60
+ ...row,
61
+ from,
62
+ to
63
+ });
64
+ }
65
+ return {
66
+ ...diff,
67
+ rows: sortDependencyRows(rows)
68
+ };
69
+ });
70
+ const ADJECTIVES = [
71
+ "brave",
72
+ "clever",
73
+ "swift",
74
+ "silver",
75
+ "lucky",
76
+ "happy",
77
+ "calm",
78
+ "bright",
79
+ "quiet",
80
+ "wild"
81
+ ];
82
+ const NOUNS = [
83
+ "dogs",
84
+ "cats",
85
+ "wolves",
86
+ "foxes",
87
+ "cups",
88
+ "ships",
89
+ "trees",
90
+ "owls",
91
+ "cranes",
92
+ "hills"
93
+ ];
94
+ const VERBS = [
95
+ "laugh",
96
+ "dream",
97
+ "fly",
98
+ "sing",
99
+ "dance",
100
+ "wander",
101
+ "soar",
102
+ "rest",
103
+ "leap",
104
+ "ponder"
105
+ ];
106
+ function pickRandomTriplet() {
107
+ return `${ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)]}-${NOUNS[Math.floor(Math.random() * NOUNS.length)]}-${VERBS[Math.floor(Math.random() * VERBS.length)]}`;
108
+ }
109
+ /**
110
+ * Pick a `<adjective>-<noun>-<verb>` filename slug that does not collide
111
+ * with an existing `.changeset/*.md` OR with a slug already claimed
112
+ * earlier in the same {@link RegenPlan.toWrite} computation. `plan()`
113
+ * never writes to disk, so `existsSync` alone cannot see slugs chosen
114
+ * moments earlier in the same call — the `chosen` set closes that gap.
115
+ * The triplet space is 1,000 combinations, so a busy repo can plausibly
116
+ * exhaust it across runs; fall back to a timestamp suffix after 20
117
+ * unlucky picks.
118
+ *
119
+ * @param changesetDir - Directory checked via `existsSync` for on-disk collisions.
120
+ * @param chosen - Basenames (without extension) already picked within this plan;
121
+ * the picked candidate is added to this set before returning.
122
+ * @internal
123
+ */
124
+ function randomFilename(changesetDir, chosen) {
125
+ for (let i = 0; i < 20; i++) {
126
+ const candidate = pickRandomTriplet();
127
+ if (!chosen.has(candidate) && !existsSync(join(changesetDir, `${candidate}.md`))) {
128
+ chosen.add(candidate);
129
+ return candidate;
130
+ }
131
+ }
132
+ let attempt = 0;
133
+ let fallback = `${pickRandomTriplet()}-${Date.now()}`;
134
+ while (chosen.has(fallback) || existsSync(join(changesetDir, `${fallback}.md`))) fallback = `${pickRandomTriplet()}-${Date.now()}-${++attempt}`;
135
+ chosen.add(fallback);
136
+ return fallback;
137
+ }
138
+ /**
139
+ * Strict detection of "pure dependency changesets" per the documented
140
+ * rules: single-package frontmatter, single `## Dependencies` heading,
141
+ * no other body content beyond that section.
142
+ *
143
+ * @param content - Raw `.changeset/*.md` file contents.
144
+ * @returns `{ isPure, package }` — `isPure` is `true` only for a
145
+ * single-package, Dependencies-only changeset; `package` is the sole
146
+ * frontmatter package name (or `null` when not pure).
147
+ *
148
+ * @public
149
+ */
150
+ function isPureDependencyChangeset(content) {
151
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
152
+ if (!fmMatch) return {
153
+ isPure: false,
154
+ package: null
155
+ };
156
+ const frontmatter = fmMatch[1];
157
+ const body = (fmMatch[2] ?? "").trim();
158
+ const fmLines = frontmatter.split(/\r?\n/).filter((l) => l.trim().length > 0 && !/^\s*#/.test(l));
159
+ if (fmLines.length !== 1) return {
160
+ isPure: false,
161
+ package: null
162
+ };
163
+ const pkgMatch = fmLines[0].match(/^\s*["']?([^"':\s]+)["']?\s*:\s*([a-z]+)\s*$/);
164
+ if (!pkgMatch) return {
165
+ isPure: false,
166
+ package: null
167
+ };
168
+ const pkg = pkgMatch[1];
169
+ const bodyTrimmed = body.replace(/^\s+/, "");
170
+ if (!/^## Dependencies\b/.test(bodyTrimmed)) return {
171
+ isPure: false,
172
+ package: null
173
+ };
174
+ if ((bodyTrimmed.match(/^## /gm) ?? []).length !== 1) return {
175
+ isPure: false,
176
+ package: null
177
+ };
178
+ if (/^# /m.test(bodyTrimmed)) return {
179
+ isPure: false,
180
+ package: null
181
+ };
182
+ return {
183
+ isPure: true,
184
+ package: pkg
185
+ };
186
+ }
187
+ function listChangesetFiles(changesetDir) {
188
+ if (!existsSync(changesetDir)) return [];
189
+ return readdirSync(changesetDir).filter((f) => f.endsWith(".md") && f !== "README.md").map((f) => join(changesetDir, f));
190
+ }
191
+ function findPureDependencyChangesets(changesetDir) {
192
+ const result = [];
193
+ for (const file of listChangesetFiles(changesetDir)) {
194
+ let content;
195
+ try {
196
+ content = readFileSync(file, "utf8");
197
+ } catch {
198
+ continue;
199
+ }
200
+ const detection = isPureDependencyChangeset(content);
201
+ if (detection.isPure && detection.package) result.push({
202
+ file,
203
+ package: detection.package
204
+ });
205
+ }
206
+ return result;
207
+ }
208
+ function findMixedDependencyChangesets(changesetDir) {
209
+ const result = [];
210
+ for (const file of listChangesetFiles(changesetDir)) {
211
+ let content;
212
+ try {
213
+ content = readFileSync(file, "utf8");
214
+ } catch {
215
+ continue;
216
+ }
217
+ if (/^## Dependencies\b/m.test(content) && !isPureDependencyChangeset(content).isPure) result.push(file);
218
+ }
219
+ return result;
220
+ }
221
+ /**
222
+ * Render a single-package, patch-bump changeset for a diff whose rows have
223
+ * already been resolved/filtered by {@link resolveDiffRows}.
224
+ */
225
+ function renderChangesetContent(diff) {
226
+ return `${`---\n"${diff.package}": patch\n---`}\n\n## Dependencies\n\n${serializeDependencyTableToMarkdown([...diff.rows])}\n`;
227
+ }
228
+ const _tag = Context.Tag("Changesets/DepsRegen");
229
+ /**
230
+ * @internal
231
+ */
232
+ const DepsRegenBase = _tag();
233
+ /**
234
+ * Effect service tag for {@link DepsRegenShape}.
235
+ *
236
+ * @example
237
+ * ```typescript
238
+ * import { Effect } from "effect";
239
+ * import { Changesets } from "@savvy-web/silk-effects";
240
+ *
241
+ * const program = Effect.gen(function* () {
242
+ * const svc = yield* Changesets.DepsRegen;
243
+ * const plan = yield* svc.plan({ cwd: process.cwd() });
244
+ * return yield* svc.execute(plan);
245
+ * });
246
+ * ```
247
+ *
248
+ * @public
249
+ */
250
+ var DepsRegen = class extends DepsRegenBase {};
251
+ /**
252
+ * Build a {@link DepsRegenShape} that closes over already-resolved service
253
+ * implementations, keeping the public `plan`/`execute` signatures
254
+ * requirement-free (`R = never`).
255
+ */
256
+ function makeShape(reader, inspector, discovery, resolver, detector) {
257
+ const provideResolver = Layer.succeed(CatalogResolver, resolver);
258
+ const provideDetector = Layer.succeed(PublishabilityDetector, detector);
259
+ const plan = (options) => Effect.gen(function* () {
260
+ const resolvedCwd = resolve(options.cwd);
261
+ const changesetDir = join(resolvedCwd, ".changeset");
262
+ let fromRef = options.from;
263
+ if (!fromRef) {
264
+ let baseBranch = options.base;
265
+ if (!baseBranch) baseBranch = (yield* inspector.inspect(resolvedCwd).pipe(Effect.catchTag("ConfigurationError", () => Effect.succeed({ baseBranch: "main" })))).baseBranch;
266
+ fromRef = yield* gitMergeBase(resolvedCwd, baseBranch);
267
+ }
268
+ const rawDiffs = computeWorkspaceDependencyDiffs(yield* reader.snapshotAt(resolvedCwd, fromRef), options.to ? yield* reader.snapshotAt(resolvedCwd, options.to) : snapshotFromWorktree(resolvedCwd));
269
+ const targetPkg = options.package;
270
+ const publishable = yield* listPublishablePackageNames(yield* discovery.listPackages(resolvedCwd)).pipe(Effect.provide(provideDetector));
271
+ const keepDevDeps = options.includeDevDeps === true;
272
+ const scoped = targetPkg ? rawDiffs.filter((d) => d.package === targetPkg) : rawDiffs.filter((d) => publishable.has(d.package));
273
+ const resolved = [];
274
+ for (const diff of scoped) {
275
+ const next = yield* resolveDiffRows(diff, keepDevDeps).pipe(Effect.provide(provideResolver));
276
+ if (next.rows.length > 0) resolved.push(next);
277
+ }
278
+ const existingPure = findPureDependencyChangesets(changesetDir);
279
+ const skippedMixed = findMixedDependencyChangesets(changesetDir);
280
+ const toDelete = targetPkg ? existingPure.filter((p) => p.package === targetPkg) : existingPure.filter((p) => publishable.has(p.package));
281
+ const chosenFilenames = /* @__PURE__ */ new Set();
282
+ return {
283
+ toDelete,
284
+ toWrite: resolved.map((diff) => ({
285
+ file: join(changesetDir, `${randomFilename(changesetDir, chosenFilenames)}.md`),
286
+ package: diff.package,
287
+ diff
288
+ })),
289
+ skippedMixed
290
+ };
291
+ });
292
+ const execute = (plan) => Effect.sync(() => {
293
+ const deleted = [];
294
+ const written = [];
295
+ for (const entry of plan.toWrite) {
296
+ writeFileSync(entry.file, renderChangesetContent(entry.diff));
297
+ written.push(entry.file);
298
+ }
299
+ for (const entry of plan.toDelete) try {
300
+ unlinkSync(entry.file);
301
+ deleted.push(entry.file);
302
+ } catch {}
303
+ return {
304
+ deleted,
305
+ written,
306
+ skippedMixed: plan.skippedMixed
307
+ };
308
+ });
309
+ return {
310
+ plan,
311
+ execute
312
+ };
313
+ }
314
+ /**
315
+ * Live layer for {@link DepsRegen}.
316
+ *
317
+ * Requires {@link WorkspaceSnapshotReader}, {@link ConfigInspector},
318
+ * `WorkspaceDiscovery`, `CatalogResolver`, and `PublishabilityDetector`
319
+ * (the last three from `workspaces-effect`).
320
+ *
321
+ * @public
322
+ */
323
+ const DepsRegenLive = Layer.effect(DepsRegen, Effect.gen(function* () {
324
+ return makeShape(yield* WorkspaceSnapshotReader, yield* ConfigInspector, yield* WorkspaceDiscovery, yield* CatalogResolver, yield* PublishabilityDetector);
325
+ }));
326
+
327
+ //#endregion
328
+ export { DepsRegen, DepsRegenBase, DepsRegenLive, isPureDependencyChangeset, resolveDiffRows };