@savvy-web/silk-effects 4.2.6 → 5.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 (40) hide show
  1. package/README.md +73 -192
  2. package/changesets/api/changelog.js +1 -2
  3. package/changesets/changelog/index.js +8 -10
  4. package/changesets/errors.js +0 -1
  5. package/changesets/index.js +1 -4
  6. package/changesets/services/changelog.js +1 -1
  7. package/changesets/services/github.js +1 -1
  8. package/changesets/utils/dependency-table.js +1 -1
  9. package/index.d.ts +95 -1398
  10. package/index.js +1 -20
  11. package/lint/cli/sections.js +9 -8
  12. package/package.json +5 -3
  13. package/schemas/SavvySections.js +26 -9
  14. package/schemas/VersioningSchemas.js +1 -32
  15. package/schemas/WorkspaceAnalysisSchemas.js +4 -5
  16. package/services/SilkPublishability.js +1 -1
  17. package/services/SilkWorkspaceAnalyzer.js +13 -16
  18. package/turbo/services/TurboInspector.js +11 -11
  19. package/changesets/services/markdown.js +0 -93
  20. package/errors/SectionParseError.js +0 -17
  21. package/errors/SectionValidationError.js +0 -17
  22. package/errors/SectionWriteError.js +0 -17
  23. package/errors/TagFormatError.js +0 -21
  24. package/errors/ToolNotFoundError.js +0 -12
  25. package/errors/ToolResolutionError.js +0 -12
  26. package/errors/ToolVersionMismatchError.js +0 -12
  27. package/errors/VersioningDetectionError.js +0 -21
  28. package/schemas/CommentStyle.js +0 -17
  29. package/schemas/ResolvedTool.js +0 -99
  30. package/schemas/SectionBlock.js +0 -71
  31. package/schemas/SectionDefinition.js +0 -123
  32. package/schemas/SectionResults.js +0 -21
  33. package/schemas/TagStrategySchemas.js +0 -19
  34. package/schemas/ToolDefinition.js +0 -40
  35. package/schemas/ToolResults.js +0 -28
  36. package/services/ManagedSection.js +0 -289
  37. package/services/TagStrategy.js +0 -56
  38. package/services/ToolDiscovery.js +0 -232
  39. package/services/VersioningStrategy.js +0 -69
  40. package/utils/ToolCommand.js +0 -67
