@redue/cli 0.1.0-alpha.3 → 0.1.0-alpha.4
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 +9 -0
- package/README.md +8 -3
- package/bin/redue.mjs +1 -0
- package/docs/user/getting-started.md +9 -1
- package/docs/user/npm-launcher-notice.md +42 -0
- package/docs/user/supported-projects.md +7 -8
- package/package.json +1 -1
- package/src/node-onboarding.mjs +2 -2
- package/src/typescript-contract-probe.mjs +4 -187
- package/src/typescript-qualification.mjs +3 -43
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.4 — 2026-10-07
|
|
4
|
+
|
|
5
|
+
- Withdraw automatic npm-launcher applicability because update notifications consume
|
|
6
|
+
unobserved remote/cache/time state. Npm scripts still run and record outcomes.
|
|
7
|
+
- Preserve historical PASS/FAIL and require stop/start when upgrading an observer.
|
|
8
|
+
Do not delete receipts. See the [maintainer notice](docs/user/npm-launcher-notice.md).
|
|
9
|
+
- Supported Yarn/pnpm and explicit reviewed contracts are unchanged. No agent
|
|
10
|
+
activation or npm direct-compiler recipe is included in this correction.
|
|
11
|
+
|
|
3
12
|
## 0.1.0-alpha.3 — 2026-10-06
|
|
4
13
|
|
|
5
14
|
Install: `npm install --global @redue/cli@alpha`.
|
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# REDUE
|
|
2
2
|
|
|
3
|
-
Maintainer notice: alpha.
|
|
4
|
-
|
|
3
|
+
Maintainer notice: alpha.4 withdraws automatic npm-launcher applicability.
|
|
4
|
+
Npm commands still execute and record historical outcomes, but remain UNVERIFIED.
|
|
5
|
+
[Why this changed and how to upgrade without deleting receipts](docs/user/npm-launcher-notice.md).
|
|
6
|
+
Alpha.2 users should also review the [receipt correction](docs/user/receipt-selection-notice.md).
|
|
5
7
|
|
|
6
8
|
**Know what still holds. Redo what's due.**
|
|
7
9
|
|
|
@@ -12,6 +14,8 @@ REDUE keeps persistent verification state for your codebase so developers and
|
|
|
12
14
|
coding agents can see which previous checks still apply to the code they have now.
|
|
13
15
|
It records evidence, not proofs of correctness.
|
|
14
16
|
|
|
17
|
+
For a qualified supported Yarn/pnpm check (ordinary npm scripts are recording-only):
|
|
18
|
+
|
|
15
19
|
```text
|
|
16
20
|
$ redue status --sync
|
|
17
21
|
✓ typecheck CURRENT / PASS
|
|
@@ -85,7 +89,8 @@ has been verified.
|
|
|
85
89
|
- macOS, Linux, and Windows on supported local filesystems; Node 22+.
|
|
86
90
|
- Linux also requires Python 3 for observation. No WSL or Git Bash needed on Windows.
|
|
87
91
|
- npm, pinned Yarn `node-modules`, and pinned pnpm 12 isolated/hoisted layouts.
|
|
88
|
-
- Automatic qualification for supported TypeScript invocations;
|
|
92
|
+
- Automatic qualification for the supported Yarn/pnpm TypeScript invocations; npm
|
|
93
|
+
launcher checks are recording-only in alpha.4. Workspace
|
|
89
94
|
boundaries follow the files actually consumed, including built declarations.
|
|
90
95
|
- Existing test/lint/build and arbitrary commands can record results while
|
|
91
96
|
their applicability remains unqualified.
|
package/bin/redue.mjs
CHANGED
|
@@ -266,6 +266,7 @@ const checks=publicConfig.checks.map(check=>{
|
|
|
266
266
|
coverageReasons:check.coverageReasons||Object.fromEntries(
|
|
267
267
|
categories.map(category=>[category,reason])),
|
|
268
268
|
qualification:check.qualification||null,script:check.script||null,
|
|
269
|
+
...(npmAutomatic?{qualificationInterpretation:'npm-launcher-withheld@1'}:{}),
|
|
269
270
|
workspace:check.workspace||null,kind:check.kind||null};
|
|
270
271
|
});
|
|
271
272
|
const runtime={schema:1,provider:'declared-project@1',root,state,checks};
|
|
@@ -57,6 +57,12 @@ prefix. Uninstalling the package does not remove local verification evidence;
|
|
|
57
57
|
`redue remove-state` is a separate, explicit action. Alpha.1 remains available as
|
|
58
58
|
historical software and is superseded for package identity, not a runtime defect.
|
|
59
59
|
|
|
60
|
+
## Upgrading to alpha.4
|
|
61
|
+
|
|
62
|
+
Stop every observer using the installation before upgrading, then restart each
|
|
63
|
+
with the same config/state selection. See [the npm launcher notice](npm-launcher-notice.md)
|
|
64
|
+
for exact commands. Do not remove state or delete receipts.
|
|
65
|
+
|
|
60
66
|
## First project
|
|
61
67
|
|
|
62
68
|
From the project root:
|
|
@@ -74,7 +80,9 @@ Use a check actually printed by `init`. For a workspace:
|
|
|
74
80
|
`redue init --workspace @example/package --check typecheck`.
|
|
75
81
|
Initialization previews detected commands and qualification confidence before writing
|
|
76
82
|
`redue.config.json`. An existing config is never overwritten. No checks are run and
|
|
77
|
-
no dependencies installed. A missing/unsupported boundary is explained.
|
|
83
|
+
no dependencies installed. A missing/unsupported boundary is explained. Npm-launcher typechecks are
|
|
84
|
+
recording-only in alpha.4; a passed execution is historical PASS with UNVERIFIED
|
|
85
|
+
applicability, not reusable CURRENT.
|
|
78
86
|
|
|
79
87
|
“Ready to qualify” means REDUE has a supported recipe whose prerequisites will be
|
|
80
88
|
validated. It is not a receipt. “Recording-only” can record PASS/FAIL without claiming
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# npm launcher applicability correction — alpha.4
|
|
2
|
+
|
|
3
|
+
Alpha.4 withdraws automatic applicability for npm-backed TypeScript checks using
|
|
4
|
+
`typescript-noemit-v1`. Alpha.3 was demonstrated to admit an npm invocation whose
|
|
5
|
+
update notification consumes unobserved remote responses, cache and time. Those
|
|
6
|
+
inputs can change the outer npm result without changing the tracked project or
|
|
7
|
+
caller identity. The controlled reproduction does not establish a public registry
|
|
8
|
+
malfunction, affected-user count, or compiler completion in the failing npm case.
|
|
9
|
+
|
|
10
|
+
Npm commands still execute exactly as configured through `redue run CHECK`.
|
|
11
|
+
They still record PASS/FAIL. Previous PASS remains historical PASS; applicability
|
|
12
|
+
is now UNVERIFIED, including after synchronization or a successful new npm run.
|
|
13
|
+
This is a correction to the claimed boundary, not evidence that the code failed.
|
|
14
|
+
Supported Yarn/pnpm and independently reviewed explicit contracts are unchanged.
|
|
15
|
+
|
|
16
|
+
Until upgraded, do not rely solely on automatic npm CURRENT to skip the npm
|
|
17
|
+
invocation. A different verification recipe requires explicit selection and fresh
|
|
18
|
+
evidence; it cannot reinterpret an old npm receipt. Alpha.4 does not ship the
|
|
19
|
+
experimental npm direct-compiler recipe or agent activation.
|
|
20
|
+
|
|
21
|
+
## Upgrade without losing history
|
|
22
|
+
|
|
23
|
+
First stop **every observer using this installation**, using each original config
|
|
24
|
+
and any explicit state directory. Then upgrade that same installation and restart
|
|
25
|
+
each observer with the same selections:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
redue --config /path/to/project/redue.config.json stop
|
|
29
|
+
npm install --global @redue/cli@0.1.0-alpha.4
|
|
30
|
+
redue --config /path/to/project/redue.config.json start
|
|
31
|
+
redue --config /path/to/project/redue.config.json status --sync
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Repeat the stop/start commands for each project. If you originally used
|
|
35
|
+
`--state-dir`, supply the identical directory on every REDUE command. If installed
|
|
36
|
+
with a custom npm `--prefix`, supply the same prefix during installation. On Windows
|
|
37
|
+
use `npm.cmd`/`redue.cmd` if required by existing PowerShell policy; do not change it.
|
|
38
|
+
|
|
39
|
+
**Do not delete receipts or use `remove-state` as an upgrade step.** An upgraded
|
|
40
|
+
client refuses an obsolete running observer interpretation and asks for stop/start.
|
|
41
|
+
Restart reconciles existing state and preserves immutable historical runs. This
|
|
42
|
+
correction does not silently change your project config or configured command.
|
|
@@ -15,14 +15,13 @@ At the repository root, `init` looks for existing typecheck, test, lint, and
|
|
|
15
15
|
build scripts. It prefers familiar script names, then unique recognizable
|
|
16
16
|
`tsc`, Vitest/Jest, ESLint, or Vite commands under other names. Multiple
|
|
17
17
|
matches are reported as ambiguous rather than guessed. It never creates a new
|
|
18
|
-
project script.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
state. Root and package-level npm/Yarn workspace scripts are discovered from
|
|
18
|
+
project script. Canonical `tsc --noEmit` npm scripts are discovered and recorded, but their
|
|
19
|
+
automatic applicability is withdrawn in alpha.4: npm's update notification uses
|
|
20
|
+
remote/cache/time inputs outside the observed contract. Even a successful run
|
|
21
|
+
remains UNVERIFIED/PASS. Historical receipts are retained. See the
|
|
22
|
+
[maintainer notice](npm-launcher-notice.md). A separately selected verification
|
|
23
|
+
obligation would require an explicit configuration and fresh evidence; alpha.4
|
|
24
|
+
does not provide the unreleased npm direct-compiler recipe. Root and package-level npm/Yarn workspace scripts are discovered from
|
|
26
25
|
`package.json` workspace declarations. Pinned Yarn 4 `node-modules`/classic
|
|
27
26
|
workspaces with a standalone `tsc -p .` script, local TypeScript, a conventional
|
|
28
27
|
`src` include, and Node module resolution can qualify automatically. For one
|
package/package.json
CHANGED
package/src/node-onboarding.mjs
CHANGED
|
@@ -116,7 +116,7 @@ function classify(kind,scriptName,script,pkg,managerInfo,root) {
|
|
|
116
116
|
managerInfo.name==='npm'&&
|
|
117
117
|
exists(path.join(root,'tsconfig.json'))&&
|
|
118
118
|
exists(path.join(root,'node_modules/typescript/package.json')))
|
|
119
|
-
return {level:'
|
|
119
|
+
return {level:'recording',reason:'npm launcher uses unobserved remote/cache/time inputs; execution can be recorded, but cannot be reused',
|
|
120
120
|
qualification:'typescript-noemit-v1'};
|
|
121
121
|
if(kind==='typecheck')return {level:'recording',reason:
|
|
122
122
|
'TypeScript invocation, installation, or configuration is outside the automatic noEmit contract'};
|
|
@@ -183,7 +183,7 @@ export function discoverNodeProject(root) {
|
|
|
183
183
|
if(!script)continue;
|
|
184
184
|
let classification=observationIssue?{level:'unsupported',reason:observationIssue}:
|
|
185
185
|
classify(kind,script,scripts[script],pkg,pm,root);
|
|
186
|
-
if(workspaces&&classification.
|
|
186
|
+
if(workspaces&&classification.qualification==='typescript-noemit-v1')classification={level:'recording',
|
|
187
187
|
reason:'workspace task and installed-input closure need explicit qualification'};
|
|
188
188
|
const check={name:kind,script,kind,inputs:sourceInputs(root,kind,pm.lockfiles||[])};
|
|
189
189
|
if(pm.name==='pnpm'&&kind==='typecheck'&&!workspaces&&!pm.issue&&
|
|
@@ -1,188 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import fs from 'node:fs';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import os from 'node:os';
|
|
9
|
-
import {createHash} from 'node:crypto';
|
|
10
|
-
import {createRequire} from 'node:module';
|
|
11
|
-
import {spawnSync} from 'node:child_process';
|
|
12
|
-
import {withinPath as within,realObservedPath} from './path-identity.mjs';
|
|
13
|
-
import {timing} from './decision-profile.mjs';
|
|
14
|
-
|
|
15
|
-
const [rootArg,scriptName,npmArg]=process.argv.slice(2);
|
|
16
|
-
const hash=value=>createHash('sha256').update(value).digest('hex');
|
|
17
|
-
const fail=code=>{throw Object.assign(Error(code),{code});};
|
|
18
|
-
const digestFile=file=>hash(fs.readFileSync(file));
|
|
19
|
-
const require=createRequire(import.meta.url);
|
|
20
|
-
function treeHash(dir){
|
|
21
|
-
const digest=createHash('sha256');let count=0;
|
|
22
|
-
const visit=(folder,rel='')=>{
|
|
23
|
-
for(const entry of fs.readdirSync(folder,{withFileTypes:true}).sort((a,b)=>
|
|
24
|
-
a.name.localeCompare(b.name))){
|
|
25
|
-
const name=rel?rel+'/'+entry.name:entry.name,full=path.join(folder,entry.name);
|
|
26
|
-
if(entry.isDirectory())visit(full,name);
|
|
27
|
-
else if(entry.isFile()){digest.update(name+'\0'+digestFile(full)+'\0');count++;}
|
|
28
|
-
else fail('npm-installation-unobservable');
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
visit(dir);return [digest.digest('hex'),count];
|
|
32
|
-
}
|
|
33
|
-
function npmRcKeys(file){
|
|
34
|
-
if(!fs.existsSync(file))return null;
|
|
35
|
-
const keys=[];
|
|
36
|
-
for(const line of fs.readFileSync(file,'utf8').split(/\r?\n/)){
|
|
37
|
-
const trimmed=line.trim();if(!trimmed||/^[#;]/.test(trimmed))continue;
|
|
38
|
-
const at=trimmed.indexOf('=');if(at<0)fail('npm-configuration-unsupported');
|
|
39
|
-
const key=trimmed.slice(0,at).trim();
|
|
40
|
-
if(key!=='registry'&&!/^\/\/[^\s]+\/:_authToken$/.test(key))
|
|
41
|
-
fail('npm-configuration-unsupported');
|
|
42
|
-
keys.push(key);
|
|
43
|
-
}
|
|
44
|
-
return keys.sort();
|
|
45
|
-
}
|
|
46
|
-
try{
|
|
47
|
-
if(!rootArg||!scriptName||!npmArg)fail('probe-arguments');
|
|
48
|
-
const root=realObservedPath(rootArg),npm=realObservedPath(npmArg);
|
|
49
|
-
const pkg=JSON.parse(fs.readFileSync(path.join(root,'package.json'),'utf8'));
|
|
50
|
-
if(pkg.scripts?.[scriptName]?.trim()!=='tsc --noEmit'||
|
|
51
|
-
pkg.scripts?.['pre'+scriptName]||pkg.scripts?.['post'+scriptName])
|
|
52
|
-
fail('typecheck-script-changed');
|
|
53
|
-
if(process.env.NODE_OPTIONS||process.env.NODE_PATH||process.env.BASH_ENV||
|
|
54
|
-
process.env.ENV||Object.entries(process.env).some(([key,value])=>{
|
|
55
|
-
if(/^npm_config_prefix$/i.test(key)&&process.platform==='win32'){
|
|
56
|
-
// The Windows runner supplies a global npm prefix independently of
|
|
57
|
-
// the selected local compiler. Its value is part of caller context.
|
|
58
|
-
return !path.isAbsolute(value);
|
|
59
|
-
}
|
|
60
|
-
return /^(npm_config_|DYLD_|TSGO_)/i.test(key);
|
|
61
|
-
}))fail('execution-environment-unsupported');
|
|
62
|
-
let npmRoot;
|
|
63
|
-
if(process.platform==='win32'){
|
|
64
|
-
if(path.basename(npm).toLowerCase()!=='npm.cmd')
|
|
65
|
-
fail('npm-installation-layout-unsupported');
|
|
66
|
-
npmRoot=path.join(path.dirname(npm),'node_modules','npm');
|
|
67
|
-
const body=fs.readFileSync(npm,'utf8');
|
|
68
|
-
if(!body.includes('node_modules\\npm\\bin\\npm-cli.js')||
|
|
69
|
-
!body.includes('node_modules\\npm\\bin\\npm-prefix.js')||
|
|
70
|
-
!body.includes('"%NODE_EXE%" "%NPM_CLI_JS%" %*'))
|
|
71
|
-
fail('npm-installation-layout-unsupported');
|
|
72
|
-
const prefix=spawnSync(process.execPath,[path.join(npmRoot,'bin','npm-prefix.js')],
|
|
73
|
-
{cwd:root,env:process.env,encoding:'utf8',timeout:4000,stdio:['ignore','pipe','ignore']});
|
|
74
|
-
const selectedPrefix=prefix.stdout?.trim();
|
|
75
|
-
if(prefix.error||prefix.status!==0||!selectedPrefix||
|
|
76
|
-
!path.isAbsolute(selectedPrefix))fail('npm-installation-layout-unsupported');
|
|
77
|
-
// npm.cmd selects the prefix's CLI only when that file exists. Otherwise
|
|
78
|
-
// it runs the bundled CLI beside npm.cmd; observe the same branch here.
|
|
79
|
-
const alternate=path.join(selectedPrefix,'node_modules','npm','bin','npm-cli.js');
|
|
80
|
-
if(fs.existsSync(alternate)&&
|
|
81
|
-
realObservedPath(alternate)!==realObservedPath(path.join(npmRoot,'bin','npm-cli.js')))
|
|
82
|
-
fail('npm-installation-layout-unsupported');
|
|
83
|
-
}else npmRoot=path.dirname(path.dirname(npm));
|
|
84
|
-
if(path.basename(npmRoot)!=='npm'||path.basename(path.dirname(npmRoot))!=='node_modules')
|
|
85
|
-
fail('npm-installation-layout-unsupported');
|
|
86
|
-
const home=process.platform==='win32'?os.homedir():process.env.HOME;
|
|
87
|
-
if(!home||!path.isAbsolute(home))fail('home-context-unavailable');
|
|
88
|
-
const npmConfig=[path.join(root,'.npmrc'),path.join(home,'.npmrc')]
|
|
89
|
-
.map(file=>[hash(file),npmRcKeys(file)]);
|
|
90
|
-
const contextFacts={
|
|
91
|
-
npmTree:treeHash(npmRoot),npmConfig,
|
|
92
|
-
node:hash(JSON.stringify([process.version,realObservedPath(process.execPath),
|
|
93
|
-
fs.statSync(process.execPath).size])),
|
|
94
|
-
shell:process.platform==='win32'?hash(JSON.stringify([
|
|
95
|
-
realObservedPath(process.env.ComSpec||process.env.COMSPEC||''),
|
|
96
|
-
digestFile(process.env.ComSpec||process.env.COMSPEC||''),
|
|
97
|
-
process.env.PATHEXT||'',digestFile(npm),
|
|
98
|
-
digestFile(path.join(root,'node_modules','.bin','tsc.cmd'))])):
|
|
99
|
-
digestFile('/bin/sh')};
|
|
100
|
-
const contextHash=hash(JSON.stringify(contextFacts));
|
|
101
|
-
if(contextOnly(contextHash))process.exit(0);
|
|
102
|
-
const tsRoot=path.join(root,'node_modules','typescript');
|
|
103
|
-
let tsc;
|
|
104
|
-
if(process.platform==='win32'){
|
|
105
|
-
const shim=path.join(root,'node_modules','.bin','tsc.cmd');
|
|
106
|
-
const body=fs.readFileSync(shim,'utf8').replace(/\r\n/g,'\n').trim();
|
|
107
|
-
const expected=`@ECHO off
|
|
108
|
-
GOTO start
|
|
109
|
-
:find_dp0
|
|
110
|
-
SET dp0=%~dp0
|
|
111
|
-
EXIT /b
|
|
112
|
-
:start
|
|
113
|
-
SETLOCAL
|
|
114
|
-
CALL :find_dp0
|
|
115
|
-
|
|
116
|
-
IF EXIST "%dp0%\\node.exe" (
|
|
117
|
-
SET "_prog=%dp0%\\node.exe"
|
|
118
|
-
) ELSE (
|
|
119
|
-
SET "_prog=node"
|
|
120
|
-
SET PATHEXT=%PATHEXT:;.JS;=;%
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\\..\\typescript\\bin\\tsc" %*`;
|
|
124
|
-
if(body!==expected)fail('typescript-executable-changed');
|
|
125
|
-
tsc=realObservedPath(path.join(tsRoot,'bin','tsc'));
|
|
126
|
-
}else{
|
|
127
|
-
tsc=realObservedPath(path.join(root,'node_modules','.bin','tsc'));
|
|
128
|
-
if(tsc!==path.join(tsRoot,'bin','tsc'))fail('typescript-executable-changed');
|
|
129
|
-
}
|
|
130
|
-
const configReads=new Set(),visited=new Set();
|
|
131
|
-
let ts;try{ts=require(tsRoot);}catch{}
|
|
132
|
-
if(typeof ts?.getParsedCommandLineOfConfigFile==='function'){
|
|
133
|
-
const host={...ts.sys,readFile(file){configReads.add(path.resolve(file));
|
|
134
|
-
return ts.sys.readFile(file);},onUnRecoverableConfigFileDiagnostic(){}};
|
|
135
|
-
const parseConfig=file=>{
|
|
136
|
-
const absolute=path.resolve(file);
|
|
137
|
-
if(!within(absolute,root))fail('typescript-config-outside-repository');
|
|
138
|
-
if(visited.has(absolute))return;visited.add(absolute);
|
|
139
|
-
if(visited.size>64)fail('typescript-config-graph-too-large');
|
|
140
|
-
const parsed=ts.getParsedCommandLineOfConfigFile(absolute,{},host);
|
|
141
|
-
if(!parsed||parsed.errors?.length)fail('typescript-config-unavailable');
|
|
142
|
-
if(parsed.options?.plugins?.length)fail('typescript-plugin-unsupported');
|
|
143
|
-
if(parsed.options?.incremental||parsed.options?.composite)
|
|
144
|
-
fail('typescript-incremental-unsupported');
|
|
145
|
-
for(const reference of parsed.projectReferences||[])
|
|
146
|
-
parseConfig(reference.path.endsWith('.json')?reference.path:
|
|
147
|
-
path.join(reference.path,'tsconfig.json'));
|
|
148
|
-
};
|
|
149
|
-
parseConfig(path.join(root,'tsconfig.json'));
|
|
150
|
-
}else{
|
|
151
|
-
// TypeScript 7's native compiler does not expose the previous config API.
|
|
152
|
-
// Its CLI reports the effective config; restrict this path to one direct
|
|
153
|
-
// config rather than guessing about extends/reference provenance.
|
|
154
|
-
const config=path.join(root,'tsconfig.json'),raw=fs.readFileSync(config,'utf8');
|
|
155
|
-
if(/"(?:extends|references|plugins|incremental|composite)"\s*:/.test(raw))
|
|
156
|
-
fail('typescript-config-graph-unsupported');
|
|
157
|
-
const shown=spawnSync(process.execPath,[tsc,'--showConfig'],{cwd:root,
|
|
158
|
-
env:process.env,encoding:'utf8',timeout:4000,maxBuffer:16*1024*1024,
|
|
159
|
-
stdio:['ignore','pipe','pipe']});
|
|
160
|
-
if(shown.error||shown.signal||shown.status!==0)fail('typescript-config-unavailable');
|
|
161
|
-
const effective=JSON.parse(shown.stdout);
|
|
162
|
-
if(effective.compilerOptions?.plugins?.length||
|
|
163
|
-
effective.compilerOptions?.incremental||effective.compilerOptions?.composite)
|
|
164
|
-
fail('typescript-config-graph-unsupported');
|
|
165
|
-
configReads.add(config);
|
|
166
|
-
}
|
|
167
|
-
for(const file of configReads)if(!within(realObservedPath(file),root))
|
|
168
|
-
fail('typescript-config-outside-repository');
|
|
169
|
-
const listingStarted=performance.now();
|
|
170
|
-
const captured=process.argv.includes('--redue-checkpoint')?compilerFiles(tsRoot,root):null;
|
|
171
|
-
const listed=captured?{status:0,stdout:captured.files.join('\n')}:spawnSync(process.execPath,[tsc,'--noEmit','--listFilesOnly'],
|
|
172
|
-
{cwd:root,env:process.env,encoding:'utf8',timeout:process.platform==='win32'?10000:4000,
|
|
173
|
-
maxBuffer:16*1024*1024,
|
|
174
|
-
stdio:['ignore','pipe','pipe']});
|
|
175
|
-
timing('typescript_contract.list_files',listingStarted,{status:listed.status??null,
|
|
176
|
-
error:listed.error?.code||null});
|
|
177
|
-
if(listed.error||listed.signal||listed.status!==0)fail('typescript-input-list-unavailable');
|
|
178
|
-
const files=listed.stdout.trim().split(/\r?\n/).filter(Boolean)
|
|
179
|
-
.map(file=>path.resolve(file));
|
|
180
|
-
if(!files.length||files.some(file=>!within(realObservedPath(file),root)))
|
|
181
|
-
fail('typescript-input-outside-repository');
|
|
182
|
-
const facts={schema:1,root:hash(root),script:scriptName,
|
|
183
|
-
configs:[...configReads].sort().map(file=>[hash(file),digestFile(file)]),
|
|
184
|
-
files:[...new Set(files)].sort().map(file=>[hash(file),digestFile(file)]),
|
|
185
|
-
...contextFacts};
|
|
186
|
-
probeResult(hash(JSON.stringify(facts)),contextHash,captured?.queries);
|
|
187
|
-
}catch(e){console.error(`VSTATE_REASON:${e.code&&/^[a-z-]+$/.test(e.code)?e.code:
|
|
188
|
-
'typescript-contract-unavailable'}`);process.exitCode=2;}
|
|
2
|
+
// Retired npm-launcher interpretation. Do not return even a context-only digest:
|
|
3
|
+
// old receipts and cached compiler certificates cannot qualify this obligation.
|
|
4
|
+
console.error('VSTATE_REASON:npm-launcher-unobserved-inputs');
|
|
5
|
+
process.exitCode=2;
|
|
@@ -50,47 +50,7 @@ export function qualifyTypeScript(root,selected,discovered={}) {
|
|
|
50
50
|
return {qualified:issues.length===0,issues};
|
|
51
51
|
}
|
|
52
52
|
if(selected.qualification!=='typescript-noemit-v1')return null;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
catch{issues.push('package manifest unavailable');}
|
|
57
|
-
if(!selected.script||pkg?.scripts?.[selected.script]?.trim()!=='tsc --noEmit'||
|
|
58
|
-
pkg?.scripts?.['pre'+selected.script]||pkg?.scripts?.['post'+selected.script])
|
|
59
|
-
issues.push('typecheck script is not the supported standalone tsc --noEmit invocation');
|
|
60
|
-
if(selected.command?.length!==3||selected.command[1]!=='run'||
|
|
61
|
-
selected.command[2]!==selected.script)
|
|
62
|
-
issues.push('package-manager invocation differs from the discovered script');
|
|
63
|
-
try{const npm=fs.realpathSync(selected.command[0]);
|
|
64
|
-
const supported=process.platform==='win32'?path.basename(npm).toLowerCase()==='npm.cmd'&&
|
|
65
|
-
fs.existsSync(path.join(path.dirname(npm),'node_modules','npm','bin','npm-cli.js')):
|
|
66
|
-
path.basename(npm)==='npm-cli.js'&&
|
|
67
|
-
path.basename(path.dirname(path.dirname(npm)))==='npm';
|
|
68
|
-
if(!supported)
|
|
69
|
-
issues.push('npm executable layout is not supported');}
|
|
70
|
-
catch{issues.push('npm executable unavailable');}
|
|
71
|
-
if(!fs.existsSync(path.join(root,'tsconfig.json')))
|
|
72
|
-
issues.push('tsconfig.json is missing');
|
|
73
|
-
if(!fs.existsSync(path.join(root,'node_modules','typescript','package.json'))||
|
|
74
|
-
!fs.existsSync(path.join(root,'node_modules','.bin',
|
|
75
|
-
process.platform==='win32'?'tsc.cmd':'tsc')))
|
|
76
|
-
issues.push('local TypeScript installation is missing');
|
|
77
|
-
if(!selected.inputs?.includes('**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,json,jsonc}')||
|
|
78
|
-
!selected.inputs?.includes('package.json')||
|
|
79
|
-
!selected.inputs?.includes('tsconfig.json'))
|
|
80
|
-
issues.push('TypeScript source/config file-set declaration is incomplete');
|
|
81
|
-
if(!selected.installedInputs?.includes('node_modules/**'))
|
|
82
|
-
issues.push('installed package contents and membership are not declared');
|
|
83
|
-
if(!selected.environment?.executableIdentity||
|
|
84
|
-
!['node','npm'].every(name=>selected.environment?.pathExecutables?.includes(name))||
|
|
85
|
-
!['NODE_OPTIONS','NODE_PATH','HOME','BASH_ENV','ENV'].every(name=>
|
|
86
|
-
selected.environment?.variables?.includes(name))||
|
|
87
|
-
!['npm_config_','DYLD_','TSGO_'].every(name=>
|
|
88
|
-
selected.environment?.prefixes?.includes(name)))
|
|
89
|
-
issues.push('caller toolchain or execution context declaration is incomplete');
|
|
90
|
-
if(!selected.probes?.some(argv=>path.basename(argv[1]||'')==='typescript-contract-probe.mjs'&&
|
|
91
|
-
argv[2]&&fs.existsSync(argv[2])&&
|
|
92
|
-
realObservedPath(argv[2])===root&&argv[3]===selected.script&&
|
|
93
|
-
argv[4]===selected.command[0]))
|
|
94
|
-
issues.push('TypeScript input and toolchain probe is missing');
|
|
95
|
-
return {qualified:issues.length===0,issues};
|
|
53
|
+
// Retired automatic launcher contract: npm's notifier consumes unobserved
|
|
54
|
+
// remote/cache/time state. A successful execution is still an outcome.
|
|
55
|
+
return {qualified:false,issues:['npm launcher applicability is unqualified: update notification uses unobserved remote/cache/time inputs; run the npm script when needed']};
|
|
96
56
|
}
|