@thomas.f/prnext 0.1.16-alpha → 0.1.17-alpha
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/AI.md +63 -0
- package/README.md +384 -9
- package/build/ADAPTERS.md +27 -2
- package/build/actions.mjs +5 -0
- package/build/analyze.mjs +184 -0
- package/build/app-config.mjs +7 -8
- package/build/app-static.mjs +12 -7
- package/build/app.mjs +23 -10
- package/build/build-lock.mjs +6 -6
- package/build/bundle-runtime.mjs +20 -5
- package/build/check.mjs +56 -11
- package/build/compiler.mjs +25 -1
- package/build/concurrent.mjs +12 -0
- package/build/config.mjs +21 -13
- package/build/css-modules.mjs +15 -4
- package/build/dev-compiler-health.mjs +11 -0
- package/build/dev-css-update.mjs +83 -0
- package/build/dev-dependencies.mjs +44 -0
- package/build/dev-discovery.mjs +28 -0
- package/build/dev-worker.mjs +14 -5
- package/build/dev.mjs +55 -4
- package/build/edge.mjs +3 -2
- package/build/framework-imports.mjs +40 -0
- package/build/incremental.mjs +8 -4
- package/build/index.mjs +96 -101
- package/build/inspect.mjs +3 -0
- package/build/inspection-summary.mjs +37 -4
- package/build/loader-cache.mjs +53 -26
- package/build/metadata-routes.mjs +2 -3
- package/build/migrate.mjs +60 -57
- package/build/npm-packages.mjs +3 -18
- package/build/package-manager.mjs +308 -0
- package/build/persistent-cache.mjs +97 -0
- package/build/precompress.mjs +62 -30
- package/build/prerender-pages.mjs +90 -0
- package/build/prerender-worker.mjs +10 -0
- package/build/production-builder.mjs +80 -0
- package/build/scan.mjs +4 -4
- package/build/source-analysis.mjs +42 -0
- package/build/static-adapter.mjs +13 -5
- package/build/static-input.mjs +48 -0
- package/build/styles.mjs +15 -3
- package/build/transform-cache.mjs +37 -4
- package/build/type-command.mjs +41 -0
- package/build/typed-routes.mjs +91 -0
- package/build/typescript-dev-worker.mjs +18 -0
- package/build/typescript-dev.mjs +22 -0
- package/build/typescript-watch.mjs +22 -0
- package/build/typescript.mjs +5 -4
- package/build/webpack-graph.mjs +164 -27
- package/build/worker-preload.mjs +9 -0
- package/cli.mjs +105 -121
- package/compat/index.d.ts +21 -7
- package/compat/link.d.cts +4 -3
- package/compat/navigation.d.cts +4 -3
- package/compat/router.d.cts +4 -3
- package/compat/typed-route.d.cts +17 -0
- package/docs/ai/compatibility.md +106 -0
- package/docs/ai/data.md +128 -0
- package/docs/ai/deployment.md +111 -0
- package/docs/ai/manifest.json +11 -0
- package/docs/ai/quickstart.md +146 -0
- package/docs/ai/routing.md +132 -0
- package/docs/ai/troubleshooting.md +120 -0
- package/index.mjs +2 -0
- package/native/diagnostics.mjs +34 -0
- package/native/resolve.mjs +7 -2
- package/package.json +25 -7
- package/release/ai-docs.mjs +48 -0
- package/release/verify.mjs +2 -0
- package/runtime/ai-docs.mjs +202 -0
- package/runtime/app-client.mjs +17 -6
- package/runtime/app-render.mjs +12 -0
- package/runtime/cloud/archive.mjs +5 -6
- package/runtime/compile-cache.mjs +8 -0
- package/runtime/dev-ai-brands.mjs +26 -0
- package/runtime/dev-ai-style.mjs +99 -0
- package/runtime/dev-build-reasons.mjs +25 -0
- package/runtime/dev-cache-control.mjs +53 -0
- package/runtime/dev-client.mjs +239 -45
- package/runtime/dev-console-copy.mjs +19 -0
- package/runtime/dev-console.mjs +106 -0
- package/runtime/dev-dependency-graph.mjs +40 -0
- package/runtime/dev-element-picker-style.mjs +27 -0
- package/runtime/dev-element-picker.mjs +213 -0
- package/runtime/dev-error-overlay-style.mjs +41 -0
- package/runtime/dev-error-overlay.mjs +167 -0
- package/runtime/dev-error-report.mjs +121 -0
- package/runtime/dev-file-changes.mjs +1 -1
- package/runtime/dev-gateway.mjs +44 -0
- package/runtime/dev-host-relay.mjs +66 -0
- package/runtime/dev-indicator.mjs +186 -0
- package/runtime/dev-issue-motion.mjs +214 -0
- package/runtime/dev-lazy-tool.mjs +18 -0
- package/runtime/dev-logo.mjs +2 -0
- package/runtime/dev-notification.mjs +81 -0
- package/runtime/dev-popout.mjs +34 -0
- package/runtime/dev-position.mjs +107 -0
- package/runtime/dev-preview-touch.mjs +122 -0
- package/runtime/dev-runtime-pool.mjs +45 -0
- package/runtime/dev-runtime-spare.mjs +57 -0
- package/runtime/dev-tools-client.mjs +155 -0
- package/runtime/dev-tools-mcp.mjs +126 -0
- package/runtime/dev-tools-metrics.mjs +31 -0
- package/runtime/dev-tools-model.mjs +42 -0
- package/runtime/dev-tools-panel.mjs +341 -0
- package/runtime/dev-tools-result.mjs +37 -0
- package/runtime/dev-tools-server.mjs +256 -0
- package/runtime/dev-tools-source.mjs +104 -0
- package/runtime/dev-tooltip.mjs +94 -0
- package/runtime/dev-ui-icons.mjs +8 -0
- package/runtime/dev-workbench-style.mjs +106 -0
- package/runtime/dev-workbench.mjs +428 -0
- package/runtime/dev-worker-logs.mjs +31 -0
- package/runtime/development.mjs +307 -0
- package/runtime/env.mjs +2 -1
- package/runtime/pages-client.mjs +10 -1
- package/runtime/pages-navigation.mjs +6 -1
- package/runtime/persistent/client.mjs +6 -5
- package/runtime/persistent/daemon.mjs +66 -56
- package/runtime/persistent/native-proxy.mjs +63 -0
- package/runtime/preload.mjs +12 -0
- package/runtime/rsc-worker.mjs +7 -0
- package/runtime/typed-route.mjs +19 -0
- package/runtime/watch-project.mjs +12 -5
- package/runtime/worker.mjs +36 -2
package/cli.mjs
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { rename } from './runtime/fs.mjs';
|
|
3
2
|
import { spawn } from 'node:child_process';
|
|
4
|
-
import { readFile,
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { readFile, rm, mkdir, readdir } from 'node:fs/promises';
|
|
6
4
|
import path from 'node:path';
|
|
7
5
|
import { resolveNativeBinary, packageManifest } from './native/resolve.mjs';
|
|
8
6
|
import { replaceWithNative } from './native/launch.mjs';
|
|
9
7
|
import { readBuildDirectory } from './runtime/build-directory.mjs';
|
|
10
|
-
import { shouldWatchProjectFile } from './runtime/env.mjs';
|
|
11
8
|
|
|
12
|
-
const self = fileURLToPath(import.meta.url);
|
|
13
9
|
const [command = 'help', ...argv] = process.argv.slice(2);
|
|
14
10
|
const help = `PRNext ${packageManifest.version}
|
|
15
11
|
|
|
16
12
|
Use prnext for portable scripts; prn is a shortcut on compatible shells.
|
|
17
13
|
|
|
18
14
|
prnext build [directory] Build React/TypeScript and static HTML
|
|
15
|
+
prnext build [directory] --watch Incremental production rebuilds on changes
|
|
19
16
|
prnext build [directory] --adapter static [--from dist] [--spa]
|
|
20
17
|
Import an existing public build directory
|
|
18
|
+
prnext build [directory] --adapter auto
|
|
19
|
+
Detect a completed browser build
|
|
20
|
+
prnext build [directory] --adapter static --from .
|
|
21
|
+
Build a plain HTML/CSS/JS site
|
|
21
22
|
prnext output [directory] [--json] Read the versioned build output contract
|
|
22
23
|
prnext start [directory] [options] Start native Rust production server
|
|
23
24
|
prnext pstart [directory] [options] Start production persistently in the background
|
|
@@ -28,9 +29,17 @@ Use prnext for portable scripts; prn is a shortcut on compatible shells.
|
|
|
28
29
|
prnext pstartup [--remove] Configure restoration at user login
|
|
29
30
|
prnext pstart --help All persistent commands and options
|
|
30
31
|
prnext dev [directory] [options] Rebuild and restart on source changes
|
|
32
|
+
prnext dev [directory] --host-ai Share local diagnostics with your connected Host AI
|
|
33
|
+
prnext dev [directory] --safe Use full builds and fresh workers for troubleshooting
|
|
34
|
+
prnext mcp [directory] Connect an AI to the running local dev server
|
|
35
|
+
prnext docs --ai [topic] Read the AI guide bundled with this version
|
|
36
|
+
prnext docs --ai --init [directory] Add a guide pointer to the project's AGENTS.md
|
|
31
37
|
prnext routes [directory] Display compiled routes
|
|
32
|
-
prnext check [directory] [--json]
|
|
38
|
+
prnext check [directory] [--json] Diagnose config, routes, Node, native and dependencies
|
|
39
|
+
prnext typegen [directory] [--json] Generate route types without building the app
|
|
40
|
+
prnext typecheck [directory] [--json] Generate types and check TypeScript without building
|
|
33
41
|
prnext inspect [directory] [--json|--otlp] Explain routes, health and sampled timings
|
|
42
|
+
prnext analyze [directory] [--json] [--max-js 150] Browser bundles and budget (kB)
|
|
34
43
|
prnext host [prnext.host.json] Host built apps by hostname; wake on demand
|
|
35
44
|
prnext host [config] --check Validate hosting configuration and builds
|
|
36
45
|
prnext host [config] --status [--json] Show memory, process and idle status
|
|
@@ -47,6 +56,20 @@ Migration options: --dry-run (preview), --no-install (prepare only), --json
|
|
|
47
56
|
Compare GET responses before switching
|
|
48
57
|
Migration keeps Next.js and backs up package.json and existing lockfiles.
|
|
49
58
|
Simple next dev/build/start scripts become prnext commands; custom shell scripts need a manual edit.
|
|
59
|
+
Installation honors npm, pnpm, Yarn Classic/Berry and the workspace root.
|
|
60
|
+
The preview reports the manager, root and selected workspace. Failed installs
|
|
61
|
+
restore the selected manifest/lockfiles; dependency caches may need a retry.
|
|
62
|
+
|
|
63
|
+
Type commands require an application tsconfig and TypeScript installation.
|
|
64
|
+
They generate route types unless typedRoutes: false, preserve JSONC/extends,
|
|
65
|
+
and report updates to tsconfig/prnext-env.d.ts. typecheck always checks errors.
|
|
66
|
+
|
|
67
|
+
Analysis options: --metric raw|gzip|brotli (default raw), --baseline analysis.json
|
|
68
|
+
--max-js 150 Maximum initial JS per route, in kB
|
|
69
|
+
--max-increase-bytes 1024 --max-increase-percent 5
|
|
70
|
+
--budgets budgets.json Exact route keys with maxJs (kB),
|
|
71
|
+
maxIncreaseBytes and/or maxIncreasePercent
|
|
72
|
+
Analysis exit codes: 0 passed, 1 budget exceeded, 2 invalid input or missing build.
|
|
50
73
|
|
|
51
74
|
Server options: --port 3000 --hostname 127.0.0.1 --workers 1 --inspect
|
|
52
75
|
Production profiles: --profile balanced|speed|memory|classic
|
|
@@ -78,6 +101,18 @@ function stop(child) {
|
|
|
78
101
|
async function main() {
|
|
79
102
|
if (['help', '--help', '-h'].includes(command)) { console.log(help); return; }
|
|
80
103
|
if (['--version', '-v'].includes(command)) { console.log(packageManifest.version); return; }
|
|
104
|
+
if (command === 'docs') {
|
|
105
|
+
const { executeAiDocs } = await import('./runtime/ai-docs.mjs');
|
|
106
|
+
const result = await executeAiDocs(argv);
|
|
107
|
+
process.stdout.write(result.output);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (command === 'mcp') {
|
|
111
|
+
if (argv.includes('--help') || argv.includes('-h')) { console.log('prnext mcp [directory]\nLocal development MCP over stdio. Start prnext dev first; no shell or production access is exposed.'); return; }
|
|
112
|
+
if (argv.length > 1 || argv.some(arg => arg.startsWith('-'))) throw Error('mcp accepts only [directory]');
|
|
113
|
+
const {runDevMcp}=await import('./runtime/dev-tools-mcp.mjs');
|
|
114
|
+
await runDevMcp(path.resolve(argv[0] || '.'));return;
|
|
115
|
+
}
|
|
81
116
|
if (['login', 'logout', 'whoami', 'deploy', 'update'].includes(command)) {
|
|
82
117
|
const { cloudCommand } = await import('./runtime/cloud/client.mjs');
|
|
83
118
|
await cloudCommand(command, argv);
|
|
@@ -150,9 +185,41 @@ async function main() {
|
|
|
150
185
|
if (!report.ok) process.exitCode = 1;
|
|
151
186
|
return;
|
|
152
187
|
}
|
|
153
|
-
if (!['build', 'start', 'dev', 'routes', 'check', 'inspect', 'output'].includes(command)) throw new Error(`Unknown command: ${command}\n${help}`);
|
|
188
|
+
if (!['build', 'start', 'dev', 'routes', 'check', 'typegen', 'typecheck', 'inspect', 'analyze', 'output'].includes(command)) throw new Error(`Unknown command: ${command}\n${help}`);
|
|
154
189
|
const args = [...argv];
|
|
155
190
|
const root = path.resolve(args[0] && !args[0].startsWith('-') ? args.shift() : '.');
|
|
191
|
+
if (['check','typegen','typecheck','analyze'].includes(command) && args.some(arg => ['--help','-h'].includes(arg))) { console.log(help); return; }
|
|
192
|
+
if(command==='analyze'){
|
|
193
|
+
try {
|
|
194
|
+
const {analyzeBuild,printAnalysis,parseAnalysisArgs}=await import('./build/analyze.mjs');
|
|
195
|
+
const options=parseAnalysisArgs(args),report=await analyzeBuild(root,options);
|
|
196
|
+
if(options.json)console.log(JSON.stringify(report,null,2));else printAnalysis(report);
|
|
197
|
+
if(!report.ok)process.exitCode=1;
|
|
198
|
+
} catch(error) {
|
|
199
|
+
if(args.includes('--json'))console.log(JSON.stringify({ok:false,error:error.message}));
|
|
200
|
+
else console.error(`PRNext analysis: ${error.message}`);
|
|
201
|
+
process.exitCode=2;
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (command === 'typegen' || command === 'typecheck') {
|
|
206
|
+
if (args.some(arg => arg !== '--json') || args.filter(arg => arg === '--json').length > 1) throw new Error(`${command} accepts only [directory] and --json`);
|
|
207
|
+
const { runTypeCommand } = await import('./build/type-command.mjs');
|
|
208
|
+
process.chdir(root);
|
|
209
|
+
process.env.INIT_CWD = root;
|
|
210
|
+
process.env.PWD = root;
|
|
211
|
+
const report = await runTypeCommand(root, { check: command === 'typecheck' });
|
|
212
|
+
if (args.includes('--json')) console.log(JSON.stringify(report, null, 2));
|
|
213
|
+
else {
|
|
214
|
+
console.log(`PRNext ${command} ${report.ok ? 'passed' : 'failed'}: ${root}`);
|
|
215
|
+
console.log(`${report.routes} page routes; tsconfig: ${report.tsconfig || 'unavailable'}; route types: ${report.typedRoutes ? 'enabled' : 'disabled'}.`);
|
|
216
|
+
for (const file of report.updated) console.log(`Updated ${file}`);
|
|
217
|
+
for (const note of report.notes) console.log(note);
|
|
218
|
+
for (const error of report.errors) console.error(error);
|
|
219
|
+
}
|
|
220
|
+
if (!report.ok) process.exitCode = 1;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
156
223
|
if (command === 'output') {
|
|
157
224
|
if (args.some(arg => arg !== '--json')) throw new Error('output accepts only [directory] and --json');
|
|
158
225
|
const { readBuildOutput } = await import('./build/output-contract.mjs');
|
|
@@ -196,7 +263,10 @@ async function main() {
|
|
|
196
263
|
else {
|
|
197
264
|
console.log(`PRNext preflight ${report.ok ? 'passed' : 'failed'}: ${root}`);
|
|
198
265
|
console.log(`${report.routes.length} routes. Installed versions: ${Object.entries(report.versions).map(([name,version])=>`${name}@${version||'missing'}`).join(', ')}`);
|
|
199
|
-
for (const
|
|
266
|
+
for (const item of report.diagnostics) {
|
|
267
|
+
const output = item.severity === 'error' ? console.error : console.log;
|
|
268
|
+
output(`${item.severity}: ${item.message}${item.action ? ` ${item.action}` : ''}`);
|
|
269
|
+
}
|
|
200
270
|
for (const note of report.notes) console.log(note);
|
|
201
271
|
}
|
|
202
272
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -207,6 +277,7 @@ async function main() {
|
|
|
207
277
|
for (let i = 0; i < args.length; i++) {
|
|
208
278
|
const flag = args[i];
|
|
209
279
|
if (flag === '--dev') options.dev = true;
|
|
280
|
+
else if (flag === '--watch') options.watch = true;
|
|
210
281
|
else if (flag === '--spa') options.spa = true;
|
|
211
282
|
else if (flag === '--adapter' || flag === '--from') {
|
|
212
283
|
const value = args[++i];
|
|
@@ -220,6 +291,17 @@ async function main() {
|
|
|
220
291
|
const envMode = process.env.NODE_ENV === 'test' ? 'test' : undefined;
|
|
221
292
|
process.env.NODE_ENV = dev ? 'development' : 'production';
|
|
222
293
|
const started = performance.now();
|
|
294
|
+
if(options.watch){
|
|
295
|
+
if(dev)throw new Error('--watch builds production; use prnext dev for development.');
|
|
296
|
+
const {watchProduction}=await import('./build/production-builder.mjs');
|
|
297
|
+
delete options.watch;await watchProduction(root,options);return;
|
|
298
|
+
}
|
|
299
|
+
if (options.adapter === undefined || options.adapter === 'auto') {
|
|
300
|
+
const { detectBuildAdapter } = await import('./build/static-input.mjs');
|
|
301
|
+
const selected = await detectBuildAdapter(root, { prepared: options.adapter === 'auto' });
|
|
302
|
+
if (selected.adapter === 'static') Object.assign(options, selected);
|
|
303
|
+
else if (options.adapter === 'auto') options.adapter = 'react';
|
|
304
|
+
}
|
|
223
305
|
if (options.adapter === 'static') {
|
|
224
306
|
const { buildStatic } = await import('./build/static-adapter.mjs');
|
|
225
307
|
await buildStatic(root, options);
|
|
@@ -231,120 +313,22 @@ async function main() {
|
|
|
231
313
|
return;
|
|
232
314
|
}
|
|
233
315
|
const binary = await resolveNativeBinary();
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const launch = () => spawn(binary, [command === 'routes' ? 'routes' : 'start', root,
|
|
241
|
-
...(command === 'routes' ? [] : ['--worker', path.join(root, outputDirectory, 'runtime/worker.mjs'), '--node', process.execPath,
|
|
242
|
-
'--shutdown-on-stdin-eof']), ...args], { stdio: ['pipe', 'inherit', 'inherit'], windowsHide: true, env: { ...process.env, NODE_ENV: process.env.NODE_ENV || (command === 'dev' ? 'development' : 'production') } });
|
|
243
|
-
let server;
|
|
244
|
-
let stopping = false;
|
|
245
|
-
let watcher;
|
|
246
|
-
let rebuildChild;
|
|
247
|
-
let timer;
|
|
248
|
-
const devStateFile = path.join(root, '.prnext-dev.json');
|
|
249
|
-
const changedFiles = new Set();
|
|
250
|
-
async function notifyDev(value) {
|
|
251
|
-
const temporary = `${devStateFile}.${process.pid}.tmp`;
|
|
252
|
-
await writeFile(temporary, JSON.stringify(value));
|
|
253
|
-
await rename(temporary, devStateFile);
|
|
316
|
+
if(command==='dev') {const {develop}=await import('./runtime/development.mjs');await develop(root,args,binary);return;}
|
|
317
|
+
const outputDirectory = await readBuildDirectory(root);
|
|
318
|
+
const serverEnvironment = {...process.env,NODE_ENV:process.env.NODE_ENV||'production'};
|
|
319
|
+
if(command==='start'&&serverEnvironment.NODE_ENV!=='development'){
|
|
320
|
+
if(replaceWithNative(binary,['start',root,'--worker',path.join(root,outputDirectory,'runtime/worker.mjs'),
|
|
321
|
+
'--node',process.execPath,...args],serverEnvironment))return;
|
|
254
322
|
}
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
process.once('SIGTERM', shutdown);
|
|
265
|
-
if (command !== 'dev') {
|
|
266
|
-
server = launch();
|
|
267
|
-
await done(server).catch(error => { if (!stopping) throw error; });
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
let building = false;
|
|
271
|
-
let queued = false;
|
|
272
|
-
let attempted = false;
|
|
273
|
-
const {createDevChangeFilter} = await import('./runtime/dev-file-changes.mjs');
|
|
274
|
-
const filterChanges = createDevChangeFilter(root);
|
|
275
|
-
async function rebuild() {
|
|
276
|
-
if (stopping) return;
|
|
277
|
-
if (building) { queued = true; return; }
|
|
278
|
-
building = true;
|
|
279
|
-
const pending = [...changedFiles];
|
|
280
|
-
changedFiles.clear();
|
|
281
|
-
let buildOutput = '';
|
|
282
|
-
try {
|
|
283
|
-
const actualChanges = await filterChanges(pending);
|
|
284
|
-
if (stopping) return;
|
|
285
|
-
if (attempted && !actualChanges.length) return;
|
|
286
|
-
attempted = true;
|
|
287
|
-
const changed = actualChanges.slice(0, 32).map(file => file.slice(0, 256));
|
|
288
|
-
await notifyDev({ state: 'building' });
|
|
289
|
-
if (actualChanges.some(file => /(?:^|\/)(?:(?:next|prnext|contentlayer)\.config\.|package(?:-lock)?\.json$|(?:pnpm-lock\.yaml|yarn\.lock)$)/.test(file))) { await stop(rebuildChild); rebuildChild = undefined; }
|
|
290
|
-
if (stopping) return;
|
|
291
|
-
if (!rebuildChild || rebuildChild.exitCode !== null || rebuildChild.signalCode) {
|
|
292
|
-
rebuildChild = spawn(process.execPath, [path.join(path.dirname(self), 'build/dev-worker.mjs')], { stdio: ['inherit', 'pipe', 'pipe', 'ipc'], env: { ...process.env, NODE_ENV: process.env.NODE_ENV || 'development' } });
|
|
293
|
-
}
|
|
294
|
-
const stdout = chunk => { process.stdout.write(chunk); buildOutput = (buildOutput + chunk).slice(-12_000); };
|
|
295
|
-
const stderr = chunk => { process.stderr.write(chunk); buildOutput = (buildOutput + chunk).slice(-12_000); };
|
|
296
|
-
rebuildChild.stdout.on('data', stdout); rebuildChild.stderr.on('data', stderr);
|
|
297
|
-
let result;
|
|
298
|
-
try {
|
|
299
|
-
result = await new Promise((resolve, reject) => {
|
|
300
|
-
const worker = rebuildChild;
|
|
301
|
-
const cleanup = () => { worker.off('message', message); worker.off('exit', exited); worker.off('error', failed); };
|
|
302
|
-
const message = value => { if (value?.type === 'result') { cleanup(); resolve(value); } };
|
|
303
|
-
const exited = (code, signal) => { cleanup(); reject(new Error(`Compiler exited ${signal || code}`)); };
|
|
304
|
-
const failed = error => { cleanup(); reject(error); };
|
|
305
|
-
worker.on('message', message); worker.once('exit', exited); worker.once('error', failed);
|
|
306
|
-
worker.send({type:'build',root});
|
|
307
|
-
});
|
|
308
|
-
} finally { rebuildChild.stdout.off('data', stdout); rebuildChild.stderr.off('data', stderr); }
|
|
309
|
-
if (result.recycle) { await stop(rebuildChild); rebuildChild = undefined; }
|
|
310
|
-
if (!result.ok) throw new Error(result.error);
|
|
311
|
-
filterChanges.acceptInputs(result.inputs || []);
|
|
312
|
-
if (!stopping) {
|
|
313
|
-
await stop(server);
|
|
314
|
-
outputDirectory = await readBuildDirectory(root);
|
|
315
|
-
await watcher.refresh();
|
|
316
|
-
// Shutdown can arrive while the previous server is draining. Never
|
|
317
|
-
// leave a replacement running after shutdown already stopped its peers.
|
|
318
|
-
if (stopping) return;
|
|
319
|
-
for (const file of changedFiles) {
|
|
320
|
-
if (file === outputDirectory || file.startsWith(outputDirectory + '/') || outputDirectory.startsWith(file + '/')) changedFiles.delete(file);
|
|
321
|
-
}
|
|
322
|
-
if (!changedFiles.size) { clearTimeout(timer); queued = false; }
|
|
323
|
-
server = launch();
|
|
324
|
-
server.on('error', error => console.error(`PRNext server: ${error.message}`));
|
|
325
|
-
const manifest = JSON.parse(await readFile(path.join(root, outputDirectory, 'manifest.json'), 'utf8'));
|
|
326
|
-
if (stopping) return;
|
|
327
|
-
await notifyDev({ state: 'ready', buildId: manifest.buildId, clientManifest: manifest.devClient, changed });
|
|
328
|
-
console.log('Source compiled. React Fast Refresh applied in connected browsers.');
|
|
329
|
-
}
|
|
330
|
-
} catch (error) { if (!stopping) { console.error(`Build failed: ${error.message}`); await notifyDev({ state: 'error', error: buildOutput || error.message }); } }
|
|
331
|
-
finally { building = false; if (queued && !stopping) { queued = false; void rebuild(); } }
|
|
332
|
-
}
|
|
333
|
-
const { watchProject } = await import('./runtime/watch-project.mjs');
|
|
334
|
-
watcher = await watchProject(root, {
|
|
335
|
-
ignore: relative => relative === outputDirectory || relative.startsWith(outputDirectory + '/'),
|
|
336
|
-
onError: error => { console.error(`PRNext development watcher: ${error.message}`); process.exitCode = 1; void shutdown(); },
|
|
337
|
-
onChange: filename => {
|
|
338
|
-
if (stopping) return;
|
|
339
|
-
const relative = String(filename || '').replaceAll(path.sep, '/');
|
|
340
|
-
if (relative === outputDirectory || relative.startsWith(outputDirectory + '/') || !shouldWatchProjectFile(filename)) return;
|
|
341
|
-
changedFiles.add(String(filename).replaceAll(path.sep, '/'));
|
|
342
|
-
clearTimeout(timer);
|
|
343
|
-
timer = setTimeout(() => { if (changedFiles.size) void rebuild(); }, 150);
|
|
344
|
-
},
|
|
345
|
-
});
|
|
346
|
-
if (stopping) { watcher.close(); return; }
|
|
347
|
-
await rebuild();
|
|
323
|
+
const server=spawn(binary,[command==='routes'?'routes':'start',root,
|
|
324
|
+
...(command==='routes'?[]:['--worker',path.join(root,outputDirectory,'runtime/worker.mjs'),'--node',process.execPath,'--shutdown-on-stdin-eof']),...args],
|
|
325
|
+
{stdio:['pipe','inherit','inherit'],windowsHide:true,env:serverEnvironment});
|
|
326
|
+
let stopping=false;
|
|
327
|
+
const shutdown=async()=>{if(stopping)return;stopping=true;await stop(server);};
|
|
328
|
+
process.once('SIGINT',shutdown);process.once('SIGTERM',shutdown);
|
|
329
|
+
try{await done(server).catch(error=>{if(!stopping)throw error;});}
|
|
330
|
+
finally{process.off('SIGINT',shutdown);process.off('SIGTERM',shutdown);await stop(server);}
|
|
331
|
+
|
|
348
332
|
}
|
|
349
333
|
|
|
350
334
|
main().catch(error => { console.error(`PRNext: ${error.message}`); process.exitCode = 1; });
|
package/compat/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import type { ComponentType } from 'react';
|
|
3
3
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
4
4
|
export type { Metadata, Viewport, ResolvingMetadata, ResolvingViewport, MetadataRoute } from './metadata.cjs';
|
|
5
|
+
export {route} from './typed-route.cjs';
|
|
6
|
+
export type {Route, RoutePattern, RouteParameters} from './typed-route.cjs';
|
|
5
7
|
|
|
6
8
|
export interface InstantSample {
|
|
7
9
|
cookies?: Array<{name: string; value: string | null}>;
|
|
@@ -116,10 +118,12 @@ export interface NextConfig {
|
|
|
116
118
|
* 0 disables the corresponding retirement; keepWarm retains one lane per used host.
|
|
117
119
|
*/
|
|
118
120
|
workerRetirement?: { idleSeconds?: number; laneIdleSeconds?: number; keepWarm?: boolean };
|
|
119
|
-
/**
|
|
121
|
+
/** Import selected Node routes on startup; top-level module code executes. RSS is a soft import budget. */
|
|
122
|
+
workerPreload?: false | { routes: string[]; maxRssMb?: number; timeoutMs?: number };
|
|
123
|
+
/** Bounded active development routes; default retains five requested routes. */
|
|
120
124
|
onDemandEntries?: { maxInactiveAge?: number; pagesBufferLength?: number };
|
|
121
|
-
/**
|
|
122
|
-
devIndicators?: false;
|
|
125
|
+
/** PRNext development menu; bottom-left by default. false hides it without disabling errors or Fast Refresh. */
|
|
126
|
+
devIndicators?: false | { position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' };
|
|
123
127
|
i18n?: { locales: string[]; defaultLocale: string; localeDetection?: boolean; domains?: Array<{domain: string; defaultLocale: string; locales?: string[]; http?: boolean}> };
|
|
124
128
|
webpack?: (config: import('webpack').Configuration, context: { buildId: string; dev: boolean; isServer: boolean; nextRuntime?: 'nodejs' | 'edge'; webpack: typeof import('webpack'); defaultLoaders: {babel: {loader: string}} }) => import('webpack').Configuration | Promise<import('webpack').Configuration>;
|
|
125
129
|
turbopack?: { root?: string; rules?: Record<string, TurbopackRule | TurbopackRule[]>; resolveAlias?: Record<string, string | { browser: string }>; resolveExtensions?: string[] };
|
|
@@ -128,10 +132,12 @@ export interface NextConfig {
|
|
|
128
132
|
reactStrictMode?: boolean;
|
|
129
133
|
/** Enabled React Compiler transforms are not implemented. */
|
|
130
134
|
reactCompiler?: false;
|
|
131
|
-
/**
|
|
132
|
-
typedRoutes?:
|
|
135
|
+
/** Generate route completions and validate Link/router destinations. */
|
|
136
|
+
typedRoutes?: boolean;
|
|
137
|
+
/** Native CSS for default styles; custom PostCSS pipelines remain authoritative. */
|
|
138
|
+
cssEngine?: 'auto'|'postcss'|'lightningcss';
|
|
133
139
|
/** Production checks use the project's installed TypeScript without emitting files. */
|
|
134
|
-
typescript?: { ignoreBuildErrors?: boolean; tsconfigPath?: string };
|
|
140
|
+
typescript?: { ignoreBuildErrors?: boolean; tsconfigPath?: string; devChecks?: boolean };
|
|
135
141
|
cacheComponents?: boolean;
|
|
136
142
|
cacheLife?: Record<string, import('./cache.cjs').CacheLife>;
|
|
137
143
|
transpilePackages?: string[];
|
|
@@ -191,9 +197,17 @@ export interface StaticBuildOptions {
|
|
|
191
197
|
dev?: false;
|
|
192
198
|
}
|
|
193
199
|
export interface BuildResult { buildId: string; outputDirectory: string; }
|
|
200
|
+
export interface ProductionBuilder {
|
|
201
|
+
build(options?: {envMode?: 'production' | 'test'}): Promise<BuildResult>;
|
|
202
|
+
stats(): {contexts: number; created: number; reused: number};
|
|
203
|
+
dispose(): Promise<void>;
|
|
204
|
+
}
|
|
205
|
+
export function createBuilder(projectRoot: string, options?: {envMode?: 'production' | 'test'}): Promise<ProductionBuilder>;
|
|
194
206
|
export interface StaticBuildResult extends BuildResult { files: number; bytes: number; }
|
|
195
207
|
export function build(projectRoot: string, options: StaticBuildOptions & { adapter: 'static' }): Promise<StaticBuildResult>;
|
|
196
|
-
|
|
208
|
+
/** Detect a completed browser output or a PRNext React project; does not run another builder. */
|
|
209
|
+
export function build(projectRoot: string, options: { adapter: 'auto'; dev?: false; spa?: boolean }): Promise<BuildResult | StaticBuildResult>;
|
|
210
|
+
export function build(projectRoot: string, options?: { adapter?: 'react'; dev?: boolean; incremental?: boolean; envMode?: 'production' | 'development' | 'test' }): Promise<BuildResult>;
|
|
197
211
|
export function buildStatic(projectRoot: string, options?: StaticBuildOptions): Promise<StaticBuildResult>;
|
|
198
212
|
export function readBuildOutput(buildDirectory: string): Promise<BuildOutput>;
|
|
199
213
|
export function validateBuildOutput(value: unknown): BuildOutput;
|
package/compat/link.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { AnchorHTMLAttributes, ForwardRefExoticComponent, ReactNode, RefAttributes } from 'react';
|
|
2
2
|
import type { UrlObject } from './router.cjs';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import type {Route,TypedUrlObject} from './typed-route.cjs';
|
|
4
|
+
export interface LinkProps<T extends string=string> extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
|
|
5
|
+
href: Route<T> | TypedUrlObject;
|
|
5
6
|
as?: string | UrlObject;
|
|
6
7
|
replace?: boolean;
|
|
7
8
|
scroll?: boolean;
|
|
@@ -13,5 +14,5 @@ export interface LinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
|
13
14
|
children?: ReactNode;
|
|
14
15
|
onNavigate?: (event: { preventDefault(): void }) => void;
|
|
15
16
|
}
|
|
16
|
-
declare
|
|
17
|
+
declare function Link<T extends string>(props:LinkProps<T> & RefAttributes<HTMLAnchorElement>): import('react').ReactElement|null;
|
|
17
18
|
export default Link;
|
package/compat/navigation.d.cts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import type {Route} from './typed-route.cjs';
|
|
1
2
|
export interface NavigateOptions { scroll?: boolean }
|
|
2
3
|
export interface PrefetchOptions { onInvalidate?: () => void }
|
|
3
4
|
export interface AppRouterInstance {
|
|
4
|
-
push(href:
|
|
5
|
-
replace(href:
|
|
5
|
+
push<T extends string>(href: Route<T>, options?: NavigateOptions): void;
|
|
6
|
+
replace<T extends string>(href: Route<T>, options?: NavigateOptions): void;
|
|
6
7
|
refresh(): void;
|
|
7
8
|
back(): void;
|
|
8
9
|
forward(): void;
|
|
9
|
-
prefetch(href:
|
|
10
|
+
prefetch<T extends string>(href: Route<T>, options?: PrefetchOptions): void;
|
|
10
11
|
}
|
|
11
12
|
export class ReadonlyURLSearchParams extends URLSearchParams {
|
|
12
13
|
append(): never;
|
package/compat/router.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ComponentType, ReactNode, ReactElement } from 'react';
|
|
2
|
+
import type {Route,TypedUrlObject} from './typed-route.cjs';
|
|
2
3
|
export interface UrlObject {
|
|
3
4
|
auth?: string | null;
|
|
4
5
|
host?: string | null;
|
|
@@ -33,9 +34,9 @@ export interface PRNextRouter extends RouterSnapshot {
|
|
|
33
34
|
isFallback: boolean;
|
|
34
35
|
isPreview: boolean;
|
|
35
36
|
basePath: string;
|
|
36
|
-
push(url:
|
|
37
|
-
replace(url:
|
|
38
|
-
prefetch(url:
|
|
37
|
+
push<T extends string>(url: Route<T> | TypedUrlObject, as?: string | UrlObject, options?: { scroll?: boolean; shallow?: boolean; locale?: string | false }): Promise<boolean>;
|
|
38
|
+
replace<T extends string>(url: Route<T> | TypedUrlObject, as?: string | UrlObject, options?: { scroll?: boolean; shallow?: boolean; locale?: string | false }): Promise<boolean>;
|
|
39
|
+
prefetch<T extends string>(url: Route<T> | TypedUrlObject, as?: string | UrlObject, options?: { priority?: boolean; locale?: string | false }): Promise<void>;
|
|
39
40
|
beforePopState(callback: (state: { url: string; as: string; options: { shallow?: boolean; scroll?: boolean } }) => boolean): void;
|
|
40
41
|
events: RouterEvents;
|
|
41
42
|
reload(): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type {UrlObject} from './router.cjs';
|
|
2
|
+
declare global { namespace PRNext { interface RouteMap {} interface RouteConfig {} } }
|
|
3
|
+
type GeneratedRoutes = keyof PRNext.RouteMap extends never ? PRNext.RouteConfig extends {enabled:true} ? true : false : true;
|
|
4
|
+
export type RoutePattern = GeneratedRoutes extends true ? Extract<keyof PRNext.RouteMap,string> : string;
|
|
5
|
+
export type RouteParameters<P extends string> = P extends keyof PRNext.RouteMap ? PRNext.RouteMap[P] : Record<string,string|string[]|undefined>;
|
|
6
|
+
type Suffix = '' | `?${string}` | `#${string}`;
|
|
7
|
+
type External = `https://${string}` | `http://${string}` | `mailto:${string}` | `tel:${string}` | `#${string}` | `?${string}`;
|
|
8
|
+
type Expand<P extends string> = P extends `${infer A}/[[...${string}]]` ? A | `${A}/${string}` : P extends `${infer A}[${string}]${infer B}` ? `${A}${string}${Expand<B>}` : P;
|
|
9
|
+
type Strip<P extends string> = P extends `${infer A}#${string}` ? Strip<A> : P extends `${infer A}?${string}` ? A : P;
|
|
10
|
+
type Segment<S extends string> = S extends ''|'.'|'..' ? false : S extends `${string}/${string}`|`${string}\\${string}` ? false : true;
|
|
11
|
+
type Segments<S extends string> = S extends `${infer H}/${infer R}` ? Segment<H> extends true ? Segments<R> : false : Segment<S>;
|
|
12
|
+
type Match<P extends string,S extends string> = P extends `${infer A}/[[...${string}]]` ? S extends A ? true : Match<`${A}/[...rest]`,S> : P extends `[...${string}]` ? Segments<S> : P extends `${infer H}/${infer R}` ? S extends `${infer V}/${infer T}` ? H extends `[${string}]` ? Segment<V> extends true ? Match<R,T> : false : H extends V ? Match<R,T> : false : false : P extends `[${string}]` ? Segment<S> : P extends S ? true : false;
|
|
13
|
+
type Matching<S extends string,P extends string=RoutePattern> = P extends unknown ? Match<P,S> extends true ? true : never : never;
|
|
14
|
+
type Suggestions = Expand<RoutePattern> extends infer P extends string ? `${P}${Suffix}` : never;
|
|
15
|
+
export type Route<T extends string=string> = GeneratedRoutes extends false ? T : string extends T ? Suggestions|External : T extends External ? T : true extends Matching<Strip<T>> ? T : never;
|
|
16
|
+
export type TypedUrlObject = GeneratedRoutes extends false ? UrlObject : {[P in RoutePattern]: Omit<UrlObject,'pathname'|'query'> & {pathname:P} & ({} extends RouteParameters<P> ? {query?:RouteParameters<P>&Record<string,unknown>} : {query:RouteParameters<P>&Record<string,unknown>})}[RoutePattern];
|
|
17
|
+
export function route<P extends RoutePattern>(pattern:P,...args:{} extends RouteParameters<P> ? [params?:RouteParameters<P>] : [params:RouteParameters<P>]): Route;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Compatibility and migration
|
|
2
|
+
|
|
3
|
+
[AI documentation index](../../AI.md)
|
|
4
|
+
|
|
5
|
+
## Treat compatibility as a checked contract
|
|
6
|
+
|
|
7
|
+
PRNext adapts many public Next.js conventions; it is not a drop-in implementation
|
|
8
|
+
of every Next.js version. Package installation, a recognized import or a green
|
|
9
|
+
source audit does not prove an application works. The installed package's
|
|
10
|
+
metadata, declarations, diagnostics and executable behavior are the authority
|
|
11
|
+
for this release. Do not use an old repository checklist to override them.
|
|
12
|
+
|
|
13
|
+
Common supported areas include App/Pages routing, React Server Components,
|
|
14
|
+
Server Actions, request APIs, navigation, route metadata, supported cache APIs,
|
|
15
|
+
CSS Modules and the project's PostCSS pipeline. Scope varies by runtime and
|
|
16
|
+
option. Advanced parallel/intercepting routing, custom webpack/loaders, package
|
|
17
|
+
transpilation, image/font tooling and caching deserve targeted build and browser
|
|
18
|
+
tests. Support for one configuration shape does not imply support for all
|
|
19
|
+
upstream plugin behavior.
|
|
20
|
+
|
|
21
|
+
Configuration uses one `next.config.*` or `prnext.config.*` file and rejects
|
|
22
|
+
unknown keys rather than ignoring them. `reactCompiler: false` is accepted;
|
|
23
|
+
enabling React Compiler is explicitly unsupported. `typedRoutes: true` is
|
|
24
|
+
implemented through PRNext's generated route registry. Do not copy a full
|
|
25
|
+
Next.js configuration template into a new application.
|
|
26
|
+
|
|
27
|
+
Only explicit private Next.js import adapters are supported. Do not add arbitrary
|
|
28
|
+
`next/dist/...` imports or install another Next runtime to satisfy one: isolate
|
|
29
|
+
or replace the incompatible dependency, retain the original framework where
|
|
30
|
+
needed, and describe the limitation honestly.
|
|
31
|
+
|
|
32
|
+
## Migrate conservatively
|
|
33
|
+
|
|
34
|
+
Use the project's existing package manager. For npm, install the intended
|
|
35
|
+
published PRNext release and its matching React peers, then inspect the dry run:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npm i --save-exact @thomas.f/prnext react@19.3.1 react-dom@19.3.1 react-server-dom-webpack@19.3.1
|
|
39
|
+
npx --no-install prnext migrate --dry-run
|
|
40
|
+
npx --no-install prnext migrate --check --json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Review scripts, manager/workspace detection, configuration and reported source
|
|
44
|
+
conflicts. When the plan is correct:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
npx --no-install prnext migrate
|
|
48
|
+
npx --no-install prnext check --json
|
|
49
|
+
npx --no-install prnext typecheck
|
|
50
|
+
npx --no-install prnext build
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Migration changes selected scripts/dependencies, backs up package files and
|
|
54
|
+
existing lockfiles, and keeps Next.js and application source. Public `next/*`
|
|
55
|
+
imports can remain. Do not remove Next, replace all imports or recreate the
|
|
56
|
+
application without a separate need.
|
|
57
|
+
|
|
58
|
+
Installation honors npm, pnpm and Yarn Classic/Berry and the owning workspace
|
|
59
|
+
root. Conflicting managers, unsafe filesystem entries and unlisted workspaces
|
|
60
|
+
can block automatic install. `migrate --no-install` prepares the manifest for
|
|
61
|
+
manual manager execution. Custom shell scripts may require a manual edit.
|
|
62
|
+
An installation failure attempts safe manifest/lockfile restoration; it cannot
|
|
63
|
+
roll back every dependency directory or manager cache. Never solve peer conflicts
|
|
64
|
+
by silently adding `--force` or `--legacy-peer-deps`.
|
|
65
|
+
|
|
66
|
+
`migrate --check` audits without applying a migration. Configuration evaluation
|
|
67
|
+
can run project code, and installed custom loaders still require a real build.
|
|
68
|
+
The audit is not a sandbox for untrusted projects.
|
|
69
|
+
|
|
70
|
+
## Compare running applications
|
|
71
|
+
|
|
72
|
+
Start the original application and the candidate on separate available ports.
|
|
73
|
+
Create `paths.json` with representative concrete URLs:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
["/", "/products/demo", "/api/health"]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Then compare public GET responses:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
prnext migrate --check --against http://localhost:3000 --candidate http://localhost:3001 --routes paths.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The comparison does not authenticate or follow redirects. It compares response
|
|
86
|
+
samples, including status, content type and selected body semantics; it does not
|
|
87
|
+
prove hydration, styling, actions or all dynamic routes. Visit important routes
|
|
88
|
+
in a real browser, test authorized and unauthorized states, and run the existing
|
|
89
|
+
application tests. Keep the original setup available until the candidate is
|
|
90
|
+
actually verified. Timings from this comparison are not controlled benchmarks.
|
|
91
|
+
|
|
92
|
+
## Boundaries that often matter
|
|
93
|
+
|
|
94
|
+
React server/client directives in `.mts`/`.cts` are rejected; use `.ts`/`.tsx`.
|
|
95
|
+
App Route Handlers use Web Request/Response, while Pages APIs retain their own
|
|
96
|
+
request/response contract. App and Pages router hooks are not interchangeable.
|
|
97
|
+
Edge code cannot assume Node APIs or Node-only package behavior.
|
|
98
|
+
|
|
99
|
+
HTTP/1 WebSockets can tunnel through supported external HTTP(S) rewrites and
|
|
100
|
+
hosting gateways; App/Pages handlers do not gain a WebSocket server API. Static
|
|
101
|
+
imports accept completed public browser output, not another framework's SSR
|
|
102
|
+
server. See [Deployment](deployment.md) before choosing an adapter.
|
|
103
|
+
|
|
104
|
+
When a feature is missing, give its exact failing API/option and a supported
|
|
105
|
+
alternative. Do not claim full compatibility, invent a replacement method or
|
|
106
|
+
mask the error with an empty shim.
|