@danieljvdm/dev-kit 0.7.1 → 0.8.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 +90 -20
- package/dev-kit.example.jsonc +7 -1
- package/package.json +3 -1
- package/schema/dev-kit.schema.json +93 -1
- package/skills/dev-kit/SKILL.md +27 -17
- package/skills/effect-ts/SKILL.md +29 -1
- package/skills/effect-ts/agents/openai.yaml +1 -1
- package/skills/{effect-atom-data-fetching/references/cache-lifecycle.md → effect-ts/references/atom-cache-lifecycle.md} +1 -1
- package/skills/{effect-atom-data-fetching/references/http-and-invalidation.md → effect-ts/references/atom-http-and-invalidation.md} +1 -1
- package/skills/{effect-atom-data-fetching/references/tanstack-start.md → effect-ts/references/atom-tanstack-start.md} +1 -1
- package/skills/{effect-atom-data-fetching/references/testing.md → effect-ts/references/atom-testing.md} +1 -1
- package/skills/{effect-atom-data-fetching/SKILL.md → effect-ts/references/guide-atom-data-fetching.md} +0 -12
- package/skills/effect-ts/references/guide-datetime.md +72 -0
- package/src/catalog.ts +2 -1
- package/src/index.ts +10 -0
- package/src/manifest.ts +59 -3
- package/src/project-package.ts +1 -0
- package/src/sync.ts +61 -30
- package/src/tool-metadata.ts +2 -0
- package/src/vite-plus-dependency.ts +70 -0
- package/src/vite-plus-hooks.ts +9 -18
- package/src/vite-plus-quality.ts +255 -6
- package/templates/vite-plus/github-actions-check.yml +11 -4
- package/skills/effect-atom-data-fetching/agents/openai.yaml +0 -4
package/README.md
CHANGED
|
@@ -193,8 +193,9 @@ tool versions. A project-local process lock also prevents concurrent applies.
|
|
|
193
193
|
```
|
|
194
194
|
|
|
195
195
|
- `dev-kit` installs guidance for operating the toolkit itself.
|
|
196
|
-
- `effect` expands to `effect-ts`
|
|
197
|
-
guidance.
|
|
196
|
+
- `effect` expands to the `effect-ts` umbrella, whose lazy references include
|
|
197
|
+
Effect Atom HTTP data fetching and Effect DateTime guidance. The former
|
|
198
|
+
`effect-atom-data-fetching` selector remains an alias for compatibility.
|
|
198
199
|
- Prefer individual external skills such as `workers-best-practices` and
|
|
199
200
|
`wrangler`, selected after scanning the project for relevant technologies.
|
|
200
201
|
- `serve-sim` selects the approved Evan Bacon simulator skill directly.
|
|
@@ -259,8 +260,8 @@ worktree converges its own copy during install while the project-owned
|
|
|
259
260
|
|
|
260
261
|
## Vite+ quality setup
|
|
261
262
|
|
|
262
|
-
|
|
263
|
-
|
|
263
|
+
Vite+/Effect repositories opt into the canonical Vite config and GitHub Actions
|
|
264
|
+
workflow independently:
|
|
264
265
|
|
|
265
266
|
```jsonc
|
|
266
267
|
{
|
|
@@ -269,24 +270,95 @@ quality configuration and GitHub Actions workflow:
|
|
|
269
270
|
"effectTsgo": { "enabled": true },
|
|
270
271
|
"vitePlus": {
|
|
271
272
|
"hooks": { "enabled": true },
|
|
272
|
-
"quality": {
|
|
273
|
+
"quality": {
|
|
274
|
+
"config": { "enabled": true },
|
|
275
|
+
"workflow": { "enabled": true },
|
|
276
|
+
},
|
|
273
277
|
},
|
|
274
278
|
},
|
|
275
279
|
}
|
|
276
280
|
```
|
|
277
281
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
TypeScript
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
removes only unchanged owned files.
|
|
282
|
+
`quality.config.enabled` owns `vite.config.ts`; `quality.workflow.enabled` owns
|
|
283
|
+
`.github/workflows/check.yml`. Selecting one does not impose the other resource's
|
|
284
|
+
config, scripts, or TypeScript topology. Both require direct
|
|
285
|
+
`@danieljvdm/dev-kit`, `vite-plus`, `effect`, `@effect/tsgo`, and native
|
|
286
|
+
TypeScript dependencies with `setup.effectTsgo.enabled`. The installed Vite+
|
|
287
|
+
must satisfy Dev Kit's peer range.
|
|
285
288
|
|
|
286
289
|
The managed Vite config composes the shared Oxfmt and Oxlint presets, configures
|
|
287
|
-
`vp staged`, and defines `vp run check` and `vp run typecheck
|
|
288
|
-
|
|
289
|
-
|
|
290
|
+
`vp staged`, and defines separate `vp run check` and pure `vp run typecheck`
|
|
291
|
+
tasks. It rejects conflicting root scripts. The default `single-project`
|
|
292
|
+
strategy requires a root `tsconfig.json` without project references. Workspaces
|
|
293
|
+
can instead select Vite Task orchestration explicitly:
|
|
294
|
+
|
|
295
|
+
```jsonc
|
|
296
|
+
{
|
|
297
|
+
"setup": {
|
|
298
|
+
"effectTsgo": { "enabled": true },
|
|
299
|
+
"vitePlus": {
|
|
300
|
+
"quality": {
|
|
301
|
+
"config": {
|
|
302
|
+
"enabled": true,
|
|
303
|
+
"typecheck": {
|
|
304
|
+
"strategy": "workspace",
|
|
305
|
+
"concurrency": 4,
|
|
306
|
+
"packages": ["apps/web", "packages/core"],
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Workspace mode requires `package.json#workspaces` and an explicit list of
|
|
316
|
+
package directories with pure `typecheck` scripts. The generated task uses
|
|
317
|
+
dependency order, caching, `--fail-if-no-match`, and bounded concurrency.
|
|
318
|
+
Project-reference builds remain custom because their `.tsbuildinfo` inputs and
|
|
319
|
+
outputs are repository-specific.
|
|
320
|
+
|
|
321
|
+
Repositories with a custom Vite config can still adopt only the hardened CI
|
|
322
|
+
workflow and declare repository-specific preparation and typecheck commands:
|
|
323
|
+
|
|
324
|
+
```jsonc
|
|
325
|
+
{
|
|
326
|
+
"setup": {
|
|
327
|
+
"effectTsgo": { "enabled": true },
|
|
328
|
+
"vitePlus": {
|
|
329
|
+
"quality": {
|
|
330
|
+
"workflow": {
|
|
331
|
+
"enabled": true,
|
|
332
|
+
"beforeChecks": [
|
|
333
|
+
{
|
|
334
|
+
"name": "Install media tools",
|
|
335
|
+
"run": ["sudo apt-get update", "sudo apt-get install --yes ffmpeg"],
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
"typecheck": [
|
|
339
|
+
"vp run -F './apps/*' -F './packages/*' check",
|
|
340
|
+
"vp exec tsc --noEmit -p scripts/tsconfig.json",
|
|
341
|
+
],
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Workflow-only setup does not validate or take ownership of the Vite config,
|
|
350
|
+
scripts, or TypeScript topology. It performs one frozen, script-suppressed
|
|
351
|
+
install, runs `dev-kit apply --locked`, and only then runs custom preparation,
|
|
352
|
+
formatting, linting, tests, and typechecking. Its default typecheck command is
|
|
353
|
+
`vp run typecheck`; `workflow.typecheck` replaces it. Vite+ maps install flags
|
|
354
|
+
to the detected package manager. The template pins an exact `setup-vp` release
|
|
355
|
+
commit because its `v1` tag is frozen; keep it current with Renovate or
|
|
356
|
+
Dependabot. Existing workflows remain user-owned until their rendered content
|
|
357
|
+
matches exactly—Dev Kit never merges YAML. See the primary
|
|
358
|
+
[`setup-vp` versioning guidance](https://github.com/voidzero-dev/setup-vp#versioning),
|
|
359
|
+
[Vite+ install guide](https://viteplus.dev/guide/install), and
|
|
360
|
+
[Vite Task run guide](https://viteplus.dev/guide/run) when maintaining the
|
|
361
|
+
templates.
|
|
290
362
|
|
|
291
363
|
## Effect source checkout
|
|
292
364
|
|
|
@@ -350,8 +422,7 @@ native TypeScript compiler. It does not download dependencies and skips an
|
|
|
350
422
|
installation that is already patched. Use `dev-kit tsgo patch --dry-run` when
|
|
351
423
|
troubleshooting the task directly.
|
|
352
424
|
|
|
353
|
-
Dependency and `tsconfig.json` edits remain explicit.
|
|
354
|
-
task only when Dev Kit can own the canonical root config and GitHub workflow.
|
|
425
|
+
Dependency and `tsconfig.json` edits remain explicit.
|
|
355
426
|
|
|
356
427
|
## Installed package skills
|
|
357
428
|
|
|
@@ -484,9 +555,8 @@ Use `lint.extends` rather than a shallow object spread so Vite+ composes the
|
|
|
484
555
|
nested plugin and rule configuration correctly. Oxfmt has no `extends`, so
|
|
485
556
|
spread its configuration before project-local formatter options. The shared
|
|
486
557
|
lint preset enables `typeAware` for semantic lint rules but leaves `typeCheck`
|
|
487
|
-
disabled.
|
|
488
|
-
|
|
489
|
-
disabled and compose the exports manually. Effect TypeScript-Go projects run:
|
|
558
|
+
disabled. Managed quality config uses this composition; custom Vite configs
|
|
559
|
+
compose the exports manually. Effect TypeScript-Go projects run:
|
|
490
560
|
|
|
491
561
|
```sh
|
|
492
562
|
vp fmt --check
|
package/dev-kit.example.jsonc
CHANGED
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
"effectTsgo": { "enabled": true },
|
|
9
9
|
"vitePlus": {
|
|
10
10
|
"hooks": { "enabled": true },
|
|
11
|
-
"quality": {
|
|
11
|
+
"quality": {
|
|
12
|
+
"config": {
|
|
13
|
+
"enabled": true,
|
|
14
|
+
"typecheck": { "strategy": "single-project" },
|
|
15
|
+
},
|
|
16
|
+
"workflow": { "enabled": true },
|
|
17
|
+
},
|
|
12
18
|
},
|
|
13
19
|
},
|
|
14
20
|
"targets": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danieljvdm/dev-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Declarative project development toolkit with portable agent skills.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"@stylistic/eslint-plugin": "5.10.0",
|
|
70
70
|
"effect": "4.0.0-beta.102",
|
|
71
71
|
"jsonc-parser": "3.3.1",
|
|
72
|
+
"semver": "7.8.5",
|
|
72
73
|
"tsx": "4.22.4"
|
|
73
74
|
},
|
|
74
75
|
"devDependencies": {
|
|
@@ -76,6 +77,7 @@
|
|
|
76
77
|
"@effect/tsgo": "0.24.3",
|
|
77
78
|
"@effect/vitest": "4.0.0-beta.102",
|
|
78
79
|
"@types/node": "25.9.1",
|
|
80
|
+
"@types/semver": "7.8.0",
|
|
79
81
|
"oxfmt": "0.60.0",
|
|
80
82
|
"oxlint": "1.75.0",
|
|
81
83
|
"typescript": "7.0.2",
|
|
@@ -161,13 +161,105 @@
|
|
|
161
161
|
}
|
|
162
162
|
},
|
|
163
163
|
"vitePlusQualitySetup": {
|
|
164
|
-
"description": "
|
|
164
|
+
"description": "Opt into independently managed Vite+ quality resources for supported Effect TypeScript-Go repositories.",
|
|
165
|
+
"type": "object",
|
|
166
|
+
"additionalProperties": false,
|
|
167
|
+
"properties": {
|
|
168
|
+
"config": {
|
|
169
|
+
"$ref": "#/$defs/vitePlusQualityConfigSetup"
|
|
170
|
+
},
|
|
171
|
+
"workflow": {
|
|
172
|
+
"$ref": "#/$defs/vitePlusQualityWorkflowSetup"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"vitePlusQualityConfigSetup": {
|
|
177
|
+
"description": "Manage the canonical root Vite config without requiring Dev Kit to own CI.",
|
|
178
|
+
"type": "object",
|
|
179
|
+
"additionalProperties": false,
|
|
180
|
+
"properties": {
|
|
181
|
+
"enabled": {
|
|
182
|
+
"type": "boolean",
|
|
183
|
+
"default": false
|
|
184
|
+
},
|
|
185
|
+
"typecheck": {
|
|
186
|
+
"$ref": "#/$defs/vitePlusQualityTypecheckSetup"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"vitePlusQualityWorkflowSetup": {
|
|
191
|
+
"description": "Manage the canonical GitHub Actions check workflow without requiring Dev Kit to own the Vite config.",
|
|
165
192
|
"type": "object",
|
|
166
193
|
"additionalProperties": false,
|
|
167
194
|
"properties": {
|
|
168
195
|
"enabled": {
|
|
169
196
|
"type": "boolean",
|
|
170
197
|
"default": false
|
|
198
|
+
},
|
|
199
|
+
"beforeChecks": {
|
|
200
|
+
"description": "Repository-specific preparation steps inserted after locked setup verification and before quality checks.",
|
|
201
|
+
"type": "array",
|
|
202
|
+
"items": {
|
|
203
|
+
"$ref": "#/$defs/vitePlusQualityWorkflowStep"
|
|
204
|
+
},
|
|
205
|
+
"default": []
|
|
206
|
+
},
|
|
207
|
+
"typecheck": {
|
|
208
|
+
"description": "Shell commands for the workflow's pure typecheck step.",
|
|
209
|
+
"type": "array",
|
|
210
|
+
"minItems": 1,
|
|
211
|
+
"items": {
|
|
212
|
+
"type": "string",
|
|
213
|
+
"minLength": 1
|
|
214
|
+
},
|
|
215
|
+
"default": ["vp run typecheck"]
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
"vitePlusQualityWorkflowStep": {
|
|
220
|
+
"type": "object",
|
|
221
|
+
"additionalProperties": false,
|
|
222
|
+
"required": ["name", "run"],
|
|
223
|
+
"properties": {
|
|
224
|
+
"name": {
|
|
225
|
+
"type": "string",
|
|
226
|
+
"minLength": 1
|
|
227
|
+
},
|
|
228
|
+
"run": {
|
|
229
|
+
"type": "array",
|
|
230
|
+
"minItems": 1,
|
|
231
|
+
"items": {
|
|
232
|
+
"type": "string",
|
|
233
|
+
"minLength": 1
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
"vitePlusQualityTypecheckSetup": {
|
|
239
|
+
"description": "Select the managed typecheck topology. Single-project is the default; workspace mode runs explicitly scoped package scripts through Vite Task.",
|
|
240
|
+
"type": "object",
|
|
241
|
+
"additionalProperties": false,
|
|
242
|
+
"properties": {
|
|
243
|
+
"strategy": {
|
|
244
|
+
"enum": ["single-project", "workspace"],
|
|
245
|
+
"default": "single-project"
|
|
246
|
+
},
|
|
247
|
+
"concurrency": {
|
|
248
|
+
"description": "Maximum concurrent workspace typecheck tasks.",
|
|
249
|
+
"type": "integer",
|
|
250
|
+
"minimum": 1,
|
|
251
|
+
"maximum": 32,
|
|
252
|
+
"default": 4
|
|
253
|
+
},
|
|
254
|
+
"packages": {
|
|
255
|
+
"description": "Project-relative workspace package directories whose typecheck scripts should run.",
|
|
256
|
+
"type": "array",
|
|
257
|
+
"items": {
|
|
258
|
+
"type": "string",
|
|
259
|
+
"minLength": 1
|
|
260
|
+
},
|
|
261
|
+
"uniqueItems": true,
|
|
262
|
+
"default": []
|
|
171
263
|
}
|
|
172
264
|
}
|
|
173
265
|
},
|
package/skills/dev-kit/SKILL.md
CHANGED
|
@@ -76,7 +76,10 @@ skill as `dev-kit` when project agents should carry the toolkit procedure.
|
|
|
76
76
|
"claudeInstructions": { "enabled": true },
|
|
77
77
|
"vitePlus": {
|
|
78
78
|
"hooks": { "enabled": true },
|
|
79
|
-
"quality": {
|
|
79
|
+
"quality": {
|
|
80
|
+
"config": { "enabled": true },
|
|
81
|
+
"workflow": { "enabled": true },
|
|
82
|
+
},
|
|
80
83
|
},
|
|
81
84
|
},
|
|
82
85
|
"targets": {
|
|
@@ -109,19 +112,26 @@ ignored dispatchers in linked worktrees. Preserve other hook managers; Dev Kit
|
|
|
109
112
|
refuses to replace an unrelated `core.hooksPath`. Use `VITE_GIT_HOOKS=0` or
|
|
110
113
|
`HUSKY=0` to skip hook setup for an invocation.
|
|
111
114
|
|
|
112
|
-
Enable `setup.vitePlus.quality`
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
Enable `setup.vitePlus.quality.config` and
|
|
116
|
+
`setup.vitePlus.quality.workflow` independently in supported Vite+/Effect
|
|
117
|
+
repositories. Config owns canonical root `vite.config.ts`; workflow owns
|
|
118
|
+
`.github/workflows/check.yml`; selecting one must not constrain the other's
|
|
119
|
+
config, scripts, or TypeScript topology. Both require direct Dev Kit, compatible
|
|
120
|
+
Vite+, Effect, Effect TypeScript-Go, and native TypeScript dependencies with
|
|
121
|
+
`setup.effectTsgo.enabled`. Preserve unowned destinations and adopt only exact
|
|
122
|
+
rendered matches.
|
|
123
|
+
|
|
124
|
+
Managed config defines separate `check` and pure `typecheck` Vite tasks. Its
|
|
125
|
+
default single-project strategy rejects project references; workspace mode
|
|
126
|
+
requires explicit package directories with `typecheck` scripts and generates
|
|
127
|
+
cached, dependency-ordered, bounded-concurrency filters. Keep project-reference
|
|
128
|
+
builds custom. Workflow-only consumers may configure `workflow.beforeChecks`
|
|
129
|
+
and `workflow.typecheck`; treat these commands as trusted manifest input.
|
|
130
|
+
|
|
131
|
+
The workflow must use one frozen, script-suppressed install, then locked Dev Kit
|
|
132
|
+
convergence before preparation or checks. Keep `setup-vp` pinned to a reviewed
|
|
133
|
+
release commit—the `v1` tag is frozen—and let Vite+ resolve the consumer's
|
|
134
|
+
compatible locked version.
|
|
125
135
|
|
|
126
136
|
## Ownership and conflicts
|
|
127
137
|
|
|
@@ -255,8 +265,8 @@ in the consuming project.
|
|
|
255
265
|
|
|
256
266
|
Manage skill outputs, the `setup.agentInstructions` wrapper, the
|
|
257
267
|
`setup.claudeInstructions` link, the `setup.vitePlus.hooks` dispatcher, the
|
|
258
|
-
opt-in `setup.vitePlus.quality` config and GitHub workflow, the
|
|
268
|
+
independently opt-in `setup.vitePlus.quality` config and GitHub workflow, the
|
|
259
269
|
`setup.effectSource` checkout, and the explicit `setup.effectTsgo` task.
|
|
260
270
|
Dependency and `tsconfig.json` contributions remain deliberate user-owned
|
|
261
|
-
edits. Custom Vite configs compose the Oxlint and Oxfmt package exports
|
|
262
|
-
|
|
271
|
+
edits. Custom Vite configs compose the Oxlint and Oxfmt package exports
|
|
272
|
+
manually, leave managed config disabled, and may still use the managed workflow.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: effect-ts
|
|
3
|
-
description: Use this skill whenever working in a repository that uses Effect, even if the current task is in a new file or the user does not explicitly ask for Effect help. Apply it to Effect patterns, services, layers, schemas, streams, runtimes, typed errors, observability, testing, HTTP, SQL, command-line scripts, project automation, and supporting tooling.
|
|
3
|
+
description: Use this skill whenever working in a repository that uses Effect, even if the current task is in a new file or the user does not explicitly ask for Effect help. Apply it to Effect patterns, services, layers, schemas, streams, runtimes, typed errors, DateTime, Effect Atom, observability, testing, HTTP, SQL, command-line scripts, project automation, and supporting tooling.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Effect Expert
|
|
@@ -106,6 +106,19 @@ When the task touches one of these areas, consult the matching guide before impl
|
|
|
106
106
|
- `./references/guide-schedule.md` for retries, repeats, backoff, polling, cron, and schedule composition
|
|
107
107
|
- `./references/guide-schema.md` for schema-first application modeling,
|
|
108
108
|
service contracts, transformations, unions, recursion, and branded types
|
|
109
|
+
- `./references/guide-datetime.md` for current time, parsing, UTC and zoned
|
|
110
|
+
values, time zones, DST-safe arithmetic, formatting, Date interoperability,
|
|
111
|
+
and deterministic `TestClock` tests
|
|
112
|
+
- `./references/guide-atom-data-fetching.md` for the core Effect Atom HTTP
|
|
113
|
+
data-fetching workflow and ownership rules
|
|
114
|
+
- `./references/atom-cache-lifecycle.md` for Effect Atom registry scope,
|
|
115
|
+
runtime memoization, families, TTL, SWR, polling, and aggregation resets
|
|
116
|
+
- `./references/atom-http-and-invalidation.md` for `AtomHttpApi.Service`,
|
|
117
|
+
queries, mutations, reactivity keys, and invalidation
|
|
118
|
+
- `./references/atom-tanstack-start.md` only for TanStack Start provider
|
|
119
|
+
placement, SSR isolation, hydration, and focus guidance
|
|
120
|
+
- `./references/atom-testing.md` when adding or diagnosing deterministic Effect
|
|
121
|
+
Atom lifecycle tests
|
|
109
122
|
- `./references/guide-sql.md` for Effect SQL usage, transactions, resolvers, schema-aware SQL, and migrations
|
|
110
123
|
- `./references/guide-testing.md` for `@effect/vitest`, deterministic testing,
|
|
111
124
|
honest test Layers, property tests, and protocol round trips
|
|
@@ -208,6 +221,15 @@ Install additional `@effect/*` packages only when the user task actually needs t
|
|
|
208
221
|
- If a type is hard to express, simplify the design or introduce a properly typed helper instead of using unsafe TypeScript.
|
|
209
222
|
- For layers, do not hide them inside `namespace` blocks. Prefer either `static` members on the service class or plain exported layer constants.
|
|
210
223
|
|
|
224
|
+
### Date and Time
|
|
225
|
+
|
|
226
|
+
- Prefer Effect `DateTime` over vanilla JavaScript `Date` for application
|
|
227
|
+
logic. Keep `Date` as an interoperability type at external boundaries.
|
|
228
|
+
- Use `DateTime.now` inside Effect programs so current time remains driven by
|
|
229
|
+
the `Clock` service and deterministic under `TestClock`.
|
|
230
|
+
- Preserve the distinction between instants, zoned wall-clock values, and
|
|
231
|
+
date-only domain values.
|
|
232
|
+
|
|
211
233
|
### Code Quality
|
|
212
234
|
|
|
213
235
|
- Write type-safe code that leverages Effect's type system.
|
|
@@ -233,6 +255,12 @@ rely on installed declarations and version-matched canonical Effect source.
|
|
|
233
255
|
- `./references/guide-retries.md`
|
|
234
256
|
- `./references/guide-schedule.md`
|
|
235
257
|
- `./references/guide-schema.md`
|
|
258
|
+
- `./references/guide-datetime.md`
|
|
259
|
+
- `./references/guide-atom-data-fetching.md`
|
|
260
|
+
- `./references/atom-cache-lifecycle.md`
|
|
261
|
+
- `./references/atom-http-and-invalidation.md`
|
|
262
|
+
- `./references/atom-tanstack-start.md`
|
|
263
|
+
- `./references/atom-testing.md`
|
|
236
264
|
- `./references/guide-sql.md`
|
|
237
265
|
- `./references/guide-testing.md`
|
|
238
266
|
- `./references/guide-cli.md`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Effect Expert"
|
|
3
3
|
short_description: "Apply current Effect v4 patterns and tooling"
|
|
4
|
-
default_prompt: "Use $effect-ts to apply the relevant Effect architecture, schema, service, error, testing, HTTP, SQL, observability, or CLI guidance."
|
|
4
|
+
default_prompt: "Use $effect-ts to apply the relevant Effect architecture, DateTime, Atom, schema, service, error, testing, HTTP, SQL, observability, or CLI guidance."
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: effect-atom-data-fetching
|
|
3
|
-
description: Design, implement, debug, or review HTTP data fetching with Effect Atom in React applications. Use for AtomHttpApi.Service queries and mutations, parameterized Atom.family caches, RegistryProvider and runtime placement, TTL/SWR/polling behavior, reactivity-key invalidation, framework-appropriate SSR and focus handling, AsyncResult aggregation resets, and deterministic cache lifecycle tests.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
1
|
# Effect Atom Data Fetching
|
|
7
2
|
|
|
8
3
|
Model server data as stable atoms owned outside React renders. Give the application one intentional registry boundary and one compatible runtime factory; then choose cache retention, freshness, polling, and invalidation independently.
|
|
@@ -28,13 +23,6 @@ Model server data as stable atoms owned outside React renders. Give the applicat
|
|
|
28
23
|
- Ensure every input to a route-level `AsyncResult.all` has stable atom identity and compatible retention. One newly allocated or immediately evicted input returns to `Initial` and makes the whole aggregate appear reset even when the other inputs remain cached.
|
|
29
24
|
- Do not describe manual refresh or polling as freshness caching. Refresh is forceful; `staleTime` only gates SWR's automatic mount/focus decisions.
|
|
30
25
|
|
|
31
|
-
## References
|
|
32
|
-
|
|
33
|
-
- Read `./references/cache-lifecycle.md` for registry scope, runtime memoization, families, TTL, SWR, polling, and the aggregation trap.
|
|
34
|
-
- Read `./references/http-and-invalidation.md` for `AtomHttpApi.Service`, query/mutation ownership, reactivity-key vocabulary, and invalidation.
|
|
35
|
-
- Read `./references/tanstack-start.md` only for its framework-specific provider placement, SSR isolation, hydration, and focus guidance.
|
|
36
|
-
- Read `./references/testing.md` when adding or diagnosing lifecycle tests.
|
|
37
|
-
|
|
38
26
|
## Completion check
|
|
39
27
|
|
|
40
28
|
Confirm one registry boundary, stable atom identity, deliberate TTL/staleness/polling values, matching query and mutation keys, SSR-safe browser access, and tests for every lifecycle behavior changed.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Effect DateTime
|
|
2
|
+
|
|
3
|
+
Prefer Effect `DateTime` over vanilla JavaScript `Date` for application logic. Keep `Date` as an interoperability type at external boundaries, not the domain model.
|
|
4
|
+
|
|
5
|
+
## Workflow
|
|
6
|
+
|
|
7
|
+
1. Inspect the installed `effect` version and `DateTime` source before relying on exact signatures. DateTime APIs are version-sensitive.
|
|
8
|
+
2. Identify whether each value represents an absolute instant, a wall-clock time in a named zone, or only a calendar date. Do not silently collapse these meanings.
|
|
9
|
+
3. Decode external strings, numbers, and `Date` objects at the boundary. Use `Schema.DateTimeUtc`, `Schema.DateTimeUtcFromString`, `Schema.DateTimeUtcFromMillis`, `Schema.DateTimeUtcFromDate`, or `Schema.DateTimeZoned` as appropriate.
|
|
10
|
+
4. Keep domain values as `DateTime.Utc` by default. Use `DateTime.Zoned` when calendar operations or presentation must retain an IANA time zone.
|
|
11
|
+
5. Use `DateTime.now` inside Effect programs so the current time comes from the `Clock` service. Reserve `DateTime.nowUnsafe()` and `Date.now()` for explicit synchronous host boundaries.
|
|
12
|
+
6. Perform comparisons, arithmetic, rounding, and formatting with `DateTime` operations, then convert to `Date` or epoch milliseconds only when an external API requires them.
|
|
13
|
+
7. Test time-dependent behavior with `it.effect` and `TestClock`; advance virtual time instead of sleeping or consulting the live clock.
|
|
14
|
+
|
|
15
|
+
## Construction and boundaries
|
|
16
|
+
|
|
17
|
+
- Prefer safe decoding for untrusted input. `DateTime.make` and zoned constructors return `Option`; Schema decoders produce structured parse failures.
|
|
18
|
+
- Use `DateTime.makeUnsafe` only for literals and values already validated by the program. Do not turn user input into defects.
|
|
19
|
+
- Prefer explicit ISO 8601 strings with offsets at wire boundaries. Avoid implementation-dependent or locale-formatted date strings.
|
|
20
|
+
- Do not invent a midnight instant for a genuinely date-only value such as a birthday. Preserve a validated `YYYY-MM-DD` or a domain-specific year/month/day structure until an actual time and zone are chosen.
|
|
21
|
+
- Use `DateTime.makeZonedFromString` or `Schema.DateTimeZoned` when a serialized value must preserve its zone.
|
|
22
|
+
- Use `DateTime.toDateUtc`, `DateTime.toEpochMillis`, and `DateTime.fromDateUnsafe` only at interop boundaries.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { DateTime, Effect, Schema } from "effect";
|
|
26
|
+
|
|
27
|
+
const Timestamp = Schema.DateTimeUtcFromString;
|
|
28
|
+
|
|
29
|
+
const expiresAt = DateTime.makeUnsafe("2030-01-01T00:00:00Z");
|
|
30
|
+
|
|
31
|
+
const isExpired = Effect.gen(function* () {
|
|
32
|
+
const now = yield* DateTime.now;
|
|
33
|
+
return DateTime.isLessThanOrEqualTo(expiresAt, now);
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Instants, zones, and arithmetic
|
|
38
|
+
|
|
39
|
+
- Treat `DateTime.Utc` as an instant without retained zone information. Treat `DateTime.Zoned` as the same kind of instant plus a zone used for wall-clock parts, formatting, and zone-aware transformations.
|
|
40
|
+
- Use `DateTime.setZone` or `setZoneNamed` to view the same instant in another zone. When constructing from local wall-clock parts, use `makeZoned` with `adjustForTimeZone: true` and choose a deliberate `disambiguation` policy for DST gaps and repeated times.
|
|
41
|
+
- Use `DateTime.addDuration` and `subtractDuration` for elapsed time. Use `DateTime.add` and `subtract` for calendar arithmetic such as days, months, and years; these operations account for a zoned value's calendar rules.
|
|
42
|
+
- Use `DateTime.startOf`, `endOf`, or `nearest` rather than hand-editing fields. State `weekStartsOn` when week boundaries are domain-sensitive.
|
|
43
|
+
- Use `DateTime.Order`, `Equivalence`, `min`, `max`, `between`, and comparison helpers rather than comparing formatted strings or mutable `Date` objects.
|
|
44
|
+
- Use `formatIso` for UTC interchange, `formatIsoZoned` when preserving a zone, and `format` / `formatIntl` for presentation. Do not persist locale-formatted output.
|
|
45
|
+
|
|
46
|
+
## Current time and tests
|
|
47
|
+
|
|
48
|
+
Code that asks what time it is must remain clock-driven:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { assert, it } from "@effect/vitest";
|
|
52
|
+
import { DateTime, Duration, Effect } from "effect";
|
|
53
|
+
import { TestClock } from "effect/testing";
|
|
54
|
+
|
|
55
|
+
it.effect("expires after an hour", () =>
|
|
56
|
+
Effect.gen(function* () {
|
|
57
|
+
yield* TestClock.setTime(DateTime.toEpochMillis(DateTime.makeUnsafe("2030-01-01T00:00:00Z")));
|
|
58
|
+
const startedAt = yield* DateTime.now;
|
|
59
|
+
|
|
60
|
+
yield* TestClock.adjust("1 hour");
|
|
61
|
+
const now = yield* DateTime.now;
|
|
62
|
+
|
|
63
|
+
assert.strictEqual(Duration.toMillis(DateTime.distance(startedAt, now)), 60 * 60 * 1000);
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`@effect/vitest` supplies `TestClock` to `it.effect`. Fork work that sleeps, times out, retries, or follows a schedule before advancing the clock. Use `it.live` only when a test intentionally needs real time.
|
|
69
|
+
|
|
70
|
+
## Completion check
|
|
71
|
+
|
|
72
|
+
Confirm that domain code uses `DateTime`, untrusted inputs are decoded, UTC versus zoned intent is explicit, DST behavior is deliberate, elapsed and calendar arithmetic are not confused, serialization is stable, and time-dependent tests use `TestClock` without wall-clock sleeps.
|
package/src/catalog.ts
CHANGED
|
@@ -176,7 +176,8 @@ export const loadSkillCatalog = Effect.fn("loadSkillCatalog")(function* (
|
|
|
176
176
|
});
|
|
177
177
|
}
|
|
178
178
|
const families: Readonly<Record<string, ReadonlyArray<string>>> = {
|
|
179
|
-
effect: ["effect-ts"
|
|
179
|
+
effect: ["effect-ts"],
|
|
180
|
+
"effect-atom-data-fetching": ["effect-ts"],
|
|
180
181
|
...Object.fromEntries(externalFamilies),
|
|
181
182
|
};
|
|
182
183
|
|
package/src/index.ts
CHANGED
|
@@ -11,10 +11,20 @@ export {
|
|
|
11
11
|
EffectTsgoSetupSchema,
|
|
12
12
|
type HarnessTarget,
|
|
13
13
|
TargetConfigSchema,
|
|
14
|
+
type VitePlusQualityConfigSetup,
|
|
15
|
+
VitePlusQualityConfigSetupSchema,
|
|
14
16
|
type VitePlusQualitySetup,
|
|
15
17
|
VitePlusQualitySetupSchema,
|
|
18
|
+
type VitePlusQualityTypecheckSetup,
|
|
19
|
+
VitePlusQualityTypecheckSetupSchema,
|
|
20
|
+
type VitePlusQualityWorkflowSetup,
|
|
21
|
+
VitePlusQualityWorkflowSetupSchema,
|
|
22
|
+
type VitePlusQualityWorkflowStep,
|
|
23
|
+
VitePlusQualityWorkflowStepSchema,
|
|
16
24
|
type VitePlusSetup,
|
|
17
25
|
VitePlusSetupSchema,
|
|
26
|
+
type VitePlusTypecheckStrategy,
|
|
27
|
+
VitePlusTypecheckStrategySchema,
|
|
18
28
|
} from "./manifest.ts";
|
|
19
29
|
export {
|
|
20
30
|
applyEffectSourcePlan,
|
package/src/manifest.ts
CHANGED
|
@@ -53,8 +53,38 @@ export const VitePlusHooksSetupSchema = Schema.Struct({
|
|
|
53
53
|
enabled: Schema.optional(Schema.Boolean),
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
export const
|
|
56
|
+
export const VitePlusTypecheckStrategySchema = Schema.Literals(["single-project", "workspace"]);
|
|
57
|
+
export type VitePlusTypecheckStrategy = typeof VitePlusTypecheckStrategySchema.Type;
|
|
58
|
+
|
|
59
|
+
export const VitePlusQualityTypecheckSetupSchema = Schema.Struct({
|
|
60
|
+
strategy: Schema.optional(VitePlusTypecheckStrategySchema),
|
|
61
|
+
concurrency: Schema.optional(Schema.Int),
|
|
62
|
+
packages: Schema.optional(Schema.Array(Schema.String)),
|
|
63
|
+
});
|
|
64
|
+
export type VitePlusQualityTypecheckSetup = typeof VitePlusQualityTypecheckSetupSchema.Type;
|
|
65
|
+
|
|
66
|
+
export const VitePlusQualityConfigSetupSchema = Schema.Struct({
|
|
57
67
|
enabled: Schema.optional(Schema.Boolean),
|
|
68
|
+
typecheck: Schema.optional(VitePlusQualityTypecheckSetupSchema),
|
|
69
|
+
});
|
|
70
|
+
export type VitePlusQualityConfigSetup = typeof VitePlusQualityConfigSetupSchema.Type;
|
|
71
|
+
|
|
72
|
+
export const VitePlusQualityWorkflowStepSchema = Schema.Struct({
|
|
73
|
+
name: Schema.String,
|
|
74
|
+
run: Schema.Array(Schema.String),
|
|
75
|
+
});
|
|
76
|
+
export type VitePlusQualityWorkflowStep = typeof VitePlusQualityWorkflowStepSchema.Type;
|
|
77
|
+
|
|
78
|
+
export const VitePlusQualityWorkflowSetupSchema = Schema.Struct({
|
|
79
|
+
enabled: Schema.optional(Schema.Boolean),
|
|
80
|
+
beforeChecks: Schema.optional(Schema.Array(VitePlusQualityWorkflowStepSchema)),
|
|
81
|
+
typecheck: Schema.optional(Schema.Array(Schema.String)),
|
|
82
|
+
});
|
|
83
|
+
export type VitePlusQualityWorkflowSetup = typeof VitePlusQualityWorkflowSetupSchema.Type;
|
|
84
|
+
|
|
85
|
+
export const VitePlusQualitySetupSchema = Schema.Struct({
|
|
86
|
+
config: Schema.optional(VitePlusQualityConfigSetupSchema),
|
|
87
|
+
workflow: Schema.optional(VitePlusQualityWorkflowSetupSchema),
|
|
58
88
|
});
|
|
59
89
|
export type VitePlusQualitySetup = typeof VitePlusQualitySetupSchema.Type;
|
|
60
90
|
|
|
@@ -123,7 +153,19 @@ export type NormalizedManifest = {
|
|
|
123
153
|
readonly enabled: boolean;
|
|
124
154
|
};
|
|
125
155
|
readonly quality: {
|
|
126
|
-
readonly
|
|
156
|
+
readonly config: {
|
|
157
|
+
readonly enabled: boolean;
|
|
158
|
+
readonly typecheck: {
|
|
159
|
+
readonly strategy: VitePlusTypecheckStrategy;
|
|
160
|
+
readonly concurrency: number;
|
|
161
|
+
readonly packages: ReadonlyArray<string>;
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
readonly workflow: {
|
|
165
|
+
readonly enabled: boolean;
|
|
166
|
+
readonly beforeChecks: ReadonlyArray<VitePlusQualityWorkflowStep>;
|
|
167
|
+
readonly typecheck: ReadonlyArray<string>;
|
|
168
|
+
};
|
|
127
169
|
};
|
|
128
170
|
};
|
|
129
171
|
};
|
|
@@ -158,6 +200,8 @@ export const normalizeManifest = (manifest: DevKitManifest): NormalizedManifest
|
|
|
158
200
|
};
|
|
159
201
|
}
|
|
160
202
|
}
|
|
203
|
+
const quality = manifest.setup?.vitePlus?.quality;
|
|
204
|
+
const typecheck = quality?.config?.typecheck;
|
|
161
205
|
|
|
162
206
|
return {
|
|
163
207
|
exclude: manifest.exclude ?? [],
|
|
@@ -186,7 +230,19 @@ export const normalizeManifest = (manifest: DevKitManifest): NormalizedManifest
|
|
|
186
230
|
enabled: manifest.setup?.vitePlus?.hooks?.enabled ?? false,
|
|
187
231
|
},
|
|
188
232
|
quality: {
|
|
189
|
-
|
|
233
|
+
config: {
|
|
234
|
+
enabled: quality?.config?.enabled ?? false,
|
|
235
|
+
typecheck: {
|
|
236
|
+
strategy: typecheck?.strategy ?? "single-project",
|
|
237
|
+
concurrency: typecheck?.concurrency ?? 4,
|
|
238
|
+
packages: typecheck?.packages ?? [],
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
workflow: {
|
|
242
|
+
enabled: quality?.workflow?.enabled ?? false,
|
|
243
|
+
beforeChecks: quality?.workflow?.beforeChecks ?? [],
|
|
244
|
+
typecheck: quality?.workflow?.typecheck ?? ["vp run typecheck"],
|
|
245
|
+
},
|
|
190
246
|
},
|
|
191
247
|
},
|
|
192
248
|
},
|
package/src/project-package.ts
CHANGED
|
@@ -13,6 +13,7 @@ const ProjectPackageSchema = Schema.fromJsonString(
|
|
|
13
13
|
devDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
14
14
|
optionalDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
15
15
|
peerDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
16
|
+
workspaces: Schema.optional(Schema.Unknown),
|
|
16
17
|
}),
|
|
17
18
|
);
|
|
18
19
|
|
package/src/sync.ts
CHANGED
|
@@ -48,6 +48,8 @@ import {
|
|
|
48
48
|
type VitePlusHooksPlan,
|
|
49
49
|
} from "./vite-plus-hooks.ts";
|
|
50
50
|
import {
|
|
51
|
+
renderVitePlusConfigTemplate,
|
|
52
|
+
renderVitePlusWorkflowTemplate,
|
|
51
53
|
validateVitePlusQualitySupport,
|
|
52
54
|
VITE_PLUS_CONFIG_PATH,
|
|
53
55
|
VITE_PLUS_CONFIG_TEMPLATE,
|
|
@@ -223,7 +225,8 @@ class ApplyRaceError extends Schema.TaggedErrorClass<ApplyRaceError>()("ApplyRac
|
|
|
223
225
|
}
|
|
224
226
|
|
|
225
227
|
const SKILL_FAMILIES: SkillCatalog = {
|
|
226
|
-
effect: ["effect-ts"
|
|
228
|
+
effect: ["effect-ts"],
|
|
229
|
+
"effect-atom-data-fetching": ["effect-ts"],
|
|
227
230
|
};
|
|
228
231
|
|
|
229
232
|
export const DEFAULT_MANIFEST = "dev-kit.jsonc";
|
|
@@ -632,34 +635,48 @@ const buildDesiredOutputs = Effect.fn("buildDesiredSkillOutputs")(function* (
|
|
|
632
635
|
linkTarget,
|
|
633
636
|
});
|
|
634
637
|
}
|
|
635
|
-
if (setup.vitePlus.quality.enabled) {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
path: VITE_PLUS_CONFIG_PATH,
|
|
640
|
-
sourcePath: VITE_PLUS_CONFIG_TEMPLATE,
|
|
641
|
-
},
|
|
642
|
-
{
|
|
643
|
-
resourceId: "setup:vite-plus-github-actions" as const,
|
|
644
|
-
path: VITE_PLUS_GITHUB_ACTIONS_PATH,
|
|
645
|
-
sourcePath: VITE_PLUS_GITHUB_ACTIONS_TEMPLATE,
|
|
646
|
-
},
|
|
647
|
-
]) {
|
|
648
|
-
const managed = yield* resolveManagedPath(projectDir, generated.path);
|
|
649
|
-
const content = yield* readGeneratedFileTemplate(packageRoot, generated.sourcePath);
|
|
638
|
+
if (setup.vitePlus.quality.config.enabled) {
|
|
639
|
+
const managed = yield* resolveManagedPath(projectDir, VITE_PLUS_CONFIG_PATH);
|
|
640
|
+
const template = yield* readGeneratedFileTemplate(packageRoot, VITE_PLUS_CONFIG_TEMPLATE);
|
|
641
|
+
const content = renderVitePlusConfigTemplate(template, setup.vitePlus.quality.config.typecheck);
|
|
650
642
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
643
|
+
outputs.push({
|
|
644
|
+
resourceId: "setup:vite-plus-config",
|
|
645
|
+
path: managed.relative,
|
|
646
|
+
sourcePath: VITE_PLUS_CONFIG_TEMPLATE,
|
|
647
|
+
mode: "copy",
|
|
648
|
+
kind: "file",
|
|
649
|
+
digest: yield* digestFileContent(content),
|
|
650
|
+
destination: managed.absolute,
|
|
651
|
+
content,
|
|
652
|
+
adoptIfExact: true,
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
if (setup.vitePlus.quality.workflow.enabled) {
|
|
656
|
+
const managed = yield* resolveManagedPath(projectDir, VITE_PLUS_GITHUB_ACTIONS_PATH);
|
|
657
|
+
const template = yield* readGeneratedFileTemplate(
|
|
658
|
+
packageRoot,
|
|
659
|
+
VITE_PLUS_GITHUB_ACTIONS_TEMPLATE,
|
|
660
|
+
);
|
|
661
|
+
const content = renderVitePlusWorkflowTemplate(template, {
|
|
662
|
+
devKitCommand:
|
|
663
|
+
projectDir === packageRoot
|
|
664
|
+
? "./bin/dev-kit.mjs apply --locked"
|
|
665
|
+
: "vp exec dev-kit apply --locked",
|
|
666
|
+
workflow: setup.vitePlus.quality.workflow,
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
outputs.push({
|
|
670
|
+
resourceId: "setup:vite-plus-github-actions",
|
|
671
|
+
path: managed.relative,
|
|
672
|
+
sourcePath: VITE_PLUS_GITHUB_ACTIONS_TEMPLATE,
|
|
673
|
+
mode: "copy",
|
|
674
|
+
kind: "file",
|
|
675
|
+
digest: yield* digestFileContent(content),
|
|
676
|
+
destination: managed.absolute,
|
|
677
|
+
content,
|
|
678
|
+
adoptIfExact: true,
|
|
679
|
+
});
|
|
663
680
|
}
|
|
664
681
|
const agentsTarget = targets.agents;
|
|
665
682
|
const duplicateOutput = skills.find(
|
|
@@ -907,17 +924,31 @@ export const planProjectSkills = Effect.fn("planProjectSkills")(function* (optio
|
|
|
907
924
|
const packageRoot = yield* resolvePackageRoot();
|
|
908
925
|
const manifest = normalizeManifest(yield* readManifest(manifestManaged.absolute));
|
|
909
926
|
|
|
910
|
-
|
|
927
|
+
const vitePlusQuality = manifest.setup.vitePlus.quality;
|
|
928
|
+
const vitePlusQualityEnabled = vitePlusQuality.config.enabled || vitePlusQuality.workflow.enabled;
|
|
929
|
+
|
|
930
|
+
if (vitePlusQualityEnabled) {
|
|
911
931
|
if (!manifest.setup.effectTsgo.enabled) {
|
|
912
932
|
return yield* new InvalidProjectStateError({
|
|
913
933
|
message:
|
|
914
|
-
"setup.vitePlus.quality requires setup.effectTsgo.enabled so
|
|
934
|
+
"setup.vitePlus.quality requires setup.effectTsgo.enabled so managed quality setup converges the Effect-patched compiler",
|
|
915
935
|
});
|
|
916
936
|
}
|
|
917
937
|
yield* validateVitePlusQualitySupport(
|
|
918
938
|
projectDir,
|
|
919
939
|
packageRoot,
|
|
920
940
|
manifest.setup.effectTsgo.typescriptPackage,
|
|
941
|
+
{
|
|
942
|
+
...(vitePlusQuality.config.enabled ? { config: vitePlusQuality.config.typecheck } : {}),
|
|
943
|
+
...(vitePlusQuality.workflow.enabled
|
|
944
|
+
? {
|
|
945
|
+
workflow: {
|
|
946
|
+
beforeChecks: vitePlusQuality.workflow.beforeChecks,
|
|
947
|
+
typecheck: vitePlusQuality.workflow.typecheck,
|
|
948
|
+
},
|
|
949
|
+
}
|
|
950
|
+
: {}),
|
|
951
|
+
},
|
|
921
952
|
);
|
|
922
953
|
}
|
|
923
954
|
const effectSource = manifest.setup.effectSource.enabled
|
package/src/tool-metadata.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import packageMetadata from "../package.json" with { type: "json" };
|
|
2
2
|
|
|
3
3
|
export const DEV_KIT_VERSION = packageMetadata.version;
|
|
4
|
+
export const VITE_PLUS_TESTED_VERSION = packageMetadata.devDependencies["vite-plus"];
|
|
5
|
+
export const VITE_PLUS_SUPPORTED_RANGE = packageMetadata.peerDependencies["vite-plus"];
|
|
@@ -0,0 +1,70 @@
|
|
|
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.TaggedErrorClass<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* new VitePlusDependencyError({
|
|
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* new VitePlusDependencyError({
|
|
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
|
+
() =>
|
|
45
|
+
new VitePlusDependencyError({
|
|
46
|
+
message: "installed vite-plus package metadata has no valid version",
|
|
47
|
+
}),
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (!semver.valid(installed.version)) {
|
|
52
|
+
return yield* new VitePlusDependencyError({
|
|
53
|
+
message: `installed vite-plus package metadata has invalid version: ${installed.version}`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (!semver.satisfies(installed.version, VITE_PLUS_SUPPORTED_RANGE)) {
|
|
57
|
+
return yield* new VitePlusDependencyError({
|
|
58
|
+
message: `installed vite-plus ${installed.version} is incompatible with @danieljvdm/dev-kit; supported range: ${VITE_PLUS_SUPPORTED_RANGE}`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const vpBin = path.join(projectDir, "node_modules", ".bin", "vp");
|
|
62
|
+
|
|
63
|
+
if (!(yield* fs.exists(vpBin))) {
|
|
64
|
+
return yield* new VitePlusDependencyError({
|
|
65
|
+
message: "vite-plus is installed but node_modules/.bin/vp is missing",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { version: installed.version, vpBin } satisfies InstalledVitePlus;
|
|
70
|
+
});
|
package/src/vite-plus-hooks.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Config, Effect, FileSystem, Path, Schema, Stream } from "effect";
|
|
2
2
|
import { ChildProcess } from "effect/unstable/process";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { validateInstalledVitePlus } from "./vite-plus-dependency.ts";
|
|
5
5
|
|
|
6
6
|
export const VITE_PLUS_HOOKS_DIR = ".vite-hooks";
|
|
7
7
|
export const VITE_PLUS_HOOKS_PATH = `${VITE_PLUS_HOOKS_DIR}/_`;
|
|
@@ -121,23 +121,14 @@ const inspectVitePlusHooks = Effect.fn("inspectVitePlusHooks")(function* (projec
|
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
export const planVitePlusHooks = Effect.fn("planVitePlusHooks")(function* (projectDir: string) {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
}
|
|
124
|
+
const { vpBin } = yield* validateInstalledVitePlus(projectDir).pipe(
|
|
125
|
+
Effect.mapError(
|
|
126
|
+
(error) =>
|
|
127
|
+
new VitePlusHooksDependencyError({
|
|
128
|
+
message: `${error.message} before enabling setup.vitePlus.hooks`,
|
|
129
|
+
}),
|
|
130
|
+
),
|
|
131
|
+
);
|
|
141
132
|
const viteGitHooks = yield* Config.string("VITE_GIT_HOOKS").pipe(Config.withDefault(""));
|
|
142
133
|
const husky = yield* Config.string("HUSKY").pipe(Config.withDefault(""));
|
|
143
134
|
const action =
|
package/src/vite-plus-quality.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
2
|
+
import { parse as parseJsonc, type ParseError } from "jsonc-parser";
|
|
2
3
|
|
|
4
|
+
import type { VitePlusQualityWorkflowStep, VitePlusTypecheckStrategy } from "./manifest.ts";
|
|
3
5
|
import { readDirectDependencyNames, readProjectPackage } from "./project-package.ts";
|
|
6
|
+
import { validateInstalledVitePlus } from "./vite-plus-dependency.ts";
|
|
4
7
|
|
|
5
8
|
export const VITE_PLUS_CONFIG_PATH = "vite.config.ts";
|
|
6
9
|
export const VITE_PLUS_CONFIG_TEMPLATE = "templates/vite-plus/vite.config.ts";
|
|
@@ -12,13 +15,136 @@ export class VitePlusQualitySupportError extends Schema.TaggedErrorClass<VitePlu
|
|
|
12
15
|
{ message: Schema.String },
|
|
13
16
|
) {}
|
|
14
17
|
|
|
18
|
+
export type VitePlusQualityTypecheck = {
|
|
19
|
+
readonly strategy: VitePlusTypecheckStrategy;
|
|
20
|
+
readonly concurrency: number;
|
|
21
|
+
readonly packages: ReadonlyArray<string>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type VitePlusQualityWorkflow = {
|
|
25
|
+
readonly beforeChecks: ReadonlyArray<VitePlusQualityWorkflowStep>;
|
|
26
|
+
readonly typecheck: ReadonlyArray<string>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type VitePlusQualitySelection = {
|
|
30
|
+
readonly config?: VitePlusQualityTypecheck;
|
|
31
|
+
readonly workflow?: VitePlusQualityWorkflow;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const SINGLE_PROJECT_TYPECHECK_TASK = ' typecheck: "tsc --noEmit",';
|
|
35
|
+
const LOCKED_DEV_KIT_COMMAND = "vp exec dev-kit apply --locked";
|
|
36
|
+
const BEFORE_CHECKS_MARKER =
|
|
37
|
+
" # Dev Kit inserts configured quality.workflow.beforeChecks steps here.\n\n";
|
|
38
|
+
const DEFAULT_WORKFLOW_TYPECHECK = ` - name: Type check with Effect TypeScript-Go
|
|
39
|
+
run: vp run typecheck`;
|
|
40
|
+
|
|
41
|
+
const replaceUniqueTemplateMarker = (
|
|
42
|
+
template: string,
|
|
43
|
+
marker: string,
|
|
44
|
+
replacement: string,
|
|
45
|
+
): string => {
|
|
46
|
+
const parts = template.split(marker);
|
|
47
|
+
|
|
48
|
+
if (parts.length !== 2) {
|
|
49
|
+
throw new Error(`expected exactly one generated template marker: ${marker}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return `${parts[0]}${replacement}${parts[1]}`;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const shellQuote = (value: string): string => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
56
|
+
|
|
57
|
+
export const renderVitePlusConfigTemplate = (
|
|
58
|
+
template: string,
|
|
59
|
+
typecheck: VitePlusQualityTypecheck,
|
|
60
|
+
): string => {
|
|
61
|
+
if (typecheck.strategy === "single-project") return template;
|
|
62
|
+
const filters = typecheck.packages
|
|
63
|
+
.map((packageDir) => `--filter ${shellQuote(`./${packageDir}`)}`)
|
|
64
|
+
.join(" ");
|
|
65
|
+
const command = `vp run --cache --concurrency-limit ${typecheck.concurrency} ${filters} --fail-if-no-match typecheck`;
|
|
66
|
+
|
|
67
|
+
return replaceUniqueTemplateMarker(
|
|
68
|
+
template,
|
|
69
|
+
SINGLE_PROJECT_TYPECHECK_TASK,
|
|
70
|
+
` typecheck: {
|
|
71
|
+
command: ${JSON.stringify(command)},
|
|
72
|
+
cache: false,
|
|
73
|
+
},`,
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const renderVitePlusWorkflowTemplate = (
|
|
78
|
+
template: string,
|
|
79
|
+
options: {
|
|
80
|
+
readonly devKitCommand?: string;
|
|
81
|
+
readonly workflow?: VitePlusQualityWorkflow;
|
|
82
|
+
} = {},
|
|
83
|
+
): string => {
|
|
84
|
+
const devKitCommand = options.devKitCommand;
|
|
85
|
+
const workflow = options.workflow;
|
|
86
|
+
let rendered =
|
|
87
|
+
devKitCommand === undefined || devKitCommand === LOCKED_DEV_KIT_COMMAND
|
|
88
|
+
? template
|
|
89
|
+
: replaceUniqueTemplateMarker(template, LOCKED_DEV_KIT_COMMAND, devKitCommand);
|
|
90
|
+
|
|
91
|
+
if (workflow !== undefined) {
|
|
92
|
+
const steps = workflow.beforeChecks
|
|
93
|
+
.map((step) => {
|
|
94
|
+
const commands = step.run
|
|
95
|
+
.flatMap((command) => command.split("\n"))
|
|
96
|
+
.map((line) => ` ${line}`)
|
|
97
|
+
.join("\n");
|
|
98
|
+
|
|
99
|
+
return ` - name: ${JSON.stringify(step.name)}
|
|
100
|
+
run: |
|
|
101
|
+
${commands}`;
|
|
102
|
+
})
|
|
103
|
+
.join("\n\n");
|
|
104
|
+
|
|
105
|
+
rendered = replaceUniqueTemplateMarker(
|
|
106
|
+
rendered,
|
|
107
|
+
BEFORE_CHECKS_MARKER,
|
|
108
|
+
steps.length === 0 ? "" : `${steps}\n\n`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
workflow !== undefined &&
|
|
113
|
+
(workflow.typecheck.length !== 1 || workflow.typecheck[0] !== "vp run typecheck")
|
|
114
|
+
) {
|
|
115
|
+
const commands = workflow.typecheck
|
|
116
|
+
.flatMap((command) => command.split("\n"))
|
|
117
|
+
.map((line) => ` ${line}`)
|
|
118
|
+
.join("\n");
|
|
119
|
+
|
|
120
|
+
rendered = replaceUniqueTemplateMarker(
|
|
121
|
+
rendered,
|
|
122
|
+
DEFAULT_WORKFLOW_TYPECHECK,
|
|
123
|
+
` - name: Type check with Effect TypeScript-Go
|
|
124
|
+
run: |
|
|
125
|
+
${commands}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return rendered;
|
|
130
|
+
};
|
|
131
|
+
|
|
15
132
|
export const validateVitePlusQualitySupport = Effect.fn("validateVitePlusQualitySupport")(
|
|
16
|
-
function* (
|
|
133
|
+
function* (
|
|
134
|
+
projectDir: string,
|
|
135
|
+
packageRoot: string,
|
|
136
|
+
typescriptPackage: string,
|
|
137
|
+
selection: VitePlusQualitySelection,
|
|
138
|
+
) {
|
|
17
139
|
const fs = yield* FileSystem.FileSystem;
|
|
18
140
|
const path = yield* Path.Path;
|
|
19
141
|
const packageJson = yield* readProjectPackage(projectDir);
|
|
20
142
|
const dependencies = yield* readDirectDependencyNames(projectDir);
|
|
21
|
-
const required = new Set(["
|
|
143
|
+
const required = new Set(["effect", "@effect/tsgo", typescriptPackage]);
|
|
144
|
+
|
|
145
|
+
yield* validateInstalledVitePlus(projectDir).pipe(
|
|
146
|
+
Effect.mapError((error) => new VitePlusQualitySupportError({ message: error.message })),
|
|
147
|
+
);
|
|
22
148
|
|
|
23
149
|
if (projectDir !== packageRoot) required.add("@danieljvdm/dev-kit");
|
|
24
150
|
const missing = [...required].filter((dependency) => !dependencies.includes(dependency));
|
|
@@ -28,22 +154,145 @@ export const validateVitePlusQualitySupport = Effect.fn("validateVitePlusQuality
|
|
|
28
154
|
message: `setup.vitePlus.quality requires direct dependencies: ${missing.join(", ")}`,
|
|
29
155
|
});
|
|
30
156
|
}
|
|
157
|
+
if (selection.workflow !== undefined) {
|
|
158
|
+
if (selection.workflow.typecheck.length === 0) {
|
|
159
|
+
return yield* new VitePlusQualitySupportError({
|
|
160
|
+
message: "setup.vitePlus.quality.workflow.typecheck requires at least one command",
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
for (const command of selection.workflow.typecheck) {
|
|
164
|
+
if (command.trim().length === 0) {
|
|
165
|
+
return yield* new VitePlusQualitySupportError({
|
|
166
|
+
message: "setup.vitePlus.quality.workflow.typecheck commands must not be empty",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const step of selection.workflow.beforeChecks) {
|
|
171
|
+
if (step.name.trim().length === 0 || step.run.length === 0) {
|
|
172
|
+
return yield* new VitePlusQualitySupportError({
|
|
173
|
+
message:
|
|
174
|
+
"setup.vitePlus.quality.workflow.beforeChecks steps require a name and at least one command",
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (step.run.some((command) => command.trim().length === 0)) {
|
|
178
|
+
return yield* new VitePlusQualitySupportError({
|
|
179
|
+
message: "setup.vitePlus.quality.workflow.beforeChecks commands must not be empty",
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const typecheck = selection.config;
|
|
185
|
+
|
|
186
|
+
if (typecheck === undefined) return;
|
|
31
187
|
const conflictingScripts = ["check", "typecheck"].filter(
|
|
32
188
|
(script) => packageJson.scripts?.[script] !== undefined,
|
|
33
189
|
);
|
|
34
190
|
|
|
35
191
|
if (conflictingScripts.length > 0) {
|
|
36
192
|
return yield* new VitePlusQualitySupportError({
|
|
37
|
-
message: `setup.vitePlus.quality defines Vite tasks that conflict with package scripts: ${conflictingScripts.join(", ")}`,
|
|
193
|
+
message: `setup.vitePlus.quality.config defines Vite tasks that conflict with package scripts: ${conflictingScripts.join(", ")}`,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (typecheck.concurrency < 1 || typecheck.concurrency > 32) {
|
|
197
|
+
return yield* new VitePlusQualitySupportError({
|
|
198
|
+
message: "setup.vitePlus.quality.config typecheck concurrency must be between 1 and 32",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (typecheck.strategy === "single-project" && typecheck.packages.length > 0) {
|
|
202
|
+
return yield* new VitePlusQualitySupportError({
|
|
203
|
+
message:
|
|
204
|
+
"setup.vitePlus.quality.config single-project typechecking does not accept workspace packages",
|
|
38
205
|
});
|
|
39
206
|
}
|
|
40
|
-
const
|
|
207
|
+
const workspacePatterns = Array.isArray(packageJson.workspaces)
|
|
208
|
+
? packageJson.workspaces
|
|
209
|
+
: typeof packageJson.workspaces === "object" && packageJson.workspaces !== null
|
|
210
|
+
? (packageJson.workspaces as { readonly packages?: unknown }).packages
|
|
211
|
+
: undefined;
|
|
41
212
|
|
|
42
|
-
if (
|
|
213
|
+
if (
|
|
214
|
+
typecheck.strategy === "workspace" &&
|
|
215
|
+
(!Array.isArray(workspacePatterns) ||
|
|
216
|
+
workspacePatterns.length === 0 ||
|
|
217
|
+
!workspacePatterns.every((pattern) => typeof pattern === "string"))
|
|
218
|
+
) {
|
|
43
219
|
return yield* new VitePlusQualitySupportError({
|
|
44
220
|
message:
|
|
45
|
-
"
|
|
221
|
+
"setup.vitePlus.quality.config workspace typechecking requires package.json workspaces",
|
|
46
222
|
});
|
|
47
223
|
}
|
|
224
|
+
if (typecheck.strategy === "workspace") {
|
|
225
|
+
if (typecheck.packages.length === 0) {
|
|
226
|
+
return yield* new VitePlusQualitySupportError({
|
|
227
|
+
message:
|
|
228
|
+
"setup.vitePlus.quality.config workspace typechecking requires explicit package directories",
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
const uniquePackages = new Set(typecheck.packages);
|
|
232
|
+
|
|
233
|
+
if (uniquePackages.size !== typecheck.packages.length) {
|
|
234
|
+
return yield* new VitePlusQualitySupportError({
|
|
235
|
+
message: "setup.vitePlus.quality.config workspace typecheck packages must be unique",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
for (const packageDir of typecheck.packages) {
|
|
239
|
+
const absolute = path.resolve(projectDir, packageDir);
|
|
240
|
+
const relative = path.relative(projectDir, absolute);
|
|
241
|
+
|
|
242
|
+
if (
|
|
243
|
+
packageDir.length === 0 ||
|
|
244
|
+
path.isAbsolute(packageDir) ||
|
|
245
|
+
relative.length === 0 ||
|
|
246
|
+
relative === ".." ||
|
|
247
|
+
relative.startsWith(`..${path.sep}`)
|
|
248
|
+
) {
|
|
249
|
+
return yield* new VitePlusQualitySupportError({
|
|
250
|
+
message: `setup.vitePlus.quality.config workspace package must be a project-relative subdirectory: ${packageDir}`,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const workspacePackage = yield* readProjectPackage(absolute).pipe(
|
|
254
|
+
Effect.mapError(
|
|
255
|
+
() =>
|
|
256
|
+
new VitePlusQualitySupportError({
|
|
257
|
+
message: `setup.vitePlus.quality.config workspace package is missing a valid package.json: ${packageDir}`,
|
|
258
|
+
}),
|
|
259
|
+
),
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
if (workspacePackage.scripts?.typecheck === undefined) {
|
|
263
|
+
return yield* new VitePlusQualitySupportError({
|
|
264
|
+
message: `setup.vitePlus.quality.config workspace package requires a typecheck script: ${packageDir}`,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (typecheck.strategy === "single-project") {
|
|
270
|
+
const tsconfigPath = path.join(projectDir, "tsconfig.json");
|
|
271
|
+
|
|
272
|
+
if (!(yield* fs.exists(tsconfigPath))) {
|
|
273
|
+
return yield* new VitePlusQualitySupportError({
|
|
274
|
+
message:
|
|
275
|
+
"setup.vitePlus.quality.config single-project typechecking requires tsconfig.json",
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
const errors: Array<ParseError> = [];
|
|
279
|
+
const tsconfig = parseJsonc(yield* fs.readFileString(tsconfigPath), errors, {
|
|
280
|
+
allowTrailingComma: true,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (errors.length > 0 || typeof tsconfig !== "object" || tsconfig === null) {
|
|
284
|
+
return yield* new VitePlusQualitySupportError({
|
|
285
|
+
message: "setup.vitePlus.quality.config requires a valid root tsconfig.json",
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
const references = (tsconfig as { readonly references?: unknown }).references;
|
|
289
|
+
|
|
290
|
+
if (Array.isArray(references) && references.length > 0) {
|
|
291
|
+
return yield* new VitePlusQualitySupportError({
|
|
292
|
+
message:
|
|
293
|
+
"setup.vitePlus.quality.config single-project typechecking does not support tsconfig project references; select the workspace strategy or use a custom Vite config",
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
48
297
|
},
|
|
49
298
|
);
|
|
@@ -22,14 +22,21 @@ jobs:
|
|
|
22
22
|
with:
|
|
23
23
|
persist-credentials: false
|
|
24
24
|
|
|
25
|
-
-
|
|
26
|
-
|
|
25
|
+
# setup-vp's v1 tag is frozen. Keep this v1.16.1 commit current with
|
|
26
|
+
# Renovate or Dependabot so fixes arrive through reviewed pull requests.
|
|
27
|
+
- name: Set up Vite+ and install dependencies
|
|
28
|
+
uses: voidzero-dev/setup-vp@143f5f385f39b1b753ffed1a01ad443811855c8b # v1.16.1
|
|
27
29
|
with:
|
|
30
|
+
# Vite+ version intentionally resolves from the consumer manifest/lock.
|
|
28
31
|
node-version: "24"
|
|
29
32
|
cache: true
|
|
33
|
+
run-install: |
|
|
34
|
+
args: ["--frozen-lockfile", "--ignore-scripts"]
|
|
30
35
|
|
|
31
|
-
- name:
|
|
32
|
-
run: vp
|
|
36
|
+
- name: Verify locked Dev Kit setup
|
|
37
|
+
run: vp exec dev-kit apply --locked
|
|
38
|
+
|
|
39
|
+
# Dev Kit inserts configured quality.workflow.beforeChecks steps here.
|
|
33
40
|
|
|
34
41
|
- name: Check formatting
|
|
35
42
|
run: vp fmt --check
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
interface:
|
|
2
|
-
display_name: "Effect Atom Data Fetching"
|
|
3
|
-
short_description: "Cache HTTP data safely with Effect Atom"
|
|
4
|
-
default_prompt: "Use $effect-atom-data-fetching to design or debug Effect Atom HTTP queries, caching, polling, invalidation, and SSR integration in a React app."
|