@savvy-web/silk-effects 0.2.0 → 0.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.
- package/README.md +242 -28
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,18 +3,15 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@savvy-web/silk-effects)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
Shared [Effect](https://effect.website/) library providing Silk Suite conventions for publishability detection, versioning strategy, tag formatting, managed sections, config discovery,
|
|
6
|
+
Shared [Effect](https://effect.website/) library providing Silk Suite conventions for publishability detection, versioning strategy, tag formatting, managed file sections, config discovery, Biome schema synchronization, and CLI tool resolution. Platform-agnostic -- consumers provide their own runtime layer (`NodeContext`, `BunContext`, etc.).
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
|
-
- Resolve publish targets
|
|
11
|
-
-
|
|
12
|
-
- Format git tags consistently based on workspace structure
|
|
13
|
-
- Manage tool-owned sections inside user-editable files without clobbering user content
|
|
14
|
-
- Define reusable section identities with typed content factories (`SectionDefinition`)
|
|
10
|
+
- Resolve publish targets and detect publishability from `package.json` with multi-registry support
|
|
11
|
+
- Manage tool-owned sections in user-editable files without clobbering surrounding content
|
|
15
12
|
- Discover and resolve CLI tools globally or locally with version enforcement and caching
|
|
16
|
-
-
|
|
17
|
-
-
|
|
13
|
+
- Detect versioning strategy and format git tags from changeset configuration
|
|
14
|
+
- Locate config files and keep Biome schema URLs in sync across workspaces
|
|
18
15
|
|
|
19
16
|
## Installation
|
|
20
17
|
|
|
@@ -22,12 +19,34 @@ Shared [Effect](https://effect.website/) library providing Silk Suite convention
|
|
|
22
19
|
pnpm add @savvy-web/silk-effects effect @effect/platform @effect/platform-node
|
|
23
20
|
```
|
|
24
21
|
|
|
25
|
-
`effect` is a peer dependency
|
|
22
|
+
`effect` is a peer dependency. Install a platform package (`@effect/platform-node`, `@effect/platform-bun`) matching your runtime.
|
|
26
23
|
|
|
27
24
|
## Quick Start
|
|
28
25
|
|
|
29
26
|
All exports come from the package root:
|
|
30
27
|
|
|
28
|
+
```typescript
|
|
29
|
+
import {
|
|
30
|
+
TargetResolver, TargetResolverLive,
|
|
31
|
+
ManagedSection, ManagedSectionLive, SectionDefinition,
|
|
32
|
+
ToolDiscovery, ToolDiscoveryLive, ToolDefinition,
|
|
33
|
+
} from "@savvy-web/silk-effects";
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Services
|
|
37
|
+
|
|
38
|
+
The 9 services are grouped by which platform layers they require.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
### No Platform Layer Required
|
|
43
|
+
|
|
44
|
+
These services are pure logic -- no filesystem or shell access needed.
|
|
45
|
+
|
|
46
|
+
#### TargetResolver
|
|
47
|
+
|
|
48
|
+
Resolve raw publish-target values into fully-normalized `ResolvedTarget` records. Supports shorthands (`"npm"`, `"github"`, `"jsr"`), custom registry URLs, and object configs. Auth strategy (OIDC vs token) is auto-detected from the registry hostname.
|
|
49
|
+
|
|
31
50
|
```typescript
|
|
32
51
|
import { Effect } from "effect";
|
|
33
52
|
import { TargetResolver, TargetResolverLive } from "@savvy-web/silk-effects";
|
|
@@ -38,26 +57,91 @@ const targets = await Effect.runPromise(
|
|
|
38
57
|
return yield* resolver.resolve(["npm", "github"]);
|
|
39
58
|
}).pipe(Effect.provide(TargetResolverLive)),
|
|
40
59
|
);
|
|
60
|
+
// => ResolvedTarget[] with registry, auth, provenance, etc.
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### SilkPublishabilityPlugin
|
|
64
|
+
|
|
65
|
+
Detect whether a package is publishable from its `package.json` and resolve its targets. Delegates to `TargetResolver` internally.
|
|
66
|
+
|
|
67
|
+
Rules: `private: true` with no `publishConfig` is not publishable. A `publishConfig.targets` array resolves each entry. A bare `publishConfig.registry` resolves a single target. Default falls back to `"npm"`.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { Effect } from "effect";
|
|
71
|
+
import {
|
|
72
|
+
SilkPublishabilityPlugin, SilkPublishabilityPluginLive,
|
|
73
|
+
TargetResolverLive,
|
|
74
|
+
} from "@savvy-web/silk-effects";
|
|
75
|
+
|
|
76
|
+
const targets = await Effect.runPromise(
|
|
77
|
+
Effect.gen(function* () {
|
|
78
|
+
const plugin = yield* SilkPublishabilityPlugin;
|
|
79
|
+
return yield* plugin.detect(packageJson);
|
|
80
|
+
}).pipe(
|
|
81
|
+
Effect.provide(SilkPublishabilityPluginLive),
|
|
82
|
+
Effect.provide(TargetResolverLive),
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### TagStrategy
|
|
88
|
+
|
|
89
|
+
Determine git-tag naming strategy and format tag strings. Strategy is `"single"` (one publishable package, tags like `1.2.3`) or `"scoped"` (multiple packages, tags like `@scope/pkg@1.2.3`). Tag format follows strict SemVer 2.0.0 with no `v` prefix.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { Effect } from "effect";
|
|
93
|
+
import { TagStrategy, TagStrategyLive } from "@savvy-web/silk-effects";
|
|
94
|
+
|
|
95
|
+
const tag = await Effect.runPromise(
|
|
96
|
+
Effect.gen(function* () {
|
|
97
|
+
const ts = yield* TagStrategy;
|
|
98
|
+
const strategy = yield* ts.determine(versioningResult);
|
|
99
|
+
return yield* ts.formatTag("@savvy-web/silk-effects", "0.2.0", strategy);
|
|
100
|
+
}).pipe(Effect.provide(TagStrategyLive)),
|
|
101
|
+
);
|
|
102
|
+
// => "@savvy-web/silk-effects@0.2.0"
|
|
41
103
|
```
|
|
42
104
|
|
|
43
|
-
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
### FileSystem Layer Required
|
|
108
|
+
|
|
109
|
+
These services read or write files. Provide a platform layer such as `NodeContext.layer` or `BunContext.layer`.
|
|
110
|
+
|
|
111
|
+
#### ManagedSection
|
|
112
|
+
|
|
113
|
+
Manage tool-owned delimited sections inside user-editable files. Sections are bounded by markers like `# --- BEGIN TOOL MANAGED SECTION ---` / `# --- END ... ---`. User content outside the markers is never touched.
|
|
114
|
+
|
|
115
|
+
**SectionDefinition** is a value object representing section identity (tool name + comment style). It creates `SectionBlock` instances that hold the actual content. Definitions support typed content factories via `generate()` and `generateEffect()`.
|
|
116
|
+
|
|
117
|
+
**SectionBlock** represents the content between markers. It supports `diff()`, `prepend()`, and `append()` operations and uses normalized content for equality comparison.
|
|
118
|
+
|
|
119
|
+
Methods: `read`, `write`, `sync`, `check`, `isManaged` -- all support dual API (data-first and data-last) for pipe composition.
|
|
44
120
|
|
|
45
121
|
```typescript
|
|
46
122
|
import { Effect } from "effect";
|
|
47
123
|
import { NodeContext } from "@effect/platform-node";
|
|
48
124
|
import {
|
|
49
|
-
ManagedSection,
|
|
50
|
-
ManagedSectionLive,
|
|
51
|
-
SectionDefinition,
|
|
125
|
+
ManagedSection, ManagedSectionLive, SectionDefinition,
|
|
52
126
|
} from "@savvy-web/silk-effects";
|
|
53
127
|
|
|
54
|
-
|
|
128
|
+
// Define section identity
|
|
129
|
+
const def = SectionDefinition.make({ toolName: "LINT-STAGED" });
|
|
130
|
+
|
|
131
|
+
// Create a content block from the definition
|
|
55
132
|
const block = def.block("\nnpx lint-staged\n");
|
|
56
133
|
|
|
57
134
|
await Effect.runPromise(
|
|
58
135
|
Effect.gen(function* () {
|
|
59
136
|
const ms = yield* ManagedSection;
|
|
60
|
-
|
|
137
|
+
|
|
138
|
+
// Sync: creates the section if missing, updates if changed, no-op if identical
|
|
139
|
+
const result = yield* ms.sync(".husky/pre-commit", block);
|
|
140
|
+
// => SyncResult: Created | Updated | Unchanged
|
|
141
|
+
|
|
142
|
+
// Check: compare file content against expected block
|
|
143
|
+
const check = yield* ms.check(".husky/pre-commit", block);
|
|
144
|
+
// => CheckResult: Found | NotFound
|
|
61
145
|
}).pipe(
|
|
62
146
|
Effect.provide(ManagedSectionLive),
|
|
63
147
|
Effect.provide(NodeContext.layer),
|
|
@@ -65,23 +149,153 @@ await Effect.runPromise(
|
|
|
65
149
|
);
|
|
66
150
|
```
|
|
67
151
|
|
|
68
|
-
|
|
152
|
+
`SectionDefinition` also supports `//` comment style for JavaScript/TypeScript files:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const jsDef = SectionDefinition.make({ toolName: "MY-TOOL", commentStyle: "//" });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Use `ShellSectionDefinition` when the comment style is always `#` and should not be configurable.
|
|
159
|
+
|
|
160
|
+
#### VersioningStrategy
|
|
161
|
+
|
|
162
|
+
Classify the versioning strategy from changeset configuration. Outputs `"single"` (0-1 publishable packages), `"fixed-group"` (all packages in one fixed group), or `"independent"` (multiple packages, not in a single group). Falls back gracefully if config is missing.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { Effect } from "effect";
|
|
166
|
+
import { NodeContext } from "@effect/platform-node";
|
|
167
|
+
import {
|
|
168
|
+
VersioningStrategy, VersioningStrategyLive,
|
|
169
|
+
ChangesetConfigReaderLive,
|
|
170
|
+
} from "@savvy-web/silk-effects";
|
|
171
|
+
|
|
172
|
+
const result = await Effect.runPromise(
|
|
173
|
+
Effect.gen(function* () {
|
|
174
|
+
const vs = yield* VersioningStrategy;
|
|
175
|
+
return yield* vs.detect(publishablePackages, process.cwd());
|
|
176
|
+
}).pipe(
|
|
177
|
+
Effect.provide(VersioningStrategyLive),
|
|
178
|
+
Effect.provide(ChangesetConfigReaderLive),
|
|
179
|
+
Effect.provide(NodeContext.layer),
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
// => { strategy: "single" | "fixed-group" | "independent", ... }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### ChangesetConfigReader
|
|
69
186
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
187
|
+
Read and decode `.changeset/config.json`. Auto-detects whether the project uses `@savvy-web/changesets` (returning `SilkChangesetConfig` with `_isSilk: true`) or standard changesets (returning `ChangesetConfig`).
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { Effect } from "effect";
|
|
191
|
+
import { NodeContext } from "@effect/platform-node";
|
|
192
|
+
import {
|
|
193
|
+
ChangesetConfigReader, ChangesetConfigReaderLive,
|
|
194
|
+
} from "@savvy-web/silk-effects";
|
|
195
|
+
|
|
196
|
+
const config = await Effect.runPromise(
|
|
197
|
+
Effect.gen(function* () {
|
|
198
|
+
const reader = yield* ChangesetConfigReader;
|
|
199
|
+
return yield* reader.read(process.cwd());
|
|
200
|
+
}).pipe(
|
|
201
|
+
Effect.provide(ChangesetConfigReaderLive),
|
|
202
|
+
Effect.provide(NodeContext.layer),
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### ConfigDiscovery
|
|
208
|
+
|
|
209
|
+
Locate config files using a priority-based search convention. Checks `lib/configs/{name}` (shared configs) first, then `{cwd}/{name}` (local override).
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { Effect } from "effect";
|
|
213
|
+
import { NodeContext } from "@effect/platform-node";
|
|
214
|
+
import { ConfigDiscovery, ConfigDiscoveryLive } from "@savvy-web/silk-effects";
|
|
215
|
+
|
|
216
|
+
const result = await Effect.runPromise(
|
|
217
|
+
Effect.gen(function* () {
|
|
218
|
+
const cd = yield* ConfigDiscovery;
|
|
219
|
+
return yield* cd.find("biome.jsonc");
|
|
220
|
+
// => { path: "/project/biome.jsonc", source: "root" } | null
|
|
221
|
+
}).pipe(
|
|
222
|
+
Effect.provide(ConfigDiscoveryLive),
|
|
223
|
+
Effect.provide(NodeContext.layer),
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### BiomeSchemaSync
|
|
229
|
+
|
|
230
|
+
Keep Biome config `$schema` URLs current. Locates `biome.json` or `biome.jsonc`, compares the `$schema` value against the expected URL for the given version, and optionally updates in place. Strips semver range prefixes.
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { Effect } from "effect";
|
|
234
|
+
import { NodeContext } from "@effect/platform-node";
|
|
235
|
+
import { BiomeSchemaSync, BiomeSchemaSyncLive } from "@savvy-web/silk-effects";
|
|
236
|
+
|
|
237
|
+
await Effect.runPromise(
|
|
238
|
+
Effect.gen(function* () {
|
|
239
|
+
const bss = yield* BiomeSchemaSync;
|
|
240
|
+
const result = yield* bss.sync("2.0.0");
|
|
241
|
+
// => { updated: true, skipped: false, current: "2.0.0" }
|
|
242
|
+
}).pipe(
|
|
243
|
+
Effect.provide(BiomeSchemaSyncLive),
|
|
244
|
+
Effect.provide(NodeContext.layer),
|
|
245
|
+
),
|
|
246
|
+
);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### FileSystem + CommandExecutor Layer Required
|
|
252
|
+
|
|
253
|
+
#### ToolDiscovery
|
|
254
|
+
|
|
255
|
+
Locate CLI tools globally (PATH) or locally (via package manager), extract versions, enforce constraints, and cache results.
|
|
256
|
+
|
|
257
|
+
**ToolDefinition** configures how a tool is resolved: `VersionExtractor` (Flag, Json, or None), `ResolutionPolicy` (Report, PreferLocal, PreferGlobal, RequireMatch), and `SourceRequirement` (Any, OnlyLocal, OnlyGlobal, Both). Equality is based on tool name only.
|
|
258
|
+
|
|
259
|
+
**ResolvedTool** is the result of resolution. It carries the tool's name, source (`"global"` or `"local"`), version, and package manager. Its `exec()` and `dlx()` methods return a **ToolCommand** -- a wrapper around `@effect/platform` `Command` with instance-method ergonomics (`string()`, `lines()`, `exitCode()`, `stream()`).
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { Effect } from "effect";
|
|
263
|
+
import { NodeContext } from "@effect/platform-node";
|
|
264
|
+
import {
|
|
265
|
+
ToolDiscovery, ToolDiscoveryLive, ToolDefinition,
|
|
266
|
+
} from "@savvy-web/silk-effects";
|
|
267
|
+
|
|
268
|
+
const output = await Effect.runPromise(
|
|
269
|
+
Effect.gen(function* () {
|
|
270
|
+
const td = yield* ToolDiscovery;
|
|
271
|
+
|
|
272
|
+
// Resolve a tool (results are cached by name)
|
|
273
|
+
const biome = yield* td.resolve(ToolDefinition.make({ name: "biome" }));
|
|
274
|
+
|
|
275
|
+
// Check availability without throwing
|
|
276
|
+
const hasBiome = yield* td.isAvailable(ToolDefinition.make({ name: "biome" }));
|
|
277
|
+
|
|
278
|
+
// Execute the resolved tool
|
|
279
|
+
return yield* biome.exec("check", ".").string();
|
|
280
|
+
}).pipe(
|
|
281
|
+
Effect.provide(ToolDiscoveryLive),
|
|
282
|
+
Effect.provide(NodeContext.layer),
|
|
283
|
+
),
|
|
284
|
+
);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Use `require()` to fail with a descriptive error if the tool is not found:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
const biome = yield* td.require(
|
|
291
|
+
ToolDefinition.make({ name: "biome" }),
|
|
292
|
+
"Biome is required for linting",
|
|
293
|
+
);
|
|
294
|
+
```
|
|
81
295
|
|
|
82
296
|
## Documentation
|
|
83
297
|
|
|
84
|
-
For
|
|
298
|
+
For architecture details, service patterns, and design rationale, see the [design documentation](./.claude/design/silk-effects/architecture.md).
|
|
85
299
|
|
|
86
300
|
## License
|
|
87
301
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/silk-effects",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared Effect library for Silk Suite conventions",
|
|
6
6
|
"homepage": "https://github.com/savvy-web/systems/tree/main/packages/silk-effects",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@effect/platform": "^0.96.0",
|
|
31
31
|
"jsonc-effect": "^0.2.1",
|
|
32
32
|
"semver-effect": "^0.2.1",
|
|
33
|
-
"workspaces-effect": "^0.
|
|
33
|
+
"workspaces-effect": "^0.3.0",
|
|
34
34
|
"yaml-effect": "^0.2.3"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|