@weft-js/create 0.1.0 → 0.1.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.
Files changed (127) hide show
  1. package/README.md +60 -10
  2. package/bin/create-weft.mjs +62 -14
  3. package/package.json +26 -16
  4. package/scripts/tarballs.mjs +56 -6
  5. package/src/data.mjs +90 -18
  6. package/src/index.mjs +210 -51
  7. package/src/stack/pages.mjs +151 -0
  8. package/src/stack/schema.mjs +195 -0
  9. package/src/stack.mjs +102 -313
  10. package/src/templates.mjs +174 -0
  11. package/src/wizard.mjs +65 -16
  12. package/templates/admin/files/drizzle/.schema-hash +1 -0
  13. package/templates/admin/files/drizzle/0000_admin.sql +136 -0
  14. package/templates/admin/files/drizzle/meta/0000_snapshot.json +771 -0
  15. package/templates/admin/files/drizzle/meta/_journal.json +13 -0
  16. package/templates/admin/files/public/favicon.svg +1 -0
  17. package/templates/admin/files/src/auth.server.ts +24 -0
  18. package/templates/admin/files/src/components/AuthPage.tsx +33 -0
  19. package/templates/admin/files/src/components/Failed.tsx +19 -0
  20. package/templates/admin/files/src/components/Logo.tsx +16 -0
  21. package/templates/admin/files/src/components/OrderDetail.tsx +93 -0
  22. package/templates/admin/files/src/components/OrdersFilters.tsx +44 -0
  23. package/templates/admin/files/src/components/OrdersTable.tsx +144 -0
  24. package/templates/admin/files/src/components/Overview.tsx +113 -0
  25. package/templates/admin/files/src/components/StatusBadge.tsx +12 -0
  26. package/templates/admin/files/src/db/schema.ts +68 -0
  27. package/templates/admin/files/src/lib/orders.ts +60 -0
  28. package/templates/admin/files/src/routes/404.tsx +20 -0
  29. package/templates/admin/files/src/routes/_error.tsx +20 -0
  30. package/templates/admin/files/src/routes/_layout.tsx +6 -0
  31. package/templates/admin/files/src/routes/admin/_layout.tsx +89 -0
  32. package/templates/admin/files/src/routes/admin/index.tsx +45 -0
  33. package/templates/admin/files/src/routes/admin/nested/_layout.tsx +35 -0
  34. package/templates/admin/files/src/routes/admin/nested/index.tsx +77 -0
  35. package/templates/admin/files/src/routes/admin/orders/[id].tsx +24 -0
  36. package/templates/admin/files/src/routes/admin/orders/index.tsx +28 -0
  37. package/templates/admin/files/src/routes/admin/per-row.tsx +81 -0
  38. package/templates/admin/files/src/routes/admin/slow.tsx +74 -0
  39. package/templates/admin/files/src/routes/index.tsx +13 -0
  40. package/templates/admin/files/src/routes/login.tsx +53 -0
  41. package/templates/admin/files/src/routes/signup.tsx +60 -0
  42. package/templates/admin/files/src/server/orders.ts +193 -0
  43. package/templates/admin/files/src/server/reports.ts +92 -0
  44. package/templates/admin/files/src/server/session.ts +42 -0
  45. package/templates/admin/files/src/theme.css +43 -0
  46. package/templates/admin/files/weft.config.ts +31 -0
  47. package/templates/admin/template.json +22 -0
  48. package/templates/catalog/files/drizzle/.schema-hash +1 -0
  49. package/templates/catalog/files/drizzle/0000_catalog.sql +47 -0
  50. package/templates/catalog/files/drizzle/meta/0000_snapshot.json +263 -0
  51. package/templates/catalog/files/drizzle/meta/_journal.json +13 -0
  52. package/templates/catalog/files/public/favicon.svg +1 -0
  53. package/templates/catalog/files/seed.sql +13 -0
  54. package/templates/catalog/files/src/components/AddToCart.tsx +22 -0
  55. package/templates/catalog/files/src/components/Aisles.tsx +31 -0
  56. package/templates/catalog/files/src/components/CartLines.tsx +96 -0
  57. package/templates/catalog/files/src/components/CartLink.tsx +29 -0
  58. package/templates/catalog/files/src/components/Failed.tsx +19 -0
  59. package/templates/catalog/files/src/components/Featured.tsx +19 -0
  60. package/templates/catalog/files/src/components/Filters.tsx +40 -0
  61. package/templates/catalog/files/src/components/Logo.tsx +16 -0
  62. package/templates/catalog/files/src/components/ProductArt.tsx +42 -0
  63. package/templates/catalog/files/src/components/ProductCard.tsx +42 -0
  64. package/templates/catalog/files/src/components/ProductList.tsx +65 -0
  65. package/templates/catalog/files/src/components/Related.tsx +19 -0
  66. package/templates/catalog/files/src/components/SiteFooter.tsx +9 -0
  67. package/templates/catalog/files/src/components/SiteHeader.tsx +16 -0
  68. package/templates/catalog/files/src/db/schema.ts +53 -0
  69. package/templates/catalog/files/src/lib/catalog.ts +35 -0
  70. package/templates/catalog/files/src/routes/404.tsx +18 -0
  71. package/templates/catalog/files/src/routes/_error.tsx +18 -0
  72. package/templates/catalog/files/src/routes/_layout.tsx +14 -0
  73. package/templates/catalog/files/src/routes/c/[slug].tsx +53 -0
  74. package/templates/catalog/files/src/routes/cart.tsx +29 -0
  75. package/templates/catalog/files/src/routes/index.tsx +84 -0
  76. package/templates/catalog/files/src/routes/p/[slug].tsx +67 -0
  77. package/templates/catalog/files/src/server/cart.ts +125 -0
  78. package/templates/catalog/files/src/server/catalog.ts +107 -0
  79. package/templates/catalog/files/src/theme.css +159 -0
  80. package/templates/catalog/files/weft.config.ts +30 -0
  81. package/templates/catalog/template.json +20 -0
  82. package/templates/landing/files/.weft/plugins.lock.json +42 -0
  83. package/templates/landing/files/assets/fonts/LICENSE-figtree.txt +93 -0
  84. package/templates/landing/files/assets/fonts/figtree.woff2 +0 -0
  85. package/templates/landing/files/assets/hero.webp +0 -0
  86. package/templates/landing/files/drizzle/.schema-hash +1 -0
  87. package/templates/landing/files/drizzle/0000_landing.sql +6 -0
  88. package/templates/landing/files/drizzle/meta/0000_snapshot.json +60 -0
  89. package/templates/landing/files/drizzle/meta/_journal.json +13 -0
  90. package/templates/landing/files/public/favicon.svg +1 -0
  91. package/templates/landing/files/src/components/SiteFooter.tsx +24 -0
  92. package/templates/landing/files/src/components/SiteHeader.tsx +23 -0
  93. package/templates/landing/files/src/components/WaitlistForm.tsx +36 -0
  94. package/templates/landing/files/src/db/schema.ts +7 -0
  95. package/templates/landing/files/src/routes/404.tsx +24 -0
  96. package/templates/landing/files/src/routes/_error.tsx +24 -0
  97. package/templates/landing/files/src/routes/_layout.tsx +14 -0
  98. package/templates/landing/files/src/routes/index.tsx +130 -0
  99. package/templates/landing/files/src/routes/joined.tsx +30 -0
  100. package/templates/landing/files/src/routes/pricing.tsx +131 -0
  101. package/templates/landing/files/src/routes/privacy.tsx +56 -0
  102. package/templates/landing/files/src/server/waitlist.ts +13 -0
  103. package/templates/landing/files/src/theme.css +108 -0
  104. package/templates/landing/files/weft.config.ts +44 -0
  105. package/templates/landing/template.json +17 -0
  106. package/templates/todo/files/drizzle/.schema-hash +1 -0
  107. package/templates/todo/files/drizzle/0000_todo.sql +82 -0
  108. package/templates/todo/files/drizzle/meta/0000_snapshot.json +462 -0
  109. package/templates/todo/files/drizzle/meta/_journal.json +13 -0
  110. package/templates/todo/files/public/favicon.svg +1 -0
  111. package/templates/todo/files/src/components/AuthPage.tsx +33 -0
  112. package/templates/todo/files/src/components/Logo.tsx +14 -0
  113. package/templates/todo/files/src/components/NewTodo.tsx +27 -0
  114. package/templates/todo/files/src/components/TodoList.tsx +101 -0
  115. package/templates/todo/files/src/db/schema.ts +18 -0
  116. package/templates/todo/files/src/lib/views.ts +13 -0
  117. package/templates/todo/files/src/routes/404.tsx +20 -0
  118. package/templates/todo/files/src/routes/_error.tsx +20 -0
  119. package/templates/todo/files/src/routes/_layout.tsx +6 -0
  120. package/templates/todo/files/src/routes/index.tsx +13 -0
  121. package/templates/todo/files/src/routes/login.tsx +53 -0
  122. package/templates/todo/files/src/routes/signup.tsx +57 -0
  123. package/templates/todo/files/src/routes/todos.tsx +87 -0
  124. package/templates/todo/files/src/server/todos.ts +98 -0
  125. package/templates/todo/files/src/theme.css +71 -0
  126. package/templates/todo/files/weft.config.ts +30 -0
  127. package/templates/todo/template.json +18 -0
