@savvy-web/silk-effects 5.1.3 → 5.2.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.
@@ -25,7 +25,7 @@ import { DependencyGraph, PackageManagerDetector, VersioningStrategy, WorkspaceD
25
25
  * const analyzer = yield* SilkWorkspaceAnalyzer;
26
26
  * return yield* analyzer.analyze("/path/to/monorepo");
27
27
  * }).pipe(
28
- * Effect.provide(SilkWorkspaceAnalyzerLive),
28
+ * Effect.provide(SilkWorkspaceAnalyzer.layer),
29
29
  * // ... provide all transitive layers
30
30
  * )
31
31
  * );
@@ -34,7 +34,114 @@ import { DependencyGraph, PackageManagerDetector, VersioningStrategy, WorkspaceD
34
34
  * @since 0.2.0
35
35
  * @public
36
36
  */
37
- var SilkWorkspaceAnalyzer = class extends Context.Service()("@savvy-web/silk-effects/SilkWorkspaceAnalyzer") {};
37
+ var SilkWorkspaceAnalyzer = class extends Context.Service()("@savvy-web/silk-effects/SilkWorkspaceAnalyzer") {
38
+ /**
39
+ * Production implementation of {@link SilkWorkspaceAnalyzer}.
40
+ *
41
+ * @remarks
42
+ * Requires `WorkspaceDiscovery`, `PackageManagerDetector` and
43
+ * {@link ChangesetConfigReader}. Versioning and tag classification are pure
44
+ * `@effected/workspaces` value operations, so neither adds a requirement.
45
+ *
46
+ * @since 0.2.0
47
+ * @public
48
+ */
49
+ static layer = Layer.effect(this, Effect.gen(function* () {
50
+ const fs = yield* FileSystem.FileSystem;
51
+ const discovery = yield* WorkspaceDiscovery;
52
+ const pmDetector = yield* PackageManagerDetector;
53
+ const configReader = yield* ChangesetConfigReader;
54
+ const analyze = (root) => Effect.gen(function* () {
55
+ const pm = yield* pmDetector.detect(root).pipe(Effect.mapError((err) => new WorkspaceAnalysisError({
56
+ root,
57
+ reason: `Package manager detection failed: ${String(err)}`
58
+ })));
59
+ const packages = yield* discovery.listPackages().pipe(Effect.mapError((err) => new WorkspaceAnalysisError({
60
+ root,
61
+ reason: `Workspace discovery failed: ${String(err)}`
62
+ })));
63
+ const topoOrder = yield* DependencyGraph.make({ packages }).sort().pipe(Effect.mapError((err) => new WorkspaceAnalysisError({
64
+ root,
65
+ reason: `Cyclic dependency detected: ${String(err)}`
66
+ })));
67
+ const packagesByName = new Map(packages.map((p) => [p.name, p]));
68
+ const reordered = topoOrder.flatMap((name) => {
69
+ const pkg = packagesByName.get(name);
70
+ return pkg ? [pkg] : [];
71
+ });
72
+ const sortedPackages = reordered.length > 0 ? reordered : [...packages];
73
+ const changesetConfigOption = yield* configReader.read(root).pipe(Effect.option);
74
+ const changesetConfig = Option.getOrNull(changesetConfigOption);
75
+ const analyzedWorkspaces = [];
76
+ for (const pkg of sortedPackages) {
77
+ const pkgJson = yield* readRawPkgJson(fs, pkg.packageJsonPath);
78
+ const binding = yield* readTargetsBinding(fs, pkg.path);
79
+ const targets = SilkPublishability.detect(pkg.name, pkgJson, binding);
80
+ const isPublishable = targets.length > 0;
81
+ const isRoot = pkg.relativePath === ".";
82
+ const { versioned, tagged, released } = computeReleaseStatus(pkg.name, pkg.private, isPublishable, changesetConfig);
83
+ const analyzed = new AnalyzedWorkspace({
84
+ name: pkg.name,
85
+ version: { current: pkg.version },
86
+ path: pkg.path,
87
+ root: isRoot,
88
+ publishConfig: null,
89
+ publishable: isPublishable,
90
+ targets: [...targets],
91
+ versioned,
92
+ tagged,
93
+ released,
94
+ linked: [],
95
+ fixed: []
96
+ });
97
+ analyzedWorkspaces.push(analyzed);
98
+ }
99
+ if (changesetConfig) {
100
+ const fixedGroups = changesetConfig.fixed ?? [];
101
+ const linkedGroups = changesetConfig.linked ?? [];
102
+ const fixedByName = /* @__PURE__ */ new Map();
103
+ for (const group of fixedGroups) {
104
+ const members = analyzedWorkspaces.filter((w) => group.includes(w.name));
105
+ for (const member of members) fixedByName.set(member.name, members.filter((m) => m !== member));
106
+ }
107
+ const linkedByName = /* @__PURE__ */ new Map();
108
+ for (const group of linkedGroups) {
109
+ const members = analyzedWorkspaces.filter((w) => group.includes(w.name));
110
+ for (const member of members) linkedByName.set(member.name, members.filter((m) => m !== member));
111
+ }
112
+ for (let i = 0; i < analyzedWorkspaces.length; i++) {
113
+ const ws = analyzedWorkspaces[i];
114
+ const fixedRefs = fixedByName.get(ws.name) ?? [];
115
+ const linkedRefs = linkedByName.get(ws.name) ?? [];
116
+ if (fixedRefs.length > 0 || linkedRefs.length > 0) analyzedWorkspaces[i] = new AnalyzedWorkspace({
117
+ ...ws,
118
+ fixed: fixedRefs,
119
+ linked: linkedRefs
120
+ });
121
+ }
122
+ }
123
+ const publishableNames = analyzedWorkspaces.filter((w) => w.publishable).map((w) => w.name);
124
+ const versioning = VersioningStrategy.classify({
125
+ packages: publishableNames,
126
+ fixedGroups: changesetConfig?.fixed ?? []
127
+ });
128
+ const tagStrategyType = versioning.tagStyle;
129
+ return new WorkspaceAnalysis({
130
+ root,
131
+ runtime: pm.runtime,
132
+ packageManager: {
133
+ type: pm.name,
134
+ ...Option.isSome(pm.version) ? { version: pm.version.value } : {}
135
+ },
136
+ workspaces: analyzedWorkspaces,
137
+ changesetConfig,
138
+ versioning,
139
+ tagStrategy: tagStrategyType
140
+ });
141
+ });
142
+ return { analyze };
143
+ }));
144
+ };
38
145
  /**
39
146
  * Read the raw package.json from disk as an untyped record.
40
147
  *
@@ -99,112 +206,6 @@ function computeReleaseStatus(pkgName, isPrivate, isPublishable, config) {
99
206
  released: versioned && tagged
100
207
  };
101
208
  }
102
- /**
103
- * Live implementation of {@link SilkWorkspaceAnalyzer}.
104
- *
105
- * @remarks
106
- * Requires `WorkspaceDiscovery`, `PackageManagerDetector` and
107
- * {@link ChangesetConfigReader}. Versioning and tag classification are pure
108
- * `@effected/workspaces` value operations, so neither adds a requirement.
109
- *
110
- * @since 0.2.0
111
- * @public
112
- */
113
- const SilkWorkspaceAnalyzerLive = Layer.effect(SilkWorkspaceAnalyzer, Effect.gen(function* () {
114
- const fs = yield* FileSystem.FileSystem;
115
- const discovery = yield* WorkspaceDiscovery;
116
- const pmDetector = yield* PackageManagerDetector;
117
- const configReader = yield* ChangesetConfigReader;
118
- const analyze = (root) => Effect.gen(function* () {
119
- const pm = yield* pmDetector.detect(root).pipe(Effect.mapError((err) => new WorkspaceAnalysisError({
120
- root,
121
- reason: `Package manager detection failed: ${String(err)}`
122
- })));
123
- const packages = yield* discovery.listPackages().pipe(Effect.mapError((err) => new WorkspaceAnalysisError({
124
- root,
125
- reason: `Workspace discovery failed: ${String(err)}`
126
- })));
127
- const topoOrder = yield* DependencyGraph.make({ packages }).sort().pipe(Effect.mapError((err) => new WorkspaceAnalysisError({
128
- root,
129
- reason: `Cyclic dependency detected: ${String(err)}`
130
- })));
131
- const packagesByName = new Map(packages.map((p) => [p.name, p]));
132
- const reordered = topoOrder.flatMap((name) => {
133
- const pkg = packagesByName.get(name);
134
- return pkg ? [pkg] : [];
135
- });
136
- const sortedPackages = reordered.length > 0 ? reordered : [...packages];
137
- const changesetConfigOption = yield* configReader.read(root).pipe(Effect.option);
138
- const changesetConfig = Option.getOrNull(changesetConfigOption);
139
- const analyzedWorkspaces = [];
140
- for (const pkg of sortedPackages) {
141
- const pkgJson = yield* readRawPkgJson(fs, pkg.packageJsonPath);
142
- const binding = yield* readTargetsBinding(fs, pkg.path);
143
- const targets = SilkPublishability.detect(pkg.name, pkgJson, binding);
144
- const isPublishable = targets.length > 0;
145
- const isRoot = pkg.relativePath === ".";
146
- const { versioned, tagged, released } = computeReleaseStatus(pkg.name, pkg.private, isPublishable, changesetConfig);
147
- const analyzed = new AnalyzedWorkspace({
148
- name: pkg.name,
149
- version: { current: pkg.version },
150
- path: pkg.path,
151
- root: isRoot,
152
- publishConfig: null,
153
- publishable: isPublishable,
154
- targets: [...targets],
155
- versioned,
156
- tagged,
157
- released,
158
- linked: [],
159
- fixed: []
160
- });
161
- analyzedWorkspaces.push(analyzed);
162
- }
163
- if (changesetConfig) {
164
- const fixedGroups = changesetConfig.fixed ?? [];
165
- const linkedGroups = changesetConfig.linked ?? [];
166
- const fixedByName = /* @__PURE__ */ new Map();
167
- for (const group of fixedGroups) {
168
- const members = analyzedWorkspaces.filter((w) => group.includes(w.name));
169
- for (const member of members) fixedByName.set(member.name, members.filter((m) => m !== member));
170
- }
171
- const linkedByName = /* @__PURE__ */ new Map();
172
- for (const group of linkedGroups) {
173
- const members = analyzedWorkspaces.filter((w) => group.includes(w.name));
174
- for (const member of members) linkedByName.set(member.name, members.filter((m) => m !== member));
175
- }
176
- for (let i = 0; i < analyzedWorkspaces.length; i++) {
177
- const ws = analyzedWorkspaces[i];
178
- const fixedRefs = fixedByName.get(ws.name) ?? [];
179
- const linkedRefs = linkedByName.get(ws.name) ?? [];
180
- if (fixedRefs.length > 0 || linkedRefs.length > 0) analyzedWorkspaces[i] = new AnalyzedWorkspace({
181
- ...ws,
182
- fixed: fixedRefs,
183
- linked: linkedRefs
184
- });
185
- }
186
- }
187
- const publishableNames = analyzedWorkspaces.filter((w) => w.publishable).map((w) => w.name);
188
- const versioning = VersioningStrategy.classify({
189
- packages: publishableNames,
190
- fixedGroups: changesetConfig?.fixed ?? []
191
- });
192
- const tagStrategyType = versioning.tagStyle;
193
- return new WorkspaceAnalysis({
194
- root,
195
- runtime: pm.runtime,
196
- packageManager: {
197
- type: pm.name,
198
- ...Option.isSome(pm.version) ? { version: pm.version.value } : {}
199
- },
200
- workspaces: analyzedWorkspaces,
201
- changesetConfig,
202
- versioning,
203
- tagStrategy: tagStrategyType
204
- });
205
- });
206
- return { analyze };
207
- }));
208
209
 
209
210
  //#endregion
210
- export { SilkWorkspaceAnalyzer, SilkWorkspaceAnalyzerLive };
211
+ export { SilkWorkspaceAnalyzer };
package/turbo/index.js CHANGED
@@ -3,7 +3,7 @@ import { TurboDigest } from "./digest.js";
3
3
  import { DryRunParseError, NotATurboRepoError, TurboExecError, TurboNotInstalledError } from "./errors.js";
4
4
  import { TurboCache, TurboDryRun, TurboDryTask, TurboEnvVars, TurboGlobalCacheInputs } from "./schemas/DryRun.js";
5
5
  import { AffectedResult, CacheDiagnosis, GlobalHashSummary, GraphNode, MissExplanation, PackageCacheStatus, TaskGraphResult } from "./schemas/results.js";
6
- import { TurboInspector, TurboInspectorLive } from "./services/TurboInspector.js";
6
+ import { TurboInspector } from "./services/TurboInspector.js";
7
7
 
8
8
  //#region src/turbo/index.ts
9
9
  var turbo_exports = /* @__PURE__ */ __exportAll({
@@ -24,9 +24,8 @@ var turbo_exports = /* @__PURE__ */ __exportAll({
24
24
  TurboExecError: () => TurboExecError,
25
25
  TurboGlobalCacheInputs: () => TurboGlobalCacheInputs,
26
26
  TurboInspector: () => TurboInspector,
27
- TurboInspectorLive: () => TurboInspectorLive,
28
27
  TurboNotInstalledError: () => TurboNotInstalledError
29
28
  });
30
29
 
31
30
  //#endregion
32
- export { AffectedResult, CacheDiagnosis, DryRunParseError, GlobalHashSummary, GraphNode, MissExplanation, NotATurboRepoError, PackageCacheStatus, TaskGraphResult, TurboCache, TurboDigest, TurboDryRun, TurboDryTask, TurboEnvVars, TurboExecError, TurboGlobalCacheInputs, TurboInspector, TurboInspectorLive, TurboNotInstalledError, turbo_exports };
31
+ export { AffectedResult, CacheDiagnosis, DryRunParseError, GlobalHashSummary, GraphNode, MissExplanation, NotATurboRepoError, PackageCacheStatus, TaskGraphResult, TurboCache, TurboDigest, TurboDryRun, TurboDryTask, TurboEnvVars, TurboExecError, TurboGlobalCacheInputs, TurboInspector, TurboNotInstalledError, turbo_exports };
@@ -18,90 +18,91 @@ import { Run, Tool, ToolDiscovery } from "@effected/commands";
18
18
  *
19
19
  * @since 0.7.0
20
20
  */
21
- var TurboInspector = class extends Context.Service()("@savvy-web/silk-effects/TurboInspector") {};
22
- const TURBO = Tool.named("turbo");
23
- /** Default task used by {@link TurboInspector.taskGraph} and {@link TurboInspector.affected}. */
24
- const DEFAULT_BUILD_TASK = "build:dev";
25
- /** Default git ref `affected` compares against — matches Turborepo's own `--affected` default. */
26
- const DEFAULT_AFFECTED_BASE = "main";
27
- /**
28
- * Live implementation of {@link TurboInspector}.
29
- *
30
- * @remarks
31
- * Requires `ToolDiscovery` from `@effected/commands` to resolve the `turbo`
32
- * binary, plus `ChildProcessSpawner` and `FileSystem` from core (provide
33
- * `NodeServices.layer` at the app edge) and `Git` from `@effected/git`. The
34
- * spawner is captured at layer construction and discharged onto each `Run`
35
- * effect with `Effect.provideService`, keeping the public method effects at
36
- * `R = never`.
37
- *
38
- * @since 0.7.0
39
- */
40
- const TurboInspectorLive = Layer.effect(TurboInspector, Effect.gen(function* () {
41
- const discovery = yield* ToolDiscovery;
42
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
43
- const fs = yield* FileSystem.FileSystem;
44
- const git = yield* Git;
45
- const runTurbo = (cwd, args, env) => discovery.resolve(TURBO).pipe(Effect.mapError((e) => new TurboNotInstalledError({ reason: e.message })), Effect.flatMap((resolved) => {
46
- const base = ChildProcess.setCwd(resolved.command(...args), cwd);
47
- const command = env ? Run.extendEnv(base, env) : base;
48
- return Effect.provideService(Run.text(command), ChildProcessSpawner.ChildProcessSpawner, spawner).pipe(Effect.mapError((e) => new TurboExecError({
49
- args: [...args],
50
- reason: String(e)
21
+ var TurboInspector = class extends Context.Service()("@savvy-web/silk-effects/TurboInspector") {
22
+ /**
23
+ * Production implementation of {@link TurboInspector}.
24
+ *
25
+ * @remarks
26
+ * Requires `ToolDiscovery` from `@effected/commands` to resolve the `turbo`
27
+ * binary, plus `ChildProcessSpawner` and `FileSystem` from core (provide
28
+ * `NodeServices.layer` at the app edge) and `Git` from `@effected/git`. The
29
+ * spawner is captured at layer construction and discharged onto each `Run`
30
+ * effect with `Effect.provideService`, keeping the public method effects at
31
+ * `R = never`.
32
+ *
33
+ * @since 0.7.0
34
+ */
35
+ static layer = Layer.effect(this, Effect.gen(function* () {
36
+ const discovery = yield* ToolDiscovery;
37
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
38
+ const fs = yield* FileSystem.FileSystem;
39
+ const git = yield* Git;
40
+ const runTurbo = (cwd, args, env) => discovery.resolve(TURBO).pipe(Effect.mapError((e) => new TurboNotInstalledError({ reason: e.message })), Effect.flatMap((resolved) => {
41
+ const base = ChildProcess.setCwd(resolved.command(...args), cwd);
42
+ const command = env ? Run.extendEnv(base, env) : base;
43
+ return Effect.provideService(Run.text(command), ChildProcessSpawner.ChildProcessSpawner, spawner).pipe(Effect.mapError((e) => new TurboExecError({
44
+ args: [...args],
45
+ reason: String(e)
46
+ })));
47
+ }));
48
+ /** The paths changed between `base` and HEAD, via `@effected/git`. */
49
+ const changedPaths = (cwd, base) => git.changedFiles(cwd, {
50
+ base,
51
+ head: "HEAD"
52
+ }).pipe(Effect.mapError((e) => new TurboExecError({
53
+ args: [
54
+ "git",
55
+ "diff",
56
+ "--name-only",
57
+ `${base}...HEAD`
58
+ ],
59
+ reason: e.message
51
60
  })));
52
- }));
53
- /** The paths changed between `base` and HEAD, via `@effected/git`. */
54
- const changedPaths = (cwd, base) => git.changedFiles(cwd, {
55
- base,
56
- head: "HEAD"
57
- }).pipe(Effect.mapError((e) => new TurboExecError({
58
- args: [
59
- "git",
60
- "diff",
61
- "--name-only",
62
- `${base}...HEAD`
63
- ],
64
- reason: e.message
65
- })));
66
- const ensureTurboRepo = (cwd) => fs.exists(`${cwd}/turbo.json`).pipe(Effect.catch(() => Effect.succeed(false)), Effect.flatMap((ok) => ok ? Effect.void : Effect.fail(new NotATurboRepoError({ cwd }))));
67
- const dryRun = (task, cwd, args, env) => ensureTurboRepo(cwd).pipe(Effect.flatMap(() => runTurbo(cwd, args, env)), Effect.flatMap((stdout) => Effect.try({
68
- try: () => JSON.parse(stdout),
69
- catch: (e) => new DryRunParseError({
70
- task,
71
- reason: `invalid JSON: ${String(e)}`
72
- })
73
- })), Effect.flatMap((json) => Schema.decodeUnknownEffect(TurboDryRun)(json).pipe(Effect.mapError((e) => new DryRunParseError({
74
- task,
75
- reason: String(e)
76
- })))));
77
- return {
78
- diagnoseCache: (task, cwd) => dryRun(task, cwd, [
79
- "run",
61
+ const ensureTurboRepo = (cwd) => fs.exists(`${cwd}/turbo.json`).pipe(Effect.catch(() => Effect.succeed(false)), Effect.flatMap((ok) => ok ? Effect.void : Effect.fail(new NotATurboRepoError({ cwd }))));
62
+ const dryRun = (task, cwd, args, env) => ensureTurboRepo(cwd).pipe(Effect.flatMap(() => runTurbo(cwd, args, env)), Effect.flatMap((stdout) => Effect.try({
63
+ try: () => JSON.parse(stdout),
64
+ catch: (e) => new DryRunParseError({
65
+ task,
66
+ reason: `invalid JSON: ${String(e)}`
67
+ })
68
+ })), Effect.flatMap((json) => Schema.decodeUnknownEffect(TurboDryRun)(json).pipe(Effect.mapError((e) => new DryRunParseError({
80
69
  task,
81
- "--dry=json"
82
- ]).pipe(Effect.map((dry) => TurboDigest.cacheDiagnosis(task, dry))),
83
- taskGraph: (cwd, task) => {
84
- const t = task ?? DEFAULT_BUILD_TASK;
85
- return dryRun(t, cwd, [
70
+ reason: String(e)
71
+ })))));
72
+ return {
73
+ diagnoseCache: (task, cwd) => dryRun(task, cwd, [
86
74
  "run",
87
- t,
75
+ task,
88
76
  "--dry=json"
89
- ]).pipe(Effect.map((dry) => TurboDigest.taskGraph(dry, task)));
90
- },
91
- affected: (cwd, base) => {
92
- const ref = base ?? DEFAULT_AFFECTED_BASE;
93
- return Effect.all({
94
- changedFiles: changedPaths(cwd, ref),
95
- dry: dryRun(`${DEFAULT_BUILD_TASK} --affected`, cwd, [
77
+ ]).pipe(Effect.map((dry) => TurboDigest.cacheDiagnosis(task, dry))),
78
+ taskGraph: (cwd, task) => {
79
+ const t = task ?? DEFAULT_BUILD_TASK;
80
+ return dryRun(t, cwd, [
96
81
  "run",
97
- DEFAULT_BUILD_TASK,
98
- "--affected",
82
+ t,
99
83
  "--dry=json"
100
- ], { TURBO_SCM_BASE: ref })
101
- }).pipe(Effect.map(({ changedFiles, dry }) => TurboDigest.affected(ref, changedFiles, dry)));
102
- }
103
- };
104
- }));
84
+ ]).pipe(Effect.map((dry) => TurboDigest.taskGraph(dry, task)));
85
+ },
86
+ affected: (cwd, base) => {
87
+ const ref = base ?? DEFAULT_AFFECTED_BASE;
88
+ return Effect.all({
89
+ changedFiles: changedPaths(cwd, ref),
90
+ dry: dryRun(`${DEFAULT_BUILD_TASK} --affected`, cwd, [
91
+ "run",
92
+ DEFAULT_BUILD_TASK,
93
+ "--affected",
94
+ "--dry=json"
95
+ ], { TURBO_SCM_BASE: ref })
96
+ }).pipe(Effect.map(({ changedFiles, dry }) => TurboDigest.affected(ref, changedFiles, dry)));
97
+ }
98
+ };
99
+ }));
100
+ };
101
+ const TURBO = Tool.named("turbo");
102
+ /** Default task used by {@link TurboInspector.taskGraph} and {@link TurboInspector.affected}. */
103
+ const DEFAULT_BUILD_TASK = "build:dev";
104
+ /** Default git ref `affected` compares against — matches Turborepo's own `--affected` default. */
105
+ const DEFAULT_AFFECTED_BASE = "main";
105
106
 
106
107
  //#endregion
107
- export { TurboInspector, TurboInspectorLive };
108
+ export { TurboInspector };