@danieljvdm/dev-kit 0.12.0 → 0.13.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 +44 -5
- package/package.json +1 -1
- package/skills/build-effect-clis/SKILL.md +61 -0
- package/skills/build-effect-clis/agents/openai.yaml +4 -0
- package/skills/build-effect-clis/references/command-design.md +93 -0
- package/skills/build-effect-clis/references/entrypoints-and-testing.md +59 -0
- package/skills/build-effect-clis/references/processes-and-platform.md +73 -0
- package/skills/dev-kit/SKILL.md +20 -3
- package/src/catalog.ts +1 -1
- package/src/effect-tsgo.ts +37 -0
- package/src/index.ts +3 -0
- package/src/skill-manager.ts +3 -3
- package/src/sync.ts +12 -12
- package/src/vite-plus.js +1 -1
- package/src/vite-plus.ts +1 -1
package/README.md
CHANGED
|
@@ -199,8 +199,10 @@ tool versions. A project-local process lock also prevents concurrent applies.
|
|
|
199
199
|
|
|
200
200
|
- `dev-kit` installs guidance for operating the toolkit itself.
|
|
201
201
|
- `effect` expands to the package-guidance `effect-ts` bootstrap, the
|
|
202
|
-
opinionated `effect-architecture-audit`,
|
|
203
|
-
|
|
202
|
+
opinionated `effect-architecture-audit`, `build-effect-apis` for shared HTTP
|
|
203
|
+
contracts and clients, and `build-effect-clis` for typed command-line
|
|
204
|
+
applications. The focused references cover Effect Atom, TanStack Start,
|
|
205
|
+
Cloudflare Workers, child processes, runtime entrypoints, and CLI testing.
|
|
204
206
|
- Prefer individual external skills such as `workers-best-practices` and
|
|
205
207
|
`wrangler`, selected after scanning the project for relevant technologies.
|
|
206
208
|
- `serve-sim` selects the approved Evan Bacon simulator skill directly.
|
|
@@ -305,7 +307,9 @@ export default defineConfig({
|
|
|
305
307
|
|
|
306
308
|
Spread the returned top-level config before local options. When overriding a
|
|
307
309
|
`fmt`, `lint`, `run`, or `staged` block, spread that returned block as well so
|
|
308
|
-
its defaults remain composed.
|
|
310
|
+
its defaults remain composed. Merge nested collections too; for example, a
|
|
311
|
+
local lint rule block starts with `...recommended.lint.rules` before adding
|
|
312
|
+
repository-specific rules.
|
|
309
313
|
|
|
310
314
|
The factory configures `vp staged`, matching Oxlint/Oxfmt ignores for Dev Kit's
|
|
311
315
|
tool-owned paths, and separate `vp run check` and pure `vp run typecheck` tasks.
|
|
@@ -313,6 +317,13 @@ Project and framework-generated paths belong in `ignorePatterns` as shown;
|
|
|
313
317
|
custom harness target paths belong there too. Dev Kit does not grow a global
|
|
314
318
|
framework ignore list.
|
|
315
319
|
|
|
320
|
+
Vite+ 0.2.6 forwards JavaScript-plugin declarations into its effective lint
|
|
321
|
+
config but its bundled native Oxlint path does not register or execute those
|
|
322
|
+
rules. Native Oxlint rules and Oxfmt settings remain active; run standalone
|
|
323
|
+
Oxlint when enforcement of Dev Kit's `effect/*` or
|
|
324
|
+
`stylistic/padding-line-between-statements` rules is required. This limitation
|
|
325
|
+
can be removed once a supported Vite+ release executes configured JS plugins.
|
|
326
|
+
|
|
316
327
|
```jsonc
|
|
317
328
|
{
|
|
318
329
|
"include": ["dev-kit", "effect"],
|
|
@@ -439,7 +450,30 @@ Pin the compatible packages in the consuming project:
|
|
|
439
450
|
{
|
|
440
451
|
"$schema": "./node_modules/@effect/tsgo/schema.json",
|
|
441
452
|
"compilerOptions": {
|
|
442
|
-
"plugins": [
|
|
453
|
+
"plugins": [
|
|
454
|
+
{
|
|
455
|
+
"name": "@effect/language-service",
|
|
456
|
+
"diagnosticSeverity": {
|
|
457
|
+
"anyUnknownInErrorContext": "warning",
|
|
458
|
+
"instanceOfSchema": "suggestion",
|
|
459
|
+
"nestedEffectGenYield": "suggestion",
|
|
460
|
+
"newSchemaClass": "suggestion",
|
|
461
|
+
"preferSchemaTypeProperty": "suggestion",
|
|
462
|
+
"unsafeEffectTypeAssertion": "warning",
|
|
463
|
+
},
|
|
464
|
+
"overrides": [
|
|
465
|
+
{
|
|
466
|
+
"include": ["src/**/*.ts"],
|
|
467
|
+
"options": {
|
|
468
|
+
"diagnosticSeverity": {
|
|
469
|
+
"nodeBuiltinImport": "warning",
|
|
470
|
+
"preferSchemaOverJson": "suggestion",
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
},
|
|
476
|
+
],
|
|
443
477
|
},
|
|
444
478
|
}
|
|
445
479
|
```
|
|
@@ -449,7 +483,12 @@ native TypeScript compiler. It does not download dependencies and skips an
|
|
|
449
483
|
installation that is already patched. Use `dev-kit tsgo patch --dry-run` when
|
|
450
484
|
troubleshooting the task directly.
|
|
451
485
|
|
|
452
|
-
|
|
486
|
+
The same typed object is exported as `recommendedEffectTsgoPlugin` for
|
|
487
|
+
programmatic configuration tooling. Dependency and `tsconfig.json` edits remain
|
|
488
|
+
explicit. In a monorepo, put the plugin in the shared root config and ensure
|
|
489
|
+
every workspace extends it without redeclaring `compilerOptions.plugins`:
|
|
490
|
+
TypeScript replaces that array in child configs rather than merging it. Adjust
|
|
491
|
+
the `src/**/*.ts` override to the source layout seen from each config file.
|
|
453
492
|
|
|
454
493
|
## Installed package skills
|
|
455
494
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-effect-clis
|
|
3
|
+
description: Build and maintain command-line applications entirely with Effect. Use when creating or changing CLI commands, arguments, flags, subcommands, prompts, help, JSON output, dry-run or confirmation flows, platform services, child processes, Node/Bun entrypoints, or CLI integration tests.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Build Effect CLIs
|
|
7
|
+
|
|
8
|
+
Treat a CLI as an Effect application: the `Command` tree owns the user-facing
|
|
9
|
+
contract, handlers adapt decoded input into application workflows, services own
|
|
10
|
+
capabilities, and the executable entrypoint supplies platform Layers and runs
|
|
11
|
+
the program. Do not introduce a separate CLI framework for new command work.
|
|
12
|
+
|
|
13
|
+
Effect CLI and process APIs are version-sensitive. Read the target repository's
|
|
14
|
+
`node_modules/effect/AGENTS.md` completely, follow its CLI and child-process
|
|
15
|
+
references, and confirm exact signatures from the installed declarations before
|
|
16
|
+
editing.
|
|
17
|
+
|
|
18
|
+
## Build the command boundary
|
|
19
|
+
|
|
20
|
+
1. Inventory the existing executable entrypoints, package scripts, command
|
|
21
|
+
tree, shared flags, prompts, application services, platform Layers, output
|
|
22
|
+
modes, and subprocess helpers. Finish when every way to invoke and test the
|
|
23
|
+
CLI is known.
|
|
24
|
+
2. Read [command-design.md](references/command-design.md). Define arguments and
|
|
25
|
+
flags with `Argument` and `Flag`, compose commands with `Command`, and give
|
|
26
|
+
every public input useful help. Use `Effect.fn` handlers and yield the root
|
|
27
|
+
command when a subcommand needs shared parent input.
|
|
28
|
+
3. Keep handlers thin. Decode user and file input at the boundary, enforce
|
|
29
|
+
cross-input invariants, then call an application service. Keep persistence,
|
|
30
|
+
network calls, orchestration, retries, and transactions in services.
|
|
31
|
+
4. Keep expected operational failures typed with `Schema.TaggedError`; map
|
|
32
|
+
platform failures into application-owned errors near the adapter that knows
|
|
33
|
+
what the operation means. Let defects remain defects.
|
|
34
|
+
5. Read [entrypoints-and-testing.md](references/entrypoints-and-testing.md).
|
|
35
|
+
Export the command tree without running it, wire one Node or Bun entrypoint,
|
|
36
|
+
and verify help, parsing, successful execution, expected failure, JSON
|
|
37
|
+
output, and every dry-run or confirmation path.
|
|
38
|
+
|
|
39
|
+
## Optional branches
|
|
40
|
+
|
|
41
|
+
- Read [processes-and-platform.md](references/processes-and-platform.md) when a
|
|
42
|
+
command reads files, inspects the environment, starts child processes,
|
|
43
|
+
streams their output, or differs between Node and Bun.
|
|
44
|
+
- Use `Prompt` only for an intentionally interactive path. Keep required inputs
|
|
45
|
+
expressible as arguments or flags so automation never depends on a terminal.
|
|
46
|
+
- Add `--dry-run` for commands that mutate important state and `--yes` for
|
|
47
|
+
explicitly authorized non-interactive confirmation. Never prompt in JSON or
|
|
48
|
+
CI-oriented modes.
|
|
49
|
+
|
|
50
|
+
## CLI ownership rules
|
|
51
|
+
|
|
52
|
+
- Let `Command`, `Argument`, and `Flag` own syntax, defaults, aliases, examples,
|
|
53
|
+
and help text.
|
|
54
|
+
- Let schemas own untrusted structured input and machine-readable output.
|
|
55
|
+
- Let handlers own CLI-to-application mapping and presentation selection.
|
|
56
|
+
- Let services own reusable behavior and external capabilities.
|
|
57
|
+
- Let Layers own implementations and runtime dependencies.
|
|
58
|
+
- Let the executable entrypoint own `Command.run`, platform provisioning,
|
|
59
|
+
scopes, signal handling, and `NodeRuntime.runMain` or `BunRuntime.runMain`.
|
|
60
|
+
- Keep stdout stable for primary or machine-readable output. Send diagnostics
|
|
61
|
+
and progress elsewhere; never mix prose into JSON output.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Command design
|
|
2
|
+
|
|
3
|
+
Model the CLI's public surface as a typed contract. Prefer a small root command,
|
|
4
|
+
shared parent flags, focused subcommands, and Effect handlers.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { Effect } from "effect";
|
|
8
|
+
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
9
|
+
|
|
10
|
+
const workspace = Flag.string("workspace").pipe(
|
|
11
|
+
Flag.withAlias("w"),
|
|
12
|
+
Flag.withDescription("Workspace to operate on"),
|
|
13
|
+
Flag.withDefault("personal"),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const root = Command.make("acme").pipe(
|
|
17
|
+
Command.withSharedFlags({
|
|
18
|
+
workspace,
|
|
19
|
+
verbose: Flag.boolean("verbose").pipe(
|
|
20
|
+
Flag.withAlias("v"),
|
|
21
|
+
Flag.withDescription("Print diagnostic output"),
|
|
22
|
+
),
|
|
23
|
+
}),
|
|
24
|
+
Command.withDescription("Operate Acme projects"),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const deploy = Command.make(
|
|
28
|
+
"deploy",
|
|
29
|
+
{
|
|
30
|
+
environment: Argument.string("environment").pipe(
|
|
31
|
+
Argument.withDescription("Target environment"),
|
|
32
|
+
),
|
|
33
|
+
dryRun: Flag.boolean("dry-run").pipe(
|
|
34
|
+
Flag.withDescription("Show the deployment plan without applying it"),
|
|
35
|
+
),
|
|
36
|
+
json: Flag.boolean("json").pipe(Flag.withDescription("Print machine-readable JSON")),
|
|
37
|
+
},
|
|
38
|
+
Effect.fn("deployCommand")(function* ({ dryRun, environment, json }) {
|
|
39
|
+
const shared = yield* root;
|
|
40
|
+
const deployments = yield* Deployments;
|
|
41
|
+
const plan = yield* deployments.plan({ environment, workspace: shared.workspace });
|
|
42
|
+
|
|
43
|
+
if (dryRun) return yield* renderPlan(plan, { json });
|
|
44
|
+
|
|
45
|
+
const result = yield* deployments.apply(plan);
|
|
46
|
+
return yield* renderDeployment(result, { json });
|
|
47
|
+
}),
|
|
48
|
+
).pipe(
|
|
49
|
+
Command.withDescription("Deploy a workspace"),
|
|
50
|
+
Command.withExamples([
|
|
51
|
+
{
|
|
52
|
+
command: "acme --workspace team deploy production --dry-run",
|
|
53
|
+
description: "Preview a production deployment",
|
|
54
|
+
},
|
|
55
|
+
]),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
export const command = root.pipe(Command.withSubcommands([deploy]));
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Input and help
|
|
62
|
+
|
|
63
|
+
- Use arguments for essential positional identity and flags for optional
|
|
64
|
+
behavior. Prefer named flags when position would be ambiguous.
|
|
65
|
+
- Give public commands, arguments, and non-obvious flags descriptions. Add
|
|
66
|
+
examples for quoting, shared flags, or surprising combinations.
|
|
67
|
+
- Put cross-command inputs in `Command.withSharedFlags`; read them by yielding
|
|
68
|
+
the parent command instead of duplicating parsing or reading globals.
|
|
69
|
+
- Use `Flag.choice` for closed vocabularies and schemas for structured values
|
|
70
|
+
loaded from JSON, files, or environment boundaries.
|
|
71
|
+
- Keep aliases additive and unsurprising. Never give two concepts the same
|
|
72
|
+
short flag in one command path.
|
|
73
|
+
|
|
74
|
+
## Interactive and automated use
|
|
75
|
+
|
|
76
|
+
- Make every required value available non-interactively. A fallback `Prompt`
|
|
77
|
+
may improve terminal use but must not be the only way to supply input.
|
|
78
|
+
- Prompt only after deterministic discovery cannot choose safely. Summarize the
|
|
79
|
+
detected state and the exact mutation before requesting confirmation.
|
|
80
|
+
- Pair destructive execution with a genuine plan/apply split. `--dry-run` must
|
|
81
|
+
run the real discovery and planning logic while skipping writes.
|
|
82
|
+
- Require explicit `--yes` or an equivalent authorization flag when a
|
|
83
|
+
non-interactive destructive path cannot prompt.
|
|
84
|
+
|
|
85
|
+
## Output contract
|
|
86
|
+
|
|
87
|
+
- Treat human and machine output as separate renderers over the same result.
|
|
88
|
+
- In `--json` mode, emit one documented schema-encoded value to stdout. Send no
|
|
89
|
+
headings, spinners, progress, or warnings to stdout.
|
|
90
|
+
- Use stderr for diagnostics and failures. Keep normal human output concise and
|
|
91
|
+
stable enough for users to understand without reading source.
|
|
92
|
+
- Do not expose stack traces for expected failures. Preserve causes for logs and
|
|
93
|
+
tests, then render an actionable message at the executable boundary.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Entrypoints and testing
|
|
2
|
+
|
|
3
|
+
Separate command definition from execution. Importing a command module in a test
|
|
4
|
+
or another program must not parse `process.argv`, start fibers, or terminate the
|
|
5
|
+
process.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
// src/cli/command.ts
|
|
9
|
+
export const command = root.pipe(Command.withSubcommands([deploy, status]));
|
|
10
|
+
|
|
11
|
+
// src/bin/acme.ts
|
|
12
|
+
import { NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
13
|
+
import { Effect } from "effect";
|
|
14
|
+
import { Command } from "effect/unstable/cli";
|
|
15
|
+
import { command } from "../cli/command";
|
|
16
|
+
|
|
17
|
+
const program = Command.run(command, { version: VERSION }).pipe(
|
|
18
|
+
Effect.scoped,
|
|
19
|
+
Effect.provide(ApplicationLive),
|
|
20
|
+
Effect.provide(NodeServices.layer),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
NodeRuntime.runMain(program, { disableErrorReporting: true });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Use `BunRuntime` and `BunServices` together when Bun owns the executable. Let
|
|
27
|
+
`runMain` own signals, interruption, and process completion. Do not call
|
|
28
|
+
`Effect.runPromise`, `process.exit`, or runtime globals inside command handlers.
|
|
29
|
+
If expected errors need custom presentation, catch and render them immediately
|
|
30
|
+
before `runMain` while preserving CLI control-flow errors such as help output.
|
|
31
|
+
|
|
32
|
+
## Test at three seams
|
|
33
|
+
|
|
34
|
+
1. Test application services directly with deterministic Layers. Cover domain
|
|
35
|
+
success, expected failure, interruption, and plan/apply separation without
|
|
36
|
+
involving argument parsing.
|
|
37
|
+
2. Test thin command handlers through their services when CLI input mapping or
|
|
38
|
+
output-mode selection contains meaningful logic.
|
|
39
|
+
3. Spawn the real executable for boundary behavior. At minimum cover:
|
|
40
|
+
- root and changed-command `--help`;
|
|
41
|
+
- representative valid arguments and flags;
|
|
42
|
+
- missing or invalid input and a non-zero exit;
|
|
43
|
+
- one expected operational failure with an actionable message;
|
|
44
|
+
- exact JSON output with no prose contamination;
|
|
45
|
+
- dry-run proving writes did not occur;
|
|
46
|
+
- confirmation behavior in both interactive and non-interactive modes.
|
|
47
|
+
|
|
48
|
+
Run executable tests through Effect's child-process APIs with an explicit `cwd`,
|
|
49
|
+
captured stdout/stderr, and controlled environment. Use the repository's normal
|
|
50
|
+
runtime launcher and command authority; a test that bypasses the packaged or
|
|
51
|
+
declared entrypoint does not prove the CLI works for users.
|
|
52
|
+
|
|
53
|
+
## Completion checks
|
|
54
|
+
|
|
55
|
+
- Ensure every executable TypeScript entrypoint belongs to a checked project.
|
|
56
|
+
- Run the repository's formatter, linter, typechecker, and tests.
|
|
57
|
+
- Execute `--help` through the real entrypoint.
|
|
58
|
+
- Exercise a harmless dry-run or read-only command outside the source module.
|
|
59
|
+
- Verify non-zero exits for parsing and expected operational failures.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Processes and platform services
|
|
2
|
+
|
|
3
|
+
Use Effect platform services inside CLI workflows. Keep direct `node:*`, Bun
|
|
4
|
+
globals, `process`, filesystem calls, and shell execution at explicit adapters
|
|
5
|
+
or the executable boundary.
|
|
6
|
+
|
|
7
|
+
## Own subprocess behavior in a service
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Context, Effect, Layer, Schema, String } from "effect";
|
|
11
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
12
|
+
|
|
13
|
+
export class ToolError extends Schema.TaggedError<ToolError>()("ToolError", {
|
|
14
|
+
command: Schema.String,
|
|
15
|
+
cause: Schema.Defect(),
|
|
16
|
+
}) {}
|
|
17
|
+
|
|
18
|
+
export class Tools extends Context.Service<
|
|
19
|
+
Tools,
|
|
20
|
+
{
|
|
21
|
+
readonly gitVersion: Effect.Effect<string, ToolError>;
|
|
22
|
+
changedFiles(baseRef: string): Effect.Effect<ReadonlyArray<string>, ToolError>;
|
|
23
|
+
}
|
|
24
|
+
>()("app/Tools") {
|
|
25
|
+
static readonly layer = Layer.effect(
|
|
26
|
+
Tools,
|
|
27
|
+
Effect.gen(function* () {
|
|
28
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
29
|
+
const gitVersion = spawner.string(ChildProcess.make("git", ["--version"])).pipe(
|
|
30
|
+
Effect.map(String.trim),
|
|
31
|
+
Effect.mapError((cause) => new ToolError({ command: "git --version", cause })),
|
|
32
|
+
);
|
|
33
|
+
const changedFiles = Effect.fn("Tools.changedFiles")(function* (baseRef: string) {
|
|
34
|
+
return yield* spawner
|
|
35
|
+
.lines(ChildProcess.make("git", ["diff", "--name-only", `${baseRef}...HEAD`]))
|
|
36
|
+
.pipe(
|
|
37
|
+
Effect.mapError(
|
|
38
|
+
(cause) =>
|
|
39
|
+
new ToolError({ command: `git diff --name-only ${baseRef}...HEAD`, cause }),
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return Tools.of({ changedFiles, gitVersion });
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Pass the executable and arguments separately to `ChildProcess.make`; never
|
|
51
|
+
construct a shell command from user input. Set `cwd` explicitly when repository
|
|
52
|
+
identity matters. Provide `env` narrowly and choose `extendEnv` deliberately so
|
|
53
|
+
tests and CI do not inherit accidental machine state.
|
|
54
|
+
|
|
55
|
+
Use the smallest spawner operation that matches the contract:
|
|
56
|
+
|
|
57
|
+
- `string` for bounded complete output;
|
|
58
|
+
- `lines` for bounded line-oriented output;
|
|
59
|
+
- `spawn` plus a scoped process handle for streaming, interactive, or
|
|
60
|
+
exit-code-sensitive work;
|
|
61
|
+
- `ChildProcess.pipeTo` for a real pipeline without invoking a shell.
|
|
62
|
+
|
|
63
|
+
When using `spawn`, consume stdout/stderr without deadlocking, inspect
|
|
64
|
+
`handle.exitCode`, and wrap the whole process lifetime in `Effect.scoped`.
|
|
65
|
+
Map platform failures once into an operation-specific error; do not expose a
|
|
66
|
+
generic subprocess error throughout the application.
|
|
67
|
+
|
|
68
|
+
## Keep the runtime choice at the edge
|
|
69
|
+
|
|
70
|
+
Application services may require `FileSystem`, `Path`, `Terminal`, or
|
|
71
|
+
`ChildProcessSpawner`, but they should not import `NodeServices` or `BunServices`.
|
|
72
|
+
Provide the matching platform Layer only in the executable entrypoint. This
|
|
73
|
+
keeps workflows reusable and lets tests provide deterministic substitutes.
|
package/skills/dev-kit/SKILL.md
CHANGED
|
@@ -155,7 +155,8 @@ matching Oxlint/Oxfmt ignores, staged checks, and separate `check` and pure
|
|
|
155
155
|
manifest defaults. Workspace mode accepts explicit package directories with
|
|
156
156
|
pure `typecheck` scripts and generates cached, dependency-ordered,
|
|
157
157
|
bounded-concurrency filters. Spread the returned top-level config before local
|
|
158
|
-
options; spread a returned nested block before overriding that block
|
|
158
|
+
options; spread a returned nested block before overriding that block, and merge
|
|
159
|
+
nested collections such as `lint.rules` so the recommended rules remain active.
|
|
159
160
|
|
|
160
161
|
Enable `setup.vitePlus.quality.workflow` to own only
|
|
161
162
|
`.github/workflows/check.yml`. It requires direct Dev Kit, compatible Vite+,
|
|
@@ -254,8 +255,18 @@ Enable the setup task in the same manifest:
|
|
|
254
255
|
Install the exact `@effect/tsgo` and native `typescript` versions required by
|
|
255
256
|
the installed dev-kit. Point `tsconfig.json` at
|
|
256
257
|
`./node_modules/@effect/tsgo/schema.json` and configure the
|
|
257
|
-
`@effect/language-service` compiler plugin
|
|
258
|
-
|
|
258
|
+
`@effect/language-service` compiler plugin with Dev Kit's exported
|
|
259
|
+
`recommendedEffectTsgoPlugin` profile: warnings for
|
|
260
|
+
`anyUnknownInErrorContext` and `unsafeEffectTypeAssertion`; suggestions for
|
|
261
|
+
`instanceOfSchema`, `nestedEffectGenYield`, `newSchemaClass`, and
|
|
262
|
+
`preferSchemaTypeProperty`; plus a `src/**/*.ts` override that warns on
|
|
263
|
+
`nodeBuiltinImport` and suggests `preferSchemaOverJson`. Copy the exact JSON
|
|
264
|
+
profile from the README into JSON tsconfigs. In monorepos, child
|
|
265
|
+
`compilerOptions.plugins` arrays replace rather than merge the root array, so
|
|
266
|
+
workspace configs must inherit the root plugin without redeclaring it and the
|
|
267
|
+
source override must be relative to the config that contains it. `dev-kit plan`
|
|
268
|
+
validates the local dependencies; `dev-kit apply` patches once and then
|
|
269
|
+
converges.
|
|
259
270
|
|
|
260
271
|
Use `dev-kit tsgo patch --dry-run` for focused diagnosis. Use `--force` only
|
|
261
272
|
after the user accepts a potentially commit-incompatible TypeScript binary.
|
|
@@ -290,6 +301,12 @@ The Oxlint preset enables the fixable
|
|
|
290
301
|
declarations grouped, requires a blank line before the next logical statement,
|
|
291
302
|
and separates every `return` statement from the preceding statement.
|
|
292
303
|
|
|
304
|
+
Vite+ 0.2.6 forwards the preset's JavaScript-plugin declarations but its native
|
|
305
|
+
Oxlint path does not register or execute their rules. Treat native rules and
|
|
306
|
+
Oxfmt as active through `vp`, and use standalone Oxlint when the `effect/*` or
|
|
307
|
+
`stylistic/padding-line-between-statements` rules must be enforced. Re-enable a
|
|
308
|
+
Vite+ execution assertion when a supported release adds JS-plugin execution.
|
|
309
|
+
|
|
293
310
|
The Oxlint preset registers Dev Kit's shared Effect plugin as `effect`, but
|
|
294
311
|
does not enable its scope-sensitive rules globally. Effect projects should
|
|
295
312
|
enable rules such as `effect/no-effect-run`, `effect/no-unsafe-promise`, and
|
package/src/catalog.ts
CHANGED
|
@@ -181,7 +181,7 @@ export const loadSkillCatalog = Effect.fn("loadSkillCatalog")(function* (
|
|
|
181
181
|
});
|
|
182
182
|
}
|
|
183
183
|
const families: Readonly<Record<string, ReadonlyArray<string>>> = {
|
|
184
|
-
effect: ["effect-ts", "effect-architecture-audit", "build-effect-apis"],
|
|
184
|
+
effect: ["effect-ts", "effect-architecture-audit", "build-effect-apis", "build-effect-clis"],
|
|
185
185
|
...Object.fromEntries(externalFamilies),
|
|
186
186
|
};
|
|
187
187
|
|
package/src/effect-tsgo.ts
CHANGED
|
@@ -9,6 +9,43 @@ export const EFFECT_TSGO_VERSION = "0.33.0";
|
|
|
9
9
|
export const EFFECT_TSGO_TYPESCRIPT_VERSION = "7.0.2";
|
|
10
10
|
export const EFFECT_TSGO_PLUGIN_NAME = "@effect/language-service";
|
|
11
11
|
|
|
12
|
+
export type EffectTsgoDiagnosticSeverity = "off" | "error" | "warning" | "message" | "suggestion";
|
|
13
|
+
|
|
14
|
+
export type EffectTsgoPluginConfig = {
|
|
15
|
+
readonly name: typeof EFFECT_TSGO_PLUGIN_NAME;
|
|
16
|
+
readonly diagnosticSeverity: Readonly<Record<string, EffectTsgoDiagnosticSeverity>>;
|
|
17
|
+
readonly overrides: ReadonlyArray<{
|
|
18
|
+
readonly include: ReadonlyArray<string>;
|
|
19
|
+
readonly options: {
|
|
20
|
+
readonly diagnosticSeverity: Readonly<Record<string, EffectTsgoDiagnosticSeverity>>;
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Recommended diagnostics for projects using the Effect TypeScript-Go plugin. */
|
|
26
|
+
export const recommendedEffectTsgoPlugin = {
|
|
27
|
+
name: EFFECT_TSGO_PLUGIN_NAME,
|
|
28
|
+
diagnosticSeverity: {
|
|
29
|
+
anyUnknownInErrorContext: "warning",
|
|
30
|
+
instanceOfSchema: "suggestion",
|
|
31
|
+
nestedEffectGenYield: "suggestion",
|
|
32
|
+
newSchemaClass: "suggestion",
|
|
33
|
+
preferSchemaTypeProperty: "suggestion",
|
|
34
|
+
unsafeEffectTypeAssertion: "warning",
|
|
35
|
+
},
|
|
36
|
+
overrides: [
|
|
37
|
+
{
|
|
38
|
+
include: ["src/**/*.ts"],
|
|
39
|
+
options: {
|
|
40
|
+
diagnosticSeverity: {
|
|
41
|
+
nodeBuiltinImport: "warning",
|
|
42
|
+
preferSchemaOverJson: "suggestion",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
} as const satisfies EffectTsgoPluginConfig;
|
|
48
|
+
|
|
12
49
|
export type EffectTsgoPatchOptions = {
|
|
13
50
|
readonly dryRun?: boolean;
|
|
14
51
|
readonly force?: boolean;
|
package/src/index.ts
CHANGED
|
@@ -43,13 +43,16 @@ export {
|
|
|
43
43
|
EFFECT_TSGO_PLUGIN_NAME,
|
|
44
44
|
EFFECT_TSGO_TYPESCRIPT_VERSION,
|
|
45
45
|
EFFECT_TSGO_VERSION,
|
|
46
|
+
type EffectTsgoDiagnosticSeverity,
|
|
46
47
|
EffectTsgoDependencyError,
|
|
47
48
|
InvalidEffectTsgoPackageNameError,
|
|
48
49
|
type EffectTsgoPatchOptions,
|
|
49
50
|
type EffectTsgoPatchPlan,
|
|
51
|
+
type EffectTsgoPluginConfig,
|
|
50
52
|
EffectTsgoPatchCommandError,
|
|
51
53
|
patchEffectTsgo,
|
|
52
54
|
planEffectTsgoPatch,
|
|
55
|
+
recommendedEffectTsgoPlugin,
|
|
53
56
|
} from "./effect-tsgo.ts";
|
|
54
57
|
export {
|
|
55
58
|
ExternalSkillSourceSchema,
|
package/src/skill-manager.ts
CHANGED
|
@@ -143,9 +143,9 @@ const readManifest = Effect.fn("readManagedSkillManifest")(function* (
|
|
|
143
143
|
if (errors.length > 0) {
|
|
144
144
|
return yield* SkillManagerError.make({ message: `could not parse ${paths.manifestPath}` });
|
|
145
145
|
}
|
|
146
|
-
const manifest = yield* Schema.decodeUnknownEffect(DevKitManifestSchema
|
|
147
|
-
|
|
148
|
-
);
|
|
146
|
+
const manifest = yield* Schema.decodeUnknownEffect(DevKitManifestSchema, {
|
|
147
|
+
onExcessProperty: "error",
|
|
148
|
+
})(parsed).pipe(Effect.mapError((error) => SkillManagerError.make({ message: error.message })));
|
|
149
149
|
|
|
150
150
|
return { ...paths, manifest, raw };
|
|
151
151
|
});
|
package/src/sync.ts
CHANGED
|
@@ -270,7 +270,7 @@ const encodePlanSnapshotJson = Schema.encodeSync(Schema.fromJsonString(Schema.Un
|
|
|
270
270
|
const encodeAppliedStatePrettyJson = Schema.encodeSync(fromJsonString(AppliedStateSchema, 2));
|
|
271
271
|
|
|
272
272
|
const SKILL_FAMILIES: SkillCatalog = {
|
|
273
|
-
effect: ["effect-ts", "effect-architecture-audit", "build-effect-apis"],
|
|
273
|
+
effect: ["effect-ts", "effect-architecture-audit", "build-effect-apis", "build-effect-clis"],
|
|
274
274
|
};
|
|
275
275
|
|
|
276
276
|
export const DEFAULT_MANIFEST = "dev-kit.jsonc";
|
|
@@ -564,6 +564,7 @@ const parseStructuredFile = Effect.fn("parseStructuredFile")(function* <A>(
|
|
|
564
564
|
filePath: string,
|
|
565
565
|
raw: string,
|
|
566
566
|
schema: Schema.ConstraintDecoder<A>,
|
|
567
|
+
options: { readonly rejectExcessProperties?: boolean } = {},
|
|
567
568
|
) {
|
|
568
569
|
const errors: Array<ParseError> = [];
|
|
569
570
|
const parsed = parseJsonc(raw, errors, { allowTrailingComma: true });
|
|
@@ -576,7 +577,10 @@ const parseStructuredFile = Effect.fn("parseStructuredFile")(function* <A>(
|
|
|
576
577
|
});
|
|
577
578
|
}
|
|
578
579
|
|
|
579
|
-
return yield* Schema.decodeUnknownEffect(
|
|
580
|
+
return yield* Schema.decodeUnknownEffect(
|
|
581
|
+
schema,
|
|
582
|
+
options.rejectExcessProperties ? { onExcessProperty: "error" } : undefined,
|
|
583
|
+
)(parsed).pipe(
|
|
580
584
|
Effect.mapError((cause) =>
|
|
581
585
|
StructuredFileError.make({ path: filePath, message: cause.message }),
|
|
582
586
|
),
|
|
@@ -591,7 +595,9 @@ const readManifest = Effect.fn("readManifest")(function* (manifestPath: string)
|
|
|
591
595
|
}
|
|
592
596
|
const raw = yield* fs.readFileString(manifestPath);
|
|
593
597
|
|
|
594
|
-
return yield* parseStructuredFile(manifestPath, raw, DevKitManifestSchema
|
|
598
|
+
return yield* parseStructuredFile(manifestPath, raw, DevKitManifestSchema, {
|
|
599
|
+
rejectExcessProperties: true,
|
|
600
|
+
});
|
|
595
601
|
});
|
|
596
602
|
|
|
597
603
|
const readOptionalStructuredFile = Effect.fn("readOptionalStructuredFile")(function* <A>(
|
|
@@ -1752,7 +1758,7 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
|
|
|
1752
1758
|
});
|
|
1753
1759
|
const stageDir = path.join(tempDir, "stage");
|
|
1754
1760
|
const backupDir = path.join(tempDir, "backup");
|
|
1755
|
-
const
|
|
1761
|
+
const stagedByAction = new Map<(typeof mutating)[number], string>();
|
|
1756
1762
|
let stageIndex = 0;
|
|
1757
1763
|
|
|
1758
1764
|
for (const action of mutating) {
|
|
@@ -1816,10 +1822,7 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
|
|
|
1816
1822
|
});
|
|
1817
1823
|
}
|
|
1818
1824
|
}
|
|
1819
|
-
|
|
1820
|
-
action.action === "remove" ? action.previous.resourceId : action.desired.resourceId,
|
|
1821
|
-
staged,
|
|
1822
|
-
);
|
|
1825
|
+
stagedByAction.set(action, staged);
|
|
1823
1826
|
}
|
|
1824
1827
|
|
|
1825
1828
|
yield* verifyPackageSkillSources(plan);
|
|
@@ -1841,10 +1844,7 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
|
|
|
1841
1844
|
let replacementIndex = 0;
|
|
1842
1845
|
|
|
1843
1846
|
for (const action of mutating) {
|
|
1844
|
-
const staged =
|
|
1845
|
-
action.action === "remove"
|
|
1846
|
-
? stagedByResource.get(action.previous.resourceId)
|
|
1847
|
-
: stagedByResource.get(action.desired.resourceId);
|
|
1847
|
+
const staged = stagedByAction.get(action);
|
|
1848
1848
|
|
|
1849
1849
|
if (
|
|
1850
1850
|
(action.action !== "remove" || action.stagedContent !== undefined) &&
|
package/src/vite-plus.js
CHANGED