@wular/pnext 0.0.21 → 0.0.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wular/pnext",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "description": "A fast little framework for server-first React apps, fully compatible with Next.js",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/create.ts CHANGED
@@ -27,11 +27,11 @@ export async function createApp(dir: string | undefined, options: CreateAppOptio
27
27
  const name = sanitizePackageName(path.basename(target))
28
28
  console.log(`${cyan('▲')} ${bold('pnext create')} ${dim(`— scaffolding ${name}`)}\n`)
29
29
 
30
- const files = scaffoldFiles(name, await latestPnextVersionRange('create'))
30
+ const files = await scaffoldFiles(name, await latestPnextVersionRange('create'))
31
31
  await Promise.all(
32
32
  Object.entries(files).map(([file, source]) => Bun.write(path.join(target, file), source)),
33
33
  )
34
- for (const file of Object.keys(files)) console.log(` ${green('+')} ${dim(file)}`)
34
+ for (const file of Object.keys(files).sort()) console.log(` ${green('+')} ${dim(file)}`)
35
35
 
36
36
  let installed = false
37
37
  if (options.install) {
@@ -112,43 +112,28 @@ function printNextSteps(dir: string, installed: boolean) {
112
112
  console.log(dim(`└${'─'.repeat(width + 2)}┘`))
113
113
  }
114
114
 
115
- function scaffoldFiles(name: string, pnextRange: string): Record<string, string> {
116
- return {
117
- 'package.json': `${JSON.stringify(
118
- {
119
- name,
120
- private: true,
121
- scripts: {
122
- dev: 'pnext dev',
123
- build: 'pnext build',
124
- start: 'pnext start',
125
- analyze: 'pnext analyze',
126
- },
127
- dependencies: { preact: '^10' },
128
- devDependencies: {
129
- '@wular/pnext': pnextRange,
130
- typescript: '^5',
131
- '@types/bun': '^1',
132
- },
133
- },
134
- null,
135
- 2,
136
- )}\n`,
137
- 'pnext.config.ts': `// See node_modules/@wular/pnext/reference/config.md for available options.\nexport default {};\n`,
138
- 'tsconfig.json': `${JSON.stringify(
139
- {
140
- extends: '@wular/pnext/config/ts/react.json',
141
- compilerOptions: { jsxImportSource: 'preact', paths: { '#gen/*': ['./.pnext/types/*'] } },
142
- include: ['**/*.ts', '**/*.tsx', '.pnext/types/**/*.ts'],
143
- },
144
- null,
145
- 2,
146
- )}\n`,
147
- 'app/layout.tsx': `import type { LayoutProps } from '@wular/pnext';\nimport './globals.css';\n\nexport const metadata = {\n title: '${name}',\n};\n\nexport default function RootLayout({ children }: LayoutProps) {\n return (\n <html>\n <body>{children}</body>\n </html>\n );\n}\n`,
148
- 'app/page.tsx': `import Counter from './counter';\n\nexport default async function Home() {\n return (\n <>\n <h1>Welcome to ${name}</h1>\n <p>Edit app/page.tsx to get started.</p>\n <Counter />\n </>\n );\n}\n`,
149
- 'app/counter.tsx': `'use client';\n\nimport { useState } from 'preact/hooks';\n\nexport default function Counter() {\n const [count, setCount] = useState(0);\n return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;\n}\n`,
150
- 'app/globals.css': `body {\n margin: 0;\n font-family: system-ui, sans-serif;\n}\n`,
151
- '.gitignore': `node_modules\n.pnext\n`,
152
- 'README.md': `# ${name}\n\nA pnext app.\n\n\`\`\`\nbun install\nbun dev\n\`\`\`\n\nDocs: node_modules/@wular/pnext/reference/getting-started.md\n`,
115
+ // The template is a real runnable app in src/cli/template; files are copied verbatim with the
116
+ // placeholder name swapped, and package.json gets the app name and pnext version stamped in.
117
+ const TEMPLATE_DIR = path.join(import.meta.dir, 'template')
118
+ const PLACEHOLDER_NAME = 'pnext-app'
119
+
120
+ async function scaffoldFiles(name: string, pnextRange: string): Promise<Record<string, string>> {
121
+ const files: Record<string, string> = {}
122
+ for (const entry of await readdir(TEMPLATE_DIR, { recursive: true, withFileTypes: true })) {
123
+ if (!entry.isFile()) continue
124
+ const rel = path.relative(TEMPLATE_DIR, path.join(entry.parentPath, entry.name))
125
+ const source = await Bun.file(path.join(TEMPLATE_DIR, rel)).text()
126
+ // npm strips .gitignore from published tarballs, so the template stores it unprefixed.
127
+ if (rel === 'gitignore') {
128
+ files['.gitignore'] = source
129
+ } else if (rel === 'package.json') {
130
+ const pkg = JSON.parse(source) as { name: string; devDependencies: Record<string, string> }
131
+ pkg.name = name
132
+ pkg.devDependencies['@wular/pnext'] = pnextRange
133
+ files[rel] = `${JSON.stringify(pkg, null, 2)}\n`
134
+ } else {
135
+ files[rel] = source.replaceAll(PLACEHOLDER_NAME, name)
136
+ }
153
137
  }
138
+ return files
154
139
  }
@@ -7,6 +7,7 @@ import { existsSync, readFileSync } from 'node:fs'
7
7
  import path from 'node:path'
8
8
  import { pathToFileURL } from 'node:url'
9
9
  import type { Plugin as EsbuildPlugin } from 'esbuild'
10
+ import { frameworkFingerprint } from '../../runtime/fingerprint'
10
11
 
11
12
  const frameworkRoot = path.resolve(import.meta.dirname, '..', '..', '..')
12
13
 
@@ -20,8 +21,17 @@ function frameworkVersion(): string {
20
21
  }
21
22
  }
22
23
 
23
- function serverEntryDir(outPath: string): string {
24
- return path.join(outPath, 'server', `bundle-${frameworkVersion()}`)
24
+ /**
25
+ * Version AND framework fingerprint: the version alone let an edited `src` keep serving the bundle
26
+ * built from the previous source, so a fix appeared to have no effect until the app was rebuilt.
27
+ * The fingerprint is the same one every other compiled artifact is keyed on, taken through the
28
+ * cache-root record so a restart pays one stat per file rather than re-reading the tree.
29
+ */
30
+ export function serverEntryDir(outPath: string): string {
31
+ // Cache root spelled inline, not imported: this module is parsed before anything else on the
32
+ // start path, and pulling in the module cache for one path.join would drag its whole graph along.
33
+ const generation = frameworkFingerprint(path.join(outPath, 'cache', 'server'))
34
+ return path.join(outPath, 'server', `bundle-${frameworkVersion()}-${generation}`)
25
35
  }
26
36
 
27
37
  function serverEntryFile(outPath: string): string {
@@ -30,7 +40,7 @@ function serverEntryFile(outPath: string): string {
30
40
 
31
41
  /**
32
42
  * The prebuilt entry for a project root, or undefined when there is none to
33
- * use (no build, a custom `outDir`, a version mismatch, or the opt-out). The
43
+ * use (no build, a custom `outDir`, a version or source-generation mismatch, or the opt-out). The
34
44
  * caller then falls back to importing src/cli/start.ts directly, so a miss only
35
45
  * costs speed. Deliberately cheap: one existsSync and one package.json read,
36
46
  * because it runs before anything else on the start path.
@@ -43,7 +53,7 @@ export function prebuiltServerEntry(root = process.cwd()): string | undefined {
43
53
  }
44
54
 
45
55
  /**
46
- * Bundle src/cli/start.ts under `<outPath>/server/bundle-<version>/`. Bare
56
+ * Bundle src/cli/start.ts under `<outPath>/server/bundle-<version>-<fingerprint>/`. Bare
47
57
  * specifiers stay external (the framework's deps are already plain JS), so only
48
58
  * the framework's own source is inlined. Splitting is on so the graph behind a
49
59
  * dynamic import (compat, dev, build) stays in its own chunk — folding it into
@@ -15,6 +15,7 @@ import type { ResolvedConfig } from '../../config'
15
15
  import { bootstrapCompat } from '../../compat-bootstrap'
16
16
  import { registerServerRuntime, serverBundleTargetForRuntime } from '../../runtime/loader'
17
17
  import { importModuleOnce } from '../../runtime/modules'
18
+ import { abortUpstreamFetchOnDisconnect } from '../../runtime/fetch-host'
18
19
  import { applyProxyResponse, createProxyRunner } from '../../routing/proxy'
19
20
  import {
20
21
  isPprShellUpgradeEligible,
@@ -287,6 +288,9 @@ export async function createRequestHandler(
287
288
  },
288
289
  )
289
290
  const response = maybeCloseNodeFetchConnection(finalized, request)
291
+ // Wired before the close-observer wraps the body: the stream a handler
292
+ // passed through from fetch is still the one the abort map knows.
293
+ abortUpstreamFetchOnDisconnect(response.body, request.signal)
290
294
  return flushWorkUnitOnClose(response, unit, request.signal)
291
295
  } catch (error) {
292
296
  // A dropped promise here would close the socket without a response
@@ -138,7 +138,7 @@ export function openBrowser(url: string) {
138
138
  // eslint-disable-next-line turbo/no-undeclared-env-vars
139
139
  const browser = process.env.BROWSER
140
140
  // eslint-disable-next-line turbo/no-undeclared-env-vars
141
- if (browser === 'none' || process.env.CI) return
141
+ if (browser === 'none' || process.env.CI || process.env.NODE_ENV === 'test') return
142
142
 
143
143
  // eslint-disable-next-line turbo/no-undeclared-env-vars
144
144
  const browserArgs = process.env.BROWSER_ARGS?.split(/\s+/).filter(Boolean) ?? []
package/src/cli/start.ts CHANGED
@@ -130,6 +130,15 @@ async function guardUnservableOutputMode(root?: string): Promise<ResolvedConfig>
130
130
  )
131
131
  process.exit(1)
132
132
  }
133
+ // Next warns on `next start` with standalone output; its standalone launcher re-execs this path
134
+ // with __NEXT_PRIVATE_STANDALONE_CONFIG set to suppress the warning. Mirror both.
135
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
136
+ if (mode === 'standalone' && !process.env.__NEXT_PRIVATE_STANDALONE_CONFIG) {
137
+ console.warn(
138
+ '"next start" does not work with "output: standalone" configuration. ' +
139
+ 'Use "node .next/standalone/server.js" instead.',
140
+ )
141
+ }
133
142
  return config
134
143
  }
135
144
 
@@ -0,0 +1,10 @@
1
+ # pnext-app
2
+
3
+ A pnext app.
4
+
5
+ ```
6
+ bun install
7
+ bun dev
8
+ ```
9
+
10
+ Docs: node_modules/@wular/pnext/reference/getting-started.md
@@ -0,0 +1,30 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'preact/hooks'
4
+
5
+ export default function ClientPage() {
6
+ const [text, setText] = useState('')
7
+ const words = text.trim() ? text.trim().split(/\s+/).length : 0
8
+ return (
9
+ <div class="page">
10
+ <span class="chip">client component</span>
11
+ <h1>Running in the browser</h1>
12
+ <input
13
+ class="field"
14
+ placeholder="Type something…"
15
+ value={text}
16
+ onInput={e => setText(e.currentTarget.value)}
17
+ />
18
+ <dl class="panel">
19
+ <div>
20
+ <dt>Characters</dt>
21
+ <dd>{text.length}</dd>
22
+ </div>
23
+ <div>
24
+ <dt>Words</dt>
25
+ <dd>{words}</dd>
26
+ </div>
27
+ </dl>
28
+ </div>
29
+ )
30
+ }
@@ -0,0 +1,322 @@
1
+ :root {
2
+ --bg: #ffffff;
3
+ --ink: #111111;
4
+ --muted: #6b6b6b;
5
+ --line: #e4e4e4;
6
+ --card: #f7f7f7;
7
+ --accent: #4f46e5;
8
+ }
9
+
10
+ @media (prefers-color-scheme: dark) {
11
+ :root {
12
+ --bg: #0a0a0a;
13
+ --ink: #ededed;
14
+ --muted: #8f8f8f;
15
+ --line: #262626;
16
+ --card: #141414;
17
+ --accent: #8583ff;
18
+ }
19
+ }
20
+
21
+ * {
22
+ box-sizing: border-box;
23
+ }
24
+
25
+ body {
26
+ margin: 0;
27
+ min-height: 100vh;
28
+ display: flex;
29
+ flex-direction: column;
30
+ background: var(--bg);
31
+ color: var(--ink);
32
+ font-family: system-ui, sans-serif;
33
+ line-height: 1.5;
34
+ overflow-x: hidden;
35
+ }
36
+
37
+ /* One quiet accent glow, top center. */
38
+ body::before {
39
+ content: '';
40
+ position: fixed;
41
+ z-index: -1;
42
+ top: -24rem;
43
+ left: 50%;
44
+ width: 64rem;
45
+ height: 48rem;
46
+ transform: translateX(-50%);
47
+ border-radius: 50%;
48
+ background: radial-gradient(
49
+ ellipse,
50
+ color-mix(in srgb, var(--accent) 10%, transparent),
51
+ transparent 70%
52
+ );
53
+ filter: blur(60px);
54
+ pointer-events: none;
55
+ }
56
+
57
+ .container {
58
+ width: min(72rem, 100% - 4rem);
59
+ margin: 0 auto;
60
+ }
61
+
62
+ .site-header {
63
+ border-bottom: 1px solid var(--line);
64
+ }
65
+
66
+ .bar {
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: space-between;
70
+ padding: 1rem 0;
71
+ }
72
+
73
+ .brand {
74
+ display: flex;
75
+ align-items: center;
76
+ gap: 0.5rem;
77
+ font-weight: 650;
78
+ letter-spacing: -0.01em;
79
+ text-decoration: none;
80
+ }
81
+
82
+ .mark {
83
+ color: var(--accent);
84
+ }
85
+
86
+ .site-header nav {
87
+ display: flex;
88
+ gap: 1.75rem;
89
+ }
90
+
91
+ .site-header nav a {
92
+ color: var(--muted);
93
+ text-decoration: none;
94
+ font-size: 0.92rem;
95
+ font-weight: 500;
96
+ transition: color 130ms ease;
97
+ }
98
+
99
+ .site-header nav a:hover {
100
+ color: var(--ink);
101
+ }
102
+
103
+ main {
104
+ flex: 1;
105
+ display: grid;
106
+ align-content: center;
107
+ padding: 3rem 0 5rem;
108
+ }
109
+
110
+ @keyframes rise {
111
+ from {
112
+ opacity: 0;
113
+ transform: translateY(10px);
114
+ }
115
+
116
+ to {
117
+ opacity: 1;
118
+ transform: translateY(0);
119
+ }
120
+ }
121
+
122
+ .hero {
123
+ display: grid;
124
+ grid-template-columns: 1.4fr 1fr;
125
+ align-items: center;
126
+ gap: 4rem;
127
+ }
128
+
129
+ .hero > * {
130
+ animation: rise 500ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
131
+ }
132
+
133
+ .hero > :last-child {
134
+ animation-delay: 120ms;
135
+ }
136
+
137
+ @media (max-width: 52rem) {
138
+ .hero {
139
+ grid-template-columns: 1fr;
140
+ gap: 2.5rem;
141
+ }
142
+ }
143
+
144
+ h1 {
145
+ margin: 0 0 1rem;
146
+ font-size: clamp(2.2rem, 4.5vw, 3.4rem);
147
+ font-weight: 700;
148
+ letter-spacing: -0.03em;
149
+ line-height: 1.08;
150
+ }
151
+
152
+ .lede {
153
+ margin: 0 0 2rem;
154
+ color: var(--muted);
155
+ font-size: 1.15rem;
156
+ max-width: 32rem;
157
+ }
158
+
159
+ a {
160
+ color: inherit;
161
+ }
162
+
163
+ .button-link,
164
+ .field {
165
+ font: inherit;
166
+ font-weight: 500;
167
+ padding: 0.55rem 1.3rem;
168
+ border-radius: 8px;
169
+ }
170
+
171
+ .button-link {
172
+ display: inline-block;
173
+ text-decoration: none;
174
+ background: var(--ink);
175
+ color: var(--bg);
176
+ border: 1px solid var(--ink);
177
+ transition:
178
+ background 150ms ease,
179
+ border-color 150ms ease;
180
+ }
181
+
182
+ .button-link:hover {
183
+ background: var(--accent);
184
+ border-color: var(--accent);
185
+ color: #ffffff;
186
+ }
187
+
188
+ .panel {
189
+ margin: 0;
190
+ background: var(--card);
191
+ border: 1px solid var(--line);
192
+ border-radius: 12px;
193
+ max-width: 26rem;
194
+ width: 100%;
195
+ overflow: hidden;
196
+ }
197
+
198
+ .panel > div,
199
+ .panel > a {
200
+ display: flex;
201
+ align-items: center;
202
+ justify-content: space-between;
203
+ gap: 1.5rem;
204
+ padding: 0.85rem 1.2rem;
205
+ }
206
+
207
+ .panel > * + * {
208
+ border-top: 1px solid var(--line);
209
+ }
210
+
211
+ .panel > a {
212
+ text-decoration: none;
213
+ transition: background 130ms ease;
214
+ }
215
+
216
+ .panel > a:hover {
217
+ background: color-mix(in srgb, var(--accent) 8%, var(--card));
218
+ }
219
+
220
+ .panel .path {
221
+ display: block;
222
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
223
+ font-size: 0.92rem;
224
+ }
225
+
226
+ .panel .desc {
227
+ display: block;
228
+ margin-top: 0.15rem;
229
+ color: var(--muted);
230
+ font-size: 0.85rem;
231
+ }
232
+
233
+ .panel .arrow {
234
+ color: var(--muted);
235
+ transition:
236
+ transform 130ms ease,
237
+ color 130ms ease;
238
+ }
239
+
240
+ .panel > a:hover .arrow {
241
+ color: var(--accent);
242
+ transform: translateX(3px);
243
+ }
244
+
245
+ .panel dt {
246
+ color: var(--muted);
247
+ font-size: 0.95rem;
248
+ }
249
+
250
+ .panel dd {
251
+ margin: 0;
252
+ font-size: 0.92rem;
253
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
254
+ font-variant-numeric: tabular-nums;
255
+ }
256
+
257
+ .page {
258
+ max-width: 36rem;
259
+ animation: rise 500ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
260
+ }
261
+
262
+ .chip {
263
+ display: inline-block;
264
+ margin-bottom: 1.1rem;
265
+ padding: 0.18rem 0.7rem;
266
+ border-radius: 999px;
267
+ border: 1px solid var(--line);
268
+ background: var(--card);
269
+ color: var(--muted);
270
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
271
+ font-size: 0.72rem;
272
+ text-transform: uppercase;
273
+ letter-spacing: 0.12em;
274
+ font-weight: 600;
275
+ }
276
+
277
+ .page h1 {
278
+ margin-bottom: 1.75rem;
279
+ }
280
+
281
+ .page .panel {
282
+ margin-top: 1.25rem;
283
+ }
284
+
285
+ .field {
286
+ display: block;
287
+ width: 100%;
288
+ max-width: 24rem;
289
+ background: var(--card);
290
+ color: var(--ink);
291
+ border: 1px solid var(--line);
292
+ }
293
+
294
+ .field:focus {
295
+ border-color: var(--accent);
296
+ outline: none;
297
+ }
298
+
299
+ .field::placeholder {
300
+ color: var(--muted);
301
+ }
302
+
303
+ .hint {
304
+ margin: 0.9rem 0 0;
305
+ min-height: 1.5em;
306
+ color: var(--muted);
307
+ font-size: 0.95rem;
308
+ font-variant-numeric: tabular-nums;
309
+ }
310
+
311
+ a:focus-visible {
312
+ outline: 2px solid var(--accent);
313
+ outline-offset: 3px;
314
+ }
315
+
316
+ @media (prefers-reduced-motion: reduce) {
317
+ *,
318
+ *::before {
319
+ animation: none !important;
320
+ transition: none !important;
321
+ }
322
+ }
@@ -0,0 +1,33 @@
1
+ import type { LayoutProps } from '@wular/pnext'
2
+ import './globals.css'
3
+
4
+ export const metadata = {
5
+ title: 'pnext-app',
6
+ }
7
+
8
+ export default function RootLayout({ children }: LayoutProps) {
9
+ return (
10
+ <html>
11
+ <body>
12
+ <header class="site-header">
13
+ <div class="container bar">
14
+ <a class="brand" href="/">
15
+ <span class="mark">▲</span> pnext-app
16
+ </a>
17
+ <nav>
18
+ <a href="/server">Server component</a>
19
+ <a href="/client">Client component</a>
20
+ <a href="https://www.pnext.dev/docs" target="_blank" rel="noopener">
21
+ Docs
22
+ </a>
23
+ <a href="https://github.com/muzam1l/pnext" target="_blank" rel="noopener">
24
+ GitHub
25
+ </a>
26
+ </nav>
27
+ </div>
28
+ </header>
29
+ <main class="container">{children}</main>
30
+ </body>
31
+ </html>
32
+ )
33
+ }
@@ -0,0 +1,29 @@
1
+ export default async function Home() {
2
+ return (
3
+ <div class="hero">
4
+ <div>
5
+ <h1>Hello, world.</h1>
6
+ <p class="lede">Your app is up and running.</p>
7
+ <a class="button-link" href="https://www.pnext.dev/docs" target="_blank" rel="noopener">
8
+ Read the docs
9
+ </a>
10
+ </div>
11
+ <nav class="panel">
12
+ <a href="/server">
13
+ <span>
14
+ <span class="path">/server</span>
15
+ <span class="desc">Server component, rendered per request</span>
16
+ </span>
17
+ <span class="arrow">→</span>
18
+ </a>
19
+ <a href="/client">
20
+ <span>
21
+ <span class="path">/client</span>
22
+ <span class="desc">Client component, interactive in the browser</span>
23
+ </span>
24
+ <span class="arrow">→</span>
25
+ </a>
26
+ </nav>
27
+ </div>
28
+ )
29
+ }
@@ -0,0 +1,27 @@
1
+ export const metadata = {
2
+ title: 'Server component',
3
+ }
4
+
5
+ export default async function ServerPage() {
6
+ const renderedAt = new Date().toLocaleTimeString('en-US', { hour12: false })
7
+ return (
8
+ <div class="page">
9
+ <span class="chip">server component</span>
10
+ <h1>Rendered on the server</h1>
11
+ <dl class="panel">
12
+ <div>
13
+ <dt>Rendered at</dt>
14
+ <dd>{renderedAt}</dd>
15
+ </div>
16
+ <div>
17
+ <dt>Runtime</dt>
18
+ <dd>Bun {Bun.version}</dd>
19
+ </div>
20
+ <div>
21
+ <dt>Client JS</dt>
22
+ <dd>none</dd>
23
+ </div>
24
+ </dl>
25
+ </div>
26
+ )
27
+ }
@@ -0,0 +1,2 @@
1
+ node_modules
2
+ .pnext
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "pnext-app",
3
+ "private": true,
4
+ "scripts": {
5
+ "dev": "pnext dev",
6
+ "build": "pnext build",
7
+ "start": "pnext start",
8
+ "analyze": "pnext analyze"
9
+ },
10
+ "dependencies": {
11
+ "preact": "^10"
12
+ },
13
+ "devDependencies": {
14
+ "@wular/pnext": "latest",
15
+ "typescript": "^5",
16
+ "@types/bun": "^1"
17
+ }
18
+ }
@@ -0,0 +1,2 @@
1
+ // See node_modules/@wular/pnext/reference/config.md for available options.
2
+ export default {}
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "@wular/pnext/config/ts/react.json",
3
+ "compilerOptions": {
4
+ "jsxImportSource": "preact",
5
+ "paths": {
6
+ "#gen/*": ["./.pnext/types/*"]
7
+ }
8
+ },
9
+ "include": ["**/*.ts", "**/*.tsx", ".pnext/types/**/*.ts"]
10
+ }
@@ -57,7 +57,10 @@ function routeEntry(route: RouteManifestEntry) {
57
57
  }
58
58
 
59
59
  async function writeChecks(config: ResolvedConfig, routes: RouteManifestEntry[]) {
60
- const checksDir = path.join(config.typesPath, 'checks')
60
+ const checksDir = config.checksPath
61
+ // Checks used to live under typesPath, which the app's own tsconfig includes; a stale
62
+ // copy there fails the app's compiler on pnext internals, so heal that on every generate.
63
+ await rm(path.join(config.typesPath, 'checks'), { recursive: true, force: true })
61
64
  await rm(checksDir, { recursive: true, force: true })
62
65
  await ensureDir(checksDir)
63
66