package/README.md CHANGED
@@ -1,28 +1,78 @@
1
1
  # @weft-js/create
2
2
 
3
- > **Early release (0.1.0).** APIs will change between minor versions.
3
+ > **Early release (0.1.x).** APIs can change between minor versions.
4
4
 
5
- Starts a new weft app.
5
+ Starts a new [Weft](https://github.com/Ravonus/weft) app: pages, a server, a database, sign-in and Warp's components,
6
+ wired up and ready for `weft dev`.
6
7
 
7
8
  ```bash
8
9
  npm create @weft-js my-app
9
10
  ```
10
11
 
11
- In a terminal it asks what the flags don't answer. `--yes` takes the defaults.
12
+ ```bash
13
+ pnpm create @weft-js my-app
14
+ ```
15
+
16
+ ```bash
17
+ bun create @weft-js my-app
18
+ ```
19
+
20
+ ```bash
21
+ deno run -A npm:@weft-js/create my-app
22
+ ```
23
+
24
+ In a terminal it asks what the flags don't answer, with the default already picked. `--yes` takes every default: Warp,
25
+ SQLite through Drizzle, and Better Auth. Then:
26
+
27
+ ```bash
28
+ cd my-app
29
+ npx weft dev
30
+ ```
31
+
32
+ With a database, run `npx weft db generate` first. The new app has a home page, an about page, a todo list on server
33
+ functions, a sign-in page, and an `AGENTS.md` written for the installed Weft. `weft check` passes on it as made; its one
34
+ warning asks for `site.url` before you deploy.
35
+
36
+ ## Flags
12
37
 
13
38
  | Flag | Values | Default |
14
39
  |---|---|---|
15
- | `--ui` | `warp`, `headless` (warp-core only), `tailwind`, `none` | `warp` |
40
+ | `[dir]` | A folder that's missing or empty | asked; `my-weft-app` with `--yes` |
41
+ | `--template` | `todo`, `admin`, `catalog`, `landing`: a fuller starting app (below) | none |
42
+ | `--ui` | `warp`, `headless` (Warp's behaviour and tokens, your markup), `tailwind`, `none` | `warp` |
16
43
  | `--db` | `sqlite`, `postgres`, `mysql`, `none` | `sqlite` |
17
- | `--layer` | `drizzle`, `prisma`, `native` | `drizzle` |
44
+ | `--layer` | `drizzle`, `prisma`, `native` (SQL migrations, no ORM) | `drizzle` |
18
45
  | `--auth`, `--no-auth` | Sign-in, when there's a database | on |
19
- | `--auth-provider` | `better-auth`, `custom` | `better-auth` |
20
- | `--runtime` | `node`, `bun`, `deno` | the one running it |
21
- | `--config` | `ts`, `json`, `yaml`: the format of the weft config | `ts` |
46
+ | `--auth-provider` | `better-auth`, `custom` (a stub for your own) | `better-auth` |
47
+ | `--runtime` | `node`, `bun`, `deno`: what `weft build` targets | the one running it |
48
+ | `--config` | `ts`, `json`, `yaml`: the format of the Weft config | `ts` |
22
49
  | `--pm` | `npm`, `pnpm`, `bun`, `deno`, `yarn` | the one you ran it with |
23
- | `--git`, `--no-git`, `--install`, `--no-install` | Start a git repository; install dependencies | on, or off with no terminal and no `--yes` |
50
+ | `--git`, `--no-git` | Start a git repository | on |
51
+ | `--install`, `--no-install` | Install the dependencies | on, or off with no terminal and no `--yes` |
24
52
  | `--name` | The package name | the folder's name |
53
+ | `--yes`, `-y` | Take the defaults for anything not given | |
25
54
 
26
55
  With npm, flags go after `--`: `npm create @weft-js my-app -- --ui tailwind --db none`.
27
56
 
28
- Until weft is on npm, make an app from a checkout instead: [Installation](../../site/content/learn/installation.md).
57
+ ## Templates
58
+
59
+ A template is a fuller app on Postgres. It sets `--db postgres` and the sign-in it needs, and keeps the other flags.
60
+
61
+ | Template | Pages |
62
+ |---|---|
63
+ | `todo` | Sign-in and sign-up, and a todo list |
64
+ | `admin` | An admin area with an orders table, order pages and nested layouts, behind sign-in |
65
+ | `catalog` | A shop: categories, product pages and a cart |
66
+ | `landing` | A landing page with pricing, a privacy page and a sign-up list |
67
+
68
+ ## Docs
69
+
70
+ [Installation](https://github.com/Ravonus/weft/blob/main/site/content/learn/installation.md) walks through every file in
71
+ a new app, and how to run, check, build and deploy it.
72
+
73
+ ## Links
74
+
75
+ - [Source](https://github.com/Ravonus/weft/tree/main/packages/create)
76
+ - [Weft on GitHub](https://github.com/Ravonus/weft): the framework, the docs and every package
77
+ - [Changelog](https://github.com/Ravonus/weft/blob/main/CHANGELOG.md)
78
+ - [Issues](https://github.com/Ravonus/weft/issues)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // create-weft [dir] [--ui warp|headless|tailwind|none] [--db sqlite|postgres|mysql|none] [--layer drizzle|prisma|native] [--[no-]auth] [--auth-provider better-auth|custom] [--runtime node|bun|deno]
2
+ // create-weft [dir] [--template landing|todo|admin|catalog] [--ui warp|headless|tailwind|none] [--db sqlite|postgres|mysql|none] [--layer drizzle|prisma|native] [--[no-]auth] [--auth-provider better-auth|custom] [--runtime node|bun|deno]
3
3
  // [--config ts|json|yaml] [--pm npm|pnpm|bun|deno|yarn] [--[no-]git] [--[no-]install] [--name <name>] [--tarballs <dir>] [--yes]
4
4
  // In a terminal it asks what the flags don't answer; --yes, or no terminal, takes the defaults.
5
5
  import { execFileSync } from 'node:child_process'
@@ -7,16 +7,42 @@ import { readFileSync, writeFileSync } from 'node:fs'
7
7
  import { join, resolve } from 'node:path'
8
8
  import { createInterface } from 'node:readline/promises'
9
9
  import { parseArgs } from 'node:util'
10
- import { CONFIGS, DB, LAYERS, RUNTIMES, scaffold, UI } from '../src/index.mjs'
10
+ import {
11
+ CONFIGS,
12
+ DB,
13
+ LAYERS,
14
+ RUNTIMES,
15
+ scaffold,
16
+ TEMPLATES,
17
+ UI,
18
+ withTemplate,
19
+ } from '../src/index.mjs'
11
20
  import { resolveChoices } from '../src/wizard.mjs'
12
- import { pin } from '../scripts/tarballs.mjs'
21
+ import { pin, pinWorkspace } from '../scripts/tarballs.mjs'
13
22
 
14
- const usage = `Usage: npm create @weft-js -- [dir] [--ui ${UI.join('|')}] [--db ${DB.join('|')}] [--layer ${LAYERS.join('|')}] [--[no-]auth] [--auth-provider better-auth|custom] [--runtime ${RUNTIMES.join('|')}] [--config ${CONFIGS.join('|')}] [--pm <manager>] [--[no-]git] [--[no-]install] [--name <name>] [--tarballs <dir>] [--yes]`
15
- const string = { type: 'string' }, boolean = { type: 'boolean' }
23
+ const usage = `Usage: npm create @weft-js -- [dir] [--template ${TEMPLATES.join('|')}] [--ui ${UI.join('|')}] [--db ${DB.join('|')}] [--layer ${LAYERS.join('|')}] [--[no-]auth] [--auth-provider better-auth|custom] [--runtime ${RUNTIMES.join('|')}] [--config ${CONFIGS.join('|')}] [--pm <manager>] [--[no-]git] [--[no-]install] [--name <name>] [--tarballs <dir>] [--yes]`
24
+ const string = { type: 'string' },
25
+ boolean = { type: 'boolean' }
16
26
  const { values, positionals } = parseArgs({
17
27
  allowPositionals: true,
18
28
  allowNegative: true,
19
- options: { ui: string, db: string, layer: string, auth: boolean, 'auth-provider': string, runtime: string, config: string, pm: string, git: boolean, install: boolean, name: string, tarballs: string, yes: { ...boolean, short: 'y' }, help: { ...boolean, short: 'h' } },
29
+ options: {
30
+ template: string,
31
+ ui: string,
32
+ db: string,
33
+ layer: string,
34
+ auth: boolean,
35
+ 'auth-provider': string,
36
+ runtime: string,
37
+ config: string,
38
+ pm: string,
39
+ git: boolean,
40
+ install: boolean,
41
+ name: string,
42
+ tarballs: string,
43
+ yes: { ...boolean, short: 'y' },
44
+ help: { ...boolean, short: 'h' },
45
+ },
20
46
  })
21
47
  const interactive = process.stdin.isTTY && process.stdout.isTTY
22
48
  if (values.help || positionals.length > 1 || (!positionals.length && !interactive)) {
@@ -33,40 +59,62 @@ async function directory() {
33
59
  return answer.trim() || 'my-weft-app'
34
60
  }
35
61
 
36
- /** Git and the install are conveniences: the app is already written, so a failure here only leaves a step for later. */
62
+ /** The app is already written, so a failure here leaves a step for later; a failed install still fails the command. */
37
63
  function step(what, command, args, cwd) {
38
64
  try {
39
- execFileSync(command, args, { cwd, stdio: ['ignore', 'ignore', 'inherit'], shell: process.platform === 'win32' }) // npm is npm.cmd there
65
+ execFileSync(command, args, {
66
+ cwd,
67
+ stdio: ['ignore', 'ignore', 'inherit'],
68
+ shell: process.platform === 'win32',
69
+ }) // npm is npm.cmd there
40
70
  return true
41
71
  } catch {
42
- console.error(`create-weft: ${what} didn't finish; run \`${[command, ...args].join(' ')}\` yourself.`)
72
+ console.error(
73
+ `create-weft: ${what} didn't finish; run \`${[command, ...args].join(' ')}\` yourself.`,
74
+ )
43
75
  return false
44
76
  }
45
77
  }
46
78
 
47
79
  try {
48
80
  const { yes, help: _help, name, tarballs, 'auth-provider': authProvider, ...flags } = values
49
- if (authProvider && !['better-auth', 'custom'].includes(authProvider)) throw new Error('--auth-provider must be better-auth or custom')
81
+ if (authProvider && !['better-auth', 'custom'].includes(authProvider))
82
+ throw new Error('--auth-provider must be better-auth or custom')
50
83
  if (authProvider) flags.auth = authProvider === 'custom' ? 'custom' : true
51
84
  const where = await directory()
52
85
  const dir = resolve(where)
53
- const choices = await resolveChoices(flags, { interactive, yes, io: { input: process.stdin, output: process.stdout } })
86
+ const choices = await resolveChoices(withTemplate(flags), {
87
+ interactive,
88
+ yes,
89
+ io: { input: process.stdin, output: process.stdout },
90
+ })
54
91
  const { files } = scaffold(dir, { ...choices, ...(name && { name }) })
55
92
  // Before the install: a checkout's packages aren't on npm, so `local.mjs new` needs them wired in from the start.
56
93
  if (tarballs) {
57
94
  const file = join(dir, 'package.json')
58
- writeFileSync(file, JSON.stringify(pin(JSON.parse(readFileSync(file, 'utf8')), JSON.parse(readFileSync(join(tarballs, 'tarballs.json'), 'utf8'))), null, 2))
95
+ const map = JSON.parse(readFileSync(join(tarballs, 'tarballs.json'), 'utf8'))
96
+ writeFileSync(file, JSON.stringify(pin(JSON.parse(readFileSync(file, 'utf8')), map), null, 2))
97
+ pinWorkspace(dir, map)
59
98
  }
60
99
  console.log(`\nCreated ${files.length} files in ${dir}`)
61
100
  if (choices.git) step('git init', 'git', ['init', '--quiet'], dir)
62
101
  const installed = choices.install && step('The install', choices.pm, ['install'], dir)
63
- const weft = { npm: 'npx weft', pnpm: 'pnpm exec weft', bun: 'bunx --bun weft', deno: 'deno run -A npm:@weft-js/cli', yarn: 'yarn weft' }[choices.pm]
102
+ const weft = {
103
+ npm: 'npx weft',
104
+ pnpm: 'pnpm exec weft',
105
+ bun: 'bunx --bun weft',
106
+ deno: 'deno run -A npm:@weft-js/cli',
107
+ yarn: 'yarn weft',
108
+ }[choices.pm]
64
109
  // AGENTS.md comes from the weft just installed, so it always matches the compiler the app runs.
65
110
  if (installed) {
66
111
  const [command, ...args] = weft.split(' ')
67
112
  step('AGENTS.md', command, [...args, 'agents-md'], dir)
68
113
  }
69
- console.log(`\n cd ${where}${installed ? '' : `\n ${choices.pm} install\n ${weft} agents-md`}${choices.db === 'none' ? '' : `\n ${weft} db generate`}\n ${weft} dev\n`)
114
+ console.log(
115
+ `\n cd ${where}${installed ? '' : `\n ${choices.pm} install\n ${weft} agents-md`}${choices.db === 'none' || choices.template ? '' : `\n ${weft} db generate`}\n ${weft} dev\n`,
116
+ )
117
+ if (choices.install && !installed) process.exitCode = 1
70
118
  } catch (e) {
71
119
  console.error(`create-weft: ${e.message}`)
72
120
  process.exit(1)
package/package.json CHANGED
@@ -1,30 +1,40 @@
1
1
  {
2
2
  "name": "@weft-js/create",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Scaffold a weft app: npm create @weft-js",
5
+ "keywords": [
6
+ "weft"
7
+ ],
5
8
  "license": "MIT",
6
- "type": "module",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Ravonus/weft.git",
12
+ "directory": "packages/create"
13
+ },
14
+ "homepage": "https://github.com/Ravonus/weft/tree/main/packages/create#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/Ravonus/weft/issues"
17
+ },
7
18
  "bin": {
8
19
  "create-weft": "./bin/create-weft.mjs"
9
20
  },
21
+ "files": [
22
+ "bin",
23
+ "src",
24
+ "templates",
25
+ "scripts/tarballs.mjs",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "type": "module",
10
30
  "exports": {
11
31
  ".": "./src/index.mjs",
12
32
  "./stack": "./src/stack.mjs"
13
33
  },
14
- "engines": {
15
- "node": ">=26"
16
- },
17
34
  "publishConfig": {
18
35
  "access": "public"
19
36
  },
20
- "keywords": [
21
- "weft"
22
- ],
23
- "files": [
24
- "bin",
25
- "src",
26
- "scripts/tarballs.mjs",
27
- "README.md",
28
- "LICENSE"
29
- ]
30
- }
37
+ "engines": {
38
+ "node": ">=26"
39
+ }
40
+ }
@@ -1,11 +1,44 @@
1
1
  // Every package as a tarball, and an app's package.json pointed at them: what the packed gates and `local.mjs` share.
2
2
  import { execFileSync } from 'node:child_process'
3
- import { existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs'
4
- import { join } from 'node:path'
3
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
4
+ import { join, resolve } from 'node:path'
5
5
  import { fileURLToPath } from 'node:url'
6
6
 
7
7
  export const pkgs = fileURLToPath(new URL('../../', import.meta.url))
8
8
 
9
+ /**
10
+ * Write the install policy and tarball overrides for a fresh pnpm workspace. weft's own dependency patches stay
11
+ * behind: their files live in this repo, not in the app.
12
+ */
13
+ export function writePackedPolicy(dir, tarballs = {}, packages = []) {
14
+ const policy = readFileSync(new URL('../../../pnpm-workspace.yaml', import.meta.url), 'utf8')
15
+ .replace(/^packages:\n(?:[ \t].*\n)*/m, '')
16
+ .replace(/^(?:#.*\n)*patchedDependencies:\n(?:[ \t].*\n)*/m, '')
17
+ const overrides = Object.entries(tarballs)
18
+ .map(([name, spec]) => ` ${JSON.stringify(name)}: ${JSON.stringify(spec)}\n`)
19
+ .join('')
20
+ const merged = /^overrides:\n/m.test(policy)
21
+ ? policy.replace(/^overrides:\n/m, `overrides:\n${overrides}`)
22
+ : `${policy}${overrides ? `\noverrides:\n${overrides}` : ''}`
23
+ writeFileSync(
24
+ join(dir, 'pnpm-workspace.yaml'),
25
+ `${packages.length ? `packages:\n${packages.map((name) => ` - ${JSON.stringify(name)}\n`).join('')}` : ''}${merged}\n`,
26
+ )
27
+ }
28
+
29
+ /** Pack one package to a known path, independent of lifecycle output. */
30
+ export function pack(dir, dest) {
31
+ const { name, version } = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'))
32
+ const file = join(resolve(dest), `${name.replace(/^@/, '').replaceAll('/', '-')}-${version}.tgz`)
33
+ execFileSync('pnpm', ['pack', '--out', file], {
34
+ cwd: dir,
35
+ stdio: ['ignore', 'pipe', 'inherit'],
36
+ shell: process.platform === 'win32',
37
+ })
38
+ if (!existsSync(file)) throw new Error(`pnpm pack did not create ${file}`)
39
+ return `file:${file}`
40
+ }
41
+
9
42
  /** Pack each package into `dest`. Returns `{ name: 'file:…tgz' }`. */
10
43
  export function packAll(dest) {
11
44
  mkdirSync(dest, { recursive: true })
@@ -14,14 +47,31 @@ export function packAll(dest) {
14
47
  const file = join(pkgs, dir, 'package.json')
15
48
  if (!existsSync(file)) continue
16
49
  const { name } = JSON.parse(readFileSync(file, 'utf8'))
17
- const packed = execFileSync('npm', ['pack', '--silent', '--pack-destination', dest], { cwd: join(pkgs, dir), encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] })
18
- tarballs[name] = `file:${join(dest, packed.trim().split('\n').pop())}`
50
+ tarballs[name] = pack(join(pkgs, dir), dest)
19
51
  }
20
52
  return tarballs
21
53
  }
22
54
 
23
55
  /** `pkg` with its weft dependencies on the tarballs. The overrides reach the ones packages declare for each other. */
56
+ /**
57
+ * The same overrides in the app's own pnpm-workspace.yaml, when create wrote one: that file makes the app its own
58
+ * workspace root, and pnpm reads overrides only from there (not from package.json, nor from a workspace above it).
59
+ */
60
+ export function pinWorkspace(dir, tarballs) {
61
+ const file = join(dir, 'pnpm-workspace.yaml')
62
+ if (!existsSync(file) || !Object.keys(tarballs).length) return
63
+ const lines = Object.entries(tarballs)
64
+ .map(([name, spec]) => ` ${JSON.stringify(name)}: ${JSON.stringify(spec)}\n`)
65
+ .join('')
66
+ writeFileSync(file, `${readFileSync(file, 'utf8')}overrides:\n${lines}`)
67
+ }
68
+
24
69
  export function pin(pkg, tarballs) {
25
- for (const deps of [pkg.dependencies, pkg.devDependencies]) for (const name in deps) if (tarballs[name]) deps[name] = tarballs[name]
26
- return { ...pkg, overrides: { ...pkg.overrides, ...tarballs } }
70
+ for (const deps of [pkg.dependencies, pkg.devDependencies])
71
+ for (const name in deps) if (tarballs[name]) deps[name] = tarballs[name]
72
+ return {
73
+ ...pkg,
74
+ overrides: { ...pkg.overrides, ...tarballs },
75
+ pnpm: { ...pkg.pnpm, overrides: { ...pkg.pnpm?.overrides, ...tarballs } },
76
+ }
27
77
  }
package/src/data.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // The Native and Prisma data layers of a scaffold (research 34 §4), beside Drizzle's in stack.mjs. Each writes the same
1
+ // The Native and Prisma data layers of a scaffold, beside Drizzle's in stack.mjs. Each writes the same
2
2
  // todo app, so the same e2e holds for every layer. `weft add db` writes these files too.
3
3
 
4
4
  const engineSql = {
@@ -9,23 +9,71 @@ const engineSql = {
9
9
 
10
10
  // Better Auth's tables under its default names, which are camelCase, so they're quoted. MySQL can't key a TEXT column.
11
11
  export function nativeAuthSql(db) {
12
- const q = name => (db === 'mysql' ? `\`${name}\`` : `"${name}"`)
12
+ const q = (name) => (db === 'mysql' ? `\`${name}\`` : `"${name}"`)
13
13
  const key = 'varchar(255)'
14
14
  const text = 'text'
15
15
  const when = { sqlite: 'date', postgres: 'timestamptz', mysql: 'datetime(3)' }[db]
16
16
  const bool = { sqlite: 'integer', postgres: 'boolean', mysql: 'boolean' }[db]
17
- const table = (name, columns) => `create table ${q(name)} (\n${columns.map(([c, type]) => ` ${q(c)} ${type}`).join(',\n')}\n);\n`
18
- const ref = to => `references ${q(to)} (${q('id')}) on delete cascade`
19
- const stamps = [['createdAt', `${when} not null`], ['updatedAt', `${when} not null`]]
17
+ const table = (name, columns) =>
18
+ `create table ${q(name)} (\n${columns.map(([c, type]) => ` ${q(c)} ${type}`).join(',\n')}\n);\n`
19
+ const ref = (to) => `references ${q(to)} (${q('id')}) on delete cascade`
20
+ const stamps = [
21
+ ['createdAt', `${when} not null`],
22
+ ['updatedAt', `${when} not null`],
23
+ ]
20
24
  return [
21
- table('user', [['id', `${key} primary key`], ['name', `${text} not null`], ['email', `${key} not null unique`], ['emailVerified', `${bool} not null`], ['image', text], ...stamps]),
22
- table('session', [['id', `${key} primary key`], ['expiresAt', `${when} not null`], ['token', `${key} not null unique`], ...stamps, ['ipAddress', text], ['userAgent', text], ['userId', `${key} not null ${ref('user')}`]]),
23
- table('account', [['id', `${key} primary key`], ['accountId', `${text} not null`], ['providerId', `${text} not null`], ['userId', `${key} not null ${ref('user')}`], ['accessToken', text], ['refreshToken', text], ['idToken', text], ['accessTokenExpiresAt', when], ['refreshTokenExpiresAt', when], ['scope', text], ['password', text], ...stamps]),
24
- table('verification', [['id', `${key} primary key`], ['identifier', `${text} not null`], ['value', `${text} not null`], ['expiresAt', `${when} not null`], ['createdAt', when], ['updatedAt', when]]),
25
+ table('user', [
26
+ ['id', `${key} primary key`],
27
+ ['name', `${text} not null`],
28
+ ['email', `${key} not null unique`],
29
+ ['emailVerified', `${bool} not null`],
30
+ ['image', text],
31
+ ...stamps,
32
+ ]),
33
+ table('session', [
34
+ ['id', `${key} primary key`],
35
+ ['expiresAt', `${when} not null`],
36
+ ['token', `${key} not null unique`],
37
+ ...stamps,
38
+ ['ipAddress', text],
39
+ ['userAgent', text],
40
+ ['userId', `${key} not null ${ref('user')}`],
41
+ ]),
42
+ table('account', [
43
+ ['id', `${key} primary key`],
44
+ ['accountId', `${text} not null`],
45
+ ['providerId', `${text} not null`],
46
+ ['userId', `${key} not null ${ref('user')}`],
47
+ ['accessToken', text],
48
+ ['refreshToken', text],
49
+ ['idToken', text],
50
+ ['accessTokenExpiresAt', when],
51
+ ['refreshTokenExpiresAt', when],
52
+ ['scope', text],
53
+ ['password', text],
54
+ ...stamps,
55
+ ]),
56
+ table('verification', [
57
+ ['id', `${key} primary key`],
58
+ ['identifier', `${text} not null`],
59
+ ['value', `${text} not null`],
60
+ ['expiresAt', `${when} not null`],
61
+ ['createdAt', when],
62
+ ['updatedAt', when],
63
+ ]),
64
+ table('jwks', [
65
+ ['id', `${key} primary key`],
66
+ ['publicKey', `${text} not null`],
67
+ ['privateKey', `${text} not null`],
68
+ ['createdAt', `${when} not null`],
69
+ ['expiresAt', when],
70
+ ['alg', text],
71
+ ['crv', text],
72
+ ]),
25
73
  ].join('\n')
26
74
  }
27
75
 
28
- const nativeTodos = auth => {
76
+ const nativeTodos = (auth) => {
29
77
  const use = auth ? `\n auth: 'user',` : ''
30
78
  const mine = auth ? ` and user_id = \${ctx.user.id}` : ''
31
79
  return `import { mutation, query } from '@weft-js/start'
@@ -103,13 +151,21 @@ export default createAuth({ db, secret: env.BETTER_AUTH_SECRET, options: { datab
103
151
 
104
152
  const PROVIDER = { sqlite: 'sqlite', postgres: 'postgresql', mysql: 'mysql' }
105
153
  const ADAPTER = {
106
- sqlite: { pkg: '@prisma/adapter-libsql', name: 'PrismaLibSql', make: "{ url: env.DATABASE_URL }" },
107
- postgres: { pkg: '@prisma/adapter-pg', name: 'PrismaPg', make: "{ connectionString: env.DATABASE_URL }" },
154
+ sqlite: {
155
+ pkg: '@prisma/adapter-libsql',
156
+ name: 'PrismaLibSql',
157
+ make: '{ url: env.DATABASE_URL }',
158
+ },
159
+ postgres: {
160
+ pkg: '@prisma/adapter-pg',
161
+ name: 'PrismaPg',
162
+ make: '{ connectionString: env.DATABASE_URL }',
163
+ },
108
164
  mysql: { pkg: '@prisma/adapter-mariadb', name: 'PrismaMariaDb', make: 'env.DATABASE_URL' },
109
165
  }
110
- export const prismaAdapterPackage = db => ADAPTER[db].pkg
166
+ export const prismaAdapterPackage = (db) => ADAPTER[db].pkg
111
167
 
112
- const authModels = db => {
168
+ const authModels = (db) => {
113
169
  const long = db === 'mysql' ? ' @db.Text' : ''
114
170
  return `
115
171
  // Better Auth's tables: it reads and writes them through Prisma, with these names.
@@ -171,12 +227,24 @@ model Verification {
171
227
 
172
228
  @@map("verification")
173
229
  }
230
+
231
+ model Jwks {
232
+ id String @id
233
+ publicKey String${long} @map("public_key")
234
+ privateKey String${long} @map("private_key")
235
+ createdAt DateTime @map("created_at")
236
+ expiresAt DateTime? @map("expires_at")
237
+ alg String?
238
+ crv String?
239
+
240
+ @@map("jwks")
241
+ }
174
242
  `
175
243
  }
176
244
 
177
- export const prismaAuthModels = db => authModels(db).replace(' todos Todo[]\n', '')
245
+ export const prismaAuthModels = (db) => authModels(db).replace(' todos Todo[]\n', '')
178
246
 
179
- const prismaTodos = auth => {
247
+ const prismaTodos = (auth) => {
180
248
  const use = auth ? `\n auth: 'user',` : ''
181
249
  const mine = auth ? `, userId: ctx.user.id` : ''
182
250
  return `import { mutation, query } from '@weft-js/start'
@@ -238,9 +306,13 @@ model Todo {
238
306
  id String @id
239
307
  title String
240
308
  done Boolean @default(false)
241
- createdAt DateTime @default(now()) @map("created_at")${auth ? `
309
+ createdAt DateTime @default(now()) @map("created_at")${
310
+ auth
311
+ ? `
242
312
  userId String @map("user_id")
243
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)` : ''}
313
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)`
314
+ : ''
315
+ }
244
316
 
245
317
  @@map("todos")
246
318
  }