@forumone/create-throughline 0.3.0 → 0.4.0

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 (32) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/generator.d.ts.map +1 -1
  3. package/dist/generator.js +7 -1
  4. package/dist/generator.js.map +1 -1
  5. package/dist/post-install.d.ts.map +1 -1
  6. package/dist/post-install.js +10 -3
  7. package/dist/post-install.js.map +1 -1
  8. package/dist/templates/base/.env.example +9 -13
  9. package/dist/templates/base/README.md +63 -11
  10. package/dist/templates/base/apps/web/package.json.template +21 -12
  11. package/dist/templates/base/apps/web/scripts/check-migrations.ts +147 -0
  12. package/dist/templates/base/apps/web/scripts/lib/target.ts +95 -0
  13. package/dist/templates/base/apps/web/scripts/write-migration-snapshot.ts +119 -0
  14. package/dist/templates/base/apps/web/src/app/api/inngest/route.ts.template +40 -4
  15. package/dist/templates/base/apps/web/src/lib/database.ts +158 -0
  16. package/dist/templates/base/apps/web/src/payload.config.ts.template +90 -20
  17. package/dist/templates/base/github/scripts/check-generated.sh +44 -0
  18. package/dist/templates/base/github/workflows/ci.yml +131 -0
  19. package/dist/templates/base/package.json.template +6 -1
  20. package/dist/templates/base/scripts/check-audit.ts +117 -0
  21. package/dist/templates/base/scripts/check-single-instance.ts +113 -0
  22. package/dist/templates/base/scripts/check-tested-packages.ts +165 -0
  23. package/dist/templates/base/turbo.json +16 -1
  24. package/dist/templates/with-reference-ds/design-system/package.json.template +3 -3
  25. package/dist/templates/with-reference-ds/design-system/scripts/build-manifest.ts +59 -29
  26. package/dist/templates/with-reference-ds/design-system/scripts/validate.ts +26 -1
  27. package/dist/utils/versions.d.ts +20 -0
  28. package/dist/utils/versions.d.ts.map +1 -0
  29. package/dist/utils/versions.js +59 -0
  30. package/dist/utils/versions.js.map +1 -0
  31. package/dist/versions.json +14 -0
  32. package/package.json +6 -6
