@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
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import {spawn} from 'node:child_process';
|
|
2
|
+
import {mkdir,readdir,readFile,writeFile,rm,link,copyFile,realpath,lstat} from 'node:fs/promises';
|
|
3
|
+
import {randomUUID} from 'node:crypto';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
import {rename} from './fs.mjs';
|
|
7
|
+
import {watchProject} from './watch-project.mjs';
|
|
8
|
+
import {createDevChangeFilter} from './dev-file-changes.mjs';
|
|
9
|
+
import {shouldWatchProjectFile} from './env.mjs';
|
|
10
|
+
import {privateCacheDirectory,pruneCompilerCache} from '../build/persistent-cache.mjs';
|
|
11
|
+
import {DevGateway} from './dev-gateway.mjs';
|
|
12
|
+
import {withBuildLock} from '../build/build-lock.mjs';
|
|
13
|
+
import {createDevToolsServer} from './dev-tools-server.mjs';
|
|
14
|
+
import {createDevSourceTools} from './dev-tools-source.mjs';
|
|
15
|
+
import {createDevMetricsCollector} from './dev-tools-metrics.mjs';
|
|
16
|
+
import {invalidateDevRouteCache} from './dev-cache-control.mjs';
|
|
17
|
+
import {parseDevLogLine} from './dev-worker-logs.mjs';
|
|
18
|
+
import {createDevRuntimePool} from './dev-runtime-pool.mjs';
|
|
19
|
+
import {tryDevCssUpdate} from '../build/dev-css-update.mjs';
|
|
20
|
+
import {devBuildReason} from './dev-build-reasons.mjs';
|
|
21
|
+
import {publishDependencyGraph,affectedDependencies,canSkipUnopenedRoutes} from './dev-dependency-graph.mjs';
|
|
22
|
+
import {prepareDevNotification} from './dev-notification.mjs';
|
|
23
|
+
|
|
24
|
+
const packageRoot=fileURLToPath(new URL('../',import.meta.url));
|
|
25
|
+
export function developmentRoute(routes,raw,config={}) {
|
|
26
|
+
let pathname=new URL(raw,'http://local').pathname;
|
|
27
|
+
const base=config.basePath||'';
|
|
28
|
+
if(base){if(pathname!==base&&!pathname.startsWith(base+'/'))return;pathname=pathname.slice(base.length)||'/';}
|
|
29
|
+
const data=/^\/(?:_prnext|_next)\/data\/[^/]+\/(.*)\.json$/.exec(pathname);
|
|
30
|
+
if(data){pathname='/'+data[1];if(pathname==='/index')pathname='/';else if(pathname.startsWith('/index/index'))pathname=pathname.slice(6);}
|
|
31
|
+
let parts;try{parts=pathname.replace(/\/$/,'').split('/').slice(1).map(decodeURIComponent);if(parts.length===1&&!parts[0])parts=[];}catch{return;}
|
|
32
|
+
if(config.i18n?.locales.includes(parts[0]))parts.shift();
|
|
33
|
+
const matches=[];
|
|
34
|
+
for(const route of routes){
|
|
35
|
+
if(route.internal)continue;
|
|
36
|
+
const pattern=route.pattern==='/'?[]:route.pattern.split('/').slice(1);let index=0,valid=true;const rank=[];
|
|
37
|
+
for(const part of pattern){
|
|
38
|
+
if(/^\[\[\.\.\./.test(part)){index=parts.length;rank.push(0);break;}
|
|
39
|
+
if(/^\[\.\.\./.test(part)){if(index===parts.length)valid=false;index=parts.length;rank.push(1);break;}
|
|
40
|
+
if(/^\[/.test(part)){if(index>=parts.length)valid=false;rank.push(2);}
|
|
41
|
+
else {if(parts[index]!==part)valid=false;rank.push(3);}
|
|
42
|
+
index++;
|
|
43
|
+
}
|
|
44
|
+
if(valid&&index===parts.length)matches.push({route,rank});
|
|
45
|
+
}
|
|
46
|
+
return matches.sort((a,b)=>{for(let i=0;i<Math.max(a.rank.length,b.rank.length);i++){const difference=(b.rank[i]??4)-(a.rank[i]??4);if(difference)return difference;}return 0;})[0]?.route;
|
|
47
|
+
}
|
|
48
|
+
async function stop(child) {
|
|
49
|
+
if(!child||child.exitCode!==null||child.signalCode)return;
|
|
50
|
+
const compiler=child.connected;
|
|
51
|
+
await new Promise(resolve=>{const timer=setTimeout(()=>child.kill('SIGKILL'),compiler?1000:5000);child.once('exit',()=>{clearTimeout(timer);resolve();});if(child.connected)child.disconnect();if(child.stdin)child.stdin.end();else child.kill('SIGTERM');});
|
|
52
|
+
}
|
|
53
|
+
async function snapshot(source,destination) {
|
|
54
|
+
const files=[];
|
|
55
|
+
async function walk(directory,relative=''){
|
|
56
|
+
await mkdir(path.join(destination,relative),{recursive:true});
|
|
57
|
+
for(const entry of await readdir(directory,{withFileTypes:true})){
|
|
58
|
+
const name=path.join(relative,entry.name),file=path.join(directory,entry.name);
|
|
59
|
+
if(entry.isDirectory())await walk(file,name);
|
|
60
|
+
else if(entry.isFile())files.push(name);
|
|
61
|
+
else throw Error('Development build snapshots cannot contain symbolic links');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
await walk(source);let next=0;
|
|
65
|
+
const results=await Promise.allSettled(Array.from({length:8},async()=>{while(next<files.length){const file=files[next++];try{await link(path.join(source,file),path.join(destination,file));}catch(error){if(!['EXDEV','EPERM','ENOTSUP','EACCES'].includes(error.code))throw error;await copyFile(path.join(source,file),path.join(destination,file));}}}));
|
|
66
|
+
const failure=results.find(result=>result.status==='rejected');if(failure)throw failure.reason;
|
|
67
|
+
}
|
|
68
|
+
export {snapshot as snapshotDevelopmentBuild};
|
|
69
|
+
function serverOptions(args) {
|
|
70
|
+
let hostname='127.0.0.1',port=3000,hostAi=false,profile,safe=false;const backend=[];
|
|
71
|
+
for(let i=0;i<args.length;i++){
|
|
72
|
+
const [flag,inline]=args[i].split('=',2);
|
|
73
|
+
if(['--port','-p','--hostname'].includes(flag)){
|
|
74
|
+
const value=inline??args[++i];if(flag==='--hostname')hostname=value;else if(/^\d+$/.test(value||'')&&Number(value)<=65535)port=Number(value);else throw Error('Invalid development port');
|
|
75
|
+
}else if(flag==='--safe'){if(inline!==undefined)throw Error('Use --safe without a value');safe=true;}
|
|
76
|
+
else if(flag==='--host-ai'&&inline===undefined)hostAi=true;else {if(flag==='--profile')profile=inline??args[i+1];backend.push(args[i]);}
|
|
77
|
+
}
|
|
78
|
+
return {hostname,port,backend,hostAi,profile,safe};
|
|
79
|
+
}
|
|
80
|
+
export async function develop(projectRoot,args,binary) {
|
|
81
|
+
return withBuildLock(projectRoot,root=>runDevelopment(root,args,binary),{development:true});
|
|
82
|
+
}
|
|
83
|
+
async function runDevelopment(projectRoot,args,binary) {
|
|
84
|
+
const root=await realpath(projectRoot),options=serverOptions(args),session=randomUUID();
|
|
85
|
+
const stateFile=path.join(root,'.prnext-dev.json'),home=path.join(root,'.prnext-dev',session);
|
|
86
|
+
const diagnosticsDirectory=process.env.PRNEXT_INSPECT_DIR||path.join(home,'diagnostics');
|
|
87
|
+
// The session is ours; never clean a path obtained from application config.
|
|
88
|
+
const container=path.join(root,'.prnext-dev'),info=await lstat(container).catch(error=>{if(error.code!=='ENOENT')throw error;});
|
|
89
|
+
if(info&&(!info.isDirectory()||info.isSymbolicLink()))throw Error('Development directory must be a real directory');
|
|
90
|
+
await mkdir(home,{recursive:true});
|
|
91
|
+
await mkdir(diagnosticsDirectory,{recursive:true,mode:0o700});
|
|
92
|
+
let compiler,gateway,watcher,timer,tools,hostRelay,hostRelayTask,stopping=false,current,discovery,typeChecker,readyState={state:'building'},typeErrors=[],sequence=0;
|
|
93
|
+
let dependencyIndexValid=false;
|
|
94
|
+
const lifecycle=new AbortController();
|
|
95
|
+
let queue=Promise.resolve(),stateQueue=Promise.resolve(),output='.prnext',changes=new Set(),active=new Map(),failed=new Set();
|
|
96
|
+
const failedPaths=new Set();
|
|
97
|
+
const generations=[],styleGenerations=[],retiring=new Set(),pins=new Map(),routeCache=new Map(),filter=createDevChangeFilter(root);
|
|
98
|
+
const runtimePool=createDevRuntimePool({binary,root,home,environment:{PRNEXT_INSPECT_DIR:diagnosticsDirectory,...(options.profile?{PRNEXT_PROFILE:options.profile}:{})},onChild:child=>captureOutput({stderr:child.stderr,once:child.once.bind(child)},'runtime',{forwardStdout:false})});
|
|
99
|
+
const toolSnapshot=()=>({...readyState,safeMode:options.safe,sequence:readyState.sequence??sequence,session,typeErrors,
|
|
100
|
+
routes:(discovery?.project.routes||[]).filter(route=>!route.internal).map(route=>({path:(discovery.config.basePath||'')+(route.pattern==='/'?'/':route.pattern),kind:route.kind,source:route.file?path.relative(root,route.file).replaceAll(path.sep,'/'):undefined,compiled:current?.patterns.has(route.pattern)||false,pinned:[...pins.values()].some(pin=>pin.pattern===route.pattern)})),
|
|
101
|
+
cache:{activeRoutes:active.size,pinnedRoutes:new Set([...pins.values()].map(pin=>pin.pattern)).size,retainedGenerations:generations.length,lookupEntries:routeCache.size,compiler:readyState.compiler,transforms:readyState.transforms,persistentTransforms:true,dependencyIndex:dependencyIndexValid?{valid:true,...current?.manifest.devDependencyIndex}:{valid:false}}});
|
|
102
|
+
function captureOutput(child,source,{forwardStdout=true}={}){
|
|
103
|
+
for(const [stream,level] of [[child.stdout,'info'],[child.stderr,'error']]){
|
|
104
|
+
stream?.setEncoding('utf8');
|
|
105
|
+
const emit=(line,newline)=>{const {text,level:reported,...metadata}=parseDevLogLine(line);tools?.addServerLog(reported||level,text,{process:source,buildId:current?.id,...metadata});if(level==='error'||forwardStdout)(level==='error'?process.stderr:process.stdout).write(text+(newline?'\n':''));};
|
|
106
|
+
let pending='';stream?.on('data',chunk=>{pending+=String(chunk);let end;while((end=pending.indexOf('\n'))>=0){emit(pending.slice(0,end),true);pending=pending.slice(end+1);}if(pending.length>32768){emit(pending.slice(0,16000)+' [line truncated]',true);pending='';}});
|
|
107
|
+
child.once('exit',()=>{if(pending)emit(pending,false);});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function findRoute(raw){
|
|
111
|
+
const key=new URL(raw,'http://local').pathname;
|
|
112
|
+
if(routeCache.has(key))return routeCache.get(key);
|
|
113
|
+
const route=developmentRoute(discovery.project.routes,raw,discovery.config);
|
|
114
|
+
if(routeCache.size>=256)routeCache.delete(routeCache.keys().next().value);routeCache.set(key,route);return route;
|
|
115
|
+
}
|
|
116
|
+
const writeState=value=>{
|
|
117
|
+
const task=stateQueue.then(async()=>{if(value)readyState=value;const temporary=stateFile+'.'+randomUUID()+'.tmp';
|
|
118
|
+
const {notification,text}=prepareDevNotification({...readyState,safeMode:options.safe,typeErrors});
|
|
119
|
+
try{await writeFile(temporary,text);await rename(temporary,stateFile);if(gateway&&!gateway.stopping)await gateway.request('notify',{value:notification});}finally{await rm(temporary,{force:true});}});
|
|
120
|
+
stateQueue=task.catch(()=>{});return task;
|
|
121
|
+
};
|
|
122
|
+
const serialized=run=>{const task=queue.then(()=>{if(stopping)throw Error('Development is stopping');return run();});queue=task.catch(()=>{});return task;};
|
|
123
|
+
async function compilerRequest(type,values={}){
|
|
124
|
+
if(!compiler||compiler.exitCode!==null||compiler.signalCode){
|
|
125
|
+
const cache=await privateCacheDirectory(root,'dev-compiler-v8');
|
|
126
|
+
await pruneCompilerCache(cache);
|
|
127
|
+
compiler=spawn(process.execPath,[path.join(packageRoot,'build/dev-worker.mjs')],{stdio:['pipe','pipe','pipe','ipc'],windowsHide:true,env:{...process.env,NODE_ENV:process.env.NODE_ENV||'development',...(options.safe?{PRNEXT_BUILD_CACHE:'0'}:{}),...(cache?{NODE_COMPILE_CACHE:cache}:{})}});
|
|
128
|
+
captureOutput(compiler,'compiler');
|
|
129
|
+
}
|
|
130
|
+
const child=compiler;
|
|
131
|
+
const result=await new Promise((resolve,reject)=>{
|
|
132
|
+
const clean=()=>{child.off('message',message);child.off('error',error);child.off('exit',exit);};
|
|
133
|
+
const message=value=>{if(value?.type==='result'){clean();resolve(value);}};
|
|
134
|
+
const error=value=>{clean();reject(value);};const exit=()=>error(Error('Development compiler exited'));
|
|
135
|
+
child.on('message',message);child.once('error',error);child.once('exit',exit);child.send({type,root,...values});
|
|
136
|
+
});
|
|
137
|
+
if(result.recycle){compiler=undefined;await stop(child);}
|
|
138
|
+
if(!result.ok)throw Error(result.error);return result;
|
|
139
|
+
}
|
|
140
|
+
async function discover(){
|
|
141
|
+
discovery=(await compilerRequest('discover')).discovery;routeCache.clear();output=discovery.config.distDir;
|
|
142
|
+
if(options.safe)discovery={...discovery,eager:true};
|
|
143
|
+
if(discovery.eager&&!options.safe)console.log('Development uses the complete route graph for middleware, rewrites or parallel routing.');
|
|
144
|
+
const {writeTypedRoutes}=await import('../build/typed-routes.mjs');
|
|
145
|
+
await writeTypedRoutes(root,discovery.project,discovery.config);
|
|
146
|
+
if(!typeChecker||typeChecker.inactive){const {startTypeChecker}=await import('../build/typescript-dev.mjs');typeChecker=await startTypeChecker(root,discovery.config,errors=>{typeErrors=errors;if(!stopping)void writeState().catch(console.error);});}
|
|
147
|
+
}
|
|
148
|
+
async function launch(manifest,directory){
|
|
149
|
+
const runtime=options.safe?undefined:await runtimePool.take(directory).catch(error=>{tools?.addServerLog('warn','Worker warmup was unavailable; starting a fresh worker.',{process:'runtime'});return undefined;});
|
|
150
|
+
const env={...process.env,NODE_ENV:process.env.NODE_ENV||'development',PRNEXT_INSPECT_DIR:diagnosticsDirectory};
|
|
151
|
+
delete env.PRNEXT_DEV_BROKER;
|
|
152
|
+
if(runtime)env.PRNEXT_DEV_BROKER=runtime.descriptor;
|
|
153
|
+
const child=spawn(binary,['start',root,'--build-dir',directory,'--worker',path.join(root,directory,'runtime/worker.mjs'),'--node',process.execPath,'--hostname','127.0.0.1','--port','0','--shutdown-on-stdin-eof',...options.backend],{stdio:['pipe','pipe','pipe'],windowsHide:true,env});
|
|
154
|
+
captureOutput(child,'runtime',{forwardStdout:false});
|
|
155
|
+
let output='';
|
|
156
|
+
const port=await new Promise((resolve,reject)=>{
|
|
157
|
+
const timer=setTimeout(()=>fail(Error('Development backend did not start')),15000);
|
|
158
|
+
const clean=()=>{clearTimeout(timer);child.off('error',fail);child.off('exit',exit);};
|
|
159
|
+
const fail=error=>{clean();reject(error);};const exit=()=>fail(Error('Development backend exited before listening'));
|
|
160
|
+
child.once('error',fail);child.once('exit',exit);child.stdout.on('data',chunk=>{output=(output+chunk).slice(-8192);process.stdout.write(chunk);const match=/http:\/\/127\.0\.0\.1:(\d+)/.exec(output);if(match){clean();resolve(Number(match[1]));}});
|
|
161
|
+
}).catch(async error=>{await stop(child);await runtime?.close();throw error;});
|
|
162
|
+
return {id:manifest.buildId,child,runtime,port,directory};
|
|
163
|
+
}
|
|
164
|
+
async function rebuild(patterns,changedFiles=[],affectedRoutes,affectedModules,cause){
|
|
165
|
+
const startedAt=Date.now(),started=performance.now(),phases={};let mark=started;
|
|
166
|
+
let reason=devBuildReason({initial:!current,safe:options.safe,changed:changedFiles,cause});
|
|
167
|
+
await writeState({state:'building',reason});
|
|
168
|
+
const changed=changedFiles.slice(0,32).map(file=>file.slice(0,256));
|
|
169
|
+
try{
|
|
170
|
+
if(!options.safe)void runtimePool.warm().catch(()=>{});
|
|
171
|
+
if(current&&!discovery.eager&&discovery.cssFast){
|
|
172
|
+
const directory=path.relative(root,path.join(home,'styles-'+randomUUID())).replaceAll(path.sep,'/');
|
|
173
|
+
const styles=await tryDevCssUpdate({root,source:path.join(root,current.cssDirectory||current.directory),destination:path.join(root,directory),manifest:current.manifest,changed:changedFiles,sequence:sequence+1,cssEngine:discovery.config.cssEngine,snapshot,onFallback:code=>{reason=devBuildReason({changed:changedFiles,cause:code});}});
|
|
174
|
+
if(styles){
|
|
175
|
+
await gateway.request('assets',{generation:styles.buildId,directory});
|
|
176
|
+
try{styles.devDependencyIndex=await publishDependencyGraph(gateway,styles);dependencyIndexValid=true;}catch(error){dependencyIndexValid=false;await gateway.request('graph',{routes:[]});tools?.addServerLog('warn','Selective HMR unavailable: '+error.message,{process:'compiler'});}
|
|
177
|
+
sequence++;current.manifest=styles;current.cssDirectory=directory;current.cssOnly=true;current.affectedRoutes=undefined;current.changed=changed;
|
|
178
|
+
styleGenerations.push({id:styles.buildId,directory});
|
|
179
|
+
reason=devBuildReason({cause:'css-only'});
|
|
180
|
+
await writeState({state:'ready',reason,buildId:styles.buildId,clientManifest:styles.devClient,sequence,session,changed,cssOnly:true,listenerPid:gateway.child.pid,compiler:current.compiler,transforms:current.transforms});
|
|
181
|
+
tools?.recordBuild({buildId:styles.buildId,reason:reason.summary,reasonCode:reason.code,sequence,time:Date.now(),startedAt,duration:performance.now()-started,phases:{styles:performance.now()-started},mode:'css',state:'ready'});
|
|
182
|
+
console.log('Styles updated. '+reason.summary);
|
|
183
|
+
while(styleGenerations.length>3){const old=styleGenerations.shift();await gateway.request('retire',{generation:old.id});await gateway.request('forget',{generation:old.id});await rm(path.join(root,old.directory),{recursive:true,force:true,maxRetries:10,retryDelay:100});}
|
|
184
|
+
await watcher?.refresh();return styles;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const result=await compilerRequest('build',{options:{...(discovery.eager?{}:{devRoutes:patterns}),devSafe:options.safe,devSequence:++sequence,devSession:session,devTools:tools?.bootstrap,devAffectedModules:affectedModules,devForceRefresh:options.safe||changedFiles.some(file=>/\.(?:css|scss|sass)$/.test(file)||/(?:postcss|browserslist|\.env|config)/.test(file))}});
|
|
188
|
+
phases.compile=performance.now()-mark;mark=performance.now();
|
|
189
|
+
if(stopping)throw Error('Development is stopping');
|
|
190
|
+
const consumed=filter.acceptInputs(result.inputs||[]);
|
|
191
|
+
// Config can change after the watch batch was captured but before the
|
|
192
|
+
// compiler reads it. Publish that change once and acknowledge its bytes.
|
|
193
|
+
for(const file of consumed)if(/(?:^|\/)(?:(?:prnext|next)\.config\.|\.env)/.test(file)&&!changed.includes(file))changed.push(file.slice(0,256));
|
|
194
|
+
const manifest=result.manifest;
|
|
195
|
+
const directory=path.relative(root,path.join(home,manifest.buildId)).replaceAll(path.sep,'/');
|
|
196
|
+
await snapshot(manifest.outputDirectory,path.join(root,directory));
|
|
197
|
+
phases.snapshot=performance.now()-mark;mark=performance.now();
|
|
198
|
+
if(stopping)throw Error('Development is stopping');
|
|
199
|
+
let next;try{next=await launch(manifest,directory);phases.launch=performance.now()-mark;mark=performance.now();if(stopping)throw Error('Development is stopping');await gateway.request('activate',{generation:next.id,port:next.port,directory});phases.activate=performance.now()-mark;if(stopping)throw Error('Development is stopping');}catch(error){await stop(next?.child);await rm(path.join(root,directory),{recursive:true,force:true,maxRetries:10,retryDelay:100});throw error;}
|
|
200
|
+
try{manifest.devDependencyIndex=await publishDependencyGraph(gateway,manifest);dependencyIndexValid=true;}
|
|
201
|
+
catch(error){dependencyIndexValid=false;await gateway.request('graph',{routes:[]});affectedRoutes=undefined;affectedModules=undefined;tools?.addServerLog('warn','Selective HMR unavailable: '+error.message,{process:'compiler'});}
|
|
202
|
+
const previous=current;current=next;current.compiler=result.compiler;current.transforms=result.transforms;generations.push(next);failed.clear();failedPaths.clear();
|
|
203
|
+
current.manifest=manifest;current.changed=changed;current.patterns=new Set(discovery.eager?discovery.project.routes.map(route=>route.pattern):patterns);
|
|
204
|
+
const affected=Object.keys(manifest.app?.actions||{}).length?undefined:affectedRoutes;
|
|
205
|
+
current.affectedRoutes=affected;
|
|
206
|
+
await writeState({state:'ready',reason,buildId:manifest.buildId,clientManifest:manifest.devClient,sequence:manifest.devSequence,session,changed,affectedRoutes:affected,listenerPid:gateway.child.pid,compiler:result.compiler,transforms:result.transforms,routes:manifest.routes.filter(route=>!route.internal).map(route=>route.pattern)});
|
|
207
|
+
tools?.recordBuild({buildId:manifest.buildId,reason:reason.summary,reasonCode:reason.code,sequence:manifest.devSequence,time:Date.now(),startedAt,duration:performance.now()-started,phases,routes:patterns.length,state:'ready'});
|
|
208
|
+
console.log('Source compiled. '+reason.summary);
|
|
209
|
+
if(previous){
|
|
210
|
+
const retiringTask=(async()=>{await gateway.request('retire',{generation:previous.id}).catch(()=>{});await previous.runtime?.close();await stop(previous.child);})().finally(()=>retiring.delete(retiringTask));retiring.add(retiringTask);
|
|
211
|
+
}
|
|
212
|
+
while(generations.length>3){const old=generations.shift();await gateway.request('retire',{generation:old.id});await old.runtime?.close();await stop(old.child);await gateway.request('forget',{generation:old.id});await rm(path.join(root,old.directory),{recursive:true,force:true,maxRetries:10,retryDelay:100});}
|
|
213
|
+
await watcher?.refresh();
|
|
214
|
+
return manifest;
|
|
215
|
+
}catch(error){if(!stopping){tools?.recordBuild({sequence,reason:reason.summary,reasonCode:reason.code,time:Date.now(),startedAt,duration:performance.now()-started,phases,state:'error'});tools?.addServerLog('error',error.message,{process:'compiler'});console.error('Build failed: '+error.message);await writeState({state:'error',reason,error:error.message});}throw error;}
|
|
216
|
+
}
|
|
217
|
+
const updateInfo=()=>({buildId:current.manifest.buildId,clientManifest:current.manifest.devClient,pagesManifest:current.manifest.pagesManifest,sequence:current.manifest.devSequence,session,changed:current.changed,cssOnly:current.cssOnly,affectedRoutes:current.affectedRoutes});
|
|
218
|
+
async function ensure(raw,client){
|
|
219
|
+
const warm=discovery&&findRoute(raw);
|
|
220
|
+
if(!stopping&¤t&&warm&&active.has(warm.pattern)&¤t.patterns.has(warm.pattern)){
|
|
221
|
+
active.set(warm.pattern,Date.now());if(pins.has(client))pins.get(client).pattern=warm.pattern;
|
|
222
|
+
return updateInfo();
|
|
223
|
+
}
|
|
224
|
+
return serialized(async()=>{
|
|
225
|
+
if(!discovery)try{await discover();}catch(error){failedPaths.add(raw);await writeState({state:'error',error:error.message});throw error;}
|
|
226
|
+
let route=findRoute(raw);
|
|
227
|
+
// A newly written route can be requested before the watcher's debounce.
|
|
228
|
+
// Resolve that miss against the filesystem instead of returning a stale 404.
|
|
229
|
+
if(!route){await discover();route=findRoute(raw);}
|
|
230
|
+
if(route)active.set(route.pattern,Date.now());
|
|
231
|
+
if(route&&pins.has(client))pins.get(client).pattern=route.pattern;
|
|
232
|
+
const max=Math.max(0,Math.min(64,discovery.config.onDemandEntries?.pagesBufferLength??5));
|
|
233
|
+
const pinned=new Set([...pins.values()].map(value=>value.pattern));
|
|
234
|
+
if(route)pinned.add(route.pattern);
|
|
235
|
+
const inactive=[...active].filter(([pattern])=>!pinned.has(pattern)).sort((a,b)=>a[1]-b[1]);
|
|
236
|
+
const age=Math.min(86400000,discovery.config.onDemandEntries?.maxInactiveAge??60000);
|
|
237
|
+
while(inactive.length>max&&(Date.now()-inactive[0][1]>=age||active.size>64+pinned.size))active.delete(inactive.shift()[0]);
|
|
238
|
+
if(!current || route&&!current.patterns.has(route.pattern)){
|
|
239
|
+
try{await rebuild([...active.keys()]);}catch(error){if(route){failed.add(route.pattern);active.delete(route.pattern);}throw error;}
|
|
240
|
+
}
|
|
241
|
+
return updateInfo();
|
|
242
|
+
});}
|
|
243
|
+
async function changed(){
|
|
244
|
+
try{
|
|
245
|
+
const pending=[...changes];changes.clear();
|
|
246
|
+
const actual=await filter(pending.filter(file=>file!==output&&!file.startsWith(output+'/')&&!output.startsWith(file+'/')));if(!actual.length)return;
|
|
247
|
+
if(actual.some(file=>/(?:^|\/)(?:package(?:-lock)?\.json|(?:pnpm-lock\.yaml|yarn\.lock)|(?:prnext|next|contentlayer)\.config\.|\.env)/.test(file))){const old=compiler;compiler=undefined;if(old){old.disconnect();await stop(old);}await typeChecker?.close();typeChecker=undefined;typeErrors=[];}
|
|
248
|
+
await discover();
|
|
249
|
+
for(const raw of failedPaths){const route=findRoute(raw);if(route)active.set(route.pattern,Date.now());}
|
|
250
|
+
const valid=new Set(discovery.project.routes.map(route=>route.pattern));for(const pattern of active.keys())if(!valid.has(pattern))active.delete(pattern);
|
|
251
|
+
let affected,modules,cause;
|
|
252
|
+
if(current&&!dependencyIndexValid)cause='dependency-capacity';
|
|
253
|
+
if(current&&dependencyIndexValid&&!discovery.eager&&!failed.size&&!failedPaths.size){
|
|
254
|
+
const result=await affectedDependencies(gateway,actual);
|
|
255
|
+
if(result.unknown.length)cause='unknown-dependency';
|
|
256
|
+
if(!result.complete)cause='dependency-capacity';
|
|
257
|
+
const dormant=new Set(discovery.project.routes.filter(route=>!current.patterns.has(route.pattern)).map(route=>path.relative(root,route.file).replaceAll(path.sep,'/')));
|
|
258
|
+
if(canSkipUnopenedRoutes(result,actual,dormant,dependencyIndexValid)){const skipped=devBuildReason({cause:'outside-active-graph'});tools?.recordBuild({time:Date.now(),duration:0,state:'skipped',reason:skipped.summary,reasonCode:skipped.code,changed:actual});return;}
|
|
259
|
+
if(!result.unknown.length&&result.complete){
|
|
260
|
+
if(result.modules.length)modules=result.modules;
|
|
261
|
+
if(result.routes.length)affected=result.routes;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if(current||failed.size||failedPaths.size)await rebuild([...new Set([...active.keys(),...failed])],actual,affected,modules,cause);
|
|
265
|
+
}catch(error){if(!stopping&&readyState.state!=='error'){console.error('Build failed: '+error.message);await writeState({state:'error',error:error.message});}}
|
|
266
|
+
}
|
|
267
|
+
let closing;
|
|
268
|
+
const shutdown=()=>{
|
|
269
|
+
if(closing)return closing;stopping=true;lifecycle.abort();clearTimeout(timer);watcher?.close();
|
|
270
|
+
closing=(async()=>{
|
|
271
|
+
await Promise.all([gateway?.close(),hostRelay?.close(),hostRelayTask,stop(compiler),typeChecker?.close()]);
|
|
272
|
+
// Keep each native data-cache bridge alive until its externally owned
|
|
273
|
+
// Node process has drained after() work and cache fills on stdin EOF.
|
|
274
|
+
await runtimePool.close();
|
|
275
|
+
await Promise.all([tools?.close(),...generations.map(g=>stop(g.child)),...retiring]);
|
|
276
|
+
await queue.catch(()=>{});await stateQueue;await rm(home,{recursive:true,force:true,maxRetries:10,retryDelay:100});await rm(stateFile,{force:true});
|
|
277
|
+
})();return closing;
|
|
278
|
+
};
|
|
279
|
+
const fatal=error=>{if(!stopping){console.error(error.message);process.exitCode=1;void shutdown();}};
|
|
280
|
+
process.once('SIGINT',shutdown);process.once('SIGTERM',shutdown);
|
|
281
|
+
try{
|
|
282
|
+
tools=await createDevToolsServer({root,session,snapshot:toolSnapshot,sourceTools:await createDevSourceTools(root),collectMetrics:createDevMetricsCollector(diagnosticsDirectory),ensureRoute:raw=>ensure(raw,'mcp'),invalidateRoute:raw=>serialized(async()=>{
|
|
283
|
+
if(!discovery)await discover();const route=findRoute(raw);if(!route)throw Error('No project route matches this pathname');
|
|
284
|
+
if(!current||current.child.exitCode!==null)return {invalidated:false,scope:'native-route-cache',reason:'No active runtime cache yet. Visit the route first.'};
|
|
285
|
+
const base=discovery.config.basePath||'',logical=base&&(raw===base||raw.startsWith(base+'/'))?raw.slice(base.length)||'/':raw;
|
|
286
|
+
const result=await invalidateDevRouteCache({root,directory:current.directory,session,buildId:current.id,paths:[raw,logical]});
|
|
287
|
+
return {...updateInfo(),...result};
|
|
288
|
+
})});
|
|
289
|
+
await writeState({state:'building',session});
|
|
290
|
+
gateway=new DevGateway(binary,root,{...options,onEnsure:ensure,onFatal:fatal,
|
|
291
|
+
onOpen:message=>{const route=discovery&&findRoute(message.path);if(route)pins.set(message.client,{pattern:route.pattern,connection:message.connection});},
|
|
292
|
+
onClose:message=>{if(pins.get(message.client)?.connection===message.connection)pins.delete(message.client);}});
|
|
293
|
+
const listener=await gateway.ready;
|
|
294
|
+
tools.setAppUrl(`http://${options.hostname.includes(':')?'['+options.hostname+']':options.hostname}:${listener.port}`);
|
|
295
|
+
if(options.hostAi){
|
|
296
|
+
tools.setHostRelayState({enabled:true,linked:false});
|
|
297
|
+
hostRelayTask=import('./dev-host-relay.mjs').then(({startDevHostRelay})=>startDevHostRelay({root,tools,signal:lifecycle.signal})).then(async relay=>{hostRelay=relay;if(stopping)await relay.close();}).catch(error=>{if(!stopping){tools.setHostRelayState({enabled:true,linked:false,error:error.message});tools.addServerLog('warn','Host AI relay is unavailable. Local development remains running.',{process:'host-relay'});}});
|
|
298
|
+
}
|
|
299
|
+
console.log(`PRNext development · http://${options.hostname.includes(':')?'['+options.hostname+']':options.hostname}:${listener.port} · on-demand routes`);
|
|
300
|
+
if(options.safe)console.log('Safe mode: full route builds, fresh compilers and no worker warmup.');
|
|
301
|
+
watcher=await watchProject(root,{ignore:relative=>relative===output||relative.startsWith(output+'/'),onError:fatal,onChange:file=>{
|
|
302
|
+
if(stopping||!shouldWatchProjectFile(file))return;changes.add(String(file).replaceAll(path.sep,'/'));clearTimeout(timer);timer=setTimeout(()=>void serialized(changed).catch(()=>{}),75);
|
|
303
|
+
}});
|
|
304
|
+
await serialized(discover).catch(async error=>{console.error('Build failed: '+error.message);await writeState({state:'error',error:error.message});});
|
|
305
|
+
await gateway.closed;
|
|
306
|
+
}finally{await shutdown();process.off('SIGINT',shutdown);process.off('SIGTERM',shutdown);}
|
|
307
|
+
}
|
package/runtime/env.mjs
CHANGED
|
@@ -24,7 +24,7 @@ export function envFileNames({ dev = false, env = process.env, mode = env.NODE_E
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/** Load root env files in Next's precedence order, without logging values. */
|
|
27
|
-
export function loadEnvConfig(projectRoot, { dev = false, env = process.env, mode } = {}) {
|
|
27
|
+
export function loadEnvConfig(projectRoot, { dev = false, env = process.env, mode, inputs } = {}) {
|
|
28
28
|
for (const [key, value] of loadedValues.get(env) || []) {
|
|
29
29
|
if (env[key] === value) delete env[key];
|
|
30
30
|
}
|
|
@@ -38,6 +38,7 @@ export function loadEnvConfig(projectRoot, { dev = false, env = process.env, mod
|
|
|
38
38
|
try {
|
|
39
39
|
if (!statSync(filename).isFile()) continue;
|
|
40
40
|
contents = readFileSync(filename, 'utf8');
|
|
41
|
+
inputs?.record(filename,contents);
|
|
41
42
|
} catch (error) {
|
|
42
43
|
if (error.code === 'ENOENT') continue;
|
|
43
44
|
throw new Error(`Could not load ${filename}: ${error.code || error.message}`, { cause: error });
|
package/runtime/pages-client.mjs
CHANGED
|
@@ -176,10 +176,19 @@ async function startPages({ Page, App, pattern, manifestUrl, basePath = '', trai
|
|
|
176
176
|
installPagesRouter(controller);
|
|
177
177
|
let resolveHydration;
|
|
178
178
|
const hydrated = new Promise(resolve => { resolveHydration = resolve; });
|
|
179
|
-
root = hydrateRoot(container, pagesView({ state: initial, strictMode, committed: resolveHydration, reportError: controller.reportRenderError })
|
|
179
|
+
root = hydrateRoot(container, pagesView({ state: initial, strictMode, committed: resolveHydration, reportError: controller.reportRenderError }), process.env.NODE_ENV === 'development' ? {
|
|
180
|
+
onRecoverableError(error, info) {
|
|
181
|
+
console.error(error);
|
|
182
|
+
globalThis.__PRNEXT_DEV__?.reportRecoverableError(error, info);
|
|
183
|
+
},
|
|
184
|
+
} : undefined);
|
|
180
185
|
await hydrated;
|
|
181
186
|
if (data.router.isFallback) void controller.refreshFallback();
|
|
182
187
|
return { root, router: controller, ...(process.env.NODE_ENV === 'development' ? {
|
|
188
|
+
devSync(options) {
|
|
189
|
+
buildId=options.dev.buildId;window.__PRNEXT_DATA__.buildId=buildId;
|
|
190
|
+
controller.devSetManifest({buildId,manifestUrl:options.manifestUrl});
|
|
191
|
+
},
|
|
183
192
|
async devCommit(options) {
|
|
184
193
|
buildId = options.dev.buildId;
|
|
185
194
|
window.__PRNEXT_DATA__.buildId = buildId;
|
|
@@ -328,6 +328,7 @@ export function createPagesNavigation({ initial, initialRoute, manifestUrl, read
|
|
|
328
328
|
async function navigate(url, as, options = {}) {
|
|
329
329
|
let { request, destination } = targets(url, as, options);
|
|
330
330
|
if (!isApplicationURL(destination, win.location.origin, basePath) || !isApplicationURL(request, win.location.origin, basePath)) { hard(destination, options.replace); return true; }
|
|
331
|
+
if(process.env.NODE_ENV==='development'&&!options.devRefresh)await globalThis.__PRNEXT_DEV__?.ensureRoute?.(request.href);
|
|
331
332
|
cancelActive(); renderingError?.abort.abort(); renderingError = undefined; saveScroll();
|
|
332
333
|
const job = { id: ++sequence, abort: new AbortController(), asPath: destination.pathname + destination.search + destination.hash, shallow: !!options.shallow };
|
|
333
334
|
active = job;
|
|
@@ -554,10 +555,14 @@ export function createPagesNavigation({ initial, initialRoute, manifestUrl, read
|
|
|
554
555
|
win.history.scrollRestoration = 'manual';
|
|
555
556
|
win.addEventListener('popstate', popstate); win.addEventListener('scroll', onScroll, { passive: true }); win.addEventListener('pagehide', pagehide);
|
|
556
557
|
return { navigate, prefetch, saveScroll, reportRenderError, snapshot: () => current.router, beforePopState(callback) { beforePop = callback; },
|
|
558
|
+
...(process.env.NODE_ENV === 'development' ? { devSetManifest(options) {
|
|
559
|
+
buildId=options.buildId;manifestUrl=options.manifestUrl;manifestPromise=undefined;
|
|
560
|
+
for(const key of prefetches.keys())dropPrefetch(key);
|
|
561
|
+
}} : {}),
|
|
557
562
|
...(process.env.NODE_ENV === 'development' ? { async devRefresh(options) {
|
|
558
563
|
buildId = options.buildId; manifestUrl = options.manifestUrl; manifestPromise = undefined;
|
|
559
564
|
for (const key of prefetches.keys()) dropPrefetch(key);
|
|
560
|
-
await navigate(currentRequest, currentURL, { replace: true, scroll: false });
|
|
565
|
+
await navigate(currentRequest, currentURL, { replace: true, scroll: false, devRefresh:true });
|
|
561
566
|
} } : {}),
|
|
562
567
|
refreshFallback,
|
|
563
568
|
dispose() { pagehide(); win.removeEventListener('popstate', popstate); win.removeEventListener('scroll', onScroll); win.removeEventListener('pagehide', pagehide); if (frame !== undefined) win.cancelAnimationFrame(frame); },
|
|
@@ -19,13 +19,14 @@ export const persistentHelp = `Persistent apps (ordinary prn start stays in the
|
|
|
19
19
|
pstart options: --name NAME --port 3000 --hostname 127.0.0.1 --workers 1
|
|
20
20
|
--profile balanced|speed|memory|classic --inspect
|
|
21
21
|
--health-path / --health-timeout 15000 --drain-timeout 30000 (milliseconds)
|
|
22
|
+
--rollback-window 15000 (post-switch monitoring; 0 disables, maximum 120000 ms)
|
|
22
23
|
prestart/pstop/plogs default to the app in the current directory.
|
|
23
24
|
plogs options: --lines 100 --follow. App environment comes from its .env files.
|
|
24
25
|
State and logs: ~/.prnext/processes (override with PRNEXT_PM_HOME).
|
|
25
26
|
`;
|
|
26
27
|
|
|
27
28
|
export function parsePersistent(command, argv) {
|
|
28
|
-
const allowed = command === 'pstart' ? ['name', 'port', 'hostname', 'workers', 'profile', 'inspect', 'health-path', 'health-timeout', 'drain-timeout']
|
|
29
|
+
const allowed = command === 'pstart' ? ['name', 'port', 'hostname', 'workers', 'profile', 'inspect', 'health-path', 'health-timeout', 'drain-timeout', 'rollback-window']
|
|
29
30
|
: command === 'plogs' ? ['follow', 'lines', 'supervisor'] : command === 'pstartup' ? ['remove']
|
|
30
31
|
: ['pstatus', 'plist'].includes(command) ? ['json'] : ['prestart', 'pstop', 'pdelete'].includes(command) ? ['all'] : [];
|
|
31
32
|
const flags = new Set(['inspect', 'follow', 'supervisor', 'remove', 'json', 'all']);
|
|
@@ -41,9 +42,9 @@ export function parsePersistent(command, argv) {
|
|
|
41
42
|
} else { if (target !== undefined) throw new Error(`${command} accepts one ${command === 'pstart' ? 'directory' : 'app name'}`); target = arg; }
|
|
42
43
|
}
|
|
43
44
|
if (target && (options.all || options.supervisor || ['pstartup', 'pdown'].includes(command))) throw new Error('Choose an app name or a global option, not both.');
|
|
44
|
-
for (const key of ['port', 'workers', 'health-timeout', 'drain-timeout', 'lines']) if (key in options) {
|
|
45
|
-
const max = key === 'port' ? 65535 : key === 'workers' ? 64 : key === 'lines' ? 10000 : 60000;
|
|
46
|
-
const min = key.endsWith('timeout') ? 100 : 1;
|
|
45
|
+
for (const key of ['port', 'workers', 'health-timeout', 'drain-timeout', 'rollback-window', 'lines']) if (key in options) {
|
|
46
|
+
const max = key === 'port' ? 65535 : key === 'workers' ? 64 : key === 'lines' ? 10000 : key === 'rollback-window' ? 120000 : 60000;
|
|
47
|
+
const min = key === 'rollback-window' ? 0 : key.endsWith('timeout') ? 100 : 1;
|
|
47
48
|
if (!/^\d+$/.test(options[key]) || Number(options[key]) < min || Number(options[key]) > max) throw new Error(`--${key} must be between ${min} and ${max}`);
|
|
48
49
|
options[key] = Number(options[key]);
|
|
49
50
|
}
|
|
@@ -156,7 +157,7 @@ export async function persistentCommand(command, argv) {
|
|
|
156
157
|
if (command === 'pstart') {
|
|
157
158
|
const root = await realpath(path.resolve(target || '.'));
|
|
158
159
|
const name = validName(options.name || path.basename(root).replace(/[^a-zA-Z0-9_-]/g, '-').replace(/^[^a-zA-Z0-9]+/, '').slice(0, 64));
|
|
159
|
-
request = { command: 'start', config: { name, root, binary: await resolveNativeBinary(), node: process.execPath, port: options.port || 3000, hostname: options.hostname || '127.0.0.1', workers: options.workers || 1, profile: options.profile || process.env.PRNEXT_PROFILE || 'balanced', inspect: !!options.inspect, healthPath: options['health-path'] || '/', healthTimeout: options['health-timeout'] || 15000, drainTimeout: options['drain-timeout'] || 30000 } };
|
|
160
|
+
request = { command: 'start', config: { name, root, binary: await resolveNativeBinary(), node: process.execPath, port: options.port || 3000, hostname: options.hostname || '127.0.0.1', workers: options.workers || 1, profile: options.profile || process.env.PRNEXT_PROFILE || 'balanced', inspect: !!options.inspect, healthPath: options['health-path'] || '/', healthTimeout: options['health-timeout'] || 15000, drainTimeout: options['drain-timeout'] || 30000, rollbackWindow: options['rollback-window'] ?? 15000 } };
|
|
160
161
|
} else request = { command: ({ prestart: 'restart', pstop: 'stop', pdelete: 'delete' })[command], name: target, all: !!options.all, root: target || options.all ? undefined : await realpath(process.cwd()) };
|
|
161
162
|
await ensure(connection);
|
|
162
163
|
const report = await rpc(connection, request);
|