@danieljvdm/dev-kit 0.11.3 → 0.12.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 +70 -76
- package/dev-kit.example.jsonc +0 -4
- package/package.json +10 -6
- package/schema/dev-kit.schema.json +1 -46
- package/skills/build-effect-apis/SKILL.md +77 -0
- package/skills/build-effect-apis/agents/openai.yaml +4 -0
- package/skills/build-effect-apis/references/cloudflare-workers.md +71 -0
- package/skills/build-effect-apis/references/effect-atom-client.md +161 -0
- package/skills/build-effect-apis/references/effect-atom-lifecycle.md +78 -0
- package/skills/build-effect-apis/references/effect-atom-testing.md +74 -0
- package/skills/build-effect-apis/references/runtime-assembly.md +56 -0
- package/skills/build-effect-apis/references/server-and-middleware.md +174 -0
- package/skills/build-effect-apis/references/shared-contracts.md +108 -0
- package/skills/build-effect-apis/references/tanstack-start.md +86 -0
- package/skills/build-effect-apis/references/verification.md +50 -0
- package/skills/dev-kit/SKILL.md +58 -46
- package/skills/effect-architecture-audit/SKILL.md +26 -0
- package/skills/effect-architecture-audit/agents/openai.yaml +4 -0
- package/skills/effect-architecture-audit/references/service-and-boundary-audit.md +150 -0
- package/skills/effect-ts/SKILL.md +21 -256
- package/skills/effect-ts/agents/openai.yaml +3 -3
- package/skills/testing/SKILL.md +5 -0
- package/src/catalog-manager.ts +16 -17
- package/src/catalog.ts +71 -16
- package/src/effect-source.ts +46 -24
- package/src/effect-tsgo.ts +49 -24
- package/src/gitignore.ts +5 -5
- package/src/index.ts +0 -6
- package/src/manifest.ts +0 -34
- package/src/node-symbolic-link.ts +2 -2
- package/src/oxfmt.js +5 -0
- package/src/oxfmt.ts +5 -0
- package/src/oxlint.js +5 -0
- package/src/oxlint.ts +5 -0
- package/src/package-skill-source.ts +51 -59
- package/src/path-digest.ts +7 -7
- package/src/project-package.ts +8 -7
- package/src/project-process-lock.ts +17 -12
- package/src/project-state.ts +1 -1
- package/src/skill-manager.ts +16 -14
- package/src/skill-selector.ts +12 -0
- package/src/sync.ts +170 -120
- package/src/tool-ignore-patterns.js +9 -0
- package/src/tool-ignore-patterns.ts +15 -0
- package/src/vendor.ts +67 -61
- package/src/vite-plus-dependency.ts +10 -11
- package/src/vite-plus-hooks.ts +24 -14
- package/src/vite-plus-quality.ts +21 -172
- package/src/vite-plus.js +81 -0
- package/src/vite-plus.ts +102 -0
- package/templates/AGENTS.md +1 -1
- package/skills/effect-ts/UPSTREAM.md +0 -28
- package/skills/effect-ts/references/atom-cache-lifecycle.md +0 -78
- package/skills/effect-ts/references/atom-http-and-invalidation.md +0 -97
- package/skills/effect-ts/references/atom-tanstack-start.md +0 -69
- package/skills/effect-ts/references/atom-testing.md +0 -67
- package/skills/effect-ts/references/audit-services.md +0 -144
- package/skills/effect-ts/references/features.md +0 -525
- package/skills/effect-ts/references/guide-atom-data-fetching.md +0 -44
- package/skills/effect-ts/references/guide-cli.md +0 -107
- package/skills/effect-ts/references/guide-datetime.md +0 -72
- package/skills/effect-ts/references/guide-effect.md +0 -440
- package/skills/effect-ts/references/guide-error-handling.md +0 -565
- package/skills/effect-ts/references/guide-http-boundaries.md +0 -55
- package/skills/effect-ts/references/guide-layers.md +0 -989
- package/skills/effect-ts/references/guide-observability.md +0 -746
- package/skills/effect-ts/references/guide-retries.md +0 -434
- package/skills/effect-ts/references/guide-schedule.md +0 -343
- package/skills/effect-ts/references/guide-schema.md +0 -664
- package/skills/effect-ts/references/guide-sql.md +0 -536
- package/skills/effect-ts/references/guide-testing.md +0 -532
- package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +0 -131
- package/skills/effect-ts/references/version-and-source.md +0 -86
- package/templates/vite-plus/vite.config.ts +0 -22
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-owned paths that should not be linted or formatted.
|
|
3
|
+
*
|
|
4
|
+
* Skill copies remain tracked project inputs, while symlinked harness targets
|
|
5
|
+
* and local Dev Kit state may duplicate or contain third-party source. Keep
|
|
6
|
+
* these exclusions in tool configuration rather than `.gitignore`.
|
|
7
|
+
*/
|
|
8
|
+
export const devKitToolIgnorePatterns = [
|
|
9
|
+
".agents/**",
|
|
10
|
+
".claude/**",
|
|
11
|
+
".dev-kit/**",
|
|
12
|
+
".opencode/**",
|
|
13
|
+
".repos/**",
|
|
14
|
+
".vite-hooks/_/**",
|
|
15
|
+
] as const;
|
package/src/vendor.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, FileSystem, Path, Schema, Stream } from "effect";
|
|
1
|
+
import { Effect, FileSystem, Path, Schema, SchemaGetter, Stream } from "effect";
|
|
2
2
|
import { ChildProcess } from "effect/unstable/process";
|
|
3
3
|
import { parse as parseJsonc, printParseErrorCode, type ParseError } from "jsonc-parser";
|
|
4
4
|
|
|
@@ -51,39 +51,30 @@ type PreparedSource = {
|
|
|
51
51
|
readonly licenseSource?: string;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
class SourceManifestError extends Schema.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
message: Schema.String,
|
|
59
|
-
},
|
|
60
|
-
) {}
|
|
54
|
+
class SourceManifestError extends Schema.TaggedError<SourceManifestError>()("SourceManifestError", {
|
|
55
|
+
path: Schema.String,
|
|
56
|
+
message: Schema.String,
|
|
57
|
+
}) {}
|
|
61
58
|
|
|
62
|
-
class InvalidSourceError extends Schema.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
reason: Schema.String,
|
|
67
|
-
},
|
|
68
|
-
) {
|
|
59
|
+
class InvalidSourceError extends Schema.TaggedError<InvalidSourceError>()("InvalidSourceError", {
|
|
60
|
+
source: Schema.String,
|
|
61
|
+
reason: Schema.String,
|
|
62
|
+
}) {
|
|
69
63
|
override get message(): string {
|
|
70
64
|
return `invalid source "${this.source}": ${this.reason}`;
|
|
71
65
|
}
|
|
72
66
|
}
|
|
73
67
|
|
|
74
|
-
class SkillCollisionError extends Schema.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
owners: Schema.Array(Schema.String),
|
|
79
|
-
},
|
|
80
|
-
) {
|
|
68
|
+
class SkillCollisionError extends Schema.TaggedError<SkillCollisionError>()("SkillCollisionError", {
|
|
69
|
+
skill: Schema.String,
|
|
70
|
+
owners: Schema.Array(Schema.String),
|
|
71
|
+
}) {
|
|
81
72
|
override get message() {
|
|
82
73
|
return `skill "${this.skill}" is owned by more than one source: ${this.owners.join(", ")}`;
|
|
83
74
|
}
|
|
84
75
|
}
|
|
85
76
|
|
|
86
|
-
class CommandError extends Schema.
|
|
77
|
+
class CommandError extends Schema.TaggedError<CommandError>()("CommandError", {
|
|
87
78
|
command: Schema.String,
|
|
88
79
|
exitCode: Schema.Int,
|
|
89
80
|
output: Schema.String,
|
|
@@ -95,6 +86,16 @@ class CommandError extends Schema.TaggedErrorClass<CommandError>()("CommandError
|
|
|
95
86
|
}
|
|
96
87
|
}
|
|
97
88
|
|
|
89
|
+
const SkillSourcesLockJsonSchema = Schema.fromJsonString(SkillSourcesLockSchema);
|
|
90
|
+
const SkillSourcesLockPrettyJsonSchema = Schema.String.pipe(
|
|
91
|
+
Schema.decodeTo(Schema.toCodecJson(SkillSourcesLockSchema), {
|
|
92
|
+
decode: SchemaGetter.parseJson(),
|
|
93
|
+
encode: SchemaGetter.stringifyJson({ space: 2 }),
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
const encodeSkillSourcesLockJson = Schema.encodeSync(SkillSourcesLockJsonSchema);
|
|
97
|
+
const encodeSkillSourcesLockPrettyJson = Schema.encodeSync(SkillSourcesLockPrettyJsonSchema);
|
|
98
|
+
|
|
98
99
|
const DEFAULT_SOURCES_PATH = "skill-sources.jsonc";
|
|
99
100
|
const DEFAULT_LOCKFILE_PATH = "skill-sources.lock.json";
|
|
100
101
|
const SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -131,7 +132,7 @@ const normalizeRepositoryLocator = (
|
|
|
131
132
|
> => {
|
|
132
133
|
if (containsControlCharacter(repository)) {
|
|
133
134
|
return Effect.fail(
|
|
134
|
-
|
|
135
|
+
InvalidSourceError.make({
|
|
135
136
|
source: repository,
|
|
136
137
|
reason: "repository contains control characters",
|
|
137
138
|
}),
|
|
@@ -142,7 +143,7 @@ const normalizeRepositoryLocator = (
|
|
|
142
143
|
|
|
143
144
|
if ((url.protocol === "http:" || url.protocol === "https:") && (url.username || url.password)) {
|
|
144
145
|
return Effect.fail(
|
|
145
|
-
|
|
146
|
+
InvalidSourceError.make({
|
|
146
147
|
source: repository,
|
|
147
148
|
reason: "repository URLs must not contain credentials",
|
|
148
149
|
}),
|
|
@@ -184,7 +185,7 @@ const runCommand = Effect.fn("runVendorCommand")(function* (
|
|
|
184
185
|
const trimmed = output.trim();
|
|
185
186
|
|
|
186
187
|
if (exitCode !== 0) {
|
|
187
|
-
return yield*
|
|
188
|
+
return yield* CommandError.make({ command: formatted, exitCode, output: trimmed });
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
return trimmed;
|
|
@@ -201,7 +202,7 @@ const readJsonc = Effect.fn("readVendorJsonc")(function* <A>(
|
|
|
201
202
|
const fs = yield* FileSystem.FileSystem;
|
|
202
203
|
|
|
203
204
|
if (!(yield* fs.exists(filePath))) {
|
|
204
|
-
return yield*
|
|
205
|
+
return yield* SourceManifestError.make({ path: filePath, message: "file not found" });
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
const raw = yield* fs.readFileString(filePath);
|
|
@@ -211,14 +212,16 @@ const readJsonc = Effect.fn("readVendorJsonc")(function* <A>(
|
|
|
211
212
|
const first = errors[0];
|
|
212
213
|
|
|
213
214
|
if (first !== undefined) {
|
|
214
|
-
return yield*
|
|
215
|
+
return yield* SourceManifestError.make({
|
|
215
216
|
path: filePath,
|
|
216
217
|
message: `${printParseErrorCode(first.error)} at offset ${first.offset}`,
|
|
217
218
|
});
|
|
218
219
|
}
|
|
219
220
|
|
|
220
221
|
return yield* Schema.decodeUnknownEffect(schema)(parsed).pipe(
|
|
221
|
-
Effect.mapError((cause) =>
|
|
222
|
+
Effect.mapError((cause) =>
|
|
223
|
+
SourceManifestError.make({ path: filePath, message: cause.message }),
|
|
224
|
+
),
|
|
222
225
|
);
|
|
223
226
|
});
|
|
224
227
|
|
|
@@ -239,7 +242,7 @@ const resolveInside = (
|
|
|
239
242
|
path.isAbsolute(relative)
|
|
240
243
|
) {
|
|
241
244
|
return Effect.fail(
|
|
242
|
-
|
|
245
|
+
InvalidSourceError.make({
|
|
243
246
|
source: sourceId,
|
|
244
247
|
reason: `${field} must be a relative path inside the source repository`,
|
|
245
248
|
}),
|
|
@@ -264,7 +267,7 @@ const ensureCanonicalPathInside = Effect.fn("ensureCanonicalSourcePathInside")(f
|
|
|
264
267
|
const relative = path.relative(canonicalRoot, canonicalTarget);
|
|
265
268
|
|
|
266
269
|
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
267
|
-
return yield*
|
|
270
|
+
return yield* InvalidSourceError.make({
|
|
268
271
|
source: sourceId,
|
|
269
272
|
reason: `${field} resolves outside the source repository`,
|
|
270
273
|
});
|
|
@@ -287,7 +290,7 @@ const rejectGitSymlinks = Effect.fn("rejectGitSymlinks")(function* (
|
|
|
287
290
|
const symlink = entries.split(/\r?\n/).find((line) => line.startsWith("120000 "));
|
|
288
291
|
|
|
289
292
|
if (symlink) {
|
|
290
|
-
return yield*
|
|
293
|
+
return yield* InvalidSourceError.make({
|
|
291
294
|
source: sourceId,
|
|
292
295
|
reason: `symlinks are not allowed in catalog paths: ${symlink.slice(symlink.indexOf("\t") + 1)}`,
|
|
293
296
|
});
|
|
@@ -302,7 +305,7 @@ const discoverSkills = Effect.fn("discoverVendoredSkills")(function* (
|
|
|
302
305
|
const path = yield* Path.Path;
|
|
303
306
|
|
|
304
307
|
if (!(yield* fs.exists(skillsDir))) {
|
|
305
|
-
return yield*
|
|
308
|
+
return yield* InvalidSourceError.make({
|
|
306
309
|
source: source.id,
|
|
307
310
|
reason: `skillsPath does not exist: ${source.skillsPath}`,
|
|
308
311
|
});
|
|
@@ -327,7 +330,7 @@ const discoverSkills = Effect.fn("discoverVendoredSkills")(function* (
|
|
|
327
330
|
.replace(/^(['"])(.*)\1$/, "$2");
|
|
328
331
|
|
|
329
332
|
if (declaredName !== entry) {
|
|
330
|
-
return yield*
|
|
333
|
+
return yield* InvalidSourceError.make({
|
|
331
334
|
source: source.id,
|
|
332
335
|
reason: `${source.skillsPath}/${entry}/SKILL.md must declare name: ${entry}`,
|
|
333
336
|
});
|
|
@@ -340,7 +343,7 @@ const discoverSkills = Effect.fn("discoverVendoredSkills")(function* (
|
|
|
340
343
|
const includeAll = source.include.length === 1 && source.include[0] === "*";
|
|
341
344
|
|
|
342
345
|
if (source.include.includes("*") && !includeAll) {
|
|
343
|
-
return yield*
|
|
346
|
+
return yield* InvalidSourceError.make({
|
|
344
347
|
source: source.id,
|
|
345
348
|
reason: 'include must contain either "*" or explicit skill names, not both',
|
|
346
349
|
});
|
|
@@ -351,7 +354,7 @@ const discoverSkills = Effect.fn("discoverVendoredSkills")(function* (
|
|
|
351
354
|
);
|
|
352
355
|
|
|
353
356
|
if (selected.length === 0) {
|
|
354
|
-
return yield*
|
|
357
|
+
return yield* InvalidSourceError.make({
|
|
355
358
|
source: source.id,
|
|
356
359
|
reason: "include must select at least one skill",
|
|
357
360
|
});
|
|
@@ -359,13 +362,13 @@ const discoverSkills = Effect.fn("discoverVendoredSkills")(function* (
|
|
|
359
362
|
|
|
360
363
|
for (const skill of selected) {
|
|
361
364
|
if (!SKILL_NAME_PATTERN.test(skill)) {
|
|
362
|
-
return yield*
|
|
365
|
+
return yield* InvalidSourceError.make({
|
|
363
366
|
source: source.id,
|
|
364
367
|
reason: `invalid skill name "${skill}"`,
|
|
365
368
|
});
|
|
366
369
|
}
|
|
367
370
|
if (!discovered.includes(skill)) {
|
|
368
|
-
return yield*
|
|
371
|
+
return yield* InvalidSourceError.make({
|
|
369
372
|
source: source.id,
|
|
370
373
|
reason: `skill not found under ${source.skillsPath}: ${skill}`,
|
|
371
374
|
});
|
|
@@ -386,19 +389,19 @@ const prepareSource = Effect.fn("prepareSkillSource")(function* (
|
|
|
386
389
|
const path = yield* Path.Path;
|
|
387
390
|
|
|
388
391
|
if (!SOURCE_ID_PATTERN.test(source.id)) {
|
|
389
|
-
return yield*
|
|
392
|
+
return yield* InvalidSourceError.make({
|
|
390
393
|
source: source.id,
|
|
391
394
|
reason: "id must use lowercase letters, numbers, and hyphens",
|
|
392
395
|
});
|
|
393
396
|
}
|
|
394
397
|
if (RESERVED_SOURCE_IDS.has(source.id)) {
|
|
395
|
-
return yield*
|
|
398
|
+
return yield* InvalidSourceError.make({
|
|
396
399
|
source: source.id,
|
|
397
400
|
reason: "id conflicts with a built-in skill family",
|
|
398
401
|
});
|
|
399
402
|
}
|
|
400
403
|
if (source.repository.length === 0 || source.ref.length === 0) {
|
|
401
|
-
return yield*
|
|
404
|
+
return yield* InvalidSourceError.make({
|
|
402
405
|
source: source.id,
|
|
403
406
|
reason: "repository and ref must not be empty",
|
|
404
407
|
});
|
|
@@ -417,7 +420,7 @@ const prepareSource = Effect.fn("prepareSkillSource")(function* (
|
|
|
417
420
|
[...(lockedSource.stripFrontmatter ?? [])].sort().join("\0") !==
|
|
418
421
|
[...(source.stripFrontmatter ?? [])].sort().join("\0"))
|
|
419
422
|
) {
|
|
420
|
-
return yield*
|
|
423
|
+
return yield* InvalidSourceError.make({
|
|
421
424
|
source: source.id,
|
|
422
425
|
reason: "no matching lockfile entry; run catalog refresh without --locked first",
|
|
423
426
|
});
|
|
@@ -431,7 +434,7 @@ const prepareSource = Effect.fn("prepareSkillSource")(function* (
|
|
|
431
434
|
const fetchRef = useLock ? lockedSource?.resolved : source.ref;
|
|
432
435
|
|
|
433
436
|
if (fetchRef === undefined) {
|
|
434
|
-
return yield*
|
|
437
|
+
return yield* InvalidSourceError.make({
|
|
435
438
|
source: source.id,
|
|
436
439
|
reason: "no matching lockfile entry; run catalog refresh without --locked first",
|
|
437
440
|
});
|
|
@@ -469,7 +472,7 @@ const prepareSource = Effect.fn("prepareSkillSource")(function* (
|
|
|
469
472
|
);
|
|
470
473
|
yield* rejectGitSymlinks(checkoutDir, source.licensePath, source.id);
|
|
471
474
|
if (!(yield* fs.exists(licenseSource))) {
|
|
472
|
-
return yield*
|
|
475
|
+
return yield* InvalidSourceError.make({
|
|
473
476
|
source: source.id,
|
|
474
477
|
reason: `licensePath does not exist: ${source.licensePath}`,
|
|
475
478
|
});
|
|
@@ -483,7 +486,7 @@ const prepareSource = Effect.fn("prepareSkillSource")(function* (
|
|
|
483
486
|
const licenseInfo = yield* fs.stat(licenseSource);
|
|
484
487
|
|
|
485
488
|
if (licenseInfo.type !== "File") {
|
|
486
|
-
return yield*
|
|
489
|
+
return yield* InvalidSourceError.make({
|
|
487
490
|
source: source.id,
|
|
488
491
|
reason: `licensePath must be a file: ${source.licensePath}`,
|
|
489
492
|
});
|
|
@@ -520,33 +523,33 @@ const validateCurrentLock = Effect.fn("validateCurrentSkillSourcesLock")(functio
|
|
|
520
523
|
|
|
521
524
|
for (const source of lock.sources) {
|
|
522
525
|
if (!SOURCE_ID_PATTERN.test(source.id) || RESERVED_SOURCE_IDS.has(source.id)) {
|
|
523
|
-
return yield*
|
|
526
|
+
return yield* InvalidSourceError.make({
|
|
524
527
|
source: source.id,
|
|
525
528
|
reason: "lockfile contains an invalid source id",
|
|
526
529
|
});
|
|
527
530
|
}
|
|
528
531
|
if (sourceIds.has(source.id)) {
|
|
529
|
-
return yield*
|
|
532
|
+
return yield* InvalidSourceError.make({
|
|
530
533
|
source: source.id,
|
|
531
534
|
reason: "lockfile source ids must be unique",
|
|
532
535
|
});
|
|
533
536
|
}
|
|
534
537
|
sourceIds.add(source.id);
|
|
535
538
|
if (!/^[0-9a-f]{40,64}$/.test(source.resolved)) {
|
|
536
|
-
return yield*
|
|
539
|
+
return yield* InvalidSourceError.make({
|
|
537
540
|
source: source.id,
|
|
538
541
|
reason: "lockfile resolved commit must be a full hexadecimal object id",
|
|
539
542
|
});
|
|
540
543
|
}
|
|
541
544
|
for (const skill of source.skills) {
|
|
542
545
|
if (!SKILL_NAME_PATTERN.test(skill)) {
|
|
543
|
-
return yield*
|
|
546
|
+
return yield* InvalidSourceError.make({
|
|
544
547
|
source: source.id,
|
|
545
548
|
reason: `lockfile contains an invalid skill name: ${skill}`,
|
|
546
549
|
});
|
|
547
550
|
}
|
|
548
551
|
if (skills.has(skill)) {
|
|
549
|
-
return yield*
|
|
552
|
+
return yield* SkillCollisionError.make({
|
|
550
553
|
skill,
|
|
551
554
|
owners: ["multiple lockfile sources"],
|
|
552
555
|
});
|
|
@@ -590,12 +593,12 @@ const validateOwnership = Effect.fn("validateSkillOwnership")(function* (
|
|
|
590
593
|
}
|
|
591
594
|
for (const [skill, skillOwners] of owners) {
|
|
592
595
|
if (skillOwners.length > 1) {
|
|
593
|
-
return yield*
|
|
596
|
+
return yield* SkillCollisionError.make({ skill, owners: skillOwners });
|
|
594
597
|
}
|
|
595
598
|
}
|
|
596
599
|
for (const preparedSource of prepared) {
|
|
597
600
|
if (owners.has(preparedSource.source.id)) {
|
|
598
|
-
return yield*
|
|
601
|
+
return yield* InvalidSourceError.make({
|
|
599
602
|
source: preparedSource.source.id,
|
|
600
603
|
reason: "id conflicts with a skill name",
|
|
601
604
|
});
|
|
@@ -603,7 +606,7 @@ const validateOwnership = Effect.fn("validateSkillOwnership")(function* (
|
|
|
603
606
|
}
|
|
604
607
|
for (const reservedFamily of RESERVED_SOURCE_IDS) {
|
|
605
608
|
if (owners.has(reservedFamily)) {
|
|
606
|
-
return yield*
|
|
609
|
+
return yield* InvalidSourceError.make({
|
|
607
610
|
source: reservedFamily,
|
|
608
611
|
reason: "skill name conflicts with a built-in skill family",
|
|
609
612
|
});
|
|
@@ -703,7 +706,7 @@ const buildLock = Effect.fn("buildSkillCatalogLock")(function* (
|
|
|
703
706
|
const observation = yield* observePath(stagedSkill);
|
|
704
707
|
|
|
705
708
|
if (observation.kind !== "directory") {
|
|
706
|
-
return yield*
|
|
709
|
+
return yield* InvalidSourceError.make({
|
|
707
710
|
source: source.id,
|
|
708
711
|
reason: `could not digest ${skill}`,
|
|
709
712
|
});
|
|
@@ -740,7 +743,7 @@ export const inspectCatalogRepository = Effect.fn("inspectCatalogRepository")(fu
|
|
|
740
743
|
const id = options.id ?? inferSourceId(repository);
|
|
741
744
|
|
|
742
745
|
if (id.length === 0) {
|
|
743
|
-
return yield*
|
|
746
|
+
return yield* InvalidSourceError.make({
|
|
744
747
|
source: repository,
|
|
745
748
|
reason: "could not infer a source id; pass --id",
|
|
746
749
|
});
|
|
@@ -844,7 +847,7 @@ export const refreshSkillCatalog = Effect.fn("refreshSkillCatalog")(function* (
|
|
|
844
847
|
|
|
845
848
|
for (const source of manifest.sources) {
|
|
846
849
|
if (sourceIds.has(source.id)) {
|
|
847
|
-
return yield*
|
|
850
|
+
return yield* InvalidSourceError.make({
|
|
848
851
|
source: source.id,
|
|
849
852
|
reason: "source ids must be unique",
|
|
850
853
|
});
|
|
@@ -853,7 +856,7 @@ export const refreshSkillCatalog = Effect.fn("refreshSkillCatalog")(function* (
|
|
|
853
856
|
}
|
|
854
857
|
if (options.locked) {
|
|
855
858
|
if (!currentLock) {
|
|
856
|
-
return yield*
|
|
859
|
+
return yield* SourceManifestError.make({
|
|
857
860
|
path: lockfilePath,
|
|
858
861
|
message: "lockfile is required with --locked",
|
|
859
862
|
});
|
|
@@ -863,7 +866,7 @@ export const refreshSkillCatalog = Effect.fn("refreshSkillCatalog")(function* (
|
|
|
863
866
|
const missingFromLock = manifest.sources.find((source) => !lockedIds.has(source.id));
|
|
864
867
|
|
|
865
868
|
if (missingFromManifest || missingFromLock) {
|
|
866
|
-
return yield*
|
|
869
|
+
return yield* SourceManifestError.make({
|
|
867
870
|
path: lockfilePath,
|
|
868
871
|
message: "source ids differ from skill-sources.jsonc; run catalog refresh without --locked",
|
|
869
872
|
});
|
|
@@ -906,8 +909,11 @@ export const refreshSkillCatalog = Effect.fn("refreshSkillCatalog")(function* (
|
|
|
906
909
|
const summary = `${skillCount} skill${skillCount === 1 ? "" : "s"} from ${nextLock.sources.length} source${nextLock.sources.length === 1 ? "" : "s"}`;
|
|
907
910
|
|
|
908
911
|
if (options.locked) {
|
|
909
|
-
if (
|
|
910
|
-
|
|
912
|
+
if (
|
|
913
|
+
currentLock === undefined ||
|
|
914
|
+
encodeSkillSourcesLockJson(currentLock) !== encodeSkillSourcesLockJson(nextLock)
|
|
915
|
+
) {
|
|
916
|
+
return yield* SourceManifestError.make({
|
|
911
917
|
path: lockfilePath,
|
|
912
918
|
message:
|
|
913
919
|
"approved catalog metadata differs from the lock; run catalog refresh and review it",
|
|
@@ -926,7 +932,7 @@ export const refreshSkillCatalog = Effect.fn("refreshSkillCatalog")(function* (
|
|
|
926
932
|
|
|
927
933
|
const nextLockPath = path.join(tempDir, "next-catalog-lock.json");
|
|
928
934
|
|
|
929
|
-
yield* fs.writeFileString(nextLockPath, `${
|
|
935
|
+
yield* fs.writeFileString(nextLockPath, `${encodeSkillSourcesLockPrettyJson(nextLock)}\n`);
|
|
930
936
|
yield* fs.rename(nextLockPath, lockfilePath);
|
|
931
937
|
yield* printStatus("success", "Catalog refreshed", summary);
|
|
932
938
|
});
|
|
@@ -8,7 +8,7 @@ const InstalledVitePlusPackageSchema = Schema.fromJsonString(
|
|
|
8
8
|
Schema.Struct({ version: Schema.String }),
|
|
9
9
|
);
|
|
10
10
|
|
|
11
|
-
export class VitePlusDependencyError extends Schema.
|
|
11
|
+
export class VitePlusDependencyError extends Schema.TaggedError<VitePlusDependencyError>()(
|
|
12
12
|
"VitePlusDependencyError",
|
|
13
13
|
{ message: Schema.String },
|
|
14
14
|
) {}
|
|
@@ -26,42 +26,41 @@ export const validateInstalledVitePlus = Effect.fn("validateInstalledVitePlus")(
|
|
|
26
26
|
const dependencies = yield* readDirectDependencyNames(projectDir);
|
|
27
27
|
|
|
28
28
|
if (!dependencies.includes("vite-plus")) {
|
|
29
|
-
return yield*
|
|
29
|
+
return yield* VitePlusDependencyError.make({
|
|
30
30
|
message: "vite-plus must be a direct project dependency",
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
const packagePath = path.join(projectDir, "node_modules", "vite-plus", "package.json");
|
|
34
34
|
|
|
35
35
|
if (!(yield* fs.exists(packagePath))) {
|
|
36
|
-
return yield*
|
|
36
|
+
return yield* VitePlusDependencyError.make({
|
|
37
37
|
message:
|
|
38
38
|
"vite-plus must be installed before enabling this setup: node_modules/vite-plus/package.json is missing",
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
const installed = yield* fs.readFileString(packagePath).pipe(
|
|
42
42
|
Effect.flatMap(Schema.decodeUnknownEffect(InstalledVitePlusPackageSchema)),
|
|
43
|
-
Effect.mapError(
|
|
44
|
-
(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}),
|
|
43
|
+
Effect.mapError(() =>
|
|
44
|
+
VitePlusDependencyError.make({
|
|
45
|
+
message: "installed vite-plus package metadata has no valid version",
|
|
46
|
+
}),
|
|
48
47
|
),
|
|
49
48
|
);
|
|
50
49
|
|
|
51
50
|
if (!semver.valid(installed.version)) {
|
|
52
|
-
return yield*
|
|
51
|
+
return yield* VitePlusDependencyError.make({
|
|
53
52
|
message: `installed vite-plus package metadata has invalid version: ${installed.version}`,
|
|
54
53
|
});
|
|
55
54
|
}
|
|
56
55
|
if (!semver.satisfies(installed.version, VITE_PLUS_SUPPORTED_RANGE)) {
|
|
57
|
-
return yield*
|
|
56
|
+
return yield* VitePlusDependencyError.make({
|
|
58
57
|
message: `installed vite-plus ${installed.version} is incompatible with @danieljvdm/dev-kit; supported range: ${VITE_PLUS_SUPPORTED_RANGE}`,
|
|
59
58
|
});
|
|
60
59
|
}
|
|
61
60
|
const vpBin = path.join(projectDir, "node_modules", ".bin", "vp");
|
|
62
61
|
|
|
63
62
|
if (!(yield* fs.exists(vpBin))) {
|
|
64
|
-
return yield*
|
|
63
|
+
return yield* VitePlusDependencyError.make({
|
|
65
64
|
message: "vite-plus is installed but node_modules/.bin/vp is missing",
|
|
66
65
|
});
|
|
67
66
|
}
|
package/src/vite-plus-hooks.ts
CHANGED
|
@@ -14,12 +14,12 @@ export type VitePlusHooksPlan = {
|
|
|
14
14
|
readonly vpBin: string;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
export class VitePlusHooksDependencyError extends Schema.
|
|
17
|
+
export class VitePlusHooksDependencyError extends Schema.TaggedError<VitePlusHooksDependencyError>()(
|
|
18
18
|
"VitePlusHooksDependencyError",
|
|
19
19
|
{ message: Schema.String },
|
|
20
20
|
) {}
|
|
21
21
|
|
|
22
|
-
export class VitePlusHooksConflictError extends Schema.
|
|
22
|
+
export class VitePlusHooksConflictError extends Schema.TaggedError<VitePlusHooksConflictError>()(
|
|
23
23
|
"VitePlusHooksConflictError",
|
|
24
24
|
{ hooksPath: Schema.String },
|
|
25
25
|
) {
|
|
@@ -28,7 +28,7 @@ export class VitePlusHooksConflictError extends Schema.TaggedErrorClass<VitePlus
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
class VitePlusHooksCommandError extends Schema.
|
|
31
|
+
class VitePlusHooksCommandError extends Schema.TaggedError<VitePlusHooksCommandError>()(
|
|
32
32
|
"VitePlusHooksCommandError",
|
|
33
33
|
{ command: Schema.String, exitCode: Schema.Int, output: Schema.String },
|
|
34
34
|
) {
|
|
@@ -39,7 +39,7 @@ class VitePlusHooksCommandError extends Schema.TaggedErrorClass<VitePlusHooksCom
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
class VitePlusHooksConvergenceError extends Schema.
|
|
42
|
+
class VitePlusHooksConvergenceError extends Schema.TaggedError<VitePlusHooksConvergenceError>()(
|
|
43
43
|
"VitePlusHooksConvergenceError",
|
|
44
44
|
{ message: Schema.String },
|
|
45
45
|
) {}
|
|
@@ -62,7 +62,7 @@ const runCommand = Effect.fn("runVitePlusHooksCommand")(function* (
|
|
|
62
62
|
const trimmed = output.trim();
|
|
63
63
|
|
|
64
64
|
if (!allowedExitCodes.includes(exitCode)) {
|
|
65
|
-
return yield*
|
|
65
|
+
return yield* VitePlusHooksCommandError.make({
|
|
66
66
|
command: [command, ...args].join(" "),
|
|
67
67
|
exitCode,
|
|
68
68
|
output: trimmed,
|
|
@@ -108,25 +108,35 @@ const inspectVitePlusHooks = Effect.fn("inspectVitePlusHooks")(function* (projec
|
|
|
108
108
|
hooksPath !== ".husky" &&
|
|
109
109
|
!hooksPath.startsWith(".husky/")
|
|
110
110
|
) {
|
|
111
|
-
return yield*
|
|
111
|
+
return yield* VitePlusHooksConflictError.make({ hooksPath });
|
|
112
112
|
}
|
|
113
113
|
const internalDir = path.join(projectDir, VITE_PLUS_HOOKS_DIR, "_");
|
|
114
|
-
const [hasLauncher, hasDispatcher, hasPreCommit] = yield* Effect.all([
|
|
114
|
+
const [hasLauncher, hasDispatcher, hasPreCommit, hasInternalIgnore] = yield* Effect.all([
|
|
115
115
|
hasExecutableFile(path.join(internalDir, "h")),
|
|
116
116
|
hasExecutableFile(path.join(internalDir, "pre-commit")),
|
|
117
117
|
hasVitePlusPreCommitHook(path.join(projectDir, VITE_PLUS_HOOKS_DIR, "pre-commit")),
|
|
118
|
+
FileSystem.FileSystem.pipe(
|
|
119
|
+
Effect.flatMap((fs) => fs.readFileString(path.join(internalDir, ".gitignore"))),
|
|
120
|
+
Effect.map((contents) => contents.split(/\r?\n/).includes("*")),
|
|
121
|
+
Effect.orElseSucceed(() => false),
|
|
122
|
+
),
|
|
118
123
|
]);
|
|
119
124
|
|
|
120
|
-
return
|
|
125
|
+
return (
|
|
126
|
+
hooksPath === VITE_PLUS_HOOKS_PATH &&
|
|
127
|
+
hasLauncher &&
|
|
128
|
+
hasDispatcher &&
|
|
129
|
+
hasPreCommit &&
|
|
130
|
+
hasInternalIgnore
|
|
131
|
+
);
|
|
121
132
|
});
|
|
122
133
|
|
|
123
134
|
export const planVitePlusHooks = Effect.fn("planVitePlusHooks")(function* (projectDir: string) {
|
|
124
135
|
const { vpBin } = yield* validateInstalledVitePlus(projectDir).pipe(
|
|
125
|
-
Effect.mapError(
|
|
126
|
-
(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}),
|
|
136
|
+
Effect.mapError((error) =>
|
|
137
|
+
VitePlusHooksDependencyError.make({
|
|
138
|
+
message: `${error.message} before enabling setup.vitePlus.hooks`,
|
|
139
|
+
}),
|
|
130
140
|
),
|
|
131
141
|
);
|
|
132
142
|
const viteGitHooks = yield* Config.string("VITE_GIT_HOOKS").pipe(Config.withDefault(""));
|
|
@@ -158,7 +168,7 @@ export const applyVitePlusHooksPlan = Effect.fn("applyVitePlusHooksPlan")(functi
|
|
|
158
168
|
plan.hooksDir,
|
|
159
169
|
]);
|
|
160
170
|
if (!(yield* inspectVitePlusHooks(plan.projectDir))) {
|
|
161
|
-
return yield*
|
|
171
|
+
return yield* VitePlusHooksConvergenceError.make({
|
|
162
172
|
message: `Vite+ hook setup did not converge at ${plan.hooksPath}`,
|
|
163
173
|
});
|
|
164
174
|
}
|