bedrock-flows 0.7.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.
Files changed (85) hide show
  1. package/auth-schema.sql +8 -0
  2. package/bin/bedrock-flows.mjs +127 -0
  3. package/lib/setup.mjs +262 -0
  4. package/package.json +11 -0
  5. package/template/.storybook/main.js +46 -0
  6. package/template/.storybook/manager-head.html +963 -0
  7. package/template/.storybook/preview-head.html +35 -0
  8. package/template/.storybook/preview.js +23 -0
  9. package/template/CHANGELOG.md +236 -0
  10. package/template/README.md +26 -0
  11. package/template/apps/dashboard/index.html +15 -0
  12. package/template/apps/dashboard/package.json +22 -0
  13. package/template/apps/dashboard/src/App.module.css +1318 -0
  14. package/template/apps/dashboard/src/App.tsx +2716 -0
  15. package/template/apps/dashboard/src/auth-client.ts +17 -0
  16. package/template/apps/dashboard/src/changelog.tsx +92 -0
  17. package/template/apps/dashboard/src/index.css +86 -0
  18. package/template/apps/dashboard/src/main.tsx +15 -0
  19. package/template/apps/dashboard/src/theme.ts +31 -0
  20. package/template/apps/dashboard/src/vite-env.d.ts +4 -0
  21. package/template/apps/dashboard/vite.config.ts +48 -0
  22. package/template/apps/worker/.dev.vars.example +50 -0
  23. package/template/apps/worker/package.json +19 -0
  24. package/template/apps/worker/src/index.ts +295 -0
  25. package/template/apps/worker/tsconfig.json +11 -0
  26. package/template/apps/worker/wrangler.jsonc +29 -0
  27. package/template/bedrock.config.ts +16 -0
  28. package/template/design-system/README.md +97 -0
  29. package/template/design-system/starter-v1/components/button/component.css +42 -0
  30. package/template/design-system/starter-v1/components/button/danger.html +21 -0
  31. package/template/design-system/starter-v1/components/button/default.html +21 -0
  32. package/template/design-system/starter-v1/components/button/disabled.html +21 -0
  33. package/template/design-system/starter-v1/components/button/ghost.html +21 -0
  34. package/template/design-system/starter-v1/components/button/macro.njk +14 -0
  35. package/template/design-system/starter-v1/components/button/primary.html +21 -0
  36. package/template/design-system/starter-v1/components/button/variants.json +30 -0
  37. package/template/design-system/starter-v1/ds.json +3 -0
  38. package/template/design-system/starter-v1/global.css +52 -0
  39. package/template/design-system/starter-v1/style.css +107 -0
  40. package/template/gitignore +8 -0
  41. package/template/package.json +41 -0
  42. package/template/prototypes/F-001-hello/1-welcome.njk +30 -0
  43. package/template/prototypes/F-001-hello/2-form.njk +46 -0
  44. package/template/prototypes/F-001-hello/3-done.njk +29 -0
  45. package/template/prototypes/F-001-hello/meta.json +6 -0
  46. package/template/prototypes/_shared/_auth-gate.njk +54 -0
  47. package/template/prototypes/_shared/delivery.njk +43 -0
  48. package/template/prototypes/_shared/layout.njk +15 -0
  49. package/template/prototypes/_shared/screen.njk +1818 -0
  50. package/template/prototypes/_shared/wireflow.njk +4731 -0
  51. package/template/public/auth-gate.css +150 -0
  52. package/template/public/bedrock/color-inspector.js +284 -0
  53. package/template/public/bedrock/component-overlay.js +219 -0
  54. package/template/public/bedrock/data/bedrock-config.js +45 -0
  55. package/template/public/bedrock/font-size-overlay.js +590 -0
  56. package/template/public/bedrock/grid-overlay.js +379 -0
  57. package/template/public/bedrock/prototype-navigation.js +974 -0
  58. package/template/public/cmdk.js +146 -0
  59. package/template/public/ds-xray.css +112 -0
  60. package/template/public/ds-xray.js +271 -0
  61. package/template/public/favicon.svg +4 -0
  62. package/template/public/icons/bolt-fill.svg +3 -0
  63. package/template/public/icons/bolt.svg +3 -0
  64. package/template/public/icons/caret-down-fill.svg +3 -0
  65. package/template/public/icons/check-double.svg +4 -0
  66. package/template/public/icons/check.svg +3 -0
  67. package/template/public/icons/chevron-left.svg +3 -0
  68. package/template/public/icons/chevron-right.svg +3 -0
  69. package/template/public/icons/circle-info.svg +6 -0
  70. package/template/public/icons/grid.svg +6 -0
  71. package/template/public/icons/message-square-1.svg +3 -0
  72. package/template/public/icons/message-square.svg +3 -0
  73. package/template/public/icons/messages.svg +4 -0
  74. package/template/public/icons/options-horizontal.svg +5 -0
  75. package/template/public/icons/swatches.svg +6 -0
  76. package/template/public/icons/workflow.svg +6 -0
  77. package/template/public/lightbox.js +87 -0
  78. package/template/public/proto-chrome.css +596 -0
  79. package/template/public/screen-comments.css +723 -0
  80. package/template/public/wireflow-client.js +26 -0
  81. package/template/scripts/build-storybooks.mjs +8 -0
  82. package/template/scripts/dev-setup.mjs +15 -0
  83. package/template/scripts/generate-stories.mjs +12 -0
  84. package/template/scripts/generate-variants.mjs +22 -0
  85. package/template/tsconfig.base.json +19 -0
