@savvy-web/silk-effects 3.3.1 → 4.0.1

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 (72) hide show
  1. package/changesets/changelog/getReleaseLine.js +1 -1
  2. package/changesets/constants.js +0 -18
  3. package/changesets/index.js +10 -24
  4. package/changesets/schemas/changeset.js +8 -4
  5. package/changesets/schemas/dependency-table.js +15 -4
  6. package/changesets/schemas/git.js +7 -2
  7. package/changesets/schemas/github.js +4 -4
  8. package/changesets/schemas/options.js +3 -3
  9. package/changesets/schemas/package-scope.js +2 -5
  10. package/changesets/schemas/primitives.js +2 -2
  11. package/changesets/schemas/release-plan.js +12 -8
  12. package/changesets/schemas/version-files.js +4 -4
  13. package/changesets/services/branch-analyzer.js +60 -191
  14. package/changesets/services/changelog.js +2 -15
  15. package/changesets/services/config-inspector.js +109 -75
  16. package/changesets/services/deps-regen.js +58 -32
  17. package/changesets/services/github.js +3 -16
  18. package/changesets/services/maintenance-reason.js +5 -1
  19. package/changesets/services/markdown.js +3 -16
  20. package/changesets/services/release-planner.js +4 -13
  21. package/changesets/utils/commit-parser.js +0 -18
  22. package/changesets/utils/dep-diff.js +0 -27
  23. package/changesets/utils/git.js +15 -50
  24. package/changesets/utils/issue-refs.js +0 -13
  25. package/changesets/utils/logger.js +0 -13
  26. package/changesets/utils/markdown-link.js +0 -14
  27. package/changesets/utils/publishability.js +11 -24
  28. package/changesets/utils/strip-frontmatter.js +0 -19
  29. package/changesets/utils/version-files.js +85 -51
  30. package/commitlint/config/schema.js +9 -5
  31. package/commitlint/detection/scopes.js +2 -7
  32. package/commitlint/formatter/messages.js +0 -5
  33. package/commitlint/hook/diagnostics/branch.js +12 -12
  34. package/commitlint/hook/diagnostics/open-issues.js +13 -7
  35. package/commitlint/hook/diagnostics/signing.js +30 -37
  36. package/commitlint/hook/envelope.js +2 -8
  37. package/commitlint/hook/silence-logger.js +14 -12
  38. package/index.d.ts +897 -932
  39. package/lint/cli/templates/markdownlint.gen.js +0 -9
  40. package/lint/handlers/PnpmWorkspace.js +26 -12
  41. package/lint/utils/Filter.js +0 -13
  42. package/lint/utils/Workspace.js +10 -6
  43. package/package.json +11 -10
  44. package/repos/index.js +3 -5
  45. package/repos/schemas/manifest.js +7 -13
  46. package/repos/schemas/reports.js +5 -1
  47. package/repos/services/config-store.js +6 -10
  48. package/repos/services/manager.js +52 -112
  49. package/schemas/CommentStyle.js +1 -1
  50. package/schemas/ResolvedTool.js +52 -17
  51. package/schemas/SectionBlock.js +1 -1
  52. package/schemas/SectionDefinition.js +2 -2
  53. package/schemas/TagStrategySchemas.js +1 -1
  54. package/schemas/ToolDefinition.js +1 -1
  55. package/schemas/ToolResults.js +1 -1
  56. package/schemas/VersioningSchemas.js +28 -9
  57. package/schemas/WorkspaceAnalysisSchemas.js +27 -17
  58. package/services/BiomeSchemaSync.js +7 -8
  59. package/services/ChangesetConfig.js +2 -2
  60. package/services/ChangesetConfigReader.js +7 -8
  61. package/services/ConfigDiscovery.js +5 -6
  62. package/services/ManagedSection.js +3 -4
  63. package/services/SilkPublishability.js +38 -15
  64. package/services/SilkWorkspaceAnalyzer.js +7 -9
  65. package/services/TagStrategy.js +3 -3
  66. package/services/ToolDiscovery.js +22 -21
  67. package/services/VersioningStrategy.js +2 -2
  68. package/tsdoc-metadata.json +1 -1
  69. package/turbo/schemas/DryRun.js +8 -14
  70. package/turbo/schemas/results.js +8 -8
  71. package/turbo/services/TurboInspector.js +30 -23
  72. package/utils/ToolCommand.js +38 -12
@@ -1,77 +1,35 @@
1
1
  import { GitError } from "../errors.js";
2
2
  import { ClassificationReasonSchema, ConfigInspector } from "./config-inspector.js";
3
- import { Context, Effect, Layer, Schema } from "effect";
4
- import { execFileSync } from "node:child_process";
3
+ import { Context, Effect, Layer, Option, Schema } from "effect";
4
+ import { Git } from "@effected/git";
5
5
 
