startx 1.0.2 → 1.0.4
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/.dockerignore +4 -0
- package/apps/cli/src/commands/index.ts +1 -1
- package/apps/cli/src/commands/{common → test}/test.ts +4 -2
- package/apps/cli/tsconfig.json +0 -1
- package/apps/core-server/Dockerfile +5 -4
- package/apps/core-server/package.json +1 -1
- package/apps/core-server/tsconfig.json +1 -1
- package/apps/queue-worker/package.json +1 -1
- package/apps/queue-worker/tsconfig.json +1 -1
- package/apps/startx-cli/dist/index.mjs +68 -53
- package/apps/startx-cli/src/commands/package.ts +453 -0
- package/apps/startx-cli/src/configs/scripts.ts +18 -2
- package/apps/startx-cli/src/index.ts +2 -4
- package/apps/startx-cli/src/types.ts +2 -4
- package/apps/startx-cli/src/utils/inquirer.ts +8 -1
- package/apps/web-client/.dockerignore +4 -0
- package/apps/web-client/app/app.css +1 -0
- package/apps/web-client/app/components.json +23 -0
- package/apps/web-client/app/config/auth/auth-state.ts +59 -0
- package/apps/web-client/app/config/axios-client.ts +87 -0
- package/apps/web-client/app/config/env.ts +5 -0
- package/apps/web-client/app/entry.client.tsx +7 -0
- package/apps/web-client/app/eslint.config.ts +4 -0
- package/apps/web-client/app/root.tsx +77 -0
- package/apps/web-client/app/routes/home.tsx +12 -0
- package/apps/web-client/app/routes.ts +3 -0
- package/apps/web-client/eslint.config.ts +4 -0
- package/apps/web-client/package.json +55 -0
- package/apps/web-client/react-router.config.ts +7 -0
- package/apps/web-client/tsconfig.json +22 -0
- package/apps/web-client/vite-env.d.ts +8 -0
- package/apps/web-client/vite.config.ts +30 -0
- package/biome.json +5 -0
- package/configs/eslint-config/eslint.config.ts +1 -0
- package/configs/eslint-config/src/configs/base.ts +2 -1
- package/configs/eslint-config/src/configs/frontend.ts +1 -1
- package/configs/eslint-config/tsconfig.json +1 -1
- package/configs/typescript-config/tsconfig.frontend.json +1 -1
- package/configs/vitest-config/tsconfig.json +1 -1
- package/package.json +1 -1
- package/packages/@db/drizzle/tsconfig.json +1 -1
- package/packages/@db/sqlite/tsconfig.json +1 -1
- package/packages/@repo/env/package.json +1 -2
- package/packages/@repo/env/src/utils.ts +17 -11
- package/packages/@repo/env/tsconfig.json +1 -1
- package/packages/@repo/lib/package.json +3 -1
- package/packages/@repo/lib/src/session-module/i-session.ts +108 -0
- package/packages/@repo/lib/src/session-module/index.ts +8 -111
- package/packages/@repo/lib/src/session-module/redis-session.ts +44 -0
- package/packages/@repo/lib/tsconfig.json +0 -1
- package/packages/@repo/logger/package.json +0 -1
- package/packages/@repo/logger/tsconfig.json +1 -1
- package/packages/@repo/mail/tsconfig.json +1 -1
- package/packages/@repo/redis/tsconfig.json +1 -1
- package/packages/aix/package.json +2 -0
- package/packages/aix/src/providers/ai-interface.ts +4 -4
- package/packages/aix/src/providers/bedrock/bedrock.ts +261 -0
- package/packages/aix/src/providers/default-models.ts +65 -0
- package/packages/aix/src/providers/openai/openai.ts +2 -2
- package/packages/aix/src/providers/providers.ts +11 -0
- package/packages/aix/src/providers/types.ts +1 -1
- package/packages/{constants → common}/package.json +4 -2
- package/packages/{constants/src/index.ts → common/src/constants.ts} +0 -5
- package/packages/common/src/types/users.ts +10 -0
- package/packages/{constants → common}/tsconfig.json +0 -3
- package/packages/ui/components.json +15 -8
- package/packages/ui/package.json +24 -36
- package/packages/ui/src/api/axios/i-client.ts +40 -0
- package/packages/ui/src/api/index.ts +6 -0
- package/packages/ui/src/api/query-provider.tsx +34 -0
- package/packages/ui/src/api/use-api/api-builder.ts +139 -0
- package/packages/ui/src/api/use-api/api-helpers.ts +165 -0
- package/packages/ui/src/api/use-api/api-types.ts +138 -0
- package/packages/ui/src/api/use-api/query-factory.ts +66 -0
- package/packages/ui/src/api/use-api/react-query/types.ts +64 -0
- package/packages/ui/src/api/use-api/react-query/use-api-client.ts +56 -0
- package/packages/ui/src/api/use-api/react-query/use-api.ts +297 -0
- package/packages/ui/src/components/custom/form-wrapper.tsx +113 -160
- package/packages/ui/src/components/custom/grid-component.tsx +4 -4
- package/packages/ui/src/components/custom/hover-tool.tsx +1 -1
- package/packages/ui/src/components/custom/image-picker.tsx +18 -20
- package/packages/ui/src/components/custom/no-content.tsx +6 -16
- package/packages/ui/src/components/custom/page-section.tsx +14 -17
- package/packages/ui/src/components/custom/simple-popover.tsx +5 -9
- package/packages/ui/src/components/custom/theme-provider.tsx +117 -42
- package/packages/ui/src/components/custom/typography.tsx +20 -22
- package/packages/ui/src/components/extensions/timeline.tsx +100 -0
- package/packages/ui/src/components/ui/alert-dialog.tsx +46 -108
- package/packages/ui/src/components/ui/avatar.tsx +79 -42
- package/packages/ui/src/components/ui/badge.tsx +29 -34
- package/packages/ui/src/components/ui/breadcrumb.tsx +65 -81
- package/packages/ui/src/components/ui/button.tsx +80 -80
- package/packages/ui/src/components/ui/card.tsx +48 -69
- package/packages/ui/src/components/ui/carousel.tsx +184 -211
- package/packages/ui/src/components/ui/checkbox.tsx +21 -24
- package/packages/ui/src/components/ui/command.tsx +121 -102
- package/packages/ui/src/components/ui/dialog.tsx +45 -32
- package/packages/ui/src/components/ui/dropdown-menu.tsx +45 -33
- package/packages/ui/src/components/ui/field.tsx +218 -0
- package/packages/ui/src/components/ui/form.tsx +63 -76
- package/packages/ui/src/components/ui/input-group.tsx +137 -0
- package/packages/ui/src/components/ui/input-otp.tsx +60 -50
- package/packages/ui/src/components/ui/input.tsx +16 -15
- package/packages/ui/src/components/ui/label.tsx +14 -17
- package/packages/ui/src/components/ui/multiple-select.tsx +22 -33
- package/packages/ui/src/components/ui/popover.tsx +20 -8
- package/packages/ui/src/components/ui/select.tsx +33 -34
- package/packages/ui/src/components/ui/separator.tsx +8 -8
- package/packages/ui/src/components/ui/sheet.tsx +32 -59
- package/packages/ui/src/components/ui/sidebar.tsx +654 -0
- package/packages/ui/src/components/ui/skeleton.tsx +2 -8
- package/packages/ui/src/components/ui/sonner.tsx +39 -0
- package/packages/ui/src/components/ui/spinner.tsx +6 -13
- package/packages/ui/src/components/ui/switch.tsx +15 -10
- package/packages/ui/src/components/ui/table.tsx +48 -89
- package/packages/ui/src/components/ui/tabs.tsx +37 -15
- package/packages/ui/src/components/ui/textarea.tsx +13 -13
- package/packages/ui/src/components/ui/tooltip.tsx +37 -23
- package/packages/ui/src/{components/hooks → hooks}/event/use-click.tsx +6 -10
- package/packages/ui/src/hooks/time/use-timer.tsx +51 -0
- package/packages/ui/src/hooks/use-media-query.tsx +19 -0
- package/packages/ui/src/hooks/use-mobile.tsx +17 -0
- package/packages/ui/src/{components/hooks → hooks}/use-update-effect.tsx +2 -2
- package/packages/ui/src/lib/utils.ts +113 -0
- package/packages/ui/src/styles/globals.css +314 -0
- package/packages/ui/src/styles/tailwind.css +89 -0
- package/packages/ui/tsconfig.json +7 -9
- package/pnpm-workspace.yaml +74 -64
- package/packages/ui/postcss.config.mjs +0 -9
- package/packages/ui/src/components/extensions/carousel.tsx +0 -392
- package/packages/ui/src/components/hooks/time/useTimer.tsx +0 -51
- package/packages/ui/src/components/hooks/use-media-query.tsx +0 -19
- package/packages/ui/src/components/lib/utils.ts +0 -242
- package/packages/ui/src/components/ui/timeline.tsx +0 -118
- package/packages/ui/src/components/util/n-formattor.ts +0 -22
- package/packages/ui/src/components/util/storage.ts +0 -37
- package/packages/ui/src/globals.css +0 -87
- package/packages/ui/tailwind.config.ts +0 -94
- /package/packages/{constants → common}/eslint.config.ts +0 -0
- /package/packages/{constants → common}/src/api.ts +0 -0
- /package/packages/{constants → common}/src/time.ts +0 -0
- /package/packages/{constants → common}/vitest.config.ts +0 -0
- /package/packages/ui/src/{components/hooks/time/useDebounce.tsx → hooks/time/use-debounce.tsx} +0 -0
- /package/packages/ui/src/{components/hooks/time/useInterval.tsx → hooks/time/use-interval.tsx} +0 -0
- /package/packages/ui/src/{components/hooks/time/useTimeout.tsx → hooks/time/use-timeout.tsx} +0 -0
- /package/packages/ui/src/{components/hooks → hooks}/use-persistent-storage.tsx +0 -0
- /package/packages/ui/src/{components/hooks → hooks}/use-window-dimension.tsx +0 -0
- /package/packages/ui/src/{components/sonner.tsx → sonner.ts} +0 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { fsTool } from "@repo/lib/file-system-module";
|
|
2
|
+
import { logger } from "@repo/logger";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import fs from "fs/promises";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import z from "zod";
|
|
8
|
+
|
|
9
|
+
import { FileCheck } from "../configs/files";
|
|
10
|
+
import type { StartXPackageJson, TAGS } from "../types";
|
|
11
|
+
import { CliUtils, type PackageItem } from "../utils/cli-utils";
|
|
12
|
+
import { FileHandler } from "../utils/file-handler";
|
|
13
|
+
import { CommonInquirer } from "../utils/inquirer";
|
|
14
|
+
|
|
15
|
+
type PackageOptions = {
|
|
16
|
+
eslint?: boolean;
|
|
17
|
+
install?: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type NewPackageOptions = PackageOptions & {
|
|
21
|
+
dir?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const packageNameSchema = z
|
|
25
|
+
.string()
|
|
26
|
+
.min(1, "Package name is required")
|
|
27
|
+
.max(214, "Package name too long")
|
|
28
|
+
.regex(/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/, "Invalid package name");
|
|
29
|
+
|
|
30
|
+
export class PackageCommand {
|
|
31
|
+
static command = new Command("package")
|
|
32
|
+
.alias("pkg")
|
|
33
|
+
.description("List and add packages in the current monorepo.")
|
|
34
|
+
.addCommand(
|
|
35
|
+
new Command("list")
|
|
36
|
+
.alias("ls")
|
|
37
|
+
.description("List packages available from the StartX template.")
|
|
38
|
+
.action(PackageCommand.list.bind(PackageCommand))
|
|
39
|
+
)
|
|
40
|
+
.addCommand(
|
|
41
|
+
new Command("add")
|
|
42
|
+
.description("Add an existing StartX package by name.")
|
|
43
|
+
.argument("[packageName]")
|
|
44
|
+
.option("--eslint", "enable ESLint support for the added package")
|
|
45
|
+
.option("--no-eslint", "skip ESLint support for the added package")
|
|
46
|
+
.option("--no-install", "do not run the package manager after updating ESLint")
|
|
47
|
+
.action(PackageCommand.add.bind(PackageCommand))
|
|
48
|
+
)
|
|
49
|
+
.addCommand(
|
|
50
|
+
new Command("new")
|
|
51
|
+
.alias("create")
|
|
52
|
+
.description("Create a new package from scratch.")
|
|
53
|
+
.argument("[packageName]")
|
|
54
|
+
.option("-d, --dir <path>", "package directory relative to the current workspace")
|
|
55
|
+
.option("--eslint", "enable ESLint support for the new package")
|
|
56
|
+
.option("--no-eslint", "skip ESLint support for the new package")
|
|
57
|
+
.option("--no-install", "do not run the package manager after updating ESLint")
|
|
58
|
+
.action(PackageCommand.create.bind(PackageCommand))
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
private static async list() {
|
|
62
|
+
const packages = await CliUtils.getPackageList();
|
|
63
|
+
const byType = new Map<PackageItem["type"], PackageItem[]>();
|
|
64
|
+
|
|
65
|
+
for (const pkg of packages) {
|
|
66
|
+
const list = byType.get(pkg.type) ?? [];
|
|
67
|
+
list.push(pkg);
|
|
68
|
+
byType.set(pkg.type, list);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const type of ["apps", "packages", "configs"] as const) {
|
|
72
|
+
const entries = byType.get(type) ?? [];
|
|
73
|
+
if (entries.length === 0) continue;
|
|
74
|
+
|
|
75
|
+
logger.info(`${type}:`);
|
|
76
|
+
for (const pkg of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
77
|
+
logger.info(` ${pkg.name} (${pkg.relativePath})`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private static async add(packageName: string | undefined, options: PackageOptions) {
|
|
83
|
+
const packages = await CliUtils.getPackageList();
|
|
84
|
+
const availablePackages = packages.filter(pkg => pkg.packageJson?.startx?.mode !== "silent");
|
|
85
|
+
const selectedName =
|
|
86
|
+
packageName ??
|
|
87
|
+
(await CommonInquirer.choose({
|
|
88
|
+
message: "Select package to add",
|
|
89
|
+
options: availablePackages.map(pkg => pkg.name),
|
|
90
|
+
mode: "single",
|
|
91
|
+
required: true,
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
const selectedPackage = this.findPackage(packages, selectedName);
|
|
95
|
+
if (!selectedPackage) {
|
|
96
|
+
throw new Error(`Package "${selectedName}" was not found in the StartX template.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const directory = CliUtils.getDirectory();
|
|
100
|
+
const eslintEnabled = await this.resolveEslintPreference(options);
|
|
101
|
+
const packagesToInstall = this.resolvePackageClosure({
|
|
102
|
+
packages,
|
|
103
|
+
selectedPackage,
|
|
104
|
+
includeEslintConfig: eslintEnabled,
|
|
105
|
+
});
|
|
106
|
+
const tags = await this.getInstallTags({
|
|
107
|
+
workspace: directory.workspace,
|
|
108
|
+
packages: packagesToInstall,
|
|
109
|
+
eslintEnabled,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
for (const pkg of packagesToInstall) {
|
|
113
|
+
await this.installTemplatePackage({
|
|
114
|
+
pkg,
|
|
115
|
+
directory,
|
|
116
|
+
tags,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
logger.info(`Package add complete: ${selectedPackage.name}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private static async create(packageName: string | undefined, options: NewPackageOptions) {
|
|
124
|
+
const name = await CommonInquirer.getText({
|
|
125
|
+
message: "Package name",
|
|
126
|
+
name: "packageName",
|
|
127
|
+
default: packageName,
|
|
128
|
+
schema: packageNameSchema,
|
|
129
|
+
});
|
|
130
|
+
const directory = CliUtils.getDirectory();
|
|
131
|
+
const packageDir = options.dir
|
|
132
|
+
? path.resolve(directory.workspace, options.dir)
|
|
133
|
+
: path.resolve(directory.workspace, this.getDefaultPackagePath(name));
|
|
134
|
+
|
|
135
|
+
if (await this.pathExists(packageDir)) {
|
|
136
|
+
throw new Error(`Package directory already exists: ${packageDir}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const eslintEnabled = await this.resolveEslintPreference(options);
|
|
140
|
+
const packages = await CliUtils.getPackageList();
|
|
141
|
+
await this.ensureTemplatePackage({
|
|
142
|
+
packages,
|
|
143
|
+
name: "typescript-config",
|
|
144
|
+
directory,
|
|
145
|
+
tags: ["common", "node"],
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (eslintEnabled) {
|
|
149
|
+
await this.ensureTemplatePackage({
|
|
150
|
+
packages,
|
|
151
|
+
name: "eslint-config",
|
|
152
|
+
directory,
|
|
153
|
+
tags: ["common", "node", "eslint"],
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await fs.mkdir(path.join(packageDir, "src"), { recursive: true });
|
|
158
|
+
await this.writeJson(path.join(packageDir, "package.json"), this.createPackageJson({ name, eslintEnabled }));
|
|
159
|
+
await fs.writeFile(
|
|
160
|
+
path.join(packageDir, "tsconfig.json"),
|
|
161
|
+
`${JSON.stringify(
|
|
162
|
+
{
|
|
163
|
+
extends: "typescript-config/tsconfig.node.json",
|
|
164
|
+
compilerOptions: {
|
|
165
|
+
moduleResolution: "bundler",
|
|
166
|
+
module: "esnext",
|
|
167
|
+
target: "es2022",
|
|
168
|
+
},
|
|
169
|
+
include: ["src/**/*.ts"],
|
|
170
|
+
},
|
|
171
|
+
null,
|
|
172
|
+
2
|
|
173
|
+
)}\n`
|
|
174
|
+
);
|
|
175
|
+
await fs.writeFile(path.join(packageDir, "src", "index.ts"), "export {};\n");
|
|
176
|
+
|
|
177
|
+
if (eslintEnabled) {
|
|
178
|
+
await fs.writeFile(
|
|
179
|
+
path.join(packageDir, "eslint.config.ts"),
|
|
180
|
+
`import { baseConfig } from "eslint-config/base";\nimport { extend } from "eslint-config/extend";\n\nexport default extend(baseConfig);\n`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
logger.info(`Created package ${name} at ${path.relative(directory.workspace, packageDir)}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private static async resolveEslintPreference(options: PackageOptions) {
|
|
188
|
+
if (options.eslint === false) return false;
|
|
189
|
+
|
|
190
|
+
const directory = CliUtils.getDirectory();
|
|
191
|
+
const rootPackage = await this.readRootPackage(directory.workspace);
|
|
192
|
+
const hasEslint = this.hasDependency(rootPackage, "eslint");
|
|
193
|
+
|
|
194
|
+
if (hasEslint) return options.eslint ?? true;
|
|
195
|
+
|
|
196
|
+
const shouldInstall =
|
|
197
|
+
options.eslint === true ||
|
|
198
|
+
(await CommonInquirer.confirm({
|
|
199
|
+
message: "ESLint is not installed in this monorepo. Install and enable it?",
|
|
200
|
+
default: true,
|
|
201
|
+
}));
|
|
202
|
+
|
|
203
|
+
if (!shouldInstall) return false;
|
|
204
|
+
|
|
205
|
+
rootPackage.devDependencies = {
|
|
206
|
+
...(rootPackage.devDependencies as Record<string, string> | undefined),
|
|
207
|
+
eslint: await this.resolveDependencyVersion(directory.workspace, "eslint"),
|
|
208
|
+
};
|
|
209
|
+
await this.writeJson(path.join(directory.workspace, "package.json"), rootPackage);
|
|
210
|
+
logger.info("Added eslint to the root devDependencies.");
|
|
211
|
+
|
|
212
|
+
if (options.install !== false) {
|
|
213
|
+
await this.installRootDependencies(directory.workspace);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private static async getInstallTags(props: {
|
|
220
|
+
workspace: string;
|
|
221
|
+
packages: PackageItem[];
|
|
222
|
+
eslintEnabled: boolean;
|
|
223
|
+
}) {
|
|
224
|
+
const tags = new Set<TAGS>(["common", "node"]);
|
|
225
|
+
const rootPackage = await this.readRootPackage(props.workspace);
|
|
226
|
+
|
|
227
|
+
if (props.eslintEnabled) tags.add("eslint");
|
|
228
|
+
if (this.hasDependency(rootPackage, "@biomejs/biome")) tags.add("biome");
|
|
229
|
+
if (this.hasDependency(rootPackage, "prettier")) tags.add("prettier");
|
|
230
|
+
if (this.hasDependency(rootPackage, "vitest")) tags.add("vitest");
|
|
231
|
+
|
|
232
|
+
for (const pkg of props.packages) {
|
|
233
|
+
pkg.packageJson?.startx?.tags?.forEach(tag => tags.add(tag));
|
|
234
|
+
pkg.packageJson?.startx?.iTags?.forEach(tag => tags.add(tag));
|
|
235
|
+
pkg.packageJson?.startx?.gTags?.forEach(tag => tags.add(tag));
|
|
236
|
+
|
|
237
|
+
if (pkg.type === "apps" || pkg.packageJson?.startx?.mode === "standalone") {
|
|
238
|
+
tags.add("runnable");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return Array.from(tags);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private static resolvePackageClosure(props: {
|
|
246
|
+
packages: PackageItem[];
|
|
247
|
+
selectedPackage: PackageItem;
|
|
248
|
+
includeEslintConfig: boolean;
|
|
249
|
+
}) {
|
|
250
|
+
const resolved = new Map<string, PackageItem>();
|
|
251
|
+
const queue = [props.selectedPackage];
|
|
252
|
+
const enqueue = (name: string) => {
|
|
253
|
+
const pkg = this.findPackage(props.packages, name);
|
|
254
|
+
if (pkg && !resolved.has(pkg.name)) queue.push(pkg);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
if (props.includeEslintConfig) enqueue("eslint-config");
|
|
258
|
+
|
|
259
|
+
while (queue.length > 0) {
|
|
260
|
+
const pkg = queue.shift()!;
|
|
261
|
+
if (resolved.has(pkg.name)) continue;
|
|
262
|
+
|
|
263
|
+
resolved.set(pkg.name, pkg);
|
|
264
|
+
for (const dep of [
|
|
265
|
+
...(pkg.packageJson?.startx?.requiredDeps ?? []),
|
|
266
|
+
...(pkg.packageJson?.startx?.requiredDevDeps ?? []),
|
|
267
|
+
]) {
|
|
268
|
+
enqueue(dep);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return Array.from(resolved.values());
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private static async ensureTemplatePackage(props: {
|
|
276
|
+
packages: PackageItem[];
|
|
277
|
+
name: string;
|
|
278
|
+
directory: ReturnType<typeof CliUtils.getDirectory>;
|
|
279
|
+
tags: TAGS[];
|
|
280
|
+
}) {
|
|
281
|
+
const pkg = this.findPackage(props.packages, props.name);
|
|
282
|
+
if (!pkg) {
|
|
283
|
+
logger.warn(`Could not find template package ${props.name}; skipping.`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
await this.installTemplatePackage({
|
|
288
|
+
pkg,
|
|
289
|
+
directory: props.directory,
|
|
290
|
+
tags: props.tags,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private static async installTemplatePackage(props: {
|
|
295
|
+
pkg: PackageItem;
|
|
296
|
+
directory: ReturnType<typeof CliUtils.getDirectory>;
|
|
297
|
+
tags: TAGS[];
|
|
298
|
+
}) {
|
|
299
|
+
if (!props.pkg.packageJson) {
|
|
300
|
+
throw new Error(`Missing package.json for ${props.pkg.name}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const destination = path.join(props.directory.workspace, props.pkg.relativePath);
|
|
304
|
+
if (await this.pathExists(path.join(destination, "package.json"))) {
|
|
305
|
+
logger.info(`Skipping ${props.pkg.name}; it already exists.`);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const tags = new Set<TAGS>([...props.tags, ...(props.pkg.packageJson.startx?.tags ?? [])]);
|
|
310
|
+
const ignoreList = props.pkg.packageJson.startx?.ignore ?? [];
|
|
311
|
+
if (ignoreList.includes("eslint-config")) tags.delete("eslint");
|
|
312
|
+
if (ignoreList.includes("vitest-config")) tags.delete("vitest");
|
|
313
|
+
|
|
314
|
+
const { packageJson, isWorkspace } = FileHandler.handlePackageJson({
|
|
315
|
+
app: props.pkg.packageJson,
|
|
316
|
+
tags: Array.from(tags),
|
|
317
|
+
name: props.pkg.packageJson.name || props.pkg.name,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
if (isWorkspace) {
|
|
321
|
+
throw new Error(`Cannot install workspace as a package: ${props.pkg.name}`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
await fsTool.writeJSONFile({ dir: destination, file: "package", content: packageJson });
|
|
325
|
+
await this.copyValidatedFilesFromFolder(props.pkg.path, destination, tags);
|
|
326
|
+
await fsTool.copyDirectory({
|
|
327
|
+
from: path.join(props.pkg.path, "src"),
|
|
328
|
+
to: path.join(destination, "src"),
|
|
329
|
+
exclude: !tags.has("vitest") ? /\.test\.tsx?$/ : undefined,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
logger.info(`Installed ${props.pkg.name}`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private static async copyValidatedFilesFromFolder(source: string, destination: string, tags: Set<TAGS>) {
|
|
336
|
+
const files = await fsTool.listFiles({ dir: source }).catch(() => []);
|
|
337
|
+
for (const file of files) {
|
|
338
|
+
const checked = FileCheck[file];
|
|
339
|
+
if (checked && !checked.tags.every(tag => tags.has(tag))) continue;
|
|
340
|
+
if (file === "package.json") continue;
|
|
341
|
+
|
|
342
|
+
await fsTool.copyFile({
|
|
343
|
+
from: path.join(source, file),
|
|
344
|
+
to: path.join(destination, file),
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private static createPackageJson(props: { name: string; eslintEnabled: boolean }) {
|
|
350
|
+
const scripts: Record<string, string> = {
|
|
351
|
+
typecheck: "tsc --noEmit",
|
|
352
|
+
clean: "rimraf dist .turbo",
|
|
353
|
+
};
|
|
354
|
+
const devDependencies: Record<string, string> = {
|
|
355
|
+
"typescript-config": "workspace:*",
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
if (props.eslintEnabled) {
|
|
359
|
+
scripts.lint = "eslint .";
|
|
360
|
+
scripts["lint:fix"] = "eslint . --fix";
|
|
361
|
+
devDependencies["eslint-config"] = "workspace:*";
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
name: props.name,
|
|
366
|
+
version: "1.0.0",
|
|
367
|
+
type: "module",
|
|
368
|
+
scripts,
|
|
369
|
+
exports: "./src/index.ts",
|
|
370
|
+
devDependencies,
|
|
371
|
+
startx: {
|
|
372
|
+
iTags: ["node"],
|
|
373
|
+
requiredDevDeps: ["typescript-config"],
|
|
374
|
+
...(props.eslintEnabled ? {} : { ignore: ["eslint-config"] }),
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private static getDefaultPackagePath(name: string) {
|
|
380
|
+
if (name.startsWith("@")) {
|
|
381
|
+
const [scope, packageName] = name.split("/");
|
|
382
|
+
return path.join("packages", scope, packageName);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return path.join("packages", name);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private static findPackage(packages: PackageItem[], name: string) {
|
|
389
|
+
return packages.find(pkg => pkg.name === name || pkg.packageJson?.name === name);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private static hasDependency(packageJson: StartXPackageJson, dependency: string) {
|
|
393
|
+
return Boolean(
|
|
394
|
+
packageJson.dependencies?.[dependency] ||
|
|
395
|
+
packageJson.devDependencies?.[dependency] ||
|
|
396
|
+
packageJson.peerDependencies?.[dependency]
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private static async readRootPackage(workspace: string) {
|
|
401
|
+
const content = await fs.readFile(path.join(workspace, "package.json"), "utf-8");
|
|
402
|
+
return JSON.parse(content) as StartXPackageJson;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private static async resolveDependencyVersion(workspace: string, dependency: string) {
|
|
406
|
+
const pnpmWorkspace = await CliUtils.parsePnpmWorkspace({ dir: workspace });
|
|
407
|
+
if (pnpmWorkspace?.catalog?.[dependency]) return "catalog:";
|
|
408
|
+
return dependency === "eslint" ? "^9.0.0" : "latest";
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private static async installRootDependencies(workspace: string) {
|
|
412
|
+
const rootPackage = await this.readRootPackage(workspace);
|
|
413
|
+
const packageManager = rootPackage.packageManager?.split("@")[0] || "pnpm";
|
|
414
|
+
const command = packageManager === "yarn" ? "yarn" : packageManager;
|
|
415
|
+
const args = packageManager === "yarn" ? ["install"] : ["install"];
|
|
416
|
+
|
|
417
|
+
logger.info(`Running ${command} ${args.join(" ")} to install ESLint...`);
|
|
418
|
+
|
|
419
|
+
await new Promise<void>((resolve, reject) => {
|
|
420
|
+
const child = spawn(command, args, {
|
|
421
|
+
cwd: workspace,
|
|
422
|
+
stdio: "inherit",
|
|
423
|
+
shell: process.platform === "win32",
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
child.on("error", reject);
|
|
427
|
+
child.on("close", code => {
|
|
428
|
+
if (code === 0) {
|
|
429
|
+
resolve();
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
reject(new Error(`${command} ${args.join(" ")} exited with code ${code}`));
|
|
434
|
+
});
|
|
435
|
+
}).catch(error => {
|
|
436
|
+
logger.warn(`Could not install dependencies automatically: ${error instanceof Error ? error.message : error}`);
|
|
437
|
+
logger.warn(`Run "${command} ${args.join(" ")}" manually in ${workspace}.`);
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private static async writeJson(file: string, content: object) {
|
|
442
|
+
await fs.writeFile(file, `${JSON.stringify(content, null, 2)}\n`);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private static async pathExists(target: string) {
|
|
446
|
+
try {
|
|
447
|
+
await fs.access(target);
|
|
448
|
+
return true;
|
|
449
|
+
} catch {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
@@ -14,6 +14,10 @@ export const scripts: SCRIPT = {
|
|
|
14
14
|
script: "email dev --port 3014 --dir ./src",
|
|
15
15
|
tags: ["node", "mail"],
|
|
16
16
|
},
|
|
17
|
+
{
|
|
18
|
+
script: "react-router dev",
|
|
19
|
+
tags: ["react-router", "frontend"],
|
|
20
|
+
},
|
|
17
21
|
],
|
|
18
22
|
"dev:debug": [
|
|
19
23
|
{
|
|
@@ -40,6 +44,10 @@ export const scripts: SCRIPT = {
|
|
|
40
44
|
script: "turbo run build",
|
|
41
45
|
tags: ["runnable", "root"],
|
|
42
46
|
},
|
|
47
|
+
{
|
|
48
|
+
script: "react-router build",
|
|
49
|
+
tags: ["react-router", "frontend", "runnable"],
|
|
50
|
+
},
|
|
43
51
|
{
|
|
44
52
|
script: "tsdown --config-loader unrun",
|
|
45
53
|
tags: ["runnable", "node", "tsdown"],
|
|
@@ -64,6 +72,10 @@ export const scripts: SCRIPT = {
|
|
|
64
72
|
script: "node dist/index.mjs",
|
|
65
73
|
tags: ["node", "runnable"],
|
|
66
74
|
},
|
|
75
|
+
{
|
|
76
|
+
script: "react-router-serve ./build/server/index.js",
|
|
77
|
+
tags: ["react-router", "frontend"],
|
|
78
|
+
},
|
|
67
79
|
],
|
|
68
80
|
"lint": [
|
|
69
81
|
{
|
|
@@ -91,7 +103,7 @@ export const scripts: SCRIPT = {
|
|
|
91
103
|
tags: ["root"],
|
|
92
104
|
},
|
|
93
105
|
{
|
|
94
|
-
script: "rimraf dist .turbo
|
|
106
|
+
script: "rimraf dist build .turbo",
|
|
95
107
|
tags: [],
|
|
96
108
|
},
|
|
97
109
|
],
|
|
@@ -101,7 +113,7 @@ export const scripts: SCRIPT = {
|
|
|
101
113
|
tags: ["root"],
|
|
102
114
|
},
|
|
103
115
|
{
|
|
104
|
-
script: "rimraf node_modules dist .turbo",
|
|
116
|
+
script: "rimraf node_modules dist build .turbo",
|
|
105
117
|
tags: ["node"],
|
|
106
118
|
},
|
|
107
119
|
],
|
|
@@ -134,6 +146,10 @@ export const scripts: SCRIPT = {
|
|
|
134
146
|
script: "tsc --noEmit",
|
|
135
147
|
tags: ["node"],
|
|
136
148
|
},
|
|
149
|
+
{
|
|
150
|
+
script: "react-router typegen && tsc",
|
|
151
|
+
tags: ["react-router", "frontend"],
|
|
152
|
+
},
|
|
137
153
|
],
|
|
138
154
|
"format": [
|
|
139
155
|
{
|
|
@@ -7,15 +7,13 @@ import { version } from "../../../package.json";
|
|
|
7
7
|
|
|
8
8
|
const program = new Command();
|
|
9
9
|
|
|
10
|
-
program
|
|
11
|
-
.name("startx")
|
|
12
|
-
.description("StartX CLI - Your all in one monorepo startup tool.")
|
|
13
|
-
.version(version);
|
|
10
|
+
program.name("startx").description("StartX CLI - Your all in one monorepo startup tool.").version(version);
|
|
14
11
|
|
|
15
12
|
program.command("ping").action(() => {
|
|
16
13
|
logger.info("pong");
|
|
17
14
|
});
|
|
18
15
|
|
|
19
16
|
program.addCommand(InitCommand.command);
|
|
17
|
+
// program.addCommand(PackageCommand.command);
|
|
20
18
|
|
|
21
19
|
program.parse(process.argv);
|
|
@@ -21,6 +21,7 @@ export type TAGS =
|
|
|
21
21
|
// Frontend tags
|
|
22
22
|
| "frontend"
|
|
23
23
|
| "react"
|
|
24
|
+
| "react-router"
|
|
24
25
|
// Cli tags
|
|
25
26
|
| "cli"
|
|
26
27
|
| "commander"
|
|
@@ -36,10 +37,7 @@ export type TAGS =
|
|
|
36
37
|
|
|
37
38
|
export type SCRIPT = Record<string, Array<{ script: string; tags: TAGS[] }>>;
|
|
38
39
|
|
|
39
|
-
export type WHITELIST_DEPS = Record<
|
|
40
|
-
string,
|
|
41
|
-
{ tags: TAGS[]; version: string; isDevDependency?: boolean }
|
|
42
|
-
>;
|
|
40
|
+
export type WHITELIST_DEPS = Record<string, { tags: TAGS[]; version: string; isDevDependency?: boolean }>;
|
|
43
41
|
export type WHITELIST_FILES = Record<string, { tags: TAGS[] }>;
|
|
44
42
|
|
|
45
43
|
export type PackageDef<T extends string> = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { input, select, checkbox } from "@inquirer/prompts";
|
|
1
|
+
import { input, select, checkbox, confirm } from "@inquirer/prompts";
|
|
2
2
|
import { type z, type ZodTypeAny, ZodEnum, ZodNumber } from "zod";
|
|
3
3
|
|
|
4
4
|
type PromptProps<T extends ZodTypeAny | undefined> = {
|
|
@@ -18,6 +18,13 @@ type MultiSelectProps<T extends "single" | "multiple"> = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export class CommonInquirer {
|
|
21
|
+
static async confirm(props: { message: string; default?: boolean }) {
|
|
22
|
+
return await confirm({
|
|
23
|
+
message: props.message,
|
|
24
|
+
default: props.default,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
static async getText<T extends ZodTypeAny | undefined>(
|
|
22
29
|
props: PromptProps<T>
|
|
23
30
|
): Promise<T extends ZodTypeAny ? z.infer<T> : string> {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "radix-sera",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "../../packages/ui/src/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true
|
|
11
|
+
},
|
|
12
|
+
"iconLibrary": "phosphor",
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/components",
|
|
15
|
+
"hooks": "@/hooks",
|
|
16
|
+
"lib": "@/lib",
|
|
17
|
+
"utils": "@repo/ui/lib/utils",
|
|
18
|
+
"ui": "@repo/ui/components"
|
|
19
|
+
},
|
|
20
|
+
"rtl": false,
|
|
21
|
+
"menuColor": "inverted-translucent",
|
|
22
|
+
"menuAccent": "subtle"
|
|
23
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { SessionUser, SessionUserRole } from "@repo/common/types/users";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { create } from "zustand";
|
|
4
|
+
import { axiosClient } from "../axios-client";
|
|
5
|
+
|
|
6
|
+
type AuthStatus = "loading" | "authenticated" | "unauthenticated";
|
|
7
|
+
|
|
8
|
+
interface AuthState {
|
|
9
|
+
status: AuthStatus;
|
|
10
|
+
role?: SessionUserRole;
|
|
11
|
+
user: SessionUser | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface AuthActions {
|
|
15
|
+
updateStatus(status: AuthStatus): void;
|
|
16
|
+
updateUser(user: SessionUser | null): void;
|
|
17
|
+
reset(): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const useAuthStore = create<AuthState & AuthActions>(set => ({
|
|
21
|
+
status: "loading",
|
|
22
|
+
user: null,
|
|
23
|
+
role: undefined,
|
|
24
|
+
|
|
25
|
+
updateStatus: status =>
|
|
26
|
+
set({
|
|
27
|
+
status,
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
updateUser: user =>
|
|
31
|
+
set({
|
|
32
|
+
user,
|
|
33
|
+
role: user?.role,
|
|
34
|
+
status: user ? "authenticated" : "unauthenticated",
|
|
35
|
+
}),
|
|
36
|
+
|
|
37
|
+
reset: () =>
|
|
38
|
+
set({
|
|
39
|
+
user: null,
|
|
40
|
+
role: undefined,
|
|
41
|
+
status: "unauthenticated",
|
|
42
|
+
}),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
export const AuthStartup = () => {
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const initAuth = async () => {
|
|
48
|
+
try {
|
|
49
|
+
await axiosClient.getAccessToken();
|
|
50
|
+
} catch {
|
|
51
|
+
useAuthStore.getState().reset();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
void initAuth();
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
return null;
|
|
59
|
+
};
|