@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 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,10 @@
1
+ {
2
+ "$schema": "../../../node_modules/oxfmt/configuration_schema.json",
3
+ "printWidth": 100,
4
+ "tabWidth": 4,
5
+ "useTabs": true,
6
+ "semi": true,
7
+ "singleQuote": false,
8
+ "trailingComma": "all",
9
+ "insertFinalNewline": true
10
+ }
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Vitest setup shared across packages.
3
+ * Extends matchers with @testing-library/jest-dom.
4
+ * @internal
5
+ */
6
+ // eslint-disable-next-line import/no-unassigned-import
7
+ import "@testing-library/jest-dom/vitest";
@@ -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
+ }