@evertondgn/solid-tooling 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vite.mjs ADDED
@@ -0,0 +1,341 @@
1
+ import { fmt } from "./fmt.mjs";
2
+ import { t as lint } from "./lint-UTfyg7AR.mjs";
3
+ import { solidTest } from "./vitest.mjs";
4
+ import path from "node:path";
5
+ import { readFile } from "node:fs/promises";
6
+ import { env, loadEnvFile } from "node:process";
7
+ import { defineConfig, lazyPlugins, loadEnv } from "vite-plus";
8
+ // src/vite/plugins.ts
9
+ const ROUTE_TYPES = "src/@types/routes.d.ts";
10
+ const SEO_FILE_PATHS = [
11
+ "/llms.txt",
12
+ "/llms-full.txt",
13
+ "/sitemap.xml",
14
+ "/sitemaps/pages/**",
15
+ "/_markdown/static.json"
16
+ ];
17
+ async function loadPluginModules() {
18
+ const [solid, routing, icons, loaders] = await Promise.all([
19
+ import("@solidjs/vite-plugin"),
20
+ import("filesystem-routing/vite"),
21
+ import("unplugin-icons/vite"),
22
+ import("unplugin-icons/loaders")
23
+ ]);
24
+ return {
25
+ solid: solid.default,
26
+ fileRoutes: routing.fileRoutes,
27
+ Icons: icons.default,
28
+ FileSystemIconLoader: loaders.FileSystemIconLoader
29
+ };
30
+ }
31
+ async function readExtension({ options, target, mode }) {
32
+ return await options.extend?.({
33
+ target,
34
+ mode
35
+ }) ?? {};
36
+ }
37
+ async function readLocales(root) {
38
+ let settings;
39
+ try {
40
+ settings = JSON.parse(await readFile(path.join(root, "project.inlang/settings.json"), "utf8"));
41
+ } catch (error) {
42
+ throw new Error("defineSolidApp: cannot read the locales from project.inlang/settings.json.", { cause: error });
43
+ }
44
+ const locales = settings !== null && typeof settings === "object" && "locales" in settings ? settings.locales : void 0;
45
+ if (!Array.isArray(locales) || !locales.every((locale) => typeof locale === "string")) throw new Error("defineSolidApp: project.inlang/settings.json must list its locales as strings.");
46
+ return locales;
47
+ }
48
+ function isUnderLocale({ staticPath, locales }) {
49
+ const [firstSegment] = staticPath.split("/").slice(1);
50
+ return locales.includes(firstSegment ?? "");
51
+ }
52
+ function staticFilePaths({ options, locales }) {
53
+ const appPaths = [...(options.publicAssets ?? []).map((asset) => `${asset.baseURL}/**`), ...options.staticPaths ?? []];
54
+ const collision = appPaths.find((staticPath) => isUnderLocale({
55
+ staticPath,
56
+ locales
57
+ }));
58
+ if (collision) throw new Error(`defineSolidApp: the static path ${collision} is inside a locale folder; move it out of /${collision.split("/")[1]}/.`);
59
+ return [
60
+ "/assets/**",
61
+ ...appPaths,
62
+ ...locales.map((locale) => `/${locale}/**.md`),
63
+ ...SEO_FILE_PATHS
64
+ ];
65
+ }
66
+ function iconsPlugin({ modules, options }) {
67
+ return modules.Icons({
68
+ compiler: "solid",
69
+ iconCustomizer(_collection, _icon, props) {
70
+ props["aria-hidden"] = "true";
71
+ },
72
+ customCollections: Object.fromEntries(Object.entries(options.icons ?? {}).map(([name, folder]) => [name, modules.FileSystemIconLoader(path.resolve(options.root, folder))]))
73
+ });
74
+ }
75
+ function staticHeaders({ securityHeaders }) {
76
+ return {
77
+ "strict-transport-security": securityHeaders["strict-transport-security"],
78
+ "x-content-type-options": securityHeaders["x-content-type-options"],
79
+ "cross-origin-resource-policy": securityHeaders["cross-origin-resource-policy"]
80
+ };
81
+ }
82
+ function appPlugins({ options, mode }) {
83
+ return lazyPlugins(async () => {
84
+ const [modules, { nitro }, extension, locales] = await Promise.all([
85
+ loadPluginModules(),
86
+ import("nitro/vite"),
87
+ readExtension({
88
+ options,
89
+ target: "app",
90
+ mode
91
+ }),
92
+ readLocales(options.root)
93
+ ]);
94
+ const isE2e = mode === "e2e";
95
+ const middleware = isE2e ? options.e2e?.middleware ?? options.middleware : options.middleware;
96
+ const e2eRoutes = isE2e ? options.e2e?.routes : void 0;
97
+ const headers = staticHeaders(options);
98
+ return [
99
+ ...extension.plugins ?? [],
100
+ iconsPlugin({
101
+ modules,
102
+ options
103
+ }),
104
+ modules.solid({
105
+ start: middleware ? { middleware } : {},
106
+ ssr: true,
107
+ serverFunctions: options.serverSetup ? { serverSetup: options.serverSetup } : true
108
+ }),
109
+ modules.fileRoutes(e2eRoutes ? {
110
+ dir: e2eRoutes,
111
+ httpMethods: true
112
+ } : {
113
+ types: ROUTE_TYPES,
114
+ httpMethods: true
115
+ }),
116
+ nitro({
117
+ serverEntry: false,
118
+ preset: "vercel",
119
+ modules: extension.nitroModules ?? [],
120
+ publicAssets: options.publicAssets ?? [],
121
+ routeRules: {
122
+ ...Object.fromEntries(staticFilePaths({
123
+ options,
124
+ locales
125
+ }).map((staticPath) => [staticPath, { headers }])),
126
+ ...options.routeRules
127
+ }
128
+ })
129
+ ];
130
+ });
131
+ }
132
+ function testPlugins({ options, mode }) {
133
+ return lazyPlugins(async () => {
134
+ const [modules, extension] = await Promise.all([loadPluginModules(), readExtension({
135
+ options,
136
+ target: "test",
137
+ mode
138
+ })]);
139
+ return [
140
+ ...extension.plugins ?? [],
141
+ iconsPlugin({
142
+ modules,
143
+ options
144
+ }),
145
+ modules.solid({ serverFunctions: true }),
146
+ modules.fileRoutes({
147
+ types: ROUTE_TYPES,
148
+ httpMethods: true
149
+ })
150
+ ];
151
+ });
152
+ }
153
+ // src/vite/tasks.ts
154
+ const TOOLING_CI_TEST_WORKERS = 3;
155
+ function fromWorkspace(pattern) {
156
+ return {
157
+ pattern,
158
+ base: "workspace"
159
+ };
160
+ }
161
+ function installInputs({ workspace }) {
162
+ const patterns = [
163
+ "bun.lock",
164
+ "bunfig.toml",
165
+ ...workspace?.inputs ?? []
166
+ ];
167
+ return workspace ? patterns.map((pattern) => fromWorkspace(pattern)) : patterns;
168
+ }
169
+ function createTasks({ workspace, isCi }) {
170
+ const install = installInputs({ workspace });
171
+ const lockfile = workspace ? fromWorkspace("bun.lock") : "bun.lock";
172
+ const dependsOn = workspace ? { dependsOn: workspace.dependsOn } : {};
173
+ const projectCheckInputs = [
174
+ { auto: true },
175
+ "src/**",
176
+ "paraglide/**",
177
+ "tooling/**",
178
+ "*.ts",
179
+ "tsconfig*.json",
180
+ "package.json",
181
+ "patches/**",
182
+ ...install
183
+ ];
184
+ const testConcurrency = isCi ? ` --test-concurrency=${TOOLING_CI_TEST_WORKERS}` : "";
185
+ return {
186
+ "i18n-generate": {
187
+ command: "bun tooling/i18n/generate.ts",
188
+ cache: {
189
+ input: [
190
+ "src/**/i18n/*.json",
191
+ "tooling/i18n/**",
192
+ "project.inlang/settings.json",
193
+ "project.inlang/paraglide.config.ts",
194
+ "package.json",
195
+ lockfile
196
+ ],
197
+ output: ["paraglide/**", ".paraglide/messages/**"]
198
+ }
199
+ },
200
+ "lint-css": {
201
+ command: "bun tooling/css/check.ts",
202
+ ...dependsOn,
203
+ cache: {
204
+ env: ["NODE_ENV"],
205
+ input: [
206
+ { auto: true },
207
+ "src/**/*.css",
208
+ "tooling/css/**",
209
+ "package.json",
210
+ ...install
211
+ ]
212
+ }
213
+ },
214
+ "dead-code-project": {
215
+ command: "knip-bun --config tooling/knip.ts && knip-bun --config tooling/knip.ts --strict",
216
+ ...dependsOn,
217
+ cache: {
218
+ env: ["NODE_ENV"],
219
+ input: [
220
+ { auto: true },
221
+ "src/**",
222
+ "paraglide/**",
223
+ "tooling/**",
224
+ "*.ts",
225
+ "tsconfig*.json",
226
+ "package.json",
227
+ ...install
228
+ ],
229
+ output: []
230
+ }
231
+ },
232
+ "lint-project": {
233
+ command: "vp lint",
234
+ dependsOn: ["lint-css"],
235
+ cache: {
236
+ env: ["NODE_ENV"],
237
+ input: projectCheckInputs
238
+ }
239
+ },
240
+ "check-project": {
241
+ command: "vp fmt --check && vp lint",
242
+ dependsOn: ["lint-css"],
243
+ cache: {
244
+ env: ["NODE_ENV"],
245
+ input: projectCheckInputs
246
+ }
247
+ },
248
+ "typecheck-node": {
249
+ command: "tsc --project tsconfig.node.json",
250
+ ...dependsOn,
251
+ cache: {
252
+ input: [
253
+ "tsconfig.json",
254
+ "tsconfig.node.json",
255
+ "vite.config.ts",
256
+ "playwright.config.ts",
257
+ "tooling/**/*.ts",
258
+ "src/**/*.ts",
259
+ "src/**/*.tsx",
260
+ "src/**/*.json",
261
+ "project.inlang/paraglide.config.ts",
262
+ "project.inlang/settings.json",
263
+ "package.json",
264
+ "patches/**",
265
+ ...install
266
+ ],
267
+ output: []
268
+ }
269
+ },
270
+ "test-tooling": {
271
+ command: `node --test${testConcurrency} --test-timeout=60000 'tooling/__tests__/*.test.ts' 'tooling/!(release)/**/__tests__/*.test.ts'`,
272
+ cache: false
273
+ }
274
+ };
275
+ }
276
+ // src/vite/index.ts
277
+ function isPathPrefix(baseURL) {
278
+ return typeof baseURL === "string" && baseURL.startsWith("/") && baseURL.length > 1 && !baseURL.endsWith("/");
279
+ }
280
+ function validateOptions(options) {
281
+ const asset = (options.publicAssets ?? []).find(({ baseURL }) => !isPathPrefix(baseURL));
282
+ if (asset) throw new Error(`defineSolidApp: the publicAssets entry for ${asset.dir} needs a baseURL such as /images, without a trailing slash.`);
283
+ const staticPath = (options.staticPaths ?? []).find((pattern) => !pattern.startsWith("/"));
284
+ if (staticPath !== void 0) throw new Error(`defineSolidApp: the static path ${staticPath} must start with a slash.`);
285
+ }
286
+ function loadTestEnvFile(root) {
287
+ try {
288
+ loadEnvFile(path.join(root, ".env.test"));
289
+ } catch (error) {
290
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
291
+ }
292
+ }
293
+ function defineSolidApp(options) {
294
+ validateOptions(options);
295
+ const { root } = options;
296
+ return defineConfig(({ mode }) => {
297
+ const localEnv = loadEnv(mode, root, ["HOST", "PORT"]);
298
+ const server = {
299
+ host: localEnv.HOST,
300
+ port: localEnv.PORT ? Number(localEnv.PORT) : void 0
301
+ };
302
+ if (mode === "test") loadTestEnvFile(root);
303
+ return {
304
+ run: { tasks: {
305
+ ...createTasks({
306
+ workspace: options.workspace,
307
+ isCi: Boolean(env.CI)
308
+ }),
309
+ ...options.tasks
310
+ } },
311
+ build: {
312
+ manifest: mode === "e2e",
313
+ rolldownOptions: { output: { comments: false } }
314
+ },
315
+ css: {
316
+ transformer: "lightningcss",
317
+ lightningcss: { cssModules: { pattern: "[content-hash]_[local]" } }
318
+ },
319
+ resolve: { tsconfigPaths: true },
320
+ server: {
321
+ ...server,
322
+ forwardConsole: true
323
+ },
324
+ preview: server,
325
+ plugins: mode === "test" ? [] : appPlugins({
326
+ options,
327
+ mode
328
+ }),
329
+ fmt,
330
+ lint,
331
+ test: solidTest({
332
+ root,
333
+ plugins: () => testPlugins({
334
+ options,
335
+ mode
336
+ })
337
+ })
338
+ };
339
+ });
340
+ }
341
+ export { defineSolidApp };
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,5 @@
1
+ import { baseLocale, setLocale } from "@paraglide/runtime.js";
2
+ import { beforeEach } from "vite-plus/test";
3
+ // src/vitest/setup/browser.ts
4
+ beforeEach(() => setLocale(baseLocale, { reload: false }));
5
+ export {};
@@ -0,0 +1,7 @@
1
+ import { TestUserConfig, ViteUserConfig } from "vite-plus";
2
+ type SolidTestOptions = {
3
+ root: string;
4
+ plugins: () => ViteUserConfig['plugins'];
5
+ };
6
+ export declare function solidTest({ root, plugins }: SolidTestOptions): TestUserConfig;
7
+ export {};
@@ -0,0 +1,129 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { env } from "node:process";
4
+ import { playwright } from "vite-plus/test/browser-playwright";
5
+ // src/vitest/index.ts
6
+ const CI_WORKERS = 2;
7
+ const STUB_PREFIX = "\0solid-tooling-stub:";
8
+ const NODE_STUBS = /* @__PURE__ */ new Map([
9
+ ["server-only", "export default undefined"],
10
+ ["virtual:file-routes", "export const pageRoutes = []\nexport default []"],
11
+ ["virtual:solid-server-function-handler", "export const endpoint = '/_server'"]
12
+ ]);
13
+ const TEST_ENV_KEYS = [
14
+ "BASE_URL_TEST",
15
+ "HOST",
16
+ "PORT"
17
+ ];
18
+ function packageFile(name) {
19
+ return fileURLToPath(new URL(name, import.meta.url));
20
+ }
21
+ function nodeStubs() {
22
+ return {
23
+ name: "solid-tooling:node-stubs",
24
+ enforce: "pre",
25
+ resolveId: (id) => NODE_STUBS.has(id) ? `${STUB_PREFIX}${id}` : void 0,
26
+ load: (id) => id.startsWith(STUB_PREFIX) ? NODE_STUBS.get(id.slice(20)) : void 0
27
+ };
28
+ }
29
+ function readTestEnv() {
30
+ return Object.fromEntries(TEST_ENV_KEYS.flatMap((key) => {
31
+ const value = env[key];
32
+ return value === void 0 ? [] : [[key, value]];
33
+ }));
34
+ }
35
+ function solidTest({ root, plugins }) {
36
+ return {
37
+ pool: "threads",
38
+ maxWorkers: env.CI ? CI_WORKERS : void 0,
39
+ css: false,
40
+ globals: true,
41
+ restoreMocks: true,
42
+ unstubGlobals: true,
43
+ unstubEnvs: true,
44
+ expect: {
45
+ requireAssertions: true,
46
+ poll: { interval: 10 }
47
+ },
48
+ env: readTestEnv(),
49
+ coverage: {
50
+ provider: "v8",
51
+ reporter: ["text", "html"],
52
+ thresholds: {
53
+ lines: 80,
54
+ functions: 80,
55
+ branches: 75,
56
+ statements: 80
57
+ },
58
+ include: ["src/**/*.{ts,tsx}"],
59
+ exclude: [
60
+ "src/**/*.d.ts",
61
+ "src/**/{constants,types,@types}/**",
62
+ "src/**/{constants,types}.{ts,tsx}",
63
+ "src/**/__tests__/**",
64
+ "src/tests/**",
65
+ "src/App.tsx",
66
+ "src/Document.tsx",
67
+ "src/entry-server.tsx",
68
+ "src/entry-client.tsx",
69
+ "src/router.ts"
70
+ ]
71
+ },
72
+ reporters: env.GITHUB_ACTIONS ? ["default", "github-actions"] : ["verbose"],
73
+ projects: [
74
+ {
75
+ plugins: [nodeStubs()],
76
+ test: {
77
+ name: {
78
+ label: "node",
79
+ color: "cyan"
80
+ },
81
+ environment: "node",
82
+ include: ["src/**/*.node.test.{ts,tsx}"]
83
+ }
84
+ },
85
+ {
86
+ plugins: plugins(),
87
+ test: {
88
+ name: {
89
+ label: "dom",
90
+ color: "magenta"
91
+ },
92
+ environment: "happy-dom",
93
+ environmentOptions: { happyDOM: { url: "http://localhost/en" } },
94
+ include: ["src/**/*.dom.test.{ts,tsx}"]
95
+ }
96
+ },
97
+ {
98
+ plugins: plugins(),
99
+ resolve: { alias: [{
100
+ find: /^@paraglide\//u,
101
+ replacement: `${path.join(root, "paraglide")}/`
102
+ }] },
103
+ test: {
104
+ css: true,
105
+ name: {
106
+ label: "browser",
107
+ color: "yellow"
108
+ },
109
+ setupFiles: [packageFile("vitest-setup/browser.mjs")],
110
+ include: ["src/**/*.browser.test.{ts,tsx}"],
111
+ browser: {
112
+ enabled: true,
113
+ headless: true,
114
+ trace: {
115
+ mode: "retain-on-failure",
116
+ tracesDir: path.join(root, "test-results/browser-traces")
117
+ },
118
+ provider: playwright({
119
+ contextOptions: { locale: "en-US" },
120
+ launchOptions: env.CI ? { channel: "chrome" } : {}
121
+ }),
122
+ instances: [{ browser: "chromium" }]
123
+ }
124
+ }
125
+ }
126
+ ]
127
+ };
128
+ }
129
+ export { solidTest };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@evertondgn/solid-tooling",
3
- "version": "0.2.0",
4
- "description": "Lint, formatter and TypeScript presets for SolidJS applications built with Vite+.",
3
+ "version": "0.4.0",
4
+ "description": "Lint, formatter, TypeScript, test and Vite app presets for SolidJS applications built with Vite+.",
5
5
  "author": "Éverton Toffanetto",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -19,18 +19,27 @@
