@aamini/config 0.0.20 → 0.2.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.
Files changed (56) hide show
  1. package/README.md +111 -20
  2. package/dist/anti-slop/index.d.mts +6 -0
  3. package/dist/anti-slop/index.mjs +2414 -0
  4. package/dist/anti-slop/index.mjs.map +1 -0
  5. package/dist/merge-BEomHgbw.d.mts +21 -0
  6. package/dist/merge-DThKwm_6.mjs +193 -0
  7. package/dist/merge-DThKwm_6.mjs.map +1 -0
  8. package/dist/playwright.d.mts +8 -3
  9. package/dist/playwright.mjs +20 -11
  10. package/dist/playwright.mjs.map +1 -1
  11. package/dist/root-U5B4x0aj.mjs +15 -0
  12. package/dist/root-U5B4x0aj.mjs.map +1 -0
  13. package/dist/tanstack/index.d.mts +45 -0
  14. package/dist/tanstack/index.mjs +189 -0
  15. package/dist/tanstack/index.mjs.map +1 -0
  16. package/dist/vite-plus/index.d.mts +101 -0
  17. package/dist/vite-plus/index.mjs +2 -0
  18. package/dist/vitest/browser.d.mts +22 -0
  19. package/dist/vitest/browser.mjs +84 -0
  20. package/dist/vitest/browser.mjs.map +1 -0
  21. package/dist/vitest/db.d.mts +28 -0
  22. package/dist/vitest/db.mjs +42 -0
  23. package/dist/vitest/db.mjs.map +1 -0
  24. package/dist/vitest/index.d.mts +29 -0
  25. package/dist/vitest/index.mjs +2 -0
  26. package/dist/vitest/react.d.mts +34 -0
  27. package/dist/vitest/react.mjs +43 -0
  28. package/dist/vitest/react.mjs.map +1 -0
  29. package/dist/vitest/server.d.mts +13 -0
  30. package/dist/vitest/server.mjs +28 -0
  31. package/dist/vitest/server.mjs.map +1 -0
  32. package/dist/vitest-CXdvqyRH.mjs +169 -0
  33. package/dist/vitest-CXdvqyRH.mjs.map +1 -0
  34. package/package.json +169 -95
  35. package/tsconfig/astro.json +9 -0
  36. package/tsconfig/{tsconfig.strict.json → base.json} +8 -0
  37. package/tsconfig/react.json +9 -0
  38. package/dist/setup/styles.d.mts +0 -1
  39. package/dist/setup/styles.mjs +0 -2
  40. package/dist/test/browser.d.mts +0 -17
  41. package/dist/test/browser.mjs +0 -35
  42. package/dist/test/browser.mjs.map +0 -1
  43. package/dist/test/db.d.mts +0 -41
  44. package/dist/test/db.mjs +0 -93
  45. package/dist/test/db.mjs.map +0 -1
  46. package/dist/test/server.d.mts +0 -16
  47. package/dist/test/server.mjs +0 -30
  48. package/dist/test/server.mjs.map +0 -1
  49. package/dist/test/visual-page.d.mts +0 -30
  50. package/dist/test/visual-page.mjs +0 -40
  51. package/dist/test/visual-page.mjs.map +0 -1
  52. package/dist/vite.d.mts +0 -73
  53. package/dist/vite.mjs +0 -211
  54. package/dist/vite.mjs.map +0 -1
  55. package/tsconfig/tsconfig.lib.json +0 -19
  56. package/tsconfig/tsconfig.tanstack.json +0 -17
