@forumone/create-throughline 1.0.2-next-3406ccb → 1.0.2-next-7b3e9c1

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/CHANGELOG.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # @forumone/create-throughline
2
2
 
3
- ## 1.0.2-next-3406ccb
3
+ ## 1.0.2-next-7b3e9c1
4
+
5
+ ### Patch Changes
6
+
7
+ - 0ecb757: A scaffolded project puts its env file where `next dev` reads it, and does not make every account an administrator. The instructions copy `.env.example` to `apps/web/.env.local` rather than the root, which `next dev` in `apps/web` never read. New users default to `editor`; the first account, made at `/admin/create-first-user`, is the admin; and only an admin may set a role or a group.
4
8
 
5
9
  ## 1.0.1
6
10
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @forumone/create-throughline
2
2
 
3
- Scaffolder for new Throughline projects (Payload CMS + MCP servers + Inngest workflows).
3
+ Scaffolder for new Throughline projects: Payload CMS, the Throughline suite registered by one `throughline()` call and served over MCP at `/api/mcp`, and an Inngest endpoint for its jobs.
4
4
 
5
5
  ## Usage
6
6
 
@@ -14,28 +14,32 @@ Or with npm:
14
14
  npm create @forumone/throughline@latest my-client-site
15
15
  ```
16
16
 
17
- The scaffolder asks a small set of questions, then generates a ready-to-run
18
- pnpm monorepo with Payload, all Throughline plugins, and an Inngest endpoint
19
- already wired. After scaffolding you'll need to fill in environment variables,
20
- implement client-specific resolvers (users, groups, approvers), and replace
21
- the example content model with your own.
17
+ The scaffolder asks a small set of questions, then generates a pnpm monorepo
18
+ with Payload, `@forumone/throughline` and an Inngest endpoint already wired,
19
+ plus CI and an agent `CLAUDE.md`. After scaffolding you'll need to fill in
20
+ environment variables, create and apply the first migration, implement
21
+ client-specific resolvers (users, groups, approvers), and replace the example
22
+ content model with your own.
22
23
 
23
24
  ## What you get
24
25
 
25
26
  ```
26
27
  my-client-site/
27
28
  ├── apps/
28
- │ └── web/ # Next.js + Payload with all plugins wired
29
- ├── packages/
30
- │ ├── design-system/ # Reference DS (or placeholder for your own)
31
- │ ├── content/ # Client-specific collections & blocks
32
- │ └── brand/ # Brand tokens + email theme
29
+ │ └── web/ # Next.js + Payload, throughline() in payload.config.ts
30
+ ├── design-system/ # the reference design system, or a placeholder for your own
31
+ ├── scripts/ # workspace gates: single instances, tested packages, audit
32
+ ├── .claude/ # agent settings and a Stop-hook gate
33
+ ├── .github/ # CI: fast and verify tiers
33
34
  ├── .env.example
35
+ ├── CLAUDE.md
34
36
  ├── pnpm-workspace.yaml
35
37
  ├── turbo.json
36
38
  └── README.md
