@evertondgn/solid-tooling 0.3.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.
@@ -1,5 +1,5 @@
1
- import { env } from "node:process";
2
1
  import { readFile } from "node:fs/promises";
2
+ import { env } from "node:process";
3
3
  import { stripVTControlCharacters } from "node:util";
4
4
  // src/playwright/teardown.ts
5
5
  const PRIVATE_MARKER = /PRIVATE_[A-Z0-9_]+/u;
@@ -1,8 +1,8 @@
1
1
  import path from "node:path";
2
- import { env, loadEnvFile } from "node:process";
3
2
  import { fileURLToPath } from "node:url";
4
- import { defineConfig, devices } from "@playwright/test";
3
+ import { env, loadEnvFile } from "node:process";
5
4
  import { loadEnv } from "vite-plus";
5
+ import { defineConfig as defineConfig$1, devices } from "@playwright/test";
6
6
  // src/playwright/index.ts
7
7
  const STARTUP_TIMEOUT = 12e4;
8
8
  const SECONDARY_PORT_OFFSET = 2;
@@ -121,7 +121,7 @@ function definePlaywrightConfig({ root, testDir = "src/tests/pages", fixtureServ
121
121
  env.SERVER_LOG_SECONDARY = path.join(root, SECONDARY_LOG);
122
122
  }
123
123
  const reportFolder = mode.isProduction ? "production" : "e2e";
124
- return defineConfig({
124
+ return defineConfig$1({
125
125
  testDir: path.join(root, testDir),
126
126
  testMatch: mode.isProduction ? "**/*.production.e2e.test.ts" : "**/*.e2e.test.ts",
127
127
  outputDir: path.join(root, mode.isProduction ? "test-results/e2e-production" : "test-results/e2e"),
@@ -0,0 +1,8 @@
1
+ import { Config } from "stylelint";
2
+ type CssCheck = {
3
+ report: string;
4
+ hasErrors: boolean;
5
+ };
6
+ export declare function defineStylelintConfig(appRoot: string): Promise<Config>;
7
+ export declare function checkCss(appRoot: string): Promise<CssCheck>;
8
+ export {};
@@ -0,0 +1,180 @@
1
+ import { cssFilenameError } from "./css-filename.mjs";
2
+ import { t as hasMatch } from "./has-match-BITlj0EE.mjs";
3
+ import path from "node:path";
4
+ import { readdir } from "node:fs/promises";
5
+ import { fileURLToPath } from "node:url";
6
+ import stylelint from "stylelint";
7
+ // src/stylelint/index.ts
8
+ const VAR = String.raw`var\(\s*--[a-z0-9-]+\s*\)`;
9
+ const TOKEN = String.raw`/^${VAR}(?:\s*[,/]\s*${VAR}|\s+${VAR})*$/`;
10
+ const LINE_HEIGHT = String.raw`(?:\d*\.?\d+(?:%|[a-z]+)?|normal)`;
11
+ const FONT_TOKEN = String.raw`/^${VAR}(?:\s+${VAR})*(?:\s*\/\s*(?:${LINE_HEIGHT}|${VAR})(?:\s+${VAR})*)?$/`;
12
+ const TOKEN_KEYWORDS = "/^(?:transparent|currentcolor|inherit|initial|unset|revert|revert-layer|none|auto)$/i";
13
+ const TOKEN_FAMILIES = [
14
+ "color",
15
+ "gradient",
16
+ "radius",
17
+ "shadow",
18
+ "ring",
19
+ "font-size",
20
+ "font-weight",
21
+ "z-index"
22
+ ];
23
+ const REFERENCE_FILES = ["src/theme/tokens/**/*.css", "src/theme/class/animation.css"];
24
+ const SYSTEM_COLORS = `
25
+ AccentColor AccentColorText ActiveText ButtonBorder ButtonFace ButtonText
26
+ Canvas CanvasText Field FieldText GrayText Highlight HighlightText LinkText
27
+ Mark MarkText SelectedItem SelectedItemText VisitedText
28
+ ActiveBorder ActiveCaption AppWorkspace Background ButtonHighlight
29
+ ButtonShadow CaptionText InactiveBorder InactiveCaption InactiveCaptionText
30
+ InfoBackground InfoText Menu MenuText Scrollbar ThreeDDarkShadow ThreeDFace
31
+ ThreeDHighlight ThreeDLightShadow ThreeDShadow Window WindowFrame WindowText
32
+ -webkit-activelink -webkit-focus-ring-color -webkit-link -webkit-text
33
+ `.trim().split(/\s+/u);
34
+ const SYSTEM_COLOR = String.raw`/(?<![\w#./-])(?:${SYSTEM_COLORS.join("|")})(?![\w.-])/i`;
35
+ const NAME_PROPERTIES = String.raw`animation(?:-name)?|transition(?:-property)?|will-change|grid(?:-[a-z]+)*|container(?:-name)?|counter-(?:increment|reset|set)|view-transition-(?:class|name)|font-family|content|quotes|list-style(?:-type)?|page`;
36
+ const CSS_FILES = "src/**/*.css";
37
+ function property(names) {
38
+ return String.raw`/^(?!--)(?:-[a-z]+-)?(?:${names})$/i`;
39
+ }
40
+ const TOKEN_PROPERTIES = [
41
+ [property("(?:[a-z]+-)*color|fill|stroke"), ["/^--color-/"]],
42
+ [property("(?:[a-z]+-)*radius"), ["/^--radius-/"]],
43
+ [property("background"), ["/^--(?:color|gradient)-/"]],
44
+ [property("background-image"), ["/^--gradient-/"]],
45
+ [property("box-shadow"), ["/^--(?:shadow|ring)-/"]],
46
+ [property("text-shadow"), []],
47
+ [
48
+ property("font"),
49
+ ["/^--font-/"],
50
+ FONT_TOKEN
51
+ ],
52
+ [property("font-size"), ["/^--font-size-/"]],
53
+ [property("font-weight"), ["/^--font-weight-/"]],
54
+ [property("z-index"), ["/^--z-index-/"]],
55
+ ...TOKEN_FAMILIES.map((family) => [`/^--${family}-/`, [`/^--${family}-/`]]),
56
+ [
57
+ "/^--font-(?!size-|weight-)/",
58
+ ["/^--font-/"],
59
+ FONT_TOKEN
60
+ ]
61
+ ];
62
+ async function referenceFiles(root) {
63
+ return (await Promise.all(REFERENCE_FILES.map(async (pattern) => await hasMatch({
64
+ root,
65
+ pattern
66
+ }) ? [path.join(root, pattern)] : []))).flat();
67
+ }
68
+ async function filenameDiagnostics(root) {
69
+ return (await readdir(path.join(root, "src"), {
70
+ recursive: true,
71
+ withFileTypes: true
72
+ })).flatMap((entry) => {
73
+ if (!entry.isFile()) return [];
74
+ const absolute = path.join(entry.parentPath, entry.name);
75
+ const file = path.relative(root, absolute).split(path.sep).join("/");
76
+ if (!file.endsWith(".css")) return [];
77
+ const message = cssFilenameError(file);
78
+ return message ? [{
79
+ file,
80
+ message
81
+ }] : [];
82
+ });
83
+ }
84
+ async function defineStylelintConfig(appRoot) {
85
+ const root = path.resolve(appRoot);
86
+ return {
87
+ referenceFiles: await referenceFiles(root),
88
+ reportDescriptionlessDisables: true,
89
+ plugins: [fileURLToPath(import.meta.resolve("stylelint-use-nesting"))],
90
+ rules: {
91
+ "property-no-unknown": true,
92
+ "declaration-property-value-no-unknown": true,
93
+ "value-no-invalid": true,
94
+ "unit-no-unknown": true,
95
+ "color-no-invalid-hex": true,
96
+ "color-no-hex": true,
97
+ "color-named": "never",
98
+ "declaration-block-no-duplicate-properties": [true, { ignore: ["consecutive-duplicates-with-different-syntaxes"] }],
99
+ "declaration-block-no-shorthand-property-overrides": true,
100
+ "selector-pseudo-class-no-unknown": true,
101
+ "selector-pseudo-element-no-unknown": true,
102
+ "selector-type-no-unknown": true,
103
+ "at-rule-no-unknown": true,
104
+ "no-unknown-animations": true,
105
+ "no-unknown-custom-properties": true,
106
+ "custom-property-no-missing-var-function": true,
107
+ "custom-property-pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
108
+ "selector-no-invalid": true,
109
+ "selector-no-deprecated": true,
110
+ "selector-no-unmatchable": true,
111
+ "at-charset-rule-no-invalid": true,
112
+ "media-query-no-invalid": true,
113
+ "media-feature-name-no-unknown": true,
114
+ "media-feature-name-value-no-unknown": true,
115
+ "function-calc-no-unspaced-operator": true,
116
+ "no-invalid-position-at-import-rule": true,
117
+ "no-duplicate-selectors": true,
118
+ "keyframe-block-no-duplicate-selectors": true,
119
+ "function-disallowed-list": [
120
+ "rgb",
121
+ "rgba",
122
+ "hsl",
123
+ "hsla",
124
+ "hwb",
125
+ "lab",
126
+ "lch",
127
+ "oklab",
128
+ "oklch",
129
+ "color",
130
+ "color-mix",
131
+ "light-dark",
132
+ "contrast-color",
133
+ "device-cmyk"
134
+ ],
135
+ "value-type-custom-property-required-list": [["color"], { ignoreValues: [TOKEN_KEYWORDS] }],
136
+ "declaration-property-value-allowed-list": [Object.fromEntries(TOKEN_PROPERTIES.map(([name, , value = TOKEN]) => [name, [value, TOKEN_KEYWORDS]])), { message: "Use only var() tokens in this property; an exception needs a stylelint-disable comment with the reason." }],
137
+ "declaration-property-custom-property-allowed-list": [Object.fromEntries(TOKEN_PROPERTIES.map(([name, families]) => [name, families])), { message: "The property \"%s\" does not accept the token \"%s\"." }],
138
+ "declaration-property-value-disallowed-list": [{ [String.raw`/^(?!(?:-[a-z]+-)?(?:${NAME_PROPERTIES})$)/i`]: [SYSTEM_COLOR] }, { message: "System colors belong in src/theme/." }],
139
+ "selector-class-pattern": ["^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$", { message: "Use snake_case for CSS classes, as in variant_default." }],
140
+ "max-nesting-depth": 2,
141
+ "nesting-selector-no-missing-scoping-root": true,
142
+ "csstools/use-nesting": "always"
143
+ },
144
+ overrides: [{
145
+ files: [path.join(root, "src/theme/**/*.css")],
146
+ rules: {
147
+ "function-disallowed-list": [
148
+ "rgb",
149
+ "rgba",
150
+ "hsl",
151
+ "hsla",
152
+ "hwb",
153
+ "lab",
154
+ "lch",
155
+ "color"
156
+ ],
157
+ "value-type-custom-property-required-list": null,
158
+ "declaration-property-value-allowed-list": null,
159
+ "declaration-property-custom-property-allowed-list": null,
160
+ "declaration-property-value-disallowed-list": null
161
+ }
162
+ }]
163
+ };
164
+ }
165
+ async function checkCss(appRoot) {
166
+ const root = path.resolve(appRoot);
167
+ const config = await defineStylelintConfig(root);
168
+ const [filenames, result] = await Promise.all([filenameDiagnostics(root), stylelint.lint({
169
+ cwd: root,
170
+ files: [CSS_FILES],
171
+ config,
172
+ formatter: "string"
173
+ })]);
174
+ const filenameReport = filenames.map(({ file, message }) => `${file}:1 error project/css-filename: ${message}\n`).join("");
175
+ return {
176
+ report: result.report + filenameReport,
177
+ hasErrors: result.errored || filenames.length > 0
178
+ };
179
+ }
180
+ export { checkCss, defineStylelintConfig };
@@ -0,0 +1,40 @@
1
+ import { PluginOption, UserConfig, ViteUserConfigFnObject } from "vite-plus";
2
+ import { NitroConfig, NitroModuleInput, PublicAssetDir } from "nitro/types";
3
+ type StaticSecurityHeaders = Record<'strict-transport-security' | 'x-content-type-options' | 'cross-origin-resource-policy', string>;
4
+ type E2eOptions = {
5
+ middleware?: string;
6
+ routes?: string;
7
+ };
8
+ type WorkspaceOptions = {
9
+ dependsOn: string[];
10
+ inputs: string[];
11
+ };
12
+ type StaticAssetDir = PublicAssetDir & {
13
+ baseURL: string;
14
+ };
15
+ type RouteRule = NonNullable<NitroConfig['routeRules']>[string];
16
+ type RunTask = NonNullable<NonNullable<UserConfig['run']>['tasks']>[string];
17
+ type ExtensionContext = {
18
+ target: 'app' | 'test';
19
+ mode: string;
20
+ };
21
+ type SolidAppExtension = {
22
+ plugins?: PluginOption[];
23
+ nitroModules?: NitroModuleInput[];
24
+ };
25
+ type SolidAppOptions = {
26
+ root: string;
27
+ middleware?: string;
28
+ serverSetup?: string;
29
+ e2e?: E2eOptions;
30
+ icons?: Record<string, string>;
31
+ securityHeaders: StaticSecurityHeaders;
32
+ publicAssets?: StaticAssetDir[];
33
+ staticPaths?: string[];
34
+ routeRules?: Record<string, RouteRule>;
35
+ extend?: (context: ExtensionContext) => SolidAppExtension | Promise<SolidAppExtension>;
36
+ workspace?: WorkspaceOptions;
37
+ tasks?: Record<string, RunTask>;
38
+ };
39
+ export declare function defineSolidApp(options: SolidAppOptions): ViteUserConfigFnObject;
40
+ export type { SolidAppOptions };
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 };
package/dist/vitest.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
- import { env } from "node:process";
3
2
  import { fileURLToPath } from "node:url";
3
+ import { env } from "node:process";
4
4
  import { playwright } from "vite-plus/test/browser-playwright";
5
5
  // src/vitest/index.ts
6
6
  const CI_WORKERS = 2;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@evertondgn/solid-tooling",
3
- "version": "0.3.0",
4
- "description": "Lint, formatter, TypeScript and test 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": {
@@ -27,11 +27,14 @@
27
27
  "./backend-plugin": "./dist/backend-plugin.mjs",
28
28
  "./css-filename": "./dist/css-filename.mjs",
29
29
  "./fmt": "./dist/fmt.mjs",
30
+ "./knip": "./dist/knip.mjs",
30
31
  "./lint": "./dist/lint.mjs",
31
32
  "./playwright": "./dist/playwright.mjs",
32
33
  "./playwright-teardown": "./dist/playwright-teardown.mjs",
33
34
  "./project-plugin": "./dist/project-plugin.mjs",
34
35
  "./solid-plugin": "./dist/solid-plugin.mjs",
36
+ "./stylelint": "./dist/stylelint.mjs",
37
+ "./vite": "./dist/vite.mjs",
35
38
  "./vitest": "./dist/vitest.mjs",
36
39
  "./vitest-setup/browser": "./dist/vitest-setup/browser.mjs",
37
40
  "./package.json": "./package.json",
@@ -48,12 +51,19 @@
48
51
  "test:tarball": "vp run test-tarball"
49
52
  },
50
53
  "dependencies": {
51
- "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"
52
57
  },
53
58
  "peerDependencies": {
54
59
  "@inlang/paraglide-js": "3.2.2",
55
60
  "vite-plus": "1.6.0",
56
- "@playwright/test": "1.65.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"
57
67
  },
58
68
  "devDependencies": {
59
69
  "@inlang/paraglide-js": "3.2.2",
@@ -61,11 +71,31 @@
61
71
  "typescript": "7.0.2",
62
72
  "valibot": "1.5.0",
63
73
  "vite-plus": "1.6.0",
64
- "@playwright/test": "1.65.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"
65
80
  },
66
81
  "peerDependenciesMeta": {
67
82
  "@playwright/test": {
68
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
69
99
  }
70
100
  }
71
101
  }