alepha 0.20.2 → 0.20.3
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/README.md +0 -1
- package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
- package/assets/swagger-ui/swagger-ui.css +1 -1
- package/dist/api/audits/index.browser.js +49 -0
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +49 -0
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +16 -75
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +1 -10
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/organizations/index.d.ts.map +1 -1
- package/dist/api/parameters/index.browser.js +37 -0
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +4 -65
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +37 -0
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/users/index.d.ts +207 -5184
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +2 -4
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +2 -1
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/bucket/index.js +5 -1
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +5 -1
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/captcha/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +217 -11647
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +706 -42
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.js +7 -1
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +41 -64
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +47 -0
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.js +15 -0
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/command/index.js +1 -1
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +2 -8
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.js.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/core/index.js.map +1 -1
- package/dist/email/smtp/index.js +2 -10522
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.d.ts +4 -8085
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +3 -33554
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.js +32 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.js +5 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +1 -361
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +14 -406
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +96 -5117
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +23 -419
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js +17 -20
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +2 -613
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js +17 -20
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.js +22 -17
- package/dist/react/intro/index.js.map +1 -1
- package/dist/react/router/index.browser.js +78 -2
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +22 -1
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +102 -4
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/testing/index.d.ts +1 -411
- package/dist/react/testing/index.d.ts.map +1 -1
- package/dist/react/testing/index.js +13 -12293
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.js +3 -0
- package/dist/react/ui/index.js.map +1 -1
- package/dist/react/websocket/index.js.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -83
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +2 -391
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js +2 -391
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +2 -325
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +3 -1362
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +1 -1054
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +16 -1224
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +1 -4
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +19 -4
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +1 -514
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +4 -4356
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.js +1 -1
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.js.map +1 -1
- package/dist/system/index.workerd.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/websocket/index.browser.js +21 -0
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.js +21 -0
- package/dist/websocket/index.js.map +1 -1
- package/package.json +18 -15
- package/src/api/files/__tests__/FileController.spec.ts +1 -1
- package/src/api/jobs/__tests__/$job.spec.ts +5 -1
- package/src/api/users/schemas/userQuerySchema.ts +0 -1
- package/src/api/users/services/UserService.ts +1 -5
- package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
- package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
- package/src/api/verifications/services/VerificationService.ts +1 -0
- package/src/cli/core/__tests__/init.spec.ts +208 -0
- package/src/cli/core/commands/init.ts +12 -0
- package/src/cli/core/services/PackageManagerUtils.ts +23 -6
- package/src/cli/core/services/ProjectScaffolder.ts +298 -20
- package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
- package/src/cli/core/tasks/BuildServerTask.ts +8 -0
- package/src/cli/core/templates/apiIndexTs.ts +23 -1
- package/src/cli/core/templates/componentsJsonTs.ts +39 -0
- package/src/cli/core/templates/mainCss.ts +1 -0
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
- package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
- package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
- package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
- package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
- package/src/cli/core/templates/webAppRouterTs.ts +104 -1
- package/src/cli/core/templates/webIndexTs.ts +23 -1
- package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
- package/src/command/providers/CliProvider.ts +1 -1
- package/src/core/interfaces/Service.ts +3 -1
- package/src/core/providers/TypeProvider.ts +1 -1
- package/src/logger/services/Logger.ts +1 -1
- package/src/mcp/__tests__/$resource.spec.ts +1 -1
- package/src/mcp/__tests__/$tool.spec.ts +1 -1
- package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
- package/src/orm/__tests__/$repository-tests.ts +1 -0
- package/src/orm/__tests__/orm-next-tests.ts +2 -67
- package/src/orm/__tests__/orm-next.spec.ts +0 -21
- package/src/orm/core/index.shared.ts +0 -2
- package/src/orm/core/index.ts +1 -2
- package/src/orm/core/primitives/$repository.ts +3 -6
- package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
- package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
- package/src/orm/core/services/ModelBuilder.ts +1 -13
- package/src/orm/core/services/Repository.ts +1 -42
- package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
- package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
- package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
- package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
- package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
- package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
- package/src/react/router/providers/ReactServerProvider.ts +1 -0
- package/src/scheduler/providers/CronProvider.ts +1 -1
- package/src/security/primitives/$basicAuth.ts +1 -1
- package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
- package/src/server/core/interfaces/ServerRequest.ts +1 -0
- package/src/server/core/providers/ServerProvider.ts +1 -1
- package/src/server/core/providers/ServerRouterProvider.ts +2 -2
- package/src/server/core/services/HttpClient.ts +1 -1
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
- package/dist/react/testing/chunk-DBEY4PJZ.js +0 -16
- package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
- package/src/orm/core/helpers/parseQueryString.ts +0 -502
- package/src/orm/core/primitives/$view.ts +0 -88
|
@@ -2,13 +2,14 @@ import { basename, dirname } from "node:path";
|
|
|
2
2
|
import { $inject, AlephaError } from "alepha";
|
|
3
3
|
import type { RunnerMethod } from "alepha/command";
|
|
4
4
|
import { $logger, ConsoleColorProvider } from "alepha/logger";
|
|
5
|
-
import { FileSystemProvider } from "alepha/system";
|
|
5
|
+
import { FileSystemProvider, ShellProvider } from "alepha/system";
|
|
6
6
|
import { type AgentMdOptions, agentMd } from "../templates/agentMd.ts";
|
|
7
7
|
import { alephaConfigTs } from "../templates/alephaConfigTs.ts";
|
|
8
8
|
import { apiHelloControllerTs } from "../templates/apiHelloControllerTs.ts";
|
|
9
9
|
import { apiHelloResponseSchemaTs } from "../templates/apiHelloResponseSchemaTs.ts";
|
|
10
10
|
import { apiIndexTs } from "../templates/apiIndexTs.ts";
|
|
11
11
|
import { biomeJson } from "../templates/biomeJson.ts";
|
|
12
|
+
import { componentsJsonTs } from "../templates/componentsJsonTs.ts";
|
|
12
13
|
import { dummySpecTs } from "../templates/dummySpecTs.ts";
|
|
13
14
|
import { editorconfig } from "../templates/editorconfig.ts";
|
|
14
15
|
import { gitignore } from "../templates/gitignore.ts";
|
|
@@ -16,6 +17,19 @@ import { logoSvg } from "../templates/logoSvg.ts";
|
|
|
16
17
|
import { mainBrowserTs } from "../templates/mainBrowserTs.ts";
|
|
17
18
|
import { mainCss } from "../templates/mainCss.ts";
|
|
18
19
|
import { mainServerTs } from "../templates/mainServerTs.ts";
|
|
20
|
+
import { saasAdminLayoutTsx } from "../templates/saasAdminLayoutTsx.ts";
|
|
21
|
+
import {
|
|
22
|
+
saasAdminSessionsTsx,
|
|
23
|
+
saasAdminUsersTsx,
|
|
24
|
+
} from "../templates/saasAdminPagesTsx.ts";
|
|
25
|
+
import { saasAuthLayoutTsx } from "../templates/saasAuthLayoutTsx.ts";
|
|
26
|
+
import {
|
|
27
|
+
saasAuthLoginTsx,
|
|
28
|
+
saasAuthRegisterTsx,
|
|
29
|
+
saasAuthResetPasswordTsx,
|
|
30
|
+
saasAuthVerifyEmailTsx,
|
|
31
|
+
} from "../templates/saasAuthPagesTsx.ts";
|
|
32
|
+
import { saasRealmProviderTs } from "../templates/saasRealmProviderTs.ts";
|
|
19
33
|
import { tsconfigJson } from "../templates/tsconfigJson.ts";
|
|
20
34
|
import { viteConfigTs } from "../templates/viteConfigTs.ts";
|
|
21
35
|
import { vitestConfigTs } from "../templates/vitestConfigTs.ts";
|
|
@@ -41,6 +55,7 @@ export class ProjectScaffolder {
|
|
|
41
55
|
protected readonly log = $logger();
|
|
42
56
|
protected readonly colors = $inject(ConsoleColorProvider);
|
|
43
57
|
protected readonly fs = $inject(FileSystemProvider);
|
|
58
|
+
protected readonly shell = $inject(ShellProvider);
|
|
44
59
|
protected readonly pm = $inject(PackageManagerUtils);
|
|
45
60
|
protected readonly utils = $inject(AlephaCliUtils);
|
|
46
61
|
|
|
@@ -70,7 +85,13 @@ export class ProjectScaffolder {
|
|
|
70
85
|
*/
|
|
71
86
|
checkWorkspace?: boolean;
|
|
72
87
|
packageJson?: boolean | DependencyModes;
|
|
73
|
-
|
|
88
|
+
/**
|
|
89
|
+
* `true` writes a tsconfig.json if one doesn't already exist (parent
|
|
90
|
+
* dirs included). `"local"` writes one when none exists *in this
|
|
91
|
+
* directory* — used by shadcn since the CLI reads the local
|
|
92
|
+
* tsconfig directly for import-alias detection.
|
|
93
|
+
*/
|
|
94
|
+
tsconfigJson?: boolean | "local";
|
|
74
95
|
biomeJson?: boolean;
|
|
75
96
|
editorconfig?: boolean;
|
|
76
97
|
agentMd?: false | AgentMdOptions;
|
|
@@ -91,7 +112,12 @@ export class ProjectScaffolder {
|
|
|
91
112
|
);
|
|
92
113
|
}
|
|
93
114
|
if (opts.tsconfigJson) {
|
|
94
|
-
tasks.push(
|
|
115
|
+
tasks.push(
|
|
116
|
+
this.ensureTsConfig(root, {
|
|
117
|
+
force,
|
|
118
|
+
localOnly: opts.tsconfigJson === "local",
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
95
121
|
}
|
|
96
122
|
if (opts.biomeJson) {
|
|
97
123
|
tasks.push(this.ensureBiomeConfig(root, { force, checkWorkspace }));
|
|
@@ -112,10 +138,15 @@ export class ProjectScaffolder {
|
|
|
112
138
|
|
|
113
139
|
public async ensureTsConfig(
|
|
114
140
|
root: string,
|
|
115
|
-
opts: { force?: boolean } = {},
|
|
141
|
+
opts: { force?: boolean; localOnly?: boolean } = {},
|
|
116
142
|
): Promise<void> {
|
|
117
|
-
// Check if tsconfig.json exists in current or parent directories
|
|
118
|
-
|
|
143
|
+
// Check if tsconfig.json exists in current or parent directories.
|
|
144
|
+
// `localOnly: true` skips the parent walk — needed when a tool reads the
|
|
145
|
+
// local tsconfig directly (shadcn does this for import-alias detection).
|
|
146
|
+
const exists = opts.localOnly
|
|
147
|
+
? await this.fs.exists(this.fs.join(root, "tsconfig.json"))
|
|
148
|
+
: await this.existsInParents(root, "tsconfig.json");
|
|
149
|
+
if (!opts.force && exists) {
|
|
119
150
|
return;
|
|
120
151
|
}
|
|
121
152
|
await this.fs.writeFile(
|
|
@@ -240,7 +271,7 @@ export class ProjectScaffolder {
|
|
|
240
271
|
*/
|
|
241
272
|
public async ensureApiProject(
|
|
242
273
|
root: string,
|
|
243
|
-
opts: { force?: boolean } = {},
|
|
274
|
+
opts: { saas?: boolean; force?: boolean } = {},
|
|
244
275
|
): Promise<void> {
|
|
245
276
|
const appName = this.getAppName(root);
|
|
246
277
|
|
|
@@ -256,7 +287,7 @@ export class ProjectScaffolder {
|
|
|
256
287
|
await this.ensureFile(
|
|
257
288
|
root,
|
|
258
289
|
"src/api/index.ts",
|
|
259
|
-
apiIndexTs({ appName }),
|
|
290
|
+
apiIndexTs({ appName, saas: opts.saas }),
|
|
260
291
|
opts.force,
|
|
261
292
|
);
|
|
262
293
|
await this.ensureFile(
|
|
@@ -271,6 +302,38 @@ export class ProjectScaffolder {
|
|
|
271
302
|
apiHelloResponseSchemaTs(),
|
|
272
303
|
opts.force,
|
|
273
304
|
);
|
|
305
|
+
|
|
306
|
+
if (opts.saas) {
|
|
307
|
+
await this.fs.mkdir(this.fs.join(root, "src/api/providers"), {
|
|
308
|
+
recursive: true,
|
|
309
|
+
});
|
|
310
|
+
const adminEmail = await this.detectGitEmail();
|
|
311
|
+
await this.ensureFile(
|
|
312
|
+
root,
|
|
313
|
+
"src/api/providers/RealmProvider.ts",
|
|
314
|
+
saasRealmProviderTs({ adminEmail }),
|
|
315
|
+
opts.force,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Best-effort lookup for the developer's git email (used as the seeded
|
|
322
|
+
* `adminEmails` entry in the SaaS realm). Returns undefined if git isn't
|
|
323
|
+
* available or if `user.email` isn't configured — the template falls back
|
|
324
|
+
* to `admin@example.com` in that case.
|
|
325
|
+
*/
|
|
326
|
+
protected async detectGitEmail(): Promise<string | undefined> {
|
|
327
|
+
try {
|
|
328
|
+
const stdout = (await this.shell.run("git config --get user.email", {
|
|
329
|
+
capture: true,
|
|
330
|
+
})) as unknown as string;
|
|
331
|
+
const email = (stdout ?? "").trim();
|
|
332
|
+
if (!email || !email.includes("@")) return undefined;
|
|
333
|
+
return email;
|
|
334
|
+
} catch {
|
|
335
|
+
return undefined;
|
|
336
|
+
}
|
|
274
337
|
}
|
|
275
338
|
|
|
276
339
|
// ===========================================
|
|
@@ -290,6 +353,8 @@ export class ProjectScaffolder {
|
|
|
290
353
|
opts: {
|
|
291
354
|
api?: boolean;
|
|
292
355
|
tailwind?: boolean;
|
|
356
|
+
shadcn?: boolean;
|
|
357
|
+
saas?: boolean;
|
|
293
358
|
force?: boolean;
|
|
294
359
|
} = {},
|
|
295
360
|
): Promise<void> {
|
|
@@ -300,6 +365,15 @@ export class ProjectScaffolder {
|
|
|
300
365
|
recursive: true,
|
|
301
366
|
});
|
|
302
367
|
|
|
368
|
+
if (opts.saas) {
|
|
369
|
+
await this.fs.mkdir(this.fs.join(root, "src/web/components/auth"), {
|
|
370
|
+
recursive: true,
|
|
371
|
+
});
|
|
372
|
+
await this.fs.mkdir(this.fs.join(root, "src/web/components/admin"), {
|
|
373
|
+
recursive: true,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
303
377
|
// public/favicon.svg
|
|
304
378
|
await this.fs.mkdir(this.fs.join(root, "public"), { recursive: true });
|
|
305
379
|
await this.ensureFile(root, "public/favicon.svg", logoSvg, opts.force);
|
|
@@ -317,17 +391,32 @@ export class ProjectScaffolder {
|
|
|
317
391
|
await this.ensureFile(root, "vite.config.ts", viteConfigTs(), opts.force);
|
|
318
392
|
}
|
|
319
393
|
|
|
394
|
+
// shadcn/ui: write components.json before running `shadcn init` — the
|
|
395
|
+
// CLI respects an existing config and skips its interactive prompts,
|
|
396
|
+
// which lets us pin our aliases (`@/web/*`) and the `@alepha` registry.
|
|
397
|
+
// The CLI itself writes the cn() helper, theme tokens, and installs
|
|
398
|
+
// runtime deps (clsx, tailwind-merge, class-variance-authority,
|
|
399
|
+
// lucide-react, tw-animate-css) — see runShadcnInit below.
|
|
400
|
+
if (opts.shadcn) {
|
|
401
|
+
await this.ensureFile(
|
|
402
|
+
root,
|
|
403
|
+
"components.json",
|
|
404
|
+
componentsJsonTs(),
|
|
405
|
+
opts.force,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
320
409
|
// Web structure
|
|
321
410
|
await this.ensureFile(
|
|
322
411
|
root,
|
|
323
412
|
"src/web/index.ts",
|
|
324
|
-
webIndexTs({ appName }),
|
|
413
|
+
webIndexTs({ appName, saas: opts.saas }),
|
|
325
414
|
opts.force,
|
|
326
415
|
);
|
|
327
416
|
await this.ensureFile(
|
|
328
417
|
root,
|
|
329
418
|
"src/web/AppRouter.ts",
|
|
330
|
-
webAppRouterTs({ api: opts.api }),
|
|
419
|
+
webAppRouterTs({ api: opts.api, saas: opts.saas }),
|
|
331
420
|
opts.force,
|
|
332
421
|
);
|
|
333
422
|
await this.ensureFile(
|
|
@@ -342,6 +431,62 @@ export class ProjectScaffolder {
|
|
|
342
431
|
mainBrowserTs(),
|
|
343
432
|
opts.force,
|
|
344
433
|
);
|
|
434
|
+
|
|
435
|
+
if (opts.saas) {
|
|
436
|
+
// Auth — layout + 4 pages, each a thin wrapper around the registry
|
|
437
|
+
// component that `shadcn add @alepha/auth-*` drops at
|
|
438
|
+
// src/web/components/auth-*.tsx.
|
|
439
|
+
await this.ensureFile(
|
|
440
|
+
root,
|
|
441
|
+
"src/web/components/auth/AuthLayout.tsx",
|
|
442
|
+
saasAuthLayoutTsx(),
|
|
443
|
+
opts.force,
|
|
444
|
+
);
|
|
445
|
+
await this.ensureFile(
|
|
446
|
+
root,
|
|
447
|
+
"src/web/components/auth/Login.tsx",
|
|
448
|
+
saasAuthLoginTsx(),
|
|
449
|
+
opts.force,
|
|
450
|
+
);
|
|
451
|
+
await this.ensureFile(
|
|
452
|
+
root,
|
|
453
|
+
"src/web/components/auth/Register.tsx",
|
|
454
|
+
saasAuthRegisterTsx(),
|
|
455
|
+
opts.force,
|
|
456
|
+
);
|
|
457
|
+
await this.ensureFile(
|
|
458
|
+
root,
|
|
459
|
+
"src/web/components/auth/ResetPassword.tsx",
|
|
460
|
+
saasAuthResetPasswordTsx(),
|
|
461
|
+
opts.force,
|
|
462
|
+
);
|
|
463
|
+
await this.ensureFile(
|
|
464
|
+
root,
|
|
465
|
+
"src/web/components/auth/VerifyEmail.tsx",
|
|
466
|
+
saasAuthVerifyEmailTsx(),
|
|
467
|
+
opts.force,
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
// Admin — AppShell layout + 5 admin-* pages
|
|
471
|
+
await this.ensureFile(
|
|
472
|
+
root,
|
|
473
|
+
"src/web/components/admin/AdminLayout.tsx",
|
|
474
|
+
saasAdminLayoutTsx(),
|
|
475
|
+
opts.force,
|
|
476
|
+
);
|
|
477
|
+
await this.ensureFile(
|
|
478
|
+
root,
|
|
479
|
+
"src/web/components/admin/Users.tsx",
|
|
480
|
+
saasAdminUsersTsx(),
|
|
481
|
+
opts.force,
|
|
482
|
+
);
|
|
483
|
+
await this.ensureFile(
|
|
484
|
+
root,
|
|
485
|
+
"src/web/components/admin/Sessions.tsx",
|
|
486
|
+
saasAdminSessionsTsx(),
|
|
487
|
+
opts.force,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
345
490
|
}
|
|
346
491
|
|
|
347
492
|
// ===========================================
|
|
@@ -394,6 +539,10 @@ export class ProjectScaffolder {
|
|
|
394
539
|
api?: boolean;
|
|
395
540
|
react?: boolean;
|
|
396
541
|
tailwind?: boolean;
|
|
542
|
+
/** boolean toggle, or a string preset id (default `b0` when bare). */
|
|
543
|
+
shadcn?: boolean | string;
|
|
544
|
+
/** boolean toggle, or a string preset id (default `b0` when bare). */
|
|
545
|
+
saas?: boolean | string;
|
|
397
546
|
test?: boolean;
|
|
398
547
|
force?: boolean;
|
|
399
548
|
};
|
|
@@ -404,13 +553,41 @@ export class ProjectScaffolder {
|
|
|
404
553
|
await this.fs.mkdir(root, { force: true });
|
|
405
554
|
}
|
|
406
555
|
|
|
407
|
-
//
|
|
556
|
+
// `--shadcn` / `--saas` are union flags: bare → true, string → preset.
|
|
557
|
+
// Capture the preset string (default `b0`), then normalize both flags
|
|
558
|
+
// to plain booleans so the rest of the pipeline keeps its boolean
|
|
559
|
+
// contract with PackageManagerUtils + ensureWebProject etc.
|
|
560
|
+
const shadcnPreset =
|
|
561
|
+
(typeof flags.saas === "string" && flags.saas) ||
|
|
562
|
+
(typeof flags.shadcn === "string" && flags.shadcn) ||
|
|
563
|
+
"b0";
|
|
564
|
+
|
|
565
|
+
// Cast to a narrower view so downstream sees pure booleans.
|
|
566
|
+
const f = flags as Omit<typeof flags, "shadcn" | "saas"> & {
|
|
567
|
+
shadcn?: boolean;
|
|
568
|
+
saas?: boolean;
|
|
569
|
+
};
|
|
570
|
+
f.shadcn = !!flags.shadcn;
|
|
571
|
+
f.saas = !!flags.saas;
|
|
572
|
+
|
|
573
|
+
// Flag cascading:
|
|
574
|
+
// --saas → --shadcn + --api
|
|
575
|
+
// --shadcn → --tailwind
|
|
576
|
+
// --tailwind→ --react
|
|
577
|
+
if (f.saas) {
|
|
578
|
+
f.shadcn = true;
|
|
579
|
+
f.api = true;
|
|
580
|
+
}
|
|
581
|
+
if (f.shadcn) {
|
|
582
|
+
f.tailwind = true;
|
|
583
|
+
}
|
|
408
584
|
if (flags.tailwind) {
|
|
409
585
|
flags.react = true;
|
|
410
586
|
}
|
|
411
587
|
|
|
412
588
|
// When codegen flags are set, target directory must be empty (unless --force)
|
|
413
|
-
const hasCodegenFlags =
|
|
589
|
+
const hasCodegenFlags =
|
|
590
|
+
flags.api || flags.react || flags.tailwind || flags.shadcn || flags.saas;
|
|
414
591
|
if (hasCodegenFlags && !flags.force) {
|
|
415
592
|
const files = await this.fs.ls(root);
|
|
416
593
|
// Allow a directory that only has package.json (common for monorepo packages)
|
|
@@ -441,9 +618,12 @@ export class ProjectScaffolder {
|
|
|
441
618
|
handler: async () => {
|
|
442
619
|
await this.ensureConfig(root, {
|
|
443
620
|
force,
|
|
444
|
-
packageJson: { ...
|
|
445
|
-
// Skip workspace-level configs if they exist at workspace root
|
|
446
|
-
|
|
621
|
+
packageJson: { ...f, isPackage: workspace.isPackage },
|
|
622
|
+
// Skip workspace-level configs if they exist at workspace root —
|
|
623
|
+
// unless --shadcn is set: the shadcn CLI reads the local
|
|
624
|
+
// tsconfig.json directly to detect import aliases (it doesn't
|
|
625
|
+
// follow `extends`), so we must ensure one exists in the package.
|
|
626
|
+
tsconfigJson: f.shadcn ? "local" : !workspace.config.tsconfigJson,
|
|
447
627
|
biomeJson: true,
|
|
448
628
|
editorconfig: !workspace.config.editorconfig,
|
|
449
629
|
agentMd: agentType ? { type: agentType } : false,
|
|
@@ -459,12 +639,14 @@ export class ProjectScaffolder {
|
|
|
459
639
|
force,
|
|
460
640
|
});
|
|
461
641
|
if (flags.api) {
|
|
462
|
-
await this.ensureApiProject(root, { force });
|
|
642
|
+
await this.ensureApiProject(root, { saas: !!flags.saas, force });
|
|
463
643
|
}
|
|
464
644
|
if (flags.react && !isExpo) {
|
|
465
645
|
await this.ensureWebProject(root, {
|
|
466
646
|
api: !!flags.api,
|
|
467
647
|
tailwind: !!flags.tailwind,
|
|
648
|
+
shadcn: !!flags.shadcn,
|
|
649
|
+
saas: !!flags.saas,
|
|
468
650
|
force,
|
|
469
651
|
});
|
|
470
652
|
}
|
|
@@ -503,10 +685,76 @@ export class ProjectScaffolder {
|
|
|
503
685
|
await this.ensureTestDir(root);
|
|
504
686
|
}
|
|
505
687
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
688
|
+
// shadcn/ui: run `<pm> shadcn init` against the components.json we wrote
|
|
689
|
+
// earlier. shadcn detects the existing config, respects our aliases,
|
|
690
|
+
// injects theme tokens into src/main.css, writes src/web/lib/utils.ts,
|
|
691
|
+
// and installs runtime deps (clsx, tailwind-merge, etc.).
|
|
692
|
+
//
|
|
693
|
+
// Flags chosen to keep this fully non-interactive:
|
|
694
|
+
// --yes skip confirmation prompts (default in shadcn v4 but
|
|
695
|
+
// passed explicitly so older versions also behave)
|
|
696
|
+
// --no-monorepo skip the monorepo prompt — we ship a single-app
|
|
697
|
+
// layout; users opt into monorepo via `--monorepo`
|
|
698
|
+
// on the alepha side later
|
|
699
|
+
// --silent suppress shadcn's own progress output; alepha's
|
|
700
|
+
// runner already prints a status line
|
|
701
|
+
//
|
|
702
|
+
// We deliberately do NOT pass `--defaults` (would force Next.js +
|
|
703
|
+
// base-nova preset) or `--template` (only applies to scratch projects;
|
|
704
|
+
// ours already has main.server.ts / main.browser.ts).
|
|
705
|
+
// Each PM has a different way to exec a project-local binary.
|
|
706
|
+
const exec = pmExecPrefix(pmName);
|
|
707
|
+
|
|
708
|
+
if (flags.shadcn) {
|
|
709
|
+
// Fully non-interactive shadcn init. The `--preset` arg is what makes
|
|
710
|
+
// this work — without it shadcn falls back to interactive prompts even
|
|
711
|
+
// with --yes/--force. Defaults: vite template + radix base + reinstall
|
|
712
|
+
// (so the components.json we pre-wrote stays canonical).
|
|
713
|
+
await run(
|
|
714
|
+
`${exec} shadcn init --no-monorepo --base radix -t vite --yes --force --reinstall --preset ${escapeShellArg(shadcnPreset)}`,
|
|
715
|
+
{ alias: `running shadcn init (preset ${shadcnPreset})`, root },
|
|
716
|
+
);
|
|
717
|
+
// Re-pin our aliases + alepha registry — `shadcn init --force`
|
|
718
|
+
// overwrites components.json with the template defaults.
|
|
719
|
+
await this.fs.writeFile(
|
|
720
|
+
this.fs.join(root, "components.json"),
|
|
721
|
+
componentsJsonTs(),
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// SaaS preset: pull in the auth + admin registry components from the
|
|
726
|
+
// public alepha registry (already wired via components.json's
|
|
727
|
+
// `registries: { "@alepha": "https://alepha.dev/r/{name}.json" }`).
|
|
728
|
+
// Each `shadcn add` writes the component into src/web/components/* and
|
|
729
|
+
// pulls its peer primitives + dependencies (sonner, etc.).
|
|
730
|
+
if (flags.saas) {
|
|
731
|
+
// Pull the public SaaS bundle in one shot — it aggregates control,
|
|
732
|
+
// auto-form, alepha-table, use-confirm, app-shell, every auth-*, and
|
|
733
|
+
// every admin-* block. Definition lives at
|
|
734
|
+
// https://alepha.dev/r/saas.json (see @alepha/ui-registry).
|
|
735
|
+
// `--yes --overwrite` is the only combo that works non-interactively
|
|
736
|
+
// when registry items would replace files we pre-wrote (auth-login etc.
|
|
737
|
+
// overlap with shadcn primitives like button/input).
|
|
738
|
+
await run(`${exec} shadcn add @alepha/saas --yes --overwrite`, {
|
|
739
|
+
alias: "adding alepha saas registry bundle",
|
|
740
|
+
root,
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Best-effort lint: shadcn-imported registry components occasionally
|
|
745
|
+
// trip biome rules (e.g. noArrayIndexKey on a Fragment loop). The user
|
|
746
|
+
// can fix or silence these later — don't block the whole init.
|
|
747
|
+
try {
|
|
748
|
+
await run(`${pmName} run lint`, {
|
|
749
|
+
alias: "running linter",
|
|
750
|
+
root,
|
|
751
|
+
});
|
|
752
|
+
} catch (err) {
|
|
753
|
+
this.log.warn(
|
|
754
|
+
"Linter reported issues during init — continuing. Run `lint` again later to inspect.",
|
|
755
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
756
|
+
);
|
|
757
|
+
}
|
|
510
758
|
|
|
511
759
|
// Initialize git repository if not in a workspace package
|
|
512
760
|
if (!workspace.isPackage) {
|
|
@@ -587,3 +835,33 @@ export class ProjectScaffolder {
|
|
|
587
835
|
}
|
|
588
836
|
}
|
|
589
837
|
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Map a package manager name to the command that runs a project-local binary.
|
|
841
|
+
*
|
|
842
|
+
* - npm: `npx`
|
|
843
|
+
* - yarn: `yarn` (yarn auto-resolves binary names; `yarn shadcn ...` works)
|
|
844
|
+
* - pnpm: `pnpm exec`
|
|
845
|
+
* - bun: `bunx`
|
|
846
|
+
*
|
|
847
|
+
* Used to invoke `shadcn init` / `shadcn add` regardless of the user's PM —
|
|
848
|
+
* `npm shadcn ...` is invalid (it tries to run a script named `shadcn`).
|
|
849
|
+
*/
|
|
850
|
+
/** Quote a value so it survives shell parsing. */
|
|
851
|
+
const escapeShellArg = (value: string): string => {
|
|
852
|
+
if (/^[A-Za-z0-9_./@:-]+$/.test(value)) return value;
|
|
853
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
const pmExecPrefix = (pmName: string): string => {
|
|
857
|
+
switch (pmName) {
|
|
858
|
+
case "npm":
|
|
859
|
+
return "npx";
|
|
860
|
+
case "pnpm":
|
|
861
|
+
return "pnpm exec";
|
|
862
|
+
case "bun":
|
|
863
|
+
return "bunx";
|
|
864
|
+
default:
|
|
865
|
+
return "yarn";
|
|
866
|
+
}
|
|
867
|
+
};
|
|
@@ -8,7 +8,7 @@ import { BuildTask, type BuildTaskContext } from "./BuildTask.ts";
|
|
|
8
8
|
*
|
|
9
9
|
* Creates:
|
|
10
10
|
* - Dockerfile with configurable base image
|
|
11
|
-
* - Copies
|
|
11
|
+
* - Copies migrations directory if it exists
|
|
12
12
|
* - Builds Docker image when `--image` flag is provided
|
|
13
13
|
*/
|
|
14
14
|
export class BuildDockerTask extends BuildTask {
|
|
@@ -32,7 +32,7 @@ export class BuildDockerTask extends BuildTask {
|
|
|
32
32
|
await ctx.run({
|
|
33
33
|
name: "generate deploy config (docker)",
|
|
34
34
|
handler: async () => {
|
|
35
|
-
await this.
|
|
35
|
+
await this.copyMigrations(ctx.root, distDir);
|
|
36
36
|
await this.writeDockerfile(
|
|
37
37
|
ctx.root,
|
|
38
38
|
distDir,
|
|
@@ -47,14 +47,13 @@ export class BuildDockerTask extends BuildTask {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
protected async
|
|
51
|
-
root
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
await this.fs.cp(drizzleDir, this.fs.join(root, distDir, "drizzle"));
|
|
50
|
+
protected async copyMigrations(root: string, distDir: string): Promise<void> {
|
|
51
|
+
const migrationsDir = this.fs.join(root, "migrations");
|
|
52
|
+
if (await this.fs.exists(migrationsDir)) {
|
|
53
|
+
await this.fs.cp(
|
|
54
|
+
migrationsDir,
|
|
55
|
+
this.fs.join(root, distDir, "migrations"),
|
|
56
|
+
);
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
|
|
@@ -135,6 +135,14 @@ export class BuildServerTask extends BuildTask {
|
|
|
135
135
|
chunkFileNames: "[hash].js",
|
|
136
136
|
assetFileNames: "[hash][extname]",
|
|
137
137
|
format: "esm",
|
|
138
|
+
codeSplitting: {
|
|
139
|
+
groups: [
|
|
140
|
+
{
|
|
141
|
+
name: "react",
|
|
142
|
+
test: /node_modules\/react(\/|-dom\/)/,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
138
146
|
// Rolldown/Oxc minifier: preserve class and function names
|
|
139
147
|
minify: {
|
|
140
148
|
mangle: { keepNames: true },
|
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
export interface ApiIndexTsOptions {
|
|
2
2
|
appName?: string;
|
|
3
|
+
/**
|
|
4
|
+
* Include `AlephaApiUsers` (realms, sessions, registration, identities,
|
|
5
|
+
* password reset, email verification, admin endpoints) plus the local
|
|
6
|
+
* `RealmProvider` that declares `$realm({ ... })`.
|
|
7
|
+
*/
|
|
8
|
+
saas?: boolean;
|
|
3
9
|
}
|
|
4
10
|
|
|
5
11
|
export const apiIndexTs = (options: ApiIndexTsOptions = {}) => {
|
|
6
|
-
const { appName = "app" } = options;
|
|
12
|
+
const { appName = "app", saas = false } = options;
|
|
13
|
+
|
|
14
|
+
if (saas) {
|
|
15
|
+
return `
|
|
16
|
+
import { $module } from "alepha";
|
|
17
|
+
import { AlephaApiUsers } from "alepha/api/users";
|
|
18
|
+
import { HelloController } from "./controllers/HelloController.ts";
|
|
19
|
+
import { RealmProvider } from "./providers/RealmProvider.ts";
|
|
20
|
+
|
|
21
|
+
export const ApiModule = $module({
|
|
22
|
+
name: "${appName}.api",
|
|
23
|
+
services: [HelloController, RealmProvider],
|
|
24
|
+
imports: [AlephaApiUsers],
|
|
25
|
+
});
|
|
26
|
+
`.trim();
|
|
27
|
+
}
|
|
28
|
+
|
|
7
29
|
return `
|
|
8
30
|
import { $module } from "alepha";
|
|
9
31
|
import { HelloController } from "./controllers/HelloController.ts";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `components.json` is the shadcn CLI's project config — it tells
|
|
3
|
+
* `shadcn add` where to drop primitives, which tailwind tokens to use,
|
|
4
|
+
* which icon library to wire up, and which custom registries to resolve.
|
|
5
|
+
*
|
|
6
|
+
* Aliases follow shadcn's defaults (`@/components`, `@/lib/utils`) so the
|
|
7
|
+
* CLI honors them across `init` + `add` calls. Alepha app code lives at
|
|
8
|
+
* `src/web/` (Home, AppRouter, …) and the shadcn primitives live at
|
|
9
|
+
* `src/components/` — kept separate to make the registry components
|
|
10
|
+
* trivially upgradable via `shadcn add --overwrite`.
|
|
11
|
+
*
|
|
12
|
+
* The `registries` block pre-wires the public Alepha registry — consumers
|
|
13
|
+
* can immediately run e.g. `shadcn add @alepha/auth-login`.
|
|
14
|
+
*/
|
|
15
|
+
export const componentsJsonTs = () =>
|
|
16
|
+
`{
|
|
17
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
18
|
+
"style": "new-york",
|
|
19
|
+
"rsc": false,
|
|
20
|
+
"tsx": true,
|
|
21
|
+
"tailwind": {
|
|
22
|
+
"config": "",
|
|
23
|
+
"css": "src/main.css",
|
|
24
|
+
"baseColor": "neutral",
|
|
25
|
+
"cssVariables": true
|
|
26
|
+
},
|
|
27
|
+
"aliases": {
|
|
28
|
+
"components": "@/components",
|
|
29
|
+
"utils": "@/lib/utils",
|
|
30
|
+
"ui": "@/components/ui",
|
|
31
|
+
"lib": "@/lib",
|
|
32
|
+
"hooks": "@/hooks"
|
|
33
|
+
},
|
|
34
|
+
"iconLibrary": "lucide",
|
|
35
|
+
"registries": {
|
|
36
|
+
"@alepha": "https://alepha.dev/r/{name}.json"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
@@ -11,6 +11,7 @@ export const mainCss = (opts: { tailwind?: boolean } = {}) => {
|
|
|
11
11
|
*
|
|
12
12
|
* Options:
|
|
13
13
|
* - Tailwind CSS: Use \`alepha init --tailwind\` to add Tailwind CSS
|
|
14
|
+
* - shadcn/ui: Use \`alepha init --shadcn\` to add shadcn/ui setup
|
|
14
15
|
* - Raw CSS: Write your own styles below
|
|
15
16
|
*/
|
|
16
17
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SaaS admin layout — full AppShell on /admin with a sidebar, breadcrumbs,
|
|
3
|
+
* a Sonner toaster, and a confirm provider. The page list grows with
|
|
4
|
+
* whatever `admin-*` registry components the user adds.
|
|
5
|
+
*
|
|
6
|
+
* All UI primitives come from `src/components/*` where `shadcn add` drops
|
|
7
|
+
* them; alepha app code lives in `src/web/*` and references them via the
|
|
8
|
+
* `@/components/*` alias.
|
|
9
|
+
*/
|
|
10
|
+
export const saasAdminLayoutTsx = () =>
|
|
11
|
+
`import { AppShell } from "@/components/app-shell";
|
|
12
|
+
import { Toaster } from "@/components/ui/sonner";
|
|
13
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
14
|
+
import { ConfirmProvider } from "@/components/use-confirm";
|
|
15
|
+
import { NestedView, useRouterState } from "alepha/react/router";
|
|
16
|
+
import { ShieldCheck, Users } from "lucide-react";
|
|
17
|
+
|
|
18
|
+
const NAV = [
|
|
19
|
+
{
|
|
20
|
+
label: "Identity",
|
|
21
|
+
items: [
|
|
22
|
+
{ href: "/admin/users", label: "Users", icon: Users },
|
|
23
|
+
{ href: "/admin/sessions", label: "Sessions", icon: ShieldCheck },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
const findCrumbs = (pathname: string): { label: string; href?: string }[] => {
|
|
29
|
+
for (const group of NAV) {
|
|
30
|
+
const match = group.items.find((it) => it.href === pathname);
|
|
31
|
+
if (match) return [{ label: group.label }, { label: match.label }];
|
|
32
|
+
}
|
|
33
|
+
return [];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const AdminLayout = () => {
|
|
37
|
+
const state = useRouterState();
|
|
38
|
+
const crumbs = findCrumbs(state.url.pathname);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<TooltipProvider>
|
|
42
|
+
<ConfirmProvider>
|
|
43
|
+
<AppShell
|
|
44
|
+
brand={
|
|
45
|
+
<a
|
|
46
|
+
href="/admin"
|
|
47
|
+
className="flex items-center gap-2 px-2 py-2 font-semibold group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0"
|
|
48
|
+
>
|
|
49
|
+
<span className="bg-primary text-primary-foreground flex size-7 shrink-0 items-center justify-center rounded">
|
|
50
|
+
α
|
|
51
|
+
</span>
|
|
52
|
+
<span className="truncate group-data-[collapsible=icon]:hidden">
|
|
53
|
+
Admin
|
|
54
|
+
</span>
|
|
55
|
+
</a>
|
|
56
|
+
}
|
|
57
|
+
nav={NAV.map((group) => ({
|
|
58
|
+
label: group.label,
|
|
59
|
+
items: group.items.map((it) => ({
|
|
60
|
+
href: it.href,
|
|
61
|
+
label: it.label,
|
|
62
|
+
icon: it.icon,
|
|
63
|
+
active: it.href === state.url.pathname,
|
|
64
|
+
})),
|
|
65
|
+
}))}
|
|
66
|
+
breadcrumbs={crumbs.length ? crumbs : undefined}
|
|
67
|
+
>
|
|
68
|
+
<NestedView />
|
|
69
|
+
</AppShell>
|
|
70
|
+
<Toaster />
|
|
71
|
+
</ConfirmProvider>
|
|
72
|
+
</TooltipProvider>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default AdminLayout;
|
|
77
|
+
`;
|