19
19
  "type": "module",
20
20
  "files": [
21
21
  "dist",
22
- "tsconfig.base.json"
22
+ "tsconfig.base.json",
23
+ "tsconfig.app.json"
23
24
  ],
24
25
  "exports": {
25
26
  "./architecture-plugin": "./dist/architecture-plugin.mjs",
26
27
  "./backend-plugin": "./dist/backend-plugin.mjs",
27
28
  "./css-filename": "./dist/css-filename.mjs",
28
29
  "./fmt": "./dist/fmt.mjs",
30
+ "./knip": "./dist/knip.mjs",
29
31
  "./lint": "./dist/lint.mjs",
32
+ "./playwright": "./dist/playwright.mjs",
33
+ "./playwright-teardown": "./dist/playwright-teardown.mjs",
30
34
  "./project-plugin": "./dist/project-plugin.mjs",
31
35
  "./solid-plugin": "./dist/solid-plugin.mjs",
36
+ "./stylelint": "./dist/stylelint.mjs",
37
+ "./vite": "./dist/vite.mjs",
38
+ "./vitest": "./dist/vitest.mjs",
39
+ "./vitest-setup/browser": "./dist/vitest-setup/browser.mjs",
32
40
  "./package.json": "./package.json",
33
- "./tsconfig.base.json": "./tsconfig.base.json"
41
+ "./tsconfig.base.json": "./tsconfig.base.json",
42
+ "./tsconfig.app.json": "./tsconfig.app.json"
34
43
  },
