@savvy-web/silk-effects 0.6.0 → 1.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.
Files changed (155) hide show
  1. package/README.md +48 -17
  2. package/_virtual/_rolldown/runtime.js +18 -0
  3. package/changesets/api/categories.js +247 -0
  4. package/changesets/api/changelog.js +134 -0
  5. package/changesets/api/dependency-table.js +163 -0
  6. package/changesets/api/linter.js +168 -0
  7. package/changesets/api/transformer.js +140 -0
  8. package/changesets/categories/index.js +299 -0
  9. package/changesets/categories/types.js +66 -0
  10. package/changesets/changelog/formatting.js +119 -0
  11. package/changesets/changelog/getDependencyReleaseLine.js +114 -0
  12. package/changesets/changelog/getReleaseLine.js +122 -0
  13. package/changesets/changelog/index.js +99 -0
  14. package/changesets/constants.js +43 -0
  15. package/changesets/errors.js +305 -0
  16. package/changesets/index.js +146 -0
  17. package/changesets/markdownlint/index.js +29 -0
  18. package/changesets/markdownlint/rules/content-structure.js +98 -0
  19. package/changesets/markdownlint/rules/dependency-table-format.js +170 -0
  20. package/changesets/markdownlint/rules/heading-hierarchy.js +61 -0
  21. package/changesets/markdownlint/rules/required-sections.js +54 -0
  22. package/changesets/markdownlint/rules/uncategorized-content.js +54 -0
  23. package/changesets/markdownlint/rules/utils.js +30 -0
  24. package/changesets/remark/plugins/aggregate-dependency-tables.js +47 -0
  25. package/changesets/remark/plugins/contributor-footnotes.js +123 -0
  26. package/changesets/remark/plugins/deduplicate-items.js +30 -0
  27. package/changesets/remark/plugins/issue-link-refs.js +58 -0
  28. package/changesets/remark/plugins/merge-sections.js +43 -0
  29. package/changesets/remark/plugins/normalize-format.js +47 -0
  30. package/changesets/remark/plugins/reorder-sections.js +34 -0
  31. package/changesets/remark/presets.js +119 -0
  32. package/changesets/remark/rules/content-structure.js +22 -0
  33. package/changesets/remark/rules/dependency-table-format.js +40 -0
  34. package/changesets/remark/rules/heading-hierarchy.js +19 -0
  35. package/changesets/remark/rules/required-sections.js +17 -0
  36. package/changesets/remark/rules/uncategorized-content.js +31 -0
  37. package/changesets/schemas/changeset.js +146 -0
  38. package/changesets/schemas/dependency-table.js +189 -0
  39. package/changesets/schemas/git.js +69 -0
  40. package/changesets/schemas/github.js +175 -0
  41. package/changesets/schemas/options.js +182 -0
  42. package/changesets/schemas/package-scope.js +128 -0
  43. package/changesets/schemas/primitives.js +72 -0
  44. package/changesets/schemas/version-files.js +151 -0
  45. package/changesets/services/branch-analyzer.js +278 -0
  46. package/changesets/services/changelog.js +50 -0
  47. package/changesets/services/config-inspector.js +390 -0
  48. package/changesets/services/github.js +178 -0
  49. package/changesets/services/markdown.js +106 -0
  50. package/changesets/services/workspace-snapshot.js +182 -0
  51. package/changesets/utils/commit-parser.js +80 -0
  52. package/changesets/utils/dep-diff.js +77 -0
  53. package/changesets/utils/dependency-table.js +347 -0
  54. package/changesets/utils/issue-refs.js +101 -0
  55. package/changesets/utils/jsonpath.js +175 -0
  56. package/changesets/utils/logger.js +50 -0
  57. package/changesets/utils/markdown-link.js +57 -0
  58. package/changesets/utils/publishability.js +39 -0
  59. package/changesets/utils/remark-pipeline.js +79 -0
  60. package/changesets/utils/section-parser.js +94 -0
  61. package/changesets/utils/strip-frontmatter.js +46 -0
  62. package/changesets/utils/version-blocks.js +108 -0
  63. package/changesets/utils/version-files.js +336 -0
  64. package/changesets/utils/worktree-snapshot.js +142 -0
  65. package/changesets/vendor/github-info.js +55 -0
  66. package/commitlint/config/factory.js +69 -0
  67. package/commitlint/config/plugins.js +227 -0
  68. package/commitlint/config/rules.js +155 -0
  69. package/commitlint/config/schema.js +46 -0
  70. package/commitlint/detection/dco.js +53 -0
  71. package/commitlint/detection/scopes.js +45 -0
  72. package/commitlint/formatter/format.js +85 -0
  73. package/commitlint/formatter/messages.js +79 -0
  74. package/commitlint/hook/diagnostics/branch.js +36 -0
  75. package/commitlint/hook/diagnostics/cache.js +37 -0
  76. package/commitlint/hook/diagnostics/commitlint-config.js +36 -0
  77. package/commitlint/hook/diagnostics/open-issues.js +56 -0
  78. package/commitlint/hook/diagnostics/package-manager.js +51 -0
  79. package/commitlint/hook/diagnostics/signing.js +107 -0
  80. package/commitlint/hook/envelope.js +46 -0
  81. package/commitlint/hook/output.js +45 -0
  82. package/commitlint/hook/parse-bash-command.js +105 -0
  83. package/commitlint/hook/rules/closes-trailer.js +31 -0
  84. package/commitlint/hook/rules/forbidden-content.js +32 -0
  85. package/commitlint/hook/rules/plan-leakage.js +36 -0
  86. package/commitlint/hook/rules/signing-flag-conflict.js +25 -0
  87. package/commitlint/hook/rules/soft-wrap.js +37 -0
  88. package/commitlint/hook/rules/types.js +14 -0
  89. package/commitlint/hook/rules/verbosity.js +31 -0
  90. package/commitlint/hook/silence-logger.js +39 -0
  91. package/commitlint/index.js +146 -0
  92. package/commitlint/prompt/config.js +91 -0
  93. package/commitlint/prompt/emojis.js +74 -0
  94. package/commitlint/prompt/prompter.js +135 -0
  95. package/commitlint/static.js +73 -0
  96. package/errors/BiomeSyncError.js +21 -0
  97. package/errors/ChangesetConfigError.js +20 -0
  98. package/errors/ConfigNotFoundError.js +21 -0
  99. package/errors/SectionParseError.js +16 -0
  100. package/errors/SectionValidationError.js +16 -0
  101. package/errors/SectionWriteError.js +16 -0
  102. package/errors/TagFormatError.js +20 -0
  103. package/errors/ToolNotFoundError.js +11 -0
  104. package/errors/ToolResolutionError.js +11 -0
  105. package/errors/ToolVersionMismatchError.js +11 -0
  106. package/errors/VersioningDetectionError.js +20 -0
  107. package/errors/WorkspaceAnalysisError.js +21 -0
  108. package/index.d.ts +9743 -8380
  109. package/index.js +36 -6657
  110. package/lint/Handler.js +39 -0
  111. package/lint/cli/sections.js +65 -0
  112. package/lint/cli/templates/markdownlint.gen.js +183 -0
  113. package/lint/config/Preset.js +152 -0
  114. package/lint/config/createConfig.js +89 -0
  115. package/lint/handlers/Biome.js +179 -0
  116. package/lint/handlers/Markdown.js +139 -0
  117. package/lint/handlers/PackageJson.js +130 -0
  118. package/lint/handlers/PnpmWorkspace.js +141 -0
  119. package/lint/handlers/ShellScripts.js +58 -0
  120. package/lint/handlers/TypeScript.js +134 -0
  121. package/lint/handlers/Yaml.js +167 -0
  122. package/lint/index.js +52 -0
  123. package/lint/utils/Command.js +285 -0
  124. package/lint/utils/Filter.js +100 -0
  125. package/lint/utils/Workspace.js +86 -0
  126. package/package.json +52 -63
  127. package/schemas/CommentStyle.js +16 -0
  128. package/schemas/ResolvedTool.js +63 -0
  129. package/schemas/SavvySections.js +113 -0
  130. package/schemas/SectionBlock.js +70 -0
  131. package/schemas/SectionDefinition.js +121 -0
  132. package/schemas/SectionResults.js +12 -0
  133. package/schemas/TagStrategySchemas.js +18 -0
  134. package/schemas/ToolDefinition.js +39 -0
  135. package/schemas/ToolResults.js +14 -0
  136. package/schemas/VersioningSchemas.js +95 -0
  137. package/schemas/WorkspaceAnalysisSchemas.js +190 -0
  138. package/services/BiomeSchemaSync.js +133 -0
  139. package/services/ChangesetConfig.js +78 -0
  140. package/services/ChangesetConfigReader.js +106 -0
  141. package/services/ConfigDiscovery.js +71 -0
  142. package/services/ManagedSection.js +288 -0
  143. package/services/SilkPublishability.js +193 -0
  144. package/services/SilkWorkspaceAnalyzer.js +213 -0
  145. package/services/TagStrategy.js +54 -0
  146. package/services/ToolDiscovery.js +229 -0
  147. package/services/VersioningStrategy.js +67 -0
  148. package/tsdoc-metadata.json +11 -11
  149. package/turbo/digest.js +127 -0
  150. package/turbo/errors.js +48 -0
  151. package/turbo/index.js +32 -0
  152. package/turbo/schemas/DryRun.js +57 -0
  153. package/turbo/schemas/results.js +61 -0
  154. package/turbo/services/TurboInspector.js +100 -0
  155. package/utils/ToolCommand.js +40 -0