6
6
  //#region src/changesets/services/branch-analyzer.ts
7
- /**
8
- * `BranchAnalyzer` service — combine a git diff against the base branch with
9
- * the per-file classification produced by {@link ConfigInspector}.
10
- *
11
- * @remarks
12
- * This is the single call that the changeset-manager agent uses during its
13
- * inventory step: one invocation returns the diff, the per-file package
14
- * attribution, the set of packages affected by the branch, and the list of
15
- * paths that did not map to any known release surface (so the agent can ask
16
- * the user about them rather than silently excluding).
17
- *
18
- * Base-branch resolution order (highest priority first):
19
- *
20
- * 1. Explicit `opts.baseBranch` passed to {@link BranchAnalyzerShape.analyzeBranch}.
21
- * 2. `baseBranch` from `.changeset/config.json` (surfaced via the inspector).
22
- * 3. The branch `origin/HEAD` points to (`git symbolic-ref refs/remotes/origin/HEAD`).
23
- * 4. `"main"` as a final fallback.
24
- *
25
- * Diff resolution covers everything the user might commit before merging:
26
- *
27
- * 1. `git merge-base <base> HEAD` finds the common ancestor.
28
- * 2. `git diff --name-status <merge-base>` (working tree vs merge-base)
29
- * returns every committed, staged, AND unstaged change since the
30
- * branch diverged. The two-arg `<merge-base>...HEAD` form would miss
31
- * work-in-progress — what the user has open in their editor right
32
- * now is exactly the state the agent needs to document.
33
- * 3. `git ls-files --others --exclude-standard` adds untracked files
34
- * (entirely new files not yet `git add`-ed). Each is reported with
35
- * status `"added"`.
36
- *
37
- * The two results are deduped by path before classification. Renames
38
- * are reported as `"renamed"` with the new path; the old path is
39
- * discarded since the classifier needs a single canonical path per
40
- * file.
41
- *
42
- * @see {@link BranchAnalyzer} for the service tag
43
- * @see {@link BranchAnalyzerLive} for the production layer
44
- * @see {@link ConfigInspector} for the underlying classification service
45
- *
46
- */
47
7
  /** Git diff status as reported by `--name-status`. @public */
48
- const FileStatusSchema = Schema.Literal("added", "modified", "deleted", "renamed", "copied", "typechange", "unmerged", "unknown").annotations({ identifier: "FileStatus" });
8
+ const FileStatusSchema = Schema.Literals([
9
+ "added",
10
+ "modified",
11
+ "deleted",
12
+ "renamed",
13
+ "copied",
14
+ "typechange",
15
+ "unmerged",
16
+ "unknown"
17
+ ]).annotate({ identifier: "FileStatus" });
49
18
  /** One file entry in the branch analysis output. @public */
50
19
  const BranchFileEntrySchema = Schema.Struct({
51
- path: Schema.String.annotations({ description: "Repo-relative path (the new path in the case of renames)." }),
20
+ path: Schema.String.annotate({ description: "Repo-relative path (the new path in the case of renames)." }),
52
21
  status: FileStatusSchema,
53
- package: Schema.NullOr(Schema.String).annotations({ description: "Owning package, or null if outside every known release surface." }),
22
+ package: Schema.NullOr(Schema.String).annotate({ description: "Owning package, or null if outside every known release surface." }),
54
23
  reason: ClassificationReasonSchema
55
- }).annotations({ identifier: "BranchFileEntry" });
24
+ }).annotate({ identifier: "BranchFileEntry" });
56
25
  /** Structured result of analyzing the current branch against its base. @public */
57
26
  const BranchAnalysisSchema = Schema.Struct({
58
27
  baseBranch: Schema.String,
59
28
  mergeBaseSha: Schema.String,
60
29
  files: Schema.Array(BranchFileEntrySchema),
61
30
  packagesAffected: Schema.Array(Schema.String),
62
- unmappedFiles: Schema.Array(Schema.String).annotations({ description: "Repo-relative paths whose package is null — candidates for an AskUserQuestion." })
63
- }).annotations({ identifier: "BranchAnalysis" });
64
- const _tag = Context.Tag("BranchAnalyzer");
65
- /**
66
- * Base class for {@link BranchAnalyzer}.
67
- *
68
- * @privateRemarks
69
- * Effect's `Context.Tag` creates an anonymous base class that api-extractor
70
- * cannot follow without an explicit export. Do not delete.
71
- *
72
- * @internal
73
- */
74
- const BranchAnalyzerBase = _tag();
31
+ unmappedFiles: Schema.Array(Schema.String).annotate({ description: "Repo-relative paths whose package is null — candidates for an AskUserQuestion." })
32
+ }).annotate({ identifier: "BranchAnalysis" });
75
33
  /**
76
34
  * Effect service tag for {@link BranchAnalyzerShape}.
77
35
  *
@@ -90,165 +48,74 @@ const BranchAnalyzerBase = _tag();
90
48
  * program.pipe(
91
49
  * Effect.provide(BranchAnalyzerLive),
92
50
  * Effect.provide(ConfigInspectorLive),
93
- * // ... + ChangesetConfigReaderLive + WorkspacesLive + NodeContext.layer
51
+ * // ... + ChangesetConfigReaderLive + kit workspace layers + NodeServices.layer
94
52
  * ),
95
53
  * );
96
54
  * ```
97
55
  *
98
56
  * @public
99
57
  */