@@ -0,0 +1,131 @@
1
+ name: CI
2
+
3
+ # Two tiers.
4
+ #
5
+ # fast every pull request and every push to main. No database: every gate
6
+ # here needs the config to parse, not to connect.
7
+ # verify pushes to main, and on demand. A Postgres service, the migrations,
8
+ # and a production build — `next build` boots Payload to collect page
9
+ # data, so it needs a migrated database.
10
+ #
11
+ # A green `fast` is what a pull request is reviewed on; `verify` is what says
12
+ # main still builds and migrates. Add a path filter to run `verify` on pull
13
+ # requests that touch the config, the migrations or the dependencies, once this
14
+ # project has enough of all three to make it worth the minutes.
15
+
16
+ on:
17
+ pull_request:
18
+ push:
19
+ branches: [main]
20
+ workflow_dispatch:
21
+
22
+ concurrency:
23
+ group: ci-${{ github.ref }}
24
+ cancel-in-progress: true
25
+
26
+ # What payload.config.ts and the plugins check at load, as placeholders. The
27
+ # config parses the connection string and never connects in `fast`; secrets only
28
+ # have to be long enough to pass the plugins' own checks. Never a real value.
29
+ env:
30
+ DATABASE_URI: postgres://ci:ci@localhost:5432/ci
31
+ PAYLOAD_SECRET: ci-placeholder-not-a-secret-000000000000
32
+ APPROVAL_TOKEN_SECRET: ci-placeholder-not-a-secret-000000000000
33
+ FORMS_IP_HASH_SECRET: ci-placeholder-not-a-secret-000000000000
34
+ RESEND_API_KEY: re_ci_placeholder
35
+ EMAIL_FROM_ADDRESS: ci@example.com
36
+ NEXT_PUBLIC_SERVER_URL: http://localhost:3000
37
+
38
+ jobs:
39
+ fast:
40
+ runs-on: ubuntu-latest
41
+ timeout-minutes: 20
42
+ steps:
43
+ - uses: actions/checkout@v4
44
+
45
+ - uses: pnpm/action-setup@v4
46
+
47
+ - uses: actions/setup-node@v4
48
+ with:
49
+ node-version: 22
50
+ cache: pnpm
51
+
52
+ # First, because a lockfile missing an entry passes every other gate
53
+ # locally and fails here.
54
+ - name: Install
55
+ run: pnpm install --frozen-lockfile
56
+
57
+ - name: Typecheck
58
+ run: pnpm typecheck
59
+
60
+ - name: Lint
61
+ run: pnpm lint
62
+
63
+ - name: Check singletons resolve once
64
+ run: pnpm check:instances
65
+
66
+ - name: Check every package that ships code has tests
67
+ run: pnpm check:tested
68
+
69
+ - name: Check the migration chain
70
+ run: pnpm check:migrations
71
+
72
+ # Builds the design system's manifest and lints it against the contract:
73
+ # an unknown token, a composition rule naming a missing component, an
74
+ # example pointing at a story that does not exist.
75
+ - name: Validate the design-system manifest
76
+ run: pnpm validate
77
+
78
+ - name: Check generated Payload artifacts are current
79
+ run: bash .github/scripts/check-generated.sh
80
+
81
+ - name: Test
82
+ run: pnpm test
83
+
84
+ # Non-blocking: a new advisory is news, not necessarily a defect, and
85
+ # blocking every open pull request the morning a CVE is published is how
86
+ # a gate gets removed rather than fixed. Read it.
87
+ - name: Audit production dependencies
88
+ run: pnpm check:audit
89
+ continue-on-error: true
90
+
91
+ verify:
92
+ if: github.event_name != 'pull_request'
93
+ runs-on: ubuntu-latest
94
+ timeout-minutes: 25
95
+
96
+ services:
97
+ postgres:
98
+ image: postgres:17-alpine
99
+ env:
100
+ POSTGRES_USER: ci
101
+ POSTGRES_PASSWORD: ci
102
+ POSTGRES_DB: ci
103
+ ports:
104
+ - 5432:5432
105
+ options: >-
106
+ --health-cmd pg_isready
107
+ --health-interval 10s
108
+ --health-timeout 5s
109
+ --health-retries 5
110
+
111
+ steps:
112
+ - uses: actions/checkout@v4
113
+
114
+ - uses: pnpm/action-setup@v4
115
+
116
+ - uses: actions/setup-node@v4
117
+ with:
118
+ node-version: 22
119
+ cache: pnpm
120
+
121
+ - name: Install
122
+ run: pnpm install --frozen-lockfile
123
+
124
+ # A fresh database, migrated exactly as a deploy would migrate it. A
125
+ # migration that does not apply cleanly from empty fails here rather than
126
+ # on the first environment somebody creates.
127
+ - name: Migrate
128
+ run: pnpm --dir apps/web migrate
129
+
130
+ - name: Build
131
+ run: pnpm build
@@ -7,14 +7,19 @@
7
7
  "dev": "{{#if useReferenceDs}}pnpm --filter \"./design-system\" build && {{/if}}turbo run dev",
8
8
  "storybook": "turbo run storybook",
9
9
  "build-storybook": "turbo run build-storybook",
10
- "validate": "turbo run validate",
10
+ "validate": "turbo run validate --filter=./design-system",
11
11
  "lint": "turbo run lint",
12
12
  "typecheck": "turbo run typecheck",
13
13
  "test": "turbo run test",
14
+ "check:instances": "tsx scripts/check-single-instance.ts",
15
+ "check:tested": "tsx scripts/check-tested-packages.ts",
16
+ "check:audit": "tsx scripts/check-audit.ts",
17
+ "check:migrations": "pnpm --dir apps/web run check:migrations",
14
18
  "format": "prettier --write ."
15
19
  },
16
20
  "devDependencies": {
17
21
  "prettier": "^3.4.0",
22
+ "tsx": "^4.21.0",
18
23
  "turbo": "^2.3.0",
19
24
  "typescript": "^5.6.0"
20
25
  },
