@danieljvdm/dev-kit 0.13.0 → 0.15.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 +84 -90
- package/dev-kit.example.jsonc +4 -3
- package/package.json +1 -1
- package/schema/dev-kit.schema.json +19 -42
- package/skills/build-effect-apis/SKILL.md +22 -0
- package/skills/build-effect-apis/references/verification.md +16 -1
- package/skills/build-effect-clis/SKILL.md +48 -17
- package/skills/build-effect-clis/agents/openai.yaml +3 -3
- package/skills/build-effect-clis/references/entrypoints-and-testing.md +5 -0
- package/skills/build-effect-clis/references/processes-and-platform.md +3 -2
- package/skills/dev-kit/SKILL.md +115 -222
- package/skills/open-pull-request/SKILL.md +122 -0
- package/skills/open-pull-request/agents/openai.yaml +4 -0
- package/src/index.ts +6 -6
- package/src/manifest.ts +28 -29
- package/src/path-digest.ts +0 -13
- package/src/project-package.ts +51 -0
- package/src/project-state.ts +3 -0
- package/src/scaffold.ts +79 -0
- package/src/sync.ts +64 -171
- package/src/vite-plus-workflow.ts +82 -0
- package/src/worktrunk-config.ts +88 -0
- package/templates/vite-plus/github-actions-check.yml +0 -2
- package/templates/worktrunk/wt.toml +27 -0
- package/src/vite-plus-quality.ts +0 -148
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
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 }}"
|
package/src/vite-plus-quality.ts
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { Effect, Schema } from "effect";
|
|
2
|
-
|
|
3
|
-
import type { VitePlusQualityWorkflowStep } from "./manifest.ts";
|
|
4
|
-
import { readDirectDependencyNames } from "./project-package.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 VitePlusQualitySupportError extends Schema.TaggedError<VitePlusQualitySupportError>()(
|
|
11
|
-
"VitePlusQualitySupportError",
|
|
12
|
-
{ message: Schema.String },
|
|
13
|
-
) {}
|
|
14
|
-
|
|
15
|
-
export type VitePlusQualityWorkflow = {
|
|
16
|
-
readonly beforeChecks: ReadonlyArray<VitePlusQualityWorkflowStep>;
|
|
17
|
-
readonly typecheck: ReadonlyArray<string>;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export type VitePlusQualitySelection = {
|
|
21
|
-
readonly workflow: VitePlusQualityWorkflow;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const LOCKED_DEV_KIT_COMMAND =
|
|
25
|
-
"bun ./node_modules/@danieljvdm/dev-kit/bin/dev-kit.mjs apply --locked";
|
|
26
|
-
const BEFORE_CHECKS_MARKER =
|
|
27
|
-
" # Dev Kit inserts configured quality.workflow.beforeChecks steps here.\n\n";
|
|
28
|
-
const DEFAULT_WORKFLOW_TYPECHECK = ` - name: Type check with Effect TypeScript-Go
|
|
29
|
-
run: vp run typecheck`;
|
|
30
|
-
|
|
31
|
-
const replaceUniqueTemplateMarker = (
|
|
32
|
-
template: string,
|
|
33
|
-
marker: string,
|
|
34
|
-
replacement: string,
|
|
35
|
-
): string => {
|
|
36
|
-
const parts = template.split(marker);
|
|
37
|
-
|
|
38
|
-
if (parts.length !== 2) {
|
|
39
|
-
throw new Error(`expected exactly one generated template marker: ${marker}`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return `${parts[0]}${replacement}${parts[1]}`;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export const renderVitePlusWorkflowTemplate = (
|
|
46
|
-
template: string,
|
|
47
|
-
options: {
|
|
48
|
-
readonly devKitCommand?: string;
|
|
49
|
-
readonly workflow?: VitePlusQualityWorkflow;
|
|
50
|
-
} = {},
|
|
51
|
-
): string => {
|
|
52
|
-
const devKitCommand = options.devKitCommand;
|
|
53
|
-
const workflow = options.workflow;
|
|
54
|
-
let rendered =
|
|
55
|
-
devKitCommand === undefined || devKitCommand === LOCKED_DEV_KIT_COMMAND
|
|
56
|
-
? template
|
|
57
|
-
: replaceUniqueTemplateMarker(template, LOCKED_DEV_KIT_COMMAND, devKitCommand);
|
|
58
|
-
|
|
59
|
-
if (workflow !== undefined) {
|
|
60
|
-
const steps = workflow.beforeChecks
|
|
61
|
-
.map((step) => {
|
|
62
|
-
const commands = step.run
|
|
63
|
-
.flatMap((command) => command.split("\n"))
|
|
64
|
-
.map((line) => ` ${line}`)
|
|
65
|
-
.join("\n");
|
|
66
|
-
|
|
67
|
-
return ` - name: ${JSON.stringify(step.name)}
|
|
68
|
-
run: |
|
|
69
|
-
${commands}`;
|
|
70
|
-
})
|
|
71
|
-
.join("\n\n");
|
|
72
|
-
|
|
73
|
-
rendered = replaceUniqueTemplateMarker(
|
|
74
|
-
rendered,
|
|
75
|
-
BEFORE_CHECKS_MARKER,
|
|
76
|
-
steps.length === 0 ? "" : `${steps}\n\n`,
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
if (
|
|
80
|
-
workflow !== undefined &&
|
|
81
|
-
(workflow.typecheck.length !== 1 || workflow.typecheck[0] !== "vp run typecheck")
|
|
82
|
-
) {
|
|
83
|
-
const commands = workflow.typecheck
|
|
84
|
-
.flatMap((command) => command.split("\n"))
|
|
85
|
-
.map((line) => ` ${line}`)
|
|
86
|
-
.join("\n");
|
|
87
|
-
|
|
88
|
-
rendered = replaceUniqueTemplateMarker(
|
|
89
|
-
rendered,
|
|
90
|
-
DEFAULT_WORKFLOW_TYPECHECK,
|
|
91
|
-
` - name: Type check with Effect TypeScript-Go
|
|
92
|
-
run: |
|
|
93
|
-
${commands}`,
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return rendered;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
export const validateVitePlusQualitySupport = Effect.fn("validateVitePlusQualitySupport")(
|
|
101
|
-
function* (
|
|
102
|
-
projectDir: string,
|
|
103
|
-
packageRoot: string,
|
|
104
|
-
typescriptPackage: string,
|
|
105
|
-
selection: VitePlusQualitySelection,
|
|
106
|
-
) {
|
|
107
|
-
const dependencies = yield* readDirectDependencyNames(projectDir);
|
|
108
|
-
const required = new Set(["effect", "@effect/tsgo", typescriptPackage]);
|
|
109
|
-
|
|
110
|
-
yield* validateInstalledVitePlus(projectDir).pipe(
|
|
111
|
-
Effect.mapError((error) => VitePlusQualitySupportError.make({ message: error.message })),
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
if (projectDir !== packageRoot) required.add("@danieljvdm/dev-kit");
|
|
115
|
-
const missing = [...required].filter((dependency) => !dependencies.includes(dependency));
|
|
116
|
-
|
|
117
|
-
if (missing.length > 0) {
|
|
118
|
-
return yield* VitePlusQualitySupportError.make({
|
|
119
|
-
message: `setup.vitePlus.quality requires direct dependencies: ${missing.join(", ")}`,
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
if (selection.workflow.typecheck.length === 0) {
|
|
123
|
-
return yield* VitePlusQualitySupportError.make({
|
|
124
|
-
message: "setup.vitePlus.quality.workflow.typecheck requires at least one command",
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
for (const command of selection.workflow.typecheck) {
|
|
128
|
-
if (command.trim().length === 0) {
|
|
129
|
-
return yield* VitePlusQualitySupportError.make({
|
|
130
|
-
message: "setup.vitePlus.quality.workflow.typecheck commands must not be empty",
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
for (const step of selection.workflow.beforeChecks) {
|
|
135
|
-
if (step.name.trim().length === 0 || step.run.length === 0) {
|
|
136
|
-
return yield* VitePlusQualitySupportError.make({
|
|
137
|
-
message:
|
|
138
|
-
"setup.vitePlus.quality.workflow.beforeChecks steps require a name and at least one command",
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
if (step.run.some((command) => command.trim().length === 0)) {
|
|
142
|
-
return yield* VitePlusQualitySupportError.make({
|
|
143
|
-
message: "setup.vitePlus.quality.workflow.beforeChecks commands must not be empty",
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
);
|