startx 0.7.2 → 0.8.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 (34) hide show
  1. package/.prettierrc.js +2 -2
  2. package/.vscode/settings.json +2 -1
  3. package/apps/core-server/package.json +1 -1
  4. package/apps/core-server/src/index.ts +0 -1
  5. package/apps/startx-cli/dist/index.mjs +2 -2
  6. package/apps/startx-cli/src/commands/init.ts +183 -255
  7. package/apps/startx-cli/src/configs/files.ts +3 -4
  8. package/apps/startx-cli/src/configs/scripts.ts +24 -6
  9. package/apps/startx-cli/src/types.ts +18 -6
  10. package/apps/startx-cli/src/utils/cli-utils.ts +60 -49
  11. package/apps/startx-cli/src/utils/file-handler.ts +8 -3
  12. package/biome.json +1 -1
  13. package/configs/eslint-config/eslint.config.ts +0 -0
  14. package/configs/eslint-config/src/configs/base.ts +32 -79
  15. package/configs/eslint-config/src/configs/extend.ts +2 -2
  16. package/configs/eslint-config/src/configs/frontend.ts +29 -19
  17. package/configs/eslint-config/src/configs/node.ts +46 -6
  18. package/configs/vitest-config/package.json +3 -2
  19. package/package.json +3 -2
  20. package/packages/@repo/db/drizzle.config.ts +14 -0
  21. package/packages/@repo/db/package.json +5 -1
  22. package/packages/@repo/db/src/index.ts +1 -1
  23. package/packages/@repo/lib/src/otp-module/index.ts +6 -13
  24. package/packages/@repo/lib/tsconfig.json +2 -1
  25. package/packages/@repo/mail/eslint.config.ts +2 -2
  26. package/packages/@repo/mail/src/emails/admin/OtpEmail.tsx +3 -9
  27. package/packages/@repo/mail/src/emails/emails.ts +1 -0
  28. package/packages/@repo/mail/src/index.ts +10 -9
  29. package/packages/@repo/mail/tsconfig.json +4 -2
  30. package/packages/ui/package.json +1 -0
  31. package/packages/ui/src/components/ui/command.tsx +5 -15
  32. package/pnpm-workspace.yaml +1 -0
  33. package/turbo.json +9 -0
  34. package/apps/startx-cli/src/utils/config.ts +0 -104
@@ -2,25 +2,37 @@
2
2
  import type { PackageJson } from "type-fest";
3
3
 
4
4
  export type TAGS =
5
- | "required"
5
+ // Always installs
6
6
  | "common"
7
+ // Used for apps
7
8
  | "runnable"
9
+ // Formatter tags
8
10
  | "biome"
9
11
  | "prettier"
10
- | "vitest"
11
12
  | "eslint"
13
+ // Testing tags
14
+ | "vitest"
15
+ // Build tags
12
16
  | "tsdown"
17
+ // Backend tags
13
18
  | "node"
14
19
  | "backend"
15
- | "dev"
20
+ | "express"
21
+ // Frontend tags
16
22
  | "frontend"
23
+ | "react"
24
+ // Cli tags
17
25
  | "cli"
18
26
  | "commander"
19
- | "react"
20
- | "extra"
27
+ // Mail
21
28
  | "mail"
29
+ // Never installs or ignore
22
30
  | "never"
23
- | "root";
31
+ // For handling workspace only files and scripts
32
+ | "root"
33
+ // Database tags
34
+ | "db"
35
+ | "drizzle";
24
36
 
25
37
  export type SCRIPT = Record<string, Array<{ script: string; tags: TAGS[] }>>;
26
38
 
@@ -6,84 +6,94 @@ import z from "zod";
6
6
 
7
7
  import type { PnpmWorkspace, StartXPackageJson } from "../types";
8
8
 
