@xmachines/shared 1.0.0-beta.10
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 +372 -0
- package/config/oxfmt.json +10 -0
- package/config/oxlint.json +27 -0
- package/config/tsconfig.json +40 -0
- package/config/vite-aliases.ts +84 -0
- package/config/vitest.node.setup.ts +15 -0
- package/config/vitest.setup.ts +7 -0
- package/config/vitest.ts +87 -0
- package/config/vitest.urlpattern.setup.ts +22 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# @xmachines/shared
|
|
2
|
+
|
|
3
|
+
**Shared TypeScript, linting, and formatting configurations for XMachines monorepo**
|
|
4
|
+
|
|
5
|
+
Centralized config enabling composite build system with project references.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
`@xmachines/shared` provides shared configuration files for all XMachines packages, establishing consistent TypeScript compilation, linting rules, formatting, and Vitest setup across the monorepo.
|
|
10
|
+
|
|
11
|
+
**Enhancement:** Added TypeScript composite build system with project references enabling correct build order, incremental compilation, and IDE source navigation via declaration maps.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
This package is automatically available to all workspace packages via npm workspaces.
|
|
16
|
+
|
|
17
|
+
## Contents
|
|
18
|
+
|
|
19
|
+
Configuration files in `config/` directory:
|
|
20
|
+
|
|
21
|
+
- **`config/tsconfig.json`** - TypeScript compiler settings with composite project support
|
|
22
|
+
- **`config/oxlint.json`** - Linting rules and plugins
|
|
23
|
+
- **`config/oxfmt.json`** - Code formatting preferences
|
|
24
|
+
- **`config/vitest.ts`** - Shared Vitest config helper (`defineXmVitestConfig`)
|
|
25
|
+
- **`config/vitest.setup.ts`** - Shared cross-runtime Vitest setup (polyfills, jest-dom matchers)
|
|
26
|
+
- **`config/vitest.node.setup.ts`** - Node-only Vitest setup (Node runtime/version preflight)
|
|
27
|
+
- **`config/vite-aliases.ts`** - Workspace alias mapping for `@xmachines/*`
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### TypeScript Configuration
|
|
32
|
+
|
|
33
|
+
**Recommended (short alias):**
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"extends": "@xmachines/shared/tsconfig",
|
|
38
|
+
"compilerOptions": {
|
|
39
|
+
"composite": true,
|
|
40
|
+
"rootDir": "./src",
|
|
41
|
+
"outDir": "./dist"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Alternative (full path):**
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"extends": "@xmachines/shared/config/tsconfig.json",
|
|
51
|
+
"compilerOptions": {
|
|
52
|
+
"composite": true,
|
|
53
|
+
"rootDir": "./src",
|
|
54
|
+
"outDir": "./dist"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
> **Note:** Both import styles work. The short alias is recommended for cleaner config files.
|
|
60
|
+
|
|
61
|
+
### Oxlint Configuration
|
|
62
|
+
|
|
63
|
+
**Recommended:**
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"extends": ["@xmachines/shared/oxlint"]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Oxfmt Configuration
|
|
72
|
+
|
|
73
|
+
**Recommended:**
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"extends": ["@xmachines/shared/oxfmt"]
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Vite Alias Configuration
|
|
82
|
+
|
|
83
|
+
Use the shared alias helper in Vite and Vitest configs so workspace packages resolve to source files without requiring `dist/` builds:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { defineConfig } from "vite";
|
|
87
|
+
import { xmAliases } from "@xmachines/shared/vite-aliases";
|
|
88
|
+
|
|
89
|
+
export default defineConfig({
|
|
90
|
+
resolve: {
|
|
91
|
+
alias: xmAliases(import.meta.url),
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This is especially important after `npm run clean`, where package `exports` targeting `dist/` are temporarily unavailable until rebuild.
|
|
97
|
+
|
|
98
|
+
### Vitest Configuration
|
|
99
|
+
|
|
100
|
+
Use the shared helper so package configs inherit common aliasing and runtime setup:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { defineXmVitestConfig } from "@xmachines/shared/vitest";
|
|
104
|
+
|
|
105
|
+
export default defineXmVitestConfig(import.meta.url, {
|
|
106
|
+
test: {
|
|
107
|
+
environment: "node",
|
|
108
|
+
include: ["test/**/*.test.ts"],
|
|
109
|
+
exclude: ["node_modules/**"],
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Browser project example:**
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { defineXmVitestConfig } from "@xmachines/shared/vitest";
|
|
118
|
+
import { playwright } from "@vitest/browser-playwright";
|
|
119
|
+
|
|
120
|
+
export default defineXmVitestConfig(import.meta.url, {
|
|
121
|
+
test: {
|
|
122
|
+
name: "my-demo-browser",
|
|
123
|
+
browser: {
|
|
124
|
+
enabled: true,
|
|
125
|
+
provider: playwright(),
|
|
126
|
+
headless: true,
|
|
127
|
+
instances: [{ browser: "chromium" }],
|
|
128
|
+
},
|
|
129
|
+
include: ["test/browser/**/*.browser.test.ts"],
|
|
130
|
+
exclude: ["node_modules/**"],
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
What the shared Vitest layer provides automatically:
|
|
136
|
+
|
|
137
|
+
- `resolve.alias` for all `@xmachines/*` packages via `xmAliases(...)`
|
|
138
|
+
- Shared setup injection (`config/vitest.setup.ts`) unless already present
|
|
139
|
+
- Node setup injection (`config/vitest.node.setup.ts`) for non-browser projects
|
|
140
|
+
- Runtime preflight:
|
|
141
|
+
- Node.js runtime/version check (`>=22`) in node setup
|
|
142
|
+
- DOM matcher extension via `@testing-library/jest-dom/vitest`
|
|
143
|
+
|
|
144
|
+
### Vitest Setup Injection Rules
|
|
145
|
+
|
|
146
|
+
`defineXmVitestConfig(...)` applies setup files predictably:
|
|
147
|
+
|
|
148
|
+
1. It normalizes `test.setupFiles` into an array.
|
|
149
|
+
2. It injects `config/vitest.setup.ts` if missing.
|
|
150
|
+
3. It injects `config/vitest.node.setup.ts` only when `test.browser.enabled !== true`.
|
|
151
|
+
4. It avoids duplicates by checking filenames (suffix match).
|
|
152
|
+
5. Injected setup files run before user-provided setup files.
|
|
153
|
+
|
|
154
|
+
This gives a safe default for Node package tests while keeping browser projects compatible.
|
|
155
|
+
|
|
156
|
+
### Vitest Exports
|
|
157
|
+
|
|
158
|
+
`@xmachines/shared` exports these Vitest entry points:
|
|
159
|
+
|
|
160
|
+
- `@xmachines/shared/vitest` → `config/vitest.ts`
|
|
161
|
+
- `@xmachines/shared/vitest-setup` → `config/vitest.setup.ts`
|
|
162
|
+
- `@xmachines/shared/vitest-node-setup` → `config/vitest.node.setup.ts`
|
|
163
|
+
- `@xmachines/shared/vitest-urlpattern-setup` → `config/vitest.urlpattern.setup.ts`
|
|
164
|
+
|
|
165
|
+
Use `@xmachines/shared/vitest` for package config files; direct setup exports are for advanced customization only.
|
|
166
|
+
|
|
167
|
+
`vitest-urlpattern-setup` must be added explicitly to `setupFiles` in packages that exercise URLPattern-based route matching (e.g. `play-router` and its adapter packages). It mirrors what consumers must do in production on Node < 24 or older browsers.
|
|
168
|
+
|
|
169
|
+
## Configuration Details
|
|
170
|
+
|
|
171
|
+
### TypeScript (`config/tsconfig.json`)
|
|
172
|
+
|
|
173
|
+
- **Target:** ESNext (Node.js 22+)
|
|
174
|
+
- **Module:** NodeNext (ESM with `.js` extensions required)
|
|
175
|
+
- **Strict:** Full strict mode enabled
|
|
176
|
+
- **Output:** Colocated `.d.ts` files with source maps
|
|
177
|
+
- **Composite:** Enabled for project references ()
|
|
178
|
+
|
|
179
|
+
**Important:** You must use `.js` extensions in imports:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { foo } from "./bar.js"; // ✅ Correct
|
|
183
|
+
import { foo } from "./bar"; // ❌ Wrong
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Oxlint (`config/oxlint.json`)
|
|
187
|
+
|
|
188
|
+
- **Plugins:** TypeScript, Unicorn, Import
|
|
189
|
+
- **Categories:** Correctness (error), Suspicious (warn), Performance (warn)
|
|
190
|
+
- **Key Rules:**
|
|
191
|
+
- Circular dependency detection (`import/no-cycle`)
|
|
192
|
+
- Unused variables (allow `_` prefix)
|
|
193
|
+
- TypeScript-specific checks
|
|
194
|
+
|
|
195
|
+
### Oxfmt (`config/oxfmt.json`)
|
|
196
|
+
|
|
197
|
+
- **Line Width:** 100 characters
|
|
198
|
+
- **Indentation:** Tabs (4 spaces)
|
|
199
|
+
- **Style:** Double quotes, semicolons, trailing commas
|
|
200
|
+
- **Final Newline:** Always insert
|
|
201
|
+
|
|
202
|
+
### Vitest (`config/vitest.ts`, `config/vitest.setup.ts`, `config/vitest.node.setup.ts`, `config/vitest.urlpattern.setup.ts`)
|
|
203
|
+
|
|
204
|
+
- **Helper API:** `defineXmVitestConfig(importMetaUrl, overrides)` merges shared defaults with package-specific Vitest config
|
|
205
|
+
- **Auto-aliasing:** `resolve.alias` is wired to `xmAliases(importMetaUrl)` so workspace packages resolve to source
|
|
206
|
+
- **Cross-runtime setup (`config/vitest.setup.ts`):**
|
|
207
|
+
- extends `expect` with `@testing-library/jest-dom/vitest`
|
|
208
|
+
- **URLPattern setup (`config/vitest.urlpattern.setup.ts`):**
|
|
209
|
+
- loads `urlpattern-polyfill` — opt-in per package via `setupFiles: ["@xmachines/shared/vitest-urlpattern-setup"]`
|
|
210
|
+
- required for packages exercising URLPattern-based route matching (play-router and adapters)
|
|
211
|
+
- mirrors what consumers must do in production on Node < 24 or older browsers
|
|
212
|
+
- **Node-only setup (`config/vitest.node.setup.ts`):**
|
|
213
|
+
- asserts Node runtime (`process.release.name === "node"`)
|
|
214
|
+
- enforces Node version `>=22`
|
|
215
|
+
- **Browser behavior:** Browser projects do not receive node-only setup injection
|
|
216
|
+
- **Override model:** Package-level config still controls test environment, includes/excludes, plugins, coverage, and browser provider
|
|
217
|
+
|
|
218
|
+
### Vite Aliases (`config/vite-aliases.ts`)
|
|
219
|
+
|
|
220
|
+
- **Helper API:** `xmAliases(importMetaUrl)` returns a complete `resolve.alias` map for all `@xmachines/*` workspace packages
|
|
221
|
+
- **Root discovery:** Walks upward from `import.meta.url` until it finds the workspace root (`package.json` with `workspaces`)
|
|
222
|
+
- **Source-first resolution:** Maps package imports to `packages/*/src/index.ts` for fast local dev without prebuild
|
|
223
|
+
- **Extra aliases:** Includes `@xmachines/play-router-shared` and `@xmachines/play-router-shared/index.css` for demo shared assets
|
|
224
|
+
- **Primary use cases:** `vite.config.ts`, `vitest` browser configs, and any local tooling relying on Vite resolver semantics
|
|
225
|
+
|
|
226
|
+
### Vite Alias Troubleshooting
|
|
227
|
+
|
|
228
|
+
- **`[xmAliases] Could not find workspace root`**
|
|
229
|
+
- Ensure `xmAliases` receives `import.meta.url` from the calling config file.
|
|
230
|
+
- Ensure the config file lives somewhere under the monorepo root.
|
|
231
|
+
- **Workspace package import resolves to missing `dist/` files**
|
|
232
|
+
- Ensure config includes `resolve.alias: xmAliases(import.meta.url)`.
|
|
233
|
+
- Re-run after cleaning caches when lockfile/deps changed.
|
|
234
|
+
|
|
235
|
+
### Vitest Troubleshooting
|
|
236
|
+
|
|
237
|
+
- **`URLPattern is unavailable after setup`**
|
|
238
|
+
- Ensure `test.setupFiles` is not replacing shared setup unexpectedly.
|
|
239
|
+
- Ensure config is created via `defineXmVitestConfig(...)`.
|
|
240
|
+
- **`Node runtime is required for this Vitest configuration`**
|
|
241
|
+
- This indicates node-only setup loaded in a browser project.
|
|
242
|
+
- Set `test.browser.enabled: true` in that project config.
|
|
243
|
+
- **Workspace import resolution failures (e.g. `@xmachines/play-signals`)**
|
|
244
|
+
- Confirm config uses `defineXmVitestConfig(import.meta.url, ...)` so aliases are injected.
|
|
245
|
+
|
|
246
|
+
## TypeScript Composite Build System
|
|
247
|
+
|
|
248
|
+
This package introduced TypeScript project references for proper build-order management in the monorepo.
|
|
249
|
+
|
|
250
|
+
### What This Means
|
|
251
|
+
|
|
252
|
+
- **Root `tsconfig.json`** coordinates all packages via `references` array
|
|
253
|
+
- **Each package** has `composite: true` enabling incremental builds
|
|
254
|
+
- **Packages with dependencies** declare `references` to their deps
|
|
255
|
+
- **TypeScript builds in correct order automatically** (no manual sequencing needed)
|
|
256
|
+
|
|
257
|
+
### Building
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
npm run build # Root-level tsc --build handles everything
|
|
261
|
+
npm run build -w <pkg> # Automatically builds dependencies first
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
The root build command uses `tsc --build` which:
|
|
265
|
+
|
|
266
|
+
- Understands the dependency graph
|
|
267
|
+
- Builds packages in correct order
|
|
268
|
+
- Only rebuilds changed packages (incremental)
|
|
269
|
+
- Produces `.d.ts.map` files for IDE navigation
|
|
270
|
+
|
|
271
|
+
### Declaration Maps
|
|
272
|
+
|
|
273
|
+
The base config enables **declaration maps** (`declarationMap: true`) for better IDE experience in composite projects.
|
|
274
|
+
|
|
275
|
+
When TypeScript compiles with `declarationMap: true`, it produces `.d.ts.map` files that map type definitions back to source. This enables:
|
|
276
|
+
|
|
277
|
+
- **Go to Definition** jumps to `.ts` source files (not compiled `.d.ts`)
|
|
278
|
+
- **Rename refactoring** works across package boundaries
|
|
279
|
+
- **Error messages** reference source locations
|
|
280
|
+
|
|
281
|
+
**Cost:** Slightly larger `dist/` output (~10% more)
|
|
282
|
+
**Benefit:** Dramatically better IDE experience
|
|
283
|
+
|
|
284
|
+
### Project References
|
|
285
|
+
|
|
286
|
+
Packages with internal dependencies should declare `references` in their `tsconfig.json`:
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"extends": "@xmachines/shared/tsconfig",
|
|
291
|
+
"compilerOptions": {
|
|
292
|
+
"composite": true,
|
|
293
|
+
"rootDir": "./src",
|
|
294
|
+
"outDir": "./dist"
|
|
295
|
+
},
|
|
296
|
+
"references": [{ "path": "../dependency-package" }]
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The root `tsconfig.json` maintains the full package reference graph, enabling TypeScript to:
|
|
301
|
+
|
|
302
|
+
- Build packages in correct dependency order
|
|
303
|
+
- Support incremental builds (only rebuild what changed)
|
|
304
|
+
- Enable cross-package type checking and navigation
|
|
305
|
+
|
|
306
|
+
### Adding New Packages
|
|
307
|
+
|
|
308
|
+
When creating a new package:
|
|
309
|
+
|
|
310
|
+
1. **Add to root tsconfig.json references:**
|
|
311
|
+
|
|
312
|
+
```json
|
|
313
|
+
{
|
|
314
|
+
"references": [{ "path": "./packages/your-new-package" }]
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
2. **Enable composite in package tsconfig.json:**
|
|
319
|
+
|
|
320
|
+
```json
|
|
321
|
+
{
|
|
322
|
+
"extends": "@xmachines/shared/tsconfig",
|
|
323
|
+
"compilerOptions": {
|
|
324
|
+
"composite": true,
|
|
325
|
+
"rootDir": "./src",
|
|
326
|
+
"outDir": "./dist"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
3. **Declare dependencies (if any):**
|
|
332
|
+
```json
|
|
333
|
+
{
|
|
334
|
+
"references": [{ "path": "../dependency-package" }]
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### IDE Benefits
|
|
339
|
+
|
|
340
|
+
With `declarationMap: true` in the base config:
|
|
341
|
+
|
|
342
|
+
- **Go to Definition** jumps to source files (not compiled `.d.ts`)
|
|
343
|
+
- **Real-time errors** across package boundaries
|
|
344
|
+
- **Refactoring works** across the entire monorepo
|
|
345
|
+
|
|
346
|
+
No build required for IDE features - TypeScript understands the source graph directly.
|
|
347
|
+
|
|
348
|
+
See `AGENTS.md` (section: TypeScript Composite Build System) for complete composite build documentation.
|
|
349
|
+
|
|
350
|
+
## Adding New Configurations
|
|
351
|
+
|
|
352
|
+
To add a new shared configuration (e.g., for Jest, Vitest, etc.):
|
|
353
|
+
|
|
354
|
+
1. Create `config/<tool>.json` (or `.js`, `.ts` as appropriate)
|
|
355
|
+
2. Add exports to `package.json` exports field:
|
|
356
|
+
```json
|
|
357
|
+
{
|
|
358
|
+
"exports": {
|
|
359
|
+
"./<tool>": "./config/<tool>.json",
|
|
360
|
+
"./config/<tool>.json": "./config/<tool>.json"
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
3. Document in this README with usage examples
|
|
365
|
+
4. Note whether the tool supports package exports or requires relative paths
|
|
366
|
+
|
|
367
|
+
## License
|
|
368
|
+
|
|
369
|
+
Copyright (c) 2016 [Mikael Karon](mailto:mikael@karon.se). All rights reserved.
|
|
370
|
+
|
|
371
|
+
This work is licensed under the terms of the MIT license.
|
|
372
|
+
For a copy, see <https://opensource.org/licenses/MIT>.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../../node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"plugins": ["typescript", "unicorn", "import"],
|
|
4
|
+
"categories": {
|
|
5
|
+
"correctness": "error",
|
|
6
|
+
"suspicious": "warn",
|
|
7
|
+
"pedantic": "off",
|
|
8
|
+
"perf": "warn",
|
|
9
|
+
"style": "off"
|
|
10
|
+
},
|
|
11
|
+
"rules": {
|
|
12
|
+
"unicorn/filename-case": "off",
|
|
13
|
+
"import/no-cycle": "error",
|
|
14
|
+
"typescript/no-explicit-any": "error",
|
|
15
|
+
"typescript/no-unused-vars": [
|
|
16
|
+
"error",
|
|
17
|
+
{
|
|
18
|
+
"argsIgnorePattern": "^_",
|
|
19
|
+
"varsIgnorePattern": "^_"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"env": {
|
|
24
|
+
"es2025": true,
|
|
25
|
+
"node": true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
/* Language and Environment */
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"lib": ["ESNext"],
|
|
7
|
+
|
|
8
|
+
/* Modules */
|
|
9
|
+
"module": "NodeNext",
|
|
10
|
+
"moduleResolution": "NodeNext",
|
|
11
|
+
|
|
12
|
+
/* Type Checking */
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true,
|
|
17
|
+
"noImplicitReturns": true,
|
|
18
|
+
"noImplicitOverride": true,
|
|
19
|
+
"exactOptionalPropertyTypes": true,
|
|
20
|
+
|
|
21
|
+
/* Emit */
|
|
22
|
+
"declaration": true,
|
|
23
|
+
"declarationMap": true,
|
|
24
|
+
"sourceMap": true,
|
|
25
|
+
"removeComments": false,
|
|
26
|
+
"importHelpers": false,
|
|
27
|
+
|
|
28
|
+
/* Interop Constraints */
|
|
29
|
+
"esModuleInterop": true,
|
|
30
|
+
"allowSyntheticDefaultImports": true,
|
|
31
|
+
"forceConsistentCasingInFileNames": true,
|
|
32
|
+
"isolatedModules": true,
|
|
33
|
+
"verbatimModuleSyntax": true,
|
|
34
|
+
|
|
35
|
+
/* Completeness */
|
|
36
|
+
"skipLibCheck": true
|
|
37
|
+
},
|
|
38
|
+
"include": ["src/**/*"],
|
|
39
|
+
"exclude": ["dist", "node_modules", "**/*.spec.ts", "**/*.test.ts"]
|
|
40
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vite resolve.alias entries for all @xmachines/* workspace packages.
|
|
6
|
+
*
|
|
7
|
+
* Maps every package to its TypeScript source entry so that `npm run dev`
|
|
8
|
+
* works without a prior `npm run build`. Required because `npm run clean`
|
|
9
|
+
* removes all dist/ directories — including transitive dependencies —
|
|
10
|
+
* leaving Vite unable to resolve package.json `exports` that point to dist/.
|
|
11
|
+
*
|
|
12
|
+
* Usage in a demo vite.config.ts:
|
|
13
|
+
*
|
|
14
|
+
* import { xmAliases } from "@xmachines/shared/vite-aliases";
|
|
15
|
+
*
|
|
16
|
+
* export default defineConfig({
|
|
17
|
+
* resolve: { alias: xmAliases(import.meta.url) },
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* @param importMetaUrl - Pass `import.meta.url` from the calling config file.
|
|
21
|
+
* Used to locate the workspace root (the directory containing `packages/`).
|
|
22
|
+
*/
|
|
23
|
+
export function xmAliases(importMetaUrl: string): Record<string, string> {
|
|
24
|
+
const configDir = fileURLToPath(new URL(".", importMetaUrl));
|
|
25
|
+
|
|
26
|
+
// Walk up from the config file's directory until we find the folder that
|
|
27
|
+
// contains a `packages/` subdirectory — that's the workspace root.
|
|
28
|
+
let root = configDir;
|
|
29
|
+
while (!isWorkspaceRoot(root)) {
|
|
30
|
+
const parent = resolve(root, "..");
|
|
31
|
+
if (parent === root)
|
|
32
|
+
throw new Error(`[xmAliases] Could not find workspace root from ${configDir}`);
|
|
33
|
+
root = parent;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const p = (pkg: string) => resolve(root, "packages", pkg, "src/index.ts");
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
"@xmachines/play": p("play"),
|
|
40
|
+
"@xmachines/play-actor": p("play-actor"),
|
|
41
|
+
"@xmachines/play-catalog": p("play-catalog"),
|
|
42
|
+
"@xmachines/play-signals": p("play-signals"),
|
|
43
|
+
"@xmachines/play-router": p("play-router"),
|
|
44
|
+
"@xmachines/play-xstate": p("play-xstate"),
|
|
45
|
+
"@xmachines/play-react": p("play-react"),
|
|
46
|
+
"@xmachines/play-react-router": p("play-react-router"),
|
|
47
|
+
"@xmachines/play-solid": p("play-solid"),
|
|
48
|
+
"@xmachines/play-solid-router": p("play-solid-router"),
|
|
49
|
+
"@xmachines/play-vue": p("play-vue"),
|
|
50
|
+
"@xmachines/play-vue-router": p("play-vue-router"),
|
|
51
|
+
"@xmachines/play-tanstack-react-router": p("play-tanstack-react-router"),
|
|
52
|
+
"@xmachines/play-tanstack-solid-router": p("play-tanstack-solid-router"),
|
|
53
|
+
"@xmachines/play-router-shared/index.css": resolve(
|
|
54
|
+
root,
|
|
55
|
+
"packages",
|
|
56
|
+
"play-router",
|
|
57
|
+
"examples",
|
|
58
|
+
"shared",
|
|
59
|
+
"src/index.css",
|
|
60
|
+
),
|
|
61
|
+
"@xmachines/play-router-shared": resolve(
|
|
62
|
+
root,
|
|
63
|
+
"packages",
|
|
64
|
+
"play-router",
|
|
65
|
+
"examples",
|
|
66
|
+
"shared",
|
|
67
|
+
"src",
|
|
68
|
+
),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
import { existsSync, readFileSync } from "fs";
|
|
73
|
+
|
|
74
|
+
function isWorkspaceRoot(dir: string): boolean {
|
|
75
|
+
const pkgPath = resolve(dir, "package.json");
|
|
76
|
+
if (!existsSync(pkgPath)) return false;
|
|
77
|
+
try {
|
|
78
|
+
const raw = readFileSync(pkgPath, "utf8");
|
|
79
|
+
const pkg = JSON.parse(raw) as { workspaces?: unknown };
|
|
80
|
+
return Array.isArray(pkg.workspaces);
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const isNodeRuntime =
|
|
2
|
+
typeof process !== "undefined" &&
|
|
3
|
+
process.release?.name === "node" &&
|
|
4
|
+
typeof process.versions?.node === "string";
|
|
5
|
+
|
|
6
|
+
if (!isNodeRuntime) {
|
|
7
|
+
throw new Error("Node runtime is required for this Vitest configuration");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const [nodeMajorRaw] = process.versions.node.split(".");
|
|
11
|
+
const nodeMajor = Number.parseInt(nodeMajorRaw ?? "0", 10);
|
|
12
|
+
|
|
13
|
+
if (!Number.isFinite(nodeMajor) || nodeMajor < 22) {
|
|
14
|
+
throw new Error(`Vitest runtime requires Node.js >=22.0.0, received ${process.versions.node}`);
|
|
15
|
+
}
|
package/config/vitest.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { defineConfig, mergeConfig } from "vitest/config";
|
|
4
|
+
import { xmAliases } from "@xmachines/shared/vite-aliases";
|
|
5
|
+
|
|
6
|
+
const configDir = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const nodeSetupFile = resolve(configDir, "vitest.node.setup.ts");
|
|
8
|
+
const setupFile = resolve(configDir, "vitest.setup.ts");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Normalize Vitest `test.setupFiles` into a string array.
|
|
12
|
+
*/
|
|
13
|
+
function normalizeSetupFiles(setupFiles: unknown): string[] {
|
|
14
|
+
if (!setupFiles) return [];
|
|
15
|
+
if (Array.isArray(setupFiles)) {
|
|
16
|
+
return setupFiles.filter((entry): entry is string => typeof entry === "string");
|
|
17
|
+
}
|
|
18
|
+
return typeof setupFiles === "string" ? [setupFiles] : [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* True when Vitest browser mode is explicitly enabled.
|
|
23
|
+
*/
|
|
24
|
+
function isBrowserEnabled(browserConfig: unknown): boolean {
|
|
25
|
+
if (!browserConfig || typeof browserConfig !== "object") return false;
|
|
26
|
+
return (browserConfig as { enabled?: unknown }).enabled === true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Checks whether setup files already include a file by suffix.
|
|
31
|
+
*
|
|
32
|
+
* This supports both absolute and relative setup file entries and normalizes
|
|
33
|
+
* Windows separators for cross-platform matching.
|
|
34
|
+
*/
|
|
35
|
+
function hasSetupFile(setupFiles: string[], fileName: string): boolean {
|
|
36
|
+
const normalizedSuffix = `/${fileName}`;
|
|
37
|
+
return setupFiles.some((entry) => entry.replaceAll("\\", "/").endsWith(normalizedSuffix));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a Vitest config with XMachines workspace defaults.
|
|
42
|
+
*
|
|
43
|
+
* What this applies automatically:
|
|
44
|
+
* - `resolve.alias` from `xmAliases(importMetaUrl)` so `@xmachines/*` imports
|
|
45
|
+
* resolve to source in local/dev test runs.
|
|
46
|
+
* - `config/vitest.setup.ts` when missing.
|
|
47
|
+
* - `config/vitest.node.setup.ts` for non-browser projects when missing.
|
|
48
|
+
*
|
|
49
|
+
* Setup injection is additive and preserves caller-provided ordering after
|
|
50
|
+
* required shared setup files.
|
|
51
|
+
*
|
|
52
|
+
* @param importMetaUrl Pass `import.meta.url` from the calling config file.
|
|
53
|
+
* @param overrides Package/project-specific Vitest config overrides.
|
|
54
|
+
* @returns Merged Vitest configuration object.
|
|
55
|
+
*/
|
|
56
|
+
export function defineXmVitestConfig(importMetaUrl: string, overrides: Record<string, unknown>) {
|
|
57
|
+
const merged = mergeConfig(
|
|
58
|
+
defineConfig({
|
|
59
|
+
resolve: {
|
|
60
|
+
alias: xmAliases(importMetaUrl),
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
defineConfig(overrides),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const testConfig = merged.test ?? {};
|
|
67
|
+
const setupFiles = normalizeSetupFiles(testConfig.setupFiles);
|
|
68
|
+
const browserEnabled = isBrowserEnabled(testConfig.browser);
|
|
69
|
+
const hasNodeSetup = hasSetupFile(setupFiles, "vitest.node.setup.ts");
|
|
70
|
+
const hasSharedSetup = hasSetupFile(setupFiles, "vitest.setup.ts");
|
|
71
|
+
const injectedSetupFiles: string[] = [];
|
|
72
|
+
|
|
73
|
+
if (!browserEnabled && !hasNodeSetup) {
|
|
74
|
+
injectedSetupFiles.push(nodeSetupFile);
|
|
75
|
+
}
|
|
76
|
+
if (!hasSharedSetup) {
|
|
77
|
+
injectedSetupFiles.push(setupFile);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
...merged,
|
|
82
|
+
test: {
|
|
83
|
+
...testConfig,
|
|
84
|
+
setupFiles: [...injectedSetupFiles, ...setupFiles],
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional Vitest setup file for packages that use URLPattern (via @xmachines/play-router).
|
|
3
|
+
*
|
|
4
|
+
* Include this in `setupFiles` for packages that exercise route matching in tests:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* // vitest.config.ts
|
|
8
|
+
* import { defineXmVitestConfig } from "@xmachines/shared/vitest";
|
|
9
|
+
* export default defineXmVitestConfig(import.meta.url, {
|
|
10
|
+
* test: {
|
|
11
|
+
* setupFiles: ["@xmachines/shared/vitest-urlpattern-setup"],
|
|
12
|
+
* },
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* This mirrors what consumers must do in production on Node < 24 or older browsers:
|
|
17
|
+
* load a URLPattern polyfill before importing @xmachines/play-router.
|
|
18
|
+
*
|
|
19
|
+
* @internal — test infrastructure only, not part of the public API
|
|
20
|
+
*/
|
|
21
|
+
// eslint-disable-next-line import/no-unassigned-import
|
|
22
|
+
import "urlpattern-polyfill";
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xmachines/shared",
|
|
3
|
+
"version": "1.0.0-beta.10",
|
|
4
|
+
"description": "Shared configurations for XMachines packages",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"config",
|
|
7
|
+
"shared",
|
|
8
|
+
"xmachines"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://gitlab.com/xmachin-es/xmachines-js/tree/main/packages/shared",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "Mikael Karon <mikael@karon.se>",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+ssh://git@gitlab.com/xmachin-es/xmachines-js.git",
|
|
16
|
+
"directory": "packages/shared"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"config",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"exports": {
|
|
24
|
+
"./tsconfig": "./config/tsconfig.json",
|
|
25
|
+
"./oxlint": "./config/oxlint.json",
|
|
26
|
+
"./oxfmt": "./config/oxfmt.json",
|
|
27
|
+
"./vite-aliases": "./config/vite-aliases.ts",
|
|
28
|
+
"./vitest": "./config/vitest.ts",
|
|
29
|
+
"./vitest-node-setup": "./config/vitest.node.setup.ts",
|
|
30
|
+
"./vitest-setup": "./config/vitest.setup.ts",
|
|
31
|
+
"./vitest-urlpattern-setup": "./config/vitest.urlpattern.setup.ts",
|
|
32
|
+
"./config/tsconfig.json": "./config/tsconfig.json",
|
|
33
|
+
"./config/oxlint.json": "./config/oxlint.json",
|
|
34
|
+
"./config/oxfmt.json": "./config/oxfmt.json",
|
|
35
|
+
"./config/vitest.ts": "./config/vitest.ts",
|
|
36
|
+
"./config/vitest.node.setup.ts": "./config/vitest.node.setup.ts",
|
|
37
|
+
"./config/vitest.setup.ts": "./config/vitest.setup.ts",
|
|
38
|
+
"./config/vitest.urlpattern.setup.ts": "./config/vitest.urlpattern.setup.ts"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"clean": "rm -rf dist coverage *.tsbuildinfo node_modules/.vite node_modules/.vite-temp",
|
|
45
|
+
"lint": "oxlint .",
|
|
46
|
+
"lint:fix": "oxlint --fix .",
|
|
47
|
+
"format": "oxfmt .",
|
|
48
|
+
"format:check": "oxfmt --check .",
|
|
49
|
+
"test": "vitest"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
53
|
+
"urlpattern-polyfill": "^10.1.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"vitest": "^4.1.0"
|
|
57
|
+
}
|
|
58
|
+
}
|