@forumone/create-throughline 0.0.1 → 0.2.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 (54) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +41 -43
  4. package/dist/bin.d.ts +3 -0
  5. package/dist/bin.d.ts.map +1 -0
  6. package/dist/bin.js +45 -0
  7. package/dist/bin.js.map +1 -0
  8. package/dist/generator.d.ts +48 -0
  9. package/dist/generator.d.ts.map +1 -0
  10. package/dist/generator.js +131 -0
  11. package/dist/generator.js.map +1 -0
  12. package/dist/index.d.ts +5 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +5 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/post-install.d.ts +9 -0
  17. package/dist/post-install.d.ts.map +1 -0
  18. package/dist/post-install.js +68 -0
  19. package/dist/post-install.js.map +1 -0
  20. package/dist/prompts.d.ts +23 -0
  21. package/dist/prompts.d.ts.map +1 -0
  22. package/dist/prompts.js +89 -0
  23. package/dist/prompts.js.map +1 -0
  24. package/dist/templates/base/.env.example +43 -0
  25. package/dist/templates/base/README.md +96 -0
  26. package/dist/templates/base/apps/web/next-env.d.ts +5 -0
  27. package/dist/templates/base/apps/web/next.config.mjs +10 -0
  28. package/dist/templates/base/apps/web/package.json.template +42 -0
  29. package/dist/templates/base/apps/web/src/app/(frontend)/layout.tsx +22 -0
  30. package/dist/templates/base/apps/web/src/app/(frontend)/page.tsx +18 -0
  31. package/dist/templates/base/apps/web/src/app/(payload)/admin/[[...segments]]/not-found.tsx +12 -0
  32. package/dist/templates/base/apps/web/src/app/(payload)/admin/[[...segments]]/page.tsx +16 -0
  33. package/dist/templates/base/apps/web/src/app/(payload)/admin/importMap.js +6 -0
  34. package/dist/templates/base/apps/web/src/app/(payload)/api/[...slug]/route.ts +16 -0
  35. package/dist/templates/base/apps/web/src/app/(payload)/layout.tsx +23 -0
  36. package/dist/templates/base/apps/web/src/app/api/inngest/route.ts.template +56 -0
  37. package/dist/templates/base/apps/web/src/payload.config.ts.template +164 -0
  38. package/dist/templates/base/apps/web/tsconfig.json +24 -0
  39. package/dist/templates/base/package.json.template +23 -0
  40. package/dist/templates/base/pnpm-workspace.yaml +3 -0
  41. package/dist/templates/base/tsconfig.json +14 -0
  42. package/dist/templates/base/turbo.json +27 -0
  43. package/dist/templates/with-reference-ds/packages/design-system/package.json.template +18 -0
  44. package/dist/templates/with-reference-ds/packages/design-system/src/index.ts +6 -0
  45. package/dist/templates/with-reference-ds/packages/design-system/src/manifest.ts +6 -0
  46. package/dist/templates/with-reference-ds/packages/design-system/tsconfig.json +9 -0
  47. package/dist/templates/without-reference-ds/packages/design-system/README.md +19 -0
  48. package/dist/templates/without-reference-ds/packages/design-system/package.json.template +14 -0
  49. package/dist/templates/without-reference-ds/packages/design-system/src/index.ts +8 -0
  50. package/dist/utils/templates.d.ts +11 -0
  51. package/dist/utils/templates.d.ts.map +1 -0
  52. package/dist/utils/templates.js +31 -0
  53. package/dist/utils/templates.js.map +1 -0
  54. package/package.json +64 -7
