@redue/cli 0.1.0-alpha.7 → 0.1.0-alpha.8
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/CHANGELOG.md +17 -0
- package/bin/redue.mjs +7 -1
- package/docs/user/direct-typescript.md +2 -2
- package/docs/user/supported-projects.md +1 -1
- package/package.json +3 -2
- package/src/cli.mjs +7 -6
- package/src/daemon.mjs +24 -12
- package/src/decision-profile.mjs +15 -1
- package/src/direct-typescript-probe.mjs +13 -4
- package/src/direct-typescript.mjs +5 -1
- package/src/history-worker.mjs +5 -0
- package/src/observation.mjs +4 -2
- package/src/plan-worker.mjs +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.8 — 2026-10-08
|
|
4
|
+
|
|
5
|
+
- Consume compiler-certificate input asynchronously while preserving the input-size
|
|
6
|
+
limit, parent deadline, complete-input validation and conservative fallbacks.
|
|
7
|
+
- Support the exact reviewed TypeScript 5.5.2 distribution in the explicitly selected
|
|
8
|
+
single-package npm direct-compiler recipe. Enforce implementation membership and
|
|
9
|
+
reject ETW module overrides before compiler API loading.
|
|
10
|
+
- Real independent Codex/macOS sessions reused compiler evidence on the pinned
|
|
11
|
+
vscode-js-debug project. Cross-platform CLI/package acceptance is separate;
|
|
12
|
+
Claude behavior and cross-host handoff remain untested.
|
|
13
|
+
- The measured eight-case workflow avoided four compiler executions but cost
|
|
14
|
+
20.01 seconds versus 16.97 seconds for eight ordinary compiler runs. No net
|
|
15
|
+
workflow or whole-agent speedup is claimed. This verifies compiler inputs only,
|
|
16
|
+
not npm lifecycle behavior or application correctness.
|
|
17
|
+
- Stop observers before upgrading, retain the same configuration/state, then
|
|
18
|
+
restart. Do not delete receipts or automatically convert existing checks.
|
|
19
|
+
|
|
3
20
|
## 0.1.0-alpha.7 — 2026-10-07
|
|
4
21
|
|
|
5
22
|
- Guide explicitly configured direct-compiler reuse through one caller-aware
|
package/bin/redue.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import {mark,traceBase,traceContext,traceEnvironment,traceId} from '../src/decision-profile.mjs';
|
|
4
|
+
traceBase({...traceContext(),request:traceId(),work:'client'});
|
|
5
|
+
mark('client.loaded');
|
|
3
6
|
import {directCompiler,directQualification,directEnvironment,directInterpretation} from '../src/direct-typescript.mjs';
|
|
4
7
|
import os from 'node:os';
|
|
5
8
|
import path from 'node:path';
|
|
@@ -154,6 +157,7 @@ function printLifecycle(output,action,machine){
|
|
|
154
157
|
if(stateOverride&&['stop','remove-state'].includes(command)){
|
|
155
158
|
const runtime=path.join(stateOverride,'project-runtime-v1.json');
|
|
156
159
|
if(!fs.existsSync(runtime)){console.log('No REDUE state exists at this path.');process.exit(0);}
|
|
160
|
+
mark('client.engine_submit');
|
|
157
161
|
const result=spawnSync(process.execPath,[engine,runtime,command==='stop'?'stop':'uninstall'],
|
|
158
162
|
{cwd:os.tmpdir(),stdio:['inherit','pipe','inherit'],encoding:'utf8',env:process.env});
|
|
159
163
|
if(result.error)error(result.error.message);
|
|
@@ -334,9 +338,11 @@ if(command==='run'){
|
|
|
334
338
|
if(outcome.error)error(outcome.error.message);
|
|
335
339
|
process.exit(outcome.status??(outcome.signal?128+os.constants.signals[outcome.signal]:1));
|
|
336
340
|
}
|
|
341
|
+
mark('client.engine_submit');
|
|
337
342
|
const result=spawnSync(process.execPath,[engine,runtimeFile,internal,...(check?[check]:[])],
|
|
338
343
|
{stdio:['inherit','pipe','inherit'],
|
|
339
|
-
encoding:'utf8',env:
|
|
344
|
+
encoding:'utf8',env:traceEnvironment()});
|
|
345
|
+
mark('client.engine_complete',{exit:result.status,signal:result.signal});
|
|
340
346
|
if(result.error)error(result.error.message);
|
|
341
347
|
if(result.stdout){
|
|
342
348
|
if(['status','detail','explain'].includes(command)){
|
|
@@ -5,7 +5,7 @@ check against declared, observed inputs and relevant execution context. It does
|
|
|
5
5
|
not mean npm or npm lifecycle scripts completed.
|
|
6
6
|
|
|
7
7
|
For a supported single-package npm project with an existing standalone
|
|
8
|
-
`tsc --noEmit` script, installed dependencies, Node 22/24 and TypeScript 5.6.3/5.9.3:
|
|
8
|
+
`tsc --noEmit` script, installed dependencies, Node 22/24 and TypeScript 5.5.2/5.6.3/5.9.3:
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
11
|
redue init --recipe typescript-direct --check typecheck --dry-run
|
|
@@ -33,7 +33,7 @@ A generated check contains:
|
|
|
33
33
|
|
|
34
34
|
`script` identifies the reviewed source recipe; `command` is what actually runs:
|
|
35
35
|
the resolved Node executable and local TypeScript implementation, with exact argv
|
|
36
|
-
and no shell. TypeScript 5.6.3
|
|
36
|
+
and no shell. TypeScript 5.5.2 and 5.6.3 use `lib/tsc.js`; 5.9.3 uses `lib/_tsc.js` directly,
|
|
37
37
|
without its outer compile-cache-enabling shim. No compiler is fetched or bundled.
|
|
38
38
|
The entire installed package must match reviewed official distribution bytes.
|
|
39
39
|
|
|
@@ -19,7 +19,7 @@ project script. Automatic npm-launcher applicability was withdrawn in alpha.4;
|
|
|
19
19
|
ordinary npm scripts remain recording-only. See the [maintainer notice](npm-launcher-notice.md).
|
|
20
20
|
The following direct recipe is opt-in and new in the alpha.5 candidate. The opt-in `init --recipe typescript-direct` supports
|
|
21
21
|
a standalone `tsc --noEmit` script in a single-package npm installation using
|
|
22
|
-
reviewed TypeScript 5.6.3/5.9.3 and Node 22/24. It executes the local compiler,
|
|
22
|
+
reviewed TypeScript 5.5.2/5.6.3/5.9.3 and Node 22/24. It executes the local compiler,
|
|
23
23
|
not npm. Full installed compiler identity, config/membership, installed inputs
|
|
24
24
|
and relevant Node environment are checked before reuse. See the
|
|
25
25
|
[exact direct boundary](direct-typescript.md). Unsupported plugins, preloads,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redue/cli",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.8",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"tag": "alpha"
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"test": "node --test --test-concurrency=1 test/*.test.mjs",
|
|
15
15
|
"test:package": "node test/acceptance/package-smoke.mjs",
|
|
16
|
-
"demo": "node test/acceptance/package-smoke.mjs --demo"
|
|
16
|
+
"demo": "node test/acceptance/package-smoke.mjs --demo",
|
|
17
|
+
"pretest": "npm --prefix test/fixtures/typescript-5-5-2 ci --no-audit --no-fund"
|
|
17
18
|
},
|
|
18
19
|
"dependencies": {
|
|
19
20
|
"@parcel/watcher": "2.5.6",
|
package/src/cli.mjs
CHANGED
|
@@ -17,10 +17,11 @@ import {assertOwnedStatePlacement} from './owned-state.mjs';
|
|
|
17
17
|
import {permittedRoots} from './installed-inputs.mjs';
|
|
18
18
|
import {acquireRunLock,inspectRunLock,recoverRunLock} from './run-lock.mjs';
|
|
19
19
|
import {atomicJson,commitReceipt,receiptRevision} from './state-store.mjs';
|
|
20
|
-
import {timing,profiling} from './decision-profile.mjs';
|
|
20
|
+
import {timing,profiling,mark,traceContext} from './decision-profile.mjs';
|
|
21
21
|
import {readCachedStatus,unavailableCached} from './cached-state.mjs';
|
|
22
22
|
import {renderRun} from './presentation.mjs';
|
|
23
23
|
|
|
24
|
+
mark('cli.loaded');
|
|
24
25
|
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
25
26
|
const [configArg,action,name]=process.argv.slice(2);
|
|
26
27
|
if(!configArg||!action)throw Error('internal usage: cli.mjs CONFIG ACTION [check]');
|
|
@@ -93,13 +94,13 @@ function owner(){
|
|
|
93
94
|
}
|
|
94
95
|
function call(message,timeout=5000) {
|
|
95
96
|
return new Promise((resolve,reject)=>{
|
|
96
|
-
let data='',done=false;const client=net.createConnection(socket);
|
|
97
|
-
const timer=setTimeout(()=>{client.destroy();reject(Error('observer synchronization timeout'));},timeout);
|
|
98
|
-
client.on('connect',()=>client.write(JSON.stringify(message)+'\n'));
|
|
97
|
+
let data='',done=false;mark('client.control_connect',{deadlineMs:timeout});const client=net.createConnection(socket);
|
|
98
|
+
const timer=setTimeout(()=>{mark('client.control_timeout');client.destroy();reject(Error('observer synchronization timeout'));},timeout);
|
|
99
|
+
client.on('connect',()=>{mark('client.control_submit');client.write(JSON.stringify({...message,...(profiling?{diagnostic:traceContext()}: {})})+'\n');});
|
|
99
100
|
client.on('data',chunk=>data+=chunk);
|
|
100
|
-
client.on('end',()=>{clearTimeout(timer);if(done)return;done=true;
|
|
101
|
+
client.on('end',()=>{mark('client.control_complete',{bytes:data.length});clearTimeout(timer);if(done)return;done=true;
|
|
101
102
|
try{resolve(JSON.parse(data));}catch(e){reject(e);}});
|
|
102
|
-
client.on('error',e=>{clearTimeout(timer);reject(e);});
|
|
103
|
+
client.on('error',e=>{mark('client.control_error',{code:e.code});clearTimeout(timer);reject(e);});
|
|
103
104
|
});
|
|
104
105
|
}
|
|
105
106
|
async function decisionCall(message,timeout) {
|
package/src/daemon.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import {assertOwnedStatePlacement} from './owned-state.mjs';
|
|
|
17
17
|
import {atomicJson,readReceipts,readReceiptSnapshot,receiptRevision} from './state-store.mjs';
|
|
18
18
|
import {history} from './observation.mjs';
|
|
19
19
|
import {inputKey,planGuard} from './decision-validation.mjs';
|
|
20
|
-
import {timing} from './decision-profile.mjs';
|
|
20
|
+
import {timing,mark,traceBase,traceContext,traceScope,traceEnvironment,traceId} from './decision-profile.mjs';
|
|
21
21
|
|
|
22
22
|
const configFile=path.resolve(process.argv[2]);
|
|
23
23
|
const config=JSON.parse(fs.readFileSync(configFile));
|
|
@@ -31,6 +31,7 @@ const endpoint=controlEndpoint(state),socket=endpoint.address,
|
|
|
31
31
|
fs.mkdirSync(lock); // Existing lock requires operator inspection; never steal it.
|
|
32
32
|
fs.writeFileSync(path.join(lock,'pid'),String(process.pid),{mode:0o600});
|
|
33
33
|
const generation=randomUUID(), startedAt=Date.now();
|
|
34
|
+
traceBase({generation,work:'startup'});
|
|
34
35
|
let index, receipts={}, watcher, healthy=false, reason='initial reconciliation', stopping=false;
|
|
35
36
|
let receiptStateError=null;
|
|
36
37
|
let receiptStateRevision=null;
|
|
@@ -136,10 +137,12 @@ function probe(row,allowReuse=false) {
|
|
|
136
137
|
const forceFull=process.env.VSTATE_TEST_FAULTS==='1'&&process.env.VSTATE_TEST_FULL_PROBES==='1';
|
|
137
138
|
if(cacheable&&allowReuse&&macGuarded&&!forceFull&&cache?.input===key){
|
|
138
139
|
const argv=row.plan.probes[0];
|
|
140
|
+
mark('daemon.probe_launch',{mode:'certificate',deadlineMs:10000});
|
|
139
141
|
const out=spawnSync(argv[0],[...argv.slice(1),macDirect?'--redue-validate-queries':'--redue-context'],{
|
|
140
|
-
cwd:row.plan.cwd,timeout:10000,maxBuffer:1024*1024,
|
|
142
|
+
cwd:row.plan.cwd,timeout:10000,maxBuffer:1024*1024,env:traceEnvironment(),
|
|
141
143
|
...(macDirect?{input:JSON.stringify({schema:cache.schema,context:cache.context,queries:cache.queries})}:
|
|
142
144
|
{stdio:['ignore','pipe','pipe']})});
|
|
145
|
+
mark('daemon.probe_complete',{mode:'certificate',childPid:out.pid,exit:out.status,signal:out.signal,code:out.error?.code});
|
|
143
146
|
metrics.probeContextProcesses=(metrics.probeContextProcesses||0)+1;
|
|
144
147
|
let stillValid=!macDirect;
|
|
145
148
|
if(macDirect)try{stillValid=macProbeBarrier()&&inputKey(row)===key&&planGuard(index.bundle)===guard;}catch{}
|
|
@@ -170,8 +173,10 @@ function probe(row,allowReuse=false) {
|
|
|
170
173
|
const timeout=row.plan.qualification==='pnpm-tsc-v1'||
|
|
171
174
|
(process.platform==='win32'&&['typescript-noemit-v1','npm-typescript-direct-v1','yarn-workspace-tsc-v1']
|
|
172
175
|
.includes(row.plan.qualification))?10000:5000;
|
|
176
|
+
mark('daemon.probe_launch',{mode:'full',reason:fullReason,deadlineMs:timeout});
|
|
173
177
|
const out=spawnSync(argv[0],[...argv.slice(1),...(cacheable?['--redue-checkpoint']:[])],{cwd:row.plan.cwd,
|
|
174
|
-
timeout,maxBuffer:1024*1024,stdio:['ignore','pipe','pipe']});
|
|
178
|
+
timeout,maxBuffer:1024*1024,stdio:['ignore','pipe','pipe'],env:traceEnvironment()});
|
|
179
|
+
mark('daemon.probe_complete',{mode:'full',childPid:out.pid,exit:out.status,signal:out.signal,code:out.error?.code});
|
|
175
180
|
if(out.error||out.signal||out.status!==0) {
|
|
176
181
|
if(!row.probeError){row.revision++;row.lastReason='declared state probe became unavailable';}
|
|
177
182
|
row.probeHash=null;row.probeAt=0;
|
|
@@ -180,7 +185,7 @@ function probe(row,allowReuse=false) {
|
|
|
180
185
|
/VSTATE_REASON:([a-z-]+)/.exec(out.stderr?.toString()||'')?.[1]:null;
|
|
181
186
|
const explanations={
|
|
182
187
|
'direct-typescript-implementation-unreviewed':'installed TypeScript bytes or membership differ from the reviewed compiler',
|
|
183
|
-
'direct-typescript-version-unsupported':'direct compiler recipe supports reviewed TypeScript 5.6.3 and 5.9.3 only',
|
|
188
|
+
'direct-typescript-version-unsupported':'direct compiler recipe supports reviewed TypeScript 5.5.2, 5.6.3 and 5.9.3 only',
|
|
184
189
|
'direct-execution-environment-unsupported':'direct compiler rejects development helpers, Node cache/preloads, and unreviewed runtime overrides',
|
|
185
190
|
'direct-node-version-unsupported':'direct compiler recipe supports Node 22 and 24',
|
|
186
191
|
'npm-launcher-unobserved-inputs':'npm launcher uses unobserved notifier inputs; execution is recording-only',
|
|
@@ -420,7 +425,7 @@ function buildInWorker(forceCold=false,reuse=null) {
|
|
|
420
425
|
return new Promise((resolve,reject)=>{
|
|
421
426
|
const workerStarted=performance.now();
|
|
422
427
|
const file=path.join(path.dirname(fileURLToPath(import.meta.url)),'plan-worker.mjs');
|
|
423
|
-
const worker=new Worker(file,{workerData:{config:configFile,state,forceCold,reuse,
|
|
428
|
+
const worker=new Worker(file,{workerData:{config:configFile,state,forceCold,reuse,diagnostic:traceContext(),
|
|
424
429
|
reconcileFaultFile:process.env.VSTATE_TEST_RECONCILE_FAULT_FILE}});
|
|
425
430
|
planningWorker=worker;let settled=false;
|
|
426
431
|
const deadline=setTimeout(()=>{
|
|
@@ -794,7 +799,7 @@ async function catchUp() {
|
|
|
794
799
|
lastObservation=Date.now();return;
|
|
795
800
|
}
|
|
796
801
|
if(historyDisabled)return;
|
|
797
|
-
if(synchronizing)return synchronizing;
|
|
802
|
+
if(synchronizing){mark('daemon.catch_up_join');return synchronizing;}
|
|
798
803
|
synchronizing=(async()=>{
|
|
799
804
|
if(!verifyRoot())throw Error('checkout identity changed');
|
|
800
805
|
if(!watcher)throw Error('subscription unavailable');
|
|
@@ -825,7 +830,8 @@ async function catchUp() {
|
|
|
825
830
|
function respond(client,value) {
|
|
826
831
|
if(client.destroyed){event('control_reply_unavailable',{classification:'client_destroyed'});return;}
|
|
827
832
|
try{const payload=JSON.stringify(value)+'\n';
|
|
828
|
-
|
|
833
|
+
mark('server.response_constructed',{bytes:payload.length});
|
|
834
|
+
client.end(payload,()=>mark('server.response_delivered'));client.vstateReplied=true;
|
|
829
835
|
if(process.env.VSTATE_DEBUG_CONTROL==='1')event('control_reply_sent',{bytes:payload.length});}
|
|
830
836
|
catch(e){event('control_reply_unavailable',{classification:e.name||'serialization_error'});
|
|
831
837
|
if(!client.destroyed)client.end('{"error":"control response unavailable"}\n');}
|
|
@@ -843,6 +849,7 @@ async function request(message) {
|
|
|
843
849
|
if(message.action==='sync'||message.action==='snapshot'||message.action==='prelaunch') {
|
|
844
850
|
const totalStarted=performance.now();
|
|
845
851
|
const decision=++decisionSequence;
|
|
852
|
+
mark('server.decision_start',{decision,pending:pending.size,planDirty,planning,recovering,healthy,historyDisabled,decisionRequests,historyDeadlineMs,reconciliationDeadlineMs});
|
|
846
853
|
timing('daemon.decision_reason',totalStarted,{decision,action:message.action,
|
|
847
854
|
plan:process.platform==='win32'?'validate_dependencies_before_reuse':'existing_plan',
|
|
848
855
|
probe:'reuse_only_with_identical_content_and_context',installed:'current_content_required',
|
|
@@ -970,9 +977,11 @@ const server=net.createServer(client=>{
|
|
|
970
977
|
try{const message=JSON.parse(text);
|
|
971
978
|
const decision=message.action==='sync'||message.action==='snapshot';
|
|
972
979
|
if(decision)decisionRequests++;
|
|
973
|
-
|
|
974
|
-
.
|
|
975
|
-
.
|
|
980
|
+
traceScope({request:message.diagnostic?.request||traceId(),work:'request'},()=>{
|
|
981
|
+
mark('server.request_received',{activeDecisions:decisionRequests});
|
|
982
|
+
Promise.resolve(request(message)).then(v=>respond(client,v))
|
|
983
|
+
.catch(e=>respond(client,{error:e.message}))
|
|
984
|
+
.finally(()=>{mark('server.request_complete');if(decision)decisionRequests--;});});}
|
|
976
985
|
catch(e){respond(client,{error:e.message});}
|
|
977
986
|
});
|
|
978
987
|
});
|
|
@@ -1032,7 +1041,8 @@ const heartbeat=setInterval(()=>{
|
|
|
1032
1041
|
}
|
|
1033
1042
|
publish();
|
|
1034
1043
|
},1000);
|
|
1035
|
-
const probeTimer=setInterval(async()=>{
|
|
1044
|
+
const probeTimer=setInterval(()=>traceScope({request:traceId(),work:'periodic'},async()=>{
|
|
1045
|
+
mark('server.periodic_start',{pending:pending.size,planDirty,planning,decisionRequests});
|
|
1036
1046
|
// Windows cached reads are conservatively UNVERIFIED until a full decision
|
|
1037
1047
|
// reconciliation. Periodic synchronous probes cannot strengthen that cache,
|
|
1038
1048
|
// but can block control requests behind several seconds of child work.
|
|
@@ -1045,7 +1055,9 @@ const probeTimer=setInterval(async()=>{
|
|
|
1045
1055
|
}
|
|
1046
1056
|
publish();}
|
|
1047
1057
|
catch(e){observationGap('periodic_query',e);}
|
|
1048
|
-
}
|
|
1058
|
+
}
|
|
1059
|
+
mark('server.periodic_complete');
|
|
1060
|
+
}),5000);
|
|
1049
1061
|
process.on('uncaughtExceptionMonitor',error=>{
|
|
1050
1062
|
try{event('observer_uncaught_exception',{pid:process.pid,code:error.code||null,
|
|
1051
1063
|
message:error.message,stack:error.stack});}catch{}
|
package/src/decision-profile.mjs
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
// Opt-in, bounded local diagnostics. Never include paths, command arguments,
|
|
2
2
|
// environment values, source, or probe output in a timing record.
|
|
3
3
|
import fs from 'node:fs';
|
|
4
|
+
import {AsyncLocalStorage} from 'node:async_hooks';
|
|
5
|
+
import {randomUUID} from 'node:crypto';
|
|
4
6
|
|
|
5
7
|
const file=process.env.REDUE_DECISION_PROFILE_FILE;
|
|
6
8
|
export const profiling=process.env.REDUE_DECISION_PROFILE==='1'&&!!file;
|
|
7
9
|
|
|
10
|
+
const traces=new AsyncLocalStorage();
|
|
11
|
+
const safe=value=>typeof value==='string'&&/^[a-zA-Z0-9_-]{1,64}$/.test(value)?value:undefined;
|
|
12
|
+
const clean=value=>Object.fromEntries(['request','generation','work'].map(key=>[key,safe(value?.[key])]).filter(([,v])=>v));
|
|
13
|
+
let base={};
|
|
14
|
+
try{base=clean(JSON.parse(process.env.REDUE_PROFILE_CONTEXT||'{}'));}catch{}
|
|
15
|
+
export const traceContext=()=>profiling?{...base,...traces.getStore()}:{};
|
|
16
|
+
export function traceBase(value){if(profiling)base=clean(value);}
|
|
17
|
+
export const traceId=()=>randomUUID();
|
|
18
|
+
export const traceScope=(value,fn)=>profiling?traces.run({...traceContext(),...clean(value)},fn):fn();
|
|
19
|
+
export const traceEnvironment=()=>profiling?{...process.env,REDUE_PROFILE_CONTEXT:JSON.stringify(traceContext())}:process.env;
|
|
20
|
+
export function mark(phase,fields={}){timing(phase,performance.now(),fields);}
|
|
21
|
+
|
|
8
22
|
export function timing(phase,started,counts={}) {
|
|
9
23
|
if(!profiling)return;
|
|
10
24
|
const memory=process.memoryUsage();
|
|
11
25
|
try{fs.appendFileSync(file,'REDUE_TIMING '+JSON.stringify({
|
|
12
|
-
at:Date.now(),pid:process.pid,phase,
|
|
26
|
+
at:Date.now(),monoNs:process.hrtime.bigint().toString(),pid:process.pid,phase,...traceContext(),
|
|
13
27
|
ms:Math.round((performance.now()-started)*10)/10,
|
|
14
28
|
rssMiB:Math.round(memory.rss/1048576*10)/10,
|
|
15
29
|
...counts
|
|
@@ -4,10 +4,9 @@ import {directProject,directInterpretation} from './direct-typescript.mjs';
|
|
|
4
4
|
import {typeScriptInputFacts} from './typescript-input-facts.mjs';
|
|
5
5
|
import {contextOnly,probeResult} from './probe-result.mjs';
|
|
6
6
|
import {realObservedPath} from './path-identity.mjs';
|
|
7
|
-
import fs from 'node:fs';
|
|
8
7
|
import path from 'node:path';
|
|
9
8
|
import {queriesMatch} from './typescript-list.mjs';
|
|
10
|
-
import {timing} from './decision-profile.mjs';
|
|
9
|
+
import {timing,mark} from './decision-profile.mjs';
|
|
11
10
|
const hash=value=>createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
12
11
|
try{
|
|
13
12
|
const [rootArg,script]=process.argv.slice(2),root=realObservedPath(rootArg);
|
|
@@ -22,8 +21,16 @@ try{
|
|
|
22
21
|
if(process.argv.includes('--redue-validate-queries')){
|
|
23
22
|
// Internal, process-local discovery certificate; never a verification receipt.
|
|
24
23
|
// Verify the current implementation above before queryAnswer can load its API.
|
|
25
|
-
const started=performance.now()
|
|
26
|
-
|
|
24
|
+
const started=performance.now();mark('certificate.input_start');
|
|
25
|
+
// A synchronous fd-0 read can stall with spawnSync's piped input on macOS
|
|
26
|
+
// Node 22.13.0. Drain the stream without blocking its input delivery. The
|
|
27
|
+
// parent's existing deadline and the same size limit remain authoritative.
|
|
28
|
+
const chunks=[];let bytes=0;
|
|
29
|
+
for await(const chunk of process.stdin){
|
|
30
|
+
bytes+=chunk.length;if(bytes>8*1024*1024)throw Error('certificate too large');
|
|
31
|
+
chunks.push(chunk);
|
|
32
|
+
}
|
|
33
|
+
const raw=Buffer.concat(chunks).toString('utf8');mark('certificate.input_complete',{bytes});
|
|
27
34
|
const certificate=JSON.parse(raw),kinds=new Set(['readDirectory','readFile',
|
|
28
35
|
'fileExists','directoryExists','realpath','directories','entries']);
|
|
29
36
|
if(certificate.schema!==1||certificate.context!==contextHash||
|
|
@@ -34,7 +41,9 @@ try{
|
|
|
34
41
|
q[3]?.argsVersion!==1||!Array.isArray(q[3]?.undefinedArguments)||
|
|
35
42
|
q[3].undefinedArguments.some(i=>!Number.isInteger(i)||i<0||i>=q[3].args.length))))
|
|
36
43
|
throw Error('certificate unavailable');
|
|
44
|
+
mark('certificate.queries_start',{queries:certificate.queries.length});
|
|
37
45
|
const matched=queriesMatch(certificate.queries);
|
|
46
|
+
mark('certificate.queries_complete',{matched});
|
|
38
47
|
timing('direct_probe.query_validation',started,{queries:certificate.queries.length,matched:matched?1:0});
|
|
39
48
|
if(!matched)throw Error('compiler queries changed');
|
|
40
49
|
process.stdout.write(contextHash);
|
|
@@ -7,17 +7,21 @@ import {realObservedPath,withinPath} from './path-identity.mjs';
|
|
|
7
7
|
export const directInterpretation='npm-direct-typescript@1';
|
|
8
8
|
export const directQualification='npm-typescript-direct-v1';
|
|
9
9
|
const reviewed={
|
|
10
|
+
'5.5.2':{entry:'lib/tsc.js',digest:'953b4844b5f6edc745be844b6c367c227b59029b73c4182f508f90cc47e21816'},
|
|
10
11
|
'5.6.3':{entry:'lib/tsc.js',digest:'e674b5dc4dac50f6e91be30094aeea983a1f340231717e62b0b6199ae3049eb2'},
|
|
11
12
|
'5.9.3':{entry:'lib/_tsc.js',digest:'d1b32e75475710fdc85102b1365254d84ceb192d3639d962c53fe1355ce274ca'},
|
|
12
13
|
};
|
|
13
14
|
const fail=code=>{throw Object.assign(Error(code),{code});};
|
|
14
15
|
const hash=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
15
16
|
export const directEnvironment={variables:['NODE_ENV','NODE_OPTIONS','NODE_PATH','NODE_COMPILE_CACHE','VSCODE_INSPECTOR_OPTIONS'],
|
|
16
|
-
prefixes:['NODE_','DYLD_','LD_','TSGO_','TSC_'],executableIdentity:true};
|
|
17
|
+
prefixes:['NODE_','DYLD_','LD_','TSGO_','TSC_','TS_ETW_'],executableIdentity:true};
|
|
17
18
|
export function directEnvironmentIssue(env=process.env){
|
|
18
19
|
if(![22,24].includes(Number(process.versions.node.split('.')[0])))return 'direct-node-version-unsupported';
|
|
19
20
|
if(env.VSCODE_INSPECTOR_OPTIONS)return 'direct-execution-environment-unsupported';
|
|
20
21
|
for(const [key,value] of Object.entries(env)){
|
|
22
|
+
// The 5.5.2 API can require this unreviewed module during probe loading.
|
|
23
|
+
// Reject even empty/case aliases; do not mutate the caller's environment.
|
|
24
|
+
if(/^TS_ETW_MODULE_PATH$/i.test(key))return 'direct-execution-environment-unsupported';
|
|
21
25
|
if(/^NODE_/i.test(key)){
|
|
22
26
|
// Node 22.13.0 predates env-proxy initialization. This injected flag is
|
|
23
27
|
// inert there, but remains strictly fingerprinted as NODE_ context.
|
package/src/history-worker.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Native history is an optional acceleration. Keep its operations off the
|
|
2
2
|
// control thread so a wedged native call cannot block status or shutdown.
|
|
3
3
|
import fs from 'node:fs';
|
|
4
|
+
import {mark} from './decision-profile.mjs';
|
|
5
|
+
mark('history.worker_loaded');
|
|
4
6
|
import watcher from '@parcel/watcher';
|
|
5
7
|
|
|
6
8
|
try {
|
|
@@ -10,8 +12,11 @@ try {
|
|
|
10
12
|
catch(e){if(e.code!=='ENOENT')throw e;}
|
|
11
13
|
if(fault==='hang')Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0);
|
|
12
14
|
if(fault==='fail')throw Error('injected history failure');
|
|
15
|
+
mark('history.snapshot_start');
|
|
13
16
|
await watcher.writeSnapshot(root,next,{backend});
|
|
17
|
+
mark('history.snapshot_complete');
|
|
14
18
|
const changes=previous?await watcher.getEventsSince(root,previous,{backend}):[];
|
|
19
|
+
mark('history.events_complete',{events:changes.length});
|
|
15
20
|
process.send({ok:true,changes});
|
|
16
21
|
} catch(error) {
|
|
17
22
|
process.send({ok:false,error:error.message,code:error.code||error.name});
|
package/src/observation.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import {mark,traceEnvironment} from './decision-profile.mjs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import {randomUUID} from 'node:crypto';
|
|
4
5
|
import {fork} from 'node:child_process';
|
|
@@ -11,11 +12,12 @@ const workerFile=path.join(path.dirname(fileURLToPath(import.meta.url)),'history
|
|
|
11
12
|
export function history(root,cursor,backend,deadlineMs,faultFile) {
|
|
12
13
|
const next=cursor+'.'+randomUUID()+'.next';
|
|
13
14
|
return new Promise((resolve,reject)=>{
|
|
15
|
+
mark('history.submit',{deadlineMs});
|
|
14
16
|
const worker=fork(workerFile,[root,fs.existsSync(cursor)?cursor:'',next,backend,
|
|
15
|
-
faultFile||''],{stdio:['ignore','ignore','ignore','ipc']});
|
|
17
|
+
faultFile||''],{stdio:['ignore','ignore','ignore','ipc'],env:traceEnvironment()});
|
|
16
18
|
let settled=false,abandoned=false;
|
|
17
19
|
const finish=(error,value)=>{
|
|
18
|
-
if(settled)return;settled=true;clearTimeout(timer);
|
|
20
|
+
if(settled)return;settled=true;mark('history.complete',{childPid:worker.pid,outcome:error?.code||error?.name||'ok',events:value?.length});clearTimeout(timer);
|
|
19
21
|
worker.kill('SIGKILL');
|
|
20
22
|
if(error){abandoned=true;try{fs.unlinkSync(next);}catch{}
|
|
21
23
|
reject(error);return;}
|
package/src/plan-worker.mjs
CHANGED
|
@@ -7,8 +7,9 @@ import {InputIndex} from './index.mjs';
|
|
|
7
7
|
import {directProject,directQualification} from './direct-typescript.mjs';
|
|
8
8
|
import {queriesMatch} from './typescript-list.mjs';
|
|
9
9
|
import {planGuard,inputKey} from './decision-validation.mjs';
|
|
10
|
-
import {timing} from './decision-profile.mjs';
|
|
10
|
+
import {timing,traceBase} from './decision-profile.mjs';
|
|
11
11
|
|
|
12
|
+
traceBase(workerData.diagnostic);
|
|
12
13
|
try {
|
|
13
14
|
const totalStarted=performance.now();
|
|
14
15
|
const configStarted=performance.now();
|