@savvy-web/silk-effects 0.1.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/index.js CHANGED
@@ -1,6 +1,1020 @@
1
- export { BiomeSchemaSync, BiomeSchemaSyncLive, BiomeSyncError } from "./biome.js";
2
- export { ChangesetConfigError, ChangesetConfigReader, ChangesetConfigReaderLive, VersioningDetectionError, VersioningStrategy, VersioningStrategyLive } from "./versioning.js";
3
- export { ConfigDiscovery, ConfigDiscoveryLive, ConfigNotFoundError } from "./config.js";
4
- export { ManagedSection, ManagedSectionLive, ManagedSectionParseError, ManagedSectionWriteError } from "./hooks.js";
5
- export { PublishConfigError, SilkPublishabilityPlugin, SilkPublishabilityPluginLive, TargetResolutionError, TargetResolver, TargetResolverLive } from "./publish.js";
6
- export { TagFormatError, TagStrategy, TagStrategyLive } from "./tags.js";
1
+ import { Context, Data, Effect, Equal, Function, Hash, Layer, Option, Ref, Schema } from "effect";
2
+ import { Command, CommandExecutor, FileSystem } from "@effect/platform";
3
+ import { parse } from "jsonc-effect";
4
+ import { PackageManagerDetector, WorkspaceRoot } from "workspaces-effect";
5
+ class BiomeSyncError extends Data.TaggedError("BiomeSyncError") {
6
+ get message() {
7
+ return `Failed to sync biome schema in ${this.path}: ${this.reason}`;
8
+ }
9
+ }
10
+ class ChangesetConfigError extends Data.TaggedError("ChangesetConfigError") {
11
+ get message() {
12
+ return `Failed to read changeset config at ${this.path}: ${this.reason}`;
13
+ }
14
+ }
15
+ class ConfigNotFoundError extends Data.TaggedError("ConfigNotFoundError") {
16
+ get message() {
17
+ return `Config '${this.name}' not found. Searched: ${this.searchedPaths.join(", ")}`;
18
+ }
19
+ }
20
+ class PublishConfigError extends Data.TaggedError("PublishConfigError") {
21
+ get message() {
22
+ return `Invalid publishConfig for ${this.packageName}: ${this.reason}`;
23
+ }
24
+ }
25
+ class SectionParseError extends Data.TaggedError("SectionParseError") {
26
+ get message() {
27
+ return `Failed to parse section in ${this.path}: ${this.reason}`;
28
+ }
29
+ }
30
+ class SectionValidationError extends Data.TaggedError("SectionValidationError") {
31
+ get message() {
32
+ return `Section validation failed for ${this.toolName}: ${this.reason}`;
33
+ }
34
+ }
35
+ class SectionWriteError extends Data.TaggedError("SectionWriteError") {
36
+ get message() {
37
+ return `Failed to write section to ${this.path}: ${this.reason}`;
38
+ }
39
+ }
40
+ class TagFormatError extends Data.TaggedError("TagFormatError") {
41
+ get message() {
42
+ return `Failed to format tag for ${this.name}@${this.version}: ${this.reason}`;
43
+ }
44
+ }
45
+ class TargetResolutionError extends Data.TaggedError("TargetResolutionError") {
46
+ get message() {
47
+ return `Failed to resolve publish target: ${this.reason}`;
48
+ }
49
+ }
50
+ class ToolNotFoundError extends Data.TaggedError("ToolNotFoundError") {
51
+ get message() {
52
+ return `Tool not found: ${this.name} — ${this.reason}`;
53
+ }
54
+ }
55
+ class ToolResolutionError extends Data.TaggedError("ToolResolutionError") {
56
+ get message() {
57
+ return `Tool resolution failed: ${this.name} — ${this.reason}`;
58
+ }
59
+ }
60
+ class ToolVersionMismatchError extends Data.TaggedError("ToolVersionMismatchError") {
61
+ get message() {
62
+ return `Tool version mismatch: ${this.name} — global ${this.globalVersion} vs local ${this.localVersion}`;
63
+ }
64
+ }
65
+ class VersioningDetectionError extends Data.TaggedError("VersioningDetectionError") {
66
+ get message() {
67
+ return `Failed to detect versioning strategy: ${this.reason}`;
68
+ }
69
+ }
70
+ const PublishProtocol = Schema.Literal("npm", "jsr");
71
+ const AuthStrategy = Schema.Literal("oidc", "token");
72
+ const PublishTargetObject = Schema.Struct({
73
+ protocol: Schema.optionalWith(PublishProtocol, {
74
+ default: ()=>"npm"
75
+ }),
76
+ registry: Schema.optional(Schema.String),
77
+ directory: Schema.optional(Schema.String),
78
+ access: Schema.optional(Schema.Literal("public", "restricted")),
79
+ provenance: Schema.optional(Schema.Boolean),
80
+ tag: Schema.optional(Schema.String)
81
+ });
82
+ const PublishTargetShorthand = Schema.Literal("npm", "github", "jsr");
83
+ const PublishTarget = Schema.Union(PublishTargetShorthand, Schema.String.pipe(Schema.filter((s)=>s.startsWith("https://"))), PublishTargetObject);
84
+ const ResolvedTarget = Schema.Struct({
85
+ protocol: PublishProtocol,
86
+ registry: Schema.NullOr(Schema.String),
87
+ directory: Schema.String,
88
+ access: Schema.Literal("public", "restricted"),
89
+ provenance: Schema.Boolean,
90
+ tag: Schema.String,
91
+ auth: AuthStrategy,
92
+ tokenEnv: Schema.NullOr(Schema.String)
93
+ });
94
+ class ToolCommand {
95
+ command;
96
+ constructor(command){
97
+ this.command = command;
98
+ }
99
+ string(encoding) {
100
+ return Command.string(this.command, encoding);
101
+ }
102
+ exitCode() {
103
+ return Command.exitCode(this.command);
104
+ }
105
+ lines(encoding) {
106
+ return Command.lines(this.command, encoding);
107
+ }
108
+ stream() {
109
+ return Command.stream(this.command);
110
+ }
111
+ env(environment) {
112
+ return new ToolCommand(Command.env(this.command, environment));
113
+ }
114
+ workingDirectory(cwd) {
115
+ return new ToolCommand(Command.workingDirectory(this.command, cwd));
116
+ }
117
+ stdin(input) {
118
+ return new ToolCommand(Command.feed(this.command, input));
119
+ }
120
+ }
121
+ const ToolSource = Schema.Literal("global", "local");
122
+ const VersionExtractor = Data.taggedEnum();
123
+ const ResolutionPolicy = Data.taggedEnum();
124
+ const SourceRequirement = Data.taggedEnum();
125
+ var _computedKey, _computedKey1;
126
+ const PackageManager = Schema.Literal("npm", "pnpm", "yarn", "bun");
127
+ _computedKey = Equal.symbol, _computedKey1 = Hash.symbol;
128
+ class ResolvedTool extends Schema.TaggedClass()("ResolvedTool", {
129
+ name: Schema.String,
130
+ source: ToolSource,
131
+ version: Schema.OptionFromSelf(Schema.String),
132
+ globalVersion: Schema.OptionFromSelf(Schema.String),
133
+ localVersion: Schema.OptionFromSelf(Schema.String),
134
+ packageManager: PackageManager,
135
+ mismatch: Schema.Boolean
136
+ }) {
137
+ get isGlobal() {
138
+ return "global" === this.source;
139
+ }
140
+ get isLocal() {
141
+ return "local" === this.source;
142
+ }
143
+ get hasVersionMismatch() {
144
+ return this.mismatch;
145
+ }
146
+ exec(...args) {
147
+ if ("global" === this.source) return new ToolCommand(Command.make(this.name, ...args));
148
+ switch(this.packageManager){
149
+ case "pnpm":
150
+ return new ToolCommand(Command.make("pnpm", "exec", this.name, ...args));
151
+ case "npm":
152
+ return new ToolCommand(Command.make("npx", "--no", "--", this.name, ...args));
153
+ case "yarn":
154
+ return new ToolCommand(Command.make("yarn", "exec", this.name, ...args));
155
+ case "bun":
156
+ return new ToolCommand(Command.make("bun", "x", "--no-install", this.name, ...args));
157
+ }
158
+ }
159
+ dlx(...args) {
160
+ switch(this.packageManager){
161
+ case "pnpm":
162
+ return new ToolCommand(Command.make("pnpm", "dlx", this.name, ...args));
163
+ case "npm":
164
+ return new ToolCommand(Command.make("npx", this.name, ...args));
165
+ case "yarn":
166
+ return new ToolCommand(Command.make("yarn", "dlx", this.name, ...args));
167
+ case "bun":
168
+ return new ToolCommand(Command.make("bun", "x", this.name, ...args));
169
+ }
170
+ }
171
+ [_computedKey](that) {
172
+ if (!(that instanceof ResolvedTool)) return false;
173
+ return this.name === that.name && this.source === that.source && Equal.equals(this.version, that.version);
174
+ }
175
+ [_computedKey1]() {
176
+ let h = Hash.hash(this.name);
177
+ h = Hash.combine(h)(Hash.hash(this.source));
178
+ h = Hash.combine(h)(Hash.hash(this.version));
179
+ return Hash.cached(this)(h);
180
+ }
181
+ }
182
+ const CommentStyle = Schema.Literal("#", "//");
183
+ const SectionDiff = Data.taggedEnum();
184
+ const SyncResult = Data.taggedEnum();
185
+ const CheckResult = Data.taggedEnum();
186
+ var SectionBlock_computedKey, SectionBlock_computedKey1;
187
+ SectionBlock_computedKey = Equal.symbol, SectionBlock_computedKey1 = Hash.symbol;
188
+ class SectionBlock extends Schema.TaggedClass()("SectionBlock", {
189
+ toolName: Schema.String,
190
+ commentStyle: CommentStyle,
191
+ content: Schema.String
192
+ }) {
193
+ static diff = Function.dual(2, (self, that)=>self.diff(that));
194
+ static prepend = Function.dual(2, (self, lines)=>self.prepend(lines));
195
+ static append = Function.dual(2, (self, lines)=>self.append(lines));
196
+ get text() {
197
+ return this.content;
198
+ }
199
+ get normalized() {
200
+ return this.content.trim().replace(/\s+/g, " ");
201
+ }
202
+ get rendered() {
203
+ const begin = `${this.commentStyle} --- BEGIN ${this.toolName.toUpperCase()} MANAGED SECTION ---`;
204
+ const end = `${this.commentStyle} --- END ${this.toolName.toUpperCase()} MANAGED SECTION ---`;
205
+ return `${begin}${this.content}${end}`;
206
+ }
207
+ prepend(lines) {
208
+ return SectionBlock.make({
209
+ toolName: this.toolName,
210
+ commentStyle: this.commentStyle,
211
+ content: `${lines}\n${this.content}`
212
+ });
213
+ }
214
+ append(lines) {
215
+ return SectionBlock.make({
216
+ toolName: this.toolName,
217
+ commentStyle: this.commentStyle,
218
+ content: `${this.content}\n${lines}`
219
+ });
220
+ }
221
+ diff(that) {
222
+ if (this.normalized === that.normalized) return SectionDiff.Unchanged();
223
+ const selfLines = this.content.trim().split("\n");
224
+ const thatLines = that.content.trim().split("\n");
225
+ const selfSet = new Set(selfLines);
226
+ const thatSet = new Set(thatLines);
227
+ const removed = selfLines.filter((line)=>!thatSet.has(line));
228
+ const added = thatLines.filter((line)=>!selfSet.has(line));
229
+ return SectionDiff.Changed({
230
+ added,
231
+ removed
232
+ });
233
+ }
234
+ [SectionBlock_computedKey](that) {
235
+ if (!(that instanceof SectionBlock)) return false;
236
+ return this.normalized === that.normalized;
237
+ }
238
+ [SectionBlock_computedKey1]() {
239
+ return Hash.cached(this)(Hash.hash(this.normalized));
240
+ }
241
+ }
242
+ var SectionDefinition_computedKey, SectionDefinition_computedKey1;
243
+ SectionDefinition_computedKey = Equal.symbol, SectionDefinition_computedKey1 = Hash.symbol;
244
+ class SectionDefinition extends Schema.TaggedClass()("SectionDefinition", {
245
+ toolName: Schema.String,
246
+ commentStyle: Schema.optionalWith(CommentStyle, {
247
+ default: ()=>"#"
248
+ })
249
+ }) {
250
+ _validate;
251
+ static generate = Function.dual(2, (self, fn)=>(config)=>self.block(fn(config)));
252
+ static generateEffect = Function.dual(2, (self, fn)=>(config)=>Effect.flatMap(fn(config), (content)=>{
253
+ try {
254
+ return Effect.succeed(self.block(content));
255
+ } catch (e) {
256
+ return Effect.fail(e);
257
+ }
258
+ }));
259
+ static withValidation = Function.dual(2, (self, fn)=>{
260
+ const copy = SectionDefinition.make({
261
+ toolName: self.toolName,
262
+ commentStyle: self.commentStyle
263
+ });
264
+ copy._validate = fn;
265
+ return copy;
266
+ });
267
+ static diff = Function.dual(2, (self, that)=>self.diff(that));
268
+ block(content) {
269
+ const block = SectionBlock.make({
270
+ toolName: this.toolName,
271
+ commentStyle: this.commentStyle,
272
+ content
273
+ });
274
+ if (this._validate && !this._validate(block)) throw new SectionValidationError({
275
+ toolName: this.toolName,
276
+ reason: "Content failed validation"
277
+ });
278
+ return block;
279
+ }
280
+ generate(fn) {
281
+ return (config)=>this.block(fn(config));
282
+ }
283
+ generateEffect(fn) {
284
+ return (config)=>Effect.flatMap(fn(config), (content)=>{
285
+ try {
286
+ return Effect.succeed(this.block(content));
287
+ } catch (e) {
288
+ return Effect.fail(e);
289
+ }
290
+ });
291
+ }
292
+ diff(that) {
293
+ if (Equal.equals(this, that)) return SectionDiff.Unchanged();
294
+ return SectionDiff.Changed({
295
+ added: [
296
+ that.toolName !== this.toolName ? `toolName: ${that.toolName}` : "",
297
+ that.commentStyle !== this.commentStyle ? `commentStyle: ${that.commentStyle}` : ""
298
+ ].filter(Boolean),
299
+ removed: [
300
+ that.toolName !== this.toolName ? `toolName: ${this.toolName}` : "",
301
+ that.commentStyle !== this.commentStyle ? `commentStyle: ${this.commentStyle}` : ""
302
+ ].filter(Boolean)
303
+ });
304
+ }
305
+ get beginMarker() {
306
+ return `${this.commentStyle} --- BEGIN ${this.toolName.toUpperCase()} MANAGED SECTION ---`;
307
+ }
308
+ get endMarker() {
309
+ return `${this.commentStyle} --- END ${this.toolName.toUpperCase()} MANAGED SECTION ---`;
310
+ }
311
+ [SectionDefinition_computedKey](that) {
312
+ if (!(that instanceof SectionDefinition)) return false;
313
+ return this.toolName === that.toolName && this.commentStyle === that.commentStyle;
314
+ }
315
+ [SectionDefinition_computedKey1]() {
316
+ return Hash.cached(this)(Hash.combine(Hash.hash(this.toolName))(Hash.hash(this.commentStyle)));
317
+ }
318
+ }
319
+ class ShellSectionDefinition extends Schema.TaggedClass()("ShellSectionDefinition", {
320
+ toolName: Schema.String
321
+ }) {
322
+ get commentStyle() {
323
+ return "#";
324
+ }
325
+ block(content) {
326
+ return SectionBlock.make({
327
+ toolName: this.toolName,
328
+ commentStyle: "#",
329
+ content
330
+ });
331
+ }
332
+ generate(fn) {
333
+ return (config)=>this.block(fn(config));
334
+ }
335
+ generateEffect(fn) {
336
+ return (config)=>Effect.map(fn(config), (content)=>this.block(content));
337
+ }
338
+ get beginMarker() {
339
+ return `# --- BEGIN ${this.toolName.toUpperCase()} MANAGED SECTION ---`;
340
+ }
341
+ get endMarker() {
342
+ return `# --- END ${this.toolName.toUpperCase()} MANAGED SECTION ---`;
343
+ }
344
+ }
345
+ var ToolDefinition_computedKey, ToolDefinition_computedKey1;
346
+ const NameSchema = Schema.Struct({
347
+ name: Schema.String
348
+ });
349
+ ToolDefinition_computedKey = Equal.symbol, ToolDefinition_computedKey1 = Hash.symbol;
350
+ class ToolDefinition {
351
+ _tag = "ToolDefinition";
352
+ name;
353
+ versionExtractor;
354
+ policy;
355
+ source;
356
+ constructor(name, versionExtractor, policy, source){
357
+ this.name = name;
358
+ this.versionExtractor = versionExtractor;
359
+ this.policy = policy;
360
+ this.source = source;
361
+ }
362
+ static make(options) {
363
+ Schema.decodeUnknownSync(NameSchema)({
364
+ name: options.name
365
+ });
366
+ return new ToolDefinition(options.name, options.versionExtractor ?? VersionExtractor.Flag({
367
+ flag: "--version"
368
+ }), options.policy ?? ResolutionPolicy.Report(), options.source ?? SourceRequirement.Any());
369
+ }
370
+ [ToolDefinition_computedKey](that) {
371
+ if (!(that instanceof ToolDefinition)) return false;
372
+ return this.name === that.name;
373
+ }
374
+ [ToolDefinition_computedKey1]() {
375
+ return Hash.cached(this)(Hash.hash(this.name));
376
+ }
377
+ }
378
+ function extractSemver(version) {
379
+ return version.replace(/^[\^~>=<v]+/, "");
380
+ }
381
+ function buildSchemaUrl(version) {
382
+ return `https://biomejs.dev/schemas/${version}/schema.json`;
383
+ }
384
+ const BIOME_SCHEMA_HOSTNAME = "biomejs.dev";
385
+ function findBiomeConfigs(cwd, fs) {
386
+ const candidates = [
387
+ `${cwd}/biome.json`,
388
+ `${cwd}/biome.jsonc`
389
+ ];
390
+ return Effect.gen(function*() {
391
+ const results = [];
392
+ for (const candidate of candidates){
393
+ const exists = yield* fs.exists(candidate).pipe(Effect.orElseSucceed(()=>false));
394
+ if (exists) results.push(candidate);
395
+ }
396
+ return results;
397
+ });
398
+ }
399
+ class BiomeSchemaSync extends Context.Tag("@savvy-web/silk-effects/BiomeSchemaSync")() {
400
+ }
401
+ const BiomeSchemaSyncLive = Layer.effect(BiomeSchemaSync, Effect.gen(function*() {
402
+ const fs = yield* FileSystem.FileSystem;
403
+ const run = (version, options, write)=>Effect.gen(function*() {
404
+ const cwd = options?.cwd ?? process.cwd();
405
+ const semver = extractSemver(version);
406
+ const expectedUrl = buildSchemaUrl(semver);
407
+ const configs = yield* findBiomeConfigs(cwd, fs);
408
+ const updated = [];
409
+ const skipped = [];
410
+ const current = [];
411
+ for (const configPath of configs){
412
+ const raw = yield* fs.readFileString(configPath).pipe(Effect.mapError((cause)=>new BiomeSyncError({
413
+ path: configPath,
414
+ reason: String(cause)
415
+ })));
416
+ const parsed = yield* parse(raw).pipe(Effect.mapError((e)=>new BiomeSyncError({
417
+ path: configPath,
418
+ reason: `Failed to parse JSONC: ${String(e)}`
419
+ })));
420
+ const schema = parsed.$schema;
421
+ if ("string" != typeof schema) {
422
+ skipped.push(configPath);
423
+ continue;
424
+ }
425
+ if (!schema.includes(BIOME_SCHEMA_HOSTNAME)) {
426
+ skipped.push(configPath);
427
+ continue;
428
+ }
429
+ if (schema === expectedUrl) {
430
+ current.push(configPath);
431
+ continue;
432
+ }
433
+ if (write) {
434
+ const updated_content = raw.replaceAll(schema, expectedUrl);
435
+ yield* fs.writeFileString(configPath, updated_content).pipe(Effect.mapError((cause)=>new BiomeSyncError({
436
+ path: configPath,
437
+ reason: String(cause)
438
+ })));
439
+ }
440
+ updated.push(configPath);
441
+ }
442
+ return {
443
+ updated,
444
+ skipped,
445
+ current
446
+ };
447
+ });
448
+ return {
449
+ sync: (version, options)=>run(version, options, true),
450
+ check: (version, options)=>run(version, options, false)
451
+ };
452
+ }));
453
+ const ChangesetConfig = Schema.Struct({
454
+ changelog: Schema.optional(Schema.Union(Schema.String, Schema.Array(Schema.Unknown))),
455
+ commit: Schema.optional(Schema.Boolean),
456
+ fixed: Schema.optional(Schema.Array(Schema.Array(Schema.String))),
457
+ linked: Schema.optional(Schema.Array(Schema.Array(Schema.String))),
458
+ access: Schema.optional(Schema.Literal("public", "restricted")),
459
+ baseBranch: Schema.optional(Schema.String),
460
+ updateInternalDependencies: Schema.optional(Schema.Literal("patch", "minor", "major")),
461
+ ignore: Schema.optional(Schema.Array(Schema.String))
462
+ });
463
+ const SilkChangesetConfig = Schema.extend(ChangesetConfig, Schema.Struct({
464
+ _isSilk: Schema.optionalWith(Schema.Boolean, {
465
+ default: ()=>true
466
+ })
467
+ }));
468
+ const VersioningStrategyType = Schema.Literal("single", "fixed-group", "independent");
469
+ Schema.Struct({
470
+ type: VersioningStrategyType,
471
+ fixedGroups: Schema.Array(Schema.Array(Schema.String)),
472
+ publishablePackages: Schema.Array(Schema.String)
473
+ });
474
+ const SILK_CHANGELOG_MARKER = "@savvy-web/changesets";
475
+ function isSilkChangelog(changelog) {
476
+ if ("string" == typeof changelog) return changelog.includes(SILK_CHANGELOG_MARKER);
477
+ if (Array.isArray(changelog) && changelog.length > 0) return "string" == typeof changelog[0] && changelog[0].includes(SILK_CHANGELOG_MARKER);
478
+ return false;
479
+ }
480
+ class ChangesetConfigReader extends Context.Tag("@savvy-web/silk-effects/ChangesetConfigReader")() {
481
+ }
482
+ const ChangesetConfigReaderLive = Layer.effect(ChangesetConfigReader, Effect.gen(function*() {
483
+ const fs = yield* FileSystem.FileSystem;
484
+ const read = (root)=>{
485
+ const configPath = `${root}/.changeset/config.json`;
486
+ return Effect.gen(function*() {
487
+ const exists = yield* fs.exists(configPath).pipe(Effect.mapError((cause)=>new ChangesetConfigError({
488
+ path: configPath,
489
+ reason: String(cause)
490
+ })));
491
+ if (!exists) return yield* Effect.fail(new ChangesetConfigError({
492
+ path: configPath,
493
+ reason: "File not found"
494
+ }));
495
+ const raw = yield* fs.readFileString(configPath).pipe(Effect.mapError((cause)=>new ChangesetConfigError({
496
+ path: configPath,
497
+ reason: String(cause)
498
+ })));
499
+ const parsed = yield* Effect["try"]({
500
+ try: ()=>JSON.parse(raw),
501
+ catch: (cause)=>new ChangesetConfigError({
502
+ path: configPath,
503
+ reason: `Invalid JSON: ${String(cause)}`
504
+ })
505
+ });
506
+ const rawConfig = parsed;
507
+ if (isSilkChangelog(rawConfig.changelog)) return yield* Schema.decodeUnknown(SilkChangesetConfig)(parsed).pipe(Effect.mapError((cause)=>new ChangesetConfigError({
508
+ path: configPath,
509
+ reason: `Schema decode failed: ${String(cause)}`
510
+ })));
511
+ return yield* Schema.decodeUnknown(ChangesetConfig)(parsed).pipe(Effect.mapError((cause)=>new ChangesetConfigError({
512
+ path: configPath,
513
+ reason: `Schema decode failed: ${String(cause)}`
514
+ })));
515
+ });
516
+ };
517
+ return {
518
+ read
519
+ };
520
+ }));
521
+ class ConfigDiscovery extends Context.Tag("@savvy-web/silk-effects/ConfigDiscovery")() {
522
+ }
523
+ function safeExists(fs, path) {
524
+ return fs.exists(path).pipe(Effect.orElseSucceed(()=>false));
525
+ }
526
+ const ConfigDiscoveryLive = Layer.effect(ConfigDiscovery, Effect.gen(function*() {
527
+ const fs = yield* FileSystem.FileSystem;
528
+ const findAll = (name, options)=>Effect.gen(function*() {
529
+ const cwd = options?.cwd ?? process.cwd();
530
+ const results = [];
531
+ const libPath = `${cwd}/lib/configs/${name}`;
532
+ const libExists = yield* safeExists(fs, libPath);
533
+ if (libExists) results.push({
534
+ path: libPath,
535
+ source: "lib"
536
+ });
537
+ const rootPath = `${cwd}/${name}`;
538
+ const rootExists = yield* safeExists(fs, rootPath);
539
+ if (rootExists) results.push({
540
+ path: rootPath,
541
+ source: "root"
542
+ });
543
+ return results;
544
+ });
545
+ const find = (name, options)=>findAll(name, options).pipe(Effect.map((results)=>results[0] ?? null));
546
+ return {
547
+ find,
548
+ findAll
549
+ };
550
+ }));
551
+ function beginMarker(toolName, commentStyle) {
552
+ return `${commentStyle} --- BEGIN ${toolName.toUpperCase()} MANAGED SECTION ---`;
553
+ }
554
+ function endMarker(toolName, commentStyle) {
555
+ return `${commentStyle} --- END ${toolName.toUpperCase()} MANAGED SECTION ---`;
556
+ }
557
+ function parseContent(content, toolName, commentStyle) {
558
+ const begin = beginMarker(toolName, commentStyle);
559
+ const end = endMarker(toolName, commentStyle);
560
+ const beginIndex = content.indexOf(begin);
561
+ const endIndex = content.indexOf(end);
562
+ if (-1 === beginIndex || -1 === endIndex || endIndex <= beginIndex) return null;
563
+ return {
564
+ before: content.slice(0, beginIndex),
565
+ managed: content.slice(beginIndex + begin.length, endIndex),
566
+ after: content.slice(endIndex + end.length)
567
+ };
568
+ }
569
+ function assembleContent(before, managed, after, toolName, commentStyle) {
570
+ const begin = beginMarker(toolName, commentStyle);
571
+ const end = endMarker(toolName, commentStyle);
572
+ return `${before}${begin}${managed}${end}${after}`;
573
+ }
574
+ class ManagedSection extends Context.Tag("@savvy-web/silk-effects/ManagedSection")() {
575
+ }
576
+ const ManagedSectionLive = Layer.effect(ManagedSection, Effect.gen(function*() {
577
+ const fs = yield* FileSystem.FileSystem;
578
+ const read = Function.dual(2, (path, definition)=>Effect.gen(function*() {
579
+ const exists = yield* fs.exists(path).pipe(Effect.orElseSucceed(()=>false));
580
+ if (!exists) return null;
581
+ const raw = yield* fs.readFileString(path).pipe(Effect.mapError((cause)=>new SectionParseError({
582
+ path,
583
+ reason: String(cause)
584
+ })));
585
+ const parsed = parseContent(raw, definition.toolName, definition.commentStyle);
586
+ if (null === parsed) return null;
587
+ return SectionBlock.make({
588
+ toolName: definition.toolName,
589
+ commentStyle: definition.commentStyle,
590
+ content: parsed.managed
591
+ });
592
+ }));
593
+ const isManaged = Function.dual(2, (path, definition)=>Effect.gen(function*() {
594
+ const exists = yield* fs.exists(path).pipe(Effect.orElseSucceed(()=>false));
595
+ if (!exists) return false;
596
+ const raw = yield* fs.readFileString(path).pipe(Effect.orElseSucceed(()=>""));
597
+ const begin = beginMarker(definition.toolName, definition.commentStyle);
598
+ const end = endMarker(definition.toolName, definition.commentStyle);
599
+ const beginIdx = raw.indexOf(begin);
600
+ const endIdx = raw.indexOf(end);
601
+ return -1 !== beginIdx && -1 !== endIdx && endIdx > beginIdx;
602
+ }));
603
+ const write = Function.dual(2, (path, block)=>Effect.gen(function*() {
604
+ const exists = yield* fs.exists(path).pipe(Effect.orElseSucceed(()=>false));
605
+ let fileContent;
606
+ if (exists) {
607
+ const raw = yield* fs.readFileString(path).pipe(Effect.mapError((cause)=>new SectionWriteError({
608
+ path,
609
+ reason: String(cause)
610
+ })));
611
+ const parsed = parseContent(raw, block.toolName, block.commentStyle);
612
+ if (null !== parsed) fileContent = assembleContent(parsed.before, block.content, parsed.after, block.toolName, block.commentStyle);
613
+ else {
614
+ const trimmed = raw.trimEnd();
615
+ const begin = beginMarker(block.toolName, block.commentStyle);
616
+ const end = endMarker(block.toolName, block.commentStyle);
617
+ fileContent = `${trimmed}\n\n${begin}${block.content}${end}\n`;
618
+ }
619
+ } else {
620
+ const begin = beginMarker(block.toolName, block.commentStyle);
621
+ const end = endMarker(block.toolName, block.commentStyle);
622
+ fileContent = `${begin}${block.content}${end}\n`;
623
+ }
624
+ yield* fs.writeFileString(path, fileContent).pipe(Effect.mapError((cause)=>new SectionWriteError({
625
+ path,
626
+ reason: String(cause)
627
+ })));
628
+ }));
629
+ const sync = Function.dual(2, (path, block)=>Effect.gen(function*() {
630
+ const onDisk = yield* read(path, {
631
+ toolName: block.toolName,
632
+ commentStyle: block.commentStyle
633
+ }).pipe(Effect.mapError((cause)=>new SectionWriteError({
634
+ path,
635
+ reason: String(cause)
636
+ })));
637
+ if (null === onDisk) {
638
+ yield* write(path, block);
639
+ return SyncResult.Created();
640
+ }
641
+ if (Equal.equals(onDisk, block)) return SyncResult.Unchanged();
642
+ const d = SectionBlock.diff(onDisk, block);
643
+ yield* write(path, block);
644
+ return SyncResult.Updated({
645
+ diff: d
646
+ });
647
+ }));
648
+ const check = Function.dual(2, (path, block)=>Effect.gen(function*() {
649
+ const onDisk = yield* read(path, {
650
+ toolName: block.toolName,
651
+ commentStyle: block.commentStyle
652
+ });
653
+ if (null === onDisk) return CheckResult.NotFound();
654
+ const isUpToDate = Equal.equals(onDisk, block);
655
+ const d = SectionBlock.diff(onDisk, block);
656
+ return CheckResult.Found({
657
+ isUpToDate,
658
+ diff: d
659
+ });
660
+ }));
661
+ return {
662
+ read,
663
+ write,
664
+ isManaged,
665
+ sync,
666
+ check
667
+ };
668
+ }));
669
+ class TargetResolver extends Context.Tag("@savvy-web/silk-effects/TargetResolver")() {
670
+ }
671
+ const DEFAULTS = {
672
+ directory: "dist/npm",
673
+ access: "public",
674
+ provenance: false,
675
+ tag: "latest"
676
+ };
677
+ function deriveTokenEnv(registryUrl) {
678
+ try {
679
+ const hostname = new URL(registryUrl).hostname;
680
+ return `NPM_TOKEN_${hostname.replace(/\./g, "_").toUpperCase()}`;
681
+ } catch {
682
+ return "NPM_TOKEN";
683
+ }
684
+ }
685
+ function resolveOne(target) {
686
+ if ("npm" === target) return Effect.succeed({
687
+ ...DEFAULTS,
688
+ protocol: "npm",
689
+ registry: "https://registry.npmjs.org/",
690
+ auth: "oidc",
691
+ tokenEnv: null
692
+ });
693
+ if ("github" === target) return Effect.succeed({
694
+ ...DEFAULTS,
695
+ protocol: "npm",
696
+ registry: "https://npm.pkg.github.com/",
697
+ auth: "token",
698
+ tokenEnv: "GITHUB_TOKEN"
699
+ });
700
+ if ("jsr" === target) return Effect.succeed({
701
+ ...DEFAULTS,
702
+ protocol: "jsr",
703
+ registry: null,
704
+ auth: "oidc",
705
+ tokenEnv: null
706
+ });
707
+ if ("string" == typeof target && target.startsWith("https://")) return Effect.succeed({
708
+ ...DEFAULTS,
709
+ protocol: "npm",
710
+ registry: target,
711
+ auth: "token",
712
+ tokenEnv: deriveTokenEnv(target)
713
+ });
714
+ if ("object" == typeof target && null !== target && !Array.isArray(target)) {
715
+ const obj = target;
716
+ const protocol = obj.protocol ?? "npm";
717
+ const registry = obj.registry ?? null;
718
+ const directory = obj.directory ?? DEFAULTS.directory;
719
+ const access = obj.access ?? DEFAULTS.access;
720
+ const provenance = obj.provenance ?? DEFAULTS.provenance;
721
+ const tag = obj.tag ?? DEFAULTS.tag;
722
+ let auth;
723
+ let tokenEnv;
724
+ if (null !== registry) try {
725
+ const hostname = new URL(registry).hostname;
726
+ if ("npm.pkg.github.com" === hostname) {
727
+ auth = "token";
728
+ tokenEnv = "GITHUB_TOKEN";
729
+ } else {
730
+ auth = "oidc";
731
+ tokenEnv = null;
732
+ }
733
+ } catch {
734
+ auth = "oidc";
735
+ tokenEnv = null;
736
+ }
737
+ else {
738
+ auth = "oidc";
739
+ tokenEnv = null;
740
+ }
741
+ return Effect.succeed({
742
+ protocol,
743
+ registry,
744
+ directory,
745
+ access,
746
+ provenance,
747
+ tag,
748
+ auth,
749
+ tokenEnv
750
+ });
751
+ }
752
+ return Effect.fail(new TargetResolutionError({
753
+ target,
754
+ reason: `Unsupported target type: ${typeof target}. Expected "npm", "github", "jsr", an https:// URL, or an object.`
755
+ }));
756
+ }
757
+ const TargetResolverLive = Layer.succeed(TargetResolver, {
758
+ resolve: (target)=>{
759
+ if (Array.isArray(target)) return Effect.all(target.map(resolveOne));
760
+ return resolveOne(target).pipe(Effect.map((resolved)=>[
761
+ resolved
762
+ ]));
763
+ }
764
+ });
765
+ class SilkPublishabilityPlugin extends Context.Tag("@savvy-web/silk-effects/SilkPublishabilityPlugin")() {
766
+ }
767
+ const SilkPublishabilityPluginLive = Layer.effect(SilkPublishabilityPlugin, Effect.gen(function*() {
768
+ const resolver = yield* TargetResolver;
769
+ return {
770
+ detect: (pkgJson)=>{
771
+ const isPrivate = true === pkgJson.private;
772
+ const publishConfig = pkgJson.publishConfig;
773
+ if (isPrivate && !publishConfig) return Effect.succeed([]);
774
+ if (!publishConfig || !publishConfig.access && !publishConfig.targets) return Effect.succeed([]);
775
+ if (Array.isArray(publishConfig.targets)) return resolver.resolve(publishConfig.targets);
776
+ if (publishConfig.registry) return resolver.resolve(publishConfig.registry);
777
+ return resolver.resolve("npm");
778
+ }
779
+ };
780
+ }));
781
+ class TagStrategy extends Context.Tag("@savvy-web/silk-effects/TagStrategy")() {
782
+ }
783
+ const TagStrategyLive = Layer.succeed(TagStrategy, TagStrategy.of({
784
+ determine: (versioningResult)=>{
785
+ if ("independent" === versioningResult.type) return Effect.succeed("scoped");
786
+ return Effect.succeed("single");
787
+ },
788
+ formatTag: (name, version, strategy)=>{
789
+ if ("" === version) return Effect.fail(new TagFormatError({
790
+ name,
791
+ version,
792
+ reason: "version cannot be empty"
793
+ }));
794
+ if ("single" === strategy) return Effect.succeed(version);
795
+ name.startsWith("@");
796
+ return Effect.succeed(`${name}@${version}`);
797
+ }
798
+ }));
799
+ class ToolDiscovery extends Context.Tag("@savvy-web/silk-effects/ToolDiscovery")() {
800
+ }
801
+ function pmExecArgs(pmType, name) {
802
+ switch(pmType){
803
+ case "pnpm":
804
+ return [
805
+ "pnpm",
806
+ "exec",
807
+ name
808
+ ];
809
+ case "npm":
810
+ return [
811
+ "npx",
812
+ "--no",
813
+ "--",
814
+ name
815
+ ];
816
+ case "yarn":
817
+ return [
818
+ "yarn",
819
+ "exec",
820
+ name
821
+ ];
822
+ case "bun":
823
+ return [
824
+ "bun",
825
+ "x",
826
+ "--no-install",
827
+ name
828
+ ];
829
+ }
830
+ }
831
+ function tryString(cmd) {
832
+ return Command.string(cmd).pipe(Effect.map((s)=>Option.some(s.trim())), Effect.catchAll(()=>Effect.succeed(Option.none())));
833
+ }
834
+ function tryExists(cmd) {
835
+ return Command.exitCode(cmd).pipe(Effect.map((code)=>0 === code), Effect.catchAll(()=>Effect.succeed(false)));
836
+ }
837
+ function extractVersion(output, extractor) {
838
+ if ("None" === extractor._tag || Option.isNone(output)) return Option.none();
839
+ const raw = output.value;
840
+ if ("Flag" === extractor._tag) {
841
+ const parsed = extractor.parse ? extractor.parse(raw) : raw.trim();
842
+ return Option.some(parsed);
843
+ }
844
+ try {
845
+ const obj = JSON.parse(raw);
846
+ const parts = extractor.path.split(".");
847
+ let current = obj;
848
+ for (const part of parts){
849
+ if (null == current || "object" != typeof current) return Option.none();
850
+ current = current[part];
851
+ }
852
+ return "string" == typeof current ? Option.some(current) : Option.none();
853
+ } catch {
854
+ return Option.none();
855
+ }
856
+ }
857
+ const ToolDiscoveryLive = Layer.effect(ToolDiscovery, Effect.gen(function*() {
858
+ const executor = yield* CommandExecutor.CommandExecutor;
859
+ const wsRoot = yield* WorkspaceRoot;
860
+ const pmDetector = yield* PackageManagerDetector;
861
+ const cache = yield* Ref.make(new Map());
862
+ const resolve = (definition)=>Effect.gen(function*() {
863
+ const cached = yield* Ref.get(cache);
864
+ const hit = cached.get(definition.name);
865
+ if (hit) return hit;
866
+ const root = yield* wsRoot.find(process.cwd()).pipe(Effect.catchAll(()=>Effect.fail(new ToolResolutionError({
867
+ name: definition.name,
868
+ reason: "Could not find workspace root"
869
+ }))));
870
+ const pm = yield* pmDetector.detect(root).pipe(Effect.catchAll(()=>Effect.fail(new ToolResolutionError({
871
+ name: definition.name,
872
+ reason: "Could not detect package manager"
873
+ }))));
874
+ const pmType = pm.type;
875
+ const globalExists = yield* Effect.provideService(tryExists(Command.make("sh", "-c", `command -v ${definition.name}`)), CommandExecutor.CommandExecutor, executor);
876
+ let globalVersion = Option.none();
877
+ if (globalExists && "None" !== definition.versionExtractor._tag) {
878
+ const flag = definition.versionExtractor.flag;
879
+ const globalOutput = yield* Effect.provideService(tryString(Command.make(definition.name, flag)), CommandExecutor.CommandExecutor, executor);
880
+ globalVersion = extractVersion(globalOutput, definition.versionExtractor);
881
+ }
882
+ let localExists = false;
883
+ let localVersion = Option.none();
884
+ const [pmBin, ...pmArgs] = pmExecArgs(pmType, definition.name);
885
+ if ("None" !== definition.versionExtractor._tag) {
886
+ const flag = definition.versionExtractor.flag;
887
+ const localOutput = yield* Effect.provideService(tryString(Command.make(pmBin, ...pmArgs, flag)), CommandExecutor.CommandExecutor, executor);
888
+ if (Option.isSome(localOutput)) {
889
+ localExists = true;
890
+ localVersion = extractVersion(localOutput, definition.versionExtractor);
891
+ }
892
+ } else localExists = yield* Effect.provideService(tryExists(Command.make(pmBin, ...pmArgs, "--version")), CommandExecutor.CommandExecutor, executor);
893
+ if (!globalExists && !localExists) return yield* Effect.fail(new ToolResolutionError({
894
+ name: definition.name,
895
+ reason: "Tool not found globally or locally"
896
+ }));
897
+ switch(definition.source._tag){
898
+ case "OnlyLocal":
899
+ if (!localExists) return yield* Effect.fail(new ToolResolutionError({
900
+ name: definition.name,
901
+ reason: "Tool is required locally but was only found globally"
902
+ }));
903
+ break;
904
+ case "OnlyGlobal":
905
+ if (!globalExists) return yield* Effect.fail(new ToolResolutionError({
906
+ name: definition.name,
907
+ reason: "Tool is required globally but was only found locally"
908
+ }));
909
+ break;
910
+ case "Both":
911
+ if (!globalExists || !localExists) return yield* Effect.fail(new ToolResolutionError({
912
+ name: definition.name,
913
+ reason: "Tool is required both globally and locally but was only found in one location"
914
+ }));
915
+ break;
916
+ case "Any":
917
+ break;
918
+ }
919
+ let mismatch = false;
920
+ let source = localExists ? "local" : "global";
921
+ let version = localExists ? localVersion : globalVersion;
922
+ if (globalExists && localExists && Option.isSome(globalVersion) && Option.isSome(localVersion)) {
923
+ if (globalVersion.value !== localVersion.value) {
924
+ mismatch = true;
925
+ switch(definition.policy._tag){
926
+ case "Report":
927
+ source = "local";
928
+ version = localVersion;
929
+ break;
930
+ case "PreferLocal":
931
+ source = "local";
932
+ version = localVersion;
933
+ break;
934
+ case "PreferGlobal":
935
+ source = "global";
936
+ version = globalVersion;
937
+ break;
938
+ case "RequireMatch":
939
+ return yield* Effect.fail(new ToolResolutionError({
940
+ name: definition.name,
941
+ reason: `Version mismatch: global ${globalVersion.value} vs local ${localVersion.value}`
942
+ }));
943
+ }
944
+ }
945
+ }
946
+ const resolved = new ResolvedTool({
947
+ name: definition.name,
948
+ source,
949
+ version,
950
+ globalVersion,
951
+ localVersion,
952
+ packageManager: pmType,
953
+ mismatch
954
+ });
955
+ yield* Ref.update(cache, (m)=>{
956
+ const next = new Map(m);
957
+ next.set(definition.name, resolved);
958
+ return next;
959
+ });
960
+ return resolved;
961
+ });
962
+ const require_ = (definition, message)=>resolve(definition).pipe(Effect.mapError((err)=>new ToolNotFoundError({
963
+ name: definition.name,
964
+ reason: message ?? err.reason
965
+ })));
966
+ const isAvailable = (definition)=>Effect.gen(function*() {
967
+ const globalFound = yield* Effect.provideService(tryExists(Command.make("sh", "-c", `command -v ${definition.name}`)), CommandExecutor.CommandExecutor, executor);
968
+ if (globalFound) return true;
969
+ const rootResult = yield* wsRoot.find(process.cwd()).pipe(Effect.option);
970
+ if (Option.isNone(rootResult)) return false;
971
+ const pmResult = yield* pmDetector.detect(rootResult.value).pipe(Effect.option);
972
+ if (Option.isNone(pmResult)) return false;
973
+ const pmType = pmResult.value.type;
974
+ const probeFlag = "None" !== definition.versionExtractor._tag ? definition.versionExtractor.flag : "--version";
975
+ const [pmBin, ...pmArgs] = pmExecArgs(pmType, definition.name);
976
+ return yield* Effect.provideService(tryExists(Command.make(pmBin, ...pmArgs, probeFlag)), CommandExecutor.CommandExecutor, executor);
977
+ });
978
+ const clearCache = Ref.set(cache, new Map());
979
+ return {
980
+ resolve,
981
+ require: require_,
982
+ isAvailable,
983
+ clearCache
984
+ };
985
+ }));
986
+ class VersioningStrategy extends Context.Tag("@savvy-web/silk-effects/VersioningStrategy")() {
987
+ }
988
+ const VersioningStrategyLive = Layer.effect(VersioningStrategy, Effect.gen(function*() {
989
+ const configReader = yield* ChangesetConfigReader;
990
+ const detect = (publishablePackages, root)=>Effect.gen(function*() {
991
+ const config = yield* configReader.read(root).pipe(Effect.orElseSucceed(()=>({
992
+ fixed: [],
993
+ linked: []
994
+ })));
995
+ const fixed = config.fixed ?? [];
996
+ const packages = [
997
+ ...publishablePackages
998
+ ];
999
+ if (packages.length <= 1) return {
1000
+ type: "single",
1001
+ fixedGroups: fixed,
1002
+ publishablePackages: packages
1003
+ };
1004
+ const containingGroup = fixed.find((group)=>packages.every((pkg)=>group.includes(pkg)));
1005
+ if (void 0 !== containingGroup) return {
1006
+ type: "fixed-group",
1007
+ fixedGroups: fixed,
1008
+ publishablePackages: packages
1009
+ };
1010
+ return {
1011
+ type: "independent",
1012
+ fixedGroups: fixed,
1013
+ publishablePackages: packages
1014
+ };
1015
+ });
1016
+ return {
1017
+ detect
1018
+ };
1019
+ }));
1020
+ export { BiomeSchemaSync, BiomeSchemaSyncLive, BiomeSyncError, ChangesetConfigError, ChangesetConfigReader, ChangesetConfigReaderLive, CheckResult, ConfigDiscovery, ConfigDiscoveryLive, ConfigNotFoundError, ManagedSection, ManagedSectionLive, PublishConfigError, PublishTarget as PublishTargetSchema, PublishTargetShorthand as PublishTargetShorthandSchema, ResolutionPolicy, ResolvedTarget as ResolvedTargetSchema, ResolvedTool, SectionBlock, SectionDefinition, SectionDiff, SectionParseError, SectionValidationError, SectionWriteError, ShellSectionDefinition, SilkPublishabilityPlugin, SilkPublishabilityPluginLive, SourceRequirement, SyncResult, TagFormatError, TagStrategy, TagStrategyLive, TargetResolutionError, TargetResolver, TargetResolverLive, ToolCommand, ToolDefinition, ToolDiscovery, ToolDiscoveryLive, ToolNotFoundError, ToolResolutionError, ToolSource, ToolVersionMismatchError, VersionExtractor, VersioningDetectionError, VersioningStrategy, VersioningStrategyLive, buildSchemaUrl, extractSemver };