@danieljvdm/dev-kit 0.6.0 → 0.7.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 +123 -56
- package/dev-kit.example.jsonc +7 -3
- package/package.json +19 -16
- package/schema/dev-kit.schema.json +38 -0
- package/skill-sources.jsonc +8 -12
- package/skill-sources.lock.json +3 -9
- package/skills/dev-kit/SKILL.md +52 -17
- package/skills/effect-ts/agents/openai.yaml +0 -1
- package/skills/effect-ts/references/audit-services.md +11 -11
- package/skills/effect-ts/references/guide-effect.md +56 -69
- package/skills/effect-ts/references/guide-error-handling.md +64 -73
- package/skills/effect-ts/references/guide-layers.md +187 -215
- package/skills/effect-ts/references/guide-observability.md +91 -116
- package/skills/effect-ts/references/guide-retries.md +32 -44
- package/skills/effect-ts/references/guide-schedule.md +26 -40
- package/skills/effect-ts/references/guide-schema.md +50 -57
- package/skills/effect-ts/references/guide-sql.md +47 -50
- package/skills/effect-ts/references/guide-testing.md +96 -98
- package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +7 -7
- package/skills/effect-ts/references/version-and-source.md +0 -1
- package/src/bin/dev-kit.ts +61 -28
- package/src/catalog-manager.ts +86 -34
- package/src/catalog.ts +71 -33
- package/src/cli-ui.ts +20 -16
- package/src/effect-source.ts +49 -19
- package/src/effect-tsgo.ts +66 -35
- package/src/gitignore.ts +19 -6
- package/src/index.ts +6 -0
- package/src/manifest.ts +38 -3
- package/src/node-symbolic-link.ts +3 -0
- package/src/oxlint-plugin-effect.js +3 -0
- package/src/oxlint-plugin-style.d.ts +8 -0
- package/src/oxlint-plugin-style.js +8 -0
- package/src/oxlint.js +14 -0
- package/src/oxlint.ts +14 -0
- package/src/package-skill-source.ts +189 -52
- package/src/path-digest.ts +31 -11
- package/src/project-package.ts +44 -19
- package/src/project-process-lock.ts +19 -12
- package/src/project-state.ts +11 -0
- package/src/skill-manager.ts +134 -55
- package/src/skill-selector.ts +8 -2
- package/src/source-manifest.ts +2 -6
- package/src/sync.ts +371 -103
- package/src/vendor.ts +112 -42
- package/src/vite-plus-hooks.ts +174 -0
- package/src/vite-plus-quality.ts +49 -0
- package/templates/vite-plus/github-actions-check.yml +44 -0
- package/templates/vite-plus/vite.config.ts +22 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { Config, Effect, FileSystem, Path, Schema, Stream } from "effect";
|
|
2
|
+
import { ChildProcess } from "effect/unstable/process";
|
|
3
|
+
|
|
4
|
+
import { readDirectDependencyNames } from "./project-package.ts";
|
|
5
|
+
|
|
6
|
+
export const VITE_PLUS_HOOKS_DIR = ".vite-hooks";
|
|
7
|
+
export const VITE_PLUS_HOOKS_PATH = `${VITE_PLUS_HOOKS_DIR}/_`;
|
|
8
|
+
|
|
9
|
+
export type VitePlusHooksPlan = {
|
|
10
|
+
readonly action: "configure" | "unchanged" | "skipped";
|
|
11
|
+
readonly hooksDir: string;
|
|
12
|
+
readonly hooksPath: string;
|
|
13
|
+
readonly projectDir: string;
|
|
14
|
+
readonly vpBin: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export class VitePlusHooksDependencyError extends Schema.TaggedErrorClass<VitePlusHooksDependencyError>()(
|
|
18
|
+
"VitePlusHooksDependencyError",
|
|
19
|
+
{ message: Schema.String },
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
export class VitePlusHooksConflictError extends Schema.TaggedErrorClass<VitePlusHooksConflictError>()(
|
|
23
|
+
"VitePlusHooksConflictError",
|
|
24
|
+
{ hooksPath: Schema.String },
|
|
25
|
+
) {
|
|
26
|
+
override get message() {
|
|
27
|
+
return `core.hooksPath is already set to "${this.hooksPath}"; refusing to replace another Git hook manager`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class VitePlusHooksCommandError extends Schema.TaggedErrorClass<VitePlusHooksCommandError>()(
|
|
32
|
+
"VitePlusHooksCommandError",
|
|
33
|
+
{ command: Schema.String, exitCode: Schema.Int, output: Schema.String },
|
|
34
|
+
) {
|
|
35
|
+
override get message() {
|
|
36
|
+
return this.output.length > 0
|
|
37
|
+
? `${this.command} exited with code ${this.exitCode}: ${this.output}`
|
|
38
|
+
: `${this.command} exited with code ${this.exitCode}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class VitePlusHooksConvergenceError extends Schema.TaggedErrorClass<VitePlusHooksConvergenceError>()(
|
|
43
|
+
"VitePlusHooksConvergenceError",
|
|
44
|
+
{ message: Schema.String },
|
|
45
|
+
) {}
|
|
46
|
+
|
|
47
|
+
const runCommand = Effect.fn("runVitePlusHooksCommand")(function* (
|
|
48
|
+
cwd: string,
|
|
49
|
+
command: string,
|
|
50
|
+
args: ReadonlyArray<string>,
|
|
51
|
+
allowedExitCodes: ReadonlyArray<number> = [0],
|
|
52
|
+
) {
|
|
53
|
+
const child = yield* ChildProcess.make(command, args, {
|
|
54
|
+
cwd,
|
|
55
|
+
stderr: "pipe",
|
|
56
|
+
stdout: "pipe",
|
|
57
|
+
});
|
|
58
|
+
const [output, exitCode] = yield* Effect.all([
|
|
59
|
+
Stream.mkString(Stream.decodeText(child.all)),
|
|
60
|
+
child.exitCode,
|
|
61
|
+
]);
|
|
62
|
+
const trimmed = output.trim();
|
|
63
|
+
|
|
64
|
+
if (!allowedExitCodes.includes(exitCode)) {
|
|
65
|
+
return yield* new VitePlusHooksCommandError({
|
|
66
|
+
command: [command, ...args].join(" "),
|
|
67
|
+
exitCode,
|
|
68
|
+
output: trimmed,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { exitCode, output: trimmed };
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const hasExecutableFile = Effect.fn("hasExecutableVitePlusHookFile")(function* (filePath: string) {
|
|
76
|
+
const fs = yield* FileSystem.FileSystem;
|
|
77
|
+
|
|
78
|
+
if (!(yield* fs.exists(filePath))) return false;
|
|
79
|
+
const info = yield* fs.stat(filePath);
|
|
80
|
+
|
|
81
|
+
return info.type === "File" && (info.mode & 0o111) !== 0;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const hasVitePlusPreCommitHook = Effect.fn("hasVitePlusPreCommitHook")(function* (
|
|
85
|
+
filePath: string,
|
|
86
|
+
) {
|
|
87
|
+
const fs = yield* FileSystem.FileSystem;
|
|
88
|
+
|
|
89
|
+
if (!(yield* fs.exists(filePath))) return false;
|
|
90
|
+
const info = yield* fs.stat(filePath);
|
|
91
|
+
|
|
92
|
+
return info.type === "File" && (yield* fs.readFileString(filePath)).includes("vp staged");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const inspectVitePlusHooks = Effect.fn("inspectVitePlusHooks")(function* (projectDir: string) {
|
|
96
|
+
const path = yield* Path.Path;
|
|
97
|
+
const configuredPath = yield* runCommand(
|
|
98
|
+
projectDir,
|
|
99
|
+
"git",
|
|
100
|
+
["config", "--local", "--get", "core.hooksPath"],
|
|
101
|
+
[0, 1],
|
|
102
|
+
);
|
|
103
|
+
const hooksPath = configuredPath.exitCode === 0 ? configuredPath.output : "";
|
|
104
|
+
|
|
105
|
+
if (
|
|
106
|
+
hooksPath.length > 0 &&
|
|
107
|
+
hooksPath !== VITE_PLUS_HOOKS_PATH &&
|
|
108
|
+
hooksPath !== ".husky" &&
|
|
109
|
+
!hooksPath.startsWith(".husky/")
|
|
110
|
+
) {
|
|
111
|
+
return yield* new VitePlusHooksConflictError({ hooksPath });
|
|
112
|
+
}
|
|
113
|
+
const internalDir = path.join(projectDir, VITE_PLUS_HOOKS_DIR, "_");
|
|
114
|
+
const [hasLauncher, hasDispatcher, hasPreCommit] = yield* Effect.all([
|
|
115
|
+
hasExecutableFile(path.join(internalDir, "h")),
|
|
116
|
+
hasExecutableFile(path.join(internalDir, "pre-commit")),
|
|
117
|
+
hasVitePlusPreCommitHook(path.join(projectDir, VITE_PLUS_HOOKS_DIR, "pre-commit")),
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
return hooksPath === VITE_PLUS_HOOKS_PATH && hasLauncher && hasDispatcher && hasPreCommit;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
export const planVitePlusHooks = Effect.fn("planVitePlusHooks")(function* (projectDir: string) {
|
|
124
|
+
const fs = yield* FileSystem.FileSystem;
|
|
125
|
+
const path = yield* Path.Path;
|
|
126
|
+
const dependencies = yield* readDirectDependencyNames(projectDir);
|
|
127
|
+
|
|
128
|
+
if (!dependencies.includes("vite-plus")) {
|
|
129
|
+
return yield* new VitePlusHooksDependencyError({
|
|
130
|
+
message: "vite-plus must be a direct project dependency before enabling setup.vitePlus.hooks",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const vpBin = path.join(projectDir, "node_modules", ".bin", "vp");
|
|
134
|
+
|
|
135
|
+
if (!(yield* fs.exists(vpBin))) {
|
|
136
|
+
return yield* new VitePlusHooksDependencyError({
|
|
137
|
+
message:
|
|
138
|
+
"vite-plus must be installed before enabling setup.vitePlus.hooks: node_modules/.bin/vp is missing",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const viteGitHooks = yield* Config.string("VITE_GIT_HOOKS").pipe(Config.withDefault(""));
|
|
142
|
+
const husky = yield* Config.string("HUSKY").pipe(Config.withDefault(""));
|
|
143
|
+
const action =
|
|
144
|
+
viteGitHooks === "0" || husky === "0"
|
|
145
|
+
? "skipped"
|
|
146
|
+
: (yield* inspectVitePlusHooks(projectDir))
|
|
147
|
+
? "unchanged"
|
|
148
|
+
: "configure";
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
action,
|
|
152
|
+
hooksDir: VITE_PLUS_HOOKS_DIR,
|
|
153
|
+
hooksPath: VITE_PLUS_HOOKS_PATH,
|
|
154
|
+
projectDir,
|
|
155
|
+
vpBin,
|
|
156
|
+
} satisfies VitePlusHooksPlan;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
export const applyVitePlusHooksPlan = Effect.fn("applyVitePlusHooksPlan")(function* (
|
|
160
|
+
plan: VitePlusHooksPlan,
|
|
161
|
+
) {
|
|
162
|
+
if (plan.action !== "configure") return;
|
|
163
|
+
yield* runCommand(plan.projectDir, plan.vpBin, [
|
|
164
|
+
"config",
|
|
165
|
+
"--no-agent",
|
|
166
|
+
"--hooks-dir",
|
|
167
|
+
plan.hooksDir,
|
|
168
|
+
]);
|
|
169
|
+
if (!(yield* inspectVitePlusHooks(plan.projectDir))) {
|
|
170
|
+
return yield* new VitePlusHooksConvergenceError({
|
|
171
|
+
message: `Vite+ hook setup did not converge at ${plan.hooksPath}`,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
import { readDirectDependencyNames, readProjectPackage } from "./project-package.ts";
|
|
4
|
+
|
|
5
|
+
export const VITE_PLUS_CONFIG_PATH = "vite.config.ts";
|
|
6
|
+
export const VITE_PLUS_CONFIG_TEMPLATE = "templates/vite-plus/vite.config.ts";
|
|
7
|
+
export const VITE_PLUS_GITHUB_ACTIONS_PATH = ".github/workflows/check.yml";
|
|
8
|
+
export const VITE_PLUS_GITHUB_ACTIONS_TEMPLATE = "templates/vite-plus/github-actions-check.yml";
|
|
9
|
+
|
|
10
|
+
export class VitePlusQualitySupportError extends Schema.TaggedErrorClass<VitePlusQualitySupportError>()(
|
|
11
|
+
"VitePlusQualitySupportError",
|
|
12
|
+
{ message: Schema.String },
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
export const validateVitePlusQualitySupport = Effect.fn("validateVitePlusQualitySupport")(
|
|
16
|
+
function* (projectDir: string, packageRoot: string, typescriptPackage: string) {
|
|
17
|
+
const fs = yield* FileSystem.FileSystem;
|
|
18
|
+
const path = yield* Path.Path;
|
|
19
|
+
const packageJson = yield* readProjectPackage(projectDir);
|
|
20
|
+
const dependencies = yield* readDirectDependencyNames(projectDir);
|
|
21
|
+
const required = new Set(["vite-plus", "effect", "@effect/tsgo", typescriptPackage]);
|
|
22
|
+
|
|
23
|
+
if (projectDir !== packageRoot) required.add("@danieljvdm/dev-kit");
|
|
24
|
+
const missing = [...required].filter((dependency) => !dependencies.includes(dependency));
|
|
25
|
+
|
|
26
|
+
if (missing.length > 0) {
|
|
27
|
+
return yield* new VitePlusQualitySupportError({
|
|
28
|
+
message: `setup.vitePlus.quality requires direct dependencies: ${missing.join(", ")}`,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const conflictingScripts = ["check", "typecheck"].filter(
|
|
32
|
+
(script) => packageJson.scripts?.[script] !== undefined,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (conflictingScripts.length > 0) {
|
|
36
|
+
return yield* new VitePlusQualitySupportError({
|
|
37
|
+
message: `setup.vitePlus.quality defines Vite tasks that conflict with package scripts: ${conflictingScripts.join(", ")}`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const vpBin = path.join(projectDir, "node_modules", ".bin", "vp");
|
|
41
|
+
|
|
42
|
+
if (!(yield* fs.exists(vpBin))) {
|
|
43
|
+
return yield* new VitePlusQualitySupportError({
|
|
44
|
+
message:
|
|
45
|
+
"vite-plus must be installed before enabling setup.vitePlus.quality: node_modules/.bin/vp is missing",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
check:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- name: Check out repository
|
|
21
|
+
uses: actions/checkout@v7
|
|
22
|
+
with:
|
|
23
|
+
persist-credentials: false
|
|
24
|
+
|
|
25
|
+
- name: Set up Vite+
|
|
26
|
+
uses: voidzero-dev/setup-vp@v1
|
|
27
|
+
with:
|
|
28
|
+
node-version: "24"
|
|
29
|
+
cache: true
|
|
30
|
+
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: vp install
|
|
33
|
+
|
|
34
|
+
- name: Check formatting
|
|
35
|
+
run: vp fmt --check
|
|
36
|
+
|
|
37
|
+
- name: Lint
|
|
38
|
+
run: vp lint
|
|
39
|
+
|
|
40
|
+
- name: Run tests
|
|
41
|
+
run: vp test
|
|
42
|
+
|
|
43
|
+
- name: Type check with Effect TypeScript-Go
|
|
44
|
+
run: vp run typecheck
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { recommendedOxfmtConfig } from "@danieljvdm/dev-kit/oxfmt";
|
|
2
|
+
import { recommendedOxlintConfig } from "@danieljvdm/dev-kit/oxlint";
|
|
3
|
+
import { defineConfig } from "vite-plus";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
staged: {
|
|
7
|
+
"*": "vp check --fix",
|
|
8
|
+
},
|
|
9
|
+
fmt: {
|
|
10
|
+
...recommendedOxfmtConfig,
|
|
11
|
+
},
|
|
12
|
+
lint: {
|
|
13
|
+
extends: [recommendedOxlintConfig],
|
|
14
|
+
ignorePatterns: [".agents/**", ".dev-kit/**", ".repos/**"],
|
|
15
|
+
},
|
|
16
|
+
run: {
|
|
17
|
+
tasks: {
|
|
18
|
+
check: ["vp fmt --check", "vp lint", "vp test", "vp run typecheck"],
|
|
19
|
+
typecheck: "tsc --noEmit",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|