startx 0.7.2 → 0.9.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 (35) 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 +179 -257
  7. package/apps/startx-cli/src/configs/deps.ts +6 -1
  8. package/apps/startx-cli/src/configs/files.ts +3 -4
  9. package/apps/startx-cli/src/configs/scripts.ts +25 -7
  10. package/apps/startx-cli/src/types.ts +18 -6
  11. package/apps/startx-cli/src/utils/cli-utils.ts +60 -49
  12. package/apps/startx-cli/src/utils/file-handler.ts +14 -5
  13. package/biome.json +1 -1
  14. package/configs/eslint-config/eslint.config.ts +0 -0
  15. package/configs/eslint-config/src/configs/base.ts +32 -79
  16. package/configs/eslint-config/src/configs/extend.ts +2 -2
  17. package/configs/eslint-config/src/configs/frontend.ts +29 -19
  18. package/configs/eslint-config/src/configs/node.ts +46 -6
  19. package/configs/vitest-config/package.json +3 -2
  20. package/package.json +3 -2
  21. package/packages/@repo/db/drizzle.config.ts +14 -0
  22. package/packages/@repo/db/package.json +5 -1
  23. package/packages/@repo/db/src/index.ts +1 -1
  24. package/packages/@repo/lib/src/otp-module/index.ts +6 -13
  25. package/packages/@repo/lib/tsconfig.json +2 -1
  26. package/packages/@repo/mail/eslint.config.ts +2 -2
  27. package/packages/@repo/mail/src/emails/admin/OtpEmail.tsx +3 -9
  28. package/packages/@repo/mail/src/emails/emails.ts +1 -0
  29. package/packages/@repo/mail/src/index.ts +10 -9
  30. package/packages/@repo/mail/tsconfig.json +4 -2
  31. package/packages/ui/package.json +1 -0
  32. package/packages/ui/src/components/ui/command.tsx +5 -15
  33. package/pnpm-workspace.yaml +1 -0
  34. package/turbo.json +9 -0
  35. package/apps/startx-cli/src/utils/config.ts +0 -104
@@ -5,8 +5,8 @@ import path from "path";
5
5
  import z from "zod";
6
6
 
7
7
  import { FileCheck } from "../configs/files";
8
- import type { StartXPackageJson, TAGS } from "../types";
9
- import { CliUtils } from "../utils/cli-utils";
8
+ import type { TAGS } from "../types";
9
+ import { CliUtils, type PackageItem } from "../utils/cli-utils";
10
10
  import { FileHandler } from "../utils/file-handler";
11
11
  import { CommonInquirer } from "../utils/inquirer";
12
12
 
@@ -14,13 +14,6 @@ type InitOptions = {
14
14
  dir?: string;
15
15
  };
16
16
 