@@ -0,0 +1,193 @@
1
+ import { mergeConfig } from "vite-plus";
2
+ //#region src/vite-plus/fmt.ts
3
+ const fmt = {
4
+ singleQuote: true,
5
+ semi: false,
6
+ useTabs: true,
7
+ experimentalTailwindcss: {},
8
+ experimentalSortImports: {},
9
+ printWidth: 80,
10
+ experimentalSortPackageJson: false,
11
+ proseWrap: "always",
12
+ ignorePatterns: [
13
+ "**/.output",
14
+ "**/.vite",
15
+ "**/dist/**",
16
+ "pnpm-lock.yaml",
17
+ "env.d.ts",
18
+ "**/routeTree.gen.ts"
19
+ ],
20
+ overrides: [{
21
+ files: ["*.{yaml,yml}"],
22
+ options: { useTabs: false }
23
+ }]
24
+ };
25
+ //#endregion
26
+ //#region src/vite-plus/lint.ts
27
+ const basePlugins = [
28
+ "eslint",
29
+ "unicorn",
30
+ "typescript",
31
+ "oxc",
32
+ "import",
33
+ "jsdoc",
34
+ "node",
35
+ "promise"
36
+ ];
37
+ const frameworkPlugins = {
38
+ react: [
39
+ "react",
40
+ "react-perf",
41
+ "jsx-a11y"
42
+ ],
43
+ astro: ["jsx-a11y"],
44
+ node: []
45
+ };
46
+ const antiSlopRules = {
47
+ "anti-slop/no-array-filter-map": "error",
48
+ "anti-slop/no-reduce-accumulator-copy": "error",
49
+ "anti-slop/no-chained-type-assertions": "error",
50
+ "anti-slop/no-conditional-empty-object-spread": "error",
51
+ "anti-slop/no-known-value-widening": "error",
52
+ "anti-slop/no-object-parameters": "error",
53
+ "anti-slop/no-reflect-apply": "error",
54
+ "anti-slop/no-reflect-get": "error",
55
+ "anti-slop/no-runtime-typeof": "error",
56
+ "anti-slop/no-shape-in-symbol-names": "error",
57
+ "anti-slop/no-unknown-parameters": "error",
58
+ "anti-slop/no-unknown-returns": "error",
59
+ "anti-slop/no-unknown-type-aliases": "error",
60
+ "anti-slop/no-unsafe-dictionary-type": "error",
61
+ "anti-slop/no-widen-then-assert": "error",
62
+ "anti-slop/require-readable-spacing": "error",
63
+ "anti-slop/require-safety-comment-for-type-assertion": "error"
64
+ };
65
+ const frameworkRules = {
66
+ react: { "eslint-js/no-restricted-syntax": ["error", {
67
+ selector: "JSXAttribute[name.name=\"className\"] TemplateLiteral",
68
+ message: "Do not build className with template literals. Use cn() from \"cn\" instead."
69
+ }] },
70
+ astro: {},
71
+ node: {}
72
+ };
73
+ const browserSettings = {
74
+ "jsx-a11y": {
75
+ components: {},
76
+ attributes: {}
77
+ },
78
+ react: {
79
+ formComponents: [],
80
+ linkComponents: []
81
+ }
82
+ };
83
+ const frameworkSettings = {
84
+ react: browserSettings,
85
+ astro: browserSettings,
86
+ node: {}
87
+ };
88
+ /** Oxlint preset for one framework. Every app shares the base and anti-slop rules. */
89
+ function lint(framework) {
90
+ return {
91
+ plugins: [...basePlugins, ...frameworkPlugins[framework]],
92
+ jsPlugins: [{
93
+ name: "eslint-js",
94
+ specifier: "oxlint-plugin-eslint"
95
+ }, {
96
+ name: "anti-slop",
97
+ specifier: "@aamini/config/anti-slop"
98
+ }],
99
+ categories: {},
100
+ options: {
101
+ maxWarnings: 0,
102
+ typeAware: true,
103
+ typeCheck: true
104
+ },
105
+ rules: {
106
+ "no-empty-pattern": "off",
107
+ "no-console": ["error", { allow: ["warn", "error"] }],
108
+ "typescript/consistent-type-assertions": ["error", { assertionStyle: "never" }],
109
+ "oxc/no-accumulating-spread": "error",
110
+ ...antiSlopRules,
111
+ ...frameworkRules[framework]
112
+ },
113
+ overrides: [{
114
+ files: [
115
+ "mise-tasks/**",
116
+ "scripts/**",
117
+ "**/*.server.ts"
118
+ ],
119
+ rules: { "no-console": "off" }
120
+ }],
121
+ settings: {
122
+ ...frameworkSettings[framework],
123
+ jsdoc: {
124
+ ignorePrivate: false,
125
+ ignoreInternal: false,
126
+ ignoreReplacesDocs: true,
127
+ overrideReplacesDocs: true,
128
+ augmentsExtendsReplacesDocs: false,
129
+ implementsReplacesDocs: false,
130
+ exemptDestructuredRootsFromChecks: false,
131
+ tagNamePreference: {}
132
+ }
133
+ },
134
+ env: { builtin: true },
135
+ globals: {},
136
+ ignorePatterns: ["**/dist/**", "**/.output/**"]
137
+ };
138
+ }
139
+ //#endregion
140
+ //#region src/vite-plus/merge.ts
141
+ function isConfigNode(value) {
142
+ if (value === null || Array.isArray(value)) return false;
143
+ return Object.getPrototypeOf(value) === Object.prototype;
144
+ }
145
+ function isReplacer(value) {
146
+ return value instanceof Function;
147
+ }
148
+ /**
149
+ * Applies the replacer functions in `override` to `preset`. Returns the
150
+ * updated preset and the override without the applied replacers.
151
+ *
152
+ * A function counts as a replacer only when the preset holds a non-function
153
+ * value at the same path. Other functions, for example a new browser command
154
+ * or a replacement for a preset command, stay plain values for `mergeConfig`.
155
+ */
156
+ function applyReplacers(preset, override, path) {
157
+ const nextPreset = { ...preset };
158
+ const rest = {};
159
+ for (const [key, value] of Object.entries(override)) {
160
+ const current = preset[key];
161
+ if (isReplacer(value) && current !== void 0 && !isReplacer(current)) {
162
+ nextPreset[key] = value(current);
163
+ continue;
164
+ }
165
+ if (path.at(-1) === "rules" && Array.isArray(value)) {
166
+ nextPreset[key] = value;
167
+ continue;
168
+ }
169
+ if (isConfigNode(value) && current !== void 0 && isConfigNode(current)) {
170
+ const [childPreset, childRest] = applyReplacers(current, value, [...path, key]);
171
+ nextPreset[key] = childPreset;
172
+ rest[key] = childRest;
173
+ continue;
174
+ }
175
+ rest[key] = value;
176
+ }
177
+ return [nextPreset, rest];
178
+ }
179
+ /**
180
+ * Merges an app override into a toolkit preset. Replacer functions run
181
+ * first. `mergeConfig` from Vite+ merges the rest: objects merge deep, arrays
182
+ * append, and scalars replace.
183
+ */
184
+ function mergeToolkitConfig(preset, override = {}) {
185
+ if (override instanceof Function) return override(preset);
186
+ if (!isConfigNode(preset) || !isConfigNode(override)) throw new TypeError("Toolkit config must be a plain object");
187
+ const [nextPreset, rest] = applyReplacers(preset, override, []);
188
+ return mergeConfig(nextPreset, rest);
189
+ }
190
+ //#endregion
191
+ export { lint as n, fmt as r, mergeToolkitConfig as t };
192
+
193
+ //# sourceMappingURL=merge-DThKwm_6.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-DThKwm_6.mjs","names":[],"sources":["../src/vite-plus/fmt.ts","../src/vite-plus/lint.ts","../src/vite-plus/merge.ts"],"sourcesContent":["import type { UserConfig } from 'vite-plus'\n\nexport const fmt = {\n\tsingleQuote: true,\n\tsemi: false,\n\tuseTabs: true,\n\texperimentalTailwindcss: {},\n\texperimentalSortImports: {},\n\tprintWidth: 80,\n\texperimentalSortPackageJson: false,\n\tproseWrap: 'always',\n\tignorePatterns: [\n\t\t'**/.output',\n\t\t'**/.vite',\n\t\t'**/dist/**',\n\t\t'pnpm-lock.yaml',\n\t\t'env.d.ts',\n\t\t'**/routeTree.gen.ts',\n\t],\n\toverrides: [\n\t\t{\n\t\t\tfiles: ['*.{yaml,yml}'],\n\t\t\toptions: { useTabs: false },\n\t\t},\n\t],\n} satisfies UserConfig['fmt']\n","import type { UserConfig } from 'vite-plus'\n\ntype LintConfig = NonNullable<UserConfig['lint']>\n\nexport type Framework = 'react' | 'astro' | 'node'\n\nconst basePlugins = [\n\t'eslint',\n\t'unicorn',\n\t'typescript',\n\t'oxc',\n\t'import',\n\t'jsdoc',\n\t'node',\n\t'promise',\n] satisfies LintConfig['plugins']\n\nconst frameworkPlugins = {\n\treact: ['react', 'react-perf', 'jsx-a11y'],\n\tastro: ['jsx-a11y'],\n\tnode: [],\n} satisfies Record<Framework, LintConfig['plugins']>\n\nconst antiSlopRules = {\n\t'anti-slop/no-array-filter-map': 'error',\n\t'anti-slop/no-reduce-accumulator-copy': 'error',\n\t'anti-slop/no-chained-type-assertions': 'error',\n\t'anti-slop/no-conditional-empty-object-spread': 'error',\n\t'anti-slop/no-known-value-widening': 'error',\n\t'anti-slop/no-object-parameters': 'error',\n\t'anti-slop/no-reflect-apply': 'error',\n\t'anti-slop/no-reflect-get': 'error',\n\t'anti-slop/no-runtime-typeof': 'error',\n\t'anti-slop/no-shape-in-symbol-names': 'error',\n\t'anti-slop/no-unknown-parameters': 'error',\n\t'anti-slop/no-unknown-returns': 'error',\n\t'anti-slop/no-unknown-type-aliases': 'error',\n\t'anti-slop/no-unsafe-dictionary-type': 'error',\n\t'anti-slop/no-widen-then-assert': 'error',\n\t'anti-slop/require-readable-spacing': 'error',\n\t'anti-slop/require-safety-comment-for-type-assertion': 'error',\n} satisfies LintConfig['rules']\n\nconst reactRules = {\n\t'eslint-js/no-restricted-syntax': [\n\t\t'error',\n\t\t{\n\t\t\tselector: 'JSXAttribute[name.name=\"className\"] TemplateLiteral',\n\t\t\tmessage:\n\t\t\t\t'Do not build className with template literals. Use cn() from \"cn\" instead.',\n\t\t},\n\t],\n} satisfies LintConfig['rules']\n\nconst frameworkRules = {\n\treact: reactRules,\n\tastro: {},\n\tnode: {},\n} satisfies Record<Framework, LintConfig['rules']>\n\nconst browserSettings = {\n\t'jsx-a11y': { components: {}, attributes: {} },\n\treact: { formComponents: [], linkComponents: [] },\n}\n\nconst frameworkSettings = {\n\treact: browserSettings,\n\tastro: browserSettings,\n\tnode: {},\n} satisfies Record<Framework, LintConfig['settings']>\n\n/** Oxlint preset for one framework. Every app shares the base and anti-slop rules. */\nexport function lint(framework: Framework) {\n\treturn {\n\t\tplugins: [...basePlugins, ...frameworkPlugins[framework]],\n\t\tjsPlugins: [\n\t\t\t{ name: 'eslint-js', specifier: 'oxlint-plugin-eslint' },\n\t\t\t{ name: 'anti-slop', specifier: '@aamini/config/anti-slop' },\n\t\t],\n\t\tcategories: {},\n\t\toptions: {\n\t\t\tmaxWarnings: 0,\n\t\t\ttypeAware: true,\n\t\t\ttypeCheck: true,\n\t\t},\n\t\trules: {\n\t\t\t'no-empty-pattern': 'off',\n\t\t\t'no-console': ['error', { allow: ['warn', 'error'] }],\n\t\t\t'typescript/consistent-type-assertions': [\n\t\t\t\t'error',\n\t\t\t\t{ assertionStyle: 'never' },\n\t\t\t],\n\t\t\t'oxc/no-accumulating-spread': 'error',\n\t\t\t...antiSlopRules,\n\t\t\t...frameworkRules[framework],\n\t\t},\n\t\toverrides: [\n\t\t\t{\n\t\t\t\tfiles: ['mise-tasks/**', 'scripts/**', '**/*.server.ts'],\n\t\t\t\trules: { 'no-console': 'off' },\n\t\t\t},\n\t\t],\n\t\tsettings: {\n\t\t\t...frameworkSettings[framework],\n\t\t\tjsdoc: {\n\t\t\t\tignorePrivate: false,\n\t\t\t\tignoreInternal: false,\n\t\t\t\tignoreReplacesDocs: true,\n\t\t\t\toverrideReplacesDocs: true,\n\t\t\t\taugmentsExtendsReplacesDocs: false,\n\t\t\t\timplementsReplacesDocs: false,\n\t\t\t\texemptDestructuredRootsFromChecks: false,\n\t\t\t\ttagNamePreference: {},\n\t\t\t},\n\t\t},\n\t\tenv: { builtin: true },\n\t\tglobals: {},\n\t\tignorePatterns: ['**/dist/**', '**/.output/**'],\n\t} satisfies LintConfig\n}\n","import { mergeConfig, type UserConfig } from 'vite-plus'\n\n// Any function type is assignable to this signature, whatever it returns.\ntype AnyFunction = (...args: never[]) => void\n\ntype Replacer<T> = (preset: T) => T\n\ntype OverrideValue<T> = T extends AnyFunction\n\t? T\n\t: T extends readonly unknown[]\n\t\t? T\n\t\t: T extends object\n\t\t\t? { [K in keyof T]?: ConfigOverride<T[K]> }\n\t\t\t: T\n\n/**\n * A partial override of `T`. Any value that the preset sets can also be a\n * function that receives the preset value and returns its replacement.\n */\nexport type ConfigOverride<T> = [NonNullable<T>] extends [AnyFunction]\n\t? T\n\t: OverrideValue<NonNullable<T>> | Replacer<NonNullable<T>>\n\n/** App-owned Vite+ config. The toolkit merges it over the preset. */\nexport type ToolkitConfig = OverrideValue<UserConfig>\n\ntype ConfigNode = { [key: string]: ConfigValue }\n\ntype ConfigValue =\n\t| ConfigNode\n\t| readonly ConfigValue[]\n\t| string\n\t| number\n\t| boolean\n\t| bigint\n\t| symbol\n\t| null\n\t| undefined\n\t| ((...args: never[]) => ConfigValue)\n\nfunction isConfigNode(\n\tvalue: ConfigValue | UserConfig | ToolkitConfig | Replacer<UserConfig>,\n): value is ConfigNode {\n\tif (value === null || Array.isArray(value)) return false\n\n\treturn Object.getPrototypeOf(value) === Object.prototype\n}\n\nfunction isReplacer(\n\tvalue: ConfigValue,\n): value is (preset: ConfigValue) => ConfigValue {\n\treturn value instanceof Function\n}\n\n/**\n * Applies the replacer functions in `override` to `preset`. Returns the\n * updated preset and the override without the applied replacers.\n *\n * A function counts as a replacer only when the preset holds a non-function\n * value at the same path. Other functions, for example a new browser command\n * or a replacement for a preset command, stay plain values for `mergeConfig`.\n */\nfunction applyReplacers(\n\tpreset: ConfigNode,\n\toverride: ConfigNode,\n\tpath: readonly string[],\n): [ConfigNode, ConfigNode] {\n\tconst nextPreset = { ...preset }\n\tconst rest: ConfigNode = {}\n\n\tfor (const [key, value] of Object.entries(override)) {\n\t\tconst current = preset[key]\n\n\t\tif (isReplacer(value) && current !== undefined && !isReplacer(current)) {\n\t\t\tnextPreset[key] = value(current)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Oxlint rule entries are tuples such as ['error', options]. Array\n\t\t// concatenation from mergeConfig would corrupt them, so they replace.\n\t\tif (path.at(-1) === 'rules' && Array.isArray(value)) {\n\t\t\tnextPreset[key] = value\n\t\t\tcontinue\n\t\t}\n\n\t\tif (isConfigNode(value) && current !== undefined && isConfigNode(current)) {\n\t\t\tconst [childPreset, childRest] = applyReplacers(current, value, [\n\t\t\t\t...path,\n\t\t\t\tkey,\n\t\t\t])\n\n\t\t\tnextPreset[key] = childPreset\n\t\t\trest[key] = childRest\n\t\t\tcontinue\n\t\t}\n\n\t\trest[key] = value\n\t}\n\n\treturn [nextPreset, rest]\n}\n\n/**\n * Merges an app override into a toolkit preset. Replacer functions run\n * first. `mergeConfig` from Vite+ merges the rest: objects merge deep, arrays\n * append, and scalars replace.\n */\nexport function mergeToolkitConfig(\n\tpreset: UserConfig,\n\toverride: ToolkitConfig | Replacer<UserConfig> = {},\n): UserConfig {\n\tif (override instanceof Function) return override(preset)\n\n\tif (!isConfigNode(preset) || !isConfigNode(override)) {\n\t\tthrow new TypeError('Toolkit config must be a plain object')\n\t}\n\n\tconst [nextPreset, rest] = applyReplacers(preset, override, [])\n\n\treturn mergeConfig(nextPreset, rest)\n}\n"],"mappings":"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"}
@@ -1,7 +1,12 @@
1
1
  import { PlaywrightTestConfig } from "@playwright/test";
