@forumone/create-throughline 0.5.0 → 1.0.0-next.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 (40) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/post-install.d.ts.map +1 -1
  3. package/dist/post-install.js +6 -2
  4. package/dist/post-install.js.map +1 -1
  5. package/dist/templates/base/.env.example +1 -5
  6. package/dist/templates/base/CLAUDE.md +1 -1
  7. package/dist/templates/base/README.md +2 -3
  8. package/dist/templates/base/apps/web/package.json.template +6 -12
  9. package/dist/templates/base/apps/web/src/access/anonymousAccess.test.ts +1 -4
  10. package/dist/templates/base/apps/web/src/app/api/inngest/route.ts.template +15 -109
  11. package/dist/templates/base/apps/web/src/instrumentation.ts +1 -1
  12. package/dist/templates/base/apps/web/src/lib/cache-tags.ts +1 -1
  13. package/dist/templates/base/apps/web/src/lib/inngest.ts.template +7 -0
  14. package/dist/templates/base/apps/web/src/payload.config.ts.template +81 -126
  15. package/dist/templates/base/apps/web/vitest.config.ts +0 -1
  16. package/dist/templates/base/github/workflows/ci.yml +14 -1
  17. package/dist/templates/base/gitignore +1 -1
  18. package/dist/templates/base/turbo.json +0 -1
  19. package/dist/templates/with-reference-ds/design-system/package.json.template +1 -1
  20. package/dist/templates/with-reference-ds/design-system/scripts/build-manifest.ts +1 -1
  21. package/dist/templates/with-reference-ds/design-system/scripts/validate.ts +3 -3
  22. package/dist/templates/with-reference-ds/design-system/src/components/CTASection/CTASection.contract.ts +1 -1
  23. package/dist/templates/with-reference-ds/design-system/src/components/Card/Card.contract.ts +1 -1
  24. package/dist/templates/with-reference-ds/design-system/src/components/CardGrid/CardGrid.contract.ts +1 -1
  25. package/dist/templates/with-reference-ds/design-system/src/components/Divider/Divider.contract.ts +1 -1
  26. package/dist/templates/with-reference-ds/design-system/src/components/FAQ/FAQ.contract.ts +1 -1
  27. package/dist/templates/with-reference-ds/design-system/src/components/Hero/Hero.contract.ts +1 -1
  28. package/dist/templates/with-reference-ds/design-system/src/components/MediaBlock/MediaBlock.contract.ts +1 -1
  29. package/dist/templates/with-reference-ds/design-system/src/components/Prose/Prose.contract.ts +1 -1
  30. package/dist/templates/with-reference-ds/design-system/src/components/Quote/Quote.contract.ts +1 -1
  31. package/dist/templates/with-reference-ds/design-system/src/components/SectionIntro/SectionIntro.contract.ts +1 -1
  32. package/dist/templates/with-reference-ds/design-system/src/components/Spacer/Spacer.contract.ts +1 -1
  33. package/dist/templates/with-reference-ds/design-system/src/components/Stats/Stats.contract.ts +1 -1
  34. package/dist/templates/with-reference-ds/design-system/src/tokens/index.ts +1 -1
  35. package/dist/utils/versions.d.ts +3 -3
  36. package/dist/utils/versions.d.ts.map +1 -1
  37. package/dist/utils/versions.js +14 -8
  38. package/dist/utils/versions.js.map +1 -1
  39. package/dist/versions.json +2 -12
  40. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @forumone/create-throughline
2
2
 
3
+ ## 1.0.0-next.2
4
+
5
+ ### Major Changes
6
+
7
+ - 1595db1: The scaffold builds a 1.0 site. It depends on `@forumone/throughline` and, with the reference design system, `@forumone/throughline-design-system`, rather than on the nine 0.x packages. `payload.config.ts` registers the suite with one `throughline()` call, and `app/api/inngest/route.ts` serves `suite.jobs`, with the one Inngest client in `lib/inngest.ts`. Forms is no longer scaffolded, nor is `FORMS_IP_HASH_SECRET` asked for. The template's stub approval resolver, which overrode the approvals plugin's own and so blocked every publish that required approval, is gone.
8
+
9
+ From 1.0 the scaffolder is versioned with `@forumone/throughline` and `@forumone/throughline-design-system`: one version across the three.
10
+
3
11
  ## 0.5.0
4
12
 
5
13
  ### Minor Changes