35
44
  "publishConfig": {
36
45
  "access": "public"
@@ -42,17 +51,51 @@
42
51
  "test:tarball": "vp run test-tarball"
43
52
  },
44
53
  "dependencies": {
45
- "eslint-plugin-solid": "0.19.0"
54
+ "eslint-plugin-solid": "0.19.0",
55
+ "stylelint": "18.0.0",
56
+ "stylelint-use-nesting": "6.0.2"
46
57
  },
47
58
  "peerDependencies": {
48
59
  "@inlang/paraglide-js": "3.2.2",
49
- "vite-plus": "1.6.0"
60
+ "vite-plus": "1.6.0",
61
+ "@playwright/test": "1.65.0",
62
+ "@solidjs/vite-plugin": "3.0.0-next.57",
63
+ "filesystem-routing": "0.5.0",
64
+ "knip": "6.45.1",
65
+ "nitro": "3.0.261103-rc",
66
+ "unplugin-icons": "24.0.0"
50
67
  },
51
68
  "devDependencies": {
52
69
  "@inlang/paraglide-js": "3.2.2",
53
70
  "@types/node": "24.19.2",
54
71
  "typescript": "7.0.2",
55
72
  "valibot": "1.5.0",
56
- "vite-plus": "1.6.0"
73
+ "vite-plus": "1.6.0",
74
+ "@playwright/test": "1.65.0",
75
+ "@solidjs/vite-plugin": "3.0.0-next.57",
76
+ "filesystem-routing": "0.5.0",
77
+ "knip": "6.45.1",
78
+ "nitro": "3.0.261103-rc",
79
+ "unplugin-icons": "24.0.0"
80
+ },
81
+ "peerDependenciesMeta": {
82
+ "@playwright/test": {
83
+ "optional": true
84
+ },
85
+ "@solidjs/vite-plugin": {
86
+ "optional": true
87
+ },
88
+ "filesystem-routing": {
89
+ "optional": true
90
+ },
91
+ "knip": {
92
+ "optional": true
93
+ },
94
+ "nitro": {
95
+ "optional": true
96
+ },
97
+ "unplugin-icons": {
98
+ "optional": true
99
+ }
57
100
  }
58
101
  }
@@ -0,0 +1,21 @@
1
+ {
2
+ "extends": "./tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "types": [
5
+ "node",
6
+ "vite/client",
7
+ "vite-plus/test/globals",
8
+ "@testing-library/jest-dom/vitest",
9
+ "@solidjs/vite-plugin/boundary-modules",
10
+ "@solidjs/vite-plugin/virtual-modules"
11
+ ]
12
+ },
13
+ "include": [
14
+ "${configDir}/src",
15
+ "${configDir}/env.ts",
16
+ "${configDir}/solid-env.d.ts",
17
+ "${configDir}/vite.config.ts",
18
+ "${configDir}/playwright.config.ts",
19
+ "${configDir}/tooling/**/*.ts"
20
+ ]
21
+ }