@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.
- package/README.md +48 -17
- package/_virtual/_rolldown/runtime.js +18 -0
- package/changesets/api/categories.js +247 -0
- package/changesets/api/changelog.js +134 -0
- package/changesets/api/dependency-table.js +163 -0
- package/changesets/api/linter.js +168 -0
- package/changesets/api/transformer.js +140 -0
- package/changesets/categories/index.js +299 -0
- package/changesets/categories/types.js +66 -0
- package/changesets/changelog/formatting.js +119 -0
- package/changesets/changelog/getDependencyReleaseLine.js +114 -0
- package/changesets/changelog/getReleaseLine.js +122 -0
- package/changesets/changelog/index.js +99 -0
- package/changesets/constants.js +43 -0
- package/changesets/errors.js +305 -0
- package/changesets/index.js +146 -0
- package/changesets/markdownlint/index.js +29 -0
- package/changesets/markdownlint/rules/content-structure.js +98 -0
- package/changesets/markdownlint/rules/dependency-table-format.js +170 -0
- package/changesets/markdownlint/rules/heading-hierarchy.js +61 -0
- package/changesets/markdownlint/rules/required-sections.js +54 -0
- package/changesets/markdownlint/rules/uncategorized-content.js +54 -0
- package/changesets/markdownlint/rules/utils.js +30 -0
- package/changesets/remark/plugins/aggregate-dependency-tables.js +47 -0
- package/changesets/remark/plugins/contributor-footnotes.js +123 -0
- package/changesets/remark/plugins/deduplicate-items.js +30 -0
- package/changesets/remark/plugins/issue-link-refs.js +58 -0
- package/changesets/remark/plugins/merge-sections.js +43 -0
- package/changesets/remark/plugins/normalize-format.js +47 -0
- package/changesets/remark/plugins/reorder-sections.js +34 -0
- package/changesets/remark/presets.js +119 -0
- package/changesets/remark/rules/content-structure.js +22 -0
- package/changesets/remark/rules/dependency-table-format.js +40 -0
- package/changesets/remark/rules/heading-hierarchy.js +19 -0
- package/changesets/remark/rules/required-sections.js +17 -0
- package/changesets/remark/rules/uncategorized-content.js +31 -0
- package/changesets/schemas/changeset.js +146 -0
- package/changesets/schemas/dependency-table.js +189 -0
- package/changesets/schemas/git.js +69 -0
- package/changesets/schemas/github.js +175 -0
- package/changesets/schemas/options.js +182 -0
- package/changesets/schemas/package-scope.js +128 -0
- package/changesets/schemas/primitives.js +72 -0
- package/changesets/schemas/version-files.js +151 -0
- package/changesets/services/branch-analyzer.js +278 -0
- package/changesets/services/changelog.js +50 -0
- package/changesets/services/config-inspector.js +390 -0
- package/changesets/services/github.js +178 -0
- package/changesets/services/markdown.js +106 -0
- package/changesets/services/workspace-snapshot.js +182 -0
- package/changesets/utils/commit-parser.js +80 -0
- package/changesets/utils/dep-diff.js +77 -0
- package/changesets/utils/dependency-table.js +347 -0
- package/changesets/utils/issue-refs.js +101 -0
- package/changesets/utils/jsonpath.js +175 -0
- package/changesets/utils/logger.js +50 -0
- package/changesets/utils/markdown-link.js +57 -0
- package/changesets/utils/publishability.js +39 -0
- package/changesets/utils/remark-pipeline.js +79 -0
- package/changesets/utils/section-parser.js +94 -0
- package/changesets/utils/strip-frontmatter.js +46 -0
- package/changesets/utils/version-blocks.js +108 -0
- package/changesets/utils/version-files.js +336 -0
- package/changesets/utils/worktree-snapshot.js +142 -0
- package/changesets/vendor/github-info.js +55 -0
- package/commitlint/config/factory.js +69 -0
- package/commitlint/config/plugins.js +227 -0
- package/commitlint/config/rules.js +155 -0
- package/commitlint/config/schema.js +46 -0
- package/commitlint/detection/dco.js +53 -0
- package/commitlint/detection/scopes.js +45 -0
- package/commitlint/formatter/format.js +85 -0
- package/commitlint/formatter/messages.js +79 -0
- package/commitlint/hook/diagnostics/branch.js +36 -0
- package/commitlint/hook/diagnostics/cache.js +37 -0
- package/commitlint/hook/diagnostics/commitlint-config.js +36 -0
- package/commitlint/hook/diagnostics/open-issues.js +56 -0
- package/commitlint/hook/diagnostics/package-manager.js +51 -0
- package/commitlint/hook/diagnostics/signing.js +107 -0
- package/commitlint/hook/envelope.js +46 -0
- package/commitlint/hook/output.js +45 -0
- package/commitlint/hook/parse-bash-command.js +105 -0
- package/commitlint/hook/rules/closes-trailer.js +31 -0
- package/commitlint/hook/rules/forbidden-content.js +32 -0
- package/commitlint/hook/rules/plan-leakage.js +36 -0
- package/commitlint/hook/rules/signing-flag-conflict.js +25 -0
- package/commitlint/hook/rules/soft-wrap.js +37 -0
- package/commitlint/hook/rules/types.js +14 -0
- package/commitlint/hook/rules/verbosity.js +31 -0
- package/commitlint/hook/silence-logger.js +39 -0
- package/commitlint/index.js +146 -0
- package/commitlint/prompt/config.js +91 -0
- package/commitlint/prompt/emojis.js +74 -0
- package/commitlint/prompt/prompter.js +135 -0
- package/commitlint/static.js +73 -0
- package/errors/BiomeSyncError.js +21 -0
- package/errors/ChangesetConfigError.js +20 -0
- package/errors/ConfigNotFoundError.js +21 -0
- package/errors/SectionParseError.js +16 -0
- package/errors/SectionValidationError.js +16 -0
- package/errors/SectionWriteError.js +16 -0
- package/errors/TagFormatError.js +20 -0
- package/errors/ToolNotFoundError.js +11 -0
- package/errors/ToolResolutionError.js +11 -0
- package/errors/ToolVersionMismatchError.js +11 -0
- package/errors/VersioningDetectionError.js +20 -0
- package/errors/WorkspaceAnalysisError.js +21 -0
- package/index.d.ts +9743 -8380
- package/index.js +36 -6657
- package/lint/Handler.js +39 -0
- package/lint/cli/sections.js +65 -0
- package/lint/cli/templates/markdownlint.gen.js +183 -0
- package/lint/config/Preset.js +152 -0
- package/lint/config/createConfig.js +89 -0
- package/lint/handlers/Biome.js +179 -0
- package/lint/handlers/Markdown.js +139 -0
- package/lint/handlers/PackageJson.js +130 -0
- package/lint/handlers/PnpmWorkspace.js +141 -0
- package/lint/handlers/ShellScripts.js +58 -0
- package/lint/handlers/TypeScript.js +134 -0
- package/lint/handlers/Yaml.js +167 -0
- package/lint/index.js +52 -0
- package/lint/utils/Command.js +285 -0
- package/lint/utils/Filter.js +100 -0
- package/lint/utils/Workspace.js +86 -0
- package/package.json +52 -63
- package/schemas/CommentStyle.js +16 -0
- package/schemas/ResolvedTool.js +63 -0
- package/schemas/SavvySections.js +113 -0
- package/schemas/SectionBlock.js +70 -0
- package/schemas/SectionDefinition.js +121 -0
- package/schemas/SectionResults.js +12 -0
- package/schemas/TagStrategySchemas.js +18 -0
- package/schemas/ToolDefinition.js +39 -0
- package/schemas/ToolResults.js +14 -0
- package/schemas/VersioningSchemas.js +95 -0
- package/schemas/WorkspaceAnalysisSchemas.js +190 -0
- package/services/BiomeSchemaSync.js +133 -0
- package/services/ChangesetConfig.js +78 -0
- package/services/ChangesetConfigReader.js +106 -0
- package/services/ConfigDiscovery.js +71 -0
- package/services/ManagedSection.js +288 -0
- package/services/SilkPublishability.js +193 -0
- package/services/SilkWorkspaceAnalyzer.js +213 -0
- package/services/TagStrategy.js +54 -0
- package/services/ToolDiscovery.js +229 -0
- package/services/VersioningStrategy.js +67 -0
- package/tsdoc-metadata.json +11 -11
- package/turbo/digest.js +127 -0
- package/turbo/errors.js +48 -0
- package/turbo/index.js +32 -0
- package/turbo/schemas/DryRun.js +57 -0
- package/turbo/schemas/results.js +61 -0
- package/turbo/services/TurboInspector.js +100 -0
- package/utils/ToolCommand.js +40 -0
|
@@ -0,0 +1,54 @@
|
|
|
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} 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
|
+
*/
|
|
27
|
+
var TagStrategy = class extends Context.Tag("@savvy-web/silk-effects/TagStrategy")() {};
|
|
28
|
+
/**
|
|
29
|
+
* Live implementation of {@link TagStrategy} with no external dependencies.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* All logic is pure: strategy determination and tag formatting involve no I/O.
|
|
33
|
+
*
|
|
34
|
+
* @since 0.1.0
|
|
35
|
+
*/
|
|
36
|
+
const TagStrategyLive = Layer.succeed(TagStrategy, TagStrategy.of({
|
|
37
|
+
determine: (versioningResult) => {
|
|
38
|
+
if (versioningResult.type === "independent") return Effect.succeed("scoped");
|
|
39
|
+
return Effect.succeed("single");
|
|
40
|
+
},
|
|
41
|
+
formatTag: (name, version, strategy) => {
|
|
42
|
+
if (version === "") return Effect.fail(new TagFormatError({
|
|
43
|
+
name,
|
|
44
|
+
version,
|
|
45
|
+
reason: "version cannot be empty"
|
|
46
|
+
}));
|
|
47
|
+
if (strategy === "single") return Effect.succeed(version);
|
|
48
|
+
if (name.startsWith("@")) return Effect.succeed(`${name}@${version}`);
|
|
49
|
+
return Effect.succeed(`${name}@${version}`);
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { TagStrategy, TagStrategyLive };
|
|
@@ -0,0 +1,229 @@
|
|
|
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 "workspaces-effect";
|
|
6
|
+
import { Command, CommandExecutor } from "@effect/platform";
|
|
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(NodeContext.layer),
|
|
25
|
+
* )
|
|
26
|
+
* );
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @since 0.2.0
|
|
30
|
+
*/
|
|
31
|
+
var ToolDiscovery = class extends Context.Tag("@savvy-web/silk-effects/ToolDiscovery")() {};
|
|
32
|
+
/**
|
|
33
|
+
* Build the PM exec prefix for running a local binary.
|
|
34
|
+
*/
|
|
35
|
+
function pmExecArgs(pmType, name) {
|
|
36
|
+
switch (pmType) {
|
|
37
|
+
case "pnpm": return [
|
|
38
|
+
"pnpm",
|
|
39
|
+
"exec",
|
|
40
|
+
name
|
|
41
|
+
];
|
|
42
|
+
case "npm": return [
|
|
43
|
+
"npx",
|
|
44
|
+
"--no",
|
|
45
|
+
"--",
|
|
46
|
+
name
|
|
47
|
+
];
|
|
48
|
+
case "yarn": return [
|
|
49
|
+
"yarn",
|
|
50
|
+
"exec",
|
|
51
|
+
name
|
|
52
|
+
];
|
|
53
|
+
case "bun": return [
|
|
54
|
+
"bun",
|
|
55
|
+
"x",
|
|
56
|
+
"--no-install",
|
|
57
|
+
name
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Run a command and return its stdout, or `Option.none()` on failure.
|
|
63
|
+
*/
|
|
64
|
+
function tryString(cmd) {
|
|
65
|
+
return Command.string(cmd).pipe(Effect.map((s) => Option.some(s.trim())), Effect.catchAll(() => Effect.succeed(Option.none())));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Run a command and return true if it succeeds (exit code 0).
|
|
69
|
+
*/
|
|
70
|
+
function tryExists(cmd) {
|
|
71
|
+
return Command.exitCode(cmd).pipe(Effect.map((code) => code === 0), Effect.catchAll(() => Effect.succeed(false)));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Extract version from command output using a VersionExtractor.
|
|
75
|
+
*/
|
|
76
|
+
function extractVersion(output, extractor) {
|
|
77
|
+
if (extractor._tag === "None" || Option.isNone(output)) return Option.none();
|
|
78
|
+
const raw = output.value;
|
|
79
|
+
if (extractor._tag === "Flag") {
|
|
80
|
+
const parsed = extractor.parse ? extractor.parse(raw) : raw.trim();
|
|
81
|
+
return Option.some(parsed);
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const obj = JSON.parse(raw);
|
|
85
|
+
const parts = extractor.path.split(".");
|
|
86
|
+
let current = obj;
|
|
87
|
+
for (const part of parts) {
|
|
88
|
+
if (current == null || typeof current !== "object") return Option.none();
|
|
89
|
+
current = current[part];
|
|
90
|
+
}
|
|
91
|
+
return typeof current === "string" ? Option.some(current) : Option.none();
|
|
92
|
+
} catch {
|
|
93
|
+
return Option.none();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Live implementation of {@link ToolDiscovery}.
|
|
98
|
+
*
|
|
99
|
+
* @remarks
|
|
100
|
+
* Requires `CommandExecutor` from `@effect/platform`, `PackageManagerDetector`
|
|
101
|
+
* and `WorkspaceRoot` from `workspaces-effect`.
|
|
102
|
+
*
|
|
103
|
+
* @since 0.2.0
|
|
104
|
+
*/
|
|
105
|
+
const ToolDiscoveryLive = Layer.effect(ToolDiscovery, Effect.gen(function* () {
|
|
106
|
+
const executor = yield* CommandExecutor.CommandExecutor;
|
|
107
|
+
const wsRoot = yield* WorkspaceRoot;
|
|
108
|
+
const pmDetector = yield* PackageManagerDetector;
|
|
109
|
+
const cache = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
110
|
+
const resolve = (definition) => Effect.gen(function* () {
|
|
111
|
+
const hit = (yield* Ref.get(cache)).get(definition.name);
|
|
112
|
+
if (hit) return hit;
|
|
113
|
+
const root = yield* wsRoot.find(process.cwd()).pipe(Effect.catchAll(() => Effect.fail(new ToolResolutionError({
|
|
114
|
+
name: definition.name,
|
|
115
|
+
reason: "Could not find workspace root"
|
|
116
|
+
}))));
|
|
117
|
+
const pmType = (yield* pmDetector.detect(root).pipe(Effect.catchAll(() => Effect.fail(new ToolResolutionError({
|
|
118
|
+
name: definition.name,
|
|
119
|
+
reason: "Could not detect package manager"
|
|
120
|
+
}))))).type;
|
|
121
|
+
const globalExists = yield* Effect.provideService(tryExists(Command.make("sh", "-c", `command -v ${definition.name}`)), CommandExecutor.CommandExecutor, executor);
|
|
122
|
+
let globalVersion = Option.none();
|
|
123
|
+
if (globalExists && definition.versionExtractor._tag !== "None") {
|
|
124
|
+
const flag = definition.versionExtractor.flag;
|
|
125
|
+
globalVersion = extractVersion(yield* Effect.provideService(tryString(Command.make(definition.name, flag)), CommandExecutor.CommandExecutor, executor), definition.versionExtractor);
|
|
126
|
+
}
|
|
127
|
+
let localExists = false;
|
|
128
|
+
let localVersion = Option.none();
|
|
129
|
+
const [pmBin, ...pmArgs] = pmExecArgs(pmType, definition.name);
|
|
130
|
+
if (definition.versionExtractor._tag !== "None") {
|
|
131
|
+
const flag = definition.versionExtractor.flag;
|
|
132
|
+
const localOutput = yield* Effect.provideService(tryString(Command.make(pmBin, ...pmArgs, flag)), CommandExecutor.CommandExecutor, executor);
|
|
133
|
+
if (Option.isSome(localOutput)) {
|
|
134
|
+
localExists = true;
|
|
135
|
+
localVersion = extractVersion(localOutput, definition.versionExtractor);
|
|
136
|
+
}
|
|
137
|
+
} else localExists = yield* Effect.provideService(tryExists(Command.make(pmBin, ...pmArgs, "--version")), CommandExecutor.CommandExecutor, executor);
|
|
138
|
+
if (!globalExists && !localExists) return yield* Effect.fail(new ToolResolutionError({
|
|
139
|
+
name: definition.name,
|
|
140
|
+
reason: "Tool not found globally or locally"
|
|
141
|
+
}));
|
|
142
|
+
switch (definition.source._tag) {
|
|
143
|
+
case "OnlyLocal":
|
|
144
|
+
if (!localExists) return yield* Effect.fail(new ToolResolutionError({
|
|
145
|
+
name: definition.name,
|
|
146
|
+
reason: "Tool is required locally but was only found globally"
|
|
147
|
+
}));
|
|
148
|
+
break;
|
|
149
|
+
case "OnlyGlobal":
|
|
150
|
+
if (!globalExists) return yield* Effect.fail(new ToolResolutionError({
|
|
151
|
+
name: definition.name,
|
|
152
|
+
reason: "Tool is required globally but was only found locally"
|
|
153
|
+
}));
|
|
154
|
+
break;
|
|
155
|
+
case "Both":
|
|
156
|
+
if (!globalExists || !localExists) return yield* Effect.fail(new ToolResolutionError({
|
|
157
|
+
name: definition.name,
|
|
158
|
+
reason: "Tool is required both globally and locally but was only found in one location"
|
|
159
|
+
}));
|
|
160
|
+
break;
|
|
161
|
+
case "Any": break;
|
|
162
|
+
}
|
|
163
|
+
let mismatch = false;
|
|
164
|
+
let source = localExists ? "local" : "global";
|
|
165
|
+
let version = localExists ? localVersion : globalVersion;
|
|
166
|
+
if (globalExists && localExists && Option.isSome(globalVersion) && Option.isSome(localVersion)) {
|
|
167
|
+
if (globalVersion.value !== localVersion.value) {
|
|
168
|
+
mismatch = true;
|
|
169
|
+
switch (definition.policy._tag) {
|
|
170
|
+
case "Report":
|
|
171
|
+
source = "local";
|
|
172
|
+
version = localVersion;
|
|
173
|
+
break;
|
|
174
|
+
case "PreferLocal":
|
|
175
|
+
source = "local";
|
|
176
|
+
version = localVersion;
|
|
177
|
+
break;
|
|
178
|
+
case "PreferGlobal":
|
|
179
|
+
source = "global";
|
|
180
|
+
version = globalVersion;
|
|
181
|
+
break;
|
|
182
|
+
case "RequireMatch": return yield* Effect.fail(new ToolResolutionError({
|
|
183
|
+
name: definition.name,
|
|
184
|
+
reason: `Version mismatch: global ${globalVersion.value} vs local ${localVersion.value}`
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const resolved = new ResolvedTool({
|
|
190
|
+
name: definition.name,
|
|
191
|
+
source,
|
|
192
|
+
version,
|
|
193
|
+
globalVersion,
|
|
194
|
+
localVersion,
|
|
195
|
+
packageManager: pmType,
|
|
196
|
+
mismatch
|
|
197
|
+
});
|
|
198
|
+
yield* Ref.update(cache, (m) => {
|
|
199
|
+
const next = new Map(m);
|
|
200
|
+
next.set(definition.name, resolved);
|
|
201
|
+
return next;
|
|
202
|
+
});
|
|
203
|
+
return resolved;
|
|
204
|
+
});
|
|
205
|
+
const require_ = (definition, message) => resolve(definition).pipe(Effect.mapError((err) => new ToolNotFoundError({
|
|
206
|
+
name: definition.name,
|
|
207
|
+
reason: message ?? err.reason
|
|
208
|
+
})));
|
|
209
|
+
const isAvailable = (definition) => Effect.gen(function* () {
|
|
210
|
+
if (yield* Effect.provideService(tryExists(Command.make("sh", "-c", `command -v ${definition.name}`)), CommandExecutor.CommandExecutor, executor)) return true;
|
|
211
|
+
const rootResult = yield* wsRoot.find(process.cwd()).pipe(Effect.option);
|
|
212
|
+
if (Option.isNone(rootResult)) return false;
|
|
213
|
+
const pmResult = yield* pmDetector.detect(rootResult.value).pipe(Effect.option);
|
|
214
|
+
if (Option.isNone(pmResult)) return false;
|
|
215
|
+
const pmType = pmResult.value.type;
|
|
216
|
+
const probeFlag = definition.versionExtractor._tag !== "None" ? definition.versionExtractor.flag : "--version";
|
|
217
|
+
const [pmBin, ...pmArgs] = pmExecArgs(pmType, definition.name);
|
|
218
|
+
return yield* Effect.provideService(tryExists(Command.make(pmBin, ...pmArgs, probeFlag)), CommandExecutor.CommandExecutor, executor);
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
resolve,
|
|
222
|
+
require: require_,
|
|
223
|
+
isAvailable,
|
|
224
|
+
clearCache: Ref.set(cache, /* @__PURE__ */ new Map())
|
|
225
|
+
};
|
|
226
|
+
}));
|
|
227
|
+
|
|
228
|
+
//#endregion
|
|
229
|
+
export { ToolDiscovery, ToolDiscoveryLive };
|
|
@@ -0,0 +1,67 @@
|
|
|
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(NodeContext.layer),
|
|
23
|
+
* )
|
|
24
|
+
* );
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @since 0.1.0
|
|
28
|
+
*/
|
|
29
|
+
var VersioningStrategy = class extends Context.Tag("@savvy-web/silk-effects/VersioningStrategy")() {};
|
|
30
|
+
/**
|
|
31
|
+
* Live implementation of {@link VersioningStrategy}.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* Requires {@link ChangesetConfigReader} to read the workspace changeset configuration.
|
|
35
|
+
* If the config file is absent, an empty `fixed` groups array is assumed.
|
|
36
|
+
*
|
|
37
|
+
* @since 0.1.0
|
|
38
|
+
*/
|
|
39
|
+
const VersioningStrategyLive = Layer.effect(VersioningStrategy, Effect.gen(function* () {
|
|
40
|
+
const configReader = yield* ChangesetConfigReader;
|
|
41
|
+
const detect = (publishablePackages, root) => Effect.gen(function* () {
|
|
42
|
+
const fixed = (yield* configReader.read(root).pipe(Effect.orElseSucceed(() => ({
|
|
43
|
+
fixed: [],
|
|
44
|
+
linked: []
|
|
45
|
+
})))).fixed ?? [];
|
|
46
|
+
const packages = [...publishablePackages];
|
|
47
|
+
if (packages.length <= 1) return {
|
|
48
|
+
type: "single",
|
|
49
|
+
fixedGroups: fixed,
|
|
50
|
+
publishablePackages: packages
|
|
51
|
+
};
|
|
52
|
+
if (fixed.find((group) => packages.every((pkg) => group.includes(pkg))) !== void 0) return {
|
|
53
|
+
type: "fixed-group",
|
|
54
|
+
fixedGroups: fixed,
|
|
55
|
+
publishablePackages: packages
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
type: "independent",
|
|
59
|
+
fixedGroups: fixed,
|
|
60
|
+
publishablePackages: packages
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
return { detect };
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { VersioningStrategy, VersioningStrategyLive };
|
package/tsdoc-metadata.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
|
|
2
|
-
// It should be published with your NPM package. It should not be tracked by Git.
|
|
3
|
-
{
|
|
4
|
-
"tsdocVersion": "0.12",
|
|
5
|
-
"toolPackages": [
|
|
6
|
-
{
|
|
7
|
-
"packageName": "@microsoft/api-extractor",
|
|
8
|
-
"packageVersion": "7.58.7"
|
|
9
|
-
}
|
|
10
|
-
]
|
|
11
|
-
}
|
|
1
|
+
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
|
|
2
|
+
// It should be published with your NPM package. It should not be tracked by Git.
|
|
3
|
+
{
|
|
4
|
+
"tsdocVersion": "0.12",
|
|
5
|
+
"toolPackages": [
|
|
6
|
+
{
|
|
7
|
+
"packageName": "@microsoft/api-extractor",
|
|
8
|
+
"packageVersion": "7.58.7"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
package/turbo/digest.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
//#region src/turbo/digest.ts
|
|
2
|
+
/**
|
|
3
|
+
* Pure transforms from decoded turbo dry-run JSON to digested result shapes.
|
|
4
|
+
* No dependency injection — directly unit-testable.
|
|
5
|
+
*
|
|
6
|
+
* @remarks All members are `static`.
|
|
7
|
+
* @since 0.7.0
|
|
8
|
+
*/
|
|
9
|
+
var TurboDigest = class TurboDigest {
|
|
10
|
+
constructor() {}
|
|
11
|
+
/** Env vars that actually feed a task's hash: configured + inferred. */
|
|
12
|
+
static hashedEnv(task) {
|
|
13
|
+
return [...task.environmentVariables.configured, ...task.environmentVariables.inferred];
|
|
14
|
+
}
|
|
15
|
+
static cacheDiagnosis(task, dry) {
|
|
16
|
+
const statuses = dry.tasks.map((t) => ({
|
|
17
|
+
package: t.package,
|
|
18
|
+
taskId: t.taskId,
|
|
19
|
+
hash: t.hash,
|
|
20
|
+
status: t.cache.status,
|
|
21
|
+
timeSaved: t.cache.timeSaved
|
|
22
|
+
}));
|
|
23
|
+
const misses = dry.tasks.filter((t) => t.cache.status === "MISS");
|
|
24
|
+
const explanations = misses.map((t) => ({
|
|
25
|
+
package: t.package,
|
|
26
|
+
taskId: t.taskId,
|
|
27
|
+
hash: t.hash,
|
|
28
|
+
inputFileCount: Object.keys(t.inputs).length,
|
|
29
|
+
hashedEnvVars: TurboDigest.hashedEnv(t),
|
|
30
|
+
externalDependenciesHash: t.hashOfExternalDependencies,
|
|
31
|
+
dependsOn: t.dependencies
|
|
32
|
+
}));
|
|
33
|
+
const g = dry.globalCacheInputs;
|
|
34
|
+
return {
|
|
35
|
+
task,
|
|
36
|
+
totalTasks: dry.tasks.length,
|
|
37
|
+
hits: dry.tasks.length - misses.length,
|
|
38
|
+
misses: misses.length,
|
|
39
|
+
statuses,
|
|
40
|
+
explanations,
|
|
41
|
+
global: {
|
|
42
|
+
rootKey: g.rootKey,
|
|
43
|
+
globalFileCount: Object.keys(g.files).length,
|
|
44
|
+
externalDependenciesHash: g.hashOfExternalDependencies,
|
|
45
|
+
internalDependenciesHash: g.hashOfInternalDependencies,
|
|
46
|
+
globalEnvVars: [...g.environmentVariables.configured, ...g.environmentVariables.inferred]
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
static taskGraph(dry, task) {
|
|
51
|
+
const nodes = dry.tasks.map((t) => ({
|
|
52
|
+
taskId: t.taskId,
|
|
53
|
+
package: t.package,
|
|
54
|
+
dependsOn: t.dependencies
|
|
55
|
+
}));
|
|
56
|
+
return {
|
|
57
|
+
...task !== void 0 ? { task } : {},
|
|
58
|
+
nodeCount: nodes.length,
|
|
59
|
+
nodes,
|
|
60
|
+
criticalPath: TurboDigest.criticalPath(dry.tasks)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Longest dependency chain by node count (memoized DFS over the dependency edges). */
|
|
64
|
+
static criticalPath(tasks) {
|
|
65
|
+
const byId = new Map(tasks.map((t) => [t.taskId, t]));
|
|
66
|
+
const memo = /* @__PURE__ */ new Map();
|
|
67
|
+
const longest = (id, seen) => {
|
|
68
|
+
const cached = memo.get(id);
|
|
69
|
+
if (cached) return cached;
|
|
70
|
+
if (seen.has(id)) return [id];
|
|
71
|
+
const node = byId.get(id);
|
|
72
|
+
if (!node || node.dependencies.length === 0) {
|
|
73
|
+
const self = [id];
|
|
74
|
+
memo.set(id, self);
|
|
75
|
+
return self;
|
|
76
|
+
}
|
|
77
|
+
const nextSeen = new Set(seen).add(id);
|
|
78
|
+
let best = [];
|
|
79
|
+
for (const dep of node.dependencies) {
|
|
80
|
+
const chain = longest(dep, nextSeen);
|
|
81
|
+
if (chain.length > best.length) best = chain;
|
|
82
|
+
}
|
|
83
|
+
const path = [...best, id];
|
|
84
|
+
memo.set(id, path);
|
|
85
|
+
return path;
|
|
86
|
+
};
|
|
87
|
+
let overall = [];
|
|
88
|
+
for (const t of tasks) {
|
|
89
|
+
const chain = longest(t.taskId, /* @__PURE__ */ new Set());
|
|
90
|
+
if (chain.length > overall.length) overall = chain;
|
|
91
|
+
}
|
|
92
|
+
return overall;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Split an `--affected` dry-run into directly-changed packages and their dependents.
|
|
96
|
+
*
|
|
97
|
+
* @remarks
|
|
98
|
+
* `turbo run <task> --affected --dry=json` already expands the set to include the
|
|
99
|
+
* directly-changed packages *and* their transitive dependents, so every task in
|
|
100
|
+
* `dry.tasks` is in the affected set. To recover the split we need the set of files
|
|
101
|
+
* that actually changed (`changedFiles`, from `git diff --name-only <base>...HEAD`):
|
|
102
|
+
* a package is *directly changed* when a changed file lives under its `directory`,
|
|
103
|
+
* and everything else in the affected set is therefore a *dependent*. When the change
|
|
104
|
+
* is outside any affected package (e.g. a root file like `pnpm-lock.yaml`, which makes
|
|
105
|
+
* the whole graph affected), no package is directly changed and the full set is reported
|
|
106
|
+
* as dependents.
|
|
107
|
+
*/
|
|
108
|
+
static affected(base, changedFiles, dry) {
|
|
109
|
+
const packageDir = /* @__PURE__ */ new Map();
|
|
110
|
+
for (const t of dry.tasks) packageDir.set(t.package, t.directory);
|
|
111
|
+
const affectedPackages = [...packageDir.keys()];
|
|
112
|
+
const isDirectlyChanged = (pkg) => {
|
|
113
|
+
const dir = packageDir.get(pkg);
|
|
114
|
+
if (dir === void 0) return false;
|
|
115
|
+
const prefix = dir.endsWith("/") ? dir : `${dir}/`;
|
|
116
|
+
return changedFiles.some((file) => file === dir || file.startsWith(prefix));
|
|
117
|
+
};
|
|
118
|
+
return {
|
|
119
|
+
base,
|
|
120
|
+
packages: affectedPackages.filter(isDirectlyChanged),
|
|
121
|
+
dependents: affectedPackages.filter((pkg) => !isDirectlyChanged(pkg))
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
export { TurboDigest };
|
package/turbo/errors.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Data } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/turbo/errors.ts
|
|
4
|
+
/**
|
|
5
|
+
* Raised when the turbo CLI cannot be resolved or executed.
|
|
6
|
+
*
|
|
7
|
+
* @since 0.7.0
|
|
8
|
+
*/
|
|
9
|
+
var TurboNotInstalledError = class extends Data.TaggedError("TurboNotInstalledError") {
|
|
10
|
+
get message() {
|
|
11
|
+
return `Turborepo is not available: ${this.reason}`;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Raised when the resolved working directory is not a Turborepo (no turbo.json).
|
|
16
|
+
*
|
|
17
|
+
* @since 0.7.0
|
|
18
|
+
*/
|
|
19
|
+
var NotATurboRepoError = class extends Data.TaggedError("NotATurboRepoError") {
|
|
20
|
+
get message() {
|
|
21
|
+
return `Not a Turborepo: no turbo.json found in ${this.cwd}`;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Raised when `turbo run <task> --dry=json` output cannot be parsed/decoded.
|
|
26
|
+
*
|
|
27
|
+
* @since 0.7.0
|
|
28
|
+
*/
|
|
29
|
+
var DryRunParseError = class extends Data.TaggedError("DryRunParseError") {
|
|
30
|
+
get message() {
|
|
31
|
+
return `Failed to parse turbo dry-run output for "${this.task}": ${this.reason}`;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Raised when an invocation of the turbo CLI exits non-zero (e.g. an unknown
|
|
36
|
+
* task name or a corrupt `turbo.json`). Distinct from {@link TurboNotInstalledError},
|
|
37
|
+
* which signals that the binary itself could not be resolved.
|
|
38
|
+
*
|
|
39
|
+
* @since 0.7.0
|
|
40
|
+
*/
|
|
41
|
+
var TurboExecError = class extends Data.TaggedError("TurboExecError") {
|
|
42
|
+
get message() {
|
|
43
|
+
return `Turbo command failed (${this.args.join(" ")}): ${this.reason}`;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { DryRunParseError, NotATurboRepoError, TurboExecError, TurboNotInstalledError };
|
package/turbo/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { __exportAll } from "../_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { TurboDigest } from "./digest.js";
|
|
3
|
+
import { DryRunParseError, NotATurboRepoError, TurboExecError, TurboNotInstalledError } from "./errors.js";
|
|
4
|
+
import { TurboCache, TurboDryRun, TurboDryTask, TurboEnvVars, TurboGlobalCacheInputs } from "./schemas/DryRun.js";
|
|
5
|
+
import { AffectedResult, CacheDiagnosis, GlobalHashSummary, GraphNode, MissExplanation, PackageCacheStatus, TaskGraphResult } from "./schemas/results.js";
|
|
6
|
+
import { TurboInspector, TurboInspectorLive } from "./services/TurboInspector.js";
|
|
7
|
+
|
|
8
|
+
//#region src/turbo/index.ts
|
|
9
|
+
var turbo_exports = /* @__PURE__ */ __exportAll({
|
|
10
|
+
AffectedResult: () => AffectedResult,
|
|
11
|
+
CacheDiagnosis: () => CacheDiagnosis,
|
|
12
|
+
DryRunParseError: () => DryRunParseError,
|
|
13
|
+
GlobalHashSummary: () => GlobalHashSummary,
|
|
14
|
+
GraphNode: () => GraphNode,
|
|
15
|
+
MissExplanation: () => MissExplanation,
|
|
16
|
+
NotATurboRepoError: () => NotATurboRepoError,
|
|
17
|
+
PackageCacheStatus: () => PackageCacheStatus,
|
|
18
|
+
TaskGraphResult: () => TaskGraphResult,
|
|
19
|
+
TurboCache: () => TurboCache,
|
|
20
|
+
TurboDigest: () => TurboDigest,
|
|
21
|
+
TurboDryRun: () => TurboDryRun,
|
|
22
|
+
TurboDryTask: () => TurboDryTask,
|
|
23
|
+
TurboEnvVars: () => TurboEnvVars,
|
|
24
|
+
TurboExecError: () => TurboExecError,
|
|
25
|
+
TurboGlobalCacheInputs: () => TurboGlobalCacheInputs,
|
|
26
|
+
TurboInspector: () => TurboInspector,
|
|
27
|
+
TurboInspectorLive: () => TurboInspectorLive,
|
|
28
|
+
TurboNotInstalledError: () => TurboNotInstalledError
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { turbo_exports };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/turbo/schemas/DryRun.ts
|
|
4
|
+
/** turbo's per-target cache descriptor in `--dry=json`. */
|
|
5
|
+
const TurboCache = Schema.Struct({
|
|
6
|
+
local: Schema.Boolean,
|
|
7
|
+
remote: Schema.Boolean,
|
|
8
|
+
status: Schema.Literal("HIT", "MISS"),
|
|
9
|
+
timeSaved: Schema.Number
|
|
10
|
+
}).annotations({ identifier: "TurboCache" });
|
|
11
|
+
/** turbo's environment-variable contribution block (per-task and global). */
|
|
12
|
+
const TurboEnvVars = Schema.Struct({
|
|
13
|
+
specified: Schema.Struct({
|
|
14
|
+
env: Schema.Array(Schema.String),
|
|
15
|
+
passThroughEnv: Schema.NullOr(Schema.Array(Schema.String))
|
|
16
|
+
}),
|
|
17
|
+
configured: Schema.Array(Schema.String),
|
|
18
|
+
inferred: Schema.Array(Schema.String),
|
|
19
|
+
passthrough: Schema.NullOr(Schema.Array(Schema.String))
|
|
20
|
+
}).annotations({ identifier: "TurboEnvVars" });
|
|
21
|
+
/** A single task entry from `turbo run <task> --dry=json`. */
|
|
22
|
+
const TurboDryTask = Schema.Struct({
|
|
23
|
+
taskId: Schema.String,
|
|
24
|
+
task: Schema.String,
|
|
25
|
+
package: Schema.String,
|
|
26
|
+
directory: Schema.String,
|
|
27
|
+
hash: Schema.String,
|
|
28
|
+
inputs: Schema.Record({
|
|
29
|
+
key: Schema.String,
|
|
30
|
+
value: Schema.String
|
|
31
|
+
}),
|
|
32
|
+
hashOfExternalDependencies: Schema.String,
|
|
33
|
+
cache: TurboCache,
|
|
34
|
+
dependencies: Schema.Array(Schema.String),
|
|
35
|
+
dependents: Schema.Array(Schema.String),
|
|
36
|
+
environmentVariables: TurboEnvVars
|
|
37
|
+
}).annotations({ identifier: "TurboDryTask" });
|
|
38
|
+
/** The `globalCacheInputs` block — the components of the global hash. */
|
|
39
|
+
const TurboGlobalCacheInputs = Schema.Struct({
|
|
40
|
+
rootKey: Schema.String,
|
|
41
|
+
files: Schema.Record({
|
|
42
|
+
key: Schema.String,
|
|
43
|
+
value: Schema.String
|
|
44
|
+
}),
|
|
45
|
+
hashOfExternalDependencies: Schema.String,
|
|
46
|
+
hashOfInternalDependencies: Schema.String,
|
|
47
|
+
environmentVariables: TurboEnvVars
|
|
48
|
+
}).annotations({ identifier: "TurboGlobalCacheInputs" });
|
|
49
|
+
/** The subset of `turbo run <task> --dry=json` the Turbo namespace reads. */
|
|
50
|
+
const TurboDryRun = Schema.Struct({
|
|
51
|
+
turboVersion: Schema.String,
|
|
52
|
+
globalCacheInputs: TurboGlobalCacheInputs,
|
|
53
|
+
tasks: Schema.Array(TurboDryTask)
|
|
54
|
+
}).annotations({ identifier: "TurboDryRun" });
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
export { TurboCache, TurboDryRun, TurboDryTask, TurboEnvVars, TurboGlobalCacheInputs };
|