2
-
3
2
  //#region src/playwright.d.ts
4
- declare const baseConfig: (overrides?: PlaywrightTestConfig) => Promise<PlaywrightTestConfig<{}, {}>>;
3
+ export interface PlaywrightOptions {
4
+ testDir: string;
5
+ /** Default: `BASE_URL`, then the local app on `APP_PORT` (3000). */
6
+ baseURL?: string;
7
+ webServer?: PlaywrightTestConfig['webServer'];
8
+ }
9
+ /** Smoke tests against a running app. Runs against any `BASE_URL`. */
10
+ export declare function playwrightConfig(options: PlaywrightOptions): PlaywrightTestConfig<{}, {}>;
5
11
  //#endregion
6
- export { baseConfig };
7
12
  //# sourceMappingURL=playwright.d.mts.map
@@ -1,20 +1,27 @@
1
1
  import { defineConfig, devices } from "@playwright/test";
2
2
  //#region src/playwright.ts
3
- const baseConfig = async (overrides) => {
4
- return defineConfig({
5
- testDir: "./e2e",
3
+ function defaultBaseURL() {
4
+ return process.env.BASE_URL?.trim() || `http://localhost:${process.env.APP_PORT ?? "3000"}`;
5
+ }
6
+ const launchOptions = { args: ["--disable-lcd-text", "--headless=new"] };
7
+ /** Smoke tests against a running app. Runs against any `BASE_URL`. */
8
+ function playwrightConfig(options) {
9
+ const baseURL = options.baseURL ?? defaultBaseURL();
10
+ const config = {
11
+ testDir: options.testDir,
6
12
  outputDir: ".playwright/test-results",
7
13
  fullyParallel: true,
8
14
  workers: process.env.CI ? 1 : "50%",
9
- forbidOnly: !!process.env.CI,
15
+ forbidOnly: Boolean(process.env.CI),
10
16
  retries: process.env.CI ? 3 : 0,
11
17
  reporter: [["html", {
12
18
  open: "never",
13
19
  outputFolder: ".playwright/report"
14
20
  }]],
15
21
  use: {
16
- colorScheme: "dark",
17
- baseURL: process.env.BASE_URL,
22
+ baseURL,
23
+ channel: "chromium",
24
+ ignoreHTTPSErrors: !process.env.CI && baseURL.startsWith("https://"),
18
25
  trace: "retain-on-first-failure",
19
26
  screenshot: "on",
20
27
  video: "retain-on-failure"
@@ -25,7 +32,7 @@ const baseConfig = async (overrides) => {
25
32
  name: "chromium",
26
33
  use: {
27
34
  ...devices["Desktop Chrome"],
28
- launchOptions: { args: ["--disable-lcd-text"] }
35
+ launchOptions
29
36
  }
30
37
  }, {
31
38
  name: "mobile",
@@ -35,12 +42,14 @@ const baseConfig = async (overrides) => {
35
42
  width: 320,
36
43
  height: 800
37
44
  },
38
- launchOptions: { args: ["--disable-lcd-text"] }
45
+ launchOptions
39
46
  }
40
47
  }]
41
- }, overrides ?? {});
42
- };
48
+ };
49
+ if (options.webServer) config.webServer = options.webServer;
50
+ return defineConfig(config);
51
+ }
43
52
  //#endregion