@@ -0,0 +1,8 @@
1
+ PRAGMA defer_foreign_keys=TRUE;
2
+ CREATE TABLE IF NOT EXISTS "user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "emailVerified" integer not null, "image" text, "createdAt" date not null, "updatedAt" date not null, "role" text, "banned" integer, "banReason" text, "banExpires" date);
3
+ CREATE TABLE IF NOT EXISTS "session" ("id" text not null primary key, "expiresAt" date not null, "token" text not null unique, "createdAt" date not null, "updatedAt" date not null, "ipAddress" text, "userAgent" text, "userId" text not null references "user" ("id") on delete cascade, "impersonatedBy" text);
4
+ CREATE TABLE IF NOT EXISTS "account" ("id" text not null primary key, "accountId" text not null, "providerId" text not null, "userId" text not null references "user" ("id") on delete cascade, "accessToken" text, "refreshToken" text, "idToken" text, "accessTokenExpiresAt" date, "refreshTokenExpiresAt" date, "scope" text, "password" text, "createdAt" date not null, "updatedAt" date not null);
5
+ CREATE TABLE IF NOT EXISTS "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" date not null, "createdAt" date not null, "updatedAt" date not null);
6
+ CREATE INDEX "session_userId_idx" on "session" ("userId");
7
+ CREATE INDEX "account_userId_idx" on "account" ("userId");
8
+ CREATE INDEX "verification_identifier_idx" on "verification" ("identifier");
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bedrock-flows — the project CLI.
4
+ *
5
+ * npx bedrock-flows create [dir] scaffold a new project (Part 1: local)
6
+ * bedrock-flows setup [name] stand up its Cloudflare backend (Part 2: team)
7
+ *
8
+ * `create` unpacks the bundled `template/` (a project that depends on the
9
+ * published @obra-studio/bedrock-flows platform) and stamps THIS CLI's version
10
+ * — which ships in lockstep with the platform — so the project pulls a matching
11
+ * platform from npm and the person never clones the platform repo.
12
+ */
13
+ import fs from 'node:fs'
14
+ import path from 'node:path'
15
+ import { fileURLToPath } from 'node:url'
16
+
17
+ const HERE = path.dirname(fileURLToPath(import.meta.url))
18
+ const CLI_ROOT = path.resolve(HERE, '..')
19
+ const TEMPLATE = path.join(CLI_ROOT, 'template')
20
+ const VERSION_TOKEN = '__BEDROCK_FLOWS_VERSION__'
21
+ const { version } = JSON.parse(fs.readFileSync(path.join(CLI_ROOT, 'package.json'), 'utf8'))
22
+
23
+ function usage() {
24
+ console.log(`bedrock-flows ${version}
25
+
26
+ npx bedrock-flows create [dir] scaffold a new project (local — needs nothing)
27
+ bedrock-flows setup [name] stand up its Cloudflare backend + deploy
28
+
29
+ setup options:
30
+ --domains=a.com,b.com restrict signup to these domains (default: open)
31
+ --google enable Google sign-in (needs google creds in your config)
32
+ --dry-run print every action without executing
33
+ --print-config print the operator config template and exit
34
+ `)
35
+ }
36
+
37
+ // ── create ─────────────────────────────────────────────────────────────────
38
+ function cmdCreate(args) {
39
+ const dirArg = args[0]
40
+ const target = path.resolve(dirArg || '.')
41
+
42
+ // Refuse to clobber a non-empty folder (a few harmless entries are tolerated).
43
+ const HARMLESS = new Set(['.git', '.DS_Store', '.gitignore'])
44
+ if (fs.existsSync(target)) {
45
+ const entries = fs.readdirSync(target).filter((e) => !HARMLESS.has(e))
46
+ if (entries.length) {
47
+ console.error(`✗ ${target} is not empty (${entries.length} item(s)).`)
48
+ console.error(' Run `bedrock-flows create` in an empty folder, or pass a new dir name.')
49
+ process.exit(1)
50
+ }
51
+ }
52
+ if (!fs.existsSync(TEMPLATE)) {
53
+ console.error(`✗ bundled template missing at ${TEMPLATE} — broken install?`)
54
+ process.exit(1)
55
+ }
56
+
57
+ fs.mkdirSync(target, { recursive: true })
58
+ fs.cpSync(TEMPLATE, target, { recursive: true })
59
+
60
+ // `gitignore` ships dotless (npm mangles a real .gitignore inside a package).
61
+ const ignoreSrc = path.join(target, 'gitignore')
62
+ if (fs.existsSync(ignoreSrc)) fs.renameSync(ignoreSrc, path.join(target, '.gitignore'))
63
+
64
+ // Stamp the platform version into every package.json (replaces the placeholder).
65
+ let stamped = 0
66
+ function stamp(dir) {
67
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
68
+ if (e.name === 'node_modules') continue
69
+ const p = path.join(dir, e.name)
70
+ if (e.isDirectory()) stamp(p)
71
+ else if (e.name === 'package.json') {
72
+ const txt = fs.readFileSync(p, 'utf8')
73
+ if (txt.includes(VERSION_TOKEN)) {
74
+ fs.writeFileSync(p, txt.split(VERSION_TOKEN).join(`^${version}`))
75
+ stamped++
76
+ }
77
+ }
78
+ }
79
+ }
80
+ stamp(target)
81
+ if (!stamped) {
82
+ console.error('✗ template had no version placeholder to stamp — regenerate it with build-starter.')
83
+ process.exit(1)
84
+ }
85
+
86
+ const shown = dirArg || '.'
87
+ const cd = shown === '.' ? '' : `cd ${shown}\n `
88
+ console.log(`✓ Created a Bedrock Flows project in ${shown} (platform ^${version})
89
+
90
+ Next:
91
+ ${cd}npm install
92
+ npm run dev # one command → http://localhost:5173
93
+
94
+ Edit prototypes/ and design-system/. Upgrade later with:
95
+ npm install @obra-studio/bedrock-flows@latest
96
+ `)
97
+ }
98
+
99
+ // ── setup ──────────────────────────────────────────────────────────────────
100
+ async function cmdSetup(args) {
101
+ const { runSetup, printConfigTemplate } = await import('../lib/setup.mjs')
102
+ const opts = { domains: '', google: false, dryRun: false }
103
+ let name
104
+ for (const a of args) {
105
+ if (a === '--print-config') return printConfigTemplate()
106
+ else if (a === '--google') opts.google = true
107
+ else if (a === '--dry-run') opts.dryRun = true
108
+ else if (a.startsWith('--domains=')) opts.domains = a.slice('--domains='.length)
109
+ else if (a.startsWith('--')) throw new Error(`Unknown flag: ${a}`)
110
+ else name = a
111
+ }
112
+ runSetup({ name, opts })
113
+ }
114
+
115
+ // ── dispatch ───────────────────────────────────────────────────────────────
116
+ const [cmd, ...rest] = process.argv.slice(2)
117
+ try {
118
+ if (cmd === 'create') cmdCreate(rest)
119
+ else if (cmd === 'setup') await cmdSetup(rest)
120
+ else {
121
+ usage()
122
+ process.exit(cmd ? 1 : 0)
123
+ }
124
+ } catch (e) {
125
+ console.error(`\n✗ ${e.message}\n`)
126
+ process.exit(1)
127
+ }
package/lib/setup.mjs ADDED
@@ -0,0 +1,262 @@
1
+ // bedrock-flows setup — stand up a project's Cloudflare backend and deploy it.
2
+ //
3
+ // Ported from bedrock-provision, minus the scaffold step: the project already
4
+ // exists (from `bedrock-flows create`), so setup runs IN it (cwd). It creates
5
+ // KV + D1, applies the bundled Better Auth schema, patches wrangler.jsonc,
6
+ // deploys (which creates the Worker), then pushes prod secrets.
7
+ //
8
+ // Required: a Cloudflare account. Postmark is OPTIONAL — without it the worker
9
+ // just no-ops notification email (invite links are shared manually). Google
10
+ // OAuth is opt-in (--google).
11
+ import { execSync } from 'node:child_process'
12
+ import { randomBytes } from 'node:crypto'
13
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from 'node:fs'
14
+ import { join, dirname, basename } from 'node:path'
15
+ import { homedir } from 'node:os'
16
+ import { fileURLToPath } from 'node:url'
17
+
18
+ const HERE = dirname(fileURLToPath(import.meta.url))
19
+ const SCHEMA = join(HERE, '..', 'auth-schema.sql') // cli/auth-schema.sql
20
+ const CONFIG_DIR = join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'bedrock-flows')
21
+ const CONFIG_PATH = process.env.BEDROCK_FLOWS_CONFIG || join(CONFIG_DIR, 'config.json')
22
+
23
+ export const genSecret = () => randomBytes(32).toString('base64')
24
+
25
+ // ── shell ────────────────────────────────────────────────────────────────────
26
+ function sh(cmd, { cwd, env, stream = false, dryRun = false } = {}) {
27
+ if (dryRun) {
28
+ console.log(` \x1b[2m[dry-run] ${cmd}${cwd ? ` (cwd: ${cwd})` : ''}\x1b[0m`)
29
+ return ''
30
+ }
31
+ return execSync(cmd, {
32
+ cwd,
33
+ env: { ...process.env, ...env },
34
+ encoding: 'utf8',
35
+ stdio: stream ? 'inherit' : ['inherit', 'pipe', 'inherit'],
36
+ })
37
+ }
38
+
39
+ // ── operator config (shared across the operator's projects) ──────────────────
40
+ const CONFIG_TEMPLATE = {
41
+ cloudflare: { accountId: '<from the Cloudflare dashboard>', subdomain: '<your workers.dev subdomain>' },
42
+ ownerEmail: 'you@example.com',
43
+ postmark: { '//': 'OPTIONAL — omit to skip emailed invites/notifications', token: '', from: 'you@your-verified-domain.com', stream: 'outbound' },
44
+ google: { '//': 'OPTIONAL — only used with --google', clientId: '', clientSecret: '' },
45
+ }
46
+
47
+ export function printConfigTemplate() {
48
+ console.log(`# bedrock-flows operator config → ${CONFIG_PATH}\n`)
49
+ console.log(JSON.stringify(CONFIG_TEMPLATE, null, 2))
50
+ }
51
+
52
+ function loadConfig() {
53
+ if (!existsSync(CONFIG_PATH)) {
54
+ throw new Error(
55
+ `No operator config at ${CONFIG_PATH}\n` +
56
+ ` Create it (run \`bedrock-flows setup --print-config\` for a template) with your\n` +
57
+ ` Cloudflare account; Postmark + Google are optional.`,
58
+ )
59
+ }
60
+ const cfg = JSON.parse(readFileSync(CONFIG_PATH, 'utf8'))
61
+ if (!cfg.cloudflare?.accountId || !cfg.cloudflare?.subdomain) {
62
+ throw new Error(`${CONFIG_PATH}: "cloudflare" needs both accountId and subdomain.`)
63
+ }
64
+ return cfg
65
+ }
66
+
67
+ const hasPostmark = (cfg) => Boolean(cfg.postmark && cfg.postmark.token)
68
+
69
+ // A worker name must be lowercase alnum + hyphens (also the DB + resource names).
70
+ function validateName(name) {
71
+ if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(name)) {
72
+ throw new Error(`Invalid name "${name}". Use lowercase letters, digits and hyphens (e.g. acme-bedrock-flows).`)
73
+ }
74
+ return name
75
+ }
76
+
77
+ // ── Cloudflare resources ──────────────────────────────────────────────────────
78
+ const workerDir = (dir) => join(dir, 'apps', 'worker')
79
+ const HEX32 = /[0-9a-f]{32}/
80
+ const UUID = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
81
+ const wenv = (cfg) => ({ CLOUDFLARE_ACCOUNT_ID: cfg.cloudflare.accountId })
82
+
83
+ // wrangler prepends chatter (version line, banners) so stdout isn't pure JSON.
84
+ function parseJsonLoose(out, what) {
85
+ const i = out.search(/[[{]/)
86
+ if (i === -1) throw new Error(`No JSON found in ${what} output:\n${out}`)
87
+ return JSON.parse(out.slice(i))
88
+ }
89
+
90
+ function createKv(dir, cfg, title, opts) {
91
+ if (!opts.dryRun) {
92
+ const list = sh(`npx wrangler kv namespace list`, { cwd: workerDir(dir), env: wenv(cfg) })
93
+ const found = parseJsonLoose(list, 'kv namespace list').find((n) => n.title === title)
94
+ if (found) {
95
+ console.log(` • KV "${title}" exists → ${found.id}`)
96
+ return found.id
97
+ }
98
+ }
99
+ const out = sh(`npx wrangler kv namespace create "${title}"`, { cwd: workerDir(dir), env: wenv(cfg), dryRun: opts.dryRun })
100
+ const id = opts.dryRun ? 'DRY_KV_ID'.padEnd(32, '0') : (out.match(HEX32) || [])[0]
101
+ if (!id) throw new Error(`Could not parse KV id from:\n${out}`)
102
+ console.log(` • KV "${title}" → ${id}`)
103
+ return id
104
+ }
105
+
106
+ function createD1(dir, cfg, dbName, opts) {
107
+ if (!opts.dryRun) {
108
+ const list = sh(`npx wrangler d1 list --json`, { cwd: workerDir(dir), env: wenv(cfg) })
109
+ const found = parseJsonLoose(list, 'd1 list').find((d) => d.name === dbName)
110
+ if (found) {
111
+ console.log(` • D1 "${dbName}" exists → ${found.uuid}`)
112
+ return found.uuid
113
+ }
114
+ }
115
+ const out = sh(`npx wrangler d1 create ${dbName}`, { cwd: workerDir(dir), env: wenv(cfg), dryRun: opts.dryRun })
116
+ const id = opts.dryRun ? '00000000-0000-0000-0000-000000000000' : (out.match(UUID) || [])[0]
117
+ if (!id) throw new Error(`Could not parse D1 id from:\n${out}`)
118
+ console.log(` • D1 "${dbName}" → ${id}`)
119
+ return id
120
+ }
121
+
122
+ function applySchema(dir, cfg, dbName, opts) {
123
+ if (!existsSync(SCHEMA)) throw new Error(`Bundled auth-schema.sql not found at ${SCHEMA}`)
124
+ sh(`npx wrangler d1 execute ${dbName} --remote --yes --file "${SCHEMA}"`, {
125
+ cwd: workerDir(dir), env: wenv(cfg), stream: true, dryRun: opts.dryRun,
126
+ })
127
+ console.log(` • D1 "${dbName}" schema applied (user/session/account/verification)`)
128
+ }
129
+
130
+ function patchWrangler(dir, { name, kv, d1Id, d1Name, url, domains, ownerEmail }, opts) {
131
+ const wf = join(workerDir(dir), 'wrangler.jsonc')
132
+ if (opts.dryRun) {
133
+ console.log(` [dry-run] patch ${wf} (name=${name}, kv, d1=${d1Name}, vars)`)
134
+ return
135
+ }
136
+ let s = readFileSync(wf, 'utf8')
137
+ s = s.replace(/("name":\s*")[^"]*(")/, `$1${name}$2`)
138
+ s = s.replace(/("binding":\s*"WF_COMMENTS",\s*"id":\s*")[0-9a-f]+(")/, `$1${kv.comments}$2`)
139
+ s = s.replace(/("binding":\s*"SPEC_KV",\s*"id":\s*")[0-9a-f]+(")/, `$1${kv.spec}$2`)
140
+ s = s.replace(/("database_name":\s*")[^"]*(")/, `$1${d1Name}$2`)
141
+ s = s.replace(/("database_id":\s*")[^"]*(")/, `$1${d1Id}$2`)
142
+ // Inject a vars block right after the assets binding (the template has none).
143
+ const varsBlock =
144
+ `\n "vars": {\n` +
145
+ ` "BETTER_AUTH_URL": "${url}",\n` +
146
+ ` "AUTH_ALLOWED_DOMAINS": "${domains}",\n` +
147
+ ` "AUTH_ALLOWLIST": "",\n` +
148
+ ` "AUTH_OWNER_EMAIL": "${ownerEmail || ''}"\n` +
149
+ ` },`
150
+ if (!s.includes('"vars"')) s = s.replace(/("assets":\s*\{[^}]*\},)/, `$1${varsBlock}`)
151
+ writeFileSync(wf, s)
152
+ console.log(` • patched apps/worker/wrangler.jsonc`)
153
+ }
154
+
155
+ function deploy(dir, opts) {
156
+ sh(`npm run deploy`, { cwd: dir, stream: true, dryRun: opts.dryRun })
157
+ }
158
+
159
+ function pushSecrets(dir, { secret, cfg, google }, opts) {
160
+ const secrets = { BETTER_AUTH_SECRET: secret }
161
+ if (hasPostmark(cfg)) {
162
+ secrets.POSTMARK_TOKEN = cfg.postmark.token
163
+ secrets.POSTMARK_FROM = cfg.postmark.from
164
+ secrets.POSTMARK_STREAM = cfg.postmark.stream || 'outbound'
165
+ }
166
+ if (google) {
167
+ secrets.GOOGLE_CLIENT_ID = cfg.google.clientId
168
+ secrets.GOOGLE_CLIENT_SECRET = cfg.google.clientSecret
169
+ }
170
+ if (opts.dryRun) {
171
+ console.log(` [dry-run] wrangler secret bulk (${Object.keys(secrets).join(', ')})`)
172
+ return
173
+ }
174
+ const tmp = join(workerDir(dir), '.secrets.tmp.json')
175
+ writeFileSync(tmp, JSON.stringify(secrets))
176
+ try {
177
+ sh(`npx wrangler secret bulk .secrets.tmp.json`, { cwd: workerDir(dir), env: wenv(cfg), stream: true })
178
+ } finally {
179
+ rmSync(tmp, { force: true })
180
+ }
181
+ console.log(` • pushed ${Object.keys(secrets).length} prod secrets`)
182
+ }
183
+
184
+ function writeManifest(name, data) {
185
+ const dir = join(CONFIG_DIR, 'manifests')
186
+ mkdirSync(dir, { recursive: true })
187
+ writeFileSync(join(dir, `${name}.json`), JSON.stringify(data, null, 2) + '\n')
188
+ console.log(` • manifest → ${join(dir, `${name}.json`)}`)
189
+ }
190
+
191
+ // The bits that can't be automated — printed AND saved into the project.
192
+ function manualSteps({ name, url, google, ownerEmail, emailOn }) {
193
+ const L = [`# ${name} — after setup`, '', `Live: ${url}`, '']
194
+ L.push(`## Invite your team`, ``)
195
+ L.push(`Sign in as ${ownerEmail || 'the owner email'} (auto-promoted to admin) → the **Users**`)
196
+ L.push(`tab: invite by email, promote, ban, remove.`)
197
+ L.push(
198
+ emailOn
199
+ ? `Invitees get an email with a signup link.`
200
+ : `Email is off, so the invite link isn't sent — copy it from the Users tab and share it (Slack/DM).`,
201
+ )
202
+ L.push('')
203
+ if (google) {
204
+ L.push(`## Register the Google OAuth redirect URIs (the only manual step)`, ``)
205
+ L.push(`Google Cloud Console → your OAuth client → Authorized redirect URIs, add BOTH`)
206
+ L.push(`(exact, no trailing slash):`)
207
+ L.push(` ${url}/api/auth/callback/google`)
208
+ L.push(` http://localhost:8787/api/auth/callback/google`)
209
+ L.push(`Until then "Continue with Google" 404s; email/password works immediately.`)
210
+ L.push('')
211
+ }
212
+ return L.join('\n')
213
+ }
214
+
215
+ export function runSetup({ name, opts }) {
216
+ const dir = process.cwd()
217
+ if (!existsSync(join(dir, 'apps', 'worker', 'wrangler.jsonc'))) {
218
+ throw new Error(
219
+ `Not a Bedrock Flows project here (no apps/worker/wrangler.jsonc).\n` +
220
+ ` Run this inside your project dir, or \`npx bedrock-flows create\` first.`,
221
+ )
222
+ }
223
+ const cfg = loadConfig()
224
+ name = validateName(name || basename(dir))
225
+ if (opts.google && !(cfg.google?.clientId && cfg.google?.clientSecret)) {
226
+ throw new Error(`--google needs google.clientId/clientSecret in ${CONFIG_PATH}.`)
227
+ }
228
+ const dbName = `${name}-auth`
229
+ const url = `https://${name}.${cfg.cloudflare.subdomain}.workers.dev`
230
+ const secret = genSecret()
231
+ const ownerEmail = cfg.ownerEmail || ''
232
+ const emailOn = hasPostmark(cfg)
233
+ const banner = opts.dryRun ? ' (dry run)' : ''
234
+ console.log(
235
+ `\n▶ Setting up "${name}"${banner}\n URL: ${url}\n Email: ${emailOn ? 'Postmark' : 'off (share invite links manually)'}\n Google: ${opts.google ? 'on' : 'off'}\n`,
236
+ )
237
+
238
+ console.log('1/6 KV namespaces')
239
+ const kv = { comments: createKv(dir, cfg, `${name}-comments`, opts), spec: createKv(dir, cfg, `${name}-spec`, opts) }
240
+
241
+ console.log('2/6 D1 auth store')
242
+ const d1Id = createD1(dir, cfg, dbName, opts)
243
+ applySchema(dir, cfg, dbName, opts)
244
+
245
+ console.log('3/6 Patch wrangler.jsonc')
246
+ patchWrangler(dir, { name, kv, d1Id, d1Name: dbName, url, domains: opts.domains, ownerEmail }, opts)
247
+
248
+ console.log('4/6 Deploy')
249
+ deploy(dir, opts)
250
+
251
+ console.log('5/6 Push prod secrets')
252
+ pushSecrets(dir, { secret, cfg, google: opts.google }, opts)
253
+
254
+ console.log('6/6 Manifest')
255
+ if (!opts.dryRun) writeManifest(name, { name, url, dbName, d1Id, kv, ownerEmail, google: opts.google, domains: opts.domains, emailOn })
256
+
257
+ const steps = manualSteps({ name, url, google: opts.google, ownerEmail, emailOn })
258
+ if (!opts.dryRun) writeFileSync(join(dir, 'SETUP-NEXT-STEPS.md'), steps)
259
+
260
+ console.log(`\n✅ Done${banner}.\n Live: ${url}\n Re-ship changes any time with: npm run deploy\n`)
261
+ console.log(steps)
262
+ }
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "bedrock-flows",
3
+ "version": "0.7.1",
4
+ "description": "Bedrock Flows CLI — `create` a project, `setup` its Cloudflare backend.",
5
+ "type": "module",
6
+ "license": "FSL-1.1-MIT",
7
+ "repository": { "type": "git", "url": "git+https://github.com/Obra-Studio/bedrock-flows-mr.git" },
8
+ "bin": { "bedrock-flows": "bin/bedrock-flows.mjs" },
9
+ "files": ["bin", "lib", "template", "auth-schema.sql"],
10
+ "engines": { "node": ">=18" }
11
+ }
@@ -0,0 +1,46 @@
1
+ // Per-version Storybook config. The `DS_VERSION` env var selects which version
2
+ // of the design system this build documents. scripts/build-storybooks.mjs
3
+ // (→ @obra-studio/bedrock-flows/design-system buildStorybooks) invokes Storybook once per
4
+ // version, each writing into dist/ds/<v>/storybook.
5
+ // Fallback when DS_VERSION isn't set (e.g. running Storybook directly):
6
+ // the project's defaultDsVersion from bedrock.config.ts. Read with a regex —
7
+ // this file is plain JS and can't import the TS config.
8
+ import fs from 'node:fs'
9
+ import path from 'node:path'
10
+ import { fileURLToPath } from 'node:url'
11
+ const projectDefault = (() => {
12
+ try {
13
+ const cfg = fs.readFileSync(
14
+ path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../bedrock.config.ts'),
15
+ 'utf8',
16
+ )
17
+ return cfg.match(/defaultDsVersion:\s*'([^']+)'/)?.[1]
18
+ } catch {
19
+ return undefined
20
+ }
21
+ })()
22
+ const version = process.env.DS_VERSION || projectDefault || 'starter-v1'
23
+
24
+ /** @type {import('@storybook/html-vite').StorybookConfig} */
25
+ const config = {
26
+ stories: [`../.stories-generated/${version}/**/*.stories.js`],
27
+ // `@storybook/addon-docs` is required in Storybook 10 for autodocs. Without
28
+ // it the html-vite renderer's `entry-preview-docs` is never loaded and every
29
+ // Docs page throws `r.renderer is not a function` at render time.
30
+ addons: ['@storybook/addon-docs'],
31
+ framework: { name: '@storybook/html-vite', options: {} },
32
+ // Per-story `tags: ['autodocs']` (set by the generator) opts each component
33
+ // into a Docs page that stacks all its variants. `docs.autodocs: 'tag'` is
34
+ // the canonical SB switch that respects that tag.
35
+ docs: { autodocs: 'tag', defaultName: 'Docs' },
36
+ staticDirs: [{ from: `../design-system/${version}`, to: '/ds-current' }],
37
+ async viteFinal(viteConfig, { configType }) {
38
+ if (configType === 'PRODUCTION') {
39
+ // Deployed path: https://.../ds/<version>/storybook/
40
+ viteConfig.base = `/ds/${version}/storybook/`
41
+ }
42
+ return viteConfig
43
+ },
44
+ }
45
+
46
+ export default config