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 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
- ...(config.portal?.enabled ? [" portal: { enabled: true },"] : []),
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
- // Portal + commerce are opt-in and unprompted: each pulls in real
247
- // infrastructure a plain marketing site should not carry.
248
- const portal = flags.portal === true || flags.portal === "true";
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
- ...(portal ? { portal: { enabled: true } } : {}),
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
- write(dir, "migrations/0001_auth.sql", generateAuthSchemaSql());
393
- // The portal's own auth tables. A SECOND set, prefixed — the two instances
394
- // share one D1 but never a row, so a portal account can't sign into the
395
- // studio and an editor doesn't appear in the portal. Without this migration
396
- // the portal builds fine and fails on the first sign-in.
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: "portal_" }),
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 (prefixed) — generate after install:\n" +
419
- "-- pnpm exec louise gen-auth-schema --table-prefix portal_ --out migrations/0002_portal_auth.sql\n",
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.2.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.2.0",
36
- "louise-toolkit": "0.16.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 `user` row per OWNER_EMAIL / ENGINEER_EMAIL.
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 user " +
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}) …`);
@@ -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 `user` row IS an editor. That table is BOTH the
5
- // role source and the magic-link allowlist — `resolveAdmins` reads it, so only an
6
- // existing editor's email can request a sign-in link. Seed the first editor with
7
- // `pnpm seed:editors`; add more from the Users panel (editorsRoute), never by
8
- // editing env. Magic-link + passkey come from `getLouiseAuth`; no passwords.
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 `user` row. */
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 user WHERE role = 'admin'",
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