@sedim/cli 1.0.1 → 1.0.2
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 +118 -117
- package/dist/index.js +90 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
# Sedim
|
|
2
|
-

|
|
1
|
+
# Sedim
|
|
3
2
|
|
|
4
|
-
[](https://discord.com/invite/H7yutstM)
|
|
5
4
|
[](https://github.com/Sedimie/Sedim)
|
|
6
5
|
[](https://www.npmjs.com/package/@sedim/cli)
|
|
7
6
|
[](./LICENSE)
|
|
8
7
|
|
|
9
8
|
**Install complete features. Own every line.**
|
|
10
9
|
|
|
11
|
-
Sedim is an open-source codegen CLI that stamps full-stack modules into your project, with out of the box working defaults. No runtime dependencies, no black boxes - every stamped file is readable, editable, and owned by you. Skip the 80% work done
|
|
10
|
+
Sedim is an open-source codegen CLI that stamps full-stack modules into your project, with out of the box working defaults. No runtime dependencies, no black boxes - every stamped file is readable, editable, and owned by you. Skip the 80% work done repetitively for just the 20% customizations on top.
|
|
12
11
|
|
|
13
12
|
```bash
|
|
14
13
|
npm install -g @sedim/cli
|
|
@@ -17,67 +16,68 @@ sedim init # detects your stack
|
|
|
17
16
|
sedim add auth # stamps auth into your project
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
The first POC for the stamping model is the Auth Module. Currently supports NextJS (
|
|
19
|
+
The first POC for the stamping model is the Auth Module. Currently supports NextJS (full stack), Hono and Express (with React and Vue frontends).
|
|
21
20
|
|
|
22
21
|
---
|
|
23
|
-
|
|
22
|
+
|
|
24
23
|
## See It In Action
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
|
|
25
|
+
[](https://youtu.be/HPEjs7-O_vk)
|
|
26
|
+
|
|
29
27
|
> 10-minute walkthrough - init, auth install, OAuth, TOTP, UI themes, and customization.
|
|
30
|
-
|
|
28
|
+
|
|
31
29
|
---
|
|
32
|
-
|
|
30
|
+
|
|
33
31
|
## Quick Start
|
|
34
|
-
|
|
32
|
+
|
|
35
33
|
**Requirements:** Node ≥ 18, an existing project with a supported framework and ORM.
|
|
36
|
-
|
|
34
|
+
|
|
37
35
|
### 1. Install the CLI
|
|
38
|
-
|
|
36
|
+
|
|
39
37
|
```bash
|
|
40
38
|
npm install -g @sedim/cli
|
|
41
39
|
```
|
|
42
|
-
|
|
40
|
+
|
|
43
41
|
### 2. Initialise your project
|
|
44
|
-
|
|
42
|
+
|
|
45
43
|
```bash
|
|
46
44
|
cd my-project
|
|
47
45
|
sedim init
|
|
48
46
|
```
|
|
49
|
-
|
|
47
|
+
|
|
50
48
|
Sedim detects your framework, ORM, and language and writes a `sedim.config.ts`. If anything is detected incorrectly, edit the config file directly.
|
|
51
|
-
|
|
49
|
+
|
|
52
50
|
### 3. Add auth
|
|
53
|
-
|
|
51
|
+
|
|
54
52
|
```bash
|
|
55
53
|
sedim add auth
|
|
56
54
|
```
|
|
57
|
-
|
|
55
|
+
|
|
56
|
+
> Before writing the module directly, you can also run `plan`, `diff`, or `--dry-run` commands for the module!
|
|
58
57
|
|
|
59
58
|
The CLI walks you through:
|
|
60
|
-
|
|
61
|
-
- **Environment detection
|
|
62
|
-
- **Feature selection
|
|
63
|
-
- **Frontend style
|
|
64
|
-
- **Confirm the plan
|
|
65
|
-
- **Stamp
|
|
66
|
-
- **Environment variables
|
|
59
|
+
|
|
60
|
+
- **Environment detection**: confirms your framework, ORM, and language
|
|
61
|
+
- **Feature selection**: email/password, OAuth providers, TOTP, magic links, JWT, RBAC/ABAC
|
|
62
|
+
- **Frontend style**: headless, Tailwind styled, or themed with CSS tokens
|
|
63
|
+
- **Confirm the plan**: review exactly what files will be stamped before anything is written
|
|
64
|
+
- **Stamp**: files land in `src/sedim/auth/`
|
|
65
|
+
- **Environment variables**: add them on the spot or copy the printed list to your `.env`
|
|
66
|
+
|
|
67
67
|
### 4. Migrate and run
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
```bash
|
|
70
70
|
# Drizzle
|
|
71
71
|
npx drizzle-kit push
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
# or Prisma
|
|
74
74
|
npx prisma migrate dev --name add_auth
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
npm run dev
|
|
77
77
|
```
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
---
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
## What Gets Stamped
|
|
82
82
|
|
|
83
83
|
Running `sedim add auth` generates this structure in your project. You own every file.
|
|
@@ -87,32 +87,32 @@ The exact files that get stamped depend on the features you select during `sedim
|
|
|
87
87
|
```
|
|
88
88
|
src/sedim/auth/
|
|
89
89
|
├── core/
|
|
90
|
-
│ ├── hash-password.ts
|
|
91
|
-
│ ├── generate-token.ts
|
|
92
|
-
│ ├── session.ts
|
|
93
|
-
│ ├── pkce.ts
|
|
94
|
-
│ ├── totp.ts
|
|
95
|
-
│ ├── totp-crypto.ts
|
|
96
|
-
│ ├── rate-limit.ts
|
|
97
|
-
│ ├── rbac.ts
|
|
98
|
-
│ ├── abac.ts
|
|
99
|
-
│ ├── jwt.ts
|
|
100
|
-
│ ├── oidc.ts
|
|
101
|
-
│ └── email-transport.ts
|
|
90
|
+
│ ├── hash-password.ts - Argon2id password hashing
|
|
91
|
+
│ ├── generate-token.ts - session tokens, OTP codes, backup codes, PKCE verifiers
|
|
92
|
+
│ ├── session.ts - session building and sliding-window validation
|
|
93
|
+
│ ├── pkce.ts - RFC 7636 PKCE (S256 only)
|
|
94
|
+
│ ├── totp.ts - RFC 6238 TOTP (Google Authenticator compatible)
|
|
95
|
+
│ ├── totp-crypto.ts - AES-256-GCM encryption for TOTP secrets
|
|
96
|
+
│ ├── rate-limit.ts - sliding-window rate limiter
|
|
97
|
+
│ ├── rbac.ts - role-based access control
|
|
98
|
+
│ ├── abac.ts - attribute-based access control
|
|
99
|
+
│ ├── jwt.ts - hybrid JWT (short-lived signed + DB-backed refresh)
|
|
100
|
+
│ ├── oidc.ts - OIDC discovery and id_token validation
|
|
101
|
+
│ └── email-transport.ts - multi-transport email (nodemailer/resend/postmark/ses)
|
|
102
102
|
├── adapters/
|
|
103
103
|
│ ├── framework/
|
|
104
|
-
│ │ ├── nextjs.ts
|
|
105
|
-
│ │ ├── express.ts
|
|
106
|
-
│ │ ├── hono.ts
|
|
107
|
-
│ │ ├── operations.ts
|
|
108
|
-
│ │ └── framework-config.ts
|
|
109
|
-
│ ├── drizzle.ts
|
|
110
|
-
│ ├── prisma.ts
|
|
111
|
-
│ └── types.ts
|
|
104
|
+
│ │ ├── nextjs.ts - Next.js App Router handler factory
|
|
105
|
+
│ │ ├── express.ts - Express router
|
|
106
|
+
│ │ ├── hono.ts - Hono route registration
|
|
107
|
+
│ │ ├── operations.ts - all auth operations (login, signup, OAuth, TOTP, etc.)
|
|
108
|
+
│ │ └── framework-config.ts - OAuth provider configuration
|
|
109
|
+
│ ├── drizzle.ts - Drizzle ORM adapter
|
|
110
|
+
│ ├── prisma.ts - Prisma ORM adapter
|
|
111
|
+
│ └── types.ts - DatabaseAdapter interface and types
|
|
112
112
|
├── ui/
|
|
113
|
-
│ ├── auth-client.ts
|
|
114
|
-
│ ├── use-auth.ts
|
|
115
|
-
│ ├── headless/
|
|
113
|
+
│ ├── auth-client.ts - client-side auth fetch utilities
|
|
114
|
+
│ ├── use-auth.ts - React hook for auth state
|
|
115
|
+
│ ├── headless/ - unstyled, logic-only components
|
|
116
116
|
│ │ ├── LoginForm.tsx
|
|
117
117
|
│ │ ├── SignupForm.tsx
|
|
118
118
|
│ │ ├── ForgotPasswordForm.tsx
|
|
@@ -120,76 +120,70 @@ src/sedim/auth/
|
|
|
120
120
|
│ │ ├── MagicLinkForm.tsx
|
|
121
121
|
│ │ ├── OAuthButton.tsx
|
|
122
122
|
│ │ └── TotpVerifyForm.tsx
|
|
123
|
-
│ ├── tailwind/
|
|
124
|
-
│ └── themed/
|
|
125
|
-
│ ├── modern-tokens.css
|
|
126
|
-
│ ├── minimal-tokens.css
|
|
127
|
-
│ └── colorful-tokens.css
|
|
123
|
+
│ ├── tailwind/ - Tailwind-styled components
|
|
124
|
+
│ └── themed/ - pre-built themes with CSS tokens
|
|
125
|
+
│ ├── modern-tokens.css - glassmorphism theme
|
|
126
|
+
│ ├── minimal-tokens.css - neumorphism theme
|
|
127
|
+
│ └── colorful-tokens.css - neubrutalism theme
|
|
128
128
|
├── emails/
|
|
129
|
-
│ └── email-verification.ts
|
|
130
|
-
├── schema.ts
|
|
131
|
-
└── index.ts
|
|
129
|
+
│ └── email-verification.ts - email template for verification
|
|
130
|
+
├── schema.ts - Drizzle schema (users, sessions, oauth_accounts, etc.)
|
|
131
|
+
└── index.ts - module barrel export
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
The stamped auth route handler lands at `src/app/api/auth/[...all]/route.ts` (Next.js) or equivalent for other frameworks.
|
|
135
|
-
|
|
135
|
+
|
|
136
136
|
---
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
## Auth Module
|
|
139
|
-
|
|
140
|
-
Auth is the first Sedim module. It ships with everything, none of it is a black box.
|
|
141
139
|
|
|
142
|
-
|
|
140
|
+
Auth is the first Sedim module. It ships with everything, none of it is a black box.
|
|
143
141
|
|
|
144
|
-
|
|
145
142
|
### Features
|
|
146
|
-
|
|
143
|
+
|
|
147
144
|
| Feature | Detail |
|
|
148
145
|
|---|---|
|
|
149
146
|
| **Password auth** | Argon2id (OWASP params), account lockout after 10 failed attempts |
|
|
150
147
|
| **Session management** | SHA-256 hashed tokens, httpOnly cookies, full revocation |
|
|
151
|
-
| **OAuth** | Google, GitHub, Discord
|
|
148
|
+
| **OAuth** | Google, GitHub, Discord: PKCE (RFC 7636) on all flows |
|
|
152
149
|
| **TOTP** | RFC 6238, AES-256-GCM encrypted secrets at rest, backup codes |
|
|
153
150
|
| **Magic links** | No email enumeration, supports SMTP / Resend / Postmark / SES |
|
|
154
151
|
| **JWT** | Hybrid: short-lived signed JWTs + DB-backed refresh tokens |
|
|
155
152
|
| **RBAC / ABAC** | Role and attribute-based access control middleware |
|
|
156
153
|
| **Rate limiting** | Sliding window, in-memory or Redis store |
|
|
157
|
-
|
|
154
|
+
|
|
158
155
|
### Stack Support
|
|
159
|
-
|
|
156
|
+
|
|
160
157
|
| Framework | Drizzle | Prisma |
|
|
161
158
|
|-----------|:-------:|:------:|
|
|
162
159
|
| Next.js (App Router) | ✓ | ✓ |
|
|
163
160
|
| Express | ✓ | ✓ |
|
|
164
161
|
| Hono | ✓ | ✓ |
|
|
165
|
-
|
|
162
|
+
|
|
166
163
|
| Email provider | Supported |
|
|
167
164
|
|---|:---:|
|
|
168
165
|
| Nodemailer (SMTP) | ✓ |
|
|
169
166
|
| Resend | ✓ |
|
|
170
167
|
| Postmark | ✓ |
|
|
171
168
|
| AWS SES | ✓ |
|
|
172
|
-
|
|
169
|
+
|
|
173
170
|
### UI Tiers
|
|
174
|
-
|
|
175
|
-
Three levels so you use your own design system or ship immediately.
|
|
176
171
|
|
|
177
|
-
|
|
172
|
+
Three levels so you use your own design system or ship immediately.
|
|
178
173
|
|
|
179
|
-
|
|
180
174
|
**Headless** - zero CSS, pure logic and markup. Bring your own styles.
|
|
181
|
-
|
|
175
|
+
|
|
182
176
|
**Tailwind** - fully styled with Tailwind classes, works with your existing Tailwind config.
|
|
183
|
-
|
|
177
|
+
|
|
184
178
|
**Themed** - pre-built themes with CSS tokens. Currently ships `minimal` and `glass`.
|
|
185
|
-
|
|
179
|
+
|
|
186
180
|
### Environment Variables
|
|
187
|
-
|
|
181
|
+
|
|
188
182
|
```bash
|
|
189
183
|
# Required
|
|
190
184
|
AUTH_SECRET= # min 32 chars, used for session signing
|
|
191
185
|
DATABASE_URL= # your database connection string
|
|
192
|
-
|
|
186
|
+
|
|
193
187
|
# OAuth - add only the providers you selected
|
|
194
188
|
GOOGLE_CLIENT_ID=
|
|
195
189
|
GOOGLE_CLIENT_SECRET=
|
|
@@ -197,7 +191,7 @@ GITHUB_CLIENT_ID=
|
|
|
197
191
|
GITHUB_CLIENT_SECRET=
|
|
198
192
|
DISCORD_CLIENT_ID=
|
|
199
193
|
DISCORD_CLIENT_SECRET=
|
|
200
|
-
|
|
194
|
+
|
|
201
195
|
# Email - add only the provider you selected
|
|
202
196
|
SMTP_HOST=
|
|
203
197
|
SMTP_PORT=
|
|
@@ -208,14 +202,14 @@ POSTMARK_API_KEY=
|
|
|
208
202
|
AWS_ACCESS_KEY_ID=
|
|
209
203
|
AWS_SECRET_ACCESS_KEY=
|
|
210
204
|
AWS_SES_REGION=
|
|
211
|
-
|
|
205
|
+
|
|
212
206
|
# Optional
|
|
213
207
|
REDIS_URL= # if using Redis for rate limiting or sessions
|
|
214
|
-
TOTP_ENCRYPTION_KEY=
|
|
208
|
+
TOTP_ENCRYPTION_KEY= # required if TOTP is enabled, 32-byte hex
|
|
215
209
|
```
|
|
216
|
-
|
|
210
|
+
|
|
217
211
|
---
|
|
218
|
-
|
|
212
|
+
|
|
219
213
|
## Architecture
|
|
220
214
|
|
|
221
215
|
Sedim uses a **stamp model**, not a runtime SDK:
|
|
@@ -236,33 +230,31 @@ The CLI has four components:
|
|
|
236
230
|
|
|
237
231
|
Every module is described by a **manifest** (`registry/<module>/latest.json`) - a declarative spec of every file the module can stamp, what features gate each file, and what environment variables are required. The CLI reads the manifest, applies your choices, and stamps only what you need.
|
|
238
232
|
|
|
239
|
-
|
|
240
|
-
flowchart LR
|
|
241
|
-
Dev["Developer\nruns: sedim add auth"] --> Detector
|
|
242
|
-
Detector --> Thinker
|
|
243
|
-
RegistryJSON["Registry\nregistry/auth/latest.json"] --> Thinker
|
|
244
|
-
Thinker --> Writer
|
|
245
|
-
Writer --> StampedFiles["src/sedim/auth/\nuser-owned stamped files"]
|
|
246
|
-
|
|
247
|
-
RegistryJSON --> Dev
|
|
248
|
-
|
|
249
|
-
Thinker --> Plan["InstallPlan\napproved by user"]
|
|
250
|
-
Plan --> Writer
|
|
233
|
+
### Flow
|
|
251
234
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
235
|
+
```
|
|
236
|
+
Developer runs "sedim add auth"
|
|
237
|
+
↓
|
|
238
|
+
Detector → identifies framework, ORM, language
|
|
239
|
+
↓
|
|
240
|
+
Thinker → loads registry manifest + module plan-config
|
|
241
|
+
+ feature selections → InstallPlan
|
|
242
|
+
↓
|
|
243
|
+
Writer → stamps files to src/sedim/auth/
|
|
244
|
+
→ writes migrations
|
|
245
|
+
→ lists env vars
|
|
246
|
+
↓
|
|
247
|
+
Showbaby → confirms what was written + next steps
|
|
256
248
|
```
|
|
257
249
|
|
|
258
250
|
The module source lives in `packages/<module>/src/` - this is where auth logic, UI components, adapters, and templates live. The CLI reads from here when stamping. `packages/core/` holds shared TypeScript types used by both the CLI and all modules.
|
|
259
|
-
|
|
251
|
+
|
|
260
252
|
---
|
|
261
|
-
|
|
253
|
+
|
|
262
254
|
## Registry
|
|
263
|
-
|
|
255
|
+
|
|
264
256
|
Module manifests live in the [`registry/`](https://github.com/Sedimie/Sedim/tree/main/registry) directory. The CLI uses the local registry during development and falls back to the GitHub raw URL for published releases.
|
|
265
|
-
|
|
257
|
+
|
|
266
258
|
**Available modules:**
|
|
267
259
|
|
|
268
260
|
| Module | Status |
|
|
@@ -274,25 +266,34 @@ Module manifests live in the [`registry/`](https://github.com/Sedimie/Sedim/tree
|
|
|
274
266
|
| `WalletAdapters` | On the roadmap |
|
|
275
267
|
| `ai-rag` | On the roadmap |
|
|
276
268
|
| `payments` | On the roadmap |
|
|
277
|
-
|
|
269
|
+
|
|
278
270
|
---
|
|
271
|
+
|
|
279
272
|
## Philosophy
|
|
280
|
-
|
|
281
|
-
Most tools give you an API to call. Customization is what's keeping modularizing full stack features at bay, but with codegen model, you own the code, and hence customize it however much you want. This is not just a library to cut your grunt work
|
|
282
|
-
|
|
273
|
+
|
|
274
|
+
Most tools give you an API to call. Customization is what's keeping modularizing full stack features at bay, but with the codegen model, you own the code, and hence customize it however much you want. This is not just a library to cut your grunt work - you can learn the overall implementations it uses to be able to basically use the full stack features as boilerplate code.
|
|
275
|
+
|
|
283
276
|
When you run `sedim add auth`, you get the auth system, not a dependency on one. The files live in your repo, show up in your git history, and bend to whatever you need them to do. Sedim writes the first version. Everything after that is yours.
|
|
284
|
-
|
|
285
|
-
*The best code is code you own.*
|
|
286
277
|
|
|
278
|
+
*The best code is code you own.*
|
|
287
279
|
|
|
288
280
|
AI Agentic coding is another thing that this might clash with, but an AI tool might hallucinate and give 90% accuracy, leave out security considerations and make it tough to customize while also using up 3 hours and a considerable amount of tokens. This allows you to skip directly to the customizations part, with 100% hit-rate since it's static codegen, and 0 cost for the grunt work with upto date security standards being followed. Use AI tools on top of this to speed you up and make your workflow unbelievably fast.
|
|
289
281
|
|
|
290
282
|
---
|
|
291
283
|
|
|
292
284
|
### Check out the docs
|
|
285
|
+
|
|
293
286
|
Full documentation - customization guides, adapter references, module authoring, and escape hatches - is at [sedim.dev/docs](https://sedim.dev/docs).
|
|
294
287
|
|
|
295
288
|
### Contributions
|
|
289
|
+
|
|
296
290
|
To add a framework adapter, ORM adapter, or new module, see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
297
|
-
|
|
291
|
+
|
|
298
292
|
The module spec and adapter interface are documented there - adding a new adapter for a supported module takes a few hours once you understand the pattern.
|
|
293
|
+
|
|
294
|
+
### Watch the Full Walkthrough
|
|
295
|
+
|
|
296
|
+
[](https://youtu.be/HPEjs7-O_vk)
|
|
297
|
+
|
|
298
|
+
> 10-minute walkthrough - init, auth install, OAuth, TOTP, UI themes, and customization.
|
|
299
|
+
> [Watch on YouTube](https://youtu.be/HPEjs7-O_vk)
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/shared/constants.ts
|
|
13
|
-
var SEDIM_DIR, SEDIM_CONFIG_FILE, SEDIM_CONFIG_CACHE, SEDIM_SESSION_FILE, SEDIM_MODULES_DIR, SEDIM_LOGS_DIR, SEDIM_LATEST_LOG, DEFAULT_REGISTRY_URL, MIN_NODE_VERSION, CLI_VERSION;
|
|
13
|
+
var SEDIM_DIR, SEDIM_CONFIG_FILE, SEDIM_CONFIG_CACHE, SEDIM_SESSION_FILE, SEDIM_MODULES_DIR, SEDIM_LOGS_DIR, SEDIM_LATEST_LOG, DEFAULT_REGISTRY_URL, DEFAULT_PACKAGES_URL, MIN_NODE_VERSION, CLI_VERSION;
|
|
14
14
|
var init_constants = __esm({
|
|
15
15
|
"src/shared/constants.ts"() {
|
|
16
16
|
"use strict";
|
|
@@ -22,8 +22,9 @@ var init_constants = __esm({
|
|
|
22
22
|
SEDIM_LOGS_DIR = `${SEDIM_DIR}/logs`;
|
|
23
23
|
SEDIM_LATEST_LOG = `${SEDIM_LOGS_DIR}/latest.log`;
|
|
24
24
|
DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/sedimie/Sedim/main/registry";
|
|
25
|
+
DEFAULT_PACKAGES_URL = "https://raw.githubusercontent.com/sedimie/Sedim/main/packages";
|
|
25
26
|
MIN_NODE_VERSION = 18;
|
|
26
|
-
CLI_VERSION = "1.0.
|
|
27
|
+
CLI_VERSION = "1.0.2";
|
|
27
28
|
}
|
|
28
29
|
});
|
|
29
30
|
|
|
@@ -4254,26 +4255,16 @@ async function classifyConflicts(projectRoot, ctx, filesToCreate, schemaTables)
|
|
|
4254
4255
|
}
|
|
4255
4256
|
|
|
4256
4257
|
// src/thinker/resolve-template.ts
|
|
4257
|
-
|
|
4258
|
+
init_constants();
|
|
4258
4259
|
import path23 from "path";
|
|
4259
4260
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4260
|
-
import { existsSync } from "fs";
|
|
4261
4261
|
var __dirname3 = path23.dirname(fileURLToPath3(import.meta.url));
|
|
4262
|
-
function
|
|
4263
|
-
return path23.resolve(projectRoot, "..", "..", "..", "packages", moduleName, "src");
|
|
4264
|
-
}
|
|
4265
|
-
async function resolveTemplate(templateKey, ctx, selectedFeatures = []) {
|
|
4262
|
+
async function resolveTemplate(templateKey, ctx, selectedFeatures = [], packagesUrl = DEFAULT_PACKAGES_URL) {
|
|
4266
4263
|
const [moduleName, ...rest] = templateKey.split("/");
|
|
4267
4264
|
if (!moduleName || rest.length === 0) {
|
|
4268
4265
|
throw new Error(`Invalid templateKey: "${templateKey}"`);
|
|
4269
4266
|
}
|
|
4270
4267
|
const relPath = rest.join("/");
|
|
4271
|
-
const projectRoot = ctx.projectRoot ?? process.cwd();
|
|
4272
|
-
const packageRoot = getPackageRoot(projectRoot, moduleName);
|
|
4273
|
-
const tryPackageRoot = (levels) => path23.resolve(__dirname3, levels, "packages", moduleName, "src");
|
|
4274
|
-
const packageRoot3 = tryPackageRoot("../../..");
|
|
4275
|
-
const packageRoot4 = tryPackageRoot("../../../../..");
|
|
4276
|
-
const resolvedRoot = existsSync(packageRoot) ? packageRoot : existsSync(packageRoot3) ? packageRoot3 : existsSync(packageRoot4) ? packageRoot4 : packageRoot;
|
|
4277
4268
|
const generatedKeys = {
|
|
4278
4269
|
"ui/pages/login-page": (f) => buildLoginPage(f),
|
|
4279
4270
|
"ui/pages/signup-page": (f) => buildSignupPage(f),
|
|
@@ -4285,58 +4276,60 @@ async function resolveTemplate(templateKey, ctx, selectedFeatures = []) {
|
|
|
4285
4276
|
return generatedKeys[relPath](selectedFeatures, ctx);
|
|
4286
4277
|
}
|
|
4287
4278
|
let content;
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4279
|
+
let found = false;
|
|
4280
|
+
const asIsUrl = `${packagesUrl}/${moduleName}/src/${relPath}`;
|
|
4281
|
+
const asIsFetched = await tryFetch(asIsUrl);
|
|
4282
|
+
if (asIsFetched !== null) {
|
|
4283
|
+
content = asIsFetched;
|
|
4284
|
+
found = true;
|
|
4292
4285
|
} else {
|
|
4293
|
-
const
|
|
4294
|
-
|
|
4295
|
-
const raw = await
|
|
4296
|
-
|
|
4286
|
+
for (const tryExt of ["ts", "tsx", "css", "prisma"]) {
|
|
4287
|
+
const url = `${packagesUrl}/${moduleName}/src/${relPath}.${tryExt}`;
|
|
4288
|
+
const raw = await tryFetch(url);
|
|
4289
|
+
if (raw !== null) {
|
|
4290
|
+
content = raw;
|
|
4291
|
+
found = true;
|
|
4292
|
+
break;
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4296
|
+
if (!found) {
|
|
4297
|
+
const templateAsIs = `${packagesUrl}/${moduleName}/src/templates/${relPath}`;
|
|
4298
|
+
const fetchedAsIs = await tryFetch(templateAsIs);
|
|
4299
|
+
if (fetchedAsIs !== null) {
|
|
4300
|
+
content = fetchedAsIs;
|
|
4301
|
+
found = true;
|
|
4297
4302
|
} else {
|
|
4298
|
-
const
|
|
4299
|
-
const
|
|
4300
|
-
if (
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
content = await readText(sourcePath);
|
|
4304
|
-
content = applySubstitutions(content, ctx, selectedFeatures);
|
|
4305
|
-
} else {
|
|
4306
|
-
content = "";
|
|
4307
|
-
}
|
|
4308
|
-
} else {
|
|
4309
|
-
let found = false;
|
|
4310
|
-
content = "";
|
|
4311
|
-
for (const ext of [".ts", ".tsx", ".css"]) {
|
|
4312
|
-
const sourcePath = path23.join(resolvedRoot, `${relPath}${ext}`);
|
|
4313
|
-
if (await exists(sourcePath)) {
|
|
4314
|
-
const raw = await readText(sourcePath);
|
|
4315
|
-
content = applySubstitutions(raw, ctx, selectedFeatures);
|
|
4316
|
-
found = true;
|
|
4317
|
-
break;
|
|
4318
|
-
}
|
|
4319
|
-
}
|
|
4320
|
-
if (!found) {
|
|
4321
|
-
const sourcePrismaPath = path23.join(resolvedRoot, `${relPath}.prisma`);
|
|
4322
|
-
if (await exists(sourcePrismaPath)) {
|
|
4323
|
-
content = await readText(sourcePrismaPath);
|
|
4324
|
-
} else {
|
|
4325
|
-
throw new Error(
|
|
4326
|
-
`Template not found for key "${templateKey}". Checked:
|
|
4327
|
-
${templatePath}
|
|
4328
|
-
${path23.join(resolvedRoot, relPath)}.[ts|tsx|css]`
|
|
4329
|
-
);
|
|
4330
|
-
}
|
|
4331
|
-
}
|
|
4303
|
+
const templateUrl = `${packagesUrl}/${moduleName}/src/templates/${relPath}.ts`;
|
|
4304
|
+
const fetched = await tryFetch(templateUrl);
|
|
4305
|
+
if (fetched !== null) {
|
|
4306
|
+
content = fetched;
|
|
4307
|
+
found = true;
|
|
4332
4308
|
}
|
|
4333
4309
|
}
|
|
4334
4310
|
}
|
|
4311
|
+
if (!found) {
|
|
4312
|
+
throw new Error(
|
|
4313
|
+
`Template not found for key "${templateKey}". Checked:
|
|
4314
|
+
${packagesUrl}/${moduleName}/src/${relPath}.[ts|tsx|css|prisma]
|
|
4315
|
+
${packagesUrl}/${moduleName}/src/templates/${relPath}.ts`
|
|
4316
|
+
);
|
|
4317
|
+
}
|
|
4318
|
+
content = applySubstitutions(content, ctx, selectedFeatures);
|
|
4335
4319
|
if (!relPath.endsWith(".css") && !relPath.endsWith(".prisma")) {
|
|
4336
4320
|
content = stripRelativeJsExtensions(content);
|
|
4337
4321
|
}
|
|
4338
4322
|
return content;
|
|
4339
4323
|
}
|
|
4324
|
+
async function tryFetch(url) {
|
|
4325
|
+
try {
|
|
4326
|
+
const res = await fetch(url);
|
|
4327
|
+
if (!res.ok) return null;
|
|
4328
|
+
return res.text();
|
|
4329
|
+
} catch {
|
|
4330
|
+
return null;
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4340
4333
|
function stripRelativeJsExtensions(content) {
|
|
4341
4334
|
return content.replace(/(from\s+['"])(\.\.?\/[^'"]*?)\.js(['"])/g, "$1$2$3");
|
|
4342
4335
|
}
|
|
@@ -4475,7 +4468,7 @@ function buildLoginPage(features) {
|
|
|
4475
4468
|
` <p style={{ textAlign: 'center', fontSize: '0.8125rem', color: '${mutedColor}', margin: 0 }}>`
|
|
4476
4469
|
);
|
|
4477
4470
|
body.push(
|
|
4478
|
-
` Don
|
|
4471
|
+
` Don't have an account?{' '}<a href="/signup" style={{ color: '${fgColor}', fontWeight: 600, textDecoration: 'none' }}>Sign up</a>`
|
|
4479
4472
|
);
|
|
4480
4473
|
body.push(` </p>`);
|
|
4481
4474
|
}
|
|
@@ -4640,7 +4633,7 @@ function buildForgotPage(features = []) {
|
|
|
4640
4633
|
const inner = [
|
|
4641
4634
|
` <div style={{ textAlign: 'center' }}>`,
|
|
4642
4635
|
` <h1 style={{ fontSize: '1.5rem', fontWeight: 700, margin: '0 0 0.25rem' }}>Reset password</h1>`,
|
|
4643
|
-
` <p style={{ fontSize: '0.875rem', color: '${mutedColor}', margin: 0 }}>Enter your email and we
|
|
4636
|
+
` <p style={{ fontSize: '0.875rem', color: '${mutedColor}', margin: 0 }}>Enter your email and we'll send a reset link.</p>`,
|
|
4644
4637
|
` </div>`,
|
|
4645
4638
|
` <ForgotPasswordForm />`,
|
|
4646
4639
|
` <p style={{ textAlign: 'center', fontSize: '0.8125rem', margin: 0 }}>`,
|
|
@@ -5017,7 +5010,31 @@ function manifestToPlanConfig(manifest, selectedFeatures, _ctx) {
|
|
|
5017
5010
|
}
|
|
5018
5011
|
|
|
5019
5012
|
// src/thinker/load-plan-config.ts
|
|
5020
|
-
|
|
5013
|
+
init_constants();
|
|
5014
|
+
import { createWriteStream } from "fs";
|
|
5015
|
+
import https from "https";
|
|
5016
|
+
import os from "os";
|
|
5017
|
+
import { pipeline } from "stream";
|
|
5018
|
+
import { promisify } from "util";
|
|
5019
|
+
var streamPipeline = promisify(pipeline);
|
|
5020
|
+
async function downloadToTemp(url, suffix) {
|
|
5021
|
+
const tmpDir = os.tmpdir();
|
|
5022
|
+
const tmpPath = `${tmpDir}/sedim-${suffix}-${Date.now()}.js`;
|
|
5023
|
+
await new Promise((resolve, reject) => {
|
|
5024
|
+
const req = https.get(url, (res) => {
|
|
5025
|
+
if (res.statusCode !== 200) {
|
|
5026
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
5027
|
+
return;
|
|
5028
|
+
}
|
|
5029
|
+
const file = createWriteStream(tmpPath);
|
|
5030
|
+
res.pipe(file);
|
|
5031
|
+
file.on("finish", () => file.close(() => resolve()));
|
|
5032
|
+
});
|
|
5033
|
+
req.on("error", reject);
|
|
5034
|
+
});
|
|
5035
|
+
return tmpPath;
|
|
5036
|
+
}
|
|
5037
|
+
async function loadPlanConfig(moduleName, manifest, ctx, selectedFeatures, packagesUrl = DEFAULT_PACKAGES_URL) {
|
|
5021
5038
|
try {
|
|
5022
5039
|
const fnName = `create${capitalize(moduleName)}PlanConfig`;
|
|
5023
5040
|
const mod = await import(`@sedim/${moduleName}/plan-config`);
|
|
@@ -5027,7 +5044,20 @@ async function loadPlanConfig(moduleName, manifest, ctx, selectedFeatures) {
|
|
|
5027
5044
|
selectedFeatures
|
|
5028
5045
|
);
|
|
5029
5046
|
}
|
|
5030
|
-
} catch
|
|
5047
|
+
} catch {
|
|
5048
|
+
}
|
|
5049
|
+
try {
|
|
5050
|
+
const fnName = `create${capitalize(moduleName)}PlanConfig`;
|
|
5051
|
+
const remoteUrl = `${packagesUrl}/${moduleName}/planConfig/plan-config.js`;
|
|
5052
|
+
const tmpPath = await downloadToTemp(remoteUrl, moduleName);
|
|
5053
|
+
const mod = await import(tmpPath);
|
|
5054
|
+
if (mod && typeof mod[fnName] === "function") {
|
|
5055
|
+
return mod[fnName](
|
|
5056
|
+
ctx,
|
|
5057
|
+
selectedFeatures
|
|
5058
|
+
);
|
|
5059
|
+
}
|
|
5060
|
+
} catch {
|
|
5031
5061
|
}
|
|
5032
5062
|
return manifestToPlanConfig(manifest, selectedFeatures, ctx);
|
|
5033
5063
|
}
|