9
- export type RawPackageItem = {
9
+ export type PackageItem = {
10
10
  type: "apps" | "configs" | "packages";
11
11
  path: string;
12
+ relativePath: string;
12
13
  name: string;
14
+ packageJson: StartXPackageJson;
13
15
  };
14
16
 
15
17
  const ENV = defineEnv({
16
18
  STARTX_ENV: z.enum(["development", "production", "test", "staging"]).default("production"),
17
19
  });
20
+
18
21
  export class CliUtils {
19
22
  static getDirectory() {
20
23
  const __filename = fileURLToPath(import.meta.url);
21
24
  let template = path.dirname(__filename);
22
25
  const workspace = process.cwd();
26
+
23
27
  if (ENV.STARTX_ENV === "development") {
24
28
  template = path.resolve(template, "../../../../");
25
- } else template = path.resolve(template, "../../../");
29
+ } else {
30
+ template = path.resolve(template, "../../../");
31
+ }
26
32
 
27
33
  return {
28
34
  template,
29
35
  workspace,
30
36
  };
31
37
  }
32
- static async getPackageList() {
33
- const packages: RawPackageItem[] = [];
38
+
39
+ static async getPackageList(): Promise<PackageItem[]> {
34
40
  const cliDirectory = this.getDirectory().template;
35
- const safeListDir = async (dir: string): Promise<string[]> => {
41
+
42
+ const fetchPackages = async (
43
+ subPath: string,
44
+ type: "apps" | "configs" | "packages",
45
+ namePrefix = "",
46
+ filterFn?: (name: string) => boolean
47
+ ): Promise<PackageItem[]> => {
48
+ const dirPath = path.join(cliDirectory, ...subPath.split("/"));
49
+
36
50
  try {
37
- return await fsTool.listDirectories({ dir });
51
+ let names = await fsTool.listDirectories({ dir: dirPath });
52
+ if (filterFn) names = names.filter(filterFn);
53
+
54
+ const packages = await Promise.all(
55
+ names.map(async name => {
56
+ const pkgPath = path.join(dirPath, name);
57
+ const relativePath = path.relative(cliDirectory, pkgPath);
58
+ const pkgName = namePrefix ? `${namePrefix}${name}` : name;
59
+ let packageJson;
60
+
61
+ try {
62
+ packageJson = await this.parsePackageJson({ dir: pkgPath });
63
+ } catch {
64
+ packageJson = null;
65
+ }
66
+
67
+ if (!packageJson) {
68
+ console.error(`Ignoring this package failed to read package.json: ${pkgName}`);
69
+ return null;
70
+ }
71
+
72
+ return {
73
+ type,
74
+ path: pkgPath,
75
+ relativePath,
76
+ name: pkgName,
77
+ packageJson,
78
+ };
79
+ })
80
+ );
81
+
82
+ return packages.filter((pkg): pkg is PackageItem => pkg !== null);
38
83
  } catch (error) {
39
- console.error("Error listing directory:", error);
84
+ console.error(`Error reading directory ${dirPath}:`, error);
40
85
  return [];
41
86
  }
42
87
  };
43
88
 
44
- const [availableApps, availableConfigs, availablePackages, availableRepoPackages] =
45
- await Promise.all([
46
- safeListDir(path.join(cliDirectory, "apps")),
47
- safeListDir(path.join(cliDirectory, "configs")),
48
- safeListDir(path.join(cliDirectory, "packages")),
49
- safeListDir(path.join(cliDirectory, "packages", "@repo")),
50
- ]);
51
-
52
- // 2. Map Apps
53
- availableApps.forEach(name => {
54
- packages.push({
55
- type: "apps",
56
- path: path.join(cliDirectory, "apps", name),
57
- name,
58
- });
59
- });
89
+ const results = await Promise.all([
90
+ fetchPackages("apps", "apps"),
91
+ fetchPackages("configs", "configs"),
92
+ fetchPackages("packages", "packages", "", name => name !== "@repo"),
93
+ fetchPackages("packages/@repo", "packages", "@repo/"),
94
+ ]);
60
95
 
61
- availableConfigs.forEach(name => {
62
- packages.push({
63
- type: "configs",
64
- path: path.join(cliDirectory, "configs", name),
65
- name,
66
- });
67
- });
68
-
69
- availablePackages.forEach(name => {
70
- if (name === "@repo") return;
71
- packages.push({
72
- type: "packages",
73
- path: path.join(cliDirectory, "packages", name),
74
- name,
75
- });
76
- });
77
-
78
- availableRepoPackages.forEach(name => {
79
- packages.push({
80
- type: "packages",
81
- path: path.join(cliDirectory, "packages", "@repo", name),
82
- name: `@repo/${name}`,
83
- });
84
- });
85
-
86
- return packages;
96
+ return results.flat();
87
97
  }
88
98
 
89
99
  static async parsePackageJson({ dir, file = "package" }: { dir: string; file?: string }) {
@@ -93,6 +103,7 @@ export class CliUtils {
93
103
  });
94
104
  return packageJson;
95
105
  }
106
+
96
107
  static async parsePnpmWorkspace({ dir }: { dir: string }) {
97
108
  const yaml = await fsTool.readYamlFile({
98
109
  file: "pnpm-workspace",
@@ -5,9 +5,7 @@ import type { StartXPackageJson, TAGS } from "../types";
5
5
 
6
6
  export class FileHandler {
7
7
  private static objSorter(obj: Record<string, unknown>, sorter: string[] = []) {
8
- const cleaned = Object.fromEntries(
9
- Object.entries(obj).filter(([, v]) => v !== null && v !== undefined)
10
- );
8
+ const cleaned = Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null && v !== undefined));
11
9
 
12
10
  const sortedEntries: Array<[string, unknown]> = [];
13
11
 
@@ -63,6 +61,13 @@ export class FileHandler {
63
61
  const devDependencies = filterDeps(props.app.devDependencies as Record<string, string>);
64
62
 
65
63
  // Removing all workspace dependencies
64
+ for (const [key, value] of Object.entries(dependencies)) {
65
+ if (value.includes("workspace:")) {
66
+ delete dependencies[key];
67
+ }
68
+ }
69
+
70
+ // Removing all workspace dev dependencies
66
71
  for (const [key, value] of Object.entries(devDependencies)) {
67
72
  if (value.includes("workspace:")) {
68
73
  delete devDependencies[key];
package/biome.json CHANGED
@@ -28,7 +28,7 @@
28
28
  "indentStyle": "tab",
29
29
  "indentWidth": 2,
30
30
  "lineEnding": "lf",
31
- "lineWidth": 100,
31
+ "lineWidth": 120,
32
32
  "attributePosition": "auto"
33
33
  },
34
34
  "assist": { "actions": { "source": { "organizeImports": "off" } } },
File without changes
@@ -1,20 +1,15 @@
1
1
  import { fixupPluginRules } from "@eslint/compat";
2
- import js from "@eslint/js";
3
- import stylisticPlugin from "@stylistic/eslint-plugin";
4
- import { defineConfig } from "eslint/config";
5
2
  import eslintConfigPrettier from "eslint-config-prettier";
6
- import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
7
3
  import importPlugin from "eslint-plugin-import-x";
8
4
  import lodashPlugin from "eslint-plugin-lodash";
9
- import reactPlugin from "eslint-plugin-react";
10
- import reactHooksPlugin from "eslint-plugin-react-hooks";
11
5
  import unicornPlugin from "eslint-plugin-unicorn";
12
6
  import unusedImportsPlugin from "eslint-plugin-unused-imports";
13
7
  import globals from "globals";
14
8
  import tseslint from "typescript-eslint";
15
9
  import { localRulesPlugin } from "../plugin.js";
16
10
 
17
- export const baseConfig = defineConfig(
11
+ // Exported as a standard array for Flat Config
12
+ export const baseConfig = tseslint.config(
18
13
  // 1. Global Ignores
19
14
  {
20
15
  ignores: [
@@ -29,11 +24,11 @@ export const baseConfig = defineConfig(
29
24
  "vite.config.ts",
30
25
  "vitest.config.ts",
31
26
  "tsdown.config.ts",
27
+ "drizzle.config.ts",
32
28
  ],
33
29
  },
34
30
 
35
31
  // 2. Base Configurations
36
- js.configs.recommended,
37
32
  ...tseslint.configs.recommended,
38
33
  ...tseslint.configs.recommendedTypeChecked,
39
34
  importPlugin.flatConfigs.recommended as any,
@@ -43,17 +38,14 @@ export const baseConfig = defineConfig(
43
38
  {
44
39
  plugins: {
45
40
  "unused-imports": fixupPluginRules(unusedImportsPlugin),
46
- "react-hooks": fixupPluginRules(reactHooksPlugin as any),
47
41
  lodash: fixupPluginRules(lodashPlugin),
48
- "@stylistic": stylisticPlugin as any,
49
42
  unicorn: unicornPlugin,
50
- react: reactPlugin,
43
+ "local-rules": localRulesPlugin,
51
44
  },
52
45
  languageOptions: {
53
- ecmaVersion: 2022,
46
+ ecmaVersion: "latest",
54
47
  sourceType: "module",
55
48
  globals: {
56
- ...globals.browser,
57
49
  ...globals.node,
58
50
  ...globals.es2021,
59
51
  },
@@ -62,25 +54,16 @@ export const baseConfig = defineConfig(
62
54
  tsconfigRootDir: import.meta.dirname,
63
55
  },
64
56
  },
65
- settings: {
66
- "import-x/resolver-next": [createTypeScriptImportResolver()],
67
- react: {
68
- version: "detect",
69
- },
70
- },
71
57
  rules: {
72
58
  // Core Rules
73
59
  "no-void": ["error", { allowAsStatement: true }],
74
60
  "no-constant-binary-expression": "error",
75
- "no-console": ["warn", { allow: ["warn", "error"] }],
76
- eqeqeq: "warn",
61
+ "no-console": ["warn", { allow: ["warn", "error", "info"] }], // Allowed info for debugging
62
+ eqeqeq: ["warn", "always", { null: "ignore" }],
77
63
  "object-shorthand": "warn",
78
64
  "prefer-const": "warn",
79
65
 
80
- "id-denylist": "off",
81
- "import-x/no-named-as-default": "off",
82
-
83
- // TypeScript
66
+ // TypeScript Quality of Life
84
67
  "@typescript-eslint/await-thenable": "error",
85
68
  "@typescript-eslint/no-floating-promises": ["error", { ignoreVoid: true }],
86
69
  "@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: false }],
@@ -90,75 +73,60 @@ export const baseConfig = defineConfig(
90
73
  "@typescript-eslint/no-unsafe-argument": "warn",
91
74
  "@typescript-eslint/no-unsafe-call": "warn",
92
75
  "@typescript-eslint/no-unsafe-return": "off",
93
- "@typescript-eslint/no-unsafe-member-access": ["error", { "allowOptionalChaining": true }],
76
+ "@typescript-eslint/no-unsafe-member-access": ["warn", { allowOptionalChaining: true }],
94
77
  "@typescript-eslint/array-type": ["warn", { default: "array-simple" }],
95
- "@typescript-eslint/consistent-type-assertions": "warn",
96
- "@typescript-eslint/consistent-type-imports": "warn",
97
- "@typescript-eslint/consistent-type-exports": "warn",
98
78
  "@typescript-eslint/return-await": ["warn", "always"],
79
+ "@typescript-eslint/no-empty-object-type": ["warn"],
99
80
 
100
- "@typescript-eslint/explicit-member-accessibility": "off",
101
-
81
+ // Naming Conventions (Relaxed for APIs and strict for standard code)
102
82
  "@typescript-eslint/naming-convention": [
103
83
  "warn",
104
84
  { selector: "default", format: ["camelCase"] },
105
-
106
85
  { selector: "import", format: ["camelCase", "PascalCase"] },
107
-
108
86
  {
109
87
  selector: "variable",
110
88
  format: ["camelCase", "snake_case", "UPPER_CASE", "PascalCase"],
111
- leadingUnderscore: "allowSingleOrDouble",
112
- trailingUnderscore: "allowSingleOrDouble",
89
+ leadingUnderscore: "allow",
113
90
  },
114
-
115
91
  {
116
92
  selector: "parameter",
117
93
  format: ["camelCase"],
118
- leadingUnderscore: "allowSingleOrDouble",
94
+ leadingUnderscore: "allow",
119
95
  },
120
-
121
96
  { selector: "typeLike", format: ["PascalCase"] },
122
- { selector: "enumMember", format: ["UPPER_CASE", "PascalCase"] },
123
- ],
124
- "@typescript-eslint/no-restricted-types": [
125
- "warn",
126
97
  {
127
- types: {
128
- Object: { message: "Use object instead", fixWith: "object" },
129
- String: { message: "Use string instead", fixWith: "string" },
130
- Boolean: { message: "Use boolean instead", fixWith: "boolean" },
131
- Number: { message: "Use number instead", fixWith: "number" },
132
- Symbol: { message: "Use symbol instead", fixWith: "symbol" },
133
- },
98
+ selector: ["objectLiteralProperty", "typeProperty"],
99
+ format: null, // Critical: Allows API payloads with snake_case or headers
134
100
  },
135
101
  ],
136
- "@typescript-eslint/no-unused-vars": "warn",
137
- "@typescript-eslint/no-empty-object-type": "warn",
138
- // Stylistic
139
- "@stylistic/member-delimiter-style": [
102
+
103
+ // Handled entirely by unused-imports
104
+ "@typescript-eslint/no-unused-vars": "off",
105
+ "unused-imports/no-unused-imports": "warn",
106
+ "unused-imports/no-unused-vars": [
140
107
  "warn",
141
108
  {
142
- multiline: { delimiter: "semi", requireLast: true },
143
- singleline: { delimiter: "semi", requireLast: false },
109
+ vars: "all",
110
+ varsIgnorePattern: "^_",
111
+ args: "after-used",
112
+ argsIgnorePattern: "^_",
144
113
  },
145
114
  ],
146
115
 
116
+ // Imports
147
117
  "import-x/no-cycle": "off",
148
- "import-x/no-default-export": "off",
149
-
118
+ "import-x/no-named-as-default": "off",
150
119
  "import-x/no-duplicates": "warn",
151
120
  "import-x/order": [
152
121
  "warn",
153
122
  {
154
123
  alphabetize: { order: "asc", caseInsensitive: true },
155
124
  groups: [["builtin", "external"], "internal", ["parent", "index", "sibling"], "object"],
156
- "newlines-between": "always",
125
+ // "newlines-between": "always",
157
126
  },
158
127
  ],
159
128
 
160
129
  // Plugins
161
- "unused-imports/no-unused-imports": "warn",
162
130
  "unicorn/no-unnecessary-await": "warn",
163
131
  "unicorn/no-useless-promise-resolve-reject": "warn",
164
132
  "lodash/path-style": ["warn", "as-needed"],
@@ -166,32 +134,17 @@ export const baseConfig = defineConfig(
166
134
  },
167
135
  },
168
136
 
169
- // 4. React Specific Overrides
137
+ // 4. Test Overrides
170
138
  {
171
- files: ["**/*.tsx", "**/*.jsx"],
172
- ...reactPlugin.configs.flat.recommended,
173
- ...reactPlugin.configs.flat["jsx-runtime"],
174
- rules: {
175
- ...reactHooksPlugin.configs.recommended.rules,
176
- "react/prop-types": "off",
177
- "react/react-in-jsx-scope": "off",
178
- "react/jsx-no-leaked-render": "error", // Security/Perf rule (Keep as error)
179
- },
180
- },
181
-
182
- // 5. Test Overrides
183
- {
184
- files: ["test/**/*.ts", "**/__tests__/*.ts", "**/*.test.ts", "**/*.cy.ts"],
139
+ files: ["test/**/*.ts", "**/__tests__/*.ts", "**/*.test.ts", "**/*.cy.ts", "**/*.spec.ts"],
185
140
  rules: {
186
141
  "local-rules/no-plain-errors": "off",
187
142
  "@typescript-eslint/unbound-method": "off",
188
- "import-x/no-default-export": "off",
143
+ "@typescript-eslint/no-unsafe-call": "off",
144
+ "@typescript-eslint/no-unsafe-member-access": "off",
189
145
  },
190
146
  },
191
147
 
192
- // 6. Local Rules
193
- localRulesPlugin.configs.recommended,
194
-
195
- // 7. Prettier (Must be last to cleanly override stylistic rules)
148
+ // 5. Prettier (Must be absolutely last)
196
149
  eslintConfigPrettier
197
150
  );
@@ -1,3 +1,3 @@
1
- import { defineConfig } from "eslint/config";
1
+ import tseslint from "typescript-eslint";
2
2
 
3
- export const extend = defineConfig;
3
+ export const extend = tseslint.config;
@@ -1,23 +1,21 @@
1
1
  import { fixupPluginRules } from "@eslint/compat";
2
- import { defineConfig } from "eslint/config";
2
+ import eslintConfigPrettier from "eslint-config-prettier";
3
3
  import jsxA11yPlugin from "eslint-plugin-jsx-a11y";
4
4
  import reactPlugin from "eslint-plugin-react";
5
5
  import reactHooksPlugin from "eslint-plugin-react-hooks";
6
6
  import globals from "globals";
7
+ import tseslint from "typescript-eslint";
8
+
7
9
  import { baseConfig } from "./base.js";
8
- export const frontendConfig = defineConfig(
9
- ...baseConfig,
10
+
11
+ const baseWithoutPrettier = baseConfig.filter(config => config !== eslintConfigPrettier);
12
+
13
+ export const frontendConfig = tseslint.config(
14
+ ...baseWithoutPrettier,
10
15
 
11
16
  // 1. Frontend Ignores
12
17
  {
13
- ignores: [
14
- "**/dist/**",
15
- "**/coverage/**",
16
- "**/storybook-static/**",
17
- "**/*.snap",
18
- "**/*.d.ts",
19
- "vite.config.ts",
20
- ],
18
+ ignores: ["**/coverage/**", "**/storybook-static/**", "**/*.snap", "**/*.d.ts"],
21
19
  },
22
20
 
23
21
  // 2. Browser/React Globals & Settings
@@ -37,8 +35,8 @@ export const frontendConfig = defineConfig(
37
35
  },
38
36
 
39
37
  // 3. React Recommended Configs
40
- reactPlugin.configs.flat.recommended,
41
- reactPlugin.configs.flat["jsx-runtime"],
38
+ reactPlugin.configs.flat.recommended as any,
39
+ reactPlugin.configs.flat["jsx-runtime"] as any,
42
40
 
43
41
  // 4. React, Hooks, and Accessibility Rules
44
42
  {
@@ -52,6 +50,7 @@ export const frontendConfig = defineConfig(
52
50
  ...jsxA11yPlugin.configs.recommended.rules,
53
51
 
54
52
  // --- Naming Convention Override for React ---
53
+ // Allows PascalCase for functional components while keeping variables camelCase
55
54
  "@typescript-eslint/naming-convention": [
56
55
  "warn",
57
56
  { selector: "default", format: ["camelCase"] },
@@ -60,17 +59,18 @@ export const frontendConfig = defineConfig(
60
59
  {
61
60
  selector: "variable",
62
61
  format: ["camelCase", "snake_case", "UPPER_CASE", "PascalCase"],
63
- leadingUnderscore: "allowSingleOrDouble",
64
- trailingUnderscore: "allowSingleOrDouble",
62
+ leadingUnderscore: "allow",
65
63
  },
66
64
  { selector: "typeLike", format: ["PascalCase"] },
67
- { selector: "enumMember", format: ["UPPER_CASE", "PascalCase"] },
68
65
  ],
69
66
 
70
67
  // --- React Specifics ---
71
- "react/prop-types": "off",
68
+ "react/prop-types": "off", // Using TS interfaces instead
69
+ "react/react-in-jsx-scope": "off", // Not needed for React 17+
70
+ "react/jsx-uses-react": "off", // Not needed for React 17+
72
71
  "react/display-name": "warn",
73
72
  "react/no-unescaped-entities": "warn",
73
+ "react/jsx-no-leaked-render": ["error", { validStrategies: ["ternary", "coerce"] }],
74
74
 
75
75
  // --- React Hooks Specifics ---
76
76
  "react-hooks/rules-of-hooks": "error",
@@ -81,7 +81,17 @@ export const frontendConfig = defineConfig(
81
81
  "jsx-a11y/click-events-have-key-events": "warn",
82
82
  "jsx-a11y/no-static-element-interactions": "warn",
83
83
  "jsx-a11y/anchor-is-valid": "warn",
84
- "jsx-a11y/no-onchange": "off",
84
+ "jsx-a11y/no-onchange": "off", // Deprecated rule, fine to turn off
85
+ },
86
+ },
87
+
88
+ {
89
+ files: ["**/emails/**/*.tsx", "**/emails/**/*.jsx"],
90
+ rules: {
91
+ "react/react-in-jsx-scope": "error",
92
+ "react/jsx-uses-react": "error",
85
93
  },
86
- }
94
+ },
95
+
96
+ eslintConfigPrettier
87
97
  );
@@ -1,10 +1,50 @@
1
- import { defineConfig } from "eslint/config";
2
1
  import globals from "globals";
2
+ import tseslint from "typescript-eslint";
3
+
3
4
  import { baseConfig } from "./base.js";
4
5
 
5
- export const nodeConfig = defineConfig(baseConfig, {
6
- languageOptions: {
7
- ecmaVersion: 2024,
8
- globals: globals.node,
6
+ export const nodeConfig = tseslint.config(
7
+ ...baseConfig,
8
+
9
+ // 1. Backend/Node Specific Globals & Environments
10
+ {
11
+ files: ["**/*.ts", "**/*.js", "**/*.cjs", "**/*.mjs"],
12
+ languageOptions: {
13
+ // "latest" perfectly aligns with Node 20+ (ES2023/ES2024 features)
14
+ ecmaVersion: "latest",
15
+ sourceType: "module",
16
+ globals: {
17
+ ...globals.node,
18
+ ...globals.nodeBuiltin, // Explicitly adds modern Node built-ins (like fetch, structuredClone)
19
+ },
20
+ },
9
21
  },
10
- });
22
+
23
+ // 2. Node-Specific Overrides & Best Practices
24
+ {
25
+ files: ["**/*.ts", "**/*.js"],
26
+ rules: {
27
+ // Enforce the 'node:' protocol for built-ins (e.g., `import fs from 'node:fs'`)
28
+ // This is a modern Node.js standard that improves performance and security
29
+ "unicorn/prefer-node-protocol": "error",
30
+
31
+ // Prevent accidental use of obscure browser globals that sometimes slip through
32
+ "no-restricted-globals": [
33
+ "error",
34
+ {
35
+ name: "name",
36
+ message: "Global 'name' is deprecated. Did you mean to declare a local variable?",
37
+ },
38
+ {
39
+ name: "event",
40
+ message: "Global 'event' is a browser feature. Do not use it in Node.js.",
41
+ },
42
+ ],
43
+
44
+ // In Node apps, developers often accidentally leave floating promises
45
+ // which can cause unhandled promise rejections crashing the server.
46
+ // (This inherits from baseConfig, but I am noting it here as a critical backend safeguard).
47
+ // "@typescript-eslint/no-floating-promises": "error",
48
+ },
49
+ }
50
+ );
@@ -3,9 +3,10 @@
3
3
  "version": "1.5.0",
4
4
  "type": "module",
5
5
  "devDependencies": {
6
- "vitest": "catalog:",
6
+ "jsdom": "^29.0.1",
7
7
  "typescript-config": "workspace:*",
8
- "vite": "catalog:"
8
+ "vite": "catalog:",
9
+ "vitest": "catalog:"
9
10
  },
10
11
  "exports": {
11
12
  ".": "./src/index.ts",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "startx",
3
3
  "description": "",
4
- "version": "0.7.2",
4
+ "version": "0.8.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/avinashid/startx.git"
@@ -31,6 +31,7 @@
31
31
  "typecheck": "turbo typecheck",
32
32
  "clean": "turbo clean",
33
33
  "test": "turbo test",
34
- "format": "turbo format"
34
+ "format": "turbo format",
35
+ "db:push": "turbo db:push"
35
36
  }
36
37
  }
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from "drizzle-kit";
2
+ import { defineEnv } from "@repo/env";
3
+ import z from "zod";
4
+ const env = defineEnv({
5
+ DATABASE_URL: z.string(),
6
+ });
7
+ export default defineConfig({
8
+ out: "./drizzle",
9
+ schema: "./src/schema/index.ts",
10
+ dialect: "postgresql",
11
+ dbCredentials: {
12
+ url: env.DATABASE_URL!,
13
+ },
14
+ });