@@ -0,0 +1,95 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/VersioningSchemas.ts
4
+ /**
5
+ * Configuration for how private packages are handled during versioning.
6
+ *
7
+ * @remarks
8
+ * When set to `false`, private packages are completely ignored.
9
+ * When set to an object, `tag` and `version` control whether private packages
10
+ * receive git tags and version bumps respectively.
11
+ *
12
+ * @since 0.2.0
13
+ */
14
+ const PrivatePackagesConfig = Schema.Union(Schema.Struct({
15
+ tag: Schema.optional(Schema.Boolean),
16
+ version: Schema.optional(Schema.Boolean)
17
+ }), Schema.Literal(false));
18
+ /**
19
+ * Snapshot release configuration for changesets.
20
+ *
21
+ * @remarks
22
+ * Controls how snapshot versions are generated.
23
+ * `useCalculatedVersion` prepends the calculated version to the snapshot tag.
24
+ * `prereleaseTemplate` is a custom template string for snapshot version format.
25
+ *
26
+ * @since 0.2.0
27
+ */
28
+ const SnapshotConfig = Schema.Struct({
29
+ useCalculatedVersion: Schema.optional(Schema.Boolean),
30
+ prereleaseTemplate: Schema.optional(Schema.String)
31
+ });
32
+ /**
33
+ * Standard changesets configuration matching the `@changesets/config@3.1.1` spec.
34
+ *
35
+ * @remarks
36
+ * Represents the parsed `.changeset/config.json` file. All fields are optional
37
+ * to allow partial configs. Use {@link SilkChangesetConfigFile} when the Silk changelog
38
+ * adapter is detected.
39
+ *
40
+ * @since 0.1.0
41
+ */
42
+ const ChangesetConfigFile = Schema.Struct({
43
+ changelog: Schema.optional(Schema.Union(Schema.String, Schema.Array(Schema.Unknown), Schema.Literal(false))),
44
+ commit: Schema.optional(Schema.Union(Schema.Boolean, Schema.String, Schema.Array(Schema.Unknown))),
45
+ fixed: Schema.optional(Schema.Array(Schema.Array(Schema.String))),
46
+ linked: Schema.optional(Schema.Array(Schema.Array(Schema.String))),
47
+ access: Schema.optional(Schema.Literal("public", "restricted")),
48
+ baseBranch: Schema.optional(Schema.String),
49
+ updateInternalDependencies: Schema.optional(Schema.Literal("patch", "minor", "major")),
50
+ ignore: Schema.optional(Schema.Array(Schema.String)),
51
+ privatePackages: Schema.optional(PrivatePackagesConfig),
52
+ prettier: Schema.optional(Schema.Boolean),
53
+ changedFilePatterns: Schema.optional(Schema.Array(Schema.String)),
54
+ bumpVersionsWithWorkspaceProtocolOnly: Schema.optional(Schema.Boolean),
55
+ snapshot: Schema.optional(SnapshotConfig)
56
+ });
57
+ /**
58
+ * Extended changeset config for repos using the `@savvy-web/changesets` changelog adapter.
59
+ *
60
+ * @remarks
61
+ * Extends {@link ChangesetConfigFile} with a `_isSilk` marker flag that is automatically
62
+ * set to `true`. Detected by {@link ChangesetConfigReader} when the `changelog` field
63
+ * references `@savvy-web/changesets`.
64
+ *
65
+ * @since 0.1.0
66
+ */
67
+ const SilkChangesetConfigFile = Schema.extend(ChangesetConfigFile, Schema.Struct({ _isSilk: Schema.optionalWith(Schema.Boolean, { default: () => true }) }));
68
+ /**
69
+ * Versioning strategy classification for a workspace.
70
+ *
71
+ * @remarks
72
+ * - `"single"` — one publishable package; a single version tag is used.
73
+ * - `"fixed-group"` — all publishable packages are in the same changesets fixed group.
74
+ * - `"independent"` — multiple publishable packages with independent version bumps.
75
+ *
76
+ * @since 0.1.0
77
+ */
78
+ const VersioningStrategyType = Schema.Literal("single", "fixed-group", "independent");
79
+ /**
80
+ * Output of the versioning strategy detection, combining the strategy type with group metadata.
81
+ *
82
+ * @remarks
83
+ * Produced by {@link VersioningStrategy.detect} and consumed by {@link TagStrategy.determine}
84
+ * to decide on the appropriate git-tag format.
85
+ *
86
+ * @since 0.1.0
87
+ */
88
+ const VersioningStrategyResult = Schema.Struct({
89
+ type: VersioningStrategyType,
90
+ fixedGroups: Schema.Array(Schema.Array(Schema.String)),
91
+ publishablePackages: Schema.Array(Schema.String)
92
+ });
93
+
94
+ //#endregion
95
+ export { ChangesetConfigFile, SilkChangesetConfigFile, VersioningStrategyResult };
@@ -0,0 +1,190 @@
1
+ import { ChangesetConfigFile, SilkChangesetConfigFile, VersioningStrategyResult } from "./VersioningSchemas.js";
2
+ import { TagStrategyType } from "./TagStrategySchemas.js";
3
+ import { Equal, Function, Hash, Option, Pretty, Schema } from "effect";
4
+ import { PublishConfig, PublishTarget } from "workspaces-effect";
5
+
6
+ //#region src/schemas/WorkspaceAnalysisSchemas.ts
7
+ const PublishProtocol = Schema.Literal("npm", "jsr");
8
+ const PublishTargetShorthand = Schema.Literal("npm", "github", "jsr");
9
+ const PublishTargetObject = Schema.Struct({
10
+ protocol: Schema.optionalWith(PublishProtocol, { default: () => "npm" }),
11
+ registry: Schema.optional(Schema.String),
12
+ directory: Schema.optional(Schema.String),
13
+ access: Schema.optional(Schema.Literal("public", "restricted")),
14
+ provenance: Schema.optional(Schema.Boolean),
15
+ tag: Schema.optional(Schema.String)
16
+ });
17
+ /**
18
+ * Silk-extended publishConfig schema.
19
+ *
20
+ * @remarks
21
+ * Extends the base PublishConfig from workspaces-effect (which covers npm
22
+ * standard fields: access, registry, directory, tag, linkDirectory) with the
23
+ * Silk `targets` extension for multi-registry publishing.
24
+ *
25
+ * @since 0.2.0
26
+ */
27
+ var SilkPublishConfig = class extends PublishConfig.extend("SilkPublishConfig")({ targets: Schema.optional(Schema.Array(Schema.Union(PublishTargetShorthand, PublishTargetObject))) }) {};
28
+ const KNOWN_REGISTRIES = {
29
+ npm: "https://registry.npmjs.org/",
30
+ github: "https://npm.pkg.github.com/",
31
+ jsr: "https://jsr.io/"
32
+ };
33
+ /**
34
+ * Compare registry URLs ignoring a trailing slash. `SilkPublishability` resolves
35
+ * targets from the bundler's `dist/prod/targets.json` binding, which writes
36
+ * registry endpoints WITHOUT a trailing slash (`https://registry.npmjs.org`),
37
+ * while `KNOWN_REGISTRIES` / `NPM_DEFAULT` use the trailing-slash form. Normalize
38
+ * both sides so `hasTarget`/`targetFor` match regardless of which form a target
39
+ * carries (binding-driven, placeholder, or access-branch fallback).
40
+ */
41
+ const sameRegistry = (a, b) => a.replace(/\/+$/, "") === b.replace(/\/+$/, "");
42
+ const WorkspaceVersion = Schema.Struct({ current: Schema.String });
43
+ /**
44
+ * A fully analyzed workspace with publish targets, versioning status,
45
+ * and release group membership.
46
+ *
47
+ * @since 0.2.0
48
+ */
49
+ var AnalyzedWorkspace = class AnalyzedWorkspace extends Schema.TaggedClass()("AnalyzedWorkspace", {
50
+ name: Schema.String,
51
+ version: WorkspaceVersion,
52
+ path: Schema.String,
53
+ root: Schema.Boolean,
54
+ publishConfig: Schema.NullOr(SilkPublishConfig),
55
+ publishable: Schema.Boolean,
56
+ targets: Schema.Array(PublishTarget),
57
+ versioned: Schema.Boolean,
58
+ tagged: Schema.Boolean,
59
+ released: Schema.Boolean,
60
+ linked: Schema.Array(Schema.suspend(() => AnalyzedWorkspace)),
61
+ fixed: Schema.Array(Schema.suspend(() => AnalyzedWorkspace))
62
+ }) {
63
+ get isRoot() {
64
+ return this.root;
65
+ }
66
+ get isPublishable() {
67
+ return this.publishable;
68
+ }
69
+ get isReleasable() {
70
+ return this.released;
71
+ }
72
+ get isFixed() {
73
+ return this.fixed.length > 0;
74
+ }
75
+ get isLinked() {
76
+ return this.linked.length > 0;
77
+ }
78
+ publishesTo(registry) {
79
+ return this.targets.some((t) => sameRegistry(t.registry, registry));
80
+ }
81
+ hasTarget(shorthand) {
82
+ const registry = KNOWN_REGISTRIES[shorthand];
83
+ return registry !== void 0 && this.publishesTo(registry);
84
+ }
85
+ targetFor(registry) {
86
+ const found = this.targets.find((t) => sameRegistry(t.registry, registry));
87
+ return found ? Option.some(found) : Option.none();
88
+ }
89
+ [Equal.symbol](that) {
90
+ if (!(that instanceof AnalyzedWorkspace)) return false;
91
+ return this.name === that.name && this.path === that.path;
92
+ }
93
+ [Hash.symbol]() {
94
+ return Hash.cached(this)(Hash.combine(Hash.hash(this.name))(Hash.hash(this.path)));
95
+ }
96
+ toString() {
97
+ return `${this.name}@${this.version.current}`;
98
+ }
99
+ toJSON() {
100
+ return {
101
+ _tag: "AnalyzedWorkspace",
102
+ name: this.name,
103
+ version: this.version,
104
+ path: this.path,
105
+ root: this.root,
106
+ publishable: this.publishable,
107
+ targets: this.targets,
108
+ versioned: this.versioned,
109
+ tagged: this.tagged,
110
+ released: this.released
111
+ };
112
+ }
113
+ static publishable(workspaces) {
114
+ return workspaces.filter((w) => w.publishable);
115
+ }
116
+ static releasable(workspaces) {
117
+ return workspaces.filter((w) => w.released);
118
+ }
119
+ static findByName;
120
+ /** Pretty-print an AnalyzedWorkspace instance. */
121
+ static pretty;
122
+ };
123
+ AnalyzedWorkspace.findByName = Function.dual(2, (workspaces, name) => {
124
+ const found = workspaces.find((w) => w.name === name);
125
+ return found ? Option.some(found) : Option.none();
126
+ });
127
+ AnalyzedWorkspace.pretty = Pretty.make(AnalyzedWorkspace);
128
+ const PackageManagerInfo = Schema.Struct({
129
+ type: Schema.Literal("npm", "pnpm", "yarn", "bun"),
130
+ version: Schema.optional(Schema.String)
131
+ });
132
+ /**
133
+ * Full workspace analysis result containing all analyzed workspaces
134
+ * and project-level configuration.
135
+ *
136
+ * @since 0.2.0
137
+ */
138
+ var WorkspaceAnalysis = class WorkspaceAnalysis extends Schema.TaggedClass()("WorkspaceAnalysis", {
139
+ root: Schema.String,
140
+ runtime: Schema.Literal("node", "bun"),
141
+ packageManager: PackageManagerInfo,
142
+ workspaces: Schema.Array(AnalyzedWorkspace),
143
+ changesetConfig: Schema.NullOr(Schema.Union(SilkChangesetConfigFile, ChangesetConfigFile)),
144
+ versioning: Schema.NullOr(VersioningStrategyResult),
145
+ tagStrategy: Schema.NullOr(TagStrategyType)
146
+ }) {
147
+ findWorkspace(name) {
148
+ const found = this.workspaces.find((w) => w.name === name);
149
+ return found ? Option.some(found) : Option.none();
150
+ }
151
+ get rootWorkspace() {
152
+ const root = this.workspaces.find((w) => w.root);
153
+ return root ? Option.some(root) : Option.none();
154
+ }
155
+ get publishableWorkspaces() {
156
+ return this.workspaces.filter((w) => w.publishable);
157
+ }
158
+ get versionedWorkspaces() {
159
+ return this.workspaces.filter((w) => w.versioned);
160
+ }
161
+ get taggedWorkspaces() {
162
+ return this.workspaces.filter((w) => w.tagged);
163
+ }
164
+ get releasableWorkspaces() {
165
+ return this.workspaces.filter((w) => w.released);
166
+ }
167
+ get isSilk() {
168
+ if (this.changesetConfig == null) return false;
169
+ return "_isSilk" in this.changesetConfig && this.changesetConfig._isSilk === true;
170
+ }
171
+ get hasChangesets() {
172
+ return this.changesetConfig != null;
173
+ }
174
+ [Equal.symbol](that) {
175
+ if (!(that instanceof WorkspaceAnalysis)) return false;
176
+ return this.root === that.root;
177
+ }
178
+ [Hash.symbol]() {
179
+ return Hash.cached(this)(Hash.hash(this.root));
180
+ }
181
+ toString() {
182
+ return `WorkspaceAnalysis(${this.root}, ${this.workspaces.length} workspaces)`;
183
+ }
184
+ /** Pretty-print a WorkspaceAnalysis instance. */
185
+ static pretty;
186
+ };
187
+ WorkspaceAnalysis.pretty = Pretty.make(WorkspaceAnalysis);
188
+
189
+ //#endregion
190
+ export { AnalyzedWorkspace, SilkPublishConfig, WorkspaceAnalysis };
@@ -0,0 +1,133 @@
1
+ import { BiomeSyncError } from "../errors/BiomeSyncError.js";
2
+ import { Context, Effect, Layer } from "effect";
3
+ import { FileSystem } from "@effect/platform";
4
+ import { parse } from "jsonc-effect";
5
+
6
+ //#region src/services/BiomeSchemaSync.ts
7
+ /**
8
+ * Strip leading semver range operators from a version string.
9
+ *
10
+ * @param version - Raw version string that may include `^`, `~`, `>=`, `<`, `=`, or `v` prefixes.
11
+ * @returns The bare semver string (e.g. `"1.9.3"`).
12
+ *
13
+ * @since 0.1.0
14
+ */
15
+ function extractSemver(version) {
16
+ return version.replace(/^[\^~>=<v]+/, "");
17
+ }
18
+ /**
19
+ * Build the expected Biome JSON schema URL for a given version.
20
+ *
21
+ * @param version - Bare semver string (e.g. `"1.9.3"`).
22
+ * @returns The canonical `biomejs.dev` schema URL for that version.
23
+ *
24
+ * @since 0.1.0
25
+ */
26
+ function buildSchemaUrl(version) {
27
+ return `https://biomejs.dev/schemas/${version}/schema.json`;
28
+ }
29
+ const BIOME_SCHEMA_HOSTNAME = "biomejs.dev";
30
+ /** Check which biome config files exist in cwd. */
31
+ function findBiomeConfigs(cwd, fs) {
32
+ const candidates = [`${cwd}/biome.json`, `${cwd}/biome.jsonc`];
33
+ return Effect.gen(function* () {
34
+ const results = [];
35
+ for (const candidate of candidates) if (yield* fs.exists(candidate).pipe(Effect.orElseSucceed(() => false))) results.push(candidate);
36
+ return results;
37
+ });
38
+ }
39
+ /**
40
+ * Service that keeps the `$schema` URL in Biome config files in sync with a target version.
41
+ *
42
+ * @remarks
43
+ * Locates `biome.json` and `biome.jsonc` files in the working directory, then compares
44
+ * each file's `$schema` field against the expected `biomejs.dev` URL for the given version.
45
+ * `sync` writes updates in-place; `check` returns the same result without modifying files.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const result = await Effect.runPromise(
50
+ * Effect.gen(function* () {
51
+ * const syncer = yield* BiomeSchemaSync;
52
+ * return yield* syncer.sync("^1.9.3");
53
+ * }).pipe(
54
+ * Effect.provide(BiomeSchemaSyncLive),
55
+ * Effect.provide(NodeContext.layer),
56
+ * )
57
+ * );
58
+ * ```
59
+ *
60
+ * @since 0.1.0
61
+ */
62
+ var BiomeSchemaSync = class extends Context.Tag("@savvy-web/silk-effects/BiomeSchemaSync")() {};
63
+ /**
64
+ * Live implementation of {@link BiomeSchemaSync}.
65
+ *
66
+ * @remarks
67
+ * Requires `FileSystem` from `@effect/platform`. Provide `NodeContext.layer` or
68
+ * `BunContext.layer` to satisfy this dependency.
69
+ *
70
+ * @since 0.1.0
71
+ */
72
+ const BiomeSchemaSyncLive = Layer.effect(BiomeSchemaSync, Effect.gen(function* () {
73
+ const fs = yield* FileSystem.FileSystem;
74
+ const run = (version, options, write) => Effect.gen(function* () {
75
+ const cwd = options?.cwd ?? process.cwd();
76
+ const expectedUrl = buildSchemaUrl(extractSemver(version));
77
+ const configs = yield* findBiomeConfigs(cwd, fs);
78
+ const updated = [];
79
+ const skipped = [];
80
+ const current = [];
81
+ for (const configPath of configs) {
82
+ const raw = yield* fs.readFileString(configPath).pipe(Effect.mapError(
83
+ /* v8 ignore next 4 -- error path requires a filesystem read failure */
84
+ (cause) => new BiomeSyncError({
85
+ path: configPath,
86
+ reason: String(cause)
87
+ })
88
+ ));
89
+ const schema = (yield* parse(raw).pipe(Effect.mapError(
90
+ /* v8 ignore next 4 -- error path requires a JSONC parse failure */
91
+ (e) => new BiomeSyncError({
92
+ path: configPath,
93
+ reason: `Failed to parse JSONC: ${String(e)}`
94
+ })
95
+ ))).$schema;
96
+ if (typeof schema !== "string") {
97
+ skipped.push(configPath);
98
+ continue;
99
+ }
100
+ if (!schema.includes(BIOME_SCHEMA_HOSTNAME)) {
101
+ skipped.push(configPath);
102
+ continue;
103
+ }
104
+ if (schema === expectedUrl) {
105
+ current.push(configPath);
106
+ continue;
107
+ }
108
+ if (write) {
109
+ const updated_content = raw.replaceAll(schema, expectedUrl);
110
+ yield* fs.writeFileString(configPath, updated_content).pipe(Effect.mapError(
111
+ /* v8 ignore next 4 -- error path requires a filesystem write failure */
112
+ (cause) => new BiomeSyncError({
113
+ path: configPath,
114
+ reason: String(cause)
115
+ })
116
+ ));
117
+ }
118
+ updated.push(configPath);
119
+ }
120
+ return {
121
+ updated,
122
+ skipped,
123
+ current
124
+ };
125
+ });
126
+ return {
127
+ sync: (version, options) => run(version, options, true),
128
+ check: (version, options) => run(version, options, false)
129
+ };
130
+ }));
131
+
132
+ //#endregion
133
+ export { BiomeSchemaSync, BiomeSchemaSyncLive, buildSchemaUrl, extractSemver };
@@ -0,0 +1,78 @@
1
+ import { ChangesetConfigReader } from "./ChangesetConfigReader.js";
2
+ import { Context, Effect, Layer, Option } from "effect";
3
+
4
+ //#region src/services/ChangesetConfig.ts
5
+ /**
6
+ * Accessor service over a workspace root's `.changeset/config.json`.
7
+ *
8
+ * @remarks
9
+ * Reads through {@link ChangesetConfigReader} (FileSystem-based) with a per-root cache.
10
+ * Every accessor is total (error channel `never`): a missing or unreadable config collapses
11
+ * to `mode: "none"` and empty/false defaults.
12
+ *
13
+ * @since 0.4.0
14
+ */
15
+ var ChangesetConfig = class extends Context.Tag("@savvy-web/silk-effects/ChangesetConfig")() {
16
+ /**
17
+ * The one ignore matcher: exact name match, or `@scope/*` wildcard.
18
+ *
19
+ * `"@scope/*"` matches `"@scope/anything"` (prefix kept includes the trailing slash),
20
+ * but not the bare scope `"@scope"`.
21
+ */
22
+ static matches(name, pattern) {
23
+ if (pattern.endsWith("/*")) {
24
+ const prefix = pattern.slice(0, -1);
25
+ return name.startsWith(prefix);
26
+ }
27
+ return name === pattern;
28
+ }
29
+ };
30
+ const isSilk = (cfg) => "_isSilk" in cfg && cfg._isSilk === true;
31
+ /**
32
+ * Live {@link ChangesetConfig} reading via {@link ChangesetConfigReader}, cached per root.
33
+ *
34
+ * @remarks
35
+ * Requires `ChangesetConfigReader` (which requires `FileSystem`). Provide
36
+ * `ChangesetConfigReaderLive` + a platform layer (`NodeContext.layer`).
37
+ *
38
+ * @since 0.4.0
39
+ */
40
+ const ChangesetConfigLive = Layer.effect(ChangesetConfig, Effect.gen(function* () {
41
+ const reader = yield* ChangesetConfigReader;
42
+ const cache = /* @__PURE__ */ new Map();
43
+ const read = (root) => Effect.gen(function* () {
44
+ const hit = cache.get(root);
45
+ if (hit !== void 0) return hit;
46
+ const result = yield* reader.read(root).pipe(Effect.option);
47
+ cache.set(root, result);
48
+ return result;
49
+ });
50
+ return {
51
+ mode: (root) => read(root).pipe(Effect.map(Option.match({
52
+ onNone: () => "none",
53
+ onSome: (cfg) => isSilk(cfg) ? "silk" : "vanilla"
54
+ }))),
55
+ versionPrivate: (root) => read(root).pipe(Effect.map(Option.match({
56
+ onNone: () => false,
57
+ onSome: (cfg) => {
58
+ const pp = cfg.privatePackages;
59
+ return pp !== void 0 && pp !== false && pp.version === true;
60
+ }
61
+ }))),
62
+ ignorePatterns: (root) => read(root).pipe(Effect.map(Option.match({
63
+ onNone: () => [],
64
+ onSome: (cfg) => cfg.ignore ?? []
65
+ }))),
66
+ isIgnored: (name, root) => read(root).pipe(Effect.map(Option.match({
67
+ onNone: () => false,
68
+ onSome: (cfg) => (cfg.ignore ?? []).some((p) => ChangesetConfig.matches(name, p))
69
+ }))),
70
+ fixed: (root) => read(root).pipe(Effect.map(Option.match({
71
+ onNone: () => [],
72
+ onSome: (cfg) => cfg.fixed ?? []
73
+ })))
74
+ };
75
+ }));
76
+
77
+ //#endregion
78
+ export { ChangesetConfig, ChangesetConfigLive };
@@ -0,0 +1,106 @@
1
+ import { ChangesetConfigError } from "../errors/ChangesetConfigError.js";
2
+ import { ChangesetConfigFile, SilkChangesetConfigFile } from "../schemas/VersioningSchemas.js";
3
+ import { Context, Effect, Layer, Schema } from "effect";
4
+ import { FileSystem } from "@effect/platform";
5
+
6
+ //#region src/services/ChangesetConfigReader.ts
7
+ /**
8
+ * Substrings that identify a Silk changelog adapter entry in `.changeset/config.json`.
9
+ *
10
+ * Both the standalone package (`@savvy-web/changesets/changelog`) and the
11
+ * consolidated Silk re-export (`@savvy-web/silk/changesets/changelog`) are Silk
12
+ * adapters. The `/silk/` segment in the consolidated path breaks the original
13
+ * `@savvy-web/changesets` substring, so each form needs its own marker.
14
+ */
15
+ const SILK_CHANGELOG_MARKERS = ["@savvy-web/changesets", "@savvy-web/silk/changesets"];
16
+ function matchesSilkMarker(value) {
17
+ return SILK_CHANGELOG_MARKERS.some((marker) => value.includes(marker));
18
+ }
19
+ function isSilkChangelog(changelog) {
20
+ if (typeof changelog === "string") return matchesSilkMarker(changelog);
21
+ if (Array.isArray(changelog) && changelog.length > 0) return typeof changelog[0] === "string" && matchesSilkMarker(changelog[0]);
22
+ return false;
23
+ }
24
+ /**
25
+ * Service that reads and decodes the `.changeset/config.json` for a given workspace root.
26
+ *
27
+ * @remarks
28
+ * Automatically detects whether the config uses the Silk changelog adapter
29
+ * (`@savvy-web/changesets`) and decodes as {@link SilkChangesetConfigFile} or the
30
+ * standard {@link ChangesetConfigFile} accordingly.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const result = await Effect.runPromise(
35
+ * Effect.gen(function* () {
36
+ * const reader = yield* ChangesetConfigReader;
37
+ * return yield* reader.read(process.cwd());
38
+ * }).pipe(
39
+ * Effect.provide(ChangesetConfigReaderLive),
40
+ * Effect.provide(NodeContext.layer),
41
+ * )
42
+ * );
43
+ * ```
44
+ *
45
+ * @since 0.1.0
46
+ */
47
+ var ChangesetConfigReader = class extends Context.Tag("@savvy-web/silk-effects/ChangesetConfigReader")() {};
48
+ /**
49
+ * Live implementation of {@link ChangesetConfigReader}.
50
+ *
51
+ * @remarks
52
+ * Requires `FileSystem` from `@effect/platform`. Provide `NodeContext.layer` or
53
+ * `BunContext.layer` to satisfy this dependency.
54
+ *
55
+ * @since 0.1.0
56
+ */
57
+ const ChangesetConfigReaderLive = Layer.effect(ChangesetConfigReader, Effect.gen(function* () {
58
+ const fs = yield* FileSystem.FileSystem;
59
+ const read = (root) => {
60
+ const configPath = `${root}/.changeset/config.json`;
61
+ return Effect.gen(function* () {
62
+ if (!(yield* fs.exists(configPath).pipe(Effect.mapError(
63
+ /* v8 ignore next 4 -- error path requires fs.exists to fail */
64
+ (cause) => new ChangesetConfigError({
65
+ path: configPath,
66
+ reason: String(cause)
67
+ })
68
+ )))) return yield* Effect.fail(new ChangesetConfigError({
69
+ path: configPath,
70
+ reason: "File not found"
71
+ }));
72
+ const raw = yield* fs.readFileString(configPath).pipe(Effect.mapError(
73
+ /* v8 ignore next 4 -- error path requires fs.readFileString to fail */
74
+ (cause) => new ChangesetConfigError({
75
+ path: configPath,
76
+ reason: String(cause)
77
+ })
78
+ ));
79
+ const parsed = yield* Effect.try({
80
+ try: () => JSON.parse(raw),
81
+ catch: (cause) => new ChangesetConfigError({
82
+ path: configPath,
83
+ reason: `Invalid JSON: ${String(cause)}`
84
+ })
85
+ });
86
+ if (isSilkChangelog(parsed.changelog)) return yield* Schema.decodeUnknown(SilkChangesetConfigFile)(parsed).pipe(Effect.mapError(
87
+ /* v8 ignore next 4 -- error path requires schema decode failure */
88
+ (cause) => new ChangesetConfigError({
89
+ path: configPath,
90
+ reason: `Schema decode failed: ${String(cause)}`
91
+ })
92
+ ));
93
+ return yield* Schema.decodeUnknown(ChangesetConfigFile)(parsed).pipe(Effect.mapError(
94
+ /* v8 ignore next 4 -- error path requires schema decode failure */
95
+ (cause) => new ChangesetConfigError({
96
+ path: configPath,
97
+ reason: `Schema decode failed: ${String(cause)}`
98
+ })
99
+ ));
100
+ });
101
+ };
102
+ return { read };
103
+ }));
104
+
105
+ //#endregion
106
+ export { ChangesetConfigReader, ChangesetConfigReaderLive };
@@ -0,0 +1,71 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import { FileSystem } from "@effect/platform";
3
+
4
+ //#region src/services/ConfigDiscovery.ts
5
+ /**
6
+ * Service that locates named config files within a workspace using priority-ordered search paths.
7
+ *
8
+ * @remarks
9
+ * Search priority (highest to lowest):
10
+ * 1. `lib/configs/{name}` — shared config provided by a dependency package.
11
+ * 2. `{cwd}/{name}` — local override at the workspace root.
12
+ *
13
+ * Missing files are silently skipped; only files that actually exist are returned.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const result = await Effect.runPromise(
18
+ * Effect.gen(function* () {
19
+ * const discovery = yield* ConfigDiscovery;
20
+ * return yield* discovery.find("biome.json");
21
+ * }).pipe(
22
+ * Effect.provide(ConfigDiscoveryLive),
23
+ * Effect.provide(NodeContext.layer),
24
+ * )
25
+ * );
26
+ * ```
27
+ *
28
+ * @since 0.1.0
29
+ */
30
+ var ConfigDiscovery = class extends Context.Tag("@savvy-web/silk-effects/ConfigDiscovery")() {};
31
+ /**
32
+ * Check whether a path exists, treating any PlatformError as "not found".
33
+ */
34
+ function safeExists(fs, path) {
35
+ return fs.exists(path).pipe(Effect.orElseSucceed(() => false));
36
+ }
37
+ /**
38
+ * Live implementation of {@link ConfigDiscovery}.
39
+ *
40
+ * @remarks
41
+ * Requires `FileSystem` from `@effect/platform`. Provide `NodeContext.layer` or
42
+ * `BunContext.layer` to satisfy this dependency.
43
+ *
44
+ * @since 0.1.0
45
+ */
46
+ const ConfigDiscoveryLive = Layer.effect(ConfigDiscovery, Effect.gen(function* () {
47
+ const fs = yield* FileSystem.FileSystem;
48
+ const findAll = (name, options) => Effect.gen(function* () {
49
+ const cwd = options?.cwd ?? process.cwd();
50
+ const results = [];
51
+ const libPath = `${cwd}/lib/configs/${name}`;
52
+ if (yield* safeExists(fs, libPath)) results.push({
53
+ path: libPath,
54
+ source: "lib"
55
+ });
56
+ const rootPath = `${cwd}/${name}`;
57
+ if (yield* safeExists(fs, rootPath)) results.push({
58
+ path: rootPath,
59
+ source: "root"
60
+ });
61
+ return results;
62
+ });
63
+ const find = (name, options) => findAll(name, options).pipe(Effect.map((results) => results[0] ?? null));
64
+ return {
65
+ find,
66
+ findAll
67
+ };
68
+ }));
69
+
70
+ //#endregion
71
+ export { ConfigDiscovery, ConfigDiscoveryLive };