@@ -1,56 +0,0 @@
1
- import { TagFormatError } from "../errors/TagFormatError.js";
2
- import { Context, Effect, Layer } from "effect";
3
-
4
- //#region src/services/TagStrategy.ts
5
- /**
6
- * Service that determines and applies the git-tag naming strategy for a release.
7
- *
8
- * @remarks
9
- * Consumes a {@link (VersioningStrategyResult:type)} to pick between `"single"` and `"scoped"`
10
- * tag formats, then formats tag strings accordingly. Independent versioning always
11
- * produces scoped tags; single and fixed-group versioning produces a single shared tag.
12
- *
13
- * @example
14
- * ```typescript
15
- * const result = await Effect.runPromise(
16
- * Effect.gen(function* () {
17
- * const tags = yield* TagStrategy;
18
- * const strategyType = yield* tags.determine({ type: "independent", fixedGroups: [], publishablePackages: [] });
19
- * return yield* tags.formatTag("@my-org/pkg", "1.2.3", strategyType);
20
- * }).pipe(Effect.provide(TagStrategyLive))
21
- * );
22
- * // => "@my-org/pkg@1.2.3"
23
- * ```
24
- *
25
- * @since 0.1.0
26
- * @public
27
- */
28
- var TagStrategy = class extends Context.Service()("@savvy-web/silk-effects/TagStrategy") {};
29
- /**
30
- * Live implementation of {@link TagStrategy} with no external dependencies.
31
- *
32
- * @remarks
33
- * All logic is pure: strategy determination and tag formatting involve no I/O.
34
- *
35
- * @since 0.1.0
36
- * @public
37
- */
38
- const TagStrategyLive = Layer.succeed(TagStrategy, {
39
- determine: (versioningResult) => {
40
- if (versioningResult.type === "independent") return Effect.succeed("scoped");
41
- return Effect.succeed("single");
42
- },
43
- formatTag: (name, version, strategy) => {
44
- if (version === "") return Effect.fail(new TagFormatError({
45
- name,
46
- version,
47
- reason: "version cannot be empty"
48
- }));
49
- if (strategy === "single") return Effect.succeed(version);
50
- if (name.startsWith("@")) return Effect.succeed(`${name}@${version}`);
51
- return Effect.succeed(`${name}@${version}`);
52
- }
53
- });
54
-
55
- //#endregion
56
- export { TagStrategy, TagStrategyLive };
@@ -1,232 +0,0 @@
1
- import { ToolNotFoundError } from "../errors/ToolNotFoundError.js";
2
- import { ToolResolutionError } from "../errors/ToolResolutionError.js";
3
- import { ResolvedTool } from "../schemas/ResolvedTool.js";
4
- import { Context, Effect, Layer, Option, Ref } from "effect";
5
- import { PackageManagerDetector, WorkspaceRoot } from "@effected/workspaces";
6
- import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
7
-
8
- //#region src/services/ToolDiscovery.ts
9
- /**
10
- * Service that resolves CLI tools — locating them globally (PATH) or locally
11
- * (via package manager), extracting versions, enforcing source and version
12
- * constraints, and caching results.
13
- *
14
- * @example
15
- * ```typescript
16
- * const result = await Effect.runPromise(
17
- * Effect.gen(function* () {
18
- * const td = yield* ToolDiscovery;
19
- * return yield* td.resolve(
20
- * ToolDefinition.make({ name: "biome" })
21
- * );
22
- * }).pipe(
23
- * Effect.provide(ToolDiscoveryLive),
24
- * Effect.provide(NodeServices.layer),
25
- * )
26
- * );
27
- * ```
28
- *
29
- * @since 0.2.0
30
- * @public
31
- */
32
- var ToolDiscovery = class extends Context.Service()("@savvy-web/silk-effects/ToolDiscovery") {};
33
- /**
34
- * Build the PM exec prefix for running a local binary.
35
- */
36
- function pmExecArgs(pmType, name) {
37
- switch (pmType) {
38
- case "pnpm": return [
39
- "pnpm",
40
- "exec",
41
- name
42
- ];
43
- case "npm": return [
44
- "npx",
45
- "--no",
46
- "--",
47
- name
48
- ];
49
- case "yarn": return [
50
- "yarn",
51
- "exec",
52
- name
53
- ];
54
- case "bun": return [
55
- "bun",
56
- "x",
57
- "--no-install",
58
- name
59
- ];
60
- }
61
- }
62
- /**
63
- * Run a command and return its stdout, or `Option.none()` on failure.
64
- */
65
- function tryString(spawner, cmd) {
66
- return spawner.string(cmd).pipe(Effect.map((s) => Option.some(s.trim())), Effect.catch(() => Effect.succeed(Option.none())));
67
- }
68
- /**
69
- * Run a command and return true if it succeeds (exit code 0).
70
- */
71
- function tryExists(spawner, cmd) {
72
- return spawner.exitCode(cmd).pipe(Effect.map((code) => code === 0), Effect.catch(() => Effect.succeed(false)));
73
- }
74
- /**
75
- * Extract version from command output using a VersionExtractor.
76
- */
77
- function extractVersion(output, extractor) {
78
- if (extractor._tag === "None" || Option.isNone(output)) return Option.none();
79
- const raw = output.value;
80
- if (extractor._tag === "Flag") {
81
- const parsed = extractor.parse ? extractor.parse(raw) : raw.trim();
82
- return Option.some(parsed);
83
- }
84
- try {
85
- const obj = JSON.parse(raw);
86
- const parts = extractor.path.split(".");
87
- let current = obj;
88
- for (const part of parts) {
89
- if (current == null || typeof current !== "object") return Option.none();
90
- current = current[part];
91
- }
92
- return typeof current === "string" ? Option.some(current) : Option.none();
93
- } catch {
94
- return Option.none();
95
- }
96
- }
97
- /**
98
- * Live implementation of {@link ToolDiscovery}.
99
- *
100
- * @remarks
101
- * Requires `ChildProcessSpawner` from `effect/unstable/process` (provide
102
- * `NodeServices.layer` at the app edge), plus `PackageManagerDetector`
103
- * and `WorkspaceRoot` from `@effected/workspaces`.
104
- *
105
- * @since 0.2.0
106
- * @public
107
- */
108
- const ToolDiscoveryLive = Layer.effect(ToolDiscovery, Effect.gen(function* () {
109
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
110
- const wsRoot = yield* WorkspaceRoot;
111
- const pmDetector = yield* PackageManagerDetector;
112
- const cache = yield* Ref.make(/* @__PURE__ */ new Map());
113
- const resolve = (definition) => Effect.gen(function* () {
114
- const hit = (yield* Ref.get(cache)).get(definition.name);
115
- if (hit) return hit;
116
- const root = yield* wsRoot.find(process.cwd()).pipe(Effect.catch(() => Effect.fail(new ToolResolutionError({
117
- name: definition.name,
118
- reason: "Could not find workspace root"
119
- }))));
120
- const pmType = (yield* pmDetector.detect(root).pipe(Effect.catch(() => Effect.fail(new ToolResolutionError({
121
- name: definition.name,
122
- reason: "Could not detect package manager"
123
- }))))).name;
124
- const globalExists = yield* tryExists(spawner, ChildProcess.make("sh", ["-c", `command -v ${definition.name}`]));
125
- let globalVersion = Option.none();
126
- if (globalExists && definition.versionExtractor._tag !== "None") {
127
- const flag = definition.versionExtractor.flag;
128
- globalVersion = extractVersion(yield* tryString(spawner, ChildProcess.make(definition.name, [flag])), definition.versionExtractor);
129
- }
130
- let localExists = false;
131
- let localVersion = Option.none();
132
- const [pmBin, ...pmArgs] = pmExecArgs(pmType, definition.name);
133
- if (definition.versionExtractor._tag !== "None") {
134
- const flag = definition.versionExtractor.flag;
135
- const localOutput = yield* tryString(spawner, ChildProcess.make(pmBin, [...pmArgs, flag]));
136
- if (Option.isSome(localOutput)) {
137
- localExists = true;
138
- localVersion = extractVersion(localOutput, definition.versionExtractor);
139
- }
140
- } else localExists = yield* tryExists(spawner, ChildProcess.make(pmBin, [...pmArgs, "--version"]));
141
- if (!globalExists && !localExists) return yield* Effect.fail(new ToolResolutionError({
142
- name: definition.name,
143
- reason: "Tool not found globally or locally"
144
- }));
145
- switch (definition.source._tag) {
146
- case "OnlyLocal":
147
- if (!localExists) return yield* Effect.fail(new ToolResolutionError({
148
- name: definition.name,
149
- reason: "Tool is required locally but was only found globally"
150
- }));
151
- break;
152
- case "OnlyGlobal":
153
- if (!globalExists) return yield* Effect.fail(new ToolResolutionError({
154
- name: definition.name,
155
- reason: "Tool is required globally but was only found locally"
156
- }));
157
- break;
158
- case "Both":
159
- if (!globalExists || !localExists) return yield* Effect.fail(new ToolResolutionError({
160
- name: definition.name,
161
- reason: "Tool is required both globally and locally but was only found in one location"
162
- }));
163
- break;
164
- case "Any": break;
165
- }
166
- let mismatch = false;
167
- let source = localExists ? "local" : "global";
168
- let version = localExists ? localVersion : globalVersion;
169
- if (globalExists && localExists && Option.isSome(globalVersion) && Option.isSome(localVersion)) {
170
- if (globalVersion.value !== localVersion.value) {
171
- mismatch = true;
172
- switch (definition.policy._tag) {
173
- case "Report":
174
- source = "local";
175
- version = localVersion;
176
- break;
177
- case "PreferLocal":
178
- source = "local";
179
- version = localVersion;
180
- break;
181
- case "PreferGlobal":
182
- source = "global";
183
- version = globalVersion;
184
- break;
185
- case "RequireMatch": return yield* Effect.fail(new ToolResolutionError({
186
- name: definition.name,
187
- reason: `Version mismatch: global ${globalVersion.value} vs local ${localVersion.value}`
188
- }));
189
- }
190
- }
191
- }
192
- const resolved = new ResolvedTool({
193
- name: definition.name,
194
- source,
195
- version,
196
- globalVersion,
197
- localVersion,
198
- packageManager: pmType,
199
- mismatch
200
- });
201
- yield* Ref.update(cache, (m) => {
202
- const next = new Map(m);
203
- next.set(definition.name, resolved);
204
- return next;
205
- });
206
- return resolved;
207
- });
208
- const require_ = (definition, message) => resolve(definition).pipe(Effect.mapError((err) => new ToolNotFoundError({
209
- name: definition.name,
210
- reason: message ?? err.reason
211
- })));
212
- const isAvailable = (definition) => Effect.gen(function* () {
213
- if (yield* tryExists(spawner, ChildProcess.make("sh", ["-c", `command -v ${definition.name}`]))) return true;
214
- const rootResult = yield* wsRoot.find(process.cwd()).pipe(Effect.option);
215
- if (Option.isNone(rootResult)) return false;
216
- const pmResult = yield* pmDetector.detect(rootResult.value).pipe(Effect.option);
217
- if (Option.isNone(pmResult)) return false;
218
- const pmType = pmResult.value.name;
219
- const probeFlag = definition.versionExtractor._tag !== "None" ? definition.versionExtractor.flag : "--version";
220
- const [pmBin, ...pmArgs] = pmExecArgs(pmType, definition.name);
221
- return yield* tryExists(spawner, ChildProcess.make(pmBin, [...pmArgs, probeFlag]));
222
- });
223
- return {
224
- resolve,
225
- require: require_,
226
- isAvailable,
227
- clearCache: Ref.set(cache, /* @__PURE__ */ new Map())
228
- };
229
- }));
230
-
231
- //#endregion
232
- export { ToolDiscovery, ToolDiscoveryLive };
@@ -1,69 +0,0 @@
1
- import { ChangesetConfigReader } from "./ChangesetConfigReader.js";
2
- import { Context, Effect, Layer } from "effect";
3
-
4
- //#region src/services/VersioningStrategy.ts
5
- /**
6
- * Service that classifies the versioning strategy used by a workspace.
7
- *
8
- * @remarks
9
- * Reads the changesets config to inspect `fixed` groups, then determines whether
10
- * the workspace uses a single-package, fixed-group, or independent versioning strategy.
11
- * Falls back to safe defaults when the changeset config is unavailable.
12
- *
13
- * @example
14
- * ```typescript
15
- * const result = await Effect.runPromise(
16
- * Effect.gen(function* () {
17
- * const strategy = yield* VersioningStrategy;
18
- * return yield* strategy.detect(["@my-org/pkg-a", "@my-org/pkg-b"]);
19
- * }).pipe(
20
- * Effect.provide(VersioningStrategyLive),
21
- * Effect.provide(ChangesetConfigReaderLive),
22
- * Effect.provide(NodeServices.layer),
23
- * )
24
- * );
25
- * ```
26
- *
27
- * @since 0.1.0
28
- * @public
29
- */
30
- var VersioningStrategy = class extends Context.Service()("@savvy-web/silk-effects/VersioningStrategy") {};
31
- /**
32
- * Live implementation of {@link VersioningStrategy}.
33
- *
34
- * @remarks
35
- * Requires {@link ChangesetConfigReader} to read the workspace changeset configuration.
36
- * If the config file is absent, an empty `fixed` groups array is assumed.
37
- *
38
- * @since 0.1.0
39
- * @public
40
- */
41
- const VersioningStrategyLive = Layer.effect(VersioningStrategy, Effect.gen(function* () {
42
- const configReader = yield* ChangesetConfigReader;
43
- const detect = (publishablePackages, root) => Effect.gen(function* () {
44
- const fixed = (yield* configReader.read(root).pipe(Effect.orElseSucceed(() => ({
45
- fixed: [],
46
- linked: []
47
- })))).fixed ?? [];
48
- const packages = [...publishablePackages];
49
- if (packages.length <= 1) return {
50
- type: "single",
51
- fixedGroups: fixed,
52
- publishablePackages: packages
53
- };
54
- if (fixed.find((group) => packages.every((pkg) => group.includes(pkg))) !== void 0) return {
55
- type: "fixed-group",
56
- fixedGroups: fixed,
57
- publishablePackages: packages
58
- };
59
- return {
60
- type: "independent",
61
- fixedGroups: fixed,
62
- publishablePackages: packages
63
- };
64
- });
65
- return { detect };
66
- }));
67
-
68
- //#endregion
69
- export { VersioningStrategy, VersioningStrategyLive };
@@ -1,67 +0,0 @@
1
- import { Effect, Stream } from "effect";
2
- import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
3
-
4
- //#region src/utils/ToolCommand.ts
5
- /**
6
- * Rebuild a command with patched options.
7
- *
8
- * @remarks
9
- * `ChildProcess.Command` values are pure data, but core only ships setters for
10
- * `cwd` and `env`. This helper rebuilds a `StandardCommand` with merged
11
- * options, recursing into both sides of a `PipedCommand` the same way
12
- * `ChildProcess.setCwd` does.
13
- */
14
- const patchOptions = (command, patch) => {
15
- switch (command._tag) {
16
- case "StandardCommand": return ChildProcess.make(command.command, command.args, patch(command.options));
17
- case "PipedCommand": return ChildProcess.pipeTo(patchOptions(command.left, patch), patchOptions(command.right, patch), command.options);
18
- }
19
- };
20
- /**
21
- * Wraps `effect/unstable/process` `ChildProcess.Command` with instance method ergonomics.
22
- *
23
- * Use `yield* cmd.string()` instead of resolving the `ChildProcessSpawner` service by hand.
24
- *
25
- * @since 0.2.0
26
- * @public
27
- */
28
- var ToolCommand = class ToolCommand {
29
- command;
30
- constructor(command) {
31
- this.command = command;
32
- }
33
- string() {
34
- return Effect.flatMap(ChildProcessSpawner.ChildProcessSpawner, (spawner) => spawner.string(this.command));
35
- }
36
- exitCode() {
37
- return Effect.flatMap(ChildProcessSpawner.ChildProcessSpawner, (spawner) => spawner.exitCode(this.command));
38
- }
39
- lines() {
40
- return Effect.flatMap(ChildProcessSpawner.ChildProcessSpawner, (spawner) => spawner.lines(this.command));
41
- }
42
- stream() {
43
- return Stream.unwrap(Effect.map(ChildProcessSpawner.ChildProcessSpawner, (spawner) => spawner.streamString(this.command)));
44
- }
45
- env(environment) {
46
- return new ToolCommand(patchOptions(this.command, (options) => ({
47
- ...options,
48
- env: {
49
- ...options.env,
50
- ...environment
51
- },
52
- extendEnv: true
53
- })));
54
- }
55
- workingDirectory(cwd) {
56
- return new ToolCommand(ChildProcess.setCwd(this.command, cwd));
57
- }
58
- stdin(input) {
59
- return new ToolCommand(patchOptions(this.command, (options) => ({
60
- ...options,
61
- stdin: Stream.succeed(new TextEncoder().encode(input))
62
- })));
63
- }
64
- };
65
-
66
- //#endregion
67
- export { ToolCommand };