100
- var BranchAnalyzer = class extends BranchAnalyzerBase {};
58
+ var BranchAnalyzer = class extends Context.Service()("BranchAnalyzer") {};
101
59
  /**
102
- * Invoke `git` with the given args under `cwd` and return stdout. On
103
- * non-zero exit (or any throw), maps to a {@link GitError} carrying the
104
- * command, cwd, and captured stderr.
60
+ * Fold a `@effected/git` typed failure into this package's {@link GitError},
61
+ * preserving the public `ConfigurationError | GitError` error channel.
105
62
  */
106
- function runGit(cwd, args) {
107
- return Effect.try({
108
- try: () => execFileSync("git", args, {
109
- cwd,
110
- encoding: "utf8",
111
- stdio: [
112
- "ignore",
113
- "pipe",
114
- "pipe"
115
- ]
116
- }),
117
- catch: (error) => {
118
- const e = error;
119
- const stderr = typeof e.stderr === "string" ? e.stderr : e.stderr?.toString() ?? "";
120
- return new GitError({
121
- command: `git ${args.join(" ")}`,
122
- cwd,
123
- reason: stderr.trim() || e.message || String(error)
124
- });
125
- }
126
- });
127
- }
128
- const STATUS_MAP = {
129
- A: "added",
130
- M: "modified",
131
- D: "deleted",
132
- R: "renamed",
133
- C: "copied",
134
- T: "typechange",
135
- U: "unmerged"
136
- };
137
- function statusFromCode(code) {
138
- const head = code.charAt(0);
139
- return STATUS_MAP[head] ?? "unknown";
140
- }
63
+ const toGitError = (command, cwd) => (e) => new GitError({
64
+ command,
65
+ cwd,
66
+ reason: e.message
67
+ });
141
68
  /**
142
- * Parse `git diff --name-status -z` output into one entry per changed file.
143
- *
144
- * `-z` separates fields with NUL bytes and avoids the per-record `\n`
145
- * delimiter, so paths containing spaces or special characters round-trip
146
- * cleanly. Rename and copy entries occupy three NUL-separated tokens
147
- * (status, old path, new path) instead of two; everything else is two.
69
+ * Map `@effected/git`'s `NameStatusEntry` status vocabulary onto this
70
+ * package's public {@link FileStatus} literals. The kit spells
71
+ * `"typeChanged"` where the public schema (stable since 0.x) says
72
+ * `"typechange"`, and adds `"broken"` (git's `B`), which the public schema
73
+ * folds into `"unknown"`.
148
74
  */
