create-meith 0.19.0 → 0.20.0

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/dist/bin.mjs CHANGED
@@ -25,7 +25,7 @@ var MATERIALIZED_AT_ROOT = [
25
25
  ];
26
26
  var VERCEL_BUILD_COMMAND = `community migrate && forum-web build ${AT_ROOT_FLAG}`;
27
27
  var TICK_PATH = "/api/system/tick";
28
- var TICK_SCHEDULE = "* * * * *";
28
+ var TICK_SCHEDULE = "0 3 * * *";
29
29
  var MATERIALIZED_PUBLIC = [
30
30
  "placeholder-logo.png",
31
31
  "placeholder-logo.svg",
@@ -330,7 +330,7 @@ var VERCEL_DERIVED_DRIVERS = [
330
330
  "MAIL_DRIVER=http"
331
331
  ];
332
332
  var VERCEL_PROMPTED_ENV = ["AUTH_SECRET", "CRON_SECRET"];
333
- var VERCEL_MARKETPLACE_STORES = [
333
+ var VERCEL_MARKETPLACE_PRODUCTS = [
334
334
  { type: "integration", integrationSlug: "neon", productSlug: "neon", protocol: "storage" },
335
335
  {
336
336
  type: "integration",
@@ -338,7 +338,13 @@ var VERCEL_MARKETPLACE_STORES = [
338
338
  productSlug: "upstash-kv",
339
339
  protocol: "storage"
340
340
  },
341
- { type: "blob" }
341
+ { type: "blob" },
342
+ {
343
+ type: "integration",
344
+ integrationSlug: "resend",
345
+ productSlug: "resend-email",
346
+ protocol: "messaging"
347
+ }
342
348
  ];
343
349
  function deployButtonUrl(templateRepositoryUrl) {
344
350
  const params = new URLSearchParams([
@@ -348,10 +354,10 @@ function deployButtonUrl(templateRepositoryUrl) {
348
354
  ["env", VERCEL_PROMPTED_ENV.join(",")],
349
355
  [
350
356
  "envDescription",
351
- "Two secrets, generated rather than chosen \u2014 32 characters or more each. Everything else the board reads from the database, cache and blob store this form links."
357
+ "Two secrets, generated rather than chosen \u2014 32 characters or more each. Everything else the board reads from the database, cache, blob store and mail provider this form links."
352
358
  ],
353
359
  ["envLink", `${templateRepositoryUrl}/blob/main/.env.example`],
354
- ["stores", JSON.stringify(VERCEL_MARKETPLACE_STORES)],
360
+ ["products", JSON.stringify(VERCEL_MARKETPLACE_PRODUCTS)],
355
361
  ["skippable-integrations", "1"]
356
362
  ]);
357
363
  return `https://vercel.com/new/clone?${params.toString()}`;
@@ -1007,18 +1013,19 @@ A forum, built on [Meith](${repositoryUrl}), running as Vercel functions.
1007
1013
  Vercel's SDK, which authenticates with the deployment's own OIDC identity, so
1008
1014
  there is no token to copy. This is what used to be four hand-typed \`S3_*\`
1009
1015
  secrets.
1016
+ - **A Resend mail account**, attached the same way, which publishes
1017
+ \`RESEND_API_KEY\`. The board reads that name directly: its mail driver already
1018
+ speaks Resend's request shape, so there is nothing to adapt.
1010
1019
  - **A Vercel project** carrying \`vercel.json\` \u2014 the build command
1011
1020
  \`${VERCEL_BUILD_COMMAND}\`,
1012
1021
  which applies the schema before it builds, materializes the board's app at
1013
1022
  the project root so the artefact lands where Vercel reads it, and the cron
1014
1023
  entry that drives the tick.
1015
1024
 
1016
- **Mail is the one thing the button does not set up**, and it takes one click
1017
- after the deploy \u2014 see *Mail, in one click* below. That is also where the
1018
- address the board sends from goes: it has to be at a domain your provider has
1019
- verified, which cannot be true of any domain before a provider exists. The
1020
- board boots and runs without mail, and delivers nothing, silently, until it is
1021
- done.
1025
+ **Mail needs one thing after the deploy**: the address the board sends from.
1026
+ It has to be at a domain Resend has verified, and no form can ask for that
1027
+ before the account exists \u2014 see *Mail, after the deploy* below. The board boots
1028
+ and runs without it, and delivers nothing, silently, until it is done.
1022
1029
 
1023
1030
  ## What to type into the deploy form
1024
1031
 
@@ -1069,26 +1076,22 @@ Vercel* below for why that matters \u2014 set \`FILESTORE_DRIVER=s3\` and add
1069
1076
  project's environment settings, with \`S3_ENDPOINT\` for a bucket that is not AWS
1070
1077
  (\`S3_REGION=auto\` for R2). The same board runs either way.
1071
1078
 
1072
- ## Mail, in one click
1079
+ ## Mail, after the deploy
1073
1080
 
1074
1081
  A board that cannot send mail cannot reset a password, so do this before you
1075
- invite anybody.
1076
-
1077
- 1. Open your project on Vercel, go to **Storage \u2192 Marketplace** (or
1078
- **Integrations**), and add **Resend**. It creates a Resend account linked to
1079
- the project and connects your sending domain.
1080
- 2. Verify that domain in the Resend dashboard if you have not already. Resend
1081
- refuses to send from an address at a domain it has not verified.
1082
- 3. Add \`MAIL_FROM\` to the project's environment settings \u2014 an address at that
1083
- verified domain, and the only mail value you ever type. The deploy form does
1084
- not ask for it, because a sender address is not something you can know
1085
- before there is a provider to verify it.
1086
- 4. Redeploy, or let the next push redeploy.
1087
-
1088
- That is all. The integration publishes its key into the project as
1089
- \`RESEND_API_KEY\`, and the board reads that name: with it set, and \`MAIL_FROM\`
1090
- beside it, mail sends over Resend's HTTPS API with nothing further to
1091
- configure.
1082
+ invite anybody. The deploy form already added Resend and published its key; two
1083
+ steps remain, and neither could have been answered on the form.
1084
+
1085
+ 1. Verify your sending domain in the Resend dashboard if you have not already.
1086
+ Resend refuses to send from an address at a domain it has not verified.
1087
+ 2. Add \`MAIL_FROM\` to the project's environment settings \u2014 an address at that
1088
+ verified domain, and the only mail value you ever type.
1089
+ 3. Redeploy, or let the next push redeploy.
1090
+
1091
+ That is all. The integration published its key into the project as
1092
+ \`RESEND_API_KEY\` when you deployed, and the board reads that name: with it set,
1093
+ and \`MAIL_FROM\` beside it, mail sends over Resend's HTTPS API with nothing
1094
+ further to configure.
1092
1095
 
1093
1096
  The board is not tied to Resend. Its mail driver is a plain JSON-over-HTTPS
1094
1097
  sender that posts \`{from, to, subject, text, html, reply_to}\` with a bearer
@@ -1122,15 +1125,23 @@ drains; nothing here runs it on its own, because there is no worker process on
1122
1125
  a function platform. Two things about it are worth knowing **before** you
1123
1126
  deploy rather than after:
1124
1127
 
1125
- - **A per-minute schedule needs a paid plan.** Hobby allows a couple of cron
1126
- jobs and runs each of them roughly once a day, at an hour Vercel chooses;
1127
- only paid plans accept an arbitrary cron expression. A board ticking daily
1128
- still loses nothing \u2014 tasks are written so a missed run delays work rather
1129
- than dropping it \u2014 but "as it happens" notifications become a daily digest in
1130
- all but name. To keep a minute-by-minute tick on Hobby, drive
1131
- \`${TICK_PATH}\` from something else that can call a URL on a schedule \u2014 a
1132
- GitHub Actions workflow, a systemd timer, an uptime pinger \u2014 presenting
1133
- \`TICK_SECRET\` instead.
1128
+ - **This ships a daily schedule, because Hobby refuses anything faster.** A
1129
+ Hobby plan rejects a cron expression that would run more than once a day \u2014
1130
+ the deployment fails outright rather than being slowed down \u2014 so
1131
+ \`vercel.json\` carries \`${TICK_SCHEDULE}\` and deploys anywhere. On a
1132
+ paid plan, edit it to \`* * * * *\` and the board ticks every minute.
1133
+
1134
+ A daily tick loses nothing permanently: tasks are written so a missed run
1135
+ delays work rather than dropping it, and a password reset is sent as the
1136
+ request is handled rather than waiting for a tick. What it does delay is
1137
+ everything the tick drives \u2014 a new post is not findable in search, and a
1138
+ notification is not sent, until the next run.
1139
+
1140
+ **To keep a fast tick without paying**, drive \`${TICK_PATH}\` from anything
1141
+ that can call a URL on a schedule \u2014 a GitHub Actions workflow, a systemd
1142
+ timer, an uptime pinger \u2014 presenting \`TICK_SECRET\` instead of
1143
+ \`CRON_SECRET\`. The endpoint accepts either, so the Vercel cron and an
1144
+ outside scheduler can both drive it.
1134
1145
  - **\`maxDuration = 300\` is validated when the project builds, not when the
1135
1146
  function runs.** A plan that does not allow 300 seconds therefore **fails the
1136
1147
  deployment** rather than clamping the request. With Fluid Compute \u2014 the
@@ -1329,7 +1340,7 @@ async function run(argv, version) {
1329
1340
  }
1330
1341
 
1331
1342
  // src/bin.ts
1332
- var result = await run(process.argv.slice(2), "0.19.0");
1343
+ var result = await run(process.argv.slice(2), "0.20.0");
1333
1344
  for (const line of result.lines) {
1334
1345
  if (result.code === 0) console.log(line);
1335
1346
  else console.error(line);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-meith",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Scaffold a Meith board — npx create-meith <name> writes a workspace that depends on @meith/web and @meith/cli.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/bin.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { run } from './cli'
3
3
 
4
- const result = await run(process.argv.slice(2), '0.19.0')
4
+ const result = await run(process.argv.slice(2), '0.20.0')
5
5
  for (const line of result.lines) {
6
6
  if (result.code === 0) console.log(line)
7
7
  else console.error(line)
package/src/index.ts CHANGED
@@ -10,7 +10,7 @@ export {
10
10
  TICK_PATH,
11
11
  TICK_SCHEDULE,
12
12
  VERCEL_BUILD_COMMAND,
13
- VERCEL_MARKETPLACE_STORES,
13
+ VERCEL_MARKETPLACE_PRODUCTS,
14
14
  VERCEL_PROMPTED_ENV,
15
15
  validateName,
16
16
  } from './scaffold'
package/src/scaffold.ts CHANGED
@@ -33,7 +33,7 @@ export const VERCEL_BUILD_COMMAND = `community migrate && forum-web build ${AT_R
33
33
 
34
34
  export const TICK_PATH = '/api/system/tick'
35
35
 
36
- export const TICK_SCHEDULE = '* * * * *'
36
+ export const TICK_SCHEDULE = '0 3 * * *'
37
37
 
38
38
  export const MATERIALIZED_PUBLIC = [
39
39
  'placeholder-logo.png',
@@ -359,7 +359,7 @@ const VERCEL_DERIVED_DRIVERS = [
359
359
 
360
360
  export const VERCEL_PROMPTED_ENV = ['AUTH_SECRET', 'CRON_SECRET'] as const
361
361
 
362
- export const VERCEL_MARKETPLACE_STORES = [
362
+ export const VERCEL_MARKETPLACE_PRODUCTS = [
363
363
  { type: 'integration', integrationSlug: 'neon', productSlug: 'neon', protocol: 'storage' },
364
364
  {
365
365
  type: 'integration',
@@ -368,6 +368,12 @@ export const VERCEL_MARKETPLACE_STORES = [
368
368
  protocol: 'storage',
369
369
  },
370
370
  { type: 'blob' },
371
+ {
372
+ type: 'integration',
373
+ integrationSlug: 'resend',
374
+ productSlug: 'resend-email',
375
+ protocol: 'messaging',
376
+ },
371
377
  ] as const
372
378
 
373
379
  export function deployButtonUrl(templateRepositoryUrl: string): string {
@@ -378,10 +384,10 @@ export function deployButtonUrl(templateRepositoryUrl: string): string {
378
384
  ['env', VERCEL_PROMPTED_ENV.join(',')],
379
385
  [
380
386
  'envDescription',
381
- 'Two secrets, generated rather than chosen — 32 characters or more each. Everything else the board reads from the database, cache and blob store this form links.',
387
+ 'Two secrets, generated rather than chosen — 32 characters or more each. Everything else the board reads from the database, cache, blob store and mail provider this form links.',
382
388
  ],
383
389
  ['envLink', `${templateRepositoryUrl}/blob/main/.env.example`],
384
- ['stores', JSON.stringify(VERCEL_MARKETPLACE_STORES)],
390
+ ['products', JSON.stringify(VERCEL_MARKETPLACE_PRODUCTS)],
385
391
  ['skippable-integrations', '1'],
386
392
  ])
387
393
 
@@ -1068,18 +1074,19 @@ A forum, built on [Meith](${repositoryUrl}), running as Vercel functions.
1068
1074
  Vercel's SDK, which authenticates with the deployment's own OIDC identity, so
1069
1075
  there is no token to copy. This is what used to be four hand-typed \`S3_*\`
1070
1076
  secrets.
1077
+ - **A Resend mail account**, attached the same way, which publishes
1078
+ \`RESEND_API_KEY\`. The board reads that name directly: its mail driver already
1079
+ speaks Resend's request shape, so there is nothing to adapt.
1071
1080
  - **A Vercel project** carrying \`vercel.json\` — the build command
1072
1081
  \`${VERCEL_BUILD_COMMAND}\`,
1073
1082
  which applies the schema before it builds, materializes the board's app at
1074
1083
  the project root so the artefact lands where Vercel reads it, and the cron
1075
1084
  entry that drives the tick.
1076
1085
 
1077
- **Mail is the one thing the button does not set up**, and it takes one click
1078
- after the deploy see *Mail, in one click* below. That is also where the
1079
- address the board sends from goes: it has to be at a domain your provider has
1080
- verified, which cannot be true of any domain before a provider exists. The
1081
- board boots and runs without mail, and delivers nothing, silently, until it is
1082
- done.
1086
+ **Mail needs one thing after the deploy**: the address the board sends from.
1087
+ It has to be at a domain Resend has verified, and no form can ask for that
1088
+ before the account exists see *Mail, after the deploy* below. The board boots
1089
+ and runs without it, and delivers nothing, silently, until it is done.
1083
1090
 
1084
1091
  ## What to type into the deploy form
1085
1092
 
@@ -1130,26 +1137,22 @@ Vercel* below for why that matters — set \`FILESTORE_DRIVER=s3\` and add
1130
1137
  project's environment settings, with \`S3_ENDPOINT\` for a bucket that is not AWS
1131
1138
  (\`S3_REGION=auto\` for R2). The same board runs either way.
1132
1139
 
1133
- ## Mail, in one click
1140
+ ## Mail, after the deploy
1134
1141
 
1135
1142
  A board that cannot send mail cannot reset a password, so do this before you
1136
- invite anybody.
1137
-
1138
- 1. Open your project on Vercel, go to **Storage → Marketplace** (or
1139
- **Integrations**), and add **Resend**. It creates a Resend account linked to
1140
- the project and connects your sending domain.
1141
- 2. Verify that domain in the Resend dashboard if you have not already. Resend
1142
- refuses to send from an address at a domain it has not verified.
1143
- 3. Add \`MAIL_FROM\` to the project's environment settings — an address at that
1144
- verified domain, and the only mail value you ever type. The deploy form does
1145
- not ask for it, because a sender address is not something you can know
1146
- before there is a provider to verify it.
1147
- 4. Redeploy, or let the next push redeploy.
1148
-
1149
- That is all. The integration publishes its key into the project as
1150
- \`RESEND_API_KEY\`, and the board reads that name: with it set, and \`MAIL_FROM\`
1151
- beside it, mail sends over Resend's HTTPS API with nothing further to
1152
- configure.
1143
+ invite anybody. The deploy form already added Resend and published its key; two
1144
+ steps remain, and neither could have been answered on the form.
1145
+
1146
+ 1. Verify your sending domain in the Resend dashboard if you have not already.
1147
+ Resend refuses to send from an address at a domain it has not verified.
1148
+ 2. Add \`MAIL_FROM\` to the project's environment settings an address at that
1149
+ verified domain, and the only mail value you ever type.
1150
+ 3. Redeploy, or let the next push redeploy.
1151
+
1152
+ That is all. The integration published its key into the project as
1153
+ \`RESEND_API_KEY\` when you deployed, and the board reads that name: with it set,
1154
+ and \`MAIL_FROM\` beside it, mail sends over Resend's HTTPS API with nothing
1155
+ further to configure.
1153
1156
 
1154
1157
  The board is not tied to Resend. Its mail driver is a plain JSON-over-HTTPS
1155
1158
  sender that posts \`{from, to, subject, text, html, reply_to}\` with a bearer
@@ -1183,15 +1186,23 @@ drains; nothing here runs it on its own, because there is no worker process on
1183
1186
  a function platform. Two things about it are worth knowing **before** you
1184
1187
  deploy rather than after:
1185
1188
 
1186
- - **A per-minute schedule needs a paid plan.** Hobby allows a couple of cron
1187
- jobs and runs each of them roughly once a day, at an hour Vercel chooses;
1188
- only paid plans accept an arbitrary cron expression. A board ticking daily
1189
- still loses nothing tasks are written so a missed run delays work rather
1190
- than dropping it but "as it happens" notifications become a daily digest in
1191
- all but name. To keep a minute-by-minute tick on Hobby, drive
1192
- \`${TICK_PATH}\` from something else that can call a URL on a schedule — a
1193
- GitHub Actions workflow, a systemd timer, an uptime pinger — presenting
1194
- \`TICK_SECRET\` instead.
1189
+ - **This ships a daily schedule, because Hobby refuses anything faster.** A
1190
+ Hobby plan rejects a cron expression that would run more than once a day
1191
+ the deployment fails outright rather than being slowed down so
1192
+ \`vercel.json\` carries \`${TICK_SCHEDULE}\` and deploys anywhere. On a
1193
+ paid plan, edit it to \`* * * * *\` and the board ticks every minute.
1194
+
1195
+ A daily tick loses nothing permanently: tasks are written so a missed run
1196
+ delays work rather than dropping it, and a password reset is sent as the
1197
+ request is handled rather than waiting for a tick. What it does delay is
1198
+ everything the tick drives — a new post is not findable in search, and a
1199
+ notification is not sent, until the next run.
1200
+
1201
+ **To keep a fast tick without paying**, drive \`${TICK_PATH}\` from anything
1202
+ that can call a URL on a schedule — a GitHub Actions workflow, a systemd
1203
+ timer, an uptime pinger — presenting \`TICK_SECRET\` instead of
1204
+ \`CRON_SECRET\`. The endpoint accepts either, so the Vercel cron and an
1205
+ outside scheduler can both drive it.
1195
1206
  - **\`maxDuration = 300\` is validated when the project builds, not when the
1196
1207
  function runs.** A plan that does not allow 300 seconds therefore **fails the
1197
1208
  deployment** rather than clamping the request. With Fluid Compute — the