44
- export { baseConfig };
53
+ export { playwrightConfig };
45
54
 
46
55
  //# sourceMappingURL=playwright.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"playwright.mjs","names":[],"sources":["../src/playwright.ts"],"sourcesContent":["import {\n\tdefineConfig,\n\tdevices,\n\ttype PlaywrightTestConfig,\n} from '@playwright/test'\n\nexport const baseConfig = async (overrides?: PlaywrightTestConfig) => {\n\treturn defineConfig(\n\t\t{\n\t\t\ttestDir: './e2e',\n\t\t\toutputDir: '.playwright/test-results',\n\t\t\tfullyParallel: true,\n\t\t\tworkers: process.env.CI ? 1 : '50%',\n\t\t\tforbidOnly: !!process.env.CI,\n\t\t\tretries: process.env.CI ? 3 : 0,\n\t\t\treporter: [\n\t\t\t\t['html', { open: 'never', outputFolder: '.playwright/report' }],\n\t\t\t],\n\t\t\tuse: {\n\t\t\t\tcolorScheme: 'dark',\n\t\t\t\tbaseURL: process.env.BASE_URL,\n\t\t\t\ttrace: 'retain-on-first-failure',\n\t\t\t\tscreenshot: 'on',\n\t\t\t\tvideo: 'retain-on-failure',\n\t\t\t},\n\t\t\ttimeout: 15_000,\n\t\t\texpect: { timeout: 5_000 },\n\t\t\tprojects: [\n\t\t\t\t{\n\t\t\t\t\tname: 'chromium',\n\t\t\t\t\tuse: {\n\t\t\t\t\t\t...devices['Desktop Chrome'],\n\t\t\t\t\t\tlaunchOptions: { args: ['--disable-lcd-text'] },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'mobile',\n\t\t\t\t\tuse: {\n\t\t\t\t\t\t...devices['Desktop Chrome'],\n\t\t\t\t\t\tviewport: { width: 320, height: 800 },\n\t\t\t\t\t\tlaunchOptions: { args: ['--disable-lcd-text'] },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\toverrides ?? {},\n\t)\n}\n"],"mappings":";;AAMA,MAAa,aAAa,OAAO,cAAqC;CACrE,OAAO,aACN;EACC,SAAS;EACT,WAAW;EACX,eAAe;EACf,SAAS,QAAQ,IAAI,KAAK,IAAI;EAC9B,YAAY,CAAC,CAAC,QAAQ,IAAI;EAC1B,SAAS,QAAQ,IAAI,KAAK,IAAI;EAC9B,UAAU,CACT,CAAC,QAAQ;GAAE,MAAM;GAAS,cAAc;GAAsB,CAAC,CAC/D;EACD,KAAK;GACJ,aAAa;GACb,SAAS,QAAQ,IAAI;GACrB,OAAO;GACP,YAAY;GACZ,OAAO;GACP;EACD,SAAS;EACT,QAAQ,EAAE,SAAS,KAAO;EAC1B,UAAU,CACT;GACC,MAAM;GACN,KAAK;IACJ,GAAG,QAAQ;IACX,eAAe,EAAE,MAAM,CAAC,qBAAqB,EAAE;IAC/C;GACD,EACD;GACC,MAAM;GACN,KAAK;IACJ,GAAG,QAAQ;IACX,UAAU;KAAE,OAAO;KAAK,QAAQ;KAAK;IACrC,eAAe,EAAE,MAAM,CAAC,qBAAqB,EAAE;IAC/C;GACD,CACD;EACD,EACD,aAAa,EAAE,CACf"}
