@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/README.md +251 -24
- package/index.d.ts +902 -162
- package/index.js +1020 -6
- package/package.json +3 -39
- package/biome.d.ts +0 -135
- package/biome.js +0 -84
- package/config.d.ts +0 -147
- package/config.js +0 -38
- package/hooks.d.ts +0 -197
- package/hooks.js +0 -96
- package/publish.d.ts +0 -260
- package/publish.js +0 -124
- package/tags.d.ts +0 -122
- package/tags.js +0 -25
- package/versioning.d.ts +0 -234
- package/versioning.js +0 -115
package/index.js
CHANGED
|
@@ -1,6 +1,1020 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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 };
|