@@ -0,0 +1,117 @@
1
+ import { execFileSync } from 'node:child_process'
2
+
3
+ /*
4
+ `pnpm audit` over the production tree, with an allowlist that cannot rot.
5
+
6
+ pnpm check:audit
7
+
8
+ **Why not `pnpm audit --prod --audit-level=high` on its own.** A Payload + Next
9
+ production tree is rarely clean: there are usually high advisories in it that no
10
+ request the site serves can reach. A gate that is red on every run is a gate
11
+ people stop reading.
12
+
13
+ **Why not `pnpm.auditConfig.ignoreGhsas`.** It filters, and that is all it does.
14
+ A list of ids in `package.json` carries no reason, and nothing notices when one
15
+ stops applying — the dependency is dropped or patched, and the entry sits there
16
+ suppressing a finding it no longer describes.
17
+
18
+ So this asserts the allowlist in both directions. An advisory that is not on the
19
+ list is reported. An entry that no longer appears in the audit is *also*
20
+ reported, as something to delete. That second half is why this is a script.
21
+
22
+ It is non-blocking in CI. A new advisory is news, not necessarily a defect, and
23
+ blocking every open pull request the morning a CVE is published is how a gate
24
+ gets removed rather than fixed.
25
+ */
26
+
27
+ interface Allowed {
28
+ ghsa: string
29
+ module: string
30
+ /** Which path it is on, and why that path cannot reach a request. */
31
+ why: string
32
+ }
33
+
34
+ /**
35
+ * High and critical advisories in the production tree that are accepted.
36
+ *
37
+ * "Not exploitable" is not a reason. Say which dependency path it arrives on
38
+ * and what would have to be true for a request to reach it.
39
+ */
40
+ const ALLOWED: Allowed[] = []
41
+
42
+ interface Advisory {
43
+ severity: string
44
+ module_name: string
45
+ title: string
46
+ github_advisory_id?: string
47
+ }
48
+
49
+ function audit(): Advisory[] {
50
+ let stdout: string
51
+ try {
52
+ // Exits non-zero whenever it finds anything at or above the level, so the
53
+ // throw is the normal case and the output is on it.
54
+ stdout = execFileSync('pnpm', ['audit', '--prod', '--audit-level=high', '--json'], {
55
+ encoding: 'utf8',
56
+ stdio: ['ignore', 'pipe', 'ignore'],
57
+ })
58
+ } catch (error) {
59
+ stdout = (error as { stdout?: string }).stdout ?? ''
60
+ }
61
+
62
+ if (stdout.trim() === '') return []
63
+ const parsed = JSON.parse(stdout) as { advisories?: Record<string, Advisory> }
64
+ // One advisory reached through several dependency paths is reported once
65
+ // per path; it is one finding.
66
+ const unique = new Map<string, Advisory>()
67
+ for (const advisory of Object.values(parsed.advisories ?? {})) {
68
+ if (advisory.severity !== 'high' && advisory.severity !== 'critical') continue
69
+ unique.set(advisory.github_advisory_id ?? `${advisory.module_name}:${advisory.title}`, advisory)
70
+ }
71
+ return [...unique.values()]
72
+ }
73
+
74
+ function main(): void {
75
+ const found = audit()
76
+ const seen = new Set(found.map((a) => a.github_advisory_id).filter(Boolean) as string[])
77
+ const allowed = new Map(ALLOWED.map((entry) => [entry.ghsa, entry]))
78
+
79
+ const unexpected = found.filter(
80
+ (a) => !a.github_advisory_id || !allowed.has(a.github_advisory_id),
81
+ )
82
+ const stale = ALLOWED.filter((entry) => !seen.has(entry.ghsa))
83
+
84
+ for (const advisory of found) {
85
+ if (advisory.github_advisory_id && allowed.has(advisory.github_advisory_id)) {
86
+ console.log(` = ${advisory.module_name.padEnd(18)} ${advisory.github_advisory_id} accepted`)
87
+ }
88
+ }
89
+
90
+ if (unexpected.length > 0) {
91
+ console.log(`\n${unexpected.length} advisory/advisories not on the allowlist:\n`)
92
+ for (const advisory of unexpected) {
93
+ console.log(` ! ${advisory.severity.padEnd(8)} ${advisory.module_name}`)
94
+ console.log(` ${advisory.github_advisory_id ?? '(no GHSA id)'} — ${advisory.title}`)
95
+ }
96
+ console.log(
97
+ '\nEither upgrade past it, or add it to ALLOWED in scripts/check-audit.ts with the\n' +
98
+ 'dependency path it arrives on and why no request can reach it.',
99
+ )
100
+ }
101
+
102
+ if (stale.length > 0) {
103
+ console.log(
104
+ `\n${stale.length} allowlist entry/entries no longer apply and should be deleted:\n`,
105
+ )
106
+ for (const entry of stale) console.log(` - ${entry.ghsa} (${entry.module})`)
107
+ }
108
+
109
+ if (unexpected.length === 0 && stale.length === 0) {
110
+ console.log(`\nNothing new. ${found.length} known advisory/advisories, each with a reason.`)
111
+ return
112
+ }
113
+
114
+ process.exitCode = 1
115
+ }
116
+
117
+ main()
@@ -0,0 +1,113 @@
1
+ import { execFileSync } from 'node:child_process'
2
+
3
+ /*
4
+ One copy of each package that carries React context or is a singleton.
5
+
6
+ pnpm check:instances
7
+
8
+ **The failure this exists for.** pnpm keys a package's identity on its version
9
+ *and* its resolved peers. When two workspace packages resolve a peer differently
10
+ — one on `@types/node@^20`, another on `^24`, say — pnpm installs the same
11
+ version of `@payloadcms/ui` twice, in two directories. Two directories are two
12
+ module instances, two instances are two React contexts, and a component that
13
+ reads the context the provider did not populate gets `undefined`. In the admin
14
+ that is an intermittent 500 on one view while every other view works.
15
+
16
+ **Nothing else can see it.** `pnpm install` succeeds, typecheck, lint and every
17
+ unit test pass, because the two copies are byte-identical: the problem exists
18
+ only at module-resolution time, and only matters at render time. So this asserts
19
+ what no other gate can — that each package below resolves to exactly one
20
+ instance across the workspace. A version range is not the thing to check; the
21
+ instance is.
22
+
23
+ When it fails, compare the peers of the packages that pull in each copy and pin
24
+ the offender in `pnpm.overrides` in the root `package.json`.
25
+ */
26
+
27
+ /**
28
+ * Packages where a second copy is a bug rather than waste: each holds React
29
+ * context or is a singleton by construction, so two copies fail at runtime.
30
+ */
31
+ const SINGLETONS = ['@payloadcms/ui', 'payload', 'react', 'react-dom']
32
+
33
+ /**
34
+ * Copies that are real but cannot reach a bundle, by version, with the reason.
35
+ * An entry here is a claim that nothing you ship imports that copy.
36
+ */
37
+ const ACCEPTED: Record<string, string[]> = {}
38
+
39
+ interface Instance {
40
+ version: string
41
+ path: string
42
+ }
43
+
44
+ /** Every resolved copy of `name`, by reading pnpm's own resolution. */
45
+ function instances(name: string): Instance[] {
46
+ const stdout = execFileSync(
47
+ 'pnpm',
48
+ ['ls', name, '--recursive', '--depth', 'Infinity', '--json'],
49
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 64 * 1024 * 1024 },
50
+ )
51
+
52
+ const found = new Map<string, Instance>()
53
+ const visit = (deps: Record<string, unknown> | undefined): void => {
54
+ for (const [depName, raw] of Object.entries(deps ?? {})) {
55
+ const info = raw as {
56
+ version?: string
57
+ path?: string
58
+ dependencies?: Record<string, unknown>
59
+ }
60
+ if (!info || typeof info !== 'object') continue
61
+ if (depName === name && info.path) {
62
+ found.set(info.path, { version: info.version ?? '?', path: info.path })
63
+ }
64
+ visit(info.dependencies)
65
+ }
66
+ }
67
+
68
+ for (const pkg of JSON.parse(stdout) as Record<string, unknown>[]) {
69
+ visit(pkg['dependencies'] as Record<string, unknown> | undefined)
70
+ visit(pkg['devDependencies'] as Record<string, unknown> | undefined)
71
+ }
72
+ return [...found.values()]
73
+ }
74
+
75
+ function main(): void {
76
+ let failed = false
77
+
78
+ for (const name of SINGLETONS) {
79
+ const all = instances(name)
80
+ const accepted = ACCEPTED[name] ?? []
81
+ const live = all.filter((i) => !accepted.includes(i.version))
82
+
83
+ if (live.length <= 1) {
84
+ const note = all.length > live.length ? ` (+${all.length - live.length} accepted)` : ''
85
+ console.log(` = ${name.padEnd(20)} one instance${note}`)
86
+ continue
87
+ }
88
+
89
+ failed = true
90
+ console.log(`\n ! ${name} resolves to ${live.length} instances:\n`)
91
+ for (const instance of live) console.log(` ${instance.version} ${instance.path}`)
92
+ }
93
+
94
+ if (!failed) {
95
+ console.log('\nEvery singleton resolves once.')
96
+ return
97
+ }
98
+
99
+ console.log(
100
+ [
101
+ '',
102
+ 'Two copies of the same version are still two modules, and two modules holding',
103
+ 'React context is a runtime failure no type or test can see.',
104
+ '',
105
+ 'The usual cause is a peer that differs between workspace packages, which changes',
106
+ "pnpm's identity hash for the dependency. Compare the peers and pin the offender",
107
+ 'in `pnpm.overrides`.',
108
+ ].join('\n'),
109
+ )
110
+ process.exitCode = 1
111
+ }
112
+
113
+ main()
@@ -0,0 +1,165 @@
1
+ import { execFileSync } from 'node:child_process'
2
+ import { existsSync, readdirSync } from 'node:fs'
3
+ import { join, relative } from 'node:path'
4
+
5
+ /*
6
+ Every workspace package that ships code has at least one test.
7
+
8
+ pnpm check:tested
9
+
10
+ Deliberately not a coverage percentage. A number invites a target, and a target
11
+ invites tests written to move it. What this asserts is the one bit a coverage
12
+ report hides: a package with source and **no test file at all**, which looks
13
+ exactly like a package whose tests pass. Defects in a code generator, a
14
+ serialiser or a set of access rules then surface downstream, one at a time, long
15
+ after the package that caused them.
16
+ */
17
+
18
+ /**
19
+ * Packages with source files and no tests, keyed by their path from the repo
20
+ * root, with the reason.
21
+ *
22
+ * This is the whole exception list. An entry is a claim that the package holds
23
+ * no behaviour worth asserting, not that testing it is inconvenient. An entry
24
+ * whose package has since gained a test fails the check, so each one is removed
25
+ * by the same change that makes it untrue.
26
+ */
27
+ const ACCEPTED: Record<string, string> = {
28
+ 'apps/web':
29
+ 'Freshly scaffolded: the config and routes are generated wiring. Delete this entry with your first test here — an access rule, a hook or a field validation is the place to start.',
30
+ {{#if useReferenceDs}}{{else}} 'design-system':
31
+ 'Placeholder until the design system is written. Delete this entry with its first component.',
32
+ {{/if}}}
33
+
34
+ /** Directories that never hold source worth testing. */
35
+ const IGNORED_DIRS = new Set([
36
+ 'node_modules',
37
+ 'dist',
38
+ 'build',
39
+ '.next',
40
+ '.turbo',
41
+ 'storybook-static',
42
+ 'coverage',
43
+ '__snapshots__',
44
+ ])
45
+
46
+ const SOURCE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']
47
+
48
+ const isTest = (name: string): boolean => /\.(test|spec)\.[cm]?[jt]sx?$/.test(name)
49
+
50
+ const isSource = (name: string): boolean =>
51
+ SOURCE_EXTENSIONS.some((extension) => name.endsWith(extension)) &&
52
+ !isTest(name) &&
53
+ !name.endsWith('.d.ts')
54
+
55
+ interface Counts {
56
+ source: number
57
+ tests: number
58
+ }
59
+
60
+ /**
61
+ * Source and test files under a package's own `src`. `src` rather than the
62
+ * package root, so a package cannot pass on the strength of its own build
63
+ * tooling. A package with no `src` ships no code and is not asked.
64
+ */
65
+ function countUnder(packagePath: string): Counts | undefined {
66
+ const src = join(packagePath, 'src')
67
+ if (!existsSync(src)) return undefined
68
+
69
+ const counts: Counts = { source: 0, tests: 0 }
70
+ const walk = (dir: string): void => {
71
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
72
+ if (IGNORED_DIRS.has(entry.name)) continue
73
+ const full = join(dir, entry.name)
74
+ if (entry.isDirectory()) walk(full)
75
+ else if (isTest(entry.name)) counts.tests += 1
76
+ else if (isSource(entry.name)) counts.source += 1
77
+ }
78
+ }
79
+ walk(src)
80
+ return counts
81
+ }
82
+
83
+ interface WorkspacePackage {
84
+ name: string
85
+ path: string
86
+ }
87
+
88
+ function workspacePackages(): WorkspacePackage[] {
89
+ const stdout = execFileSync('pnpm', ['ls', '--recursive', '--depth', '-1', '--json'], {
90
+ encoding: 'utf8',
91
+ stdio: ['ignore', 'pipe', 'ignore'],
92
+ maxBuffer: 64 * 1024 * 1024,
93
+ })
94
+ return (JSON.parse(stdout) as WorkspacePackage[]).filter((pkg) => Boolean(pkg.name && pkg.path))
95
+ }
96
+
97
+ function main(): void {
98
+ const root = process.cwd()
99
+ const untested: string[] = []
100
+ const staleExceptions: string[] = []
101
+ let checked = 0
102
+
103
+ for (const pkg of workspacePackages()) {
104
+ const counts = countUnder(pkg.path)
105
+ if (!counts || counts.source === 0) continue
106
+
107
+ checked += 1
108
+ const where = relative(root, pkg.path) || '.'
109
+ const accepted = ACCEPTED[where]
110
+
111
+ if (counts.tests > 0) {
112
+ // An exception that has stopped applying reads, in review, as though
113
+ // somebody decided this package needs no tests.
114
+ if (accepted) staleExceptions.push(where)
115
+ console.log(
116
+ ` = ${where.padEnd(32)} ${String(counts.tests).padStart(3)} test files ` +
117
+ `${String(counts.source).padStart(4)} source`,
118
+ )
119
+ continue
120
+ }
121
+
122
+ if (accepted) {
123
+ console.log(` - ${where.padEnd(32)} no tests, accepted`)
124
+ continue
125
+ }
126
+
127
+ untested.push(where)
128
+ console.log(` ! ${where.padEnd(32)} 0 test files ${String(counts.source).padStart(4)} source`)
129
+ }
130
+
131
+ if (staleExceptions.length > 0) {
132
+ console.log(
133
+ [
134
+ '',
135
+ `${staleExceptions.length} package(s) are on the accepted list and now have tests:`,
136
+ '',
137
+ ...staleExceptions.map((where) => ` ${where}`),
138
+ '',
139
+ 'Delete the entry from ACCEPTED in scripts/check-tested-packages.ts.',
140
+ ].join('\n'),
141
+ )
142
+ process.exitCode = 1
143
+ }
144
+
145
+ if (untested.length === 0) {
146
+ if (!process.exitCode) console.log(`\nAll ${checked} packages that ship code have tests.`)
147
+ return
148
+ }
149
+
150
+ console.log(
151
+ [
152
+ '',
153
+ `${untested.length} package(s) ship code with no test file:`,
154
+ '',
155
+ ...untested.map((where) => ` ${where}`),
156
+ '',
157
+ 'One test is the bar, not a coverage number. If the package genuinely holds no',
158
+ 'runtime behaviour, add it to ACCEPTED in scripts/check-tested-packages.ts with',
159
+ 'the reason.',
160
+ ].join('\n'),
161
+ )
162
+ process.exitCode = 1
163
+ }
164
+
165
+ main()
@@ -5,7 +5,22 @@
5
5
  "build": {
6
6
  "dependsOn": ["^build"],
7
7
  "outputs": ["dist/**", ".next/**", "!.next/cache/**"],
8
- "env": ["NODE_ENV"]
8
+ "env": [
9
+ "NODE_ENV",
10
+ "DATABASE_URI",
11
+ "DATABASE_URL",
12
+ "DATABASE_URL_UNPOOLED",
13
+ "POSTGRES_URL",
14
+ "POSTGRES_URL_NON_POOLING",
15
+ "PAYLOAD_SECRET",
16
+ "APPROVAL_TOKEN_SECRET",
17
+ "FORMS_IP_HASH_SECRET",
18
+ "RESEND_API_KEY",
19
+ "EMAIL_*",
20
+ "INNGEST_*",
21
+ "BLOB_READ_WRITE_TOKEN",
22
+ "NEXT_PUBLIC_*"
23
+ ]
9
24
  },
10
25
  "build-storybook": {
11
26
  "dependsOn": ["^build"],
@@ -34,7 +34,7 @@
34
34
  "clean": "rm -rf dist .turbo storybook-static *.tsbuildinfo"
35
35
  },
36
36
  "dependencies": {
37
- "@forumone/throughline-design-contract": "^0.2.0",
37
+ "@forumone/throughline-design-contract": "^{{version_design_contract}}",
38
38
  "clsx": "^2.1.1"
39
39
  },
40
40
  "peerDependencies": {
@@ -44,7 +44,7 @@
44
44
  "devDependencies": {
45
45
  "@storybook/addon-a11y": "^10.3.5",
46
46
  "@storybook/react-vite": "^10.3.5",
47
- "@testing-library/jest-dom": "^6.9.1",
47
+ "@testing-library/jest-dom": "^7.0.1",
48
48
  "@testing-library/react": "^16.3.2",
49
49
  "@types/node": "^20.17.0",
50
50
  "@types/react": "^19.2.0",
@@ -57,6 +57,6 @@
57
57
  "tsx": "^4.21.0",
58
58
  "typescript": "^5.6.0",
59
59
  "vite": "^8.0.9",
60
- "vitest": "^2.1.0"
60
+ "vitest": "^4.1.10"
61
61
  }
62
62
  }