37
39
  ```
38
40
 
41
+ The full list is in the [reference](https://github.com/forumone/throughline/blob/main/docs/reference/create-throughline.md).
42
+
39
43
  ## After scaffolding
40
44
 
41
45
  Read the generated `README.md` for full setup instructions. Expect 1–2 hours
@@ -1 +1 @@
1
- {"version":3,"file":"post-install.d.ts","sourceRoot":"","sources":["../src/post-install.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6FrD"}
1
+ {"version":3,"file":"post-install.d.ts","sourceRoot":"","sources":["../src/post-install.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA+FrD"}
@@ -14,7 +14,10 @@ export function printNextSteps(answers) {
14
14
  if (!answers.installDeps) {
15
15
  lines.push(formatStep(step++, pc.bold('pnpm install')));
16
16
  }
17
- lines.push(formatStep(step++, `Copy ${pc.bold('.env.example')} to ${pc.bold('.env.local')} and fill in values`));
17
+ lines.push(formatStep(step++,
18
+ // apps/web, not the root: `next dev` reads env files from its own
19
+ // directory, and the Payload CLI looks there before walking up.
20
+ `Copy ${pc.bold('.env.example')} to ${pc.bold('apps/web/.env.local')} and fill in values`));
18
21
  if (answers.databasePlatform === 'neon') {
19
22
  lines.push(` ${pc.dim('- Create a Neon project; use its connection string for DATABASE_URI')}`);
20
23
  }
@@ -43,7 +46,7 @@ export function printNextSteps(answers) {
43
46
  lines.push(` ${pc.dim('pnpm --dir apps/web generate:importmap')}`);
44
47
  lines.push(formatStep(step++, `${pc.bold('pnpm dev')} to run locally`));
45
48
  lines.push(formatStep(step++, 'Open http://localhost:3000/admin and create your first admin user'));
46
- lines.push(formatStep(step++, 'In the admin, create one key under MCP → Payload MCP API Keys'));
49
+ lines.push(formatStep(step++, 'In the admin, create one key under MCP → API Keys'));
47
50
  lines.push(formatStep(step, 'Give that key to Claude (or your MCP client) for /api/mcp — see the generated README.md'));
48
51
  // eslint-disable-next-line no-console
49
52
  console.log();
@@ -1 +1 @@
1
- {"version":3,"file":"post-install.js","sourceRoot":"","sources":["../src/post-install.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"post-install.js","sourceRoot":"","sources":["../src/post-install.ts"],"names":[],"mappings":"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"}
@@ -35,9 +35,9 @@ APPROVAL_TOKEN_SECRET=
35
35
 
36
36
  # --- MCP ---
37
37
  # No keys here. Every Throughline tool is served on one /api/mcp by
38
- # @payloadcms/plugin-mcp; create a key in the admin under MCP -> Payload MCP
39
- # API Keys and give it to your MCP client, not to this file. Scheduled
40
- # publishes run in process and need no key either.
38
+ # @payloadcms/plugin-mcp; create a key in the admin under MCP -> API Keys and
39
+ # give it to your MCP client, not to this file. Scheduled publishes run in
40
+ # process and need no key either.
41
41
 
42
42
  # --- Optional: error reporting ---
43
43
  # Where unhandled request errors, workflows that ran out of retries and failing
@@ -48,6 +48,7 @@ APPROVAL_TOKEN_SECRET=
48
48
  ERROR_WEBHOOK_URL=
49
49
 
50
50
  # --- Optional: storage ---
51
- # Vercel Blob token if deploying to Vercel; remove this and the storage
52
- # adapter from payload.config.ts if you're using a different provider.
51
+ # payload.config.ts registers no storage adapter, so nothing reads this yet.
52
+ # Set it when you add one: the Vercel Blob token, if you add
53
+ # @payloadcms/storage-vercel-blob to deploy on Vercel.
53
54
  BLOB_READ_WRITE_TOKEN=
@@ -21,10 +21,14 @@ pnpm install --frozen-lockfile \
21
21
  && pnpm typecheck && pnpm lint \
22
22
  && pnpm check:instances && pnpm check:tested && pnpm check:migrations \
23
23
  && pnpm validate \
24
+ && pnpm --filter ./design-system run --if-present build \
24
25
  && bash .github/scripts/check-generated.sh \
25
26
  && pnpm test
26
27
  ```
27
28
 
29
+ `fast` then runs `pnpm check:audit`, which reports advisories without failing
30
+ the job.
31
+
28
32
  `--frozen-lockfile` leads because a lockfile missing an entry passes every
29
33
  other gate locally and fails in CI. `check-generated.sh` needs the environment
30
34
  `payload.config.ts` validates; the placeholders at the top of
@@ -75,8 +79,8 @@ and demands `--confirm` before overwriting.
75
79
 
76
80
  ## The design system reaches the CMS through its manifest
77
81
 
78
- `payload.config.ts` hands the design system's manifest to `componentsPlugin`,
79
- and that manifest is how the CMS knows which components exist and how they may
82
+ `payload.config.ts` hands the design system's manifest to the `components` key
83
+ of its `throughline()` call, and that manifest is how the CMS knows which components exist and how they may
80
84
  be composed. It is built from a `<Name>.contract.ts` beside every component.
