@savvy-web/silk-effects 0.2.2 → 0.4.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 CHANGED
@@ -1,122 +1,172 @@
1
1
  # @savvy-web/silk-effects
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@savvy-web/silk-effects)](https://www.npmjs.com/package/@savvy-web/silk-effects)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
3
+ [![npm](https://img.shields.io/npm/v/@savvy-web%2Fsilk-effects?label=npm&color=cb3837)](https://www.npmjs.com/package/@savvy-web/silk-effects)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-4caf50.svg)](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 file sections, config discovery, Biome schema synchronization, and CLI tool resolution. Platform-agnostic -- consumers provide their own runtime layer (`NodeContext`, `BunContext`, etc.).
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 and detect publishability from `package.json` with multi-registry support
10
+ - Detect a package's publish targets from its `package.json` `publishConfig`, with multi-registry support and a changeset-ignore-aware override for `workspaces-effect`'s `PublishabilityDetector`
11
+ - Read changeset config through a typed accessor service that reports silk vs vanilla mode, ignore patterns and fixed groups
11
12
  - Manage tool-owned sections in user-editable files without clobbering surrounding content
12
13
  - Discover and resolve CLI tools globally or locally with version enforcement and caching
13
14
  - Detect versioning strategy and format git tags from changeset configuration
14
15
  - Locate config files and keep Biome schema URLs in sync across workspaces
15
16
 
16
- ## Installation
17
+ ## Install
17
18
 
18
19
  ```bash
20
+ npm install @savvy-web/silk-effects effect @effect/platform @effect/platform-node
21
+ # or
19
22
  pnpm add @savvy-web/silk-effects effect @effect/platform @effect/platform-node
20
23
  ```
21
24
 
22
- `effect` is a peer dependency. Install a platform package (`@effect/platform-node`, `@effect/platform-bun`) matching your runtime.
25
+ `effect` and `@effect/platform` are peer dependencies. Install a platform package (`@effect/platform-node`, `@effect/platform-bun`) matching your runtime.
23
26
 
24
- ## Quick Start
27
+ ## Quick start
25
28
 
26
29
  All exports come from the package root:
27
30
 
28
31
  ```typescript
29
32
  import {
30
- TargetResolver, TargetResolverLive,
33
+ SilkPublishability,
31
34
  ManagedSection, ManagedSectionLive, SectionDefinition,
32
35
  ToolDiscovery, ToolDiscoveryLive, ToolDefinition,
33
36
  } from "@savvy-web/silk-effects";
34
37
  ```
35
38
 
39
+ `SilkPublishability.detect` is a pure static — no layers, no Effect runtime. Pass a package name and the raw `package.json` and get back the publish targets the silk rules resolve:
40
+
41
+ ```typescript
42
+ import { SilkPublishability } from "@savvy-web/silk-effects";
43
+
44
+ const targets = SilkPublishability.detect("@my-org/my-package", {
45
+ private: true,
46
+ publishConfig: { access: "public", targets: ["npm", "github"] },
47
+ });
48
+ // => [PublishTarget { name: "@my-org/my-package", registry: "https://registry.npmjs.org/", ... },
49
+ // PublishTarget { name: "@my-org/my-package", registry: "https://npm.pkg.github.com/", ... }]
50
+ ```
51
+
36
52
  ## Services
37
53
 
38
- The 9 services are grouped by which platform layers they require.
54
+ The services are grouped by which platform layers they require.
39
55
 
40
56
  ---
41
57
 
42
- ### No Platform Layer Required
58
+ ### No platform layer required
43
59
 
44
- These services are pure logic -- no filesystem or shell access needed.
60
+ These services are pure logic no filesystem or shell access needed.
45
61
 
46
- #### TargetResolver
62
+ #### SilkPublishability
47
63
 
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.
64
+ Apply silk publishability rules to a raw `package.json` and resolve its publish targets. Targets are `PublishTarget` records from `workspaces-effect` with `name`, `registry`, `directory`, `access` and `provenance` fields. The static `detect`, `expandShorthand` and `resolveTargetAccess` helpers are pure; `resolveTargets` and `listPublishable` are Effects that read from disk (see below).
65
+
66
+ In silk mode `private: true` is the norm on workspace `package.json` files. Publishability is derived from `publishConfig`, with the `private` flag consulted only as a last-resort default.
67
+
68
+ ```typescript
69
+ import { SilkPublishability } from "@savvy-web/silk-effects";
70
+
71
+ // Targets-first: one PublishTarget per surviving publishConfig.targets entry
72
+ const targets = SilkPublishability.detect("@my-org/pkg", {
73
+ private: true,
74
+ publishConfig: { access: "public", targets: ["npm", "github"] },
75
+ });
76
+ // => [PublishTarget { registry: "https://registry.npmjs.org/", access: "public", ... },
77
+ // PublishTarget { registry: "https://npm.pkg.github.com/", access: "public", ... }]
78
+
79
+ // Not publishable -> empty array
80
+ const none = SilkPublishability.detect("@my-org/internal", { private: true });
81
+ // => []
82
+ ```
83
+
84
+ See [Publishability](./docs/publishability.md) for the full rule order and the disk-reading helpers.
85
+
86
+ #### TagStrategy
87
+
88
+ 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.
49
89
 
50
90
  ```typescript
51
91
  import { Effect } from "effect";
52
- import { TargetResolver, TargetResolverLive } from "@savvy-web/silk-effects";
92
+ import { TagStrategy, TagStrategyLive } from "@savvy-web/silk-effects";
53
93
 
54
- const targets = await Effect.runPromise(
94
+ const tag = await Effect.runPromise(
55
95
  Effect.gen(function* () {
56
- const resolver = yield* TargetResolver;
57
- return yield* resolver.resolve(["npm", "github"]);
58
- }).pipe(Effect.provide(TargetResolverLive)),
96
+ const ts = yield* TagStrategy;
97
+ const strategy = yield* ts.determine(versioningResult);
98
+ return yield* ts.formatTag("@savvy-web/silk-effects", "1.0.0", strategy);
99
+ }).pipe(Effect.provide(TagStrategyLive)),
59
100
  );
60
- // => ResolvedTarget[] with registry, auth, provenance, etc.
101
+ // => "@savvy-web/silk-effects@1.0.0"
61
102
  ```
62
103
 
63
- #### SilkPublishabilityPlugin
104
+ ---
105
+
106
+ ### FileSystem layer required
107
+
108
+ These services read or write files. Provide a platform layer such as `NodeContext.layer` or `BunContext.layer`.
109
+
110
+ #### SilkPublishabilityDetectorLive and PublishabilityDetectorAdaptiveLive
64
111
 
65
- Detect whether a package is publishable from its `package.json` and resolve its targets. Delegates to `TargetResolver` internally.
112
+ `SilkPublishability.detect` is also exposed through `workspaces-effect`'s `PublishabilityDetector` Tag so consumers can swap silk rules into any program that already yields the detector. Two layers override the Tag:
66
113
 
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"`.
114
+ - `SilkPublishabilityDetectorLive` applies silk rules unconditionally. Requires `FileSystem`.
115
+ - `PublishabilityDetectorAdaptiveLive` — ignore-aware. Changeset-`ignore`d packages resolve to `[]`, then it dispatches by changeset mode (`none` → `[]`, `silk` → silk rules, `vanilla` → the `workspaces-effect` default). Requires `FileSystem` and `ChangesetConfig`.
68
116
 
69
117
  ```typescript
70
118
  import { Effect } from "effect";
71
- import {
72
- SilkPublishabilityPlugin, SilkPublishabilityPluginLive,
73
- TargetResolverLive,
74
- } from "@savvy-web/silk-effects";
119
+ import { NodeContext } from "@effect/platform-node";
120
+ import { PublishabilityDetector } from "workspaces-effect";
121
+ import { SilkPublishabilityDetectorLive } from "@savvy-web/silk-effects";
75
122
 
76
123
  const targets = await Effect.runPromise(
77
124
  Effect.gen(function* () {
78
- const plugin = yield* SilkPublishabilityPlugin;
79
- return yield* plugin.detect(packageJson);
125
+ const detector = yield* PublishabilityDetector;
126
+ return yield* detector.detect(pkg, root);
80
127
  }).pipe(
81
- Effect.provide(SilkPublishabilityPluginLive),
82
- Effect.provide(TargetResolverLive),
128
+ Effect.provide(SilkPublishabilityDetectorLive),
129
+ Effect.provide(NodeContext.layer),
83
130
  ),
84
131
  );
132
+ // => ReadonlyArray<PublishTarget>
85
133
  ```
86
134
 
87
- #### TagStrategy
135
+ See [Publishability](./docs/publishability.md) for the adaptive layer and the `ChangesetConfig` service.
88
136
 
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.
137
+ #### ChangesetConfig
138
+
139
+ Typed accessor over a workspace root's `.changeset/config.json`, reading through `ChangesetConfigReader` with a per-root cache. Every accessor is total — a missing or unreadable config collapses to `mode: "none"` and empty defaults. Methods: `mode`, `versionPrivate`, `ignorePatterns`, `isIgnored`, `fixed`, plus a static `ChangesetConfig.matches(name, pattern)`.
90
140
 
91
141
  ```typescript
92
142
  import { Effect } from "effect";
93
- import { TagStrategy, TagStrategyLive } from "@savvy-web/silk-effects";
143
+ import { NodeContext } from "@effect/platform-node";
144
+ import {
145
+ ChangesetConfig, ChangesetConfigLive, ChangesetConfigReaderLive,
146
+ } from "@savvy-web/silk-effects";
94
147
 
95
- const tag = await Effect.runPromise(
148
+ const mode = await Effect.runPromise(
96
149
  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)),
150
+ const config = yield* ChangesetConfig;
151
+ return yield* config.mode(process.cwd());
152
+ }).pipe(
153
+ Effect.provide(ChangesetConfigLive),
154
+ Effect.provide(ChangesetConfigReaderLive),
155
+ Effect.provide(NodeContext.layer),
156
+ ),
101
157
  );
102
- // => "@savvy-web/silk-effects@0.2.0"
158
+ // => "silk" | "vanilla" | "none"
103
159
  ```
104
160
 
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
161
  #### ManagedSection
112
162
 
113
163
  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
164
 
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()`.
165
+ `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
166
 
117
- **SectionBlock** represents the content between markers. It supports `diff()`, `prepend()`, and `append()` operations and uses normalized content for equality comparison.
167
+ `SectionBlock` represents the content between markers. It supports `diff()`, `prepend()` and `append()` operations and uses normalized content for equality comparison.
118
168
 
119
- Methods: `read`, `write`, `sync`, `check`, `isManaged` -- all support dual API (data-first and data-last) for pipe composition.
169
+ Methods: `read`, `write`, `sync`, `check`, `isManaged` all support dual API (data-first and data-last) for pipe composition.
120
170
 
121
171
  ```typescript
122
172
  import { Effect } from "effect";
@@ -159,7 +209,7 @@ Use `ShellSectionDefinition` when the comment style is always `#` and should not
159
209
 
160
210
  #### VersioningStrategy
161
211
 
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.
212
+ 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
213
 
164
214
  ```typescript
165
215
  import { Effect } from "effect";
@@ -184,7 +234,7 @@ const result = await Effect.runPromise(
184
234
 
185
235
  #### ChangesetConfigReader
186
236
 
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`).
237
+ Read and decode `.changeset/config.json`. Auto-detects whether the project uses `@savvy-web/changesets` (returning `SilkChangesetConfigFile` with `_isSilk: true`) or standard changesets (returning `ChangesetConfigFile`).
188
238
 
189
239
  ```typescript
190
240
  import { Effect } from "effect";
@@ -202,6 +252,7 @@ const config = await Effect.runPromise(
202
252
  Effect.provide(NodeContext.layer),
203
253
  ),
204
254
  );
255
+ // => ChangesetConfigFile | SilkChangesetConfigFile
205
256
  ```
206
257
 
207
258
  #### ConfigDiscovery
@@ -217,12 +268,12 @@ const result = await Effect.runPromise(
217
268
  Effect.gen(function* () {
218
269
  const cd = yield* ConfigDiscovery;
219
270
  return yield* cd.find("biome.jsonc");
220
- // => { path: "/project/biome.jsonc", source: "root" } | null
221
271
  }).pipe(
222
272
  Effect.provide(ConfigDiscoveryLive),
223
273
  Effect.provide(NodeContext.layer),
224
274
  ),
225
275
  );
276
+ // => { path: "/project/biome.jsonc", source: "root" } | null
226
277
  ```
227
278
 
228
279
  #### BiomeSchemaSync
@@ -234,29 +285,29 @@ import { Effect } from "effect";
234
285
  import { NodeContext } from "@effect/platform-node";
235
286
  import { BiomeSchemaSync, BiomeSchemaSyncLive } from "@savvy-web/silk-effects";
236
287
 
237
- await Effect.runPromise(
288
+ const result = await Effect.runPromise(
238
289
  Effect.gen(function* () {
239
290
  const bss = yield* BiomeSchemaSync;
240
- const result = yield* bss.sync("2.0.0");
241
- // => { updated: true, skipped: false, current: "2.0.0" }
291
+ return yield* bss.sync("2.0.0");
242
292
  }).pipe(
243
293
  Effect.provide(BiomeSchemaSyncLive),
244
294
  Effect.provide(NodeContext.layer),
245
295
  ),
246
296
  );
297
+ // => { updated: true, skipped: false, current: "2.0.0" }
247
298
  ```
248
299
 
249
300
  ---
250
301
 
251
- ### FileSystem + CommandExecutor Layer Required
302
+ ### FileSystem + CommandExecutor layer required
252
303
 
253
304
  #### ToolDiscovery
254
305
 
255
- Locate CLI tools globally (PATH) or locally (via package manager), extract versions, enforce constraints, and cache results.
306
+ Locate CLI tools globally (PATH) or locally (via package manager), extract versions, enforce constraints and cache results.
256
307
 
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.
308
+ `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
309
 
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()`).
310
+ `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
311
 
261
312
  ```typescript
262
313
  import { Effect } from "effect";
@@ -295,8 +346,17 @@ const biome = yield* td.require(
295
346
 
296
347
  ## Documentation
297
348
 
298
- For architecture details, service patterns, and design rationale, see the [design documentation](./.claude/design/silk-effects/architecture.md).
349
+ - [Overview](./docs/overview.md) what the library is, its design philosophy and platform-layer model
350
+ - [Publishability](./docs/publishability.md) — silk publishability rules, the detector overrides and the ChangesetConfig service
351
+ - [Changeset config](./docs/changeset-config.md) — reading and decoding `.changeset/config.json`
352
+ - [Platform layers](./docs/platform-layers.md) — composing layers and providing platform dependencies
353
+ - [Managed sections](./docs/managed-section.md) — tool-owned regions in user-editable files
354
+ - [Tool discovery](./docs/tool-discovery.md) — locating and resolving CLI tools
355
+ - [Versioning strategy](./docs/versioning-strategy.md) — classifying workspace versioning
356
+ - [Config discovery](./docs/config-discovery.md) — priority-based config file search
357
+ - [Biome sync](./docs/biome-sync.md) — keeping Biome `$schema` URLs current
358
+ - [Tag strategy](./docs/tag-strategy.md) — git tag naming and formatting
299
359
 
300
360
  ## License
301
361
 
302
- [MIT](./LICENSE)
362
+ [MIT](LICENSE)