@danieljvdm/dev-kit 0.17.0 → 1.0.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 +148 -648
- package/package.json +19 -62
- package/skills/dev-kit/SKILL.md +42 -213
- package/skills/dev-kit/agents/openai.yaml +2 -2
- package/skills/dev-kit/references/cloudflare-worker-api.md +37 -0
- package/skills/dev-kit/references/default-typescript-repository.md +43 -0
- package/skills/dev-kit/references/legacy-eject.md +46 -0
- package/skills/dev-kit/references/repository-setup.md +53 -0
- package/skills/dev-kit/references/skills.md +35 -0
- package/src/bin/dev-kit.ts +153 -134
- package/src/catalog-manager.ts +23 -18
- package/src/catalog.ts +8 -5
- package/src/cli-ui.ts +2 -2
- package/src/effect-tsgo.ts +10 -6
- package/src/eject.ts +715 -0
- package/src/gitignore.ts +6 -3
- package/src/legacy-project.ts +67 -0
- package/src/oxfmt.ts +1 -4
- package/src/oxlint-plugin-anti-slop/LICENSE +21 -0
- package/src/oxlint-plugin-anti-slop/index.ts +43 -0
- package/src/oxlint-plugin-anti-slop/rules/no-chained-type-assertions.ts +79 -0
- package/src/oxlint-plugin-anti-slop/rules/no-conditional-empty-object-spread.ts +51 -0
- package/src/oxlint-plugin-anti-slop/rules/no-known-value-widening.ts +267 -0
- package/src/oxlint-plugin-anti-slop/rules/no-module-mocking.ts +105 -0
- package/src/oxlint-plugin-anti-slop/rules/no-object-parameters.ts +141 -0
- package/src/oxlint-plugin-anti-slop/rules/no-reflect-apply.ts +28 -0
- package/src/oxlint-plugin-anti-slop/rules/no-reflect-get.ts +28 -0
- package/src/oxlint-plugin-anti-slop/rules/no-runtime-typeof.ts +25 -0
- package/src/oxlint-plugin-anti-slop/rules/no-shape-in-symbol-names.ts +38 -0
- package/src/oxlint-plugin-anti-slop/rules/no-unknown-parameters.ts +86 -0
- package/src/oxlint-plugin-anti-slop/rules/no-unknown-returns.ts +125 -0
- package/src/oxlint-plugin-anti-slop/rules/no-unknown-type-aliases.ts +73 -0
- package/src/oxlint-plugin-anti-slop/rules/no-unsafe-dictionary-type.ts +139 -0
- package/src/oxlint-plugin-anti-slop/rules/no-widen-then-assert.ts +396 -0
- package/src/oxlint-plugin-anti-slop/rules/require-safety-comment-for-type-assertion.ts +61 -0
- package/src/oxlint-plugin-anti-slop/runtime.js +1209 -0
- package/src/oxlint-plugin-anti-slop/shared/dictionary-types.ts +555 -0
- package/src/oxlint-plugin-anti-slop/shared/reflect-method.ts +40 -0
- package/src/oxlint.ts +26 -14
- package/src/package-skill-source.ts +17 -25
- package/src/path-digest.ts +62 -1
- package/src/project-package.ts +14 -12
- package/src/project-skills.ts +722 -0
- package/src/tool-metadata.ts +0 -2
- package/src/vendor.ts +35 -21
- package/src/vite-plus.ts +1 -3
- package/dev-kit.example.jsonc +0 -22
- package/schema/dev-kit.schema.json +0 -218
- package/schema/skill-sources.schema.json +0 -83
- package/src/index.ts +0 -125
- package/src/manifest.ts +0 -216
- package/src/oxfmt.js +0 -23
- package/src/oxlint-plugin-effect.d.ts +0 -17
- package/src/oxlint-plugin-style.d.ts +0 -8
- package/src/oxlint.js +0 -94
- package/src/project-state.ts +0 -122
- package/src/scaffold.ts +0 -79
- package/src/skill-manager.ts +0 -515
- package/src/sync.ts +0 -1919
- package/src/tool-ignore-patterns.js +0 -9
- package/src/vite-plus-dependency.ts +0 -69
- package/src/vite-plus-hooks.ts +0 -175
- package/src/vite-plus-workflow.ts +0 -82
- package/src/vite-plus.js +0 -88
- package/src/worktrunk-config.ts +0 -88
- package/templates/AGENTS.md +0 -11
- package/templates/vite-plus/github-actions-check.yml +0 -51
- package/templates/worktrunk/wt.toml +0 -27
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
2
|
-
import semver from "semver";
|
|
3
|
-
|
|
4
|
-
import { readDirectDependencyNames } from "./project-package.ts";
|
|
5
|
-
import { VITE_PLUS_SUPPORTED_RANGE } from "./tool-metadata.ts";
|
|
6
|
-
|
|
7
|
-
const InstalledVitePlusPackageSchema = Schema.fromJsonString(
|
|
8
|
-
Schema.Struct({ version: Schema.String }),
|
|
9
|
-
);
|
|
10
|
-
|
|
11
|
-
export class VitePlusDependencyError extends Schema.TaggedError<VitePlusDependencyError>()(
|
|
12
|
-
"VitePlusDependencyError",
|
|
13
|
-
{ message: Schema.String },
|
|
14
|
-
) {}
|
|
15
|
-
|
|
16
|
-
export type InstalledVitePlus = {
|
|
17
|
-
readonly version: string;
|
|
18
|
-
readonly vpBin: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const validateInstalledVitePlus = Effect.fn("validateInstalledVitePlus")(function* (
|
|
22
|
-
projectDir: string,
|
|
23
|
-
) {
|
|
24
|
-
const fs = yield* FileSystem.FileSystem;
|
|
25
|
-
const path = yield* Path.Path;
|
|
26
|
-
const dependencies = yield* readDirectDependencyNames(projectDir);
|
|
27
|
-
|
|
28
|
-
if (!dependencies.includes("vite-plus")) {
|
|
29
|
-
return yield* VitePlusDependencyError.make({
|
|
30
|
-
message: "vite-plus must be a direct project dependency",
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
const packagePath = path.join(projectDir, "node_modules", "vite-plus", "package.json");
|
|
34
|
-
|
|
35
|
-
if (!(yield* fs.exists(packagePath))) {
|
|
36
|
-
return yield* VitePlusDependencyError.make({
|
|
37
|
-
message:
|
|
38
|
-
"vite-plus must be installed before enabling this setup: node_modules/vite-plus/package.json is missing",
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
const installed = yield* fs.readFileString(packagePath).pipe(
|
|
42
|
-
Effect.flatMap(Schema.decodeUnknownEffect(InstalledVitePlusPackageSchema)),
|
|
43
|
-
Effect.mapError(() =>
|
|
44
|
-
VitePlusDependencyError.make({
|
|
45
|
-
message: "installed vite-plus package metadata has no valid version",
|
|
46
|
-
}),
|
|
47
|
-
),
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
if (!semver.valid(installed.version)) {
|
|
51
|
-
return yield* VitePlusDependencyError.make({
|
|
52
|
-
message: `installed vite-plus package metadata has invalid version: ${installed.version}`,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
if (!semver.satisfies(installed.version, VITE_PLUS_SUPPORTED_RANGE)) {
|
|
56
|
-
return yield* VitePlusDependencyError.make({
|
|
57
|
-
message: `installed vite-plus ${installed.version} is incompatible with @danieljvdm/dev-kit; supported range: ${VITE_PLUS_SUPPORTED_RANGE}`,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
const vpBin = path.join(projectDir, "node_modules", ".bin", "vp");
|
|
61
|
-
|
|
62
|
-
if (!(yield* fs.exists(vpBin))) {
|
|
63
|
-
return yield* VitePlusDependencyError.make({
|
|
64
|
-
message: "vite-plus is installed but node_modules/.bin/vp is missing",
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return { version: installed.version, vpBin } satisfies InstalledVitePlus;
|
|
69
|
-
});
|
package/src/vite-plus-hooks.ts
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { Config, Effect, FileSystem, Path, Schema, Stream } from "effect";
|
|
2
|
-
import { ChildProcess } from "effect/unstable/process";
|
|
3
|
-
|
|
4
|
-
import { validateInstalledVitePlus } from "./vite-plus-dependency.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.TaggedError<VitePlusHooksDependencyError>()(
|
|
18
|
-
"VitePlusHooksDependencyError",
|
|
19
|
-
{ message: Schema.String },
|
|
20
|
-
) {}
|
|
21
|
-
|
|
22
|
-
export class VitePlusHooksConflictError extends Schema.TaggedError<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.TaggedError<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.TaggedError<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* VitePlusHooksCommandError.make({
|
|
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* VitePlusHooksConflictError.make({ hooksPath });
|
|
112
|
-
}
|
|
113
|
-
const internalDir = path.join(projectDir, VITE_PLUS_HOOKS_DIR, "_");
|
|
114
|
-
const [hasLauncher, hasDispatcher, hasPreCommit, hasInternalIgnore] = 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
|
-
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
|
-
),
|
|
123
|
-
]);
|
|
124
|
-
|
|
125
|
-
return (
|
|
126
|
-
hooksPath === VITE_PLUS_HOOKS_PATH &&
|
|
127
|
-
hasLauncher &&
|
|
128
|
-
hasDispatcher &&
|
|
129
|
-
hasPreCommit &&
|
|
130
|
-
hasInternalIgnore
|
|
131
|
-
);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
export const planVitePlusHooks = Effect.fn("planVitePlusHooks")(function* (projectDir: string) {
|
|
135
|
-
const { vpBin } = yield* validateInstalledVitePlus(projectDir).pipe(
|
|
136
|
-
Effect.mapError((error) =>
|
|
137
|
-
VitePlusHooksDependencyError.make({
|
|
138
|
-
message: `${error.message} before enabling setup.vitePlus.hooks`,
|
|
139
|
-
}),
|
|
140
|
-
),
|
|
141
|
-
);
|
|
142
|
-
const viteGitHooks = yield* Config.string("VITE_GIT_HOOKS").pipe(Config.withDefault(""));
|
|
143
|
-
const husky = yield* Config.string("HUSKY").pipe(Config.withDefault(""));
|
|
144
|
-
const action =
|
|
145
|
-
viteGitHooks === "0" || husky === "0"
|
|
146
|
-
? "skipped"
|
|
147
|
-
: (yield* inspectVitePlusHooks(projectDir))
|
|
148
|
-
? "unchanged"
|
|
149
|
-
: "configure";
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
action,
|
|
153
|
-
hooksDir: VITE_PLUS_HOOKS_DIR,
|
|
154
|
-
hooksPath: VITE_PLUS_HOOKS_PATH,
|
|
155
|
-
projectDir,
|
|
156
|
-
vpBin,
|
|
157
|
-
} satisfies VitePlusHooksPlan;
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
export const applyVitePlusHooksPlan = Effect.fn("applyVitePlusHooksPlan")(function* (
|
|
161
|
-
plan: VitePlusHooksPlan,
|
|
162
|
-
) {
|
|
163
|
-
if (plan.action !== "configure") return;
|
|
164
|
-
yield* runCommand(plan.projectDir, plan.vpBin, [
|
|
165
|
-
"config",
|
|
166
|
-
"--no-agent",
|
|
167
|
-
"--hooks-dir",
|
|
168
|
-
plan.hooksDir,
|
|
169
|
-
]);
|
|
170
|
-
if (!(yield* inspectVitePlusHooks(plan.projectDir))) {
|
|
171
|
-
return yield* VitePlusHooksConvergenceError.make({
|
|
172
|
-
message: `Vite+ hook setup did not converge at ${plan.hooksPath}`,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { Effect, Schema } from "effect";
|
|
2
|
-
|
|
3
|
-
import { readDirectDependencyNames } from "./project-package.ts";
|
|
4
|
-
import { planScaffold, readScaffoldTemplate, replaceUniqueTemplateMarker } from "./scaffold.ts";
|
|
5
|
-
import { validateInstalledVitePlus } from "./vite-plus-dependency.ts";
|
|
6
|
-
|
|
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 VitePlusWorkflowSupportError extends Schema.TaggedError<VitePlusWorkflowSupportError>()(
|
|
11
|
-
"VitePlusWorkflowSupportError",
|
|
12
|
-
{ message: Schema.String },
|
|
13
|
-
) {}
|
|
14
|
-
|
|
15
|
-
const LOCKED_DEV_KIT_COMMAND =
|
|
16
|
-
"bun ./node_modules/@danieljvdm/dev-kit/bin/dev-kit.mjs apply --locked";
|
|
17
|
-
|
|
18
|
-
export const renderVitePlusWorkflowTemplate = (
|
|
19
|
-
template: string,
|
|
20
|
-
options: { readonly devKitCommand?: string } = {},
|
|
21
|
-
): string => {
|
|
22
|
-
const devKitCommand = options.devKitCommand;
|
|
23
|
-
|
|
24
|
-
return devKitCommand === undefined || devKitCommand === LOCKED_DEV_KIT_COMMAND
|
|
25
|
-
? template
|
|
26
|
-
: replaceUniqueTemplateMarker(template, LOCKED_DEV_KIT_COMMAND, devKitCommand);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const validateVitePlusWorkflowSupport = Effect.fn("validateVitePlusWorkflowSupport")(
|
|
30
|
-
function* (projectDir: string, packageRoot: string, typescriptPackage: string) {
|
|
31
|
-
const dependencies = yield* readDirectDependencyNames(projectDir);
|
|
32
|
-
const required = new Set(["effect", "@effect/tsgo", typescriptPackage]);
|
|
33
|
-
|
|
34
|
-
yield* validateInstalledVitePlus(projectDir).pipe(
|
|
35
|
-
Effect.mapError((error) => VitePlusWorkflowSupportError.make({ message: error.message })),
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
if (projectDir !== packageRoot) required.add("@danieljvdm/dev-kit");
|
|
39
|
-
const missing = [...required].filter((dependency) => !dependencies.includes(dependency));
|
|
40
|
-
|
|
41
|
-
if (missing.length > 0) {
|
|
42
|
-
return yield* VitePlusWorkflowSupportError.make({
|
|
43
|
-
message: `setup.vitePlus.workflow requires direct dependencies: ${missing.join(", ")}`,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
export const planVitePlusWorkflow = (options: {
|
|
50
|
-
readonly packageRoot: string;
|
|
51
|
-
readonly projectDir: string;
|
|
52
|
-
readonly effectTsgoEnabled: boolean;
|
|
53
|
-
readonly typescriptPackage: string;
|
|
54
|
-
}) =>
|
|
55
|
-
planScaffold({
|
|
56
|
-
projectDir: options.projectDir,
|
|
57
|
-
path: VITE_PLUS_GITHUB_ACTIONS_PATH,
|
|
58
|
-
content: Effect.gen(function* () {
|
|
59
|
-
if (!options.effectTsgoEnabled) {
|
|
60
|
-
return yield* VitePlusWorkflowSupportError.make({
|
|
61
|
-
message:
|
|
62
|
-
"setup.vitePlus.workflow requires setup.effectTsgo.enabled so the scaffolded workflow's typecheck uses the Effect-patched compiler",
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
yield* validateVitePlusWorkflowSupport(
|
|
66
|
-
options.projectDir,
|
|
67
|
-
options.packageRoot,
|
|
68
|
-
options.typescriptPackage,
|
|
69
|
-
);
|
|
70
|
-
const template = yield* readScaffoldTemplate(
|
|
71
|
-
options.packageRoot,
|
|
72
|
-
VITE_PLUS_GITHUB_ACTIONS_TEMPLATE,
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
return renderVitePlusWorkflowTemplate(
|
|
76
|
-
template,
|
|
77
|
-
options.projectDir === options.packageRoot
|
|
78
|
-
? { devKitCommand: "./bin/dev-kit.mjs apply --locked" }
|
|
79
|
-
: {},
|
|
80
|
-
);
|
|
81
|
-
}),
|
|
82
|
-
});
|
package/src/vite-plus.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { recommendedOxfmtConfig } from "./oxfmt.js";
|
|
2
|
-
import { createAbsoluteImportsOxlintOverride, recommendedOxlintConfig } from "./oxlint.js";
|
|
3
|
-
import { devKitToolIgnorePatterns } from "./tool-ignore-patterns.js";
|
|
4
|
-
|
|
5
|
-
export { devKitToolIgnorePatterns } from "./tool-ignore-patterns.js";
|
|
6
|
-
|
|
7
|
-
const shellQuote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
8
|
-
|
|
9
|
-
const validateWorkspacePackage = (packageDir) => {
|
|
10
|
-
const segments = packageDir.split(/[\\/]/);
|
|
11
|
-
|
|
12
|
-
if (
|
|
13
|
-
packageDir.trim().length === 0 ||
|
|
14
|
-
packageDir === "." ||
|
|
15
|
-
packageDir === "./" ||
|
|
16
|
-
packageDir === ".\\" ||
|
|
17
|
-
packageDir.startsWith("/") ||
|
|
18
|
-
packageDir.startsWith("\\") ||
|
|
19
|
-
/^[A-Za-z]:[\\/]/.test(packageDir) ||
|
|
20
|
-
segments.includes("..")
|
|
21
|
-
) {
|
|
22
|
-
throw new Error(`workspace package must be a project-relative subdirectory: ${packageDir}`);
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const createTypecheckTask = (options) => {
|
|
27
|
-
if (options?.strategy !== "workspace") {
|
|
28
|
-
const command = options?.command ?? "tsc --noEmit";
|
|
29
|
-
|
|
30
|
-
if (command.trim().length === 0) throw new Error("typecheck command must not be empty");
|
|
31
|
-
|
|
32
|
-
return command;
|
|
33
|
-
}
|
|
34
|
-
const concurrency = options.concurrency ?? 4;
|
|
35
|
-
|
|
36
|
-
if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 32) {
|
|
37
|
-
throw new Error("workspace typecheck concurrency must be between 1 and 32");
|
|
38
|
-
}
|
|
39
|
-
if (options.packages.length === 0) {
|
|
40
|
-
throw new Error("workspace typechecking requires at least one package directory");
|
|
41
|
-
}
|
|
42
|
-
const packages = [...new Set(options.packages)];
|
|
43
|
-
|
|
44
|
-
if (packages.length !== options.packages.length) {
|
|
45
|
-
throw new Error("workspace typecheck packages must be unique");
|
|
46
|
-
}
|
|
47
|
-
for (const packageDir of packages) validateWorkspacePackage(packageDir);
|
|
48
|
-
const filters = packages
|
|
49
|
-
.map((packageDir) => `--filter ${shellQuote(`./${packageDir}`)}`)
|
|
50
|
-
.join(" ");
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
command: `vp run --cache --concurrency-limit ${concurrency} ${filters} --fail-if-no-match typecheck`,
|
|
54
|
-
cache: false,
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
/** Build composable quality defaults for a project-owned Vite+ config. */
|
|
59
|
-
export const createRecommendedVitePlusConfig = (options = {}) => {
|
|
60
|
-
const ignorePatterns = [...devKitToolIgnorePatterns, ...(options.ignorePatterns ?? [])];
|
|
61
|
-
const lintOverrides = options.absoluteImports
|
|
62
|
-
? [
|
|
63
|
-
...recommendedOxlintConfig.overrides,
|
|
64
|
-
createAbsoluteImportsOxlintOverride(options.absoluteImports),
|
|
65
|
-
]
|
|
66
|
-
: recommendedOxlintConfig.overrides;
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
staged: {
|
|
70
|
-
"*": "vp check --fix",
|
|
71
|
-
},
|
|
72
|
-
fmt: {
|
|
73
|
-
...recommendedOxfmtConfig,
|
|
74
|
-
ignorePatterns,
|
|
75
|
-
},
|
|
76
|
-
lint: {
|
|
77
|
-
...recommendedOxlintConfig,
|
|
78
|
-
ignorePatterns,
|
|
79
|
-
overrides: lintOverrides,
|
|
80
|
-
},
|
|
81
|
-
run: {
|
|
82
|
-
tasks: {
|
|
83
|
-
check: ["vp fmt --check", "vp lint", "vp test", "vp run typecheck"],
|
|
84
|
-
typecheck: createTypecheckTask(options.typecheck),
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
};
|
package/src/worktrunk-config.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { Effect, Schema } from "effect";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
detectPackageManager,
|
|
5
|
-
PACKAGE_MANAGER_COMMANDS,
|
|
6
|
-
readDirectDependencyNames,
|
|
7
|
-
readProjectPackage,
|
|
8
|
-
} from "./project-package.ts";
|
|
9
|
-
import { planScaffold, readScaffoldTemplate, replaceUniqueTemplateMarker } from "./scaffold.ts";
|
|
10
|
-
|
|
11
|
-
export const WORKTRUNK_CONFIG_PATH = ".config/wt.toml";
|
|
12
|
-
export const WORKTRUNK_CONFIG_TEMPLATE = "templates/worktrunk/wt.toml";
|
|
13
|
-
|
|
14
|
-
export class WorktrunkConfigSupportError extends Schema.TaggedError<WorktrunkConfigSupportError>()(
|
|
15
|
-
"WorktrunkConfigSupportError",
|
|
16
|
-
{ message: Schema.String },
|
|
17
|
-
) {}
|
|
18
|
-
|
|
19
|
-
export type WorktrunkConfigCommands = {
|
|
20
|
-
readonly preMerge: string;
|
|
21
|
-
readonly install: string;
|
|
22
|
-
readonly dev: string;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const TEMPLATE_COMMANDS: WorktrunkConfigCommands = {
|
|
26
|
-
preMerge: "vp run check",
|
|
27
|
-
install: "vp install",
|
|
28
|
-
dev: "vp dev",
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export const renderWorktrunkConfigTemplate = (
|
|
32
|
-
template: string,
|
|
33
|
-
commands: WorktrunkConfigCommands = TEMPLATE_COMMANDS,
|
|
34
|
-
): string => {
|
|
35
|
-
const replacements: ReadonlyArray<readonly [string, string]> = [
|
|
36
|
-
[`pre-merge = "${TEMPLATE_COMMANDS.preMerge}"`, `pre-merge = "${commands.preMerge}"`],
|
|
37
|
-
[`install = "${TEMPLATE_COMMANDS.install}"`, `install = "${commands.install}"`],
|
|
38
|
-
[TEMPLATE_COMMANDS.dev, commands.dev],
|
|
39
|
-
];
|
|
40
|
-
let rendered = template;
|
|
41
|
-
|
|
42
|
-
for (const [marker, replacement] of replacements) {
|
|
43
|
-
if (marker === replacement) continue;
|
|
44
|
-
rendered = replaceUniqueTemplateMarker(rendered, marker, replacement);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return rendered;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export const resolveWorktrunkConfigCommands = Effect.fn("resolveWorktrunkConfigCommands")(
|
|
51
|
-
function* (projectDir: string) {
|
|
52
|
-
const dependencies = yield* readDirectDependencyNames(projectDir);
|
|
53
|
-
|
|
54
|
-
if (dependencies.includes("vite-plus")) return TEMPLATE_COMMANDS;
|
|
55
|
-
const projectPackage = yield* readProjectPackage(projectDir).pipe(
|
|
56
|
-
Effect.catchTag("ProjectPackageError", (error) =>
|
|
57
|
-
error.message.startsWith("package.json not found:") ? Effect.void : Effect.fail(error),
|
|
58
|
-
),
|
|
59
|
-
);
|
|
60
|
-
const scripts = projectPackage?.scripts ?? {};
|
|
61
|
-
|
|
62
|
-
if (scripts["check"] === undefined) {
|
|
63
|
-
return yield* WorktrunkConfigSupportError.make({
|
|
64
|
-
message:
|
|
65
|
-
'setup.worktrunk.config requires a direct vite-plus dependency or a root "check" package script for the pre-merge hook',
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
const manager = yield* detectPackageManager(projectDir, projectPackage?.packageManager);
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
preMerge: "bun run check",
|
|
72
|
-
install: PACKAGE_MANAGER_COMMANDS[manager ?? "bun"].install,
|
|
73
|
-
dev: "bun run dev",
|
|
74
|
-
} satisfies WorktrunkConfigCommands;
|
|
75
|
-
},
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
export const planWorktrunkConfig = (packageRoot: string, projectDir: string) =>
|
|
79
|
-
planScaffold({
|
|
80
|
-
projectDir,
|
|
81
|
-
path: WORKTRUNK_CONFIG_PATH,
|
|
82
|
-
content: Effect.gen(function* () {
|
|
83
|
-
const template = yield* readScaffoldTemplate(packageRoot, WORKTRUNK_CONFIG_TEMPLATE);
|
|
84
|
-
const commands = yield* resolveWorktrunkConfigCommands(projectDir);
|
|
85
|
-
|
|
86
|
-
return renderWorktrunkConfigTemplate(template, commands);
|
|
87
|
-
}),
|
|
88
|
-
});
|
package/templates/AGENTS.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
<!-- DEV KIT START -->
|
|
2
|
-
|
|
3
|
-
# Dev Kit
|
|
4
|
-
|
|
5
|
-
This project uses `@danieljvdm/dev-kit` to manage portable agent skills and reproducible setup from `dev-kit.jsonc` and `dev-kit.lock.json`.
|
|
6
|
-
|
|
7
|
-
For dev-kit operations, use the `dev-kit` skill and read `{{DEV_KIT_SKILL_PATH}}` before changing managed outputs.
|
|
8
|
-
|
|
9
|
-
{{EFFECT_INSTRUCTIONS}}{{PROJECT_COMMAND_POLICY}}
|
|
10
|
-
|
|
11
|
-
<!-- DEV KIT END -->
|
|
@@ -1,51 +0,0 @@
|
|
|
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
|
-
# setup-bun resolves the consumer's packageManager or engines.bun version.
|
|
26
|
-
- name: Set up Bun
|
|
27
|
-
uses: oven-sh/setup-bun@v2
|
|
28
|
-
|
|
29
|
-
- name: Set up Vite+ and install dependencies
|
|
30
|
-
uses: voidzero-dev/setup-vp@v1.16.1
|
|
31
|
-
with:
|
|
32
|
-
# Vite+ version intentionally resolves from the consumer manifest/lock.
|
|
33
|
-
node-version: "24"
|
|
34
|
-
cache: true
|
|
35
|
-
run-install: |
|
|
36
|
-
args: ["--frozen-lockfile", "--ignore-scripts"]
|
|
37
|
-
|
|
38
|
-
- name: Verify locked Dev Kit setup
|
|
39
|
-
run: bun ./node_modules/@danieljvdm/dev-kit/bin/dev-kit.mjs apply --locked
|
|
40
|
-
|
|
41
|
-
- name: Check formatting
|
|
42
|
-
run: vp fmt --check
|
|
43
|
-
|
|
44
|
-
- name: Lint
|
|
45
|
-
run: vp lint
|
|
46
|
-
|
|
47
|
-
- name: Run tests
|
|
48
|
-
run: vp test
|
|
49
|
-
|
|
50
|
-
- name: Type check with Effect TypeScript-Go
|
|
51
|
-
run: vp run typecheck
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Worktrunk project hooks, scaffolded by @danieljvdm/dev-kit (setup.worktrunk.config).
|
|
2
|
-
# This repository owns the file — edit hooks freely; dev-kit never rewrites it.
|
|
3
|
-
# Hooks never run until each user reviews and approves them: wt config approvals add
|
|
4
|
-
# Reference: https://worktrunk.dev/hooks/
|
|
5
|
-
|
|
6
|
-
# Full validation before a worktree merges back.
|
|
7
|
-
pre-merge = "vp run check"
|
|
8
|
-
|
|
9
|
-
# Copy gitignored files matched by .worktreeinclude (.env, local caches) from
|
|
10
|
-
# the source worktree; without a .worktreeinclude file this is a no-op.
|
|
11
|
-
[[pre-start]]
|
|
12
|
-
copy-ignored = "wt step copy-ignored --require-include"
|
|
13
|
-
|
|
14
|
-
# Install dependencies after ignored files land so the new worktree is runnable.
|
|
15
|
-
[[pre-start]]
|
|
16
|
-
install = "vp install"
|
|
17
|
-
|
|
18
|
-
# Optional per-worktree dev server on a stable branch-derived port; tether stops
|
|
19
|
-
# the server when its worktree is removed. Point the command at this
|
|
20
|
-
# repository's dev entrypoint (for example add `apps/web` in a monorepo) and
|
|
21
|
-
# uncomment:
|
|
22
|
-
# [post-start]
|
|
23
|
-
# dev = "wt step tether -- vp dev --host 127.0.0.1 --port {{ branch | hash_port }} --strictPort"
|
|
24
|
-
|
|
25
|
-
# Optional: show each worktree's dev-server URL in `wt list`:
|
|
26
|
-
# [list]
|
|
27
|
-
# url = "http://localhost:{{ branch | hash_port }}"
|