@thomas.f/prnext 0.1.19-alpha → 0.1.21-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/build/app.mjs +46 -78
- package/build/build-lock.mjs +2 -71
- package/build/build-retention.mjs +119 -0
- package/build/build-timings.mjs +38 -0
- package/build/bundle-runtime.mjs +17 -16
- package/build/cache-command.mjs +31 -0
- package/build/check.mjs +41 -3
- package/build/compilation-cache.mjs +32 -12
- package/build/compilation-storage.mjs +190 -0
- package/build/compiler.mjs +5 -4
- package/build/config.mjs +23 -2
- package/build/create.mjs +1 -1
- package/build/css-modules.mjs +4 -0
- package/build/dev-action-graph.mjs +21 -0
- package/build/flight-summary.mjs +64 -0
- package/build/font-cache.mjs +9 -6
- package/build/font-files.mjs +1 -1
- package/build/index.mjs +57 -36
- package/build/inspect.mjs +3 -0
- package/build/inspection-summary.mjs +7 -2
- package/build/native-discovery.mjs +18 -6
- package/build/native-ppr.mjs +32 -0
- package/build/npm-packages.mjs +2 -2
- package/build/og.mjs +121 -1
- package/build/parallel-plan.mjs +55 -0
- package/build/persistent-cache.mjs +29 -24
- package/build/production-check.mjs +34 -0
- package/build/react-compiler-report.mjs +31 -8
- package/build/react-compiler.mjs +101 -10
- package/build/server-config.mjs +35 -0
- package/build/source-analysis.mjs +108 -5
- package/build/standalone-copy.mjs +48 -0
- package/build/standalone.mjs +2 -22
- package/build/static-adapter.mjs +7 -3
- package/build/transform-cache.mjs +17 -3
- package/build/transform.mjs +38 -6
- package/build/typescript-dev.mjs +3 -1
- package/cli.mjs +27 -5
- package/compat/app-layout-context.cjs +7 -3
- package/compat/diagnostics.cjs +5 -4
- package/compat/flight-delta.cjs +47 -12
- package/compat/index.d.ts +24 -1
- package/compat/navigation-authorization.cjs +8 -1
- package/compat/runtime-sample.cjs +56 -0
- package/compat/use-cache.cjs +5 -2
- package/docs/ai/compatibility.md +113 -10
- package/docs/ai/deployment.md +144 -0
- package/docs/ai/quickstart.md +5 -0
- package/docs/ai/troubleshooting.md +22 -0
- package/package.json +25 -22
- package/release/fontkit.mjs +31 -0
- package/release/verify.mjs +3 -0
- package/runtime/api.mjs +5 -2
- package/runtime/app-client.mjs +246 -54
- package/runtime/app-partial.mjs +2 -10
- package/runtime/app-prefetch.mjs +2 -2
- package/runtime/app-render.mjs +42 -13
- package/runtime/app-stream-channel.mjs +20 -6
- package/runtime/app-stream-html.mjs +30 -11
- package/runtime/app-styles.mjs +22 -4
- package/runtime/cache-components-rsc.mjs +26 -6
- package/runtime/cache-result.mjs +58 -0
- package/runtime/compile-cache-retention.mjs +53 -0
- package/runtime/compile-cache.mjs +28 -1
- package/runtime/dev-client.mjs +32 -8
- package/runtime/dev-compiler-broker.mjs +20 -23
- package/runtime/dev-gateway.mjs +23 -6
- package/runtime/dev-route-selection.mjs +64 -0
- package/runtime/dev-runtime-pool.mjs +3 -2
- package/runtime/dev-runtime-spare.mjs +1 -1
- package/runtime/dev-session-retention.mjs +154 -0
- package/runtime/development-flight.mjs +59 -0
- package/runtime/development-queue.mjs +33 -0
- package/runtime/development.mjs +37 -16
- package/runtime/env.mjs +9 -0
- package/runtime/flight-delta.mjs +31 -5
- package/runtime/flight-diagnostics.mjs +48 -0
- package/runtime/flight-tap.mjs +10 -5
- package/runtime/html-envelope.mjs +34 -0
- package/runtime/loopback-rpc.mjs +92 -0
- package/runtime/native-ppr.mjs +88 -0
- package/runtime/og/NOTICE.txt +1 -1
- package/runtime/og/index.node.mjs +76 -31216
- package/runtime/og/upstream.json +115 -0
- package/runtime/persistent/client.mjs +5 -3
- package/runtime/persistent/common.mjs +7 -18
- package/runtime/persistent/daemon.mjs +4 -12
- package/runtime/persistent/health.mjs +58 -0
- package/runtime/persistent/native-proxy.mjs +1 -1
- package/runtime/persistent/watchdog.mjs +12 -27
- package/runtime/prewarm.mjs +35 -0
- package/runtime/project-lock.mjs +71 -0
- package/runtime/request-transport.mjs +112 -32
- package/runtime/rsc-worker.mjs +58 -8
- package/runtime/storage-path.mjs +36 -0
- package/runtime/trace.mjs +44 -4
- package/runtime/worker.mjs +37 -17
- package/vendor/fontkit/NOTICE.txt +5 -0
- package/vendor/fontkit/SOURCE-NOTICES.txt +209 -0
- package/vendor/fontkit/index.mjs +3 -0
- package/vendor/fontkit/licenses/@swc__helpers/LICENSE +201 -0
- package/vendor/fontkit/licenses/base64-js/LICENSE +21 -0
- package/vendor/fontkit/licenses/brotli/readme.md +61 -0
- package/vendor/fontkit/licenses/clone/LICENSE +18 -0
- package/vendor/fontkit/licenses/dfa/README.md +83 -0
- package/vendor/fontkit/licenses/fast-deep-equal/LICENSE +21 -0
- package/vendor/fontkit/licenses/fontkit/README.md +266 -0
- package/vendor/fontkit/licenses/restructure/LICENSE +21 -0
- package/vendor/fontkit/licenses/tiny-inflate/LICENSE +21 -0
- package/vendor/fontkit/licenses/tslib/LICENSE.txt +12 -0
- package/vendor/fontkit/licenses/unicode-properties/LICENSE +8 -0
- package/vendor/fontkit/licenses/unicode-trie/LICENSE +8 -0
- package/vendor/fontkit/upstream.json +215 -0
|
@@ -1,36 +1,139 @@
|
|
|
1
1
|
import {parseSync} from 'oxc-parser';
|
|
2
2
|
import {createHash} from 'node:crypto';
|
|
3
|
+
import parserPackage from 'oxc-parser/package' with {type:'json'};
|
|
4
|
+
import {cachedValidatedTransform} from './transform-cache.mjs';
|
|
3
5
|
|
|
4
6
|
// Read-only analyses share one Rust parse. Transformations still use Babel's
|
|
5
7
|
// binding-aware AST: ESTree nodes must never be passed to Babel transforms.
|
|
6
8
|
const cache=new Map();
|
|
7
9
|
let bytes=0,hits=0,misses=0;
|
|
8
|
-
|
|
10
|
+
const summaries=new Map();
|
|
11
|
+
let summaryBytes=0,summaryHits=0,summaryMisses=0,summaryReplays=0;
|
|
12
|
+
const SUMMARY_BYTES=8*1024*1024,SUMMARY_ENTRIES=4096,SUMMARY_ITEM=128*1024;
|
|
13
|
+
export const sourceAnalysisStats=()=>({entries:cache.size,bytes,hits,misses,
|
|
14
|
+
summaries:{entries:summaries.size,bytes:summaryBytes,hits:summaryHits,misses:summaryMisses,replays:summaryReplays}});
|
|
15
|
+
const summaryFields=['directives','imports','computedRequire','hasServerDirective','runtimeExports','exportStars','frameworkBoundary'];
|
|
16
|
+
const text=value=>typeof value==='string'&&value.length<=32768;
|
|
17
|
+
const texts=value=>Array.isArray(value)&&value.length<=4096&&value.every(text);
|
|
18
|
+
function validSummary(value){
|
|
19
|
+
return value&&Object.getPrototypeOf(value)===Object.prototype&&Object.keys(value).length===summaryFields.length
|
|
20
|
+
&&summaryFields.every(name=>Object.hasOwn(value,name))&&texts(value.directives)&&texts(value.runtimeExports)&&texts(value.exportStars)
|
|
21
|
+
&&['computedRequire','hasServerDirective','frameworkBoundary'].every(name=>typeof value[name]==='boolean')
|
|
22
|
+
&&Array.isArray(value.imports)&&value.imports.length<=4096&&value.imports.every(item=>item&&Object.getPrototypeOf(item)===Object.prototype
|
|
23
|
+
&&Object.keys(item).length===2&&text(item.path)&&['import-statement','dynamic-import','require-call'].includes(item.kind));
|
|
24
|
+
}
|
|
25
|
+
function immutableSummary(value){
|
|
26
|
+
return Object.freeze({directives:Object.freeze([...value.directives]),imports:Object.freeze(value.imports.map(item=>Object.freeze({path:item.path,kind:item.kind}))),
|
|
27
|
+
computedRequire:value.computedRequire,hasServerDirective:value.hasServerDirective,runtimeExports:Object.freeze([...value.runtimeExports]),
|
|
28
|
+
exportStars:Object.freeze([...value.exportStars]),frameworkBoundary:value.frameworkBoundary});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Pure, versioned facts only: no AST, source, resolver, registry or generation. */
|
|
32
|
+
export function analyzeSourceSummary(source,file='source.js'){
|
|
33
|
+
const digest=createHash('sha256').update(source).digest('hex'),key=file+'\0'+digest;
|
|
34
|
+
const found=summaries.get(key);
|
|
35
|
+
if(found){summaries.delete(key);summaries.set(key,found);summaryHits++;return found.value;}
|
|
36
|
+
summaryMisses++;
|
|
37
|
+
let produced=false,producedAnalysis;
|
|
38
|
+
const validate=raw=>{
|
|
39
|
+
if(typeof raw!=='string'||Buffer.byteLength(raw)>SUMMARY_ITEM)return false;
|
|
40
|
+
const record=JSON.parse(raw);
|
|
41
|
+
return record&&Object.keys(record).length===5&&record.version===1&&record.parser===parserPackage.version
|
|
42
|
+
&&record.file===file&&record.digest===digest&&validSummary(record.analysis);
|
|
43
|
+
};
|
|
44
|
+
const produce=()=>{
|
|
45
|
+
produced=true;
|
|
46
|
+
const analysis=producedAnalysis=analyzeSource(source,file);
|
|
47
|
+
return JSON.stringify({version:1,parser:parserPackage.version,file,digest,analysis:Object.fromEntries(summaryFields.map(name=>[name,analysis[name]]))});
|
|
48
|
+
};
|
|
49
|
+
// Oversized summaries remain ordinary uncached parses, preserving the old
|
|
50
|
+
// accepted input surface without weakening bounded disk/memory retention.
|
|
51
|
+
let raw,value;
|
|
52
|
+
try{raw=cachedValidatedTransform('source-summary',source,[file,parserPackage.version,'v1'],validate,produce);value=JSON.parse(raw).analysis;}
|
|
53
|
+
catch(error){if(error?.message!=='Invalid typed compiler cache result')throw error;value=Object.fromEntries(summaryFields.map(name=>[name,producedAnalysis[name]]));}
|
|
54
|
+
if(!produced)summaryReplays++;
|
|
55
|
+
const immutable=immutableSummary(value),size=Buffer.byteLength(JSON.stringify(value))*2+Buffer.byteLength(key)*2;
|
|
56
|
+
if(size<=SUMMARY_ITEM){
|
|
57
|
+
while(summaries.size&&(summaries.size>=SUMMARY_ENTRIES||summaryBytes+size>SUMMARY_BYTES)){const[old,entry]=summaries.entries().next().value;summaries.delete(old);summaryBytes-=entry.size;}
|
|
58
|
+
summaries.set(key,{value:immutable,size});summaryBytes+=size;
|
|
59
|
+
}
|
|
60
|
+
return immutable;
|
|
61
|
+
}
|
|
62
|
+
const identifier=(node,name)=>node?.type==='Identifier'&&(name===undefined||node.name===name);
|
|
63
|
+
const property=node=>node?.computed?node.property?.value:node?.property?.name;
|
|
64
|
+
const moduleExports=node=>node?.type==='MemberExpression'&&identifier(node.object,'module')&&property(node)==='exports';
|
|
65
|
+
function bindingNames(node,names){
|
|
66
|
+
if(!node)return;
|
|
67
|
+
if(identifier(node))names.add(node.name);
|
|
68
|
+
else if(node.type==='ObjectPattern')for(const item of node.properties)bindingNames(item.type==='RestElement'?item.argument:item.value,names);
|
|
69
|
+
else if(node.type==='ArrayPattern')for(const item of node.elements)bindingNames(item,names);
|
|
70
|
+
else if(node.type==='RestElement')bindingNames(node.argument,names);
|
|
71
|
+
else if(node.type==='AssignmentPattern')bindingNames(node.left,names);
|
|
72
|
+
}
|
|
73
|
+
function runtimeDeclaration(node,names){
|
|
74
|
+
if(!node||node.declare)return;
|
|
75
|
+
if(node.type==='VariableDeclaration')for(const item of node.declarations)bindingNames(item.id,names);
|
|
76
|
+
else if(['FunctionDeclaration','ClassDeclaration','TSEnumDeclaration','TSModuleDeclaration'].includes(node.type))bindingNames(node.id,names);
|
|
77
|
+
}
|
|
9
78
|
export function analyzeSource(source,file='source.js') {
|
|
10
79
|
const key=file+'\0'+createHash('sha256').update(source).digest('hex');
|
|
11
80
|
if(cache.has(key)){const hit=cache.get(key);cache.delete(key);cache.set(key,hit);hits++;return hit.value;}
|
|
12
81
|
misses++;
|
|
13
|
-
|
|
82
|
+
// Custom loaders also return JSX under their original asset filenames (for
|
|
83
|
+
// example .svg). Match the compiler's JSX fallback for non-TypeScript inputs,
|
|
84
|
+
// preserving TypeScript's extension rules, including angle assertions in .ts.
|
|
85
|
+
const parsed=parseSync(file,source,{sourceType:'unambiguous',allowReturnOutsideFunction:true,...(/\.(?:[cm]?ts|tsx)$/.test(file)?{}:{lang:'jsx'})});
|
|
14
86
|
if(parsed.errors.length)throw new SyntaxError(`${file}: ${parsed.errors.map(error=>error.message).join('; ')}`);
|
|
15
87
|
const program=parsed.program,directives=[];
|
|
16
88
|
for(const node of program.body){if(!node.directive)break;directives.push(node.directive);}
|
|
17
|
-
const imports=[];
|
|
18
|
-
|
|
89
|
+
const imports=[],runtimeExports=new Set(),exportStars=[];
|
|
90
|
+
for(const node of program.body){
|
|
91
|
+
if(node.exportKind==='type')continue;
|
|
92
|
+
if(node.type==='ExportDefaultDeclaration'&&!node.declaration?.declare&&!['TSInterfaceDeclaration','TSTypeAliasDeclaration'].includes(node.declaration?.type))runtimeExports.add('default');
|
|
93
|
+
if(node.type==='ExportNamedDeclaration'){
|
|
94
|
+
runtimeDeclaration(node.declaration,runtimeExports);
|
|
95
|
+
for(const item of node.specifiers)if(item.exportKind!=='type')runtimeExports.add(item.exported.name??item.exported.value);
|
|
96
|
+
}
|
|
97
|
+
if(node.type==='ExportAllDeclaration'){
|
|
98
|
+
if(node.exported)runtimeExports.add(node.exported.name??node.exported.value);
|
|
99
|
+
else exportStars.push(node.source.value);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
let computedRequire=false,hasServerDirective=false;
|
|
19
103
|
const pending=[program];
|
|
20
104
|
while(pending.length){
|
|
21
105
|
const node=pending.pop();
|
|
106
|
+
if(node.directive==='use server')hasServerDirective=true;
|
|
22
107
|
if(['ImportDeclaration','ExportNamedDeclaration','ExportAllDeclaration'].includes(node.type)&&node.source&&node.importKind!=='type'&&node.exportKind!=='type')imports.push({path:node.source.value,kind:'import-statement'});
|
|
23
108
|
if(node.type==='ImportExpression'&&typeof node.source?.value==='string')imports.push({path:node.source.value,kind:'dynamic-import'});
|
|
24
109
|
if(node.type==='CallExpression'&&node.callee?.type==='Identifier'&&node.callee.name==='require'){
|
|
25
110
|
const argument=node.arguments[0];
|
|
26
111
|
if(argument?.type==='Literal'&&typeof argument.value==='string')imports.push({path:argument.value,kind:'require-call'});else computedRequire=true;
|
|
27
112
|
}
|
|
113
|
+
if(node.type==='CallExpression'&&node.callee?.type==='MemberExpression'&&identifier(node.callee.object,'Object')&&property(node.callee)==='defineProperty'&&identifier(node.arguments[0],'exports')&&typeof node.arguments[1]?.value==='string'&&node.arguments[1].value!=='__esModule'){
|
|
114
|
+
runtimeExports.add('default');runtimeExports.add(node.arguments[1].value);
|
|
115
|
+
}
|
|
116
|
+
if(node.type==='AssignmentExpression'&&node.left.type==='MemberExpression'){
|
|
117
|
+
const left=node.left;
|
|
118
|
+
if(moduleExports(left)){
|
|
119
|
+
runtimeExports.add('default');
|
|
120
|
+
if(node.right.type==='ObjectExpression')for(const item of node.right.properties){
|
|
121
|
+
const key=item.computed?item.key?.value:item.key?.name??item.key?.value;
|
|
122
|
+
if(typeof key==='string')runtimeExports.add(key);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if(identifier(left.object,'exports')||moduleExports(left.object)){
|
|
126
|
+
runtimeExports.add('default');if(typeof property(left)==='string')runtimeExports.add(property(left));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
28
129
|
for(const value of Object.values(node)){
|
|
29
130
|
if(Array.isArray(value)){for(let i=value.length-1;i>=0;i--)if(value[i]?.type)pending.push(value[i]);}
|
|
30
131
|
else if(value&&typeof value==='object'&&value.type)pending.push(value);
|
|
31
132
|
}
|
|
32
133
|
}
|
|
33
|
-
|
|
134
|
+
runtimeExports.delete('__esModule');
|
|
135
|
+
const value=Object.freeze({program,directives:Object.freeze(directives),imports:Object.freeze(imports.map(Object.freeze)),computedRequire,hasServerDirective,
|
|
136
|
+
runtimeExports:Object.freeze([...runtimeExports]),exportStars:Object.freeze(exportStars),frameworkBoundary:directives.some(item=>item==='use client'||item==='use server')});
|
|
34
137
|
// ASTs are substantially larger than source. Bound both count and estimated
|
|
35
138
|
// retained size; large individual npm modules are analyzed without retention.
|
|
36
139
|
const size=Buffer.byteLength(source)*16+Buffer.byteLength(file);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {lstat, realpath, mkdir, copyFile, stat} from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {mapConcurrent} from './concurrent.mjs';
|
|
4
|
+
|
|
5
|
+
const within = (root, file) => file === root || file.startsWith(root + path.sep);
|
|
6
|
+
const version = info => [info.dev, info.ino, info.size, info.mtimeMs, info.ctimeMs];
|
|
7
|
+
const unchanged = (left, right) => version(left).every((value, index) => value === version(right)[index]);
|
|
8
|
+
|
|
9
|
+
/** Materialize a finite trace, keeping links deferred until all file writes settle. */
|
|
10
|
+
export async function copyStandaloneTrace(files, {tracingRoot, relocate, maxFiles, maxBytes, concurrency = 8}, io = {lstat, realpath, mkdir, copyFile, stat}) {
|
|
11
|
+
const sources = Array.from(files);
|
|
12
|
+
if (sources.length > maxFiles) throw new Error('Standalone output exceeds 100000 files / 2 GiB. Narrow tracing includes.');
|
|
13
|
+
// Metadata can overlap, but admission and destination ordering remain exactly
|
|
14
|
+
// the trace's order. No bytes are copied before the whole budget is admitted.
|
|
15
|
+
const metadata = await mapConcurrent(sources, async source => {
|
|
16
|
+
const info = await io.lstat(source);
|
|
17
|
+
if (info.isSymbolicLink()) return {source, info, destination: await io.realpath(source)};
|
|
18
|
+
return {source, info};
|
|
19
|
+
}, concurrency);
|
|
20
|
+
let copiedFiles = 0, copiedBytes = 0;
|
|
21
|
+
const pendingLinks = [], groups = new Map();
|
|
22
|
+
for (const {source, info, destination} of metadata) {
|
|
23
|
+
if (info.isSymbolicLink() && !within(tracingRoot, source) && within(destination, tracingRoot)) continue;
|
|
24
|
+
const target = relocate(source);
|
|
25
|
+
if (++copiedFiles > maxFiles || (copiedBytes += info.size) > maxBytes) throw new Error('Standalone output exceeds 100000 files / 2 GiB. Narrow tracing includes.');
|
|
26
|
+
if (info.isSymbolicLink()) { pendingLinks.push([target, relocate(destination)]); continue; }
|
|
27
|
+
if (!info.isFile()) continue;
|
|
28
|
+
const key = process.platform === 'win32' ? target.toLowerCase() : target;
|
|
29
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
30
|
+
groups.get(key).push({source, target, info});
|
|
31
|
+
}
|
|
32
|
+
const directories = new Map();
|
|
33
|
+
await mapConcurrent(groups.values(), async entries => {
|
|
34
|
+
// Alias collisions retain the old serial last-writer order. Unrelated files
|
|
35
|
+
// share a small I/O worker set and one mkdir promise per destination parent.
|
|
36
|
+
for (const {source, target, info} of entries) {
|
|
37
|
+
const before = await io.lstat(source);
|
|
38
|
+
if (!before.isFile() || !unchanged(info, before)) throw new Error('Standalone input changed before copying.');
|
|
39
|
+
const directory = path.dirname(target);
|
|
40
|
+
if (!directories.has(directory)) directories.set(directory, io.mkdir(directory, {recursive: true}));
|
|
41
|
+
await directories.get(directory);
|
|
42
|
+
await io.copyFile(source, target);
|
|
43
|
+
const [after, copied] = await Promise.all([io.lstat(source), io.stat(target)]);
|
|
44
|
+
if (!after.isFile() || !unchanged(before, after) || !copied.isFile() || copied.size !== before.size) throw new Error('Standalone input changed while copying.');
|
|
45
|
+
}
|
|
46
|
+
}, concurrency);
|
|
47
|
+
return {copiedFiles, copiedBytes, pendingLinks};
|
|
48
|
+
}
|
package/build/standalone.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import path from 'node:path';
|
|
|
7
7
|
import { resolveNativeBinary } from '../native/resolve.mjs';
|
|
8
8
|
import { createHash } from 'node:crypto';
|
|
9
9
|
import { createRequire } from 'node:module';
|
|
10
|
+
import {copyStandaloneTrace} from './standalone-copy.mjs';
|
|
10
11
|
|
|
11
12
|
const slash = value => value.split(path.sep).join('/');
|
|
12
13
|
const within = (root, file) => file === root || file.startsWith(root + path.sep);
|
|
@@ -219,28 +220,7 @@ export async function createStandalone({ projectRoot, stage, manifest, config })
|
|
|
219
220
|
} else traceFiles.add(target);
|
|
220
221
|
}
|
|
221
222
|
await mkdir(output, { recursive: true });
|
|
222
|
-
let copiedBytes =
|
|
223
|
-
let copiedFiles = 0;
|
|
224
|
-
const pendingLinks = [];
|
|
225
|
-
for (const source of traceFiles) {
|
|
226
|
-
const info = await lstat(source);
|
|
227
|
-
// NFT also emits ancestor aliases used by absolute workspace links, such as
|
|
228
|
-
// macOS /var -> /private/var. Their canonical children are already traced;
|
|
229
|
-
// these host directory aliases are not dependencies of the artifact.
|
|
230
|
-
// Real files and links to unrelated external workspaces still go through
|
|
231
|
-
// relocated(), which enforces the configured tracing boundary.
|
|
232
|
-
if (info.isSymbolicLink() && !within(tracingRoot, source) && within(await realpath(source), tracingRoot)) continue;
|
|
233
|
-
const target = relocated(source);
|
|
234
|
-
if (++copiedFiles > maxFiles || (copiedBytes += info.size) > maxBytes) throw new Error('Standalone output exceeds 100000 files / 2 GiB. Narrow tracing includes.');
|
|
235
|
-
if (info.isSymbolicLink()) {
|
|
236
|
-
const destination = await realpath(source);
|
|
237
|
-
pendingLinks.push([target, relocated(destination)]);
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
if (!info.isFile()) continue;
|
|
241
|
-
await mkdir(path.dirname(target), { recursive: true });
|
|
242
|
-
await copyFile(source, target);
|
|
243
|
-
}
|
|
223
|
+
let {copiedBytes, copiedFiles, pendingLinks} = await copyStandaloneTrace(traceFiles, {tracingRoot, relocate: relocated, maxFiles, maxBytes});
|
|
244
224
|
const owners = new Set(bridges.values());
|
|
245
225
|
const includedOwners = new Set();
|
|
246
226
|
for (const file of traceFiles) for (let dir = path.dirname(file); dir !== path.dirname(dir); dir = path.dirname(dir)) if (owners.has(dir)) includedOwners.add(dir);
|
package/build/static-adapter.mjs
CHANGED
|
@@ -7,6 +7,8 @@ import { OUTPUT_POINTER, prepareBuildDirectory, readBuildDirectory, validateDist
|
|
|
7
7
|
import { writeBuildOutput } from './output-contract.mjs';
|
|
8
8
|
import { precompressBuild } from './precompress.mjs';
|
|
9
9
|
import { withBuildLock } from './build-lock.mjs';
|
|
10
|
+
import {withCompilationStorage} from './compilation-storage.mjs';
|
|
11
|
+
import {reclaimBuildStages,trackBuildStage} from './build-retention.mjs';
|
|
10
12
|
import { plainStaticPath } from './static-input.mjs';
|
|
11
13
|
|
|
12
14
|
const MAX_FILES = 20000, MAX_BYTES = 512 * 1024 * 1024;
|
|
@@ -15,7 +17,7 @@ const urlFor = file => '/' + file.split('/').map(encodeURIComponent).join('/');
|
|
|
15
17
|
|
|
16
18
|
/** Import an already-built public directory. Never evaluate its config or code. */
|
|
17
19
|
export async function buildStatic(directory, { from = 'dist', spa = false, distDir, dev = false } = {}) {
|
|
18
|
-
return withBuildLock(directory, root => importStatic(root, { from, spa, distDir, dev }));
|
|
20
|
+
return withBuildLock(directory, async root => {await reclaimBuildStages(root);return withCompilationStorage(root,()=>importStatic(root, { from, spa, distDir, dev }));});
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
async function importStatic(directory, { from, spa, distDir, dev }) {
|
|
@@ -41,6 +43,7 @@ async function importStatic(directory, { from, spa, distDir, dev }) {
|
|
|
41
43
|
|
|
42
44
|
const id = randomUUID(), stage = path.join(root, `.prnext-build-${id}`);
|
|
43
45
|
const backup = path.join(root, `.prnext-backup-${id}`), pointer = path.join(root, `.prnext-output-${id}.tmp`);
|
|
46
|
+
const recovery=await trackBuildStage(root,{stage,backup}).catch(()=>null);
|
|
44
47
|
const manifest = { version: 1, buildId: id, config: { staticOnly: true, staticFallback: spa, compress: true, poweredByHeader: true }, routes: [], prerendered: [] };
|
|
45
48
|
const paths = new Set();
|
|
46
49
|
let count = 0, bytes = 0, previous = false, published = false;
|
|
@@ -99,10 +102,11 @@ async function importStatic(directory, { from, spa, distDir, dev }) {
|
|
|
99
102
|
if (previous) await rename(backup, destination);
|
|
100
103
|
throw error;
|
|
101
104
|
}
|
|
102
|
-
|
|
105
|
+
await recovery?.committed().catch(()=>{});
|
|
106
|
+
if (!recovery&&previous) await rm(backup, { recursive: true, force: true });
|
|
103
107
|
return { ...manifest, outputDirectory: destination, files: count, bytes };
|
|
104
108
|
} finally {
|
|
105
109
|
await rm(pointer, { force: true });
|
|
106
|
-
await rm(stage, { recursive: true, force: true });
|
|
110
|
+
if(recovery)await recovery.cleanup();else await rm(stage, { recursive: true, force: true });
|
|
107
111
|
}
|
|
108
112
|
}
|
|
@@ -25,7 +25,14 @@ export async function withPersistentTransforms(root,{enabled=true,dev=false}={},
|
|
|
25
25
|
return result;
|
|
26
26
|
}
|
|
27
27
|
function identity(kind,source,settings){return createHash('sha256').update(persistent.getStore()?.key||'memory').update(JSON.stringify([kind,settings,sourceMapsEnabled()])).update('\0').update(source).digest('hex');}
|
|
28
|
-
function
|
|
28
|
+
function discard(key){const value=entries.get(key);if(value){entries.delete(key);bytes-=value.size;}}
|
|
29
|
+
function validValue(value,validate){if(!validate)return true;try{return validate(value)===true;}catch{return false;}}
|
|
30
|
+
function diskValue(key,validate){
|
|
31
|
+
const scope=persistent.getStore();if(!scope?.values.has(key))return;
|
|
32
|
+
const value=scope.values.get(key);scope.values.delete(key);
|
|
33
|
+
if(!validValue(value,validate)){scope.bytes-=Buffer.byteLength(value);scope.dirty=true;return;}
|
|
34
|
+
scope.values.set(key,value);diskHits++;hits++;return value;
|
|
35
|
+
}
|
|
29
36
|
function remember(key,output){
|
|
30
37
|
const scope=persistent.getStore();if(!scope||typeof output!=='string')return;
|
|
31
38
|
const size=Buffer.byteLength(output);if(size>128*1024)return;
|
|
@@ -38,16 +45,23 @@ function remember(key,output){
|
|
|
38
45
|
// closure or mutable build metadata survives in the cache. Include all options
|
|
39
46
|
// affecting output in settings, including build identity and target mode.
|
|
40
47
|
export function cachedTransform(kind, source, settings, transform) {
|
|
48
|
+
return cachedValidatedTransform(kind,source,settings,undefined,transform);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Typed compiler facts may replay only structurally validated string records. */
|
|
52
|
+
export function cachedValidatedTransform(kind, source, settings, validate, transform) {
|
|
41
53
|
const key = identity(kind,source,settings);
|
|
42
54
|
const found = entries.get(key);
|
|
43
|
-
if (found) {
|
|
55
|
+
if (found && validValue(found.output,validate)) {
|
|
44
56
|
hits++; entries.delete(key); entries.set(key,found);
|
|
45
57
|
if(persistent.getStore()&&!persistent.getStore().values.has(key))remember(key,found.output);
|
|
46
58
|
return found.output;
|
|
47
59
|
}
|
|
48
|
-
|
|
60
|
+
if(found)discard(key);
|
|
61
|
+
const saved=diskValue(key,validate);if(saved!==undefined)return saved;
|
|
49
62
|
misses++;
|
|
50
63
|
const output = transform();
|
|
64
|
+
if(!validValue(output,validate))throw new TypeError('Invalid typed compiler cache result');
|
|
51
65
|
remember(key,output);
|
|
52
66
|
// UTF-16 is an upper bound for the retained string payload. Metadata adds
|
|
53
67
|
// engine overhead; the entry count also bounds that overhead.
|
package/build/transform.mjs
CHANGED
|
@@ -37,17 +37,49 @@ function relativeChunksUncached(source,chunks,prefix) {
|
|
|
37
37
|
return source;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
function rewriteChunkSource(source,map,file,chunks,publicPath){
|
|
41
|
+
const result=relativeServerChunkImports(inlineSourceMap(source,map),chunks,publicPath);
|
|
42
|
+
if(map){
|
|
43
|
+
const mapped=extractSourceMap(result);
|
|
44
|
+
return {source:mapped.code.replace(/\/\/[#@]\s*sourceMappingURL=[^\s]+/g,'')+'\n//# sourceMappingURL='+path.basename(file)+'.map\n',map:JSON.stringify({...mapped.map,file:path.basename(file)})};
|
|
45
|
+
}
|
|
46
|
+
return {source:result};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Rewrite before the first disk write, without modifying compiler/cache buffers. */
|
|
50
|
+
export function rewriteServerChunkOutputFiles(files,chunks,publicPath,importers){
|
|
51
|
+
const outputs=new Map(files.map(file=>[path.resolve(file.path),file])),replacements=new Map();
|
|
52
|
+
const replace=(file,source)=>{
|
|
53
|
+
const contents=Buffer.from(source),metadata=Object.getOwnPropertyDescriptors(file);
|
|
54
|
+
// esbuild's hash describes the original buffer. Do not retain a stale hash.
|
|
55
|
+
// Reading/spreading esbuild's lazy text getter would decode the old buffer
|
|
56
|
+
// again and retain that string even though only the new bytes are needed.
|
|
57
|
+
delete metadata.hash;delete metadata.contents;delete metadata.text;
|
|
58
|
+
const output=Object.create(Object.getPrototypeOf(file),metadata);
|
|
59
|
+
Object.defineProperties(output,{contents:{value:contents,writable:true,enumerable:true,configurable:true},text:{get(){return this.contents.toString();},enumerable:true,configurable:true}});
|
|
60
|
+
replacements.set(file,output);
|
|
61
|
+
};
|
|
62
|
+
for(const file of files){
|
|
63
|
+
if(!file.path.endsWith('.mjs')||importers&&!importers.has(path.resolve(file.path)))continue;
|
|
64
|
+
const source=Buffer.from(file.contents.buffer,file.contents.byteOffset,file.contents.byteLength).toString();
|
|
65
|
+
if(!source.includes(publicPath.replace(/\/+$/,'')+'/'))continue;
|
|
66
|
+
const mapFile=sourceMapsEnabled()&&outputs.get(path.resolve(file.path+'.map'));
|
|
67
|
+
const map=mapFile?JSON.parse(Buffer.from(mapFile.contents).toString()):undefined;
|
|
68
|
+
const result=rewriteChunkSource(source,map,file.path,chunks,publicPath);
|
|
69
|
+
if(result.source!==source)replace(file,result.source);
|
|
70
|
+
if(mapFile)replace(mapFile,result.map);
|
|
71
|
+
}
|
|
72
|
+
return files.map(file=>replacements.get(file)||file);
|
|
73
|
+
}
|
|
74
|
+
|
|
40
75
|
export async function rewriteServerChunks(file,chunks,publicPath){
|
|
41
76
|
const source=await readFile(file,'utf8');
|
|
42
77
|
if(!source.includes(publicPath.replace(/\/+$/,'')+'/'))return;
|
|
43
78
|
let map;
|
|
44
79
|
if(sourceMapsEnabled())try{map=JSON.parse(await readFile(file+'.map','utf8'));}catch(error){if(error.code!=='ENOENT')throw error;}
|
|
45
|
-
const result=
|
|
46
|
-
if(map)
|
|
47
|
-
|
|
48
|
-
await writeFile(file+'.map',JSON.stringify({...mapped.map,file:path.basename(file)}));
|
|
49
|
-
await writeFile(file,mapped.code.replace(/\/\/[#@]\s*sourceMappingURL=[^\s]+/g,'')+'\n//# sourceMappingURL='+path.basename(file)+'.map\n');
|
|
50
|
-
}else await writeFile(file,result);
|
|
80
|
+
const result=rewriteChunkSource(source,map,file,chunks,publicPath);
|
|
81
|
+
if(map)await writeFile(file+'.map',result.map);
|
|
82
|
+
await writeFile(file,result.source);
|
|
51
83
|
}
|
|
52
84
|
|
|
53
85
|
function parserOptions(filename) {
|
package/build/typescript-dev.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import {access} from 'node:fs/promises';
|
|
|
3
3
|
import {resolveTypeScript} from './typescript-toolchain.mjs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import {privateCacheDirectory} from './persistent-cache.mjs';
|
|
6
|
-
export async function startTypeChecker(root,config,notify,{fork:spawnChecker=fork,recheckDelayMs=3000}={}){
|
|
6
|
+
export async function startTypeChecker(root,config,notify,{fork:spawnChecker=fork,recheckDelayMs=3000,onBeforeChild=()=>{},onChild=()=>{}}={}){
|
|
7
7
|
const noop={inactive:true,async close(){}};
|
|
8
8
|
if(config.typescript?.devChecks===false)return noop;
|
|
9
9
|
const filename=path.resolve(root,config.typescript?.tsconfigPath||'tsconfig.json');try{await access(filename);}catch{return noop;}
|
|
@@ -13,7 +13,9 @@ export async function startTypeChecker(root,config,notify,{fork:spawnChecker=for
|
|
|
13
13
|
function launch(){
|
|
14
14
|
if(closed)return;
|
|
15
15
|
const worker=toolchain.native?'./typescript-native-worker.mjs':'./typescript-dev-worker.mjs';
|
|
16
|
+
const ticket=onBeforeChild();
|
|
16
17
|
child=spawnChecker(new URL(worker,import.meta.url),[toolchain.compiler,filename,toolchain.native?'watch':directory?path.join(directory,'project.tsbuildinfo'):''],{cwd:root,execArgv:['--max-old-space-size=512'],stdio:['ignore','ignore','pipe','ipc'],windowsHide:true});
|
|
18
|
+
onChild(child,ticket);
|
|
17
19
|
const current=child;
|
|
18
20
|
let details='',healthyAt=0;child.stderr.on('data',chunk=>{details=(details+chunk).slice(-2048);});
|
|
19
21
|
child.on('message',message=>{
|
package/cli.mjs
CHANGED
|
@@ -22,6 +22,7 @@ Use prnext for portable scripts; prn is a shortcut on compatible shells.
|
|
|
22
22
|
Detect a completed browser build
|
|
23
23
|
prnext build [directory] --adapter static --from .
|
|
24
24
|
Build a plain HTML/CSS/JS site
|
|
25
|
+
prnext build [directory] --timings Show time spent in React build phases
|
|
25
26
|
prnext output [directory] [--json] Read the versioned build output contract
|
|
26
27
|
prnext start [directory] [options] Start native Rust production server
|
|
27
28
|
prnext pstart [directory] [options] Start production persistently in the background
|
|
@@ -38,12 +39,15 @@ Use prnext for portable scripts; prn is a shortcut on compatible shells.
|
|
|
38
39
|
prnext docs --ai [topic] Read the AI guide bundled with this version
|
|
39
40
|
prnext docs --ai --init [directory] Add a guide pointer to the project's AGENTS.md
|
|
40
41
|
prnext routes [directory] Display compiled routes
|
|
41
|
-
prnext check [directory] [--json]
|
|
42
|
+
prnext check [directory] [--production] [--json] Check config, deployment and dependencies
|
|
42
43
|
prnext typegen [directory] [--json] Generate route types without building the app
|
|
43
44
|
prnext typecheck [directory] [--json] Generate types and check TypeScript without building
|
|
44
45
|
prnext inspect [directory] [--json|--otlp] Explain routes, health and sampled timings
|
|
45
46
|
prnext analyze [directory] [--json] [--max-js 150] Browser bundles and budget (kB)
|
|
46
47
|
prnext compiler [directory] [--json] React Compiler results from the last successful build
|
|
48
|
+
prnext cache [directory] [--json] Show compilation cache sizes and disk budget
|
|
49
|
+
prnext cache [directory] --prune [--dry-run] [--max-bytes N] [--json]
|
|
50
|
+
Reclaim regenerable compilation caches
|
|
47
51
|
prnext host [prnext.host.json] Host built apps by hostname; wake on demand
|
|
48
52
|
prnext host [config] --check Validate hosting configuration and builds
|
|
49
53
|
prnext host [config] --status [--json] Show memory, process and idle status
|
|
@@ -109,6 +113,10 @@ function stop(child) {
|
|
|
109
113
|
async function main() {
|
|
110
114
|
if (['help', '--help', '-h'].includes(command)) { console.log(help); return; }
|
|
111
115
|
if (['--version', '-v'].includes(command)) { console.log(packageManifest.version); return; }
|
|
116
|
+
if(command==='cache'){
|
|
117
|
+
const {cacheCommand}=await import('./build/cache-command.mjs');
|
|
118
|
+
await cacheCommand(argv);return;
|
|
119
|
+
}
|
|
112
120
|
if (command === 'create') {
|
|
113
121
|
const { createHelp, parseCreateArgs, createProject } = await import('./build/create.mjs');
|
|
114
122
|
if (argv.length === 1 && ['--help', '-h'].includes(argv[0])) { console.log(createHelp); return; }
|
|
@@ -313,12 +321,12 @@ async function main() {
|
|
|
313
321
|
process.env.PWD = root;
|
|
314
322
|
}
|
|
315
323
|
if (command === 'check') {
|
|
316
|
-
if (args.some(arg =>
|
|
324
|
+
if (args.some(arg => !['--json','--production'].includes(arg))||new Set(args).size!==args.length) throw new Error('check accepts [directory], --production and --json once each');
|
|
317
325
|
const { checkProject } = await import('./build/check.mjs');
|
|
318
|
-
const report = await checkProject(root);
|
|
326
|
+
const report = await checkProject(root,{production:args.includes('--production')});
|
|
319
327
|
if (args.includes('--json')) console.log(JSON.stringify(report, null, 2));
|
|
320
328
|
else {
|
|
321
|
-
console.log(`PRNext preflight ${report.ok ? 'passed' : 'failed'}: ${root}`);
|
|
329
|
+
console.log(`PRNext ${report.mode==='production'?'production ':''}preflight ${report.ok ? 'passed' : 'failed'}: ${root}`);
|
|
322
330
|
console.log(`${report.routes.length} routes. Installed versions: ${Object.entries(report.versions).map(([name,version])=>`${name}@${version||'missing'}`).join(', ')}`);
|
|
323
331
|
for (const item of report.diagnostics) {
|
|
324
332
|
const output = item.severity === 'error' ? console.error : console.log;
|
|
@@ -331,11 +339,16 @@ async function main() {
|
|
|
331
339
|
}
|
|
332
340
|
if (command === 'build') {
|
|
333
341
|
const options = {};
|
|
342
|
+
let showTimings = false;
|
|
334
343
|
for (let i = 0; i < args.length; i++) {
|
|
335
344
|
const flag = args[i];
|
|
336
345
|
if (flag === '--dev') options.dev = true;
|
|
337
346
|
else if (flag === '--watch') options.watch = true;
|
|
338
347
|
else if (flag === '--spa') options.spa = true;
|
|
348
|
+
else if (flag === '--timings') {
|
|
349
|
+
if (showTimings) throw new Error('Duplicate build option: --timings');
|
|
350
|
+
showTimings = true;
|
|
351
|
+
}
|
|
339
352
|
else if (flag === '--adapter' || flag === '--from') {
|
|
340
353
|
const value = args[++i];
|
|
341
354
|
if (!value || value.startsWith('-')) throw new Error(`${flag} requires a value`);
|
|
@@ -349,6 +362,7 @@ async function main() {
|
|
|
349
362
|
process.env.NODE_ENV = dev ? 'development' : 'production';
|
|
350
363
|
const started = performance.now();
|
|
351
364
|
if(options.watch){
|
|
365
|
+
if(showTimings)throw new Error('--timings reports one build. Run prnext build --timings without --watch.');
|
|
352
366
|
if(dev)throw new Error('--watch builds production; use prnext dev for development.');
|
|
353
367
|
const {watchProduction}=await import('./build/production-builder.mjs');
|
|
354
368
|
delete options.watch;await watchProduction(root,options);return;
|
|
@@ -360,11 +374,17 @@ async function main() {
|
|
|
360
374
|
else if (options.adapter === 'auto') options.adapter = 'react';
|
|
361
375
|
}
|
|
362
376
|
if (options.adapter === 'static') {
|
|
377
|
+
if(showTimings)throw new Error('--timings requires the React build adapter.');
|
|
363
378
|
const { buildStatic } = await import('./build/static-adapter.mjs');
|
|
364
379
|
await buildStatic(root, options);
|
|
365
380
|
} else {
|
|
366
381
|
const { build } = await import('./build/index.mjs');
|
|
367
382
|
const result=await build(root, { ...options, dev, envMode });
|
|
383
|
+
if(showTimings&&result.buildTimings){
|
|
384
|
+
console.log('Build timings (milliseconds; nested phases are not additive):');
|
|
385
|
+
for(const[name,phase]of Object.entries(result.buildTimings.phases))console.log(` ${name}: ${phase.milliseconds.toFixed(2)} (${phase.calls} ${phase.calls===1?'call':'calls'})`);
|
|
386
|
+
console.log(` total: ${result.buildTimings.totalMilliseconds.toFixed(2)} (excludes CLI module startup)`);
|
|
387
|
+
}
|
|
368
388
|
if(!dev&&result.compilationCache?.hits){
|
|
369
389
|
const {hits,reusedBytes}=result.compilationCache;
|
|
370
390
|
console.log(`Compilation cache: ${hits} ${hits===1?'graph':'graphs'} reused (${Math.ceil(reusedBytes/1024)} KiB).`);
|
|
@@ -380,7 +400,9 @@ async function main() {
|
|
|
380
400
|
const binary = await resolveNativeBinary();
|
|
381
401
|
if(command==='dev') {const {develop}=await import('./runtime/development.mjs');await develop(root,args,binary);return;}
|
|
382
402
|
const outputDirectory = await readBuildDirectory(root);
|
|
383
|
-
const serverEnvironment =
|
|
403
|
+
const serverEnvironment = command==='start'
|
|
404
|
+
? (await import('./runtime/env.mjs')).productionEnvironment(root)
|
|
405
|
+
: {...process.env,NODE_ENV:process.env.NODE_ENV||'production'};
|
|
384
406
|
if(command==='start'&&serverEnvironment.NODE_ENV!=='development'){
|
|
385
407
|
if(replaceWithNative(binary,['start',root,'--worker',path.join(root,outputDirectory,'runtime/worker.mjs'),
|
|
386
408
|
'--node',process.execPath,...args],serverEnvironment))return;
|
|
@@ -12,17 +12,21 @@ function ActivitySlot({ identity, children, authorizationKey }) {
|
|
|
12
12
|
const router = React.useContext(AppRouterContext);
|
|
13
13
|
const epoch = retention?.epoch ?? 0;
|
|
14
14
|
const authorizationEpoch = retention?.authorizationEpoch ?? 0;
|
|
15
|
+
const flight = retention?.flight;
|
|
15
16
|
const registrations = React.useRef(new Set());
|
|
16
17
|
const serial = React.useRef(0);
|
|
17
18
|
const [saved, setSaved] = React.useState(() => ({ epoch, authorizationEpoch, active: identity, entries: new Map() }));
|
|
18
19
|
let entries = saved.entries;
|
|
19
20
|
const current = entries.get(identity);
|
|
20
|
-
|
|
21
|
+
const incompleteInactive = [...entries].some(([key, entry]) => key !== identity && entry.flight?.complete === false);
|
|
22
|
+
if (saved.epoch !== epoch || saved.authorizationEpoch !== authorizationEpoch || current?.children !== children || current?.router !== router || current?.authorizationKey !== authorizationKey || current?.flight !== flight || incompleteInactive) {
|
|
21
23
|
const sameAuthorization = saved.authorizationEpoch === authorizationEpoch && !(current?.authorizationKey && authorizationKey && current.authorizationKey !== authorizationKey);
|
|
22
|
-
|
|
24
|
+
// Aborted lazy Flight rows cannot remain in an inactive Activity. Restore
|
|
25
|
+
// complete siblings while keeping the current shared layout's React key.
|
|
26
|
+
entries = saved.epoch === epoch && sameAuthorization ? new Map([...entries].filter(([key, entry]) => key === identity || entry.flight?.complete !== false)) : new Map();
|
|
23
27
|
const preserve = current && saved.authorizationEpoch === authorizationEpoch && (sameAuthorization && (saved.epoch === epoch || saved.active === identity)
|
|
24
28
|
|| !retention?.strictAuthorization && saved.active === identity);
|
|
25
|
-
entries.set(identity, { children, router, authorizationKey, token: preserve ? current.token : {}, reactKey: preserve ? current.reactKey : ++serial.current });
|
|
29
|
+
entries.set(identity, { children, router, authorizationKey, flight, token: preserve ? current.token : {}, reactKey: preserve ? current.reactKey : ++serial.current });
|
|
26
30
|
setSaved({ epoch, authorizationEpoch, active: identity, entries });
|
|
27
31
|
}
|
|
28
32
|
React.useLayoutEffect(() => {
|
package/compat/diagnostics.cjs
CHANGED
|
@@ -4,10 +4,11 @@ const directory = process.env.PRNEXT_INSPECT_DIR;
|
|
|
4
4
|
const { threadId } = require('node:worker_threads');
|
|
5
5
|
let queue = [], scheduled = false, writing = false, size;
|
|
6
6
|
const limit = 1024 * 1024;
|
|
7
|
-
if (directory)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
if (directory) {
|
|
8
|
+
// No GC observer or periodic sampler exists when diagnostics are disabled.
|
|
9
|
+
const sampler = require('./runtime-sample.cjs').createRuntimeSampler();
|
|
10
|
+
setInterval(() => record('process', sampler.sample()), 5000).unref();
|
|
11
|
+
}
|
|
11
12
|
function record(type, fields = {}) {
|
|
12
13
|
if (!directory || queue.length >= 256) return;
|
|
13
14
|
const line = JSON.stringify({ time: Date.now(), pid: process.pid, type, ...fields }) + '\n';
|