@@ -0,0 +1,43 @@
1
+ # --- Required ---
2
+ DATABASE_URI=postgres://user:pass@host/dbname
3
+ PAYLOAD_SECRET=generate-with-openssl-rand-base64-48
4
+ NEXT_PUBLIC_SERVER_URL=http://localhost:3000
5
+
6
+ # --- Inngest ---
7
+ # Leave blank in local dev; use the inngest dev server (npx inngest-cli dev).
8
+ # Set both in production after creating an Inngest app.
9
+ INNGEST_EVENT_KEY=
10
+ INNGEST_SIGNING_KEY=
11
+
12
+ # --- Resend (for approval + form notification emails) ---
13
+ RESEND_API_KEY=
14
+ EMAIL_FROM_ADDRESS=notifications@example.com
15
+ EMAIL_FROM_NAME={{projectName}}
16
+ EMAIL_REPLY_TO=
17
+
18
+ # --- Approval tokens ---
19
+ # Generate: openssl rand -base64 48
20
+ APPROVAL_TOKEN_SECRET=
21
+
22
+ # --- Forms ---
23
+ # Generate: openssl rand -base64 48
24
+ FORMS_IP_HASH_SECRET=
25
+
26
+ # --- MCP server keys ---
27
+ # Each key is generated in the Payload admin (API Keys collection) after
28
+ # first deploy and pasted back here for local dev.
29
+ COMPONENT_SERVER_API_KEY=
30
+ PUBLISHING_SERVER_API_KEY=
31
+ APPROVALS_SERVER_API_KEY=
32
+ AUDIT_SERVER_API_KEY=
33
+ FORMS_SERVER_API_KEY=
34
+ INTEGRATIONS_SERVER_API_KEY=
35
+
36
+ # System API key used by scheduled workflows that talk back to the
37
+ # Publishing MCP server (executes scheduled publishes).
38
+ PUBLISHING_SYSTEM_API_KEY=
39
+
40
+ # --- Optional: storage ---
41
+ # Vercel Blob token if deploying to Vercel; remove this and the storage
42
+ # adapter from payload.config.ts if you're using a different provider.
43
+ BLOB_READ_WRITE_TOKEN=
@@ -0,0 +1,96 @@
1
+ # {{projectName}}
2
+
3
+ Generated by [`@forumone/create-throughline`](https://github.com/forumone/throughline/tree/main/packages/create-throughline).
4
+
5
+ This is a Throughline project: a Payload CMS application wired with the Throughline plugin suite (audit, components, publishing, approvals, audit-query, integrations, email, forms) plus an Inngest endpoint that runs the workflow functions.
6
+
7
+ ## Prerequisites
8
+
9
+ - Node.js >= 20.9
10
+ - pnpm >= 10
11
+ - A Postgres database (Neon / Supabase / self-hosted)
12
+ - An [Inngest](https://inngest.com) account (free tier is fine for development)
13
+ - A [Resend](https://resend.com) API key for transactional email
14
+
15
+ ## First-time setup
16
+
17
+ ```bash
18
+ # 1. Install deps (skip if you let the scaffolder do it)
19
+ pnpm install
20
+
21
+ # 2. Copy the env template
22
+ cp .env.example .env.local
23
+
24
+ # 3. Generate secrets (run each three times for the three secret slots)
25
+ openssl rand -base64 48
26
+
27
+ # 4. Edit .env.local and fill in:
28
+ # - DATABASE_URI — your Postgres connection string
29
+ # - PAYLOAD_SECRET — generated above
30
+ # - APPROVAL_TOKEN_SECRET — generated above
31
+ # - FORMS_IP_HASH_SECRET — generated above
32
+ # - RESEND_API_KEY — from Resend dashboard
33
+ # - INNGEST_EVENT_KEY/SIGNING — from Inngest (optional in local dev)
34
+
35
+ # 5. Start the dev server
36
+ pnpm dev
37
+
38
+ # 6. Visit http://localhost:3000/admin and create your first user.
39
+ ```
40
+
41
+ ## After admin signup
42
+
43
+ 1. In the Payload admin, open the **API Keys** collection and create one key per MCP server (Component, Publishing, Approvals, Audit, Forms, Integrations). Paste each key into `.env.local` (`COMPONENT_SERVER_API_KEY`, etc.) and restart `pnpm dev`.
44
+
45
+ 2. **Plug the MCP servers into Claude (or your MCP client).** The endpoints are:
46
+
47
+ ```
48
+ /api/components/mcp
49
+ /api/publishing/mcp
50
+ /api/approvals/mcp
51
+ /api/audit/mcp
52
+ /api/forms/mcp
53
+ /api/integrations/mcp
54
+ ```
55
+
56
+ Authenticate with the corresponding API key from `.env.local`.
57
+
58
+ 3. **Replace the example `pages` collection** in `apps/web/src/payload.config.ts` with your real content model. Keep the `policy` group on collections that should require approval — that field shape is what the approvals plugin reads.
59
+
60
+ 4. **Implement the user/group resolvers** in `payload.config.ts`. The scaffold ships with stub functions that return empty arrays so the app compiles; the approvals plugin won't actually work until they're real.
61
+
62
+ ## Development
63
+
64
+ - `pnpm dev` — start Next.js + Payload in dev mode
65
+ - `pnpm build` — production build
66
+ - `pnpm typecheck` — TypeScript check across the monorepo
67
+ - `pnpm lint` — ESLint
68
+ - `pnpm test` — run all package tests
69
+
70
+ ## Project layout
71
+
72
+ ```
73
+ {{projectName}}/
74
+ ├── apps/
75
+ │ └── web/ # Next.js + Payload host
76
+ ├── packages/
77
+ │ ├── design-system/ # UI components consumed by Components MCP
78
+ │ ├── content/ # Reusable collections + blocks (optional)
79
+ │ └── brand/ # Brand tokens / email theme (optional)
80
+ ├── .env.example
81
+ ├── pnpm-workspace.yaml
82
+ ├── turbo.json
83
+ └── tsconfig.json
84
+ ```
85
+
86
+ ## Documentation
87
+
88
+ Throughline docs: https://github.com/forumone/throughline
89
+
90
+ Payload docs: https://payloadcms.com/docs
91
+
92
+ Inngest docs: https://www.inngest.com/docs
93
+
94
+ ## License
95
+
96
+ UNLICENSED — generated project. Add your own license file before publishing.
@@ -0,0 +1,5 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ // NOTE: This file should not be edited
5
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,10 @@
1
+ import { withPayload } from '@payloadcms/next/withPayload'
2
+
3
+ /** @type {import('next').NextConfig} */
4
+ const nextConfig = {
5
+ experimental: {
6
+ reactCompiler: false,
7
+ },
8
+ }
9
+
10
+ export default withPayload(nextConfig)
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "{{#if packageScope}}@{{packageScope}}/web{{else}}{{projectName}}-web{{/if}}",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "next dev",
8
+ "build": "next build",
9
+ "start": "next start",
10
+ "clean": "rm -rf .next .turbo",
11
+ "typecheck": "tsc --noEmit",
12
+ "payload": "payload"
13
+ },
14
+ "dependencies": {
15
+ "@forumone/throughline-approvals": "^0.2.0",
16
+ "@forumone/throughline-audit": "^0.2.0",
17
+ "@forumone/throughline-components": "^0.2.0",
18
+ "@forumone/throughline-core": "^0.2.0",
19
+ "@forumone/throughline-email": "^0.2.0",
20
+ "@forumone/throughline-forms": "^0.2.0",
21
+ "@forumone/throughline-integrations": "^0.2.0",
22
+ "@forumone/throughline-publishing": "^0.2.0",
23
+ "@forumone/throughline-workflows": "^0.2.0",
24
+ {{#if useReferenceDs}}"@forumone/throughline-reference-ds": "^0.2.0",
25
+ {{/if}}"@payloadcms/db-postgres": "^3.83.0",
26
+ "@payloadcms/next": "^3.83.0",
27
+ "@payloadcms/plugin-form-builder": "3.83.0",
28
+ "@payloadcms/richtext-lexical": "^3.83.0",
29
+ "graphql": "^16.8.1",
30
+ "inngest": "^4.2.0",
31
+ "next": "^16.2.4",
32
+ "payload": "^3.83.0",
33
+ "react": "^19.2.0",
34
+ "react-dom": "^19.2.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^20.17.0",
38
+ "@types/react": "^19.2.0",
39
+ "@types/react-dom": "^19.2.0",
40
+ "typescript": "^5.6.0"
41
+ }
42
+ }
@@ -0,0 +1,22 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ export const metadata = {
4
+ title: '{{projectName}}',
5
+ description: 'Powered by Payload CMS + Throughline',
6
+ }
7
+
8
+ export default function FrontendLayout({ children }: { children: ReactNode }) {
9
+ return (
10
+ <html lang="en">
11
+ <body
12
+ style={{
13
+ fontFamily: 'system-ui, -apple-system, sans-serif',
14
+ margin: 0,
15
+ padding: 0,
16
+ }}
17
+ >
18
+ {children}
19
+ </body>
20
+ </html>
21
+ )
22
+ }
@@ -0,0 +1,18 @@
1
+ export default function HomePage() {
2
+ return (
3
+ <main style={{ padding: '4rem 2rem', maxWidth: 720, margin: '0 auto' }}>
4
+ <h1>{{projectName}}</h1>
5
+ <p>
6
+ This is a placeholder home page generated by{' '}
7
+ <code>@forumone/create-throughline</code>.
8
+ </p>
9
+ <p>
10
+ Visit{' '}
11
+ <a href="/admin">
12
+ <code>/admin</code>
13
+ </a>{' '}
14
+ to set up your first user, then replace this page with your real frontend.
15
+ </p>
16
+ </main>
17
+ )
18
+ }
@@ -0,0 +1,12 @@
1
+ import configPromise from '@payload-config'
2
+ import { NotFoundPage } from '@payloadcms/next/views'
3
+ import { importMap } from '../importMap.js'
4
+
5
+ type Args = {
6
+ params: Promise<{ segments: string[] }>
7
+ searchParams: Promise<Record<string, string | string[]>>
8
+ }
9
+
10
+ export default function NotFound({ params, searchParams }: Args) {
11
+ return NotFoundPage({ config: configPromise, importMap, params, searchParams })
12
+ }
@@ -0,0 +1,16 @@
1
+ import type { Metadata } from 'next'
2
+ import configPromise from '@payload-config'
3
+ import { generatePageMetadata, RootPage } from '@payloadcms/next/views'
4
+ import { importMap } from '../importMap.js'
5
+
6
+ type Args = {
7
+ params: Promise<{ segments: string[] }>
8
+ searchParams: Promise<Record<string, string | string[]>>
9
+ }
10
+
11
+ export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
12
+ generatePageMetadata({ config: configPromise, params, searchParams })
13
+
14
+ export default function Page({ params, searchParams }: Args) {
15
+ return RootPage({ config: configPromise, importMap, params, searchParams })
16
+ }
@@ -0,0 +1,6 @@
1
+ // This file is regenerated by `pnpm payload generate:importmap`. The
2
+ // initial export below is a stub so the app compiles before the first
3
+ // generation; payload will rewrite it on first run / when richtext
4
+ // features are added.
5
+ /** @type import('payload').ImportMap */
6
+ export const importMap = {}
@@ -0,0 +1,16 @@
1
+ import configPromise from '@payload-config'
2
+ import {
3
+ REST_DELETE,
4
+ REST_GET,
5
+ REST_OPTIONS,
6
+ REST_PATCH,
7
+ REST_POST,
8
+ REST_PUT,
9
+ } from '@payloadcms/next/routes'
10
+
11
+ export const DELETE = REST_DELETE(configPromise)
12
+ export const GET = REST_GET(configPromise)
13
+ export const OPTIONS = REST_OPTIONS(configPromise)
14
+ export const PATCH = REST_PATCH(configPromise)
15
+ export const POST = REST_POST(configPromise)
16
+ export const PUT = REST_PUT(configPromise)
@@ -0,0 +1,23 @@
1
+ import type { ServerFunctionClient } from 'payload'
2
+ import configPromise from '@payload-config'
3
+ import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'
4
+ import React from 'react'
5
+ import { importMap } from './admin/importMap.js'
6
+ import '@payloadcms/next/css'
7
+
8
+ const serverFunction: ServerFunctionClient = async function (args) {
9
+ 'use server'
10
+ return handleServerFunctions({
11
+ ...args,
12
+ config: configPromise,
13
+ importMap,
14
+ })
15
+ }
16
+
17
+ export default function Layout({ children }: { children: React.ReactNode }) {
18
+ return (
19
+ <RootLayout config={configPromise} importMap={importMap} serverFunction={serverFunction}>
20
+ {children}
21
+ </RootLayout>
22
+ )
23
+ }
@@ -0,0 +1,56 @@
1
+ import { serve } from 'inngest/next'
2
+ import { getPayload } from 'payload'
3
+ import config from '@/payload.config'
4
+ import { createInngestClient } from '@forumone/throughline-core'
5
+ import {
6
+ createAuditEventEchoFunction,
7
+ createExecuteScheduledPublishesFunction,
8
+ createExpireStaleApprovalsFunction,
9
+ createHealthcheckFunction,
10
+ createPayloadReachableCheck,
11
+ createRevalidateOnPublishFunction,
12
+ } from '@forumone/throughline-workflows'
13
+ import { getEmailFunctions } from '@forumone/throughline-email'
14
+ import { getFormsFunctions } from '@forumone/throughline-forms'
15
+ import {
16
+ getIntegrationContext,
17
+ getIntegrationRegistry,
18
+ } from '@forumone/throughline-integrations'
19
+
20
+ const inngest = createInngestClient({ id: '{{projectName}}' })
21
+ const payload = await getPayload({ config })
22
+
23
+ const emailFunctions = getEmailFunctions(payload) ?? []
24
+ const formsFunctions = getFormsFunctions(payload) ?? []
25
+ const integrationRegistry = getIntegrationRegistry(payload)
26
+ const integrationContext = getIntegrationContext(payload)
27
+ const integrationFunctions =
28
+ integrationRegistry && integrationContext
29
+ ? integrationRegistry
30
+ .list()
31
+ .flatMap((integration) => integration.createFunctions(integrationContext))
32
+ : []
33
+
34
+ export const { GET, POST, PUT } = serve({
35
+ client: inngest,
36
+ functions: [
37
+ createRevalidateOnPublishFunction({ inngest, payload }),
38
+ createExecuteScheduledPublishesFunction({
39
+ inngest,
40
+ payload,
41
+ collections: [{ slug: 'pages' }],
42
+ publishingServerUrl: process.env.NEXT_PUBLIC_SERVER_URL!,
43
+ systemApiKey: process.env.PUBLISHING_SYSTEM_API_KEY!,
44
+ }),
45
+ createExpireStaleApprovalsFunction({ inngest, payload }),
46
+ createAuditEventEchoFunction({ inngest }),
47
+ createHealthcheckFunction({
48
+ inngest,
49
+ payload,
50
+ checks: [createPayloadReachableCheck({ payload })],
51
+ }),
52
+ ...emailFunctions,
53
+ ...formsFunctions,
54
+ ...integrationFunctions,
55
+ ],
56
+ })
@@ -0,0 +1,164 @@
1
+ import path from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { buildConfig } from 'payload'
4
+ import type { CollectionConfig } from 'payload'
5
+ import { postgresAdapter } from '@payloadcms/db-postgres'
6
+ import { lexicalEditor } from '@payloadcms/richtext-lexical'
7
+ import {
8
+ auditPlugin,
9
+ createApiKeysCollection,
10
+ createInngestClient,
11
+ } from '@forumone/throughline-core'
12
+ import { componentsPlugin } from '@forumone/throughline-components'
13
+ import { publishingPlugin } from '@forumone/throughline-publishing'
14
+ import { approvalsPlugin } from '@forumone/throughline-approvals'
15
+ import { auditQueryPlugin } from '@forumone/throughline-audit'
16
+ import { integrationsPlugin } from '@forumone/throughline-integrations'
17
+ import { emailPlugin } from '@forumone/throughline-email'
18
+ import { formsPlugin } from '@forumone/throughline-forms'
19
+ {{#if useReferenceDs}}import referenceManifest from '@forumone/throughline-reference-ds/manifest' with { type: 'json' }
20
+ {{/if}}
21
+
22
+ const __filename = fileURLToPath(import.meta.url)
23
+ const __dirname = path.dirname(__filename)
24
+
25
+ const inngest = createInngestClient({ id: '{{projectName}}' })
26
+
27
+ // Users collection — extend with your own fields and SSO/auth provider.
28
+ const Users: CollectionConfig = {
29
+ slug: 'users',
30
+ auth: true,
31
+ admin: { useAsTitle: 'email' },
32
+ fields: [
33
+ { name: 'name', type: 'text' },
34
+ {
35
+ name: 'roles',
36
+ type: 'select',
37
+ hasMany: true,
38
+ defaultValue: ['admin'],
39
+ options: ['admin', 'editor', 'approver', 'form-admin'],
40
+ },
41
+ {
42
+ name: 'groups',
43
+ type: 'select',
44
+ hasMany: true,
45
+ options: ['editorial', 'legal', 'communications', 'senior'],
46
+ },
47
+ ],
48
+ }
49
+
50
+ // Example Pages collection. Replace this with your real content model;
51
+ // keep the `policy` group on collections that should require approval —
52
+ // the approvals plugin reads that field shape.
53
+ const Pages: CollectionConfig = {
54
+ slug: 'pages',
55
+ admin: { useAsTitle: 'title' },
56
+ versions: { drafts: true },
57
+ fields: [
58
+ { name: 'title', type: 'text', required: true },
59
+ { name: 'slug', type: 'text', required: true, unique: true },
60
+ {
61
+ name: 'seo',
62
+ type: 'group',
63
+ fields: [
64
+ { name: 'title', type: 'text' },
65
+ { name: 'description', type: 'textarea' },
66
+ ],
67
+ },
68
+ {
69
+ name: 'policy',
70
+ type: 'group',
71
+ fields: [
72
+ { name: 'requiresApproval', type: 'checkbox', defaultValue: false },
73
+ {
74
+ name: 'approverGroups',
75
+ type: 'select',
76
+ hasMany: true,
77
+ options: ['editorial', 'legal', 'communications', 'senior'],
78
+ },
79
+ { name: 'embargoedUntil', type: 'date' },
80
+ ],
81
+ },
82
+ { name: 'publishedAt', type: 'date' },
83
+ { name: 'scheduledPublishAt', type: 'date' },
84
+ ],
85
+ }
86
+
87
+ export default buildConfig({
88
+ admin: { user: 'users' },
89
+ editor: lexicalEditor(),
90
+ db: postgresAdapter({
91
+ pool: { connectionString: process.env.DATABASE_URI! },
92
+ }),
93
+ collections: [Users, Pages, createApiKeysCollection()],
94
+ secret: process.env.PAYLOAD_SECRET!,
95
+ typescript: {
96
+ outputFile: path.resolve(__dirname, 'payload-types.ts'),
97
+ },
98
+ plugins: [
99
+ // Order matters: audit first (so other plugins can write to it),
100
+ // then content plugins, then notification/forms/integrations.
101
+ auditPlugin({ inngest }),
102
+
103
+ componentsPlugin({
104
+ {{#if useReferenceDs}}manifest: { type: 'object', manifest: referenceManifest },
105
+ {{else}}// TODO: point at your design system manifest
106
+ manifest: { type: 'url', url: 'https://your-design-system.example.com/manifest.json' },
107
+ {{/if}}matching: { strategy: 'tfidf' },
108
+ }),
109
+
110
+ publishingPlugin({
111
+ collections: [{ slug: 'pages' }],
112
+ inngest,
113
+ // Lazy-resolve approvals at call time so plugin order doesn't matter.
114
+ approvalResolver: {
115
+ getActiveApproval: async () => null, // TODO: wire to approvals plugin
116
+ },
117
+ }),
118
+
119
+ approvalsPlugin({
120
+ inngest,
121
+ groups: [
122
+ { slug: 'editorial', name: 'Editorial' },
123
+ { slug: 'legal', name: 'Legal' },
124
+ { slug: 'communications', name: 'Communications' },
125
+ { slug: 'senior', name: 'Senior leadership' },
126
+ ],
127
+ groupResolver: {
128
+ // TODO: replace with your actual user/group lookup (DB query, SSO, etc.)
129
+ async resolveUsers(_groupSlugs) {
130
+ return []
131
+ },
132
+ },
133
+ }),
134
+
135
+ auditQueryPlugin(),
136
+
137
+ emailPlugin({
138
+ inngest,
139
+ // TODO: replace with your real user lookups
140
+ resolveApprover: async (_userId) => null,
141
+ resolveRequester: async (_userId) => null,
142
+ buildActionUrl: ({ token, action }) =>
143
+ `${process.env.NEXT_PUBLIC_SERVER_URL}/api/approvals/decision?token=${token}&action=${action}`,
144
+ tokens: { brandName: '{{projectName}}' },
145
+ }),
146
+
147
+ formsPlugin({
148
+ inngest,
149
+ ipHashSecret: process.env.FORMS_IP_HASH_SECRET!,
150
+ // TODO: list every place a form is allowed to send to. The plugin
151
+ // refuses to deliver to destinations that aren't in this list.
152
+ allowedDestinations: [
153
+ // {
154
+ // type: 'email',
155
+ // value: 'team@example.com',
156
+ // label: 'Main inbox',
157
+ // description: 'General contact form destination',
158
+ // },
159
+ ],
160
+ }),
161
+
162
+ integrationsPlugin({ inngest }),
163
+ ],
164
+ })
@@ -0,0 +1,24 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "jsx": "preserve",
8
+ "allowJs": true,
9
+ "noEmit": true,
10
+ "incremental": true,
11
+ "isolatedModules": true,
12
+ "plugins": [{ "name": "next" }],
13
+ "paths": {
14
+ "@/*": ["./src/*"]
15
+ }
16
+ },
17
+ "include": [
18
+ "next-env.d.ts",
19
+ "**/*.ts",
20
+ "**/*.tsx",
21
+ ".next/types/**/*.ts"
22
+ ],
23
+ "exclude": ["node_modules", ".next"]
24
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "turbo run build",
7
+ "dev": "turbo run dev",
8
+ "lint": "turbo run lint",
9
+ "typecheck": "turbo run typecheck",
10
+ "test": "turbo run test",
11
+ "format": "prettier --write ."
12
+ },
13
+ "devDependencies": {
14
+ "prettier": "^3.4.0",
15
+ "turbo": "^2.3.0",
16
+ "typescript": "^5.6.0"
17
+ },
18
+ "packageManager": "pnpm@10.33.0",
19
+ "engines": {
20
+ "node": ">=20.9.0",
21
+ "pnpm": ">=10.0.0"
22
+ }
23
+ }
@@ -0,0 +1,3 @@
1
+ packages:
2
+ - apps/*
3
+ - packages/*
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022"],
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "strict": true,
8
+ "noUncheckedIndexedAccess": true,
9
+ "exactOptionalPropertyTypes": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "resolveJsonModule": true
13
+ }
14
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://turborepo.com/schema.json",
3
+ "ui": "stream",
4
+ "tasks": {
5
+ "build": {
6
+ "dependsOn": ["^build"],
7
+ "outputs": ["dist/**", ".next/**", "!.next/cache/**"],
8
+ "env": ["NODE_ENV"]
9
+ },
10
+ "dev": {
11
+ "cache": false,
12
+ "persistent": true
13
+ },
14
+ "lint": {
15
+ "dependsOn": ["^build"]
16
+ },
17
+ "typecheck": {
18
+ "dependsOn": ["^build"]
19
+ },
20
+ "test": {
21
+ "dependsOn": ["^build"]
22
+ },
23
+ "clean": {
24
+ "cache": false
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "{{#if packageScope}}@{{packageScope}}/design-system{{else}}{{projectName}}-design-system{{/if}}",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./manifest": "./src/manifest.ts"
11
+ },
12
+ "dependencies": {
13
+ "@forumone/throughline-reference-ds": "^0.2.0"
14
+ },
15
+ "devDependencies": {
16
+ "typescript": "^5.6.0"
17
+ }
18
+ }
@@ -0,0 +1,6 @@
1
+ // Re-export the Throughline reference design system. Wrap or override
2
+ // individual components here when you start building your own brand layer
3
+ // (e.g. swap colors, replace Hero, add new components). When you have
4
+ // enough custom work to stand alone, drop the reference DS dependency
5
+ // and replace this file with your own component exports.
6
+ export * from '@forumone/throughline-reference-ds'