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
package/dist/cli/core/index.js
CHANGED
|
@@ -37,7 +37,20 @@ const buildOptions = $atom({
|
|
|
37
37
|
name: "alepha.cli.build.options",
|
|
38
38
|
description: "Build configuration options",
|
|
39
39
|
schema: t.object({
|
|
40
|
+
/**
|
|
41
|
+
* Generate build stats report.
|
|
42
|
+
*
|
|
43
|
+
* - `true` - Generate a static HTML report
|
|
44
|
+
* - `"json"` - Generate a JSON report
|
|
45
|
+
*/
|
|
40
46
|
stats: t.optional(t.union([t.boolean(), t.enum(["json"])])),
|
|
47
|
+
/**
|
|
48
|
+
* Deployment target for the build output.
|
|
49
|
+
*
|
|
50
|
+
* - `docker` - Generate Dockerfile for containerized deployment
|
|
51
|
+
* - `vercel` - Generate Vercel deployment configuration (forces node runtime)
|
|
52
|
+
* - `cloudflare` - Generate Cloudflare Workers configuration (forces workerd runtime)
|
|
53
|
+
*/
|
|
41
54
|
target: t.optional(t.enum([
|
|
42
55
|
"bare",
|
|
43
56
|
"docker",
|
|
@@ -45,15 +58,45 @@ const buildOptions = $atom({
|
|
|
45
58
|
"cloudflare",
|
|
46
59
|
"static"
|
|
47
60
|
])),
|
|
61
|
+
/**
|
|
62
|
+
* JavaScript runtime for the build output.
|
|
63
|
+
*
|
|
64
|
+
* - `node` - Node.js runtime (default)
|
|
65
|
+
* - `bun` - Bun runtime (uses bun export conditions)
|
|
66
|
+
* - `workerd` - Cloudflare Workers runtime (auto-set with cloudflare target)
|
|
67
|
+
*
|
|
68
|
+
* Note: Some targets force a specific runtime:
|
|
69
|
+
* - `cloudflare` always uses `workerd`
|
|
70
|
+
* - `vercel` always uses `node`
|
|
71
|
+
*/
|
|
48
72
|
runtime: t.optional(t.enum([
|
|
49
73
|
"node",
|
|
50
74
|
"bun",
|
|
51
75
|
"workerd"
|
|
52
76
|
])),
|
|
77
|
+
/**
|
|
78
|
+
* Output directory configuration.
|
|
79
|
+
*/
|
|
53
80
|
output: t.optional(t.object({
|
|
81
|
+
/**
|
|
82
|
+
* Root dist directory.
|
|
83
|
+
*
|
|
84
|
+
* @default "dist"
|
|
85
|
+
*/
|
|
54
86
|
dist: t.optional(t.string({ default: "dist" })),
|
|
87
|
+
/**
|
|
88
|
+
* Public/client subdirectory.
|
|
89
|
+
*
|
|
90
|
+
* @default "public"
|
|
91
|
+
*/
|
|
55
92
|
public: t.optional(t.string({ default: "public" }))
|
|
56
93
|
})),
|
|
94
|
+
/**
|
|
95
|
+
* Vercel-specific deployment configuration.
|
|
96
|
+
*
|
|
97
|
+
* Note: Set `target: "vercel"` to enable Vercel deployment.
|
|
98
|
+
* This object is only for additional configuration.
|
|
99
|
+
*/
|
|
57
100
|
vercel: t.optional(t.object({
|
|
58
101
|
projectName: t.optional(t.string()),
|
|
59
102
|
orgId: t.optional(t.string()),
|
|
@@ -63,31 +106,143 @@ const buildOptions = $atom({
|
|
|
63
106
|
schedule: t.string()
|
|
64
107
|
}))) }))
|
|
65
108
|
})),
|
|
109
|
+
/**
|
|
110
|
+
* Cloudflare-specific deployment configuration.
|
|
111
|
+
*
|
|
112
|
+
* Note: Set `target: "cloudflare"` to enable Cloudflare deployment.
|
|
113
|
+
* This object is only for additional configuration.
|
|
114
|
+
*/
|
|
66
115
|
cloudflare: t.optional(t.object({ config: t.optional(t.json()) })),
|
|
116
|
+
/**
|
|
117
|
+
* Docker-specific deployment configuration.
|
|
118
|
+
*
|
|
119
|
+
* Note: Set `target: "docker"` to enable Docker deployment.
|
|
120
|
+
* This object is only for additional configuration.
|
|
121
|
+
*/
|
|
67
122
|
docker: t.optional(t.object({
|
|
123
|
+
/**
|
|
124
|
+
* Base image for the Dockerfile (FROM instruction).
|
|
125
|
+
*
|
|
126
|
+
* @default "node:24-alpine" for node runtime
|
|
127
|
+
* @default "oven/bun:alpine" for bun runtime
|
|
128
|
+
*/
|
|
68
129
|
from: t.optional(t.string()),
|
|
130
|
+
/**
|
|
131
|
+
* Command to run in the Docker container.
|
|
132
|
+
*
|
|
133
|
+
* @default "node" for node runtime
|
|
134
|
+
* @default "bun" for bun runtime
|
|
135
|
+
*/
|
|
69
136
|
command: t.optional(t.string()),
|
|
137
|
+
/**
|
|
138
|
+
* Docker build options (used when --image flag is passed).
|
|
139
|
+
*/
|
|
70
140
|
image: t.optional(t.object({
|
|
141
|
+
/**
|
|
142
|
+
* Default image tag (name without version).
|
|
143
|
+
*
|
|
144
|
+
* Used when --image is provided without a full override:
|
|
145
|
+
* - `--image` → `tag:latest`
|
|
146
|
+
* - `--image=1.3.4` → `tag:1.3.4`
|
|
147
|
+
* - `--image=other/img:v1` → `other/img:v1` (full override)
|
|
148
|
+
*
|
|
149
|
+
* @example "myproject/myapp"
|
|
150
|
+
* @example "ghcr.io/myorg/myapp"
|
|
151
|
+
*/
|
|
71
152
|
tag: t.string(),
|
|
153
|
+
/**
|
|
154
|
+
* Additional arguments to pass to `docker build`.
|
|
155
|
+
*
|
|
156
|
+
* @example '--platform linux/amd64 --no-cache'
|
|
157
|
+
*/
|
|
72
158
|
args: t.optional(t.string()),
|
|
159
|
+
/**
|
|
160
|
+
* Auto-add OCI standard labels (revision, created, version).
|
|
161
|
+
*
|
|
162
|
+
* Adds:
|
|
163
|
+
* - org.opencontainers.image.revision (git commit SHA)
|
|
164
|
+
* - org.opencontainers.image.created (build timestamp)
|
|
165
|
+
* - org.opencontainers.image.version (from image tag)
|
|
166
|
+
*/
|
|
73
167
|
oci: t.optional(t.boolean())
|
|
74
168
|
}))
|
|
75
169
|
})),
|
|
76
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Static site deployment configuration.
|
|
172
|
+
*
|
|
173
|
+
* Note: Set `target: "static"` to enable static site generation.
|
|
174
|
+
*/
|
|
175
|
+
static: t.optional(t.object({
|
|
176
|
+
/**
|
|
177
|
+
* Surge domain for deployment.
|
|
178
|
+
*
|
|
179
|
+
* If set, a CNAME file is written to dist/public/.
|
|
180
|
+
* If not set, a domain is auto-generated from package.json name.
|
|
181
|
+
*
|
|
182
|
+
* @example "my-app.surge.sh"
|
|
183
|
+
* @example "my-custom-domain.com"
|
|
184
|
+
*/
|
|
185
|
+
domain: t.optional(t.string()) })),
|
|
186
|
+
/**
|
|
187
|
+
* PWA (Progressive Web App) configuration.
|
|
188
|
+
*
|
|
189
|
+
* Generates a web app manifest and enables installability.
|
|
190
|
+
* Requires a client-side bundle (React).
|
|
191
|
+
*/
|
|
77
192
|
pwa: t.optional(t.object({
|
|
193
|
+
/**
|
|
194
|
+
* Full application name displayed on the splash screen
|
|
195
|
+
* and in the OS app switcher.
|
|
196
|
+
*/
|
|
78
197
|
name: t.string(),
|
|
198
|
+
/**
|
|
199
|
+
* Short name displayed on the home screen icon.
|
|
200
|
+
* Falls back to `name` if omitted.
|
|
201
|
+
*/
|
|
79
202
|
shortName: t.optional(t.string()),
|
|
203
|
+
/**
|
|
204
|
+
* Theme color used for the browser toolbar and OS chrome.
|
|
205
|
+
*
|
|
206
|
+
* @default "#ffffff"
|
|
207
|
+
*/
|
|
80
208
|
themeColor: t.optional(t.string()),
|
|
209
|
+
/**
|
|
210
|
+
* Background color for the splash screen.
|
|
211
|
+
*
|
|
212
|
+
* @default "#ffffff"
|
|
213
|
+
*/
|
|
81
214
|
backgroundColor: t.optional(t.string()),
|
|
215
|
+
/**
|
|
216
|
+
* Display mode for the installed PWA.
|
|
217
|
+
*
|
|
218
|
+
* - `standalone` - Looks like a native app (default)
|
|
219
|
+
* - `fullscreen` - Uses entire screen (games, immersive)
|
|
220
|
+
* - `minimal-ui` - Like standalone with minimal browser UI
|
|
221
|
+
* - `browser` - Standard browser tab
|
|
222
|
+
*
|
|
223
|
+
* @default "standalone"
|
|
224
|
+
*/
|
|
82
225
|
display: t.optional(t.enum([
|
|
83
226
|
"standalone",
|
|
84
227
|
"fullscreen",
|
|
85
228
|
"minimal-ui",
|
|
86
229
|
"browser"
|
|
87
230
|
])),
|
|
231
|
+
/**
|
|
232
|
+
* Enable offline support via service worker.
|
|
233
|
+
*
|
|
234
|
+
* TODO: Not yet implemented.
|
|
235
|
+
*/
|
|
88
236
|
offline: t.optional(t.boolean())
|
|
89
237
|
})),
|
|
90
|
-
|
|
238
|
+
/**
|
|
239
|
+
* Sitemap generation configuration.
|
|
240
|
+
*/
|
|
241
|
+
sitemap: t.optional(t.object({
|
|
242
|
+
/**
|
|
243
|
+
* Base URL for sitemap entries.
|
|
244
|
+
*/
|
|
245
|
+
hostname: t.string() }))
|
|
91
246
|
}),
|
|
92
247
|
default: {}
|
|
93
248
|
});
|
|
@@ -102,7 +257,11 @@ const buildOptions = $atom({
|
|
|
102
257
|
const devOptions = $atom({
|
|
103
258
|
name: "alepha.cli.dev.options",
|
|
104
259
|
description: "Dev configuration options",
|
|
105
|
-
schema: t.object({
|
|
260
|
+
schema: t.object({
|
|
261
|
+
/**
|
|
262
|
+
* Disable Vite React plugin.
|
|
263
|
+
*/
|
|
264
|
+
noViteReactPlugin: t.optional(t.boolean({ default: false })) }),
|
|
106
265
|
default: {}
|
|
107
266
|
});
|
|
108
267
|
//#endregion
|
|
@@ -598,7 +757,7 @@ var AlephaCliUtils = class {
|
|
|
598
757
|
};
|
|
599
758
|
//#endregion
|
|
600
759
|
//#region ../../package.json
|
|
601
|
-
var version$1 = "0.20.
|
|
760
|
+
var version$1 = "0.20.3";
|
|
602
761
|
//#endregion
|
|
603
762
|
//#region ../../src/cli/core/alephaPackageJson.ts
|
|
604
763
|
const alephaPackageJson = {
|
|
@@ -627,18 +786,19 @@ const alephaPackageJson = {
|
|
|
627
786
|
"drizzle-orm": "^0.45.2",
|
|
628
787
|
"postgres": "^3.4.9",
|
|
629
788
|
"tsx": "^4.21.0",
|
|
630
|
-
"typebox": "^1.1.
|
|
631
|
-
"typescript": "^6.0.
|
|
632
|
-
"vite-bundle-analyzer": "^1.3.
|
|
789
|
+
"typebox": "^1.1.34",
|
|
790
|
+
"typescript": "^6.0.3",
|
|
791
|
+
"vite-bundle-analyzer": "^1.3.8",
|
|
633
792
|
"ws": "^8.20.0"
|
|
634
793
|
},
|
|
635
794
|
devDependencies: {
|
|
636
|
-
"@biomejs/biome": "^2.4.
|
|
637
|
-
"@electric-sql/pglite": "^0.4.
|
|
795
|
+
"@biomejs/biome": "^2.4.13",
|
|
796
|
+
"@electric-sql/pglite": "^0.4.5",
|
|
638
797
|
"@faker-js/faker": "^10.4.0",
|
|
798
|
+
"@tailwindcss/vite": "^4.2.4",
|
|
639
799
|
"@testing-library/dom": "^10.4.1",
|
|
640
800
|
"@testing-library/react": "^16.3.2",
|
|
641
|
-
"@types/bun": "^1.3.
|
|
801
|
+
"@types/bun": "^1.3.13",
|
|
642
802
|
"@types/node": "^25.6.0",
|
|
643
803
|
"@types/nodemailer": "^8.0.0",
|
|
644
804
|
"@types/react": "^19.2.14",
|
|
@@ -646,17 +806,19 @@ const alephaPackageJson = {
|
|
|
646
806
|
"@types/ws": "^8.18.1",
|
|
647
807
|
"cron-schedule": "^6.0.0",
|
|
648
808
|
"drizzle-kit": "^0.31.10",
|
|
649
|
-
"jose": "^6.2.
|
|
650
|
-
"jsdom": "^29.0
|
|
651
|
-
"nodemailer": "^8.0.
|
|
652
|
-
"openid-client": "^6.8.
|
|
809
|
+
"jose": "^6.2.3",
|
|
810
|
+
"jsdom": "^29.1.0",
|
|
811
|
+
"nodemailer": "^8.0.7",
|
|
812
|
+
"openid-client": "^6.8.4",
|
|
653
813
|
"prom-client": "^15.1.3",
|
|
654
814
|
"react": "^19.2.5",
|
|
655
815
|
"react-dom": "^19.2.5",
|
|
656
|
-
"
|
|
657
|
-
"
|
|
658
|
-
"
|
|
659
|
-
"
|
|
816
|
+
"shadcn": "^4.6.0",
|
|
817
|
+
"swagger-ui-dist": "^5.32.5",
|
|
818
|
+
"tailwindcss": "^4.2.4",
|
|
819
|
+
"tsdown": "^0.21.10",
|
|
820
|
+
"vite": "^8.0.10",
|
|
821
|
+
"vitest": "^4.1.5"
|
|
660
822
|
},
|
|
661
823
|
peerDependencies: {
|
|
662
824
|
"react": "^19",
|
|
@@ -1285,14 +1447,14 @@ var PackageManagerUtils = class {
|
|
|
1285
1447
|
"yarn.lock"
|
|
1286
1448
|
]);
|
|
1287
1449
|
await this.editPackageJson(root, (pkg) => {
|
|
1288
|
-
|
|
1450
|
+
pkg.packageManager = void 0;
|
|
1289
1451
|
return pkg;
|
|
1290
1452
|
});
|
|
1291
1453
|
}
|
|
1292
1454
|
async removePnpm(root) {
|
|
1293
1455
|
await this.removeFiles(root, ["pnpm-lock.yaml", "pnpm-workspace.yaml"]);
|
|
1294
1456
|
await this.editPackageJson(root, (pkg) => {
|
|
1295
|
-
|
|
1457
|
+
pkg.packageManager = void 0;
|
|
1296
1458
|
return pkg;
|
|
1297
1459
|
});
|
|
1298
1460
|
}
|
|
@@ -1342,7 +1504,7 @@ var PackageManagerUtils = class {
|
|
|
1342
1504
|
const alephaDeps = alephaPackageJson.devDependencies;
|
|
1343
1505
|
const dependencies = { alepha: `^${version}` };
|
|
1344
1506
|
const devDependencies = { vite: alephaDeps.vite };
|
|
1345
|
-
if (!modes.react) devDependencies["drizzle-kit"] = alephaDeps["drizzle-kit"];
|
|
1507
|
+
if (!modes.react || modes.saas) devDependencies["drizzle-kit"] = alephaDeps["drizzle-kit"];
|
|
1346
1508
|
if (!modes.isPackage) {
|
|
1347
1509
|
devDependencies["@biomejs/biome"] = alephaDeps["@biomejs/biome"];
|
|
1348
1510
|
if (modes.test) devDependencies.vitest = alephaDeps.vitest;
|
|
@@ -1356,9 +1518,10 @@ var PackageManagerUtils = class {
|
|
|
1356
1518
|
};
|
|
1357
1519
|
if (modes.test) scripts.test = "vitest run";
|
|
1358
1520
|
if (modes.tailwind) {
|
|
1359
|
-
devDependencies.tailwindcss =
|
|
1360
|
-
devDependencies["@tailwindcss/vite"] = "
|
|
1521
|
+
devDependencies.tailwindcss = alephaDeps.tailwindcss;
|
|
1522
|
+
devDependencies["@tailwindcss/vite"] = alephaDeps["@tailwindcss/vite"];
|
|
1361
1523
|
}
|
|
1524
|
+
if (modes.shadcn) devDependencies.shadcn = alephaDeps.shadcn;
|
|
1362
1525
|
if (modes.react) {
|
|
1363
1526
|
dependencies.react = alephaDeps.react;
|
|
1364
1527
|
dependencies["react-dom"] = alephaDeps["react-dom"];
|
|
@@ -1472,7 +1635,19 @@ export type HelloResponse = Static<typeof helloResponseSchema>;
|
|
|
1472
1635
|
//#endregion
|
|
1473
1636
|
//#region ../../src/cli/core/templates/apiIndexTs.ts
|
|
1474
1637
|
const apiIndexTs = (options = {}) => {
|
|
1475
|
-
const { appName = "app" } = options;
|
|
1638
|
+
const { appName = "app", saas = false } = options;
|
|
1639
|
+
if (saas) return `
|
|
1640
|
+
import { $module } from "alepha";
|
|
1641
|
+
import { AlephaApiUsers } from "alepha/api/users";
|
|
1642
|
+
import { HelloController } from "./controllers/HelloController.ts";
|
|
1643
|
+
import { RealmProvider } from "./providers/RealmProvider.ts";
|
|
1644
|
+
|
|
1645
|
+
export const ApiModule = $module({
|
|
1646
|
+
name: "${appName}.api",
|
|
1647
|
+
services: [HelloController, RealmProvider],
|
|
1648
|
+
imports: [AlephaApiUsers],
|
|
1649
|
+
});
|
|
1650
|
+
`.trim();
|
|
1476
1651
|
return `
|
|
1477
1652
|
import { $module } from "alepha";
|
|
1478
1653
|
import { HelloController } from "./controllers/HelloController.ts";
|
|
@@ -1519,6 +1694,46 @@ const biomeJson = () => `
|
|
|
1519
1694
|
}
|
|
1520
1695
|
`.trim();
|
|
1521
1696
|
//#endregion
|
|
1697
|
+
//#region ../../src/cli/core/templates/componentsJsonTs.ts
|
|
1698
|
+
/**
|
|
1699
|
+
* `components.json` is the shadcn CLI's project config — it tells
|
|
1700
|
+
* `shadcn add` where to drop primitives, which tailwind tokens to use,
|
|
1701
|
+
* which icon library to wire up, and which custom registries to resolve.
|
|
1702
|
+
*
|
|
1703
|
+
* Aliases follow shadcn's defaults (`@/components`, `@/lib/utils`) so the
|
|
1704
|
+
* CLI honors them across `init` + `add` calls. Alepha app code lives at
|
|
1705
|
+
* `src/web/` (Home, AppRouter, …) and the shadcn primitives live at
|
|
1706
|
+
* `src/components/` — kept separate to make the registry components
|
|
1707
|
+
* trivially upgradable via `shadcn add --overwrite`.
|
|
1708
|
+
*
|
|
1709
|
+
* The `registries` block pre-wires the public Alepha registry — consumers
|
|
1710
|
+
* can immediately run e.g. `shadcn add @alepha/auth-login`.
|
|
1711
|
+
*/
|
|
1712
|
+
const componentsJsonTs = () => `{
|
|
1713
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
1714
|
+
"style": "new-york",
|
|
1715
|
+
"rsc": false,
|
|
1716
|
+
"tsx": true,
|
|
1717
|
+
"tailwind": {
|
|
1718
|
+
"config": "",
|
|
1719
|
+
"css": "src/main.css",
|
|
1720
|
+
"baseColor": "neutral",
|
|
1721
|
+
"cssVariables": true
|
|
1722
|
+
},
|
|
1723
|
+
"aliases": {
|
|
1724
|
+
"components": "@/components",
|
|
1725
|
+
"utils": "@/lib/utils",
|
|
1726
|
+
"ui": "@/components/ui",
|
|
1727
|
+
"lib": "@/lib",
|
|
1728
|
+
"hooks": "@/hooks"
|
|
1729
|
+
},
|
|
1730
|
+
"iconLibrary": "lucide",
|
|
1731
|
+
"registries": {
|
|
1732
|
+
"@alepha": "https://alepha.dev/r/{name}.json"
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
`;
|
|
1736
|
+
//#endregion
|
|
1522
1737
|
//#region ../../src/cli/core/templates/dummySpecTs.ts
|
|
1523
1738
|
const dummySpecTs = () => `
|
|
1524
1739
|
import { test, expect } from "vitest";
|
|
@@ -1660,6 +1875,7 @@ const mainCss = (opts = {}) => {
|
|
|
1660
1875
|
*
|
|
1661
1876
|
* Options:
|
|
1662
1877
|
* - Tailwind CSS: Use \`alepha init --tailwind\` to add Tailwind CSS
|
|
1878
|
+
* - shadcn/ui: Use \`alepha init --shadcn\` to add shadcn/ui setup
|
|
1663
1879
|
* - Raw CSS: Write your own styles below
|
|
1664
1880
|
*/
|
|
1665
1881
|
|
|
@@ -1690,6 +1906,225 @@ run(alepha);
|
|
|
1690
1906
|
`.trim();
|
|
1691
1907
|
};
|
|
1692
1908
|
//#endregion
|
|
1909
|
+
//#region ../../src/cli/core/templates/saasAdminLayoutTsx.ts
|
|
1910
|
+
/**
|
|
1911
|
+
* SaaS admin layout — full AppShell on /admin with a sidebar, breadcrumbs,
|
|
1912
|
+
* a Sonner toaster, and a confirm provider. The page list grows with
|
|
1913
|
+
* whatever `admin-*` registry components the user adds.
|
|
1914
|
+
*
|
|
1915
|
+
* All UI primitives come from `src/components/*` where `shadcn add` drops
|
|
1916
|
+
* them; alepha app code lives in `src/web/*` and references them via the
|
|
1917
|
+
* `@/components/*` alias.
|
|
1918
|
+
*/
|
|
1919
|
+
const saasAdminLayoutTsx = () => `import { AppShell } from "@/components/app-shell";
|
|
1920
|
+
import { Toaster } from "@/components/ui/sonner";
|
|
1921
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
1922
|
+
import { ConfirmProvider } from "@/components/use-confirm";
|
|
1923
|
+
import { NestedView, useRouterState } from "alepha/react/router";
|
|
1924
|
+
import { ShieldCheck, Users } from "lucide-react";
|
|
1925
|
+
|
|
1926
|
+
const NAV = [
|
|
1927
|
+
{
|
|
1928
|
+
label: "Identity",
|
|
1929
|
+
items: [
|
|
1930
|
+
{ href: "/admin/users", label: "Users", icon: Users },
|
|
1931
|
+
{ href: "/admin/sessions", label: "Sessions", icon: ShieldCheck },
|
|
1932
|
+
],
|
|
1933
|
+
},
|
|
1934
|
+
] as const;
|
|
1935
|
+
|
|
1936
|
+
const findCrumbs = (pathname: string): { label: string; href?: string }[] => {
|
|
1937
|
+
for (const group of NAV) {
|
|
1938
|
+
const match = group.items.find((it) => it.href === pathname);
|
|
1939
|
+
if (match) return [{ label: group.label }, { label: match.label }];
|
|
1940
|
+
}
|
|
1941
|
+
return [];
|
|
1942
|
+
};
|
|
1943
|
+
|
|
1944
|
+
const AdminLayout = () => {
|
|
1945
|
+
const state = useRouterState();
|
|
1946
|
+
const crumbs = findCrumbs(state.url.pathname);
|
|
1947
|
+
|
|
1948
|
+
return (
|
|
1949
|
+
<TooltipProvider>
|
|
1950
|
+
<ConfirmProvider>
|
|
1951
|
+
<AppShell
|
|
1952
|
+
brand={
|
|
1953
|
+
<a
|
|
1954
|
+
href="/admin"
|
|
1955
|
+
className="flex items-center gap-2 px-2 py-2 font-semibold group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0"
|
|
1956
|
+
>
|
|
1957
|
+
<span className="bg-primary text-primary-foreground flex size-7 shrink-0 items-center justify-center rounded">
|
|
1958
|
+
α
|
|
1959
|
+
</span>
|
|
1960
|
+
<span className="truncate group-data-[collapsible=icon]:hidden">
|
|
1961
|
+
Admin
|
|
1962
|
+
</span>
|
|
1963
|
+
</a>
|
|
1964
|
+
}
|
|
1965
|
+
nav={NAV.map((group) => ({
|
|
1966
|
+
label: group.label,
|
|
1967
|
+
items: group.items.map((it) => ({
|
|
1968
|
+
href: it.href,
|
|
1969
|
+
label: it.label,
|
|
1970
|
+
icon: it.icon,
|
|
1971
|
+
active: it.href === state.url.pathname,
|
|
1972
|
+
})),
|
|
1973
|
+
}))}
|
|
1974
|
+
breadcrumbs={crumbs.length ? crumbs : undefined}
|
|
1975
|
+
>
|
|
1976
|
+
<NestedView />
|
|
1977
|
+
</AppShell>
|
|
1978
|
+
<Toaster />
|
|
1979
|
+
</ConfirmProvider>
|
|
1980
|
+
</TooltipProvider>
|
|
1981
|
+
);
|
|
1982
|
+
};
|
|
1983
|
+
|
|
1984
|
+
export default AdminLayout;
|
|
1985
|
+
`;
|
|
1986
|
+
//#endregion
|
|
1987
|
+
//#region ../../src/cli/core/templates/saasAdminPagesTsx.ts
|
|
1988
|
+
/**
|
|
1989
|
+
* Admin pages — each is a thin wrapper around the matching `admin-*`
|
|
1990
|
+
* registry component (placed at `src/components/admin/*`). The starter
|
|
1991
|
+
* ships with Users + Sessions; add more by `shadcn add @alepha/admin-…`
|
|
1992
|
+
* and a matching `$page(...)` in AppRouter.
|
|
1993
|
+
*/
|
|
1994
|
+
const saasAdminUsersTsx = () => `import { AdminUsers } from "@/components/admin/admin-users";
|
|
1995
|
+
|
|
1996
|
+
const AdminUsersPage = () => {
|
|
1997
|
+
return <AdminUsers />;
|
|
1998
|
+
};
|
|
1999
|
+
|
|
2000
|
+
export default AdminUsersPage;
|
|
2001
|
+
`;
|
|
2002
|
+
const saasAdminSessionsTsx = () => `import { AdminSessions } from "@/components/admin/admin-sessions";
|
|
2003
|
+
|
|
2004
|
+
const AdminSessionsPage = () => {
|
|
2005
|
+
return <AdminSessions />;
|
|
2006
|
+
};
|
|
2007
|
+
|
|
2008
|
+
export default AdminSessionsPage;
|
|
2009
|
+
`;
|
|
2010
|
+
//#endregion
|
|
2011
|
+
//#region ../../src/cli/core/templates/saasAuthLayoutTsx.ts
|
|
2012
|
+
/**
|
|
2013
|
+
* SaaS auth layout — wraps every /auth/* page with a centered card.
|
|
2014
|
+
* Routes (login, register, reset-password, verify-email) are mounted as
|
|
2015
|
+
* children so they share this shell.
|
|
2016
|
+
*/
|
|
2017
|
+
const saasAuthLayoutTsx = () => `import { NestedView } from "alepha/react/router";
|
|
2018
|
+
|
|
2019
|
+
const AuthLayout = () => {
|
|
2020
|
+
return (
|
|
2021
|
+
<div className="bg-background flex min-h-screen items-center justify-center p-4">
|
|
2022
|
+
<div className="w-full max-w-md">
|
|
2023
|
+
<NestedView />
|
|
2024
|
+
</div>
|
|
2025
|
+
</div>
|
|
2026
|
+
);
|
|
2027
|
+
};
|
|
2028
|
+
|
|
2029
|
+
export default AuthLayout;
|
|
2030
|
+
`;
|
|
2031
|
+
//#endregion
|
|
2032
|
+
//#region ../../src/cli/core/templates/saasAuthPagesTsx.ts
|
|
2033
|
+
/**
|
|
2034
|
+
* Per-page wrapper around the registry components. The registry component
|
|
2035
|
+
* receives the realm config from the page loader; the page itself stays a
|
|
2036
|
+
* thin shell so apps can layer their branding around it.
|
|
2037
|
+
*
|
|
2038
|
+
* Registry components land at `src/components/auth/*` (shadcn defaults).
|
|
2039
|
+
*/
|
|
2040
|
+
const saasAuthLoginTsx = () => `import { AuthLogin } from "@/components/auth/auth-login";
|
|
2041
|
+
import type { RealmConfig } from "alepha/api/users";
|
|
2042
|
+
|
|
2043
|
+
export interface AuthLoginPageProps {
|
|
2044
|
+
realmConfig: RealmConfig;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
const AuthLoginPage = (props: AuthLoginPageProps) => {
|
|
2048
|
+
return <AuthLogin realmConfig={props.realmConfig} />;
|
|
2049
|
+
};
|
|
2050
|
+
|
|
2051
|
+
export default AuthLoginPage;
|
|
2052
|
+
`;
|
|
2053
|
+
const saasAuthRegisterTsx = () => `import { AuthRegister } from "@/components/auth/auth-register";
|
|
2054
|
+
import type { RealmConfig } from "alepha/api/users";
|
|
2055
|
+
|
|
2056
|
+
export interface AuthRegisterPageProps {
|
|
2057
|
+
realmConfig: RealmConfig;
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
const AuthRegisterPage = (props: AuthRegisterPageProps) => {
|
|
2061
|
+
return <AuthRegister realmConfig={props.realmConfig} />;
|
|
2062
|
+
};
|
|
2063
|
+
|
|
2064
|
+
export default AuthRegisterPage;
|
|
2065
|
+
`;
|
|
2066
|
+
const saasAuthResetPasswordTsx = () => `import { AuthResetPassword } from "@/components/auth/auth-reset-password";
|
|
2067
|
+
import type { RealmConfig } from "alepha/api/users";
|
|
2068
|
+
|
|
2069
|
+
export interface AuthResetPasswordPageProps {
|
|
2070
|
+
realmConfig: RealmConfig;
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
const AuthResetPasswordPage = (props: AuthResetPasswordPageProps) => {
|
|
2074
|
+
return <AuthResetPassword realmConfig={props.realmConfig} />;
|
|
2075
|
+
};
|
|
2076
|
+
|
|
2077
|
+
export default AuthResetPasswordPage;
|
|
2078
|
+
`;
|
|
2079
|
+
const saasAuthVerifyEmailTsx = () => `import { AuthVerifyEmail } from "@/components/auth/auth-verify-email";
|
|
2080
|
+
|
|
2081
|
+
const AuthVerifyEmailPage = () => {
|
|
2082
|
+
return <AuthVerifyEmail />;
|
|
2083
|
+
};
|
|
2084
|
+
|
|
2085
|
+
export default AuthVerifyEmailPage;
|
|
2086
|
+
`;
|
|
2087
|
+
//#endregion
|
|
2088
|
+
//#region ../../src/cli/core/templates/saasRealmProviderTs.ts
|
|
2089
|
+
/**
|
|
2090
|
+
* Realm provider scaffolded by `alepha init --saas`.
|
|
2091
|
+
*
|
|
2092
|
+
* Minimal hello-world setup: credentials login with email, one admin seeded
|
|
2093
|
+
* with the developer's git email at scaffold time, and an `admin:ui`
|
|
2094
|
+
* permission used by the AppRouter to gate `/admin/*`. The default `admin`
|
|
2095
|
+
* role grants `*` (so it inherits `admin:ui`); the default `user` role
|
|
2096
|
+
* excludes `admin:*` (so non-admins get a 403 before the shell renders).
|
|
2097
|
+
*
|
|
2098
|
+
* Add `$env`, more permissions, or stricter settings as the project grows.
|
|
2099
|
+
*/
|
|
2100
|
+
const saasRealmProviderTs = (options = {}) => {
|
|
2101
|
+
const adminEmail = options.adminEmail ?? "admin@example.com";
|
|
2102
|
+
return `import { $realm } from "alepha/api/users";
|
|
2103
|
+
import { $permission } from "alepha/security";
|
|
2104
|
+
|
|
2105
|
+
export class RealmProvider {
|
|
2106
|
+
/**
|
|
2107
|
+
* Permission required to open the admin UI. Wired into AppRouter.adminLayout
|
|
2108
|
+
* via \`$secure({ permissions: ["admin:ui"] })\`.
|
|
2109
|
+
*/
|
|
2110
|
+
adminUi = $permission({
|
|
2111
|
+
group: "admin",
|
|
2112
|
+
name: "ui",
|
|
2113
|
+
description: "Access to the admin UI shell",
|
|
2114
|
+
});
|
|
2115
|
+
|
|
2116
|
+
realm = $realm({
|
|
2117
|
+
settings: {
|
|
2118
|
+
adminEmails: [${JSON.stringify(adminEmail)}],
|
|
2119
|
+
},
|
|
2120
|
+
identities: {
|
|
2121
|
+
credentials: true,
|
|
2122
|
+
},
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
`;
|
|
2126
|
+
};
|
|
2127
|
+
//#endregion
|
|
1693
2128
|
//#region ../../src/cli/core/templates/tsconfigJson.ts
|
|
1694
2129
|
const tsconfigJson = () => `
|
|
1695
2130
|
{
|
|
@@ -1733,6 +2168,10 @@ export default defineConfig({
|
|
|
1733
2168
|
//#endregion
|
|
1734
2169
|
//#region ../../src/cli/core/templates/webAppRouterTs.ts
|
|
1735
2170
|
const webAppRouterTs = (options) => {
|
|
2171
|
+
if (options.saas) return saasAppRouterTs();
|
|
2172
|
+
return basicAppRouterTs(options);
|
|
2173
|
+
};
|
|
2174
|
+
const basicAppRouterTs = (options) => {
|
|
1736
2175
|
const imports = ["import { $page } from \"alepha/react/router\";"];
|
|
1737
2176
|
const classMembers = [];
|
|
1738
2177
|
if (options.api) {
|
|
@@ -1754,6 +2193,95 @@ export class AppRouter {
|
|
|
1754
2193
|
${classMembers.join("\n\n")}
|
|
1755
2194
|
}`;
|
|
1756
2195
|
};
|
|
2196
|
+
/**
|
|
2197
|
+
* SaaS router wires three trees onto the app:
|
|
2198
|
+
* / → Home
|
|
2199
|
+
* /auth/* → AuthLayout + login / register / reset / verify
|
|
2200
|
+
* /admin/* → AdminLayout + users / sessions / api-keys / parameters / audits
|
|
2201
|
+
*
|
|
2202
|
+
* Each auth page resolves the realm config from its loader, so the registry
|
|
2203
|
+
* components render with everything they need on first paint.
|
|
2204
|
+
*/
|
|
2205
|
+
const saasAppRouterTs = () => `import type { RealmController } from "alepha/api/users";
|
|
2206
|
+
import { $page, NotFound } from "alepha/react/router";
|
|
2207
|
+
import { $secure } from "alepha/security";
|
|
2208
|
+
import { $client } from "alepha/server/links";
|
|
2209
|
+
import type { HelloController } from "../api/controllers/HelloController.ts";
|
|
2210
|
+
|
|
2211
|
+
export class AppRouter {
|
|
2212
|
+
protected readonly api = $client<HelloController>();
|
|
2213
|
+
protected readonly realmApi = $client<RealmController>();
|
|
2214
|
+
|
|
2215
|
+
home = $page({
|
|
2216
|
+
path: "/",
|
|
2217
|
+
lazy: () => import("./components/Home.tsx"),
|
|
2218
|
+
loader: () => this.api.hello(),
|
|
2219
|
+
});
|
|
2220
|
+
|
|
2221
|
+
// ── /auth — login, register, reset, verify ─────────────────────────────
|
|
2222
|
+
authLayout = $page({
|
|
2223
|
+
path: "/auth",
|
|
2224
|
+
lazy: () => import("./components/auth/AuthLayout.tsx"),
|
|
2225
|
+
});
|
|
2226
|
+
|
|
2227
|
+
login = $page({
|
|
2228
|
+
parent: this.authLayout,
|
|
2229
|
+
path: "/login",
|
|
2230
|
+
name: "login",
|
|
2231
|
+
head: { title: "Sign in" },
|
|
2232
|
+
lazy: () => import("./components/auth/Login.tsx"),
|
|
2233
|
+
loader: async () => ({ realmConfig: await this.realmApi.getRealmConfig() }),
|
|
2234
|
+
});
|
|
2235
|
+
|
|
2236
|
+
register = $page({
|
|
2237
|
+
parent: this.authLayout,
|
|
2238
|
+
path: "/register",
|
|
2239
|
+
name: "register",
|
|
2240
|
+
head: { title: "Sign up" },
|
|
2241
|
+
lazy: () => import("./components/auth/Register.tsx"),
|
|
2242
|
+
loader: async () => ({ realmConfig: await this.realmApi.getRealmConfig() }),
|
|
2243
|
+
});
|
|
2244
|
+
|
|
2245
|
+
resetPassword = $page({
|
|
2246
|
+
parent: this.authLayout,
|
|
2247
|
+
path: "/reset-password",
|
|
2248
|
+
head: { title: "Reset password" },
|
|
2249
|
+
lazy: () => import("./components/auth/ResetPassword.tsx"),
|
|
2250
|
+
loader: async () => ({ realmConfig: await this.realmApi.getRealmConfig() }),
|
|
2251
|
+
});
|
|
2252
|
+
|
|
2253
|
+
verifyEmail = $page({
|
|
2254
|
+
parent: this.authLayout,
|
|
2255
|
+
path: "/verify-email",
|
|
2256
|
+
head: { title: "Verify email" },
|
|
2257
|
+
lazy: () => import("./components/auth/VerifyEmail.tsx"),
|
|
2258
|
+
});
|
|
2259
|
+
|
|
2260
|
+
// ── /admin — gated by 'admin:ui' permission, declared in RealmProvider.
|
|
2261
|
+
// Children inherit the gate via the parent chain.
|
|
2262
|
+
adminLayout = $page({
|
|
2263
|
+
path: "/admin",
|
|
2264
|
+
use: [$secure({ permissions: ["admin:ui"] })],
|
|
2265
|
+
lazy: () => import("./components/admin/AdminLayout.tsx"),
|
|
2266
|
+
});
|
|
2267
|
+
|
|
2268
|
+
adminUsers = $page({
|
|
2269
|
+
parent: this.adminLayout,
|
|
2270
|
+
path: "/users",
|
|
2271
|
+
head: { title: "Users" },
|
|
2272
|
+
lazy: () => import("./components/admin/Users.tsx"),
|
|
2273
|
+
});
|
|
2274
|
+
|
|
2275
|
+
adminSessions = $page({
|
|
2276
|
+
parent: this.adminLayout,
|
|
2277
|
+
path: "/sessions",
|
|
2278
|
+
head: { title: "Sessions" },
|
|
2279
|
+
lazy: () => import("./components/admin/Sessions.tsx"),
|
|
2280
|
+
});
|
|
2281
|
+
|
|
2282
|
+
notFound = $page({ path: "/*", component: NotFound });
|
|
2283
|
+
}
|
|
2284
|
+
`;
|
|
1757
2285
|
//#endregion
|
|
1758
2286
|
//#region ../../src/cli/core/templates/webHomeComponentTsx.ts
|
|
1759
2287
|
const webHomeComponentTsx = (options = {}) => {
|
|
@@ -1782,7 +2310,19 @@ export default Home;
|
|
|
1782
2310
|
//#endregion
|
|
1783
2311
|
//#region ../../src/cli/core/templates/webIndexTs.ts
|
|
1784
2312
|
const webIndexTs = (options = {}) => {
|
|
1785
|
-
const { appName = "app" } = options;
|
|
2313
|
+
const { appName = "app", saas = false } = options;
|
|
2314
|
+
if (saas) return `
|
|
2315
|
+
import { $module } from "alepha";
|
|
2316
|
+
import { AlephaReactAuth } from "alepha/react/auth";
|
|
2317
|
+
import { AlephaReactI18n } from "alepha/react/i18n";
|
|
2318
|
+
import { AppRouter } from "./AppRouter.ts";
|
|
2319
|
+
|
|
2320
|
+
export const WebModule = $module({
|
|
2321
|
+
name: "${appName}.web",
|
|
2322
|
+
services: [AppRouter],
|
|
2323
|
+
imports: [AlephaReactAuth, AlephaReactI18n],
|
|
2324
|
+
});
|
|
2325
|
+
`.trim();
|
|
1786
2326
|
return `
|
|
1787
2327
|
import { $module } from "alepha";
|
|
1788
2328
|
import { AppRouter } from "./AppRouter.ts";
|
|
@@ -1808,6 +2348,7 @@ var ProjectScaffolder = class {
|
|
|
1808
2348
|
log = $logger();
|
|
1809
2349
|
colors = $inject(ConsoleColorProvider);
|
|
1810
2350
|
fs = $inject(FileSystemProvider);
|
|
2351
|
+
shell = $inject(ShellProvider);
|
|
1811
2352
|
pm = $inject(PackageManagerUtils);
|
|
1812
2353
|
utils = $inject(AlephaCliUtils);
|
|
1813
2354
|
/**
|
|
@@ -1829,7 +2370,10 @@ var ProjectScaffolder = class {
|
|
|
1829
2370
|
const force = opts.force ?? false;
|
|
1830
2371
|
const checkWorkspace = opts.checkWorkspace ?? false;
|
|
1831
2372
|
if (opts.packageJson) tasks.push(this.pm.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson).then(() => {}));
|
|
1832
|
-
if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root, {
|
|
2373
|
+
if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root, {
|
|
2374
|
+
force,
|
|
2375
|
+
localOnly: opts.tsconfigJson === "local"
|
|
2376
|
+
}));
|
|
1833
2377
|
if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root, {
|
|
1834
2378
|
force,
|
|
1835
2379
|
checkWorkspace
|
|
@@ -1845,7 +2389,8 @@ var ProjectScaffolder = class {
|
|
|
1845
2389
|
await Promise.all(tasks);
|
|
1846
2390
|
}
|
|
1847
2391
|
async ensureTsConfig(root, opts = {}) {
|
|
1848
|
-
|
|
2392
|
+
const exists = opts.localOnly ? await this.fs.exists(this.fs.join(root, "tsconfig.json")) : await this.existsInParents(root, "tsconfig.json");
|
|
2393
|
+
if (!opts.force && exists) return;
|
|
1849
2394
|
await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson());
|
|
1850
2395
|
}
|
|
1851
2396
|
async ensureBiomeConfig(root, opts = {}) {
|
|
@@ -1904,9 +2449,32 @@ var ProjectScaffolder = class {
|
|
|
1904
2449
|
const appName = this.getAppName(root);
|
|
1905
2450
|
await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
|
|
1906
2451
|
await this.fs.mkdir(this.fs.join(root, "src/api/schemas"), { recursive: true });
|
|
1907
|
-
await this.ensureFile(root, "src/api/index.ts", apiIndexTs({
|
|
2452
|
+
await this.ensureFile(root, "src/api/index.ts", apiIndexTs({
|
|
2453
|
+
appName,
|
|
2454
|
+
saas: opts.saas
|
|
2455
|
+
}), opts.force);
|
|
1908
2456
|
await this.ensureFile(root, "src/api/controllers/HelloController.ts", apiHelloControllerTs({ appName }), opts.force);
|
|
1909
2457
|
await this.ensureFile(root, "src/api/schemas/helloResponseSchema.ts", apiHelloResponseSchemaTs(), opts.force);
|
|
2458
|
+
if (opts.saas) {
|
|
2459
|
+
await this.fs.mkdir(this.fs.join(root, "src/api/providers"), { recursive: true });
|
|
2460
|
+
const adminEmail = await this.detectGitEmail();
|
|
2461
|
+
await this.ensureFile(root, "src/api/providers/RealmProvider.ts", saasRealmProviderTs({ adminEmail }), opts.force);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
/**
|
|
2465
|
+
* Best-effort lookup for the developer's git email (used as the seeded
|
|
2466
|
+
* `adminEmails` entry in the SaaS realm). Returns undefined if git isn't
|
|
2467
|
+
* available or if `user.email` isn't configured — the template falls back
|
|
2468
|
+
* to `admin@example.com` in that case.
|
|
2469
|
+
*/
|
|
2470
|
+
async detectGitEmail() {
|
|
2471
|
+
try {
|
|
2472
|
+
const email = (await this.shell.run("git config --get user.email", { capture: true }) ?? "").trim();
|
|
2473
|
+
if (!email || !email.includes("@")) return void 0;
|
|
2474
|
+
return email;
|
|
2475
|
+
} catch {
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
1910
2478
|
}
|
|
1911
2479
|
/**
|
|
1912
2480
|
* Ensure web/React project structure exists.
|
|
@@ -1919,14 +2487,35 @@ var ProjectScaffolder = class {
|
|
|
1919
2487
|
async ensureWebProject(root, opts = {}) {
|
|
1920
2488
|
const appName = this.getAppName(root);
|
|
1921
2489
|
await this.fs.mkdir(this.fs.join(root, "src/web/components"), { recursive: true });
|
|
2490
|
+
if (opts.saas) {
|
|
2491
|
+
await this.fs.mkdir(this.fs.join(root, "src/web/components/auth"), { recursive: true });
|
|
2492
|
+
await this.fs.mkdir(this.fs.join(root, "src/web/components/admin"), { recursive: true });
|
|
2493
|
+
}
|
|
1922
2494
|
await this.fs.mkdir(this.fs.join(root, "public"), { recursive: true });
|
|
1923
2495
|
await this.ensureFile(root, "public/favicon.svg", logoSvg, opts.force);
|
|
1924
2496
|
await this.ensureFile(root, "src/main.css", mainCss({ tailwind: opts.tailwind }), opts.force);
|
|
1925
2497
|
if (opts.tailwind) await this.ensureFile(root, "vite.config.ts", viteConfigTs(), opts.force);
|
|
1926
|
-
await this.ensureFile(root, "
|
|
1927
|
-
await this.ensureFile(root, "src/web/
|
|
2498
|
+
if (opts.shadcn) await this.ensureFile(root, "components.json", componentsJsonTs(), opts.force);
|
|
2499
|
+
await this.ensureFile(root, "src/web/index.ts", webIndexTs({
|
|
2500
|
+
appName,
|
|
2501
|
+
saas: opts.saas
|
|
2502
|
+
}), opts.force);
|
|
2503
|
+
await this.ensureFile(root, "src/web/AppRouter.ts", webAppRouterTs({
|
|
2504
|
+
api: opts.api,
|
|
2505
|
+
saas: opts.saas
|
|
2506
|
+
}), opts.force);
|
|
1928
2507
|
await this.ensureFile(root, "src/web/components/Home.tsx", webHomeComponentTsx({ api: opts.api }), opts.force);
|
|
1929
2508
|
await this.ensureFile(root, "src/main.browser.ts", mainBrowserTs(), opts.force);
|
|
2509
|
+
if (opts.saas) {
|
|
2510
|
+
await this.ensureFile(root, "src/web/components/auth/AuthLayout.tsx", saasAuthLayoutTsx(), opts.force);
|
|
2511
|
+
await this.ensureFile(root, "src/web/components/auth/Login.tsx", saasAuthLoginTsx(), opts.force);
|
|
2512
|
+
await this.ensureFile(root, "src/web/components/auth/Register.tsx", saasAuthRegisterTsx(), opts.force);
|
|
2513
|
+
await this.ensureFile(root, "src/web/components/auth/ResetPassword.tsx", saasAuthResetPasswordTsx(), opts.force);
|
|
2514
|
+
await this.ensureFile(root, "src/web/components/auth/VerifyEmail.tsx", saasAuthVerifyEmailTsx(), opts.force);
|
|
2515
|
+
await this.ensureFile(root, "src/web/components/admin/AdminLayout.tsx", saasAdminLayoutTsx(), opts.force);
|
|
2516
|
+
await this.ensureFile(root, "src/web/components/admin/Users.tsx", saasAdminUsersTsx(), opts.force);
|
|
2517
|
+
await this.ensureFile(root, "src/web/components/admin/Sessions.tsx", saasAdminSessionsTsx(), opts.force);
|
|
2518
|
+
}
|
|
1930
2519
|
}
|
|
1931
2520
|
/**
|
|
1932
2521
|
* Ensure test directory exists with a dummy test file + a self-contained
|
|
@@ -1953,8 +2542,17 @@ var ProjectScaffolder = class {
|
|
|
1953
2542
|
root = this.fs.join(root, args);
|
|
1954
2543
|
await this.fs.mkdir(root, { force: true });
|
|
1955
2544
|
}
|
|
2545
|
+
const shadcnPreset = typeof flags.saas === "string" && flags.saas || typeof flags.shadcn === "string" && flags.shadcn || "b0";
|
|
2546
|
+
const f = flags;
|
|
2547
|
+
f.shadcn = !!flags.shadcn;
|
|
2548
|
+
f.saas = !!flags.saas;
|
|
2549
|
+
if (f.saas) {
|
|
2550
|
+
f.shadcn = true;
|
|
2551
|
+
f.api = true;
|
|
2552
|
+
}
|
|
2553
|
+
if (f.shadcn) f.tailwind = true;
|
|
1956
2554
|
if (flags.tailwind) flags.react = true;
|
|
1957
|
-
if ((flags.api || flags.react || flags.tailwind) && !flags.force) {
|
|
2555
|
+
if ((flags.api || flags.react || flags.tailwind || flags.shadcn || flags.saas) && !flags.force) {
|
|
1958
2556
|
if ((await this.fs.ls(root)).filter((f) => f !== "package.json").length > 0) throw new AlephaError(`Target directory is not empty (${root}). Use --force to overwrite existing files.`);
|
|
1959
2557
|
}
|
|
1960
2558
|
const workspace = await this.pm.getWorkspaceContext(root);
|
|
@@ -1968,10 +2566,10 @@ var ProjectScaffolder = class {
|
|
|
1968
2566
|
await this.ensureConfig(root, {
|
|
1969
2567
|
force,
|
|
1970
2568
|
packageJson: {
|
|
1971
|
-
...
|
|
2569
|
+
...f,
|
|
1972
2570
|
isPackage: workspace.isPackage
|
|
1973
2571
|
},
|
|
1974
|
-
tsconfigJson: !workspace.config.tsconfigJson,
|
|
2572
|
+
tsconfigJson: f.shadcn ? "local" : !workspace.config.tsconfigJson,
|
|
1975
2573
|
biomeJson: true,
|
|
1976
2574
|
editorconfig: !workspace.config.editorconfig,
|
|
1977
2575
|
agentMd: agentType ? { type: agentType } : false
|
|
@@ -1982,10 +2580,15 @@ var ProjectScaffolder = class {
|
|
|
1982
2580
|
react: !!flags.react && !isExpo,
|
|
1983
2581
|
force
|
|
1984
2582
|
});
|
|
1985
|
-
if (flags.api) await this.ensureApiProject(root, {
|
|
2583
|
+
if (flags.api) await this.ensureApiProject(root, {
|
|
2584
|
+
saas: !!flags.saas,
|
|
2585
|
+
force
|
|
2586
|
+
});
|
|
1986
2587
|
if (flags.react && !isExpo) await this.ensureWebProject(root, {
|
|
1987
2588
|
api: !!flags.api,
|
|
1988
2589
|
tailwind: !!flags.tailwind,
|
|
2590
|
+
shadcn: !!flags.shadcn,
|
|
2591
|
+
saas: !!flags.saas,
|
|
1989
2592
|
force
|
|
1990
2593
|
});
|
|
1991
2594
|
}
|
|
@@ -2003,10 +2606,26 @@ var ProjectScaffolder = class {
|
|
|
2003
2606
|
root: installRoot
|
|
2004
2607
|
});
|
|
2005
2608
|
if (flags.test) await this.ensureTestDir(root);
|
|
2006
|
-
|
|
2007
|
-
|
|
2609
|
+
const exec = pmExecPrefix(pmName);
|
|
2610
|
+
if (flags.shadcn) {
|
|
2611
|
+
await run(`${exec} shadcn init --no-monorepo --base radix -t vite --yes --force --reinstall --preset ${escapeShellArg(shadcnPreset)}`, {
|
|
2612
|
+
alias: `running shadcn init (preset ${shadcnPreset})`,
|
|
2613
|
+
root
|
|
2614
|
+
});
|
|
2615
|
+
await this.fs.writeFile(this.fs.join(root, "components.json"), componentsJsonTs());
|
|
2616
|
+
}
|
|
2617
|
+
if (flags.saas) await run(`${exec} shadcn add @alepha/saas --yes --overwrite`, {
|
|
2618
|
+
alias: "adding alepha saas registry bundle",
|
|
2008
2619
|
root
|
|
2009
2620
|
});
|
|
2621
|
+
try {
|
|
2622
|
+
await run(`${pmName} run lint`, {
|
|
2623
|
+
alias: "running linter",
|
|
2624
|
+
root
|
|
2625
|
+
});
|
|
2626
|
+
} catch (err) {
|
|
2627
|
+
this.log.warn("Linter reported issues during init — continuing. Run `lint` again later to inspect.", { error: err instanceof Error ? err.message : String(err) });
|
|
2628
|
+
}
|
|
2010
2629
|
if (!workspace.isPackage) {
|
|
2011
2630
|
if (await this.ensureGitRepo(root, { force })) await run("git add .", {
|
|
2012
2631
|
alias: "staging generated files",
|
|
@@ -2045,6 +2664,30 @@ var ProjectScaffolder = class {
|
|
|
2045
2664
|
}
|
|
2046
2665
|
}
|
|
2047
2666
|
};
|
|
2667
|
+
/**
|
|
2668
|
+
* Map a package manager name to the command that runs a project-local binary.
|
|
2669
|
+
*
|
|
2670
|
+
* - npm: `npx`
|
|
2671
|
+
* - yarn: `yarn` (yarn auto-resolves binary names; `yarn shadcn ...` works)
|
|
2672
|
+
* - pnpm: `pnpm exec`
|
|
2673
|
+
* - bun: `bunx`
|
|
2674
|
+
*
|
|
2675
|
+
* Used to invoke `shadcn init` / `shadcn add` regardless of the user's PM —
|
|
2676
|
+
* `npm shadcn ...` is invalid (it tries to run a script named `shadcn`).
|
|
2677
|
+
*/
|
|
2678
|
+
/** Quote a value so it survives shell parsing. */
|
|
2679
|
+
const escapeShellArg = (value) => {
|
|
2680
|
+
if (/^[A-Za-z0-9_./@:-]+$/.test(value)) return value;
|
|
2681
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
2682
|
+
};
|
|
2683
|
+
const pmExecPrefix = (pmName) => {
|
|
2684
|
+
switch (pmName) {
|
|
2685
|
+
case "npm": return "npx";
|
|
2686
|
+
case "pnpm": return "pnpm exec";
|
|
2687
|
+
case "bun": return "bunx";
|
|
2688
|
+
default: return "yarn";
|
|
2689
|
+
}
|
|
2690
|
+
};
|
|
2048
2691
|
//#endregion
|
|
2049
2692
|
//#region ../../src/cli/core/tasks/BuildTask.ts
|
|
2050
2693
|
/**
|
|
@@ -2456,7 +3099,7 @@ var BuildCompressTask = class extends BuildTask {
|
|
|
2456
3099
|
*
|
|
2457
3100
|
* Creates:
|
|
2458
3101
|
* - Dockerfile with configurable base image
|
|
2459
|
-
* - Copies
|
|
3102
|
+
* - Copies migrations directory if it exists
|
|
2460
3103
|
* - Builds Docker image when `--image` flag is provided
|
|
2461
3104
|
*/
|
|
2462
3105
|
var BuildDockerTask = class extends BuildTask {
|
|
@@ -2471,15 +3114,15 @@ var BuildDockerTask = class extends BuildTask {
|
|
|
2471
3114
|
await ctx.run({
|
|
2472
3115
|
name: "generate deploy config (docker)",
|
|
2473
3116
|
handler: async () => {
|
|
2474
|
-
await this.
|
|
3117
|
+
await this.copyMigrations(ctx.root, distDir);
|
|
2475
3118
|
await this.writeDockerfile(ctx.root, distDir, dockerFrom, dockerCommand);
|
|
2476
3119
|
}
|
|
2477
3120
|
});
|
|
2478
3121
|
if (ctx.flags?.image) await this.buildDockerImage(ctx, distDir);
|
|
2479
3122
|
}
|
|
2480
|
-
async
|
|
2481
|
-
const
|
|
2482
|
-
if (await this.fs.exists(
|
|
3123
|
+
async copyMigrations(root, distDir) {
|
|
3124
|
+
const migrationsDir = this.fs.join(root, "migrations");
|
|
3125
|
+
if (await this.fs.exists(migrationsDir)) await this.fs.cp(migrationsDir, this.fs.join(root, distDir, "migrations"));
|
|
2483
3126
|
}
|
|
2484
3127
|
async writeDockerfile(root, distDir, image, command) {
|
|
2485
3128
|
const dockerfile = `# This file was automatically generated. DO NOT MODIFY.
|
|
@@ -2740,6 +3383,10 @@ var BuildServerTask = class extends BuildTask {
|
|
|
2740
3383
|
chunkFileNames: "[hash].js",
|
|
2741
3384
|
assetFileNames: "[hash][extname]",
|
|
2742
3385
|
format: "esm",
|
|
3386
|
+
codeSplitting: { groups: [{
|
|
3387
|
+
name: "react",
|
|
3388
|
+
test: /node_modules\/react(\/|-dom\/)/
|
|
3389
|
+
}] },
|
|
2743
3390
|
minify: {
|
|
2744
3391
|
mangle: { keepNames: true },
|
|
2745
3392
|
compress: { keepNames: {
|
|
@@ -4280,7 +4927,12 @@ const DEFAULT_IGNORE = [
|
|
|
4280
4927
|
*/
|
|
4281
4928
|
const changelogOptions = $atom({
|
|
4282
4929
|
name: "alepha.cli.changelog.options",
|
|
4283
|
-
schema: t.object({
|
|
4930
|
+
schema: t.object({
|
|
4931
|
+
/**
|
|
4932
|
+
* Scopes to ignore (e.g., "project", "release", "chore").
|
|
4933
|
+
* Commits like `feat(chore): ...` will be excluded from changelog.
|
|
4934
|
+
*/
|
|
4935
|
+
ignore: t.optional(t.array(t.string())) }),
|
|
4284
4936
|
default: { ignore: DEFAULT_IGNORE }
|
|
4285
4937
|
});
|
|
4286
4938
|
//#endregion
|
|
@@ -4422,10 +5074,20 @@ var ChangelogCommand = class {
|
|
|
4422
5074
|
name: "changelog",
|
|
4423
5075
|
description: "Generate changelog from conventional commits (outputs to stdout)",
|
|
4424
5076
|
flags: t.object({
|
|
5077
|
+
/**
|
|
5078
|
+
* Show changes from this ref (tag, commit, branch).
|
|
5079
|
+
* Defaults to the latest version tag.
|
|
5080
|
+
* Example: --from=1.0.0
|
|
5081
|
+
*/
|
|
4425
5082
|
from: t.optional(t.string({
|
|
4426
5083
|
aliases: ["f"],
|
|
4427
5084
|
description: "Starting ref (default: latest tag)"
|
|
4428
5085
|
})),
|
|
5086
|
+
/**
|
|
5087
|
+
* Show changes up to this ref (tag, commit, branch).
|
|
5088
|
+
* Defaults to HEAD.
|
|
5089
|
+
* Example: --to=main
|
|
5090
|
+
*/
|
|
4429
5091
|
to: t.optional(t.string({
|
|
4430
5092
|
aliases: ["t"],
|
|
4431
5093
|
description: "Ending ref (default: HEAD)"
|
|
@@ -4588,6 +5250,8 @@ var InitCommand = class {
|
|
|
4588
5250
|
description: "Include React dependencies and web module (src/web/)"
|
|
4589
5251
|
})),
|
|
4590
5252
|
tailwind: t.optional(t.boolean({ description: "Include Tailwind CSS with Vite plugin. Implies --react" })),
|
|
5253
|
+
shadcn: t.optional(t.union([t.boolean(), t.text()], { description: "Set up shadcn/ui (components.json, cn helper, theme tokens, alepha registry). Pass an optional preset id (default: b0). Implies --react and --tailwind" })),
|
|
5254
|
+
saas: t.optional(t.union([t.boolean(), t.text()], { description: "Scaffold a SaaS starter: auth (login/register/reset/verify) + admin panel (/admin AppShell with users/sessions/api-keys/parameters/audits). Pass an optional preset id (default: b0). Implies --shadcn and --api" })),
|
|
4591
5255
|
test: t.optional(t.boolean({ description: "Include Vitest and create test directory" })),
|
|
4592
5256
|
force: t.optional(t.boolean({
|
|
4593
5257
|
aliases: ["f"],
|