149
- function parseNameStatus(output) {
150
- if (output.length === 0) return [];
151
- const tokens = output.split("\0");
152
- if (tokens[tokens.length - 1] === "") tokens.pop();
153
- const entries = [];
154
- for (let i = 0; i < tokens.length;) {
155
- const code = tokens[i] ?? "";
156
- if (code.length === 0) {
157
- /* v8 ignore next 2 -- defensive guard; trailing empties stripped at parse time */
158
- i += 1;
159
- continue;
160
- }
161
- const status = statusFromCode(code);
162
- if (status === "renamed" || status === "copied") {
163
- const newPath = tokens[i + 2] ?? "";
164
- if (newPath.length > 0) entries.push({
165
- path: newPath,
166
- status
167
- });
168
- i += 3;
169
- } else {
170
- const path = tokens[i + 1] ?? "";
171
- if (path.length > 0) entries.push({
172
- path,
173
- status
174
- });
175
- i += 2;
176
- }
75
+ function toFileStatus(status) {
76
+ switch (status) {
77
+ case "typeChanged": return "typechange";
78
+ case "broken": return "unknown";
79
+ default: return status;
177
80
  }
178
- return entries;
179
- }
180
- /**
181
- * Parse a NUL-separated list of paths (e.g., `git ls-files -z` output) into
182
- * an array of strings, dropping any trailing empty entry left by the final
183
- * NUL byte.
184
- */
185
- function parseNulSeparatedPaths(output) {
186
- if (output.length === 0) return [];
187
- const tokens = output.split("\0");
188
- if (tokens[tokens.length - 1] === "") tokens.pop();
189
- return tokens.filter((t) => t.length > 0);
190
81
  }
191
82
  /**
192
83
  * Resolve the base branch using the documented priority order.
193
84
  */
194
- function resolveBaseBranch(opts) {
85
+ function resolveBaseBranch(git, opts) {
195
86
  if (opts.explicit && opts.explicit.length > 0) return Effect.succeed(opts.explicit);
196
87
  if (opts.configBaseBranch && opts.configBaseBranch !== "main") return Effect.succeed(opts.configBaseBranch);
197
- return runGit(opts.cwd, [
198
- "symbolic-ref",
199
- "--quiet",
200
- "--short",
201
- "refs/remotes/origin/HEAD"
202
- ]).pipe(
203
- /* v8 ignore start -- callback only reachable with a remote that exposes origin/HEAD */
204
- Effect.map((stdout) => {
205
- const trimmed = stdout.trim();
206
- return trimmed.length > 0 ? trimmed.replace(/^origin\//, "") : opts.configBaseBranch;
207
- }),
208
- /* v8 ignore stop */
209
- Effect.catchAll(() => Effect.succeed(opts.configBaseBranch))
210
- );
88
+ return git.defaultBranch(opts.cwd).pipe(Effect.map(Option.getOrElse(() => opts.configBaseBranch)), Effect.catch(() => Effect.succeed(opts.configBaseBranch)));
211
89
  }
212
- function makeShape(inspector) {
90
+ function makeShape(inspector, git) {
213
91
  const analyzeBranch = (cwd, opts) => Effect.gen(function* () {
214
92
  const inspected = yield* inspector.inspect(cwd);
215
- const baseBranch = yield* resolveBaseBranch({
93
+ const baseBranch = yield* resolveBaseBranch(git, {
216
94
  explicit: opts?.baseBranch,
217
95
  configBaseBranch: inspected.baseBranch,
218
96
  cwd
219
97
  });
220
- const mergeBaseSha = (yield* runGit(cwd, [
221
- "merge-base",
222
- baseBranch,
223
- "HEAD"
224
- ])).trim();
225
- const diffEntries = parseNameStatus(yield* runGit(cwd, [
226
- "diff",
227
- "--name-status",
228
- "-z",
229
- mergeBaseSha
230
- ]));
231
- const untrackedEntries = parseNulSeparatedPaths(yield* runGit(cwd, [
232
- "ls-files",
233
- "-z",
234
- "--others",
235
- "--exclude-standard"
236
- ])).map((path) => ({
237
- path,
238
- status: "added"
239
- }));
98
+ const mergeBaseSha = (yield* git.mergeBase(cwd, baseBranch, "HEAD").pipe(Effect.mapError(toGitError(`git merge-base ${baseBranch} HEAD`, cwd)))).trim();
99
+ const diffEntries = yield* git.nameStatus(cwd, { base: mergeBaseSha }).pipe(Effect.mapError(toGitError(`git diff --name-status ${mergeBaseSha}`, cwd)));
100
+ const untracked = yield* git.untrackedFiles(cwd).pipe(Effect.mapError(toGitError("git ls-files --others --exclude-standard", cwd)));
240
101
  const isOwnChangeset = (path) => path.startsWith(".changeset/") && path.endsWith(".md");
241
102
  const seen = /* @__PURE__ */ new Set();
242
103
  const rawEntries = [];
243
104
  for (const e of diffEntries) {
244
105
  if (seen.has(e.path) || isOwnChangeset(e.path)) continue;
245
106
  seen.add(e.path);
246
- rawEntries.push(e);
107
+ rawEntries.push({
108
+ path: e.path,
109
+ status: toFileStatus(e.status)
110
+ });
247
111
  }
248
- for (const e of untrackedEntries) {
249
- if (seen.has(e.path) || isOwnChangeset(e.path)) continue;
250
- seen.add(e.path);
251
- rawEntries.push(e);
112
+ for (const path of untracked) {
113
+ if (seen.has(path) || isOwnChangeset(path)) continue;
114
+ seen.add(path);
115
+ rawEntries.push({
116
+ path,
117
+ status: "added"
118
+ });
252
119
  }
253
120
  const paths = rawEntries.map((e) => e.path);
254
121
  const classifications = yield* inspector.classify(cwd, paths);
@@ -275,13 +142,15 @@ function makeShape(inspector) {
275
142
  * Live layer for {@link BranchAnalyzer}.
276
143
  *
277
144
  * Requires {@link ConfigInspector} (which in turn requires
278
- * `ChangesetConfigReader` and `WorkspaceDiscovery`).
145
+ * `ChangesetConfigReader` and `WorkspaceDiscovery`) and a
146
+ * `ChildProcessSpawner` (satisfied by `NodeServices.layer`) for the
147
+ * internally-composed `@effected/git` layer.
279
148
  *
280
149
  * @public
281
150
  */
282
151
  const BranchAnalyzerLive = Layer.effect(BranchAnalyzer, Effect.gen(function* () {
283
- return makeShape(yield* ConfigInspector);
284
- }));
152
+ return makeShape(yield* ConfigInspector, yield* Git);
153
+ })).pipe(Layer.provide(Git.layer));
285
154
  /**
286
155
  * Test factory — build a {@link BranchAnalyzer} that returns a fixed
287
156
  * {@link BranchAnalysis} for any input.
@@ -293,4 +162,4 @@ function makeBranchAnalyzerTest(fixed) {
293
162
  }
294
163
 
295
164
  //#endregion
296
- export { BranchAnalysisSchema, BranchAnalyzer, BranchAnalyzerBase, BranchAnalyzerLive, BranchFileEntrySchema, FileStatusSchema, makeBranchAnalyzerTest };
165
+ export { BranchAnalysisSchema, BranchAnalyzer, BranchAnalyzerLive, BranchFileEntrySchema, FileStatusSchema, makeBranchAnalyzerTest };
@@ -1,18 +1,6 @@
1
1
  import { Context } from "effect";
2
2
 
3
3
  //#region src/changesets/services/changelog.ts
4
- const _tag = Context.Tag("ChangelogService");
5
- /**
6
- * Base class for ChangelogService.
7
- *
8
- * @privateRemarks
9
- * This export is required for api-extractor documentation generation.
10
- * Effect's Context.Tag creates an anonymous base class that must be
11
- * explicitly exported to avoid "forgotten export" warnings. Do not delete.
12
- *
13
- * @internal
14
- */
15
- const ChangelogServiceBase = _tag();
16
4
  /**
17
5
  * Effect service tag for changelog formatting.
18
6
  *
@@ -40,11 +28,10 @@ const ChangelogServiceBase = _tag();
40
28
  * ```
41
29
  *
42
30
  * @see {@link ChangelogServiceShape} for the service interface
43
- * @see {@link ChangelogServiceBase} for the api-extractor base class
44
31
  *
45
32
  * @public
46
33
  */
47
- var ChangelogService = class extends ChangelogServiceBase {};
34
+ var ChangelogService = class extends Context.Service()("ChangelogService") {};
48
35
 
49
36
  //#endregion
50
- export { ChangelogService, ChangelogServiceBase };
37
+ export { ChangelogService };
@@ -2,11 +2,11 @@ import { ConfigurationError } from "../errors.js";
2
2
  import { ChangesetOptionsSchema } from "../schemas/options.js";
3
3
  import { ChangesetConfigReader } from "../../services/ChangesetConfigReader.js";
4
4
  import { SilkPublishability, readTargetsBinding } from "../../services/SilkPublishability.js";
5
- import { Context, Effect, Layer, Schema } from "effect";
5
+ import { Context, Effect, FileSystem, Layer, Path, Result, Schema } from "effect";
6
6
  import { isAbsolute, join, relative, resolve } from "node:path";
7
- import { FileSystem } from "@effect/platform";
8
- import { globSync } from "tinyglobby";
9
- import { WorkspaceDiscovery } from "workspaces-effect";
7
+ import { GlobPattern, GlobPatternOptions } from "@effected/glob";
8
+ import { descend } from "@effected/walker";
9
+ import { WorkspaceDiscovery } from "@effected/workspaces";
10
10
 
11
11
  //#region src/changesets/services/config-inspector.ts
12
12
  /**
@@ -45,9 +45,9 @@ import { WorkspaceDiscovery } from "workspaces-effect";
45
45
  /** A `versionFiles` entry expanded to its absolute target paths. @public */
46
46
  const ResolvedVersionFileSchema = Schema.Struct({
47
47
  glob: Schema.String,
48
- paths: Schema.Array(Schema.String).annotations({ description: "JSONPath expressions to update (defaults to [\"$.version\"])." }),
48
+ paths: Schema.Array(Schema.String).annotate({ description: "JSONPath expressions to update (defaults to [\"$.version\"])." }),
49
49
  matchedFiles: Schema.Array(Schema.String)
50
- }).annotations({ identifier: "ResolvedVersionFile" });
50
+ }).annotate({ identifier: "ResolvedVersionFile" });
51
51
  /** A package's resolved release surface. @public */
52
52
  const ResolvedPackageScopeSchema = Schema.Struct({
53
53
  name: Schema.String,
@@ -56,43 +56,37 @@ const ResolvedPackageScopeSchema = Schema.Struct({
56
56
  additionalScopes: Schema.Array(Schema.String),
57
57
  additionalScopeFiles: Schema.Array(Schema.String),
58
58
  versionFiles: Schema.Array(ResolvedVersionFileSchema)
59
- }).annotations({ identifier: "ResolvedPackageScope" });
59
+ }).annotate({ identifier: "ResolvedPackageScope" });
60
60
  /** Structured representation of a resolved `.changeset/config.json`. @public */
61
61
  const InspectedConfigSchema = Schema.Struct({
62
62
  configPath: Schema.String,
63
- projectDir: Schema.String.annotations({ description: "Absolute project root (the directory containing .changeset/)." }),
63
+ projectDir: Schema.String.annotate({ description: "Absolute project root (the directory containing .changeset/)." }),
64
64
  changelog: Schema.NullOr(Schema.String),
65
65
  baseBranch: Schema.String,
66
- access: Schema.Literal("public", "restricted"),
66
+ access: Schema.Literals(["public", "restricted"]),
67
67
  ignore: Schema.Array(Schema.String),
68
68
  packages: Schema.Array(ResolvedPackageScopeSchema),
69
69
  legacyVersionFilesUsed: Schema.Boolean
70
- }).annotations({ identifier: "InspectedConfig" });
70
+ }).annotate({ identifier: "InspectedConfig" });
71
71
  /** Reason a path was attributed to a package (or left unmapped). @public */
72
- const ClassificationReasonSchema = Schema.Union(Schema.Literal("workspace"), Schema.Struct({
73
- kind: Schema.Literal("additionalScope"),
74
- glob: Schema.String
75
- }), Schema.Struct({
76
- kind: Schema.Literal("versionFile"),
77
- glob: Schema.String
78
- }), Schema.Null).annotations({ identifier: "ClassificationReason" });
72
+ const ClassificationReasonSchema = Schema.Union([
73
+ Schema.Literal("workspace"),
74
+ Schema.Struct({
75
+ kind: Schema.Literal("additionalScope"),
76
+ glob: Schema.String
77
+ }),
78
+ Schema.Struct({
79
+ kind: Schema.Literal("versionFile"),
80
+ glob: Schema.String
81
+ }),
82
+ Schema.Null
83
+ ]).annotate({ identifier: "ClassificationReason" });
79
84
  /** The result of classifying a single path against a resolved config. @public */
80
85
  const ClassificationSchema = Schema.Struct({
81
86
  path: Schema.String,
82
87
  package: Schema.NullOr(Schema.String),
83
88
  reason: ClassificationReasonSchema
84
- }).annotations({ identifier: "Classification" });
85
- const _tag = Context.Tag("ConfigInspector");
86
- /**
87
- * Base class for {@link ConfigInspector}.
88
- *
89
- * @privateRemarks
90
- * Effect's `Context.Tag` creates an anonymous base class that api-extractor
91
- * cannot follow without an explicit export. Do not delete.
92
- *
93
- * @internal
94
- */
95
- const ConfigInspectorBase = _tag();
89
+ }).annotate({ identifier: "Classification" });
96
90
  /**
97
91
  * Effect service tag for {@link ConfigInspectorShape}.
98
92
  *
@@ -112,7 +106,7 @@ const ConfigInspectorBase = _tag();
112
106
  *
113
107
  * @public
114
108
  */
115
- var ConfigInspector = class extends ConfigInspectorBase {};
109
+ var ConfigInspector = class extends Context.Service()("ConfigInspector") {};
116
110
  /**
117
111
  * Pull the changelog formatter ID and its options object out of the raw
118
112
  * `.changeset/config.json` shape (where `changelog` may be a tuple, a string,
@@ -175,16 +169,48 @@ function normalizeLegacyOptions(options, configPath) {
175
169
  legacyUsed: true
176
170
  };
177
171
  }
172
+ /** Match dotfiles, mirroring the former tinyglobby `dot: true` behavior. */
173
+ const GLOB_OPTIONS = GlobPatternOptions.make({ dot: true });
174
+ /**
175
+ * Compile `glob` via `@effected/glob`, folding a compile-guard trip
176
+ * (over-length pattern, brace-expansion budget, nesting depth) into a
177
+ * {@link ConfigurationError} naming the offending glob on the typed channel.
178
+ */
179
+ function compileGlob(glob) {
180
+ return GlobPattern.compile(glob, GLOB_OPTIONS).pipe(Effect.mapError((error) => new ConfigurationError({
181
+ field: "glob",
182
+ reason: `Invalid glob pattern ${JSON.stringify(glob)}: ${error.message}`
183
+ })));
184
+ }
185
+ /**
186
+ * Pure attribution helper: does `glob` (under this service's `dot: true`
187
+ * semantics) match the repo-relative POSIX path `rel`? An uncompilable
188
+ * pattern matches nothing.
189
+ */
190
+ function globMatchesRel(glob, rel) {
191
+ const result = Effect.runSync(Effect.result(GlobPattern.compile(glob, GLOB_OPTIONS)));
192
+ return Result.isSuccess(result) && result.success.matches(rel);
193
+ }
178
194
  /**
179
195
  * Materialize a glob against `cwd` and return the matched file paths as
180
- * repo-relative strings. Honors negation patterns and ignores `node_modules`.
196
+ * repo-relative POSIX strings, sorted by relative path. Matches dotfiles (the
197
+ * former tinyglobby `dot: true` semantics); the walk is `@effected/walker`'s
198
+ * `descend` (literal fast path, `enumerationPrefix` bounding,
199
+ * `node_modules`/`.git` pruning), with `onUnreadable: "skip"` preserving the
200
+ * previous silent-skip policy for unreadable directories. The one remaining
201
+ * `DescendError` (depth cap) folds into a {@link ConfigurationError} naming
202
+ * the glob — this service's single typed failure. `descend`'s `Path`
203
+ * requirement is satisfied internally with the core POSIX `Path.layer` (this
204
+ * module already speaks POSIX-relative match paths).
181
205
  */
182
206
  function materializeGlob(glob, cwd) {
183
- return globSync(glob, {
207
+ return compileGlob(glob).pipe(Effect.flatMap((pattern) => descend(pattern, {
184
208
  cwd,
185
- ignore: ["**/node_modules/**"],
186
- dot: true
187
- });
209
+ onUnreadable: "skip"
210
+ }).pipe(Effect.mapError((error) => new ConfigurationError({
211
+ field: "glob",
212
+ reason: `Failed to materialize glob ${JSON.stringify(glob)}: ${error.message}`
213
+ })), Effect.provide(Path.layer))));
188
214
  }
189
215
  /**
190
216
  * Determine whether `child` is the same directory as `parent` or sits inside
@@ -199,33 +225,41 @@ function isInside(parent, child) {
199
225
  * options with workspace info.
200
226
  */
201
227
  function buildResolvedScopes(params) {
202
- const { options, workspaces, projectDir, configPath } = params;
203
- const packages = options.packages ?? {};
204
- const workspacesByName = new Map(workspaces.map((w) => [w.name, w]));
205
- const scopes = [];
206
- for (const [pkgName, scope] of Object.entries(packages)) {
207
- const ws = workspacesByName.get(pkgName);
208
- if (!ws) throw new ConfigurationError({
209
- field: `packages["${pkgName}"]`,
210
- reason: `Unknown package "${pkgName}" in ${configPath}. Known workspace packages: ${workspaces.map((w) => w.name).join(", ") || "(none)"}.`
211
- });
212
- const additionalScopes = scope.additionalScopes ?? [];
213
- const additionalScopeFiles = additionalScopes.flatMap((g) => materializeGlob(g, projectDir));
214
- const resolvedVersionFiles = (scope.versionFiles ?? []).map((entry) => ({
215
- glob: entry.glob,
216
- paths: entry.paths ?? ["$.version"],
217
- matchedFiles: materializeGlob(entry.glob, projectDir).map((rel) => join(projectDir, rel))
218
- }));
219
- scopes.push({
220
- name: pkgName,
221
- workspaceDir: ws.path,
222
- version: ws.version,
223
- additionalScopes,
224
- additionalScopeFiles: additionalScopeFiles.map((rel) => join(projectDir, rel)),
225
- versionFiles: resolvedVersionFiles
226
- });
227
- }
228
- return scopes;
228
+ return Effect.gen(function* () {
229
+ const { options, workspaces, projectDir, configPath } = params;
230
+ const packages = options.packages ?? {};
231
+ const workspacesByName = new Map(workspaces.map((w) => [w.name, w]));
232
+ const scopes = [];
233
+ for (const [pkgName, scope] of Object.entries(packages)) {
234
+ const ws = workspacesByName.get(pkgName);
235
+ if (!ws) return yield* Effect.fail(new ConfigurationError({
236
+ field: `packages["${pkgName}"]`,
237
+ reason: `Unknown package "${pkgName}" in ${configPath}. Known workspace packages: ${workspaces.map((w) => w.name).join(", ") || "(none)"}.`
238
+ }));
239
+ const additionalScopes = scope.additionalScopes ?? [];
240
+ const additionalScopeFiles = [];
241
+ for (const g of additionalScopes) additionalScopeFiles.push(...yield* materializeGlob(g, projectDir));
242
+ const versionFileEntries = scope.versionFiles ?? [];
243
+ const resolvedVersionFiles = [];
244
+ for (const entry of versionFileEntries) {
245
+ const matched = yield* materializeGlob(entry.glob, projectDir);
246
+ resolvedVersionFiles.push({
247
+ glob: entry.glob,
248
+ paths: entry.paths ?? ["$.version"],
249
+ matchedFiles: matched.map((rel) => join(projectDir, rel))
250
+ });
251
+ }
252
+ scopes.push({
253
+ name: pkgName,
254
+ workspaceDir: ws.path,
255
+ version: ws.version,
256
+ additionalScopes,
257
+ additionalScopeFiles: additionalScopeFiles.map((rel) => join(projectDir, rel)),
258
+ versionFiles: resolvedVersionFiles
259
+ });
260
+ }
261
+ return scopes;
262
+ });
229
263
  }
230
264
  /**
231
265
  * Read a workspace package's raw package.json. A genuinely missing manifest
@@ -237,8 +271,8 @@ function buildResolvedScopes(params) {
237
271
  */
238
272
  function readRawPackageJson(fs, pkgDir) {
239
273
  const pkgJsonPath = join(pkgDir, "package.json");
240
- return fs.readFileString(pkgJsonPath).pipe(Effect.flatMap((content) => Effect.try(() => JSON.parse(content))), Effect.catchAll((err) => {
241
- if (err.reason === "NotFound") return Effect.succeed(null);
274
+ return fs.readFileString(pkgJsonPath).pipe(Effect.flatMap((content) => Effect.try(() => JSON.parse(content))), Effect.catch((err) => {
275
+ if (err.reason?._tag === "NotFound") return Effect.succeed(null);
242
276
  return Effect.fail(new ConfigurationError({
243
277
  field: "workspace",
244
278
  reason: `Failed to read or parse ${pkgJsonPath}: ${err instanceof Error ? err.message : String(err)}`
@@ -356,8 +390,8 @@ function makeShape(reader, discovery, fs) {
356
390
  if (e instanceof ConfigurationError) return yield* Effect.fail(e);
357
391
  throw e;
358
392
  }
359
- const decodedOptions = yield* Schema.decodeUnknown(ChangesetOptionsSchema)(normalized).pipe(Effect.mapError((parseError) => configErrorFromParseError(parseError, configPath)));
360
- const workspaces = (yield* discovery.listPackages(projectDir).pipe(Effect.mapError((err) => new ConfigurationError({
393
+ const decodedOptions = yield* Schema.decodeUnknownEffect(ChangesetOptionsSchema)(normalized).pipe(Effect.mapError((parseError) => configErrorFromParseError(parseError, configPath)));
394
+ const workspaces = (yield* discovery.listPackages().pipe(Effect.mapError((err) => new ConfigurationError({
361
395
  field: "workspace",
362
396
  reason: `Workspace discovery failed for ${projectDir}: ${err.message}`
363
397
  })))).map((w) => ({
@@ -368,14 +402,13 @@ function makeShape(reader, discovery, fs) {
368
402
  const hasExplicitPackages = Object.keys(decodedOptions.packages ?? {}).length > 0;
369
403
  let scopes;
370
404
  if (hasExplicitPackages) {
371
- let explicitScopes;
405
+ const explicitScopes = yield* buildResolvedScopes({
406
+ options: decodedOptions,
407
+ workspaces,
408
+ projectDir,
409
+ configPath
410
+ }).pipe(Effect.provideService(FileSystem.FileSystem, fs));
372
411
  try {
373
- explicitScopes = buildResolvedScopes({
374
- options: decodedOptions,
375
- workspaces,
376
- projectDir,
377
- configPath
378
- });
379
412
  checkConflicts(explicitScopes, workspaces, projectDir, configPath);
380
413
  } catch (e) {
381
414
  if (e instanceof ConfigurationError) return yield* Effect.fail(e);
@@ -431,7 +464,8 @@ function classifyOne(inspected, path) {
431
464
  reason: "workspace"
432
465
  };
433
466
  for (const s of inspected.packages) if (s.additionalScopeFiles.includes(abs)) {
434
- const glob = s.additionalScopes.find((g) => materializeGlob(g, inspected.projectDir).map((rel) => join(inspected.projectDir, rel)).includes(abs));
467
+ const rel = relative(inspected.projectDir, abs).replaceAll("\\", "/");
468
+ const glob = s.additionalScopes.find((g) => globMatchesRel(g, rel));
435
469
  return {
436
470
  path,
437
471
  package: s.name,
@@ -485,4 +519,4 @@ function makeConfigInspectorTest(fixed) {
485
519
  }
486
520
 
487
521
  //#endregion
488
- export { ClassificationReasonSchema, ClassificationSchema, ConfigInspector, ConfigInspectorBase, ConfigInspectorLive, InspectedConfigSchema, ResolvedPackageScopeSchema, ResolvedVersionFileSchema, makeConfigInspectorTest };
522
+ export { ClassificationReasonSchema, ClassificationSchema, ConfigInspector, ConfigInspectorLive, InspectedConfigSchema, ResolvedPackageScopeSchema, ResolvedVersionFileSchema, makeConfigInspectorTest };