81
85
  {{#if useReferenceDs}}
82
86
  - **A new component needs a contract.** `build:manifest` refuses a component
@@ -86,8 +90,9 @@ be composed. It is built from a `<Name>.contract.ts` beside every component.
86
90
  that is not there.
87
91
  {{else}}
88
92
  The design system here is a placeholder. When you build it, give every
89
- component a contract, publish a manifest, and point `componentsPlugin` at it —
90
- see `@forumone/throughline-design-system` in the Throughline repository.
93
+ component a contract, publish a manifest, and point `components.manifest` in
94
+ `throughline()` at it — see `@forumone/throughline-design-system` in the
95
+ Throughline repository.
91
96
  {{/if}}
92
97
  ## Tests
93
98
 
@@ -101,7 +106,7 @@ refused. A new collection fails it until you add it to a bucket. Decide which,
101
106
  and don't open a collection to anonymous reads to make the test pass. More good
102
107
  first tests need no database: field validations, and hooks as functions.
103
108
 
104
- `pnpm test:smoke` runs the Playwright smoke pack in `e2e/` against
109
+ `pnpm --dir apps/web test:smoke` runs the Playwright smoke pack in `e2e/` against
105
110
  `next start`, so build first, or against a deployed site when `E2E_BASE_URL`
106
111
  is set. Every check is a GET. CI runs it in `verify`, after the production
107
112
  build. Add routes to `e2e/site.ts` as the site gains them.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Generated by [`@forumone/create-throughline`](https://github.com/forumone/throughline/tree/main/packages/create-throughline).
4
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) plus an Inngest endpoint that runs the workflow functions.
5
+ This is a Throughline project: a Payload CMS application with the Throughline suite registered by one `throughline()` call in `apps/web/src/payload.config.ts` (audit, components, publishing, approvals, audit queries, integrations, email and a healthcheck), its MCP tools served at `/api/mcp`, plus an Inngest endpoint that runs the suite's jobs.
6
6
 
7
7
  ## Prerequisites
8
8
 
@@ -19,12 +19,13 @@ This is a Throughline project: a Payload CMS application wired with the Throughl
19
19
  pnpm install
20
20
 
21
21
  # 2. Copy the env template
22
- cp .env.example .env.local
22
+ cp .env.example apps/web/.env.local # apps/web: `next dev` reads env files from there
23
23
 
24
- # 3. Generate secrets (run each three times for the three secret slots)
24
+ # 3. Generate the two secrets, PAYLOAD_SECRET and APPROVAL_TOKEN_SECRET (run it
25
+ # once for each)
25
26
  openssl rand -base64 48
26
27
 
27
- # 4. Edit .env.local and fill in:
28
+ # 4. Edit apps/web/.env.local and fill in:
28
29
  # - DATABASE_URI — your Postgres connection string
29
30
  # - PAYLOAD_SECRET — generated above
30
31
  # - APPROVAL_TOKEN_SECRET — generated above
@@ -50,12 +51,13 @@ pnpm --dir apps/web generate:importmap
50
51
  # 7. Start the dev server
51
52
  pnpm dev
52
53
 
53
- # 8. Visit http://localhost:3000/admin and create your first user.
54
+ # 8. Visit http://localhost:3000/admin and create your first user. It is the admin;
55
+ # every account after it starts as an editor, and an admin grants more.
54
56
  ```
55
57
 
56
58
  ## After admin signup
57
59
 
58
- 1. In the Payload admin, open **MCP → Payload MCP API Keys** and create one key. Pick a **User** — required; the key inherits that user's access control — tick **Enable API Key**, save, and copy the key. It is shown once, and it does not go in `.env.local`.
60
+ 1. In the Payload admin, open **MCP → API Keys** and create one key. Only an admin can. The key is bound to you, its creator: it inherits your access control, and every tool logs you as the actor. Give it a **Label**, tick **Enable API Key**, save, and copy the key. It does not go in `.env.local`.
59
61
 
60
62
  2. **Plug it into Claude (or your MCP client).** One endpoint carries every server's tools:
61
63
 
@@ -140,25 +140,46 @@ const isAdmin: Access = ({ req: { user } }) =>
140
140
  Array.isArray(user.roles) &&
141
141
  (user.roles as unknown[]).includes('admin')
142
142
 
143
+ // Only an admin may set a role or a group, so nobody can grant themselves one.
144
+ const adminOnlyField = ({ req: { user } }: { req: { user?: unknown } }) =>
145
+ Array.isArray((user as { roles?: unknown } | undefined)?.roles) &&
146
+ ((user as { roles: unknown[] }).roles).includes('admin')
147
+
143
148
  // Users collection — extend with your own fields and SSO/auth provider.
144
149
  const Users: CollectionConfig = {
145
150
  slug: 'users',
146
151
  auth: true,
147
152
  admin: { useAsTitle: 'email' },
153
+ hooks: {
154
+ beforeChange: [
155
+ // The first account is the admin: /admin/create-first-user is how one
156
+ // exists at all. Every account after it starts as an editor, and an admin
157
+ // grants anything more.
158
+ async ({ data, operation, req }) => {
159
+ if (operation !== 'create') return data
160
+ const { totalDocs } = await req.payload.count({ collection: 'users', req })
161
+ return totalDocs === 0 ? { ...data, roles: ['admin'] } : data
162
+ },
163
+ ],
164
+ },
148
165
  fields: [
149
166
  { name: 'name', type: 'text' },
150
167
  {
151
168
  name: 'roles',
152
169
  type: 'select',
153
170
  hasMany: true,
154
- defaultValue: ['admin'],
171
+ // Least privilege: an editor authors content. The scaffold used to make
172
+ // every new account an administrator.
173
+ defaultValue: ['editor'],
155
174
  options: ['admin', 'editor', 'approver'],
175
+ access: { create: adminOnlyField, update: adminOnlyField },
156
176
  },
157
177
  {
158
178
  name: 'groups',
159
179
  type: 'select',
160
180
  hasMany: true,
161
181
  options: ['editorial', 'legal', 'communications', 'senior'],
182
+ access: { create: adminOnlyField, update: adminOnlyField },
162
183
  },
163
184
  ],
164
185
  }
@@ -22,7 +22,7 @@ pnpm --filter "./design-system" validate # lint contracts + cross-check
22
22
  pnpm --filter "./design-system" test # component tests (vitest + testing-library)
23
23
  ```
24
24
 
25
- The web app imports `./manifest` (the built `dist/manifest.json`) and feeds it to the Components MCP server. The root `pnpm dev` builds the design system first so the manifest exists.
25
+ The web app imports `./manifest` (the built `dist/manifest.json`) and passes it to `components.manifest` in the `throughline()` call in `apps/web/src/payload.config.ts`, which serves it over MCP. The root `pnpm dev` builds the design system first so the manifest exists.
26
26
 
27
27
  ## Branding
28
28
 
@@ -17,4 +17,4 @@ pnpm --filter "./design-system" build-storybook # static Storybook
17
17
  pnpm --filter "./design-system" typecheck
18
18
  ```
19
19
 
20
- Point the Components MCP server at your manifest in `apps/web/src/payload.config.ts` (`componentsPlugin({ manifest })`) once you generate one. For a worked starting point, re-scaffold with the reference design system.
20
+ Point `components.manifest` in the `throughline()` call in `apps/web/src/payload.config.ts` at your manifest once you generate one; until then it names a placeholder URL. For a worked starting point, re-scaffold with the reference design system.
@@ -1,4 +1,4 @@
1
1
  {
2
- "@forumone/throughline-design-system": "1.0.2-next-3406ccb",
3
- "@forumone/throughline": "1.0.2-next-3406ccb"
2
+ "@forumone/throughline-design-system": "1.0.2-next-7b3e9c1",
3
+ "@forumone/throughline": "1.0.2-next-7b3e9c1"
4
4
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@forumone/create-throughline",
3
- "version": "1.0.2-next-3406ccb",
4
- "description": "Scaffolder for new Throughline projects (Payload CMS + MCP servers + Inngest workflows).",
3
+ "version": "1.0.2-next-7b3e9c1",
4
+ "description": "Scaffolder for new Throughline projects: Payload CMS with the Throughline suite, served over MCP, and its jobs on Inngest.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "create-throughline": "./dist/bin.js"