create-astroid 0.2.0 → 0.3.1
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/index.mjs +42 -16
- package/package.json +3 -3
- package/template/scripts/seed-editors.mjs +2 -2
- package/template/src/auth.ts +14 -8
package/index.mjs
CHANGED
|
@@ -173,8 +173,23 @@ function astroidConfigSource(config) {
|
|
|
173
173
|
...(config.modules?.length ? [` modules: ${JSON.stringify(config.modules)},`] : []),
|
|
174
174
|
// Must be emitted: `astroid generate` rebuilds the middleware from THIS
|
|
175
175
|
// file, so a config that omitted the portal would regenerate a middleware
|
|
176
|
-
// with no guard while src/portal-auth.ts sat there unused.
|
|
177
|
-
|
|
176
|
+
// with no guard while src/portal-auth.ts sat there unused. And the FULL shape
|
|
177
|
+
// (tablePrefix, signUp) — not a bare `{ enabled: true }` — so a regenerate
|
|
178
|
+
// reproduces the SAME unprefixed `user`/`session` seam the 0002_portal_auth
|
|
179
|
+
// migration created, rather than defaulting the prefix back to `portal_`.
|
|
180
|
+
...(config.portal?.enabled
|
|
181
|
+
? [
|
|
182
|
+
" portal: {",
|
|
183
|
+
" enabled: true,",
|
|
184
|
+
...(config.portal.tablePrefix !== undefined
|
|
185
|
+
? [` tablePrefix: ${JSON.stringify(config.portal.tablePrefix)},`]
|
|
186
|
+
: []),
|
|
187
|
+
...(config.portal.signUp !== undefined
|
|
188
|
+
? [` signUp: ${JSON.stringify(config.portal.signUp)},`]
|
|
189
|
+
: []),
|
|
190
|
+
" },",
|
|
191
|
+
]
|
|
192
|
+
: []),
|
|
178
193
|
' deploy: { platform: "cloudflare" },',
|
|
179
194
|
"});",
|
|
180
195
|
"",
|
|
@@ -243,9 +258,11 @@ async function main() {
|
|
|
243
258
|
const archetype = ARCHETYPES.includes(archetypeRaw) ? archetypeRaw : "marketing";
|
|
244
259
|
const color = flags.color || (await prompt("Brand color (hex)", "#5b4bff"));
|
|
245
260
|
const host = flags.host && flags.host !== true ? flags.host : undefined;
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
|
|
261
|
+
// The customer PORTAL is opt-in via --portal, but a storefront IMPLIES one — a
|
|
262
|
+
// shop has customers who sign in, reorder, and track orders — so enable it there
|
|
263
|
+
// by default. (Commerce below stays opt-in: infra a marketing site shouldn't carry.)
|
|
264
|
+
const portal =
|
|
265
|
+
flags.portal === true || flags.portal === "true" || archetype === "storefront";
|
|
249
266
|
// The map module is opt-in and pulls real weight (maplibre-gl is ~1 MB), so
|
|
250
267
|
// it is never on by default.
|
|
251
268
|
const map = flags.map === true || flags.map === "true";
|
|
@@ -284,7 +301,10 @@ async function main() {
|
|
|
284
301
|
theme: { name, colors: { brand: color } },
|
|
285
302
|
sections: ARCHETYPE_SECTIONS[archetype],
|
|
286
303
|
...(commerce ? { commerce: { provider: commerce } } : {}),
|
|
287
|
-
|
|
304
|
+
// Unprefixed `user`/`session` (customers — email + password), so the studio's
|
|
305
|
+
// `louise_`-prefixed tables and the portal's never collide (mirrors the
|
|
306
|
+
// reference storefront). `signUp: true` because a shop lets customers register.
|
|
307
|
+
...(portal ? { portal: { enabled: true, tablePrefix: "", signUp: true } } : {}),
|
|
288
308
|
// ONE array, built from every enabled flag. Two separate `...(x ? {modules}
|
|
289
309
|
// : {})` spreads would let the later one overwrite the earlier, silently
|
|
290
310
|
// dropping a module whenever both were passed.
|
|
@@ -389,16 +409,22 @@ async function main() {
|
|
|
389
409
|
let authMigrationOk = false;
|
|
390
410
|
try {
|
|
391
411
|
const { generateAuthSchemaSql } = await import("louise-toolkit/auth");
|
|
392
|
-
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
|
|
412
|
+
// The EDITOR instance's tables — `louise_`-prefixed (the editor convention),
|
|
413
|
+
// leaving the unprefixed `user`/`session` names free for a second/portal
|
|
414
|
+
// instance. Must match the `tablePrefix` in src/auth.ts and the `louise_user`
|
|
415
|
+
// table the generated `editorsRoute` reads.
|
|
416
|
+
write(dir, "migrations/0001_auth.sql", generateAuthSchemaSql({ tablePrefix: "louise_" }));
|
|
417
|
+
// The portal's own auth tables — a SECOND Better Auth instance sharing one D1
|
|
418
|
+
// but never a row, so a portal account can't sign into the studio and an
|
|
419
|
+
// editor doesn't appear in the portal. `customers: true` (email + password)
|
|
420
|
+
// and the config's tablePrefix, so this schema matches the scaffolded
|
|
421
|
+
// portal-auth.ts exactly. Without this migration the portal builds fine and
|
|
422
|
+
// fails on the first sign-in with a missing table.
|
|
397
423
|
if (config.portal?.enabled) {
|
|
398
424
|
write(
|
|
399
425
|
dir,
|
|
400
426
|
"migrations/0002_portal_auth.sql",
|
|
401
|
-
generateAuthSchemaSql({ tablePrefix: "
|
|
427
|
+
generateAuthSchemaSql({ customers: true, tablePrefix: config.portal.tablePrefix ?? "" }),
|
|
402
428
|
);
|
|
403
429
|
}
|
|
404
430
|
authMigrationOk = true;
|
|
@@ -406,7 +432,7 @@ async function main() {
|
|
|
406
432
|
write(
|
|
407
433
|
dir,
|
|
408
434
|
"migrations/0001_auth.sql",
|
|
409
|
-
"-- Better Auth tables — generate after install:\n-- pnpm exec louise gen-auth-schema --out migrations/0001_auth.sql\n",
|
|
435
|
+
"-- Better Auth tables (editor, louise_ prefix) — generate after install:\n-- pnpm exec louise gen-auth-schema --table-prefix louise_ --out migrations/0001_auth.sql\n",
|
|
410
436
|
);
|
|
411
437
|
// Same stub for the portal's prefixed set. Without it a portal scaffold
|
|
412
438
|
// looks complete, builds, and fails on the first sign-in with a missing
|
|
@@ -415,8 +441,8 @@ async function main() {
|
|
|
415
441
|
write(
|
|
416
442
|
dir,
|
|
417
443
|
"migrations/0002_portal_auth.sql",
|
|
418
|
-
"-- Portal Better Auth tables (
|
|
419
|
-
"-- pnpm exec louise gen-auth-schema --
|
|
444
|
+
"-- Portal Better Auth tables (customers, unprefixed) — generate after install:\n" +
|
|
445
|
+
"-- pnpm exec louise gen-auth-schema --out migrations/0002_portal_auth.sql\n",
|
|
420
446
|
);
|
|
421
447
|
}
|
|
422
448
|
}
|
|
@@ -441,7 +467,7 @@ async function main() {
|
|
|
441
467
|
: [
|
|
442
468
|
" # generate the Better Auth migration (it could not be written at scaffold",
|
|
443
469
|
" # time — `louise` is on your path once the install above finishes):",
|
|
444
|
-
" pnpm exec louise gen-auth-schema --out migrations/0001_auth.sql",
|
|
470
|
+
" pnpm exec louise gen-auth-schema --table-prefix louise_ --out migrations/0001_auth.sql",
|
|
445
471
|
...(config.portal?.enabled
|
|
446
472
|
? [
|
|
447
473
|
" pnpm exec louise gen-auth-schema --table-prefix portal_ \\",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-astroid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Scaffold a new Astroid site — an editable, multi-editor Astro app on Cloudflare Workers — in one command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@better-auth/passkey": "^1.6.23",
|
|
34
34
|
"better-auth": "^1.6.23",
|
|
35
|
-
"astroidjs": "0.
|
|
36
|
-
"louise-toolkit": "0.
|
|
35
|
+
"astroidjs": "0.3.1",
|
|
36
|
+
"louise-toolkit": "0.17.1"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=24.0.0"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Seed the first editor(s) — an admin `
|
|
2
|
+
// Seed the first editor(s) — an admin `louise_user` row per OWNER_EMAIL / ENGINEER_EMAIL.
|
|
3
3
|
// A row here IS an editor and IS the magic-link allowlist, so this bootstraps
|
|
4
4
|
// access before anyone can sign in. Idempotent (INSERT OR IGNORE on unique email).
|
|
5
5
|
// After this, add more editors from the Users panel (never by editing env).
|
|
@@ -46,7 +46,7 @@ for (const email of emails) {
|
|
|
46
46
|
const id = randomUUID();
|
|
47
47
|
const name = email.split("@")[0];
|
|
48
48
|
const sql =
|
|
49
|
-
"INSERT OR IGNORE INTO
|
|
49
|
+
"INSERT OR IGNORE INTO louise_user " +
|
|
50
50
|
"(id, name, email, emailVerified, createdAt, updatedAt, role, firstName, lastName) VALUES " +
|
|
51
51
|
`(${q(id)}, ${q(name)}, ${q(email)}, 1, ${q(now)}, ${q(now)}, 'admin', NULL, NULL);`;
|
|
52
52
|
console.log(`Seeding editor ${email} (${target}) …`);
|
package/template/src/auth.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
// The auth seam Astroid's generated worker.ts + middleware.ts import
|
|
2
2
|
// (`resolveEditor`), plus the Better Auth catch-all the /api/auth route calls.
|
|
3
3
|
//
|
|
4
|
-
// DB-managed editors: an admin `
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// DB-managed editors: an admin `louise_user` row IS an editor. The editor
|
|
5
|
+
// instance's tables are `louise_`-prefixed (the editor convention), leaving the
|
|
6
|
+
// unprefixed `user`/`session` names free for a second/portal instance. That
|
|
7
|
+
// table is BOTH the role source and the magic-link allowlist — `resolveAdmins`
|
|
8
|
+
// reads it, so only an existing editor's email can request a sign-in link. Seed
|
|
9
|
+
// the first editor with `pnpm seed:editors`; add more from the Users panel
|
|
10
|
+
// (editorsRoute), never by editing env. Magic-link + passkey come from
|
|
11
|
+
// `getLouiseAuth`; no passwords.
|
|
9
12
|
|
|
10
13
|
import { astroidMailTheme, magicLinkEmail } from "astroidjs";
|
|
11
14
|
import { env } from "cloudflare:workers";
|
|
@@ -26,10 +29,10 @@ const BRAND = "__BRAND_NAME__";
|
|
|
26
29
|
// second argument to override any slot.
|
|
27
30
|
const MAIL_THEME = astroidMailTheme(astroidConfig);
|
|
28
31
|
|
|
29
|
-
/** The DB-managed editor allowlist: every admin `
|
|
32
|
+
/** The DB-managed editor allowlist: every admin `louise_user` row. */
|
|
30
33
|
async function resolveAdmins(): Promise<string[]> {
|
|
31
34
|
const { results } = await env.DB.prepare(
|
|
32
|
-
"SELECT email FROM
|
|
35
|
+
"SELECT email FROM louise_user WHERE role = 'admin'",
|
|
33
36
|
).all<{ email: string }>();
|
|
34
37
|
return results.map((r) => r.email);
|
|
35
38
|
}
|
|
@@ -39,13 +42,16 @@ function renderMagicLinkEmail({ url, toEmail }: { url: string; toEmail: string }
|
|
|
39
42
|
return magicLinkEmail(MAIL_THEME, { url, toEmail });
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
/** The request-scoped Better Auth instance (magic-link + passkey, DB allowlist).
|
|
45
|
+
/** The request-scoped Better Auth instance (magic-link + passkey, DB allowlist).
|
|
46
|
+
* `tablePrefix: "louise_"` namespaces the editor's Better Auth tables so a
|
|
47
|
+
* second (portal) instance can take the unprefixed `user`/`session` names. */
|
|
43
48
|
function getAuth(request: Request): Promise<LouiseAuth> {
|
|
44
49
|
return getLouiseAuth(env, new URL(request.url).origin, {
|
|
45
50
|
rpName: BRAND,
|
|
46
51
|
mailFrom: { email: env.MAIL_FROM, name: BRAND },
|
|
47
52
|
renderMagicLinkEmail,
|
|
48
53
|
resolveAdmins,
|
|
54
|
+
tablePrefix: "louise_",
|
|
49
55
|
});
|
|
50
56
|
}
|
|
51
57
|
|