@@ -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,CAwFrD"}
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"}
@@ -27,7 +27,12 @@ export function printNextSteps(answers) {
27
27
  lines.push(formatStep(step++, 'Generate required secrets:'));
28
28
  lines.push(` ${pc.dim('openssl rand -base64 48 # PAYLOAD_SECRET')}`);
29
29
  lines.push(` ${pc.dim('openssl rand -base64 48 # APPROVAL_TOKEN_SECRET')}`);
30
- lines.push(` ${pc.dim('openssl rand -base64 48 # FORMS_IP_HASH_SECRET')}`);
30
+ // The config imports the design system's built manifest, and every Payload
31
+ // command below loads the config. `pnpm dev` builds it too, but runs last.
32
+ if (answers.useReferenceDs) {
33
+ lines.push(formatStep(step++, 'Build the design system, which the Payload config reads:'));
34
+ lines.push(` ${pc.dim('pnpm --filter ./design-system build')}`);
35
+ }
31
36
  // `push: false`: the schema exists only once a migration has created it.
32
37
  lines.push(formatStep(step++, 'Create and apply the first migration:'));
33
38
  lines.push(` ${pc.dim('pnpm --dir apps/web migrate:create initial')}`);
@@ -57,7 +62,6 @@ export function printNextSteps(answers) {
57
62
  const customizations = [
58
63
  'Replace the example Pages collection with your content model',
59
64
  'Implement the groupResolver and user resolvers in payload.config.ts',
60
- 'Add your destinations to the formsPlugin allowlist',
61
65
  ];
62
66
  if (!answers.useReferenceDs) {
63
67
  customizations.push('Point componentsPlugin at your design system manifest URL');
@@ -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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"}
@@ -21,7 +21,7 @@ NEXT_PUBLIC_SERVER_URL=http://localhost:3000
21
21
  INNGEST_EVENT_KEY=
22
22
  INNGEST_SIGNING_KEY=
23
23
 
24
- # --- Resend (for approval + form notification emails) ---
24
+ # --- Resend (for approval notification emails) ---
25
25
  # RESEND_API_KEY and EMAIL_FROM_ADDRESS are checked at startup (emailEnv).
26
26
  RESEND_API_KEY=
27
27
  EMAIL_FROM_ADDRESS=notifications@example.com
@@ -33,10 +33,6 @@ EMAIL_REPLY_TO=
33
33
  # 32+ characters. Generate: openssl rand -base64 48
34
34
  APPROVAL_TOKEN_SECRET=
35
35
 
36
- # --- Forms (checked at startup: formsEnv) ---
37
- # 32+ characters. Generate: openssl rand -base64 48
38
- FORMS_IP_HASH_SECRET=
39
-
40
36
  # --- MCP ---
41
37
  # No keys here. Every Throughline tool is served on one /api/mcp by
42
38
  # @payloadcms/plugin-mcp; create a key in the admin under MCP -> Payload MCP
@@ -87,7 +87,7 @@ be composed. It is built from a `<Name>.contract.ts` beside every component.
87
87
  {{else}}
88
88
  The design system here is a placeholder. When you build it, give every
89
89
  component a contract, publish a manifest, and point `componentsPlugin` at it —
90
- see the design-contract package in the Throughline repository.
90
+ see `@forumone/throughline-design-system` in the Throughline repository.
91
91
  {{/if}}
92
92
  ## Tests
93
93
 
@@ -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, forms) plus an Inngest endpoint that runs the workflow functions.
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.
6
6
 
7
7
  ## Prerequisites
8
8
 
@@ -28,7 +28,6 @@ openssl rand -base64 48
28
28
  # - DATABASE_URI — your Postgres connection string
29
29
  # - PAYLOAD_SECRET — generated above
30
30
  # - APPROVAL_TOKEN_SECRET — generated above
31
- # - FORMS_IP_HASH_SECRET — generated above
32
31
  # - RESEND_API_KEY — from Resend dashboard
33
32
  # - EMAIL_FROM_ADDRESS — a verified sender on your Resend domain
34
33
  # - INNGEST_EVENT_KEY/SIGNING — from Inngest (optional in local dev)
@@ -84,7 +83,7 @@ pnpm dev
84
83
  - `pnpm payload:reap` — kill any Payload CLI run a killed shell left behind (the next Payload command does this too)
85
84
 
86
85
  Every script that runs the Payload CLI — `payload`, `generate:*`, `migrate*` —
87
- goes through `throughline-payload` from `@forumone/throughline-core`. pnpm does
86
+ goes through `throughline-payload` from `@forumone/throughline`. pnpm does
88
87
  not forward signals to the node process it starts, so a bare `payload` whose
89
88
  shell is killed keeps running, and a hung one spins on a core indefinitely. The
90
89
  runner gives Payload its own process group, forwards Ctrl-C and friends to it,
@@ -24,28 +24,22 @@
24
24
  "test:smoke:install": "playwright install --with-deps chromium"
25
25
  },
26
26
  "dependencies": {
27
- "@forumone/throughline-approvals": "^{{version_approvals}}",
28
- "@forumone/throughline-audit": "^{{version_audit}}",
29
- "@forumone/throughline-components": "^{{version_components}}",
30
- "@forumone/throughline-core": "^{{version_core}}",
31
- "@forumone/throughline-email": "^{{version_email}}",
32
- "@forumone/throughline-forms": "^{{version_forms}}",
33
- "@forumone/throughline-integrations": "^{{version_integrations}}",
34
- "@forumone/throughline-publishing": "^{{version_publishing}}",
35
- "@forumone/throughline-workflows": "^{{version_workflows}}",
36
- {{#if useReferenceDs}}"@forumone/throughline-design-contract": "^{{version_design_contract}}",
27
+ "@forumone/throughline": "^{{version_throughline}}",
28
+ {{#if useReferenceDs}}"@forumone/throughline-design-system": "^{{version_design_system}}",
37
29
  "{{designSystemName}}": "workspace:*",
38
30
  {{/if}}"@payloadcms/db-postgres": "^3.89.0",
39
31
  "@payloadcms/next": "^3.89.0",
40
- "@payloadcms/plugin-form-builder": "^3.89.0",
41
32
  "@payloadcms/plugin-mcp": "^3.89.0",
42
33
  "@payloadcms/richtext-lexical": "^3.89.0",
34
+ "@react-email/components": "^0.5.4",
35
+ "@react-email/render": "^1.3.2",
43
36
  "graphql": "^16.8.1",
44
37
  "inngest": "^4.2.0",
45
38
  "next": "^16.2.4",
46
39
  "payload": "^3.89.0",
47
40
  "react": "^19.2.0",
48
- "react-dom": "^19.2.0"
41
+ "react-dom": "^19.2.0",
42
+ "resend": "^6.1.4"
49
43
  },
50
44
  "devDependencies": {
51
45
  "@playwright/test": "^1.63.0",
@@ -1,4 +1,4 @@
1
- import { describeAnonymousAccess } from '@forumone/throughline-core/testing'
1
+ import { describeAnonymousAccess } from '@forumone/throughline/testing'
2
2
  import config from '../payload.config'
3
3
 
4
4
  /*
@@ -17,15 +17,12 @@ describeAnonymousAccess(config, {
17
17
  renderPath: {
18
18
  // Anonymous reads see published pages only; signed-in users see drafts.
19
19
  pages: 'the page routes, once the frontend renders them',
20
- // formsPlugin opens form definitions so a page can render the fields.
21
- forms: 'any page that embeds a form',
22
20
  },
23
21
  private: {
24
22
  users: 'accounts',
25
23
  'payload-mcp-api-keys': 'MCP keys are credentials; narrowed to admins in payload.config.ts',
26
24
  'audit-events': 'who did what',
27
25
  approvals: 'who signed off on what',
28
- 'form-submissions': 'what readers typed into a form',
29
26
  integrations: 'connection settings and sync state',
30
27
  'job-failures': 'background jobs that ran out of retries, with their errors',
31
28
  'payload-kv': 'the key-value store Payload keeps',
@@ -1,122 +1,28 @@
1
- import type { InngestFunction } from 'inngest'
2
1
  import { serve } from 'inngest/next'
3
2
  import { getPayload } from 'payload'
4
- import config from '@/payload.config'
5
- import { cacheTags } from '@/lib/cache-tags'
6
- import { createInngestClient } from '@forumone/throughline-core'
7
- import {
8
- createAuditEventEchoFunction,
9
- createExecuteScheduledPublishesFunction,
10
- createExpireStaleApprovalsFunction,
11
- createHealthcheckFailureHandler,
12
- createHealthcheckFunction,
13
- createPayloadReachableCheck,
14
- createPublishAtScheduledTimeFunction,
15
- createRevalidateOnPublishFunction,
16
- createTerminalFailureHandler,
17
- type ScheduledPublishRequest,
18
- } from '@forumone/throughline-workflows'
19
- import { getPublishingService } from '@forumone/throughline-publishing'
20
- import { getEmailFunctions } from '@forumone/throughline-email'
21
- import { getFormsFunctions } from '@forumone/throughline-forms'
22
- import {
23
- getIntegrationContext,
24
- getIntegrationRegistry,
25
- } from '@forumone/throughline-integrations'
3
+ import config, { suite } from '@/payload.config'
4
+ import { inngest } from '@/lib/inngest'
5
+ import { inngestJobs } from '@forumone/throughline/jobs/inngest'
6
+ import { createTerminalFailureHandler } from '@forumone/throughline/jobs'
26
7
 
27
- const inngest = createInngestClient({ id: '{{projectName}}' })
28
8
  const payload = await getPayload({ config })
29
9
 
30
10
  /*
31
- A workflow that runs out of retries is logged, recorded in the `job-failures`
32
- collection, and posted to ERROR_WEBHOOK_URL if that is set. Passed to every
33
- factory below: a factory without it fails silently into Inngest's dashboard.
34
- */
35
- const onTerminalFailure = createTerminalFailureHandler({ payload })
36
-
37
- const emailFunctions = getEmailFunctions(payload) ?? []
38
- const formsFunctions = getFormsFunctions(payload) ?? []
39
- /*
40
- Through the publishing service, in process — so a scheduled publish passes the
41
- same composition, accessibility and approval gates as an interactive one. Not
42
- `payload.update`, which skips all of them.
43
- */
44
- async function publishScheduled({ collection, id, reasoning }: ScheduledPublishRequest) {
45
- const outcome = await getPublishingService(payload).publish({
46
- collection,
47
- id,
48
- actor: { apiKeyName: 'scheduled-publish', channel: 'mcp' },
49
- meta: { reasoning },
50
- })
51
- return { published: outcome.published, reason: outcome.reason }
52
- }
11
+ Every job the Throughline suite runs, as `throughline()` in payload.config.ts
12
+ listed them: publish revalidation, scheduled publishing and its backstop,
13
+ approval expiry, the audit echo, the healthcheck, the approval emails and each
14
+ integration's jobs. Each keeps the function id it has always had, so a run in
15
+ flight survives an upgrade.
53
16
 
54
- const scheduledCollections = [{ slug: 'pages' }]
17
+ A job that runs out of retries is logged, recorded in the `job-failures`
18
+ collection, and posted to ERROR_WEBHOOK_URL if that is set: given once, here,
19
+ and handed to every job.
55
20
 
56
- /*
57
- Where each publishable collection's documents are served, so a publish
58
- revalidates the right path. There are no defaults: a collection missing here
59
- has its cache tags dropped but no path revalidated, and the run logs a warning.
60
- Add an entry for every collection you give publishingPlugin.
61
- */
62
- const urlBuilders = {
63
- pages: (slug: string) => (slug === 'home' ? '/' : `/${slug}`),
64
- }
65
-
66
- /*
67
- Typed with this app's own `InngestFunction.Any`. The registry is generic in what
68
- `createFunctions` returns and defaults to `unknown`, because the platform never
69
- touches those functions — and one `unknown` element leaves `serve()` unable to
70
- type the whole array below.
21
+ Add this site's own functions to the list beside them.
71
22
  */
72
- const integrationRegistry = getIntegrationRegistry<InngestFunction.Any>(payload)
73
- const integrationContext = getIntegrationContext(payload)
74
- const integrationFunctions =
75
- integrationRegistry && integrationContext
76
- ? integrationRegistry
77
- .list()
78
- .flatMap((integration) => integration.createFunctions(integrationContext))
79
- : []
23
+ const jobs = inngestJobs(inngest, { onFailure: createTerminalFailureHandler({ payload }), payload })
80
24
 
81
25
  export const { GET, POST, PUT } = serve({
82
26
  client: inngest,
83
- functions: [
84
- createRevalidateOnPublishFunction({
85
- inngest,
86
- payload,
87
- urlBuilders,
88
- cacheTags,
89
- onTerminalFailure,
90
- }),
91
- // On time: sleeps until each document's scheduled time.
92
- createPublishAtScheduledTimeFunction({
93
- inngest,
94
- payload,
95
- collections: scheduledCollections,
96
- publish: publishScheduled,
97
- onTerminalFailure,
98
- }),
99
- // The backstop, for a schedule whose wake-up event never arrived.
100
- createExecuteScheduledPublishesFunction({
101
- inngest,
102
- payload,
103
- collections: scheduledCollections,
104
- publish: publishScheduled,
105
- schedule: '17 3 * * *',
106
- overdueByMs: 60 * 60 * 1000,
107
- onTerminalFailure,
108
- }),
109
- createExpireStaleApprovalsFunction({ inngest, payload, onTerminalFailure }),
110
- createAuditEventEchoFunction({ inngest, onTerminalFailure }),
111
- createHealthcheckFunction({
112
- inngest,
113
- payload,
114
- checks: [createPayloadReachableCheck()],
115
- onFailure: createHealthcheckFailureHandler({ payload }),
116
- onTerminalFailure,
117
- }),
118
- ...emailFunctions,
119
- ...formsFunctions,
120
- ...integrationFunctions,
121
- ],
27
+ functions: [...jobs.functions(suite.jobs)],
122
28
  })
@@ -3,7 +3,7 @@ import {
3
3
  buildRequestErrorReport,
4
4
  describeErrorReporting,
5
5
  reportError,
6
- } from '@forumone/throughline-core/observability'
6
+ } from '@forumone/throughline/observability'
7
7
 
8
8
  /*
9
9
  Where a server-side error goes.
@@ -18,6 +18,6 @@ Change the scheme here and every end moves with it. Imported from the
18
18
  package's `/cache-tags` subpath, which imports nothing, so frontend code that
19
19
  uses it does not pull in Payload.
20
20
  */
21
- import { createCacheTags } from '@forumone/throughline-workflows/cache-tags'
21
+ import { createCacheTags } from '@forumone/throughline/cache-tags'
22
22
 
23
23
  export const cacheTags = createCacheTags()
@@ -0,0 +1,7 @@
1
+ import { createInngestClient } from '@forumone/throughline/jobs/inngest'
2
+
3
+ /*
4
+ The one Inngest client: payload.config.ts sends events through it, and the
5
+ endpoint at app/api/inngest serves this site's jobs with it.
6
+ */
7
+ export const inngest = createInngestClient({ id: '{{projectName}}' })
@@ -4,31 +4,17 @@ import { buildConfig } from 'payload'
4
4
  import type { Access, CollectionConfig } from 'payload'
5
5
  import { postgresAdapter } from '@payloadcms/db-postgres'
6
6
  import { lexicalEditor } from '@payloadcms/richtext-lexical'
7
- import {
8
- assertEnvironment,
9
- auditPlugin,
10
- createInngestClient,
11
- createMcpToolCollector,
12
- mcpApiKeyAccess,
13
- } from '@forumone/throughline-core'
7
+ import { assertEnvironment, mcpApiKeyAccess, throughline } from '@forumone/throughline'
8
+ import { inngestJobs } from '@forumone/throughline/jobs/inngest'
14
9
  import { mcpPlugin } from '@payloadcms/plugin-mcp'
15
- import { componentsPlugin } from '@forumone/throughline-components'
16
- import { publishingPlugin } from '@forumone/throughline-publishing'
17
- import {
18
- approvalsEnv,
19
- approvalsPlugin,
20
- buildActionUrl,
21
- generateActionToken,
22
- } from '@forumone/throughline-approvals'
23
- import { auditQueryPlugin } from '@forumone/throughline-audit'
24
- import { integrationsPlugin } from '@forumone/throughline-integrations'
25
- import { emailEnv, emailPlugin } from '@forumone/throughline-email'
26
- import { formsEnv, formsPlugin } from '@forumone/throughline-forms'
27
- import { createTagRevalidationHooks } from '@forumone/throughline-workflows'
10
+ import { approvalsEnv } from '@forumone/throughline/approvals'
11
+ import { emailEnv } from '@forumone/throughline/email'
12
+ import { createPayloadReachableCheck } from '@forumone/throughline/integrations'
13
+ import { createTagRevalidationHooks } from '@forumone/throughline/publishing'
28
14
  import { cacheTags } from './lib/cache-tags'
29
- import { jobFailuresPlugin } from '@forumone/throughline-core/observability'
15
+ import { inngest } from './lib/inngest'
30
16
  import { databaseConnectionString } from './lib/database'
31
- {{#if useReferenceDs}}import type { Manifest } from '@forumone/throughline-design-contract'
17
+ {{#if useReferenceDs}}import type { Manifest } from '@forumone/throughline-design-system/contract'
32
18
  import designSystemManifest from '{{designSystemName}}/manifest' with { type: 'json' }
33
19
  {{/if}}
34
20
 
@@ -51,7 +37,6 @@ and type generation), placeholders of the right length do; see
51
37
  assertEnvironment(
52
38
  approvalsEnv,
53
39
  emailEnv,
54
- formsEnv,
55
40
  {
56
41
  name: 'PAYLOAD_SECRET',
57
42
  minLength: 32,
@@ -70,16 +55,74 @@ assertEnvironment(
70
55
  const __filename = fileURLToPath(import.meta.url)
71
56
  const __dirname = path.dirname(__filename)
72
57
 
73
- const inngest = createInngestClient({ id: '{{projectName}}' })
74
-
75
58
  /*
76
- Where every Throughline server puts its tools for `mcpPlugin`, which serves them
77
- all on one `/api/mcp`. Each plugin declares its tools as the config is built and
78
- binds the handlers at `onInit`; `mcpPlugin` reads this array at both moments.
79
- Hand over `mcpTools.tools` itself — a spread or `.slice()` would hand over
80
- something nobody fills.
59
+ The Throughline suite, in one call. It registers every plugin in the order they
60
+ need, gives each the one MCP tool collector, sends their events through the jobs
61
+ adapter, and lists the jobs these options call for, which app/api/inngest
62
+ serves. One value for each thing two plugins must agree on: the approvals
63
+ collection's slug reaches approvals, the emails and the expiry job alike.
64
+
65
+ A plugin is on when its key is here. Delete a key to turn one off.
81
66
  */
82
- const mcpTools = createMcpToolCollector()
67
+ export const suite = throughline({
68
+ jobs: inngestJobs(inngest),
69
+ // The collections publishing gates, "Your work" lists and scheduling publishes.
70
+ collections: ['pages'],
71
+
72
+ {{#if useReferenceDs}}components: {
73
+ manifest: { type: 'object', manifest: designSystemManifest as unknown as Manifest },
74
+ matching: { strategy: 'tfidf' },
75
+ },
76
+ {{else}}components: {
77
+ // TODO: point at your design system manifest
78
+ manifest: { type: 'url', url: 'https://your-design-system.example.com/manifest.json' },
79
+ matching: { strategy: 'tfidf' },
80
+ },
81
+ {{/if}}publishing: {
82
+ /*
83
+ Where each publishable collection's documents are served, so a publish
84
+ revalidates the right path. There are no defaults: a collection missing here
85
+ has its cache tags dropped but no path revalidated, and the run logs a
86
+ warning. Add an entry for every collection in `collections`.
87
+ */
88
+ urls: { pages: (slug: string) => (slug === 'home' ? '/' : `/${slug}`) },
89
+ revalidation: { cacheTags },
90
+ // Scheduled publishes go through the publishing pipeline, as an editor's
91
+ // would. The backstop catches a schedule whose wake-up event never arrived.
92
+ scheduling: { backstop: { schedule: '17 3 * * *', overdueByMs: 60 * 60 * 1000 } },
93
+ },
94
+
95
+ approvals: {
96
+ groups: [
97
+ { slug: 'editorial', name: 'Editorial' },
98
+ { slug: 'legal', name: 'Legal' },
99
+ { slug: 'communications', name: 'Communications' },
100
+ { slug: 'senior', name: 'Senior leadership' },
101
+ ],
102
+ groupResolver: {
103
+ // TODO: replace with your actual user/group lookup (DB query, SSO, etc.)
104
+ async resolveUsers(_groupSlugs) {
105
+ return []
106
+ },
107
+ },
108
+ },
109
+
110
+ auditQuery: {},
111
+
112
+ // Approval links are signed with APPROVAL_TOKEN_SECRET and point at
113
+ // NEXT_PUBLIC_SERVER_URL, both checked above.
114
+ email: {
115
+ // TODO: replace with your real user lookups
116
+ resolveApprover: async (_userId) => null,
117
+ resolveRequester: async (_userId) => null,
118
+ tokens: { brandName: '{{projectName}}' },
119
+ },
120
+
121
+ integrations: {},
122
+
123
+ // A failing check is recorded in `job-failures` and posted to ERROR_WEBHOOK_URL.
124
+ healthcheck: { checks: [createPayloadReachableCheck()] },
125
+ })
83
126
 
84
127
  /*
85
128
  Drops a Next cache tag when content changes, named by the same scheme your
@@ -109,7 +152,7 @@ const Users: CollectionConfig = {
109
152
  type: 'select',
110
153
  hasMany: true,
111
154
  defaultValue: ['admin'],
112
- options: ['admin', 'editor', 'approver', 'form-admin'],
155
+ options: ['admin', 'editor', 'approver'],
113
156
  },
114
157
  {
115
158
  name: 'groups',
@@ -194,102 +237,14 @@ export default buildConfig({
194
237
  outputFile: path.resolve(__dirname, 'payload-types.ts'),
195
238
  },
196
239
  plugins: [
197
- // Order matters twice. `auditPlugin` first: every other Throughline plugin
198
- // requires the audit log at init. And every tool-bearing plugin before
199
- // `mcpPlugin`, which reads the declared tools when the config is built — a
200
- // plugin registered after it gets no per-key checkbox, and its tools are
201
- // denied to every key with no error anywhere.
202
- auditPlugin({ inngest }),
203
-
204
- // Where a workflow that ran out of retries, or a failing healthcheck, leaves
205
- // a row — see the failure handlers in app/api/inngest/route.ts. Not the
206
- // audit log: that records who did what through an MCP tool.
207
- jobFailuresPlugin({}),
208
-
209
- componentsPlugin({
210
- {{#if useReferenceDs}}manifest: { type: 'object', manifest: designSystemManifest as unknown as Manifest },
211
- {{else}}// TODO: point at your design system manifest
212
- manifest: { type: 'url', url: 'https://your-design-system.example.com/manifest.json' },
213
- {{/if}}matching: { strategy: 'tfidf' },
214
- mcpTools,
215
- }),
216
-
217
- publishingPlugin({
218
- collections: [{ slug: 'pages' }],
219
- inngest,
220
- // Lazy-resolve approvals at call time so plugin order doesn't matter.
221
- approvalResolver: {
222
- getActiveApproval: async () => null, // TODO: wire to approvals plugin
223
- },
224
- mcpTools,
225
- }),
226
-
227
- approvalsPlugin({
228
- inngest,
229
- groups: [
230
- { slug: 'editorial', name: 'Editorial' },
231
- { slug: 'legal', name: 'Legal' },
232
- { slug: 'communications', name: 'Communications' },
233
- { slug: 'senior', name: 'Senior leadership' },
234
- ],
235
- groupResolver: {
236
- // TODO: replace with your actual user/group lookup (DB query, SSO, etc.)
237
- async resolveUsers(_groupSlugs) {
238
- return []
239
- },
240
- },
241
- mcpTools,
242
- }),
243
-
244
- auditQueryPlugin({ mcpTools }),
245
-
246
- emailPlugin({
247
- inngest,
248
- // TODO: replace with your real user lookups
249
- resolveApprover: async (_userId) => null,
250
- resolveRequester: async (_userId) => null,
251
- // Signed, and pointed at the route approvalsPlugin serves
252
- // (`/api/approvals/action`). The token binds one approval, one action
253
- // and one approver, so a link cannot be edited into a different decision.
254
- buildActionUrl: async ({ approvalId, action, approverId }) => {
255
- const secret = process.env.APPROVAL_TOKEN_SECRET
256
- if (!secret) throw new Error('APPROVAL_TOKEN_SECRET is required to sign approval links.')
257
- const token = await generateActionToken(
258
- { approvalId, action, approverId, issuedAt: Date.now() },
259
- secret,
260
- )
261
- return buildActionUrl(process.env.NEXT_PUBLIC_SERVER_URL!, token)
262
- },
263
- tokens: { brandName: '{{projectName}}' },
264
- }),
265
-
266
- formsPlugin({
267
- inngest,
268
- // ipHashSecret: read from FORMS_IP_HASH_SECRET, checked above.
269
- // TODO: list every place a form is allowed to send to, and delete the
270
- // placeholder. The plugin refuses to start with an empty list — the
271
- // allowlist is the security perimeter — and refuses to deliver anywhere
272
- // not on it. example.com is reserved and accepts no mail, so the
273
- // placeholder routes nothing anywhere while it stands.
274
- allowedDestinations: [
275
- {
276
- type: 'email',
277
- value: 'forms@example.com',
278
- label: 'Placeholder',
279
- description: 'Replace before launch: example.com accepts no mail.',
280
- },
281
- ],
282
- mcpTools,
283
- }),
284
-
285
- integrationsPlugin({ inngest, mcpTools }),
240
+ suite.plugin,
286
241
 
287
- // Last of the tool-bearing chain. An MCP key is a credential that runs
288
- // every tool as the person it is bound to, so who may mint, read and
289
- // revoke one is an admin's call: `mcpApiKeyAccess` narrows the key
290
- // collection to `isAdmin`, and refuses a key principal outright.
242
+ // After the suite, whose plugins fill the tools it serves. An MCP key is a
243
+ // credential that runs every tool as the person it is bound to, so who may
244
+ // mint, read and revoke one is an admin's call: `mcpApiKeyAccess` narrows
245
+ // the key collection to `isAdmin`, and refuses a key principal outright.
291
246
  mcpPlugin({
292
- mcp: { tools: mcpTools.tools },
247
+ mcp: { tools: suite.mcpTools },
293
248
  overrideApiKeyCollection: mcpApiKeyAccess(isAdmin),
294
249
  }),
295
250
  ],
@@ -48,7 +48,6 @@ export default defineConfig({
48
48
  DATABASE_URI: 'postgres://test:test@localhost:5432/does-not-exist',
49
49
  PAYLOAD_SECRET: 'test-only-not-a-real-secret-000000000000',
50
50
  APPROVAL_TOKEN_SECRET: 'test-only-not-a-real-secret-000000000000',
51
- FORMS_IP_HASH_SECRET: 'test-only-not-a-real-secret-000000000000',
52
51
  RESEND_API_KEY: 're_test_only_not_a_real_key',
53
52
  EMAIL_FROM_ADDRESS: 'test-only@example.invalid',
54
53
  NEXT_PUBLIC_SERVER_URL: 'http://localhost:3000',
@@ -31,7 +31,6 @@ env:
31
31
  DATABASE_URI: postgres://ci:ci@localhost:5432/ci
32
32
  PAYLOAD_SECRET: ci-placeholder-not-a-secret-000000000000
33
33
  APPROVAL_TOKEN_SECRET: ci-placeholder-not-a-secret-000000000000
34
- FORMS_IP_HASH_SECRET: ci-placeholder-not-a-secret-000000000000
35
34
  RESEND_API_KEY: re_ci_placeholder
36
35
  EMAIL_FROM_ADDRESS: ci@example.com
37
36
  NEXT_PUBLIC_SERVER_URL: http://localhost:3000
@@ -76,6 +75,13 @@ jobs:
76
75
  - name: Validate the design-system manifest
77
76
  run: pnpm validate
78
77
 
78
+ # payload.config.ts imports the design system's built manifest, and the
79
+ # Payload commands below load the config outside turbo, which would
80
+ # otherwise have built it first. A placeholder design system has nothing
81
+ # to build.
82
+ - name: Build the design system
83
+ run: pnpm --filter ./design-system run --if-present build
84
+
79
85
  - name: Check generated Payload artifacts are current
80
86
  run: bash .github/scripts/check-generated.sh
81
87
 
@@ -122,6 +128,13 @@ jobs:
122
128
  - name: Install
123
129
  run: pnpm install --frozen-lockfile
124
130
 
131
+ # payload.config.ts imports the design system's built manifest, and the
132
+ # Payload commands below load the config outside turbo, which would
133
+ # otherwise have built it first. A placeholder design system has nothing
134
+ # to build.
135
+ - name: Build the design system
136
+ run: pnpm --filter ./design-system run --if-present build
137
+
125
138
  # A fresh database, migrated exactly as a deploy would migrate it. A
126
139
  # migration that does not apply cleanly from empty fails here rather than
127
140
  # on the first environment somebody creates.
@@ -14,7 +14,7 @@ dist/
14
14
 
15
15
  coverage/
16
16
 
17
- # Written by throughline-payload (@forumone/throughline-core): the process
17
+ # Written by throughline-payload (@forumone/throughline): the process
18
18
  # group of each in-flight Payload CLI run, so the next run can reap one whose
19
19
  # runner was killed before it could clean up.
20
20
  .payload-cli-pids
@@ -14,7 +14,6 @@
14
14
  "POSTGRES_URL_NON_POOLING",
15
15
  "PAYLOAD_SECRET",
16
16
  "APPROVAL_TOKEN_SECRET",
17
- "FORMS_IP_HASH_SECRET",
18
17
  "RESEND_API_KEY",
19
18
  "EMAIL_*",
20
19
  "INNGEST_*",
@@ -34,7 +34,7 @@
34
34
  "clean": "rm -rf dist .turbo storybook-static *.tsbuildinfo"
35
35
  },
36
36
  "dependencies": {
37
- "@forumone/throughline-design-contract": "^{{version_design_contract}}",
37
+ "@forumone/throughline-design-system": "^{{version_design_system}}",
38
38
  "clsx": "^2.1.1"
39
39
  },
40
40
  "peerDependencies": {
@@ -5,7 +5,7 @@ import {
5
5
  CONTRACT_VERSION,
6
6
  ManifestSchema,
7
7
  type ComponentContract,
8
- } from '@forumone/throughline-design-contract'
8
+ } from '@forumone/throughline-design-system/contract'
9
9
  import { getTokenList } from '../src/tokens/index.ts'
10
10
 
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url))
@@ -1,8 +1,8 @@
1
1
  import { readFile, stat } from 'node:fs/promises'
2
2
  import { dirname, resolve } from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
- import { loadManifest } from '@forumone/throughline-design-contract'
5
- import { formatLintIssues, lintManifest } from '@forumone/throughline-design-contract/lint'
4
+ import { loadManifest } from '@forumone/throughline-design-system/contract'
5
+ import { formatLintIssues, lintManifest } from '@forumone/throughline-design-system/lint'
6
6
 
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url))
8
8
  const packageRoot = resolve(__dirname, '..')
@@ -36,7 +36,7 @@ async function collectStoryIds(): Promise<Set<string> | undefined> {
36
36
  Lint rules this design system treats as failures, though the shared linter
37
37
  reports them as warnings.
38
38
 
39
- `design-contract` sets both to `warning` because it serves every project, and a
39
+ The suite's linter sets both to `warning` because it serves every project, and a
40
40
  project migrating an existing design system onto the contract should not be
41
41
  blocked by prose. A design system written against the contract from the start is
42
42
  never mid-migration, so it starts strict. Relax a rule here, deliberately, if
@@ -1,4 +1,4 @@
1
- import type { ComponentContract } from '@forumone/throughline-design-contract'
1
+ import type { ComponentContract } from '@forumone/throughline-design-system/contract'
2
2
 
3
3
  export const contract: ComponentContract = {
4
4
  name: 'CTASection',
@@ -1,4 +1,4 @@
1
- import type { ComponentContract } from '@forumone/throughline-design-contract'
1
+ import type { ComponentContract } from '@forumone/throughline-design-system/contract'
2
2
 
3
3
  export const contract: ComponentContract = {
4
4
  name: 'Card',
@@ -1,4 +1,4 @@
1
- import type { ComponentContract } from '@forumone/throughline-design-contract'
1
+ import type { ComponentContract } from '@forumone/throughline-design-system/contract'
2
2
 
3
3
  export const contract: ComponentContract = {
4
4
  name: 'CardGrid',
@@ -1,4 +1,4 @@
1
- import type { ComponentContract } from '@forumone/throughline-design-contract'
1
+ import type { ComponentContract } from '@forumone/throughline-design-system/contract'
2
2
 
3
3
  export const contract: ComponentContract = {
4
4
  name: 'Divider',
@@ -1,4 +1,4 @@
1
- import type { ComponentContract } from '@forumone/throughline-design-contract'
1
+ import type { ComponentContract } from '@forumone/throughline-design-system/contract'
2
2
 
3
3
  export const contract: ComponentContract = {
4
4
  name: 'FAQ',
@@ -1,4 +1,4 @@
1
- import type { ComponentContract } from '@forumone/throughline-design-contract'
1
+ import type { ComponentContract } from '@forumone/throughline-design-system/contract'
2
2
 
3
3
  export const contract: ComponentContract = {
4
4
  name: 'Hero',
@@ -1,4 +1,4 @@
1
- import type { ComponentContract } from '@forumone/throughline-design-contract'
1
+ import type { ComponentContract } from '@forumone/throughline-design-system/contract'
2
2
 
3
3
  export const contract: ComponentContract = {
4
4
  name: 'MediaBlock',
@@ -1,4 +1,4 @@
1
- import type { ComponentContract } from '@forumone/throughline-design-contract'
1
+ import type { ComponentContract } from '@forumone/throughline-design-system/contract'
2
2
 
3
3
  export const contract: ComponentContract = {
4
4
  name: 'Prose',
@@ -1,4 +1,4 @@
1
- import type { ComponentContract } from '@forumone/throughline-design-contract'
1
+ import type { ComponentContract } from '@forumone/throughline-design-system/contract'
2
2
 
3
3
  export const contract: ComponentContract = {
4
4
  name: 'Quote',
@@ -1,4 +1,4 @@
1
- import type { ComponentContract } from '@forumone/throughline-design-contract'
1
+ import type { ComponentContract } from '@forumone/throughline-design-system/contract'
2
2
 
3
3
  export const contract: ComponentContract = {
4
4
  name: 'SectionIntro',
@@ -1,4 +1,4 @@
1
- import type { ComponentContract } from '@forumone/throughline-design-contract'
1
+ import type { ComponentContract } from '@forumone/throughline-design-system/contract'
2
2
 
3
3
  export const contract: ComponentContract = {
4
4
  name: 'Spacer',
@@ -1,4 +1,4 @@
1
- import type { ComponentContract } from '@forumone/throughline-design-contract'
1
+ import type { ComponentContract } from '@forumone/throughline-design-system/contract'
2
2
 
3
3
  export const contract: ComponentContract = {
4
4
  name: 'Stats',
@@ -21,7 +21,7 @@ export const allTokens = {
21
21
 
22
22
  export type TokenName = keyof typeof allTokens
23
23
 
24
- /** Returns every token in the shape the design-contract manifest expects. */
24
+ /** Returns every token in the shape the manifest schema (`@forumone/throughline-design-system/contract`) expects. */
25
25
  export function getTokenList() {
26
26
  const categorize = (name: string) => name.split('.')[0] ?? 'other'
27
27
  return Object.entries(allTokens).map(([name, value]) => ({
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The version of every Throughline package the scaffold depends on, keyed for
3
- * the template renderer: `@forumone/throughline-design-contract` becomes
4
- * `version_design_contract`, rendered as `"^{{version_design_contract}}"`.
3
+ * the template renderer: `@forumone/throughline-design-system` becomes
4
+ * `version_design_system`, rendered as `"^{{version_design_system}}"`.
5
5
  *
6
6
  * The ranges used to be typed into the templates by hand, and nothing moved
7
7
  * them: they sat at `^0.2.0` — which for a 0.x version admits patches only —
@@ -15,6 +15,6 @@
15
15
  * no such file and the workspace's own `package.json` files are the answer.
16
16
  */
17
17
  export declare function loadPackageVersions(templatesDir: string): Promise<Record<string, string>>;
18
- /** `{ '@forumone/throughline-core': '0.9.1', … }` from a `packages/` directory. */
18
+ /** `{ '@forumone/throughline': '1.0.0', … }` from a `packages/` directory. */
19
19
  export declare function readWorkspaceVersions(packagesDir: string): Promise<Record<string, string>>;
20
20
  //# sourceMappingURL=versions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"versions.d.ts","sourceRoot":"","sources":["../../src/utils/versions.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAO/F;AAED,mFAAmF;AACnF,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA0BhG"}
1
+ {"version":3,"file":"versions.d.ts","sourceRoot":"","sources":["../../src/utils/versions.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAO/F;AAED,8EAA8E;AAC9E,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA0BhG"}
@@ -2,10 +2,12 @@ import { existsSync } from 'node:fs';
2
2
  import { readFile, readdir } from 'node:fs/promises';
3
3
  import { join, resolve } from 'node:path';
4
4
  const SCOPE_PREFIX = '@forumone/throughline-';
5
+ /** The one package whose name has no suffix: `version_throughline`. */
6
+ const BARE_NAME = '@forumone/throughline';
5
7
  /**
6
8
  * The version of every Throughline package the scaffold depends on, keyed for
7
- * the template renderer: `@forumone/throughline-design-contract` becomes
8
- * `version_design_contract`, rendered as `"^{{version_design_contract}}"`.
9
+ * the template renderer: `@forumone/throughline-design-system` becomes
10
+ * `version_design_system`, rendered as `"^{{version_design_system}}"`.
9
11
  *
10
12
  * The ranges used to be typed into the templates by hand, and nothing moved
11
13
  * them: they sat at `^0.2.0` — which for a 0.x version admits patches only —
@@ -26,7 +28,7 @@ export async function loadPackageVersions(templatesDir) {
26
28
  // src/templates or dist/templates -> the package -> packages/
27
29
  return templateKeys(await readWorkspaceVersions(resolve(templatesDir, '..', '..', '..')));
28
30
  }
29
- /** `{ '@forumone/throughline-core': '0.9.1', … }` from a `packages/` directory. */
31
+ /** `{ '@forumone/throughline': '1.0.0', … }` from a `packages/` directory. */
30
32
  export async function readWorkspaceVersions(packagesDir) {
31
33
  const versions = {};
32
34
  const entries = await readdir(packagesDir, { withFileTypes: true }).catch(() => []);
@@ -37,22 +39,26 @@ export async function readWorkspaceVersions(packagesDir) {
37
39
  if (!existsSync(manifest))
38
40
  continue;
39
41
  const { name, version, private: isPrivate, } = JSON.parse(await readFile(manifest, 'utf-8'));
40
- if (!name?.startsWith(SCOPE_PREFIX) || !version || isPrivate)
42
+ if (!name || !isThroughline(name) || !version || isPrivate)
41
43
  continue;
42
44
  versions[name] = version;
43
45
  }
44
46
  if (Object.keys(versions).length === 0) {
45
47
  throw new Error(`create-throughline: no package versions found. Expected versions.json beside the ` +
46
- `templates, or @forumone/throughline-* packages in ${packagesDir}.`);
48
+ `templates, or @forumone/throughline packages in ${packagesDir}.`);
47
49
  }
48
50
  return versions;
49
51
  }
52
+ function isThroughline(name) {
53
+ return name === BARE_NAME || name.startsWith(SCOPE_PREFIX);
54
+ }
50
55
  function templateKeys(versions) {
51
56
  const keys = {};
52
57
  for (const [name, version] of Object.entries(versions)) {
53
- if (!name.startsWith(SCOPE_PREFIX))
54
- continue;
55
- keys[`version_${name.slice(SCOPE_PREFIX.length).replace(/-/g, '_')}`] = version;
58
+ if (name === BARE_NAME)
59
+ keys['version_throughline'] = version;
60
+ else if (name.startsWith(SCOPE_PREFIX))
61
+ keys[`version_${name.slice(SCOPE_PREFIX.length).replace(/-/g, '_')}`] = version;
56
62
  }
57
63
  return keys;
58
64
  }
@@ -1 +1 @@
1
- {"version":3,"file":"versions.js","sourceRoot":"","sources":["../../src/utils/versions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEzC,MAAM,YAAY,GAAG,wBAAwB,CAAA;AAE7C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,YAAoB;IAC5D,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,EAAE,IAAI,EAAE,eAAe,CAAC,CAAA;IAC1D,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAA2B,CAAC,CAAA;IAC3F,CAAC;IACD,8DAA8D;IAC9D,OAAO,YAAY,CAAC,MAAM,qBAAqB,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;AAC3F,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,WAAmB;IAC7D,MAAM,QAAQ,GAA2B,EAAE,CAAA;IAC3C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IACnF,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAQ;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC,CAAA;QAC9D,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,SAAQ;QACnC,MAAM,EACJ,IAAI,EACJ,OAAO,EACP,OAAO,EAAE,SAAS,GACnB,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAI/C,CAAA;QACD,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,IAAI,SAAS;YAAE,SAAQ;QACtE,QAAQ,CAAC,IAAI,CAAC,GAAG,OAAO,CAAA;IAC1B,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CACb,mFAAmF;YACjF,qDAAqD,WAAW,GAAG,CACtE,CAAA;IACH,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,SAAS,YAAY,CAAC,QAAgC;IACpD,MAAM,IAAI,GAA2B,EAAE,CAAA;IACvC,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;YAAE,SAAQ;QAC5C,IAAI,CAAC,WAAW,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAA;IACjF,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"versions.js","sourceRoot":"","sources":["../../src/utils/versions.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,4 @@
1
1
  {
2
- "@forumone/throughline-approvals": "0.9.0",
3
- "@forumone/throughline-audit": "0.6.0",
4
- "@forumone/throughline-components": "0.6.0",
5
- "@forumone/throughline-core": "0.10.0",
6
- "@forumone/throughline-design-contract": "0.6.0",
7
- "@forumone/throughline-email": "0.3.0",
8
- "@forumone/throughline-forms": "0.7.0",
9
- "@forumone/throughline-integrations": "0.9.0",
10
- "@forumone/throughline-plugin-contract": "0.5.0",
11
- "@forumone/throughline-publishing": "0.11.0",
12
- "@forumone/throughline-reference-ds": "0.3.7",
13
- "@forumone/throughline-workflows": "0.5.0"
2
+ "@forumone/throughline-design-system": "1.0.0-next.2",
3
+ "@forumone/throughline": "1.0.0-next.2"
14
4
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forumone/create-throughline",
3
- "version": "0.5.0",
3
+ "version": "1.0.0-next.2",
4
4
  "description": "Scaffolder for new Throughline projects (Payload CMS + MCP servers + Inngest workflows).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,8 +50,8 @@
50
50
  "tsx": "^4.21.0",
51
51
  "typescript": "^5.6.0",
52
52
  "vitest": "^4.1.10",
53
- "@forumone/throughline-eslint-config": "0.0.0",
54
- "@forumone/throughline-tsconfig": "0.0.0"
53
+ "@forumone/throughline-tsconfig": "0.0.0",
54
+ "@forumone/throughline-eslint-config": "0.0.0"
55
55
  },
56
56
  "engines": {
57
57
  "node": ">=20.9.0"