17
- type PackageWithJson = {
18
- packageJson: StartXPackageJson | null;
19
- type: "apps" | "configs" | "packages";
20
- path: string;
21
- name: string;
22
- };
23
-
24
17
  export class InitCommand {
25
18
  static command = new Command("init")
26
19
  .argument("[projectName]")
@@ -28,81 +21,63 @@ export class InitCommand {
28
21
  .action(InitCommand.run.bind(InitCommand));
29
22
 
30
23
  private static async run(projectName: string | undefined, options: InitOptions) {
31
- const packageList: PackageWithJson[] = await Promise.all(
32
- (await CliUtils.getPackageList()).map(async e => ({
33
- ...e,
34
- packageJson: await CliUtils.parsePackageJson({ dir: e.path }),
35
- }))
36
- );
37
-
38
- const prefs = await InitCommand.getPrefs({
39
- projectName,
40
- options,
41
- projects: packageList.filter(e => {
42
- if (e.type !== "apps") return false;
43
- if (e.packageJson?.startx?.mode === "silent") return false;
44
- return true;
45
- }),
46
- });
47
-
48
- const packages = await Promise.all(
49
- packageList
50
- .filter(e => e.type !== "apps")
51
- .map(async e => ({
52
- ...e,
53
- packageJson: await CliUtils.parsePackageJson({ dir: e.path }),
54
- }))
55
- );
24
+ const packageList = await CliUtils.getPackageList();
25
+ const availableApps = packageList.filter(pkg => pkg.type === "apps" && pkg.packageJson?.startx?.mode !== "silent");
26
+ const prefs = await this.getPrefs({ projectName, options, projects: availableApps });
27
+ const nonAppPackages = packageList.filter(pkg => pkg.type !== "apps");
56
28
 
57
29
  const config = await this.getConfigPrefs({
58
30
  selectedApps: prefs.selectedApps,
59
- packages,
31
+ packages: nonAppPackages,
60
32
  });
61
-
62
33
  const packagePrefs = await this.getPackagesPrefs({
63
- selectedApps: prefs.selectedApps,
64
34
  selectedPackages: config.selectedConfigs,
65
- packages,
66
- tags: config.tags,
35
+ packages: nonAppPackages,
36
+ tags: config.gTags,
67
37
  });
38
+
39
+ // Installing Workspace
40
+ const workspaceTags = [...packagePrefs.gTags, "runnable"] as TAGS[];
68
41
  await this.installWorkspace({
69
42
  name: prefs.projectName,
70
- tags: [...packagePrefs.tags, "runnable"],
71
- dir: {
72
- workspace: prefs.directory.workspace,
73
- template: prefs.directory.template,
74
- },
43
+ tags: [...workspaceTags, "runnable"],
44
+ dir: prefs.directory,
75
45
  });
76
46
 
47
+ // Installing Apps
48
+ const allSelectedPackages = [...packagePrefs.selectedPackages, ...prefs.selectedApps];
77
49
  await Promise.all(
78
- packagePrefs.selectedPackages.concat(prefs.selectedApps).map(async pkg => {
79
- const appDeps = new Map<string, string>();
80
- const tags = new Set(packagePrefs.tags);
50
+ allSelectedPackages.map(async pkg => {
51
+ const appDeps: Record<string, string> = {};
52
+ const tags = new Set<TAGS>(packagePrefs.gTags);
81
53
 
82
- if (pkg.packageJson?.startx?.mode === "standalone") tags.add("runnable");
54
+ if (pkg.packageJson?.startx?.mode === "standalone") {
55
+ tags.add("runnable");
56
+ }
83
57
 
84
58
  if (pkg.type === "apps") {
85
59
  tags.add("runnable");
86
60
 
87
61
  packagePrefs.selectedPackages
88
- .filter(
89
- e =>
90
- e.packageJson?.startx?.mode !== "standalone" &&
91
- e.packageJson?.startx?.iTags?.every(tag =>
92
- pkg.packageJson?.startx?.tags?.includes(tag)
93
- )
94
- )
95
- .forEach(e => appDeps.set(e.packageJson?.name || e.name, "workspace:^"));
62
+ .filter(depPkg => {
63
+ if (depPkg.type !== "packages") return false;
64
+ if (depPkg.packageJson?.startx?.mode === "standalone") return false;
65
+ const sharesTags = depPkg.packageJson?.startx?.iTags?.every(tag =>
66
+ pkg.packageJson?.startx?.gTags?.includes(tag)
67
+ );
68
+ return sharesTags;
69
+ })
70
+ .forEach(depPkg => {
71
+ const depName = depPkg.packageJson?.name || depPkg.name;
72
+ appDeps[depName] = "workspace:^";
73
+ });
96
74
  }
97
75
 
98
76
  await this.installPackage({
99
- packages: pkg,
100
- directory: {
101
- workspace: prefs.directory.workspace,
102
- template: prefs.directory.template,
103
- },
77
+ pkg,
78
+ directory: prefs.directory,
104
79
  tags: Array.from(tags),
105
- dependencies: Object.fromEntries(appDeps),
80
+ dependencies: appDeps,
106
81
  });
107
82
  })
108
83
  );
@@ -112,104 +87,70 @@ export class InitCommand {
112
87
  projectName?: string;
113
88
  directory?: string;
114
89
  options: InitOptions;
115
- projects: PackageWithJson[];
90
+ projects: PackageItem[];
116
91
  }) {
117
- if (!props.projectName) {
118
- props.projectName = await CommonInquirer.getText({
119
- message: "Project name",
120
- name: "projectName",
121
- schema: z.string().min(2).trim(),
122
- });
123
- }
124
-
125
- // Handling workspace directory
126
- const directory = CliUtils.getDirectory();
127
- let workspace;
128
- if (props.options.dir) {
129
- try {
130
- workspace = path.resolve(directory.workspace, props.options.dir);
131
- } catch {
132
- throw new Error("Invalid template directory");
133
- }
134
- } else {
135
- workspace = path.join(directory.workspace, props.projectName);
136
- }
137
-
92
+ const projectName = await CommonInquirer.getText({
93
+ message: "Project name",
94
+ name: "projectName",
95
+ default: props.projectName,
96
+ schema: z
97
+ .string()
98
+ .min(1, "Package name is required")
99
+ .max(214, "Package name too long")
100
+ .regex(/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/, "Invalid package name"),
101
+ });
138
102
  if (props.projects.length === 0) {
139
- throw new Error("No apps found");
103
+ throw new Error("No apps found to install.");
140
104
  }
105
+ const directory = CliUtils.getDirectory();
106
+ const workspace = props.options.dir
107
+ ? path.resolve(directory.workspace, props.options.dir)
108
+ : path.join(directory.workspace, projectName);
141
109
 
142
110
  const selectedAppNames = await CommonInquirer.choose({
143
111
  message: "Select apps to install",
144
- options: props.projects.map(e => e.name),
112
+ options: props.projects.map(pkg => pkg.name),
145
113
  includeAllOption: true,
146
114
  mode: "multiple",
147
115
  required: true,
148
116
  });
149
117
 
150
- const selectedApps = props.projects.filter(e => selectedAppNames.includes(e.name));
151
-
152
118
  return {
153
- projectName: props.projectName,
154
- directory: {
155
- workspace,
156
- template: directory.template,
157
- },
158
- selectedApps,
119
+ projectName,
120
+ directory: { workspace, template: directory.template },
121
+ selectedApps: props.projects.filter(pkg => selectedAppNames.includes(pkg.name)),
159
122
  };
160
123
  }
161
124
 
162
- private static async getConfigPrefs(props: {
163
- packages: PackageWithJson[];
164
- selectedApps: PackageWithJson[];
165
- }) {
166
- const tags = new Set<TAGS>(["common"]);
167
- const configs = new Map<string, PackageWithJson>();
168
- // Add additional tags from apps
169
- props.selectedApps
170
- .flatMap(app => app.packageJson?.startx?.gTags || [])
171
- .forEach(tag => tags.add(tag));
172
-
173
- // Add required configs from apps
174
- props.selectedApps
175
- .flatMap(app => [
176
- ...(app.packageJson?.startx?.requiredDeps || []),
177
- ...(app.packageJson?.startx?.requiredDevDeps || []),
178
- ])
179
- .forEach(pkg => {
180
- const config = props.packages.find(e => e.packageJson?.name === pkg);
181
- if (config) configs.set(config.name, config);
182
- });
183
-
184
- const availableConfigs = props.packages.filter(e => {
185
- if (e.type !== "configs") return false;
186
- if (e.packageJson?.startx?.mode === "silent") {
187
- return false;
188
- }
189
- if (configs.has(e.name)) return false;
190
- if (!e.packageJson?.startx?.iTags?.every(t => tags.has(t))) return false;
191
- return true;
125
+ private static async getConfigPrefs(props: { packages: PackageItem[]; selectedApps: PackageItem[] }) {
126
+ const gTags = new Set<TAGS>(["common"]);
127
+ const configs = new Map<string, PackageItem>();
128
+ // Selected apps globals tags and dependencies resolver
129
+ this.getGlobalTags({ pkgs: props.selectedApps }).forEach(tag => gTags.add(tag));
130
+
131
+ this.getPackageDeps({
132
+ allPkgs: props.packages,
133
+ pkgs: props.selectedApps,
134
+ }).forEach(pkg => configs.set(pkg.name, pkg));
135
+
136
+ const availableConfigs = props.packages.filter(pkg => {
137
+ if (pkg.type !== "configs") return false;
138
+ if (pkg.packageJson?.startx?.mode === "silent") return false;
139
+ if (configs.has(pkg.name)) return false;
140
+ return pkg.packageJson?.startx?.iTags?.every(t => gTags.has(t)) ?? true;
192
141
  });
142
+ if (availableConfigs.length > 0) {
143
+ const rawSelectedConfigs = await CommonInquirer.choose({
144
+ message: "Select configs to install",
145
+ options: availableConfigs.map(pkg => pkg.name),
146
+ includeAllOption: true,
147
+ mode: "multiple",
148
+ required: false,
149
+ });
150
+ availableConfigs.filter(pkg => rawSelectedConfigs.includes(pkg.name)).forEach(pkg => configs.set(pkg.name, pkg));
151
+ }
193
152
 
194
- if (availableConfigs.length === 0)
195
- return {
196
- tags: Array.from(tags),
197
- selectedConfigs: Array.from(configs.values()),
198
- };
199
-
200
- const rawSelectedConfigs = await CommonInquirer.choose({
201
- message: "Select configs to install",
202
- options: availableConfigs.map(e => e.name),
203
- includeAllOption: true,
204
- mode: "multiple",
205
- required: false,
206
- });
207
-
208
- availableConfigs
209
- .filter(e => rawSelectedConfigs.includes(e.name))
210
- .forEach(e => configs.set(e.name, e));
211
-
212
- if (tags.has("node")) {
153
+ if (gTags.has("node")) {
213
154
  const formatter: string | string[] = await CommonInquirer.choose({
214
155
  message: "Select formatter",
215
156
  options: ["prettier + biome", "prettier"],
@@ -218,100 +159,72 @@ export class InitCommand {
218
159
  required: true,
219
160
  });
220
161
  if (formatter === "prettier") {
221
- tags.add("prettier");
162
+ gTags.add("prettier");
222
163
  } else {
223
- tags.add("biome");
224
- tags.add("prettier");
164
+ gTags.add("biome");
165
+ gTags.add("prettier");
225
166
  }
226
167
  }
227
168
 
228
- // Include required configs
229
- Array.from(configs.values()).forEach(e => {
230
- const requiredDeps = e.packageJson?.startx?.requiredDeps || [];
231
- const requiredDevDeps = e.packageJson?.startx?.requiredDevDeps || [];
232
-
233
- const required = [...requiredDeps, ...requiredDevDeps];
234
- required.forEach(pkg => {
235
- const config = props.packages.find(e => e.packageJson?.name === pkg);
236
- if (config) {
237
- configs.set(config.name, config);
238
- }
239
- });
240
- });
169
+ // Resolving deps for selected configs
170
+ this.getPackageDeps({
171
+ allPkgs: props.packages,
172
+ pkgs: Array.from(configs.values()),
173
+ }).forEach(pkg => configs.set(pkg.name, pkg));
241
174
 
242
- // Include all tags
243
- Array.from(configs.values()).forEach(e => {
244
- const gTags = e.packageJson?.startx?.gTags || [];
245
- gTags.forEach(tag => tags.add(tag));
246
- });
175
+ // Adding global tags
176
+ this.getGlobalTags({ pkgs: Array.from(configs.values()) }).forEach(tag => gTags.add(tag));
247
177
 
248
178
  return {
249
- tags: Array.from(tags),
179
+ gTags: Array.from(gTags),
250
180
  selectedConfigs: Array.from(configs.values()),
251
181
  };
252
182
  }
253
183
 
254
184
  private static async getPackagesPrefs(props: {
255
185
  tags: TAGS[];
256
- packages: PackageWithJson[];
257
- selectedApps: PackageWithJson[];
258
- selectedPackages: PackageWithJson[];
186
+ packages: PackageItem[];
187
+ selectedPackages: PackageItem[];
259
188
  }) {
260
- const appTags = new Set<TAGS>(props.tags);
261
- const packages = new Map<string, PackageWithJson>(props.selectedPackages.map(e => [e.name, e]));
189
+ const gTags = new Set<TAGS>(props.tags);
190
+ const packages = new Map<string, PackageItem>(props.selectedPackages.map(pkg => [pkg.name, pkg]));
262
191
  const availablePackages = props.packages.filter(pkg => {
263
192
  if (pkg.type !== "packages") return false;
264
- if (pkg.packageJson?.startx?.mode === "silent") {
265
- return false;
266
- }
193
+ if (pkg.packageJson?.startx?.mode === "silent") return false;
267
194
  if (packages.has(pkg.name)) return false;
268
- if (!pkg.packageJson?.startx?.iTags?.every(t => appTags.has(t))) return false;
269
- return true;
195
+ return pkg.packageJson?.startx?.iTags?.every(t => gTags.has(t)) ?? false;
270
196
  });
271
- if (!availablePackages.length)
272
- return {
273
- selectedPackages: Array.from(packages.values()),
274
- tags: Array.from(appTags),
275
- };
276
- const rawSelectedPackages = await CommonInquirer.choose({
277
- message: "Select packages to install",
278
- options: availablePackages.map(e => e.name),
279
- includeAllOption: true,
280
- mode: "multiple",
281
- required: false,
282
- });
283
-
284
- availablePackages
285
- .filter(e => rawSelectedPackages.includes(e.name))
286
- .forEach(e => packages.set(e.name, e));
197
+ if (availablePackages.length > 0) {
198
+ const rawSelectedPackages = await CommonInquirer.choose({
199
+ message: "Select packages to install",
200
+ options: availablePackages.map(pkg => pkg.name),
201
+ includeAllOption: true,
202
+ mode: "multiple",
203
+ required: false,
204
+ });
287
205
 
288
- Array.from(packages.values()).forEach(e => {
289
- const requiredDeps = e.packageJson?.startx?.requiredDeps || [];
290
- const requiredDevDeps = e.packageJson?.startx?.requiredDevDeps || [];
206
+ availablePackages
207
+ .filter(pkg => rawSelectedPackages.includes(pkg.name))
208
+ .forEach(pkg => packages.set(pkg.name, pkg));
209
+ }
291
210
 
292
- const required = [...requiredDeps, ...requiredDevDeps];
293
- required.forEach(tag => {
294
- const config = props.packages.find(e => e.packageJson?.name === tag);
295
- if (config) {
296
- packages.set(config.name, config);
297
- }
298
- });
299
- });
211
+ this.getPackageDeps({
212
+ allPkgs: props.packages,
213
+ pkgs: Array.from(packages.values()),
214
+ }).forEach(pkg => packages.set(pkg.name, pkg));
300
215
 
301
- // Include all tags
302
- Array.from(packages.values()).forEach(e => {
303
- const gTags = e.packageJson?.startx?.gTags || [];
304
- gTags.forEach(tag => appTags.add(tag));
305
- });
216
+ this.getGlobalTags({
217
+ pkgs: Array.from(packages.values()),
218
+ }).forEach(tag => gTags.add(tag));
306
219
 
307
220
  return {
308
- tags: Array.from(appTags),
221
+ gTags: Array.from(gTags),
309
222
  selectedPackages: Array.from(packages.values()),
310
223
  };
311
224
  }
312
225
 
313
226
  private static async installPackage(props: {
314
- packages: PackageWithJson;
227
+ pkg: PackageItem;
315
228
  directory: {
316
229
  workspace: string;
317
230
  template: string;
@@ -319,57 +232,36 @@ export class InitCommand {
319
232
  tags: TAGS[];
320
233
  dependencies: Record<string, string>;
321
234
  }) {
322
- const tags = new Set(props.tags.concat(props.packages.packageJson?.startx?.tags || []));
323
- if (props.packages.packageJson?.startx?.ignore?.find(e => e === "eslint-config"))
324
- tags.delete("eslint");
325
- if (props.packages.packageJson?.startx?.ignore?.find(e => e === "vitest-config"))
326
- tags.delete("vitest");
235
+ if (!props.pkg.packageJson) {
236
+ throw new Error(`Missing package.json for ${props.pkg.name}`);
237
+ }
238
+ const tags = new Set<TAGS>([...props.tags, ...(props.pkg.packageJson.startx?.tags || [])]);
239
+ const ignoreList = props.pkg.packageJson.startx?.ignore || [];
240
+ if (ignoreList.includes("eslint-config")) tags.delete("eslint");
241
+ if (ignoreList.includes("vitest-config")) tags.delete("vitest");
327
242
  const { packageJson, isWorkspace } = FileHandler.handlePackageJson({
328
- app: props.packages.packageJson!,
243
+ app: props.pkg.packageJson,
329
244
  tags: Array.from(tags),
330
- name: props.packages.name,
245
+ name: props.pkg.name,
246
+ dependencies: props.dependencies,
331
247
  });
332
248
 
333
- if (isWorkspace) throw new Error("Can't install workspace as package.");
334
-
335
- let iDirectory = path.join(props.directory.workspace, props.packages.type);
336
- let iTemplate = path.join(props.directory.template, props.packages.type);
337
- if (props.packages.packageJson?.name?.startsWith("@repo")) {
338
- const repoName = props.packages.packageJson.name.split("/")[1];
339
- iDirectory = path.join(iDirectory, "@repo", repoName);
340
- iTemplate = path.join(iTemplate, "@repo", repoName);
341
- } else {
342
- iDirectory = path.join(iDirectory, props.packages.name);
343
- iTemplate = path.join(iTemplate, props.packages.name);
249
+ if (isWorkspace) {
250
+ throw new Error(`Cannot install workspace as a package: ${props.pkg.name}`);
344
251
  }
345
252
 
346
- await fsTool.writeJSONFile({
347
- dir: iDirectory,
348
- file: "package",
349
- content: packageJson,
350
- });
253
+ const iDirectory = path.join(props.directory.workspace, props.pkg.relativePath);
254
+ const iTemplate = path.join(props.pkg.path);
255
+ await fsTool.writeJSONFile({ dir: iDirectory, file: "package", content: packageJson });
351
256
 
352
- const files = await fsTool.listFiles({ dir: iTemplate });
353
- for (const file of files) {
354
- const checked = FileCheck[file];
355
- if (checked && !checked.tags.every(e => tags.has(e))) continue;
356
- try {
357
- await fsTool.copyFile({
358
- from: path.join(iTemplate, file),
359
- to: path.join(iDirectory, file),
360
- });
361
- } catch (error) {
362
- logger.error(`Failed to copy file ${file}:`, error);
363
- }
364
- }
365
-
366
- // Installing src
257
+ await this.copyValidatedFilesFromFolder(iTemplate, iDirectory, tags);
367
258
  await fsTool.copyDirectory({
368
259
  from: path.join(iTemplate, "src"),
369
260
  to: path.join(iDirectory, "src"),
370
261
  exclude: !tags.has("vitest") ? /\.test\.tsx?$/ : undefined,
371
262
  });
372
- logger.info(`Successfully installed ${props.packages.name}`);
263
+
264
+ logger.info(`Successfully installed ${props.pkg.name}`);
373
265
  }
374
266
 
375
267
  private static async installWorkspace(props: {
@@ -380,19 +272,19 @@ export class InitCommand {
380
272
  template: string;
381
273
  };
382
274
  }) {
383
- const rootTags = ["root", ...props.tags] as TAGS[];
384
275
  const rawPackage = await CliUtils.parsePackageJson({ dir: props.dir.template });
385
276
  const startXRawPackage = await CliUtils.parsePackageJson({
386
277
  dir: props.dir.template,
387
278
  file: "startx",
388
279
  });
389
280
 
390
- if (!rawPackage) throw new Error("Failed to parse package.json");
391
- rawPackage.dependencies = startXRawPackage?.dependencies || {};
392
- rawPackage.devDependencies = startXRawPackage?.devDependencies || {};
281
+ if (!rawPackage) throw new Error("Failed to parse root package.json");
282
+ rawPackage.dependencies = { ...rawPackage.dependencies, ...(startXRawPackage?.dependencies || {}) };
283
+ rawPackage.devDependencies = { ...rawPackage.devDependencies, ...(startXRawPackage?.devDependencies || {}) };
284
+
393
285
  const { packageJson } = FileHandler.handlePackageJson({
394
286
  app: rawPackage,
395
- tags: rootTags,
287
+ tags: ["root", ...props.tags] as TAGS[],
396
288
  name: props.name,
397
289
  });
398
290
 
@@ -401,15 +293,45 @@ export class InitCommand {
401
293
  file: "package",
402
294
  content: packageJson,
403
295
  });
296
+ await this.copyValidatedFilesFromFolder(
297
+ props.dir.template,
298
+ props.dir.workspace,
299
+ new Set(["root", ...props.tags] as TAGS[])
300
+ );
301
+ }
404
302
 
405
- const files = await fsTool.listFiles({ dir: props.dir.template });
303
+ // Helpers
304
+ private static getPackageDeps(props: { pkgs: PackageItem[]; allPkgs: PackageItem[] }) {
305
+ const deps = new Map<string, PackageItem>(props.pkgs.map(pkg => [pkg.name, pkg]));
306
+ Array.from(deps.values()).forEach(pkg => {
307
+ const required = [
308
+ ...(pkg.packageJson?.startx?.requiredDeps || []),
309
+ ...(pkg.packageJson?.startx?.requiredDevDeps || []),
310
+ ];
311
+ required.forEach(reqPkgName => {
312
+ const config = props.allPkgs.find(p => p.packageJson?.name === reqPkgName);
313
+ if (config) deps.set(config.name, config);
314
+ });
315
+ });
316
+ props.pkgs.forEach(pkg => deps.delete(pkg.name));
317
+ return Array.from(deps.values());
318
+ }
319
+ private static getGlobalTags(props: { pkgs: PackageItem[]; gTags?: TAGS[] }) {
320
+ const tags = new Set<TAGS>(props.gTags || []);
321
+ props.pkgs.forEach(pkg => {
322
+ pkg.packageJson?.startx?.gTags?.forEach(tag => tags.add(tag));
323
+ });
324
+ return Array.from(tags);
325
+ }
326
+ private static async copyValidatedFilesFromFolder(source: string, destination: string, tags: Set<TAGS>) {
327
+ const files = await fsTool.listFiles({ dir: source }).catch(() => []);
406
328
  for (const file of files) {
407
329
  const checked = FileCheck[file];
408
- if (checked && !checked.tags.every(e => rootTags.includes(e))) continue;
330
+ if (checked && !checked.tags.every(tag => tags.has(tag))) continue;
409
331
  try {
410
332
  await fsTool.copyFile({
411
- from: path.join(props.dir.template, file),
412
- to: path.join(props.dir.workspace, file),
333
+ from: path.join(source, file),
334
+ to: path.join(destination, file),
413
335
  });
414
336
  } catch (error) {
415
337
  logger.error(`Failed to copy file ${file}:`, error);
@@ -1,30 +1,35 @@
1
1
  import type { WHITELIST_DEPS } from "../types";
2
2
 
3
- /* eslint-disable @typescript-eslint/naming-convention */
4
3
  export const DepCheck: WHITELIST_DEPS = {
5
4
  "@biomejs/biome": {
6
5
  tags: ["node", "biome", "root"],
7
6
  version: "catalog:",
7
+ isDevDependency: true,
8
8
  },
9
9
  "prettier": {
10
10
  tags: ["node", "prettier", "root"],
11
11
  version: "catalog:",
12
+ isDevDependency: true,
12
13
  },
13
14
  "eslint": {
14
15
  tags: ["node", "eslint", "root"],
15
16
  version: "catalog:",
17
+ isDevDependency: true,
16
18
  },
17
19
  "vitest": {
18
20
  tags: ["node", "vitest", "root"],
19
21
  version: "catalog:",
22
+ isDevDependency: true,
20
23
  },
21
24
  "tsdown": {
25
+ isDevDependency: true,
22
26
  tags: ["node", "tsdown", "root"],
23
27
  version: "catalog:",
24
28
  },
25
29
  "tsdown-config": {
26
30
  tags: ["node", "tsdown", "runnable"],
27
31
  version: "workspace:^",
32
+ isDevDependency: true,
28
33
  },
29
34
  "@types/node": {
30
35
  tags: ["node", "root"],
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/naming-convention */
2
1
  import type { WHITELIST_FILES } from "../types";
3
2
 
4
3
  export const FileCheck: WHITELIST_FILES = {
@@ -11,12 +10,12 @@ export const FileCheck: WHITELIST_FILES = {
11
10
  ".npmrc": {
12
11
  tags: ["never"],
13
12
  },
14
- ".prettierignore": {
15
- tags: ["prettier"],
16
- },
17
13
  ".prettier.js": {
18
14
  tags: ["prettier"],
19
15
  },
16
+ ".prettierignore": {
17
+ tags: ["biome"],
18
+ },
20
19
  "biome.json": {
21
20
  tags: ["biome"],
22
21
  },