1
+ {"version":3,"file":"playwright.mjs","names":[],"sources":["../src/playwright.ts"],"sourcesContent":["import {\n\tdefineConfig,\n\tdevices,\n\ttype PlaywrightTestConfig,\n} from '@playwright/test'\n\nexport interface PlaywrightOptions {\n\ttestDir: string\n\t/** Default: `BASE_URL`, then the local app on `APP_PORT` (3000). */\n\tbaseURL?: string\n\twebServer?: PlaywrightTestConfig['webServer']\n}\n\nfunction defaultBaseURL() {\n\treturn (\n\t\tprocess.env.BASE_URL?.trim() ||\n\t\t`http://localhost:${process.env.APP_PORT ?? '3000'}`\n\t)\n}\n\nconst launchOptions = { args: ['--disable-lcd-text', '--headless=new'] }\n\n/** Smoke tests against a running app. Runs against any `BASE_URL`. */\nexport function playwrightConfig(options: PlaywrightOptions) {\n\tconst baseURL = options.baseURL ?? defaultBaseURL()\n\n\tconst config: PlaywrightTestConfig = {\n\t\ttestDir: options.testDir,\n\t\toutputDir: '.playwright/test-results',\n\t\tfullyParallel: true,\n\t\tworkers: process.env.CI ? 1 : '50%',\n\t\tforbidOnly: Boolean(process.env.CI),\n\t\tretries: process.env.CI ? 3 : 0,\n\t\treporter: [['html', { open: 'never', outputFolder: '.playwright/report' }]],\n\t\tuse: {\n\t\t\tbaseURL,\n\t\t\tchannel: 'chromium',\n\t\t\tignoreHTTPSErrors: !process.env.CI && baseURL.startsWith('https://'),\n\t\t\ttrace: 'retain-on-first-failure',\n\t\t\tscreenshot: 'on',\n\t\t\tvideo: 'retain-on-failure',\n\t\t},\n\t\ttimeout: 15_000,\n\t\texpect: { timeout: 5_000 },\n\t\tprojects: [\n\t\t\t{\n\t\t\t\tname: 'chromium',\n\t\t\t\tuse: { ...devices['Desktop Chrome'], launchOptions },\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'mobile',\n\t\t\t\tuse: {\n\t\t\t\t\t...devices['Desktop Chrome'],\n\t\t\t\t\tviewport: { width: 320, height: 800 },\n\t\t\t\t\tlaunchOptions,\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t}\n\n\tif (options.webServer) config.webServer = options.webServer\n\n\treturn defineConfig(config)\n}\n"],"mappings":";;AAaA,SAAS,iBAAiB;CACzB,OACC,QAAQ,IAAI,UAAU,KAAK,KAC3B,oBAAoB,QAAQ,IAAI,YAAY;AAE9C;AAEA,MAAM,gBAAgB,EAAE,MAAM,CAAC,sBAAsB,gBAAgB,EAAE;;AAGvE,SAAgB,iBAAiB,SAA4B;CAC5D,MAAM,UAAU,QAAQ,WAAW,eAAe;CAElD,MAAM,SAA+B;EACpC,SAAS,QAAQ;EACjB,WAAW;EACX,eAAe;EACf,SAAS,QAAQ,IAAI,KAAK,IAAI;EAC9B,YAAY,QAAQ,QAAQ,IAAI,EAAE;EAClC,SAAS,QAAQ,IAAI,KAAK,IAAI;EAC9B,UAAU,CAAC,CAAC,QAAQ;GAAE,MAAM;GAAS,cAAc;EAAqB,CAAC,CAAC;EAC1E,KAAK;GACJ;GACA,SAAS;GACT,mBAAmB,CAAC,QAAQ,IAAI,MAAM,QAAQ,WAAW,UAAU;GACnE,OAAO;GACP,YAAY;GACZ,OAAO;EACR;EACA,SAAS;EACT,QAAQ,EAAE,SAAS,IAAM;EACzB,UAAU,CACT;GACC,MAAM;GACN,KAAK;IAAE,GAAG,QAAQ;IAAmB;GAAc;EACpD,GACA;GACC,MAAM;GACN,KAAK;IACJ,GAAG,QAAQ;IACX,UAAU;KAAE,OAAO;KAAK,QAAQ;IAAI;IACpC;GACD;EACD,CACD;CACD;CAEA,IAAI,QAAQ,WAAW,OAAO,YAAY,QAAQ;CAElD,OAAO,aAAa,MAAM;AAC3B"}
@@ -0,0 +1,15 @@
1
+ import { resolve } from "node:path";
2
+ //#region src/vitest/root.ts
3
+ const rootKey = "aaminiToolkitRoot";
4
+ /**
5
+ * Resolves a path against the app root. Without a root, for example when the
6
+ * app does not use `defineToolkit()`, the path resolves against the current
7
+ * working directory.
8
+ */
9
+ function resolveFromRoot(root, path) {
10
+ return resolve(root ?? process.cwd(), path);
11
+ }
12
+ //#endregion
13
+ export { rootKey as n, resolveFromRoot as t };
14
+
15
+ //# sourceMappingURL=root-U5B4x0aj.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-U5B4x0aj.mjs","names":[],"sources":["../src/vitest/root.ts"],"sourcesContent":["import { resolve } from 'node:path'\n\ndeclare module 'vitest' {\n\tinterface ProvidedContext {\n\t\t/** The app root. The TanStack `defineToolkit()` provides it. */\n\t\taaminiToolkitRoot?: string\n\t}\n}\n\nexport const rootKey = 'aaminiToolkitRoot'\n\n/**\n * Resolves a path against the app root. Without a root, for example when the\n * app does not use `defineToolkit()`, the path resolves against the current\n * working directory.\n */\nexport function resolveFromRoot(root: string | undefined, path: string) {\n\treturn resolve(root ?? process.cwd(), path)\n}\n"],"mappings":";;AASA,MAAa,UAAU;;;;;;AAOvB,SAAgB,gBAAgB,MAA0B,MAAc;CACvE,OAAO,QAAQ,QAAQ,QAAQ,IAAI,GAAG,IAAI;AAC3C"}
@@ -0,0 +1,45 @@
1
+ import { n as ToolkitConfig } from "../merge-BEomHgbw.mjs";
2
+ import { ProjectOptions } from "../vitest/index.mjs";
3
+ import { UserConfig } from "vite-plus";
4
+ import tailwindcss from "@tailwindcss/vite";
5
+ import { devtools } from "@tanstack/devtools-vite";
6
+ import { tanstackStart } from "@tanstack/react-start/plugin/vite";
7
+ import { varlockVitePlugin } from "@varlock/vite-integration";
8
+ import viteReact, { reactCompilerPreset } from "@vitejs/plugin-react";
9
+ import { nitro } from "nitro/vite";
10
+ //#region src/tanstack/index.d.ts
11
+ type Options<T extends (...args: never[]) => void> = NonNullable<Parameters<T>[0]>;
12
+ /** `false` removes the plugin. An object merges over the toolkit options. */
13
+ type PluginSetting<T> = false | T;
14
+ export interface TanstackPlugins {
15
+ tanstackStart?: PluginSetting<Options<typeof tanstackStart>>;
16
+ /** Dev only. Vitest runs without it. */
17
+ devtools?: PluginSetting<Options<typeof devtools>>;
18
+ /** Dev and build only. Vitest runs without it. */
19
+ nitro?: PluginSetting<Options<typeof nitro>>;
20
+ tailwindcss?: PluginSetting<Options<typeof tailwindcss>>;
21
+ react?: PluginSetting<Options<typeof viteReact>>;
22
+ reactCompiler?: PluginSetting<Options<typeof reactCompilerPreset>>;
23
+ varlock?: PluginSetting<Options<typeof varlockVitePlugin>>;
24
+ }
25
+ /** `false` removes the project. An object merges over the toolkit options. */
26
+ export interface TanstackTests {
27
+ unit?: false | ProjectOptions;
28
+ server?: false | ProjectOptions;
29
+ /** Vitest browser mode, `desktop` and `mobile` instances. */
30
+ browser?: false | ProjectOptions;
31
+ }
32
+ export interface TanstackToolkitOptions {
33
+ /** The app root. Pass `import.meta.dirname` from `vite.config.ts`. */
34
+ root?: string;
35
+ plugins?: TanstackPlugins;
36
+ /** Adds the `@shadcn/lint` rules. */
37
+ shadcn?: boolean;
38
+ tests?: TanstackTests;
39
+ /** Vite+ config merged last. App plugins in `config.plugins` run after the toolkit plugins. */
40
+ config?: ToolkitConfig;
41
+ }
42
+ /** The complete Vite+ config for a TanStack Start app. */
43
+ export declare function defineToolkit(options?: TanstackToolkitOptions): Promise<UserConfig>;
44
+ //#endregion
45
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1,189 @@
1
+ import { a as canResolve, c as mergeOptions, n as serverProject, o as importPeer, r as unitProject, s as requirePeer, t as browserProject } from "../vitest-CXdvqyRH.mjs";
2
+ import { n as lint, r as fmt, t as mergeToolkitConfig } from "../merge-DThKwm_6.mjs";
3
+ import { n as rootKey } from "../root-U5B4x0aj.mjs";
4
+ import { resolve } from "node:path";
5
+ //#region src/tanstack/index.ts
6
+ const entry = "@aamini/config/tanstack";
7
+ const peers = {
8
+ tanstackStart: {
9
+ name: "@tanstack/react-start",
10
+ specifier: "@tanstack/react-start/plugin/vite"
11
+ },
12
+ devtools: {
13
+ name: "@tanstack/devtools-vite",
14
+ specifier: "@tanstack/devtools-vite"
15
+ },
16
+ nitro: {
17
+ name: "nitro",
18
+ specifier: "nitro/vite"
19
+ },
20
+ tailwindcss: {
21
+ name: "@tailwindcss/vite",
22
+ specifier: "@tailwindcss/vite"
23
+ },
24
+ react: {
25
+ name: "@vitejs/plugin-react",
26
+ specifier: "@vitejs/plugin-react"
27
+ },
28
+ babel: {
29
+ name: "@rolldown/plugin-babel",
30
+ specifier: "@rolldown/plugin-babel"
31
+ },
32
+ reactCompiler: {
33
+ name: "babel-plugin-react-compiler",
34
+ specifier: "babel-plugin-react-compiler"
35
+ },
36
+ varlock: {
37
+ name: "@varlock/vite-integration",
38
+ specifier: "@varlock/vite-integration"
39
+ },
40
+ shadcn: {
41
+ name: "@shadcn/lint",
42
+ specifier: "@shadcn/lint"
43
+ }
44
+ };
45
+ const pluginDefaults = {
46
+ tanstackStart: {
47
+ router: { routeFileIgnorePattern: "(\\.test\\.tsx$|__screenshots__)" },
48
+ server: { build: { inlineCss: true } }
49
+ },
50
+ devtools: { injectSource: { enabled: false } },
51
+ nitro: {
52
+ sourcemap: true,
53
+ experimental: { sourcemapMinify: false }
54
+ },
55
+ tailwindcss: {},
56
+ react: {},
57
+ reactCompiler: {},
58
+ varlock: { ssrInjectMode: "resolved-env" }
59
+ };
60
+ const shadcnLint = {
61
+ jsPlugins: ["@shadcn/lint"],
62
+ rules: {
63
+ "shadcn/require-static-classes": "error",
64
+ "shadcn/no-raw-colors": "error",
65
+ "shadcn/no-unknown-classes": "error",
66
+ "shadcn/no-arbitrary-values": ["error", { allow: ["layout"] }],
67
+ "shadcn/no-inline-styles": "error",
68
+ "shadcn/no-restyle": ["error", { allow: ["layout"] }]
69
+ },
70
+ overrides: [{
71
+ files: ["src/components/ui/**"],
72
+ rules: {
73
+ "shadcn/require-static-classes": "off",
74
+ "shadcn/no-restyle": "off",
75
+ "shadcn/no-arbitrary-values": "off"
76
+ }
77
+ }]
78
+ };
79
+ function pluginOptions(defaults, setting) {
80
+ if (setting === false) return void 0;
81
+ return mergeOptions(defaults, setting);
82
+ }
83
+ /**
84
+ * Builds the TanStack plugins in the toolkit order. Each import runs only
85
+ * when its plugin is on, so an app never loads a plugin that it removes.
86
+ */
87
+ async function tanstackPlugins(plugins) {
88
+ const underVitest = process.env.VITEST === "true";
89
+ const result = [];
90
+ const startOptions = pluginOptions(pluginDefaults.tanstackStart, plugins.tanstackStart);
91
+ if (startOptions) {
92
+ const { tanstackStart } = await importPeer(entry, peers.tanstackStart, () => import("@tanstack/react-start/plugin/vite"));
93
+ result.push(tanstackStart(startOptions));
94
+ }
95
+ const devtoolsOptions = pluginOptions(pluginDefaults.devtools, plugins.devtools);
96
+ if (devtoolsOptions && !underVitest) {
97
+ const { devtools } = await importPeer(entry, peers.devtools, () => import("@tanstack/devtools-vite"));
98
+ result.push(devtools(devtoolsOptions));
99
+ }
100
+ const nitroOptions = pluginOptions(pluginDefaults.nitro, plugins.nitro);
101
+ if (nitroOptions && !underVitest) {
102
+ const { nitro } = await importPeer(entry, peers.nitro, () => import("nitro/vite"));
103
+ result.push(nitro(nitroOptions));
104
+ }
105
+ const tailwindOptions = pluginOptions(pluginDefaults.tailwindcss, plugins.tailwindcss);
106
+ if (tailwindOptions) {
107
+ const { default: tailwindcss } = await importPeer(entry, peers.tailwindcss, () => import("@tailwindcss/vite"));
108
+ result.push(tailwindcss(tailwindOptions));
109
+ }
110
+ const reactOptions = pluginOptions(pluginDefaults.react, plugins.react);
111
+ if (reactOptions) {
112
+ const { default: viteReact } = await importPeer(entry, peers.react, () => import("@vitejs/plugin-react"));
113
+ result.push(viteReact(reactOptions));
114
+ }
115
+ const compilerOptions = pluginOptions(pluginDefaults.reactCompiler, plugins.reactCompiler);
116
+ if (compilerOptions) {
117
+ requirePeer(entry, peers.reactCompiler);
118
+ const { reactCompilerPreset } = await importPeer(entry, peers.react, () => import("@vitejs/plugin-react"));
119
+ const { default: babel } = await importPeer(entry, peers.babel, () => import("@rolldown/plugin-babel"));
120
+ result.push(babel({ presets: [reactCompilerPreset(compilerOptions)] }));
121
+ }
122
+ const varlockOptions = pluginOptions(pluginDefaults.varlock, plugins.varlock);
123
+ if (varlockOptions) {
124
+ const { varlockVitePlugin } = await importPeer(entry, peers.varlock, () => import("@varlock/vite-integration"));
125
+ result.push(varlockVitePlugin(varlockOptions));
126
+ }
127
+ return result;
128
+ }
129
+ async function testProjects(tests) {
130
+ const projects = [];
131
+ if (tests.unit !== false) projects.push(unitProject(tests.unit));
132
+ if (tests.server !== false) projects.push(serverProject(tests.server));
133
+ if (tests.browser !== false) projects.push(await browserProject(mergeOptions({ setupFiles: ["./src/styles.css"] }, tests.browser)));
134
+ return projects;
135
+ }
136
+ function browserTestDeps() {
137
+ const deps = ["vite-plus/test", "vite-plus/test/browser"];
138
+ for (const optional of [
139
+ "vitest-browser-react",
140
+ "msw",
141
+ "msw/browser"
142
+ ]) if (canResolve(optional)) deps.push(optional);
143
+ return deps;
144
+ }
145
+ /** The complete Vite+ config for a TanStack Start app. */
146
+ async function defineToolkit(options = {}) {
147
+ const root = options.root ?? process.cwd();
148
+ const shadcn = options.shadcn !== false;
149
+ const tests = options.tests ?? {};
150
+ if (shadcn) requirePeer(entry, peers.shadcn);
151
+ const preset = {
152
+ root,
153
+ staged: { "*": "vp check --fix" },
154
+ server: {
155
+ host: "127.0.0.1",
156
+ port: Number(process.env.APP_PORT ?? 3e3)
157
+ },
158
+ build: { rollupOptions: { checks: { moduleLevelDirective: false } } },
159
+ resolve: {
160
+ tsconfigPaths: true,
161
+ dedupe: ["react", "react-dom"],
162
+ alias: [{
163
+ find: "@",
164
+ replacement: resolve(root, "src")
165
+ }, {
166
+ find: "@tests",
167
+ replacement: resolve(root, "tests")
168
+ }]
169
+ },
170
+ plugins: await tanstackPlugins(options.plugins ?? {}),
171
+ fmt,
172
+ lint: lint("react"),
173
+ test: {
174
+ api: {
175
+ allowWrite: true,
176
+ allowExec: false
177
+ },
178
+ provide: { [rootKey]: root },
179
+ projects: await testProjects(tests)
180
+ }
181
+ };
182
+ if (tests.browser !== false) preset.optimizeDeps = { include: browserTestDeps() };
183
+ const withShadcn = shadcn ? mergeToolkitConfig(preset, { lint: shadcnLint }) : preset;
184
+ return mergeToolkitConfig(withShadcn, options.config);
185
+ }
186
+ //#endregion
187
+ export { defineToolkit };
188
+
189
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/tanstack/index.ts"],"sourcesContent":["import { resolve } from 'node:path'\n\nimport type tailwindcss from '@tailwindcss/vite'\nimport type { devtools } from '@tanstack/devtools-vite'\nimport type { tanstackStart } from '@tanstack/react-start/plugin/vite'\nimport type { varlockVitePlugin } from '@varlock/vite-integration'\nimport type { reactCompilerPreset } from '@vitejs/plugin-react'\nimport type viteReact from '@vitejs/plugin-react'\nimport type { nitro } from 'nitro/vite'\nimport type { PluginOption, UserConfig } from 'vite-plus'\n\nimport { mergeOptions } from '../options.ts'\nimport { canResolve, importPeer, requirePeer, type Peer } from '../peers.ts'\nimport { fmt } from '../vite-plus/fmt.ts'\nimport { lint } from '../vite-plus/lint.ts'\nimport { mergeToolkitConfig, type ToolkitConfig } from '../vite-plus/merge.ts'\nimport {\n\tbrowserProject,\n\tserverProject,\n\tunitProject,\n\ttype ProjectOptions,\n} from '../vitest/index.ts'\nimport { rootKey } from '../vitest/root.ts'\n\n// Any function type is assignable to this signature, whatever it returns.\ntype Options<T extends (...args: never[]) => void> = NonNullable<\n\tParameters<T>[0]\n>\n\n/** `false` removes the plugin. An object merges over the toolkit options. */\ntype PluginSetting<T> = false | T\n\nexport interface TanstackPlugins {\n\ttanstackStart?: PluginSetting<Options<typeof tanstackStart>>\n\t/** Dev only. Vitest runs without it. */\n\tdevtools?: PluginSetting<Options<typeof devtools>>\n\t/** Dev and build only. Vitest runs without it. */\n\tnitro?: PluginSetting<Options<typeof nitro>>\n\ttailwindcss?: PluginSetting<Options<typeof tailwindcss>>\n\treact?: PluginSetting<Options<typeof viteReact>>\n\treactCompiler?: PluginSetting<Options<typeof reactCompilerPreset>>\n\tvarlock?: PluginSetting<Options<typeof varlockVitePlugin>>\n}\n\n/** `false` removes the project. An object merges over the toolkit options. */\nexport interface TanstackTests {\n\tunit?: false | ProjectOptions\n\tserver?: false | ProjectOptions\n\t/** Vitest browser mode, `desktop` and `mobile` instances. */\n\tbrowser?: false | ProjectOptions\n}\n\nexport interface TanstackToolkitOptions {\n\t/** The app root. Pass `import.meta.dirname` from `vite.config.ts`. */\n\troot?: string\n\tplugins?: TanstackPlugins\n\t/** Adds the `@shadcn/lint` rules. */\n\tshadcn?: boolean\n\ttests?: TanstackTests\n\t/** Vite+ config merged last. App plugins in `config.plugins` run after the toolkit plugins. */\n\tconfig?: ToolkitConfig\n}\n\nconst entry = '@aamini/config/tanstack'\n\nconst peers = {\n\ttanstackStart: {\n\t\tname: '@tanstack/react-start',\n\t\tspecifier: '@tanstack/react-start/plugin/vite',\n\t},\n\tdevtools: {\n\t\tname: '@tanstack/devtools-vite',\n\t\tspecifier: '@tanstack/devtools-vite',\n\t},\n\tnitro: { name: 'nitro', specifier: 'nitro/vite' },\n\ttailwindcss: { name: '@tailwindcss/vite', specifier: '@tailwindcss/vite' },\n\treact: { name: '@vitejs/plugin-react', specifier: '@vitejs/plugin-react' },\n\tbabel: {\n\t\tname: '@rolldown/plugin-babel',\n\t\tspecifier: '@rolldown/plugin-babel',\n\t},\n\treactCompiler: {\n\t\tname: 'babel-plugin-react-compiler',\n\t\tspecifier: 'babel-plugin-react-compiler',\n\t},\n\tvarlock: {\n\t\tname: '@varlock/vite-integration',\n\t\tspecifier: '@varlock/vite-integration',\n\t},\n\tshadcn: { name: '@shadcn/lint', specifier: '@shadcn/lint' },\n} satisfies Record<string, Peer>\n\ntype PluginDefaults = Required<{\n\t[K in keyof TanstackPlugins]: Exclude<TanstackPlugins[K], false | undefined>\n}>\n\nconst pluginDefaults: PluginDefaults = {\n\ttanstackStart: {\n\t\trouter: { routeFileIgnorePattern: '(\\\\.test\\\\.tsx$|__screenshots__)' },\n\t\tserver: { build: { inlineCss: true } },\n\t},\n\tdevtools: { injectSource: { enabled: false } },\n\tnitro: { sourcemap: true, experimental: { sourcemapMinify: false } },\n\ttailwindcss: {},\n\treact: {},\n\treactCompiler: {},\n\tvarlock: { ssrInjectMode: 'resolved-env' },\n}\n\nconst shadcnLint = {\n\tjsPlugins: ['@shadcn/lint'],\n\trules: {\n\t\t'shadcn/require-static-classes': 'error',\n\t\t'shadcn/no-raw-colors': 'error',\n\t\t'shadcn/no-unknown-classes': 'error',\n\t\t'shadcn/no-arbitrary-values': ['error', { allow: ['layout'] }],\n\t\t'shadcn/no-inline-styles': 'error',\n\t\t'shadcn/no-restyle': ['error', { allow: ['layout'] }],\n\t},\n\toverrides: [\n\t\t{\n\t\t\tfiles: ['src/components/ui/**'],\n\t\t\trules: {\n\t\t\t\t'shadcn/require-static-classes': 'off',\n\t\t\t\t'shadcn/no-restyle': 'off',\n\t\t\t\t'shadcn/no-arbitrary-values': 'off',\n\t\t\t},\n\t\t},\n\t],\n} satisfies UserConfig['lint']\n\nfunction pluginOptions<T extends object>(\n\tdefaults: T,\n\tsetting: PluginSetting<T> | undefined,\n) {\n\tif (setting === false) return undefined\n\n\treturn mergeOptions(defaults, setting)\n}\n\n/**\n * Builds the TanStack plugins in the toolkit order. Each import runs only\n * when its plugin is on, so an app never loads a plugin that it removes.\n */\nasync function tanstackPlugins(plugins: TanstackPlugins) {\n\tconst underVitest = process.env.VITEST === 'true'\n\tconst result: PluginOption[] = []\n\n\tconst startOptions = pluginOptions(\n\t\tpluginDefaults.tanstackStart,\n\t\tplugins.tanstackStart,\n\t)\n\n\tif (startOptions) {\n\t\tconst { tanstackStart } = await importPeer(\n\t\t\tentry,\n\t\t\tpeers.tanstackStart,\n\t\t\t() => import('@tanstack/react-start/plugin/vite'),\n\t\t)\n\n\t\tresult.push(tanstackStart(startOptions))\n\t}\n\n\tconst devtoolsOptions = pluginOptions(\n\t\tpluginDefaults.devtools,\n\t\tplugins.devtools,\n\t)\n\n\tif (devtoolsOptions && !underVitest) {\n\t\tconst { devtools } = await importPeer(\n\t\t\tentry,\n\t\t\tpeers.devtools,\n\t\t\t() => import('@tanstack/devtools-vite'),\n\t\t)\n\n\t\tresult.push(devtools(devtoolsOptions))\n\t}\n\n\tconst nitroOptions = pluginOptions(pluginDefaults.nitro, plugins.nitro)\n\n\tif (nitroOptions && !underVitest) {\n\t\tconst { nitro } = await importPeer(\n\t\t\tentry,\n\t\t\tpeers.nitro,\n\t\t\t() => import('nitro/vite'),\n\t\t)\n\n\t\tresult.push(nitro(nitroOptions))\n\t}\n\n\tconst tailwindOptions = pluginOptions(\n\t\tpluginDefaults.tailwindcss,\n\t\tplugins.tailwindcss,\n\t)\n\n\tif (tailwindOptions) {\n\t\tconst { default: tailwindcss } = await importPeer(\n\t\t\tentry,\n\t\t\tpeers.tailwindcss,\n\t\t\t() => import('@tailwindcss/vite'),\n\t\t)\n\n\t\tresult.push(tailwindcss(tailwindOptions))\n\t}\n\n\tconst reactOptions = pluginOptions(pluginDefaults.react, plugins.react)\n\n\tif (reactOptions) {\n\t\tconst { default: viteReact } = await importPeer(\n\t\t\tentry,\n\t\t\tpeers.react,\n\t\t\t() => import('@vitejs/plugin-react'),\n\t\t)\n\n\t\tresult.push(viteReact(reactOptions))\n\t}\n\n\tconst compilerOptions = pluginOptions(\n\t\tpluginDefaults.reactCompiler,\n\t\tplugins.reactCompiler,\n\t)\n\n\tif (compilerOptions) {\n\t\t// Babel loads the compiler plugin by name, after this config resolves.\n\t\trequirePeer(entry, peers.reactCompiler)\n\n\t\tconst { reactCompilerPreset } = await importPeer(\n\t\t\tentry,\n\t\t\tpeers.react,\n\t\t\t() => import('@vitejs/plugin-react'),\n\t\t)\n\n\t\tconst { default: babel } = await importPeer(\n\t\t\tentry,\n\t\t\tpeers.babel,\n\t\t\t() => import('@rolldown/plugin-babel'),\n\t\t)\n\n\t\tresult.push(babel({ presets: [reactCompilerPreset(compilerOptions)] }))\n\t}\n\n\tconst varlockOptions = pluginOptions(pluginDefaults.varlock, plugins.varlock)\n\n\tif (varlockOptions) {\n\t\tconst { varlockVitePlugin } = await importPeer(\n\t\t\tentry,\n\t\t\tpeers.varlock,\n\t\t\t() => import('@varlock/vite-integration'),\n\t\t)\n\n\t\tresult.push(varlockVitePlugin(varlockOptions))\n\t}\n\n\treturn result\n}\n\nasync function testProjects(tests: TanstackTests) {\n\tconst projects = []\n\n\tif (tests.unit !== false) projects.push(unitProject(tests.unit))\n\n\tif (tests.server !== false) projects.push(serverProject(tests.server))\n\n\tif (tests.browser !== false) {\n\t\tprojects.push(\n\t\t\tawait browserProject(\n\t\t\t\tmergeOptions<ProjectOptions>(\n\t\t\t\t\t{ setupFiles: ['./src/styles.css'] },\n\t\t\t\t\ttests.browser,\n\t\t\t\t),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn projects\n}\n\n// Pre-bundles the browser test stack at startup. Without it, Vite discovers\n// each dependency mid-run, and the re-optimization reload aborts the test\n// file that is loading.\nfunction browserTestDeps() {\n\tconst deps = ['vite-plus/test', 'vite-plus/test/browser']\n\n\tfor (const optional of ['vitest-browser-react', 'msw', 'msw/browser']) {\n\t\tif (canResolve(optional)) deps.push(optional)\n\t}\n\n\treturn deps\n}\n\n/** The complete Vite+ config for a TanStack Start app. */\nexport async function defineToolkit(\n\toptions: TanstackToolkitOptions = {},\n): Promise<UserConfig> {\n\tconst root = options.root ?? process.cwd()\n\tconst shadcn = options.shadcn !== false\n\tconst tests = options.tests ?? {}\n\n\t// Oxlint loads the shadcn rules by name when it lints.\n\tif (shadcn) requirePeer(entry, peers.shadcn)\n\n\tconst preset: UserConfig = {\n\t\troot,\n\t\tstaged: { '*': 'vp check --fix' },\n\t\tserver: { host: '127.0.0.1', port: Number(process.env.APP_PORT ?? 3000) },\n\t\t// TanStack Router ships RSC \"use client\" directives in its dist for RSC\n\t\t// compat. These apps build no RSC graph, so the bundling warnings are\n\t\t// noise.\n\t\tbuild: { rollupOptions: { checks: { moduleLevelDirective: false } } },\n\t\tresolve: {\n\t\t\ttsconfigPaths: true,\n\t\t\tdedupe: ['react', 'react-dom'],\n\t\t\talias: [\n\t\t\t\t{ find: '@', replacement: resolve(root, 'src') },\n\t\t\t\t{ find: '@tests', replacement: resolve(root, 'tests') },\n\t\t\t],\n\t\t},\n\t\tplugins: await tanstackPlugins(options.plugins ?? {}),\n\t\tfmt,\n\t\tlint: lint('react'),\n\t\ttest: {\n\t\t\t// Headless runs and screenshot baselines work with writes disabled.\n\t\t\t// allowWrite serves UI clients on the tailnet, which update snapshots\n\t\t\t// and baselines from the browser UI. Execution stays disabled.\n\t\t\tapi: { allowWrite: true, allowExec: false },\n\t\t\t// Test fixtures resolve app paths against this root, not the cwd.\n\t\t\tprovide: { [rootKey]: root },\n\t\t\tprojects: await testProjects(tests),\n\t\t},\n\t}\n\n\tif (tests.browser !== false) {\n\t\tpreset.optimizeDeps = { include: browserTestDeps() }\n\t}\n\n\tconst withShadcn = shadcn\n\t\t? mergeToolkitConfig(preset, { lint: shadcnLint })\n\t\t: preset\n\n\treturn mergeToolkitConfig(withShadcn, options.config)\n}\n"],"mappings":"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"}