@bleedingdev/modern-js-app-tools-extensions 3.9.3-ultramodern.1 → 3.9.3-ultramodern.11
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/dist/cjs/build-config/build-environment.js +7 -5
- package/dist/cjs/build-config/internal-effect-discovery.js +94 -6
- package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +24 -3
- package/dist/cjs/native-type-checker.js +66 -20
- package/dist/cjs/release-identity.js +1 -1
- package/dist/cjs/renderer-build-identity.js +13 -3
- package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +18 -1
- package/dist/esm/build-config/build-environment.mjs +7 -5
- package/dist/esm/build-config/internal-effect-discovery.mjs +90 -5
- package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +24 -3
- package/dist/esm/native-type-checker.mjs +68 -22
- package/dist/esm/release-identity.mjs +1 -1
- package/dist/esm/renderer-build-identity.mjs +13 -3
- package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +18 -1
- package/dist/esm-node/build-config/build-environment.mjs +7 -5
- package/dist/esm-node/build-config/internal-effect-discovery.mjs +90 -5
- package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +24 -3
- package/dist/esm-node/native-type-checker.mjs +68 -22
- package/dist/esm-node/release-identity.mjs +1 -1
- package/dist/esm-node/renderer-build-identity.mjs +13 -3
- package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +18 -1
- package/dist/types/build-config/build-environment.d.ts +3 -1
- package/dist/types/build-config/internal-effect-discovery.d.ts +3 -1
- package/dist/types/cloudflare/worker-manifest.d.ts +3 -2
- package/dist/types/config.d.ts +4 -0
- package/dist/types/native-type-checker.d.ts +4 -2
- package/dist/types/release-identity.d.ts +10 -0
- package/dist/types/renderer-build-identity.d.ts +12 -1
- package/package.json +27 -27
- package/src/build-config/build-environment.ts +11 -9
- package/src/build-config/internal-effect-discovery.ts +238 -19
- package/src/cloudflare/worker-manifest.ts +7 -3
- package/src/cloudflare-output-verifier/mutation-policy.ts +41 -3
- package/src/config.ts +4 -0
- package/src/native-type-checker.ts +87 -43
- package/src/release-identity.ts +11 -2
- package/src/renderer-build-identity.ts +51 -11
- package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +18 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import promises from "node:fs/promises";
|
|
2
2
|
import node_path from "node:path";
|
|
3
3
|
import { addIssue } from "./issues.mjs";
|
|
4
|
-
const SOURCE_SCAN_FILE_PATTERN = /\.(?:[cm]?[jt]
|
|
4
|
+
const SOURCE_SCAN_FILE_PATTERN = /\.(?:[cm]?[jt]sx?)$/u;
|
|
5
|
+
const DECLARATION_FILE_PATTERN = /\.d\.[cm]?ts$/u;
|
|
5
6
|
const SOURCE_SCAN_IGNORED_DIRECTORY_NAMES = new Set([
|
|
6
7
|
'.codex',
|
|
7
8
|
'.fastcontext',
|
|
@@ -74,7 +75,7 @@ const walkFiles = async (root)=>{
|
|
|
74
75
|
files.push(...await walkFiles(entryPath));
|
|
75
76
|
continue;
|
|
76
77
|
}
|
|
77
|
-
if (SOURCE_SCAN_FILE_PATTERN.test(entry.name)) files.push(entryPath);
|
|
78
|
+
if ('package.json' === entry.name || SOURCE_SCAN_FILE_PATTERN.test(entry.name) && !DECLARATION_FILE_PATTERN.test(entry.name)) files.push(entryPath);
|
|
78
79
|
}
|
|
79
80
|
return files;
|
|
80
81
|
};
|
|
@@ -91,7 +92,26 @@ const verifyCloudflareOutputMutationPolicy = async (options)=>{
|
|
|
91
92
|
if ('ENOENT' === error.code) continue;
|
|
92
93
|
throw error;
|
|
93
94
|
}
|
|
94
|
-
|
|
95
|
+
let executableSources = [
|
|
96
|
+
source
|
|
97
|
+
];
|
|
98
|
+
if ('package.json' === node_path.basename(file)) try {
|
|
99
|
+
const manifest = JSON.parse(source);
|
|
100
|
+
if (!isRecord(manifest)) throw new TypeError('Package metadata must be an object.');
|
|
101
|
+
const scripts = Object.hasOwn(manifest, "scripts") ? manifest.scripts : {};
|
|
102
|
+
if (!isRecord(scripts)) throw new TypeError("Package scripts must be an object.");
|
|
103
|
+
const commands = Object.values(scripts);
|
|
104
|
+
if (!commands.every((value)=>'string' == typeof value)) throw new TypeError("Package scripts must contain only strings.");
|
|
105
|
+
executableSources = commands;
|
|
106
|
+
} catch {
|
|
107
|
+
addIssue(issues, {
|
|
108
|
+
code: 'invalid-package',
|
|
109
|
+
message: "Cannot verify executable package lifecycle scripts: invalid package metadata.",
|
|
110
|
+
path: file
|
|
111
|
+
});
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
for (const forbidden of FORBIDDEN_MUTATION_PATTERNS)if (executableSources.some((value)=>forbidden.pattern.test(value))) addIssue(issues, {
|
|
95
115
|
code: forbidden.code,
|
|
96
116
|
message: forbidden.message,
|
|
97
117
|
path: file
|
|
@@ -102,4 +122,5 @@ const verifyCloudflareOutputMutationPolicy = async (options)=>{
|
|
|
102
122
|
issues
|
|
103
123
|
};
|
|
104
124
|
};
|
|
125
|
+
const isRecord = (value)=>null !== value && 'object' == typeof value && !Array.isArray(value);
|
|
105
126
|
export { verifyCloudflareOutputMutationPolicy, walkFiles };
|
|
@@ -1,29 +1,59 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
1
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { existsSync, readFileSync, realpathSync,
|
|
3
|
+
import { existsSync, readFileSync, realpathSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
4
5
|
import node_path from "node:path";
|
|
5
6
|
import { promisify } from "node:util";
|
|
6
7
|
import { refreshTsgoCheckerConfig } from "@modern-js/builder";
|
|
7
8
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
9
|
+
import { resolveEffectTsgoCompiler } from "./build-config/build-environment.mjs";
|
|
8
10
|
const execute = promisify(execFile);
|
|
9
11
|
const native_type_checker_name = 'UltramodernNativeTypeChecker';
|
|
10
|
-
function resolveNativeTypeCheckerCommand(
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
function resolveNativeTypeCheckerCommand(from) {
|
|
13
|
+
const executable = resolveEffectTsgoCompiler({
|
|
14
|
+
from
|
|
15
|
+
});
|
|
16
|
+
const version = execFileSync(executable, [
|
|
17
|
+
'--version'
|
|
18
|
+
], {
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
env: {
|
|
21
|
+
...process.env,
|
|
22
|
+
TSRS_CHECKER_ASSIGNMENT: process.env.TSRS_CHECKER_ASSIGNMENT || 'go'
|
|
23
|
+
},
|
|
24
|
+
timeout: 10000
|
|
25
|
+
});
|
|
26
|
+
if (version.includes('(tsrs ')) return {
|
|
27
|
+
executable,
|
|
28
|
+
args: [],
|
|
29
|
+
configApi: createRequire(from).resolve('tsrs/sync')
|
|
30
|
+
};
|
|
20
31
|
return {
|
|
21
|
-
executable
|
|
22
|
-
args: [
|
|
23
|
-
launcher
|
|
24
|
-
]
|
|
32
|
+
executable,
|
|
33
|
+
args: []
|
|
25
34
|
};
|
|
26
35
|
}
|
|
36
|
+
const inspectNativeConfig = `
|
|
37
|
+
import { pathToFileURL } from 'node:url';
|
|
38
|
+
const [modulePath, compiler, configFile] = process.argv.slice(1);
|
|
39
|
+
const { API, formatDiagnostics } = await import(pathToFileURL(modulePath).href);
|
|
40
|
+
const api = new API({ tsserverPath: compiler });
|
|
41
|
+
try {
|
|
42
|
+
const config = api.parseConfigFile(configFile);
|
|
43
|
+
if (config.errors.length) {
|
|
44
|
+
process.stdout.write(formatDiagnostics(config.errors, api));
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
} else {
|
|
47
|
+
process.stdout.write(JSON.stringify({
|
|
48
|
+
compilerOptions: config.options,
|
|
49
|
+
files: config.fileNames,
|
|
50
|
+
references: config.projectReferences,
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
} finally {
|
|
54
|
+
api.close();
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
27
57
|
const watchDependencyPath = (file)=>{
|
|
28
58
|
if ('win32' !== process.platform) return file;
|
|
29
59
|
try {
|
|
@@ -135,19 +165,37 @@ class UltramodernNativeTypeChecker {
|
|
|
135
165
|
executable: selected,
|
|
136
166
|
args: []
|
|
137
167
|
} : selected;
|
|
138
|
-
const
|
|
168
|
+
const inspect = '--showConfig' === args[0] && command.configApi;
|
|
169
|
+
const result = await execute(inspect ? process.execPath : command.executable, inspect ? [
|
|
170
|
+
'--input-type=module',
|
|
171
|
+
'--eval',
|
|
172
|
+
inspectNativeConfig,
|
|
173
|
+
command.configApi,
|
|
174
|
+
command.executable,
|
|
175
|
+
args[args.indexOf('--project') + 1]
|
|
176
|
+
] : [
|
|
139
177
|
...command.args,
|
|
140
178
|
...args
|
|
141
179
|
], {
|
|
142
180
|
cwd: node_path.dirname(this.options.configFile),
|
|
143
|
-
|
|
181
|
+
env: {
|
|
182
|
+
...process.env,
|
|
183
|
+
TSRS_CHECKER_ASSIGNMENT: process.env.TSRS_CHECKER_ASSIGNMENT || 'go'
|
|
184
|
+
},
|
|
185
|
+
maxBuffer: 16777216,
|
|
186
|
+
...inspect ? {
|
|
187
|
+
timeout: 10000
|
|
188
|
+
} : {}
|
|
144
189
|
});
|
|
145
190
|
return result.stdout;
|
|
146
191
|
} catch (cause) {
|
|
147
192
|
const output = cause;
|
|
148
193
|
const diagnostics = `${output.stdout ?? ''}${output.stderr ?? ''}`;
|
|
149
|
-
throw new Error(`${native_type_checker_name} failed:\n${diagnostics || (cause instanceof Error ? cause.message : String(cause))}${missingJsxRuntimeHint(diagnostics)}`, {
|
|
194
|
+
throw Object.assign(new Error(`${native_type_checker_name} failed:\n${diagnostics || (cause instanceof Error ? cause.message : String(cause))}${missingJsxRuntimeHint(diagnostics)}`, {
|
|
150
195
|
cause
|
|
196
|
+
}), {
|
|
197
|
+
code: output.code,
|
|
198
|
+
signal: output.signal
|
|
151
199
|
});
|
|
152
200
|
}
|
|
153
201
|
}
|
|
@@ -209,9 +257,7 @@ class UltramodernNativeTypeChecker {
|
|
|
209
257
|
this.removeProjection(configFile);
|
|
210
258
|
}
|
|
211
259
|
} catch (cause) {
|
|
212
|
-
compilation.errors.push(
|
|
213
|
-
cause
|
|
214
|
-
}));
|
|
260
|
+
compilation.errors.push(cause instanceof Error ? cause : new Error(String(cause)));
|
|
215
261
|
}
|
|
216
262
|
});
|
|
217
263
|
});
|
|
@@ -20,7 +20,7 @@ const gitOutput = (workspaceRoot, args)=>{
|
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
|
-
const createUltramodernReleaseBuildMarker = ({ generationBuildMarker, sourceRevision, unitId })=>createHash('sha256').update(`${BUILD_MARKER_NAMESPACE}:${unitId}:${generationBuildMarker}:${sourceRevision}`).digest('hex')
|
|
23
|
+
const createUltramodernReleaseBuildMarker = ({ generationBuildMarker, sourceRevision, unitId })=>createHash('sha256').update(`${BUILD_MARKER_NAMESPACE}:${unitId}:${generationBuildMarker}:${sourceRevision}`).digest('hex');
|
|
24
24
|
const resolveUltramodernSourceRevision = (workspaceRoot = process.cwd(), configuredSourceRevision, environment = process.env)=>{
|
|
25
25
|
const configuredArgumentValue = configuredSourceRevision?.trim();
|
|
26
26
|
const configuredEnvironmentValue = environment.ULTRAMODERN_SOURCE_REVISION?.trim();
|
|
@@ -10,6 +10,7 @@ const LOCKFILES = [
|
|
|
10
10
|
'bun.lock',
|
|
11
11
|
'bun.lockb'
|
|
12
12
|
];
|
|
13
|
+
const RELEASE_BUILD_MARKER_PATTERN = /^[a-f0-9]{64}$/u;
|
|
13
14
|
const processNonce = randomUUID();
|
|
14
15
|
const sha256 = (value)=>createHash('sha256').update(value).digest('hex');
|
|
15
16
|
function rendererProfileKey(profile) {
|
|
@@ -54,12 +55,21 @@ function resolveRendererBuildIdentities(options) {
|
|
|
54
55
|
const projectRoot = node_path.resolve(options.projectRoot);
|
|
55
56
|
const appId = options.appId ?? JSON.parse(node_fs.readFileSync(node_path.join(projectRoot, 'package.json'), 'utf8')).name;
|
|
56
57
|
if ('string' != typeof appId || !appId || appId.trim() !== appId) throw new Error('Renderer application identity requires a delivery artifact appId or a package name.');
|
|
58
|
+
const { deliveryUnit } = options;
|
|
59
|
+
if (void 0 !== deliveryUnit && ('string' != typeof deliveryUnit.unitId || !deliveryUnit.unitId || deliveryUnit.unitId.trim() !== deliveryUnit.unitId)) throw new Error('Renderer delivery-unit identity requires a nonempty trimmed unitId.');
|
|
57
60
|
const profileKey = rendererProfileKey(options.profile);
|
|
58
|
-
const sourceRevision = resolveUltramodernSourceRevision(projectRoot, options.sourceRevision);
|
|
59
|
-
const
|
|
61
|
+
const sourceRevision = resolveUltramodernSourceRevision(projectRoot, deliveryUnit ? deliveryUnit.sourceRevision : options.sourceRevision);
|
|
62
|
+
const promotable = 'production' === options.mode && 'workspace' !== sourceRevision;
|
|
63
|
+
let buildId;
|
|
64
|
+
if (promotable && deliveryUnit) {
|
|
65
|
+
if (deliveryUnit.sourceRevision !== sourceRevision || !RELEASE_BUILD_MARKER_PATTERN.test(deliveryUnit.buildMarker)) throw new Error(`Renderer delivery unit ${deliveryUnit.unitId} must carry the release identity of source revision ${sourceRevision}.`);
|
|
66
|
+
buildId = deliveryUnit.buildMarker;
|
|
67
|
+
} else buildId = sha256(JSON.stringify([
|
|
68
|
+
appId,
|
|
69
|
+
deliveryUnit?.unitId ?? null,
|
|
60
70
|
profileKey,
|
|
61
71
|
lockfileHash(projectRoot),
|
|
62
|
-
|
|
72
|
+
promotable ? sourceRevision : processNonce
|
|
63
73
|
]));
|
|
64
74
|
const identities = Object.fromEntries(entries.map((entryName)=>[
|
|
65
75
|
entryName,
|
|
@@ -306,7 +306,24 @@ async function invokeNativeRouteWorker(route, identity, request, env, ctx) {
|
|
|
306
306
|
const resources =
|
|
307
307
|
nativeRenderer?.renderer === identity.renderer &&
|
|
308
308
|
Object.hasOwn(nativeRenderer.entries, route.entryName)
|
|
309
|
-
?
|
|
309
|
+
? {
|
|
310
|
+
...nativeRenderer.entries[route.entryName],
|
|
311
|
+
...(Object.hasOwn(nativeRenderer, 'workerFederation')
|
|
312
|
+
? { workerFederation: nativeRenderer.workerFederation }
|
|
313
|
+
: {}),
|
|
314
|
+
...(Object.hasOwn(nativeRenderer, 'workerFederationManifestUrl')
|
|
315
|
+
? {
|
|
316
|
+
workerFederationManifestUrl:
|
|
317
|
+
nativeRenderer.workerFederationManifestUrl,
|
|
318
|
+
}
|
|
319
|
+
: {}),
|
|
320
|
+
...(Object.hasOwn(nativeRenderer, 'workerFederationServices')
|
|
321
|
+
? {
|
|
322
|
+
workerFederationServices:
|
|
323
|
+
nativeRenderer.workerFederationServices,
|
|
324
|
+
}
|
|
325
|
+
: {}),
|
|
326
|
+
}
|
|
310
327
|
: undefined;
|
|
311
328
|
// Native renderers have no Flight transport. Reject before the bundle loads.
|
|
312
329
|
if (
|
|
@@ -10,8 +10,8 @@ const owningManifest = findPackageJSON(owningModuleUrl, owningModuleUrl);
|
|
|
10
10
|
if (!owningManifest) throw new Error('Cannot find owning build-config package manifest');
|
|
11
11
|
const owningName = JSON.parse(readFileSync(owningManifest, 'utf8')).name;
|
|
12
12
|
if ('string' != typeof owningName || 0 === owningName.length) throw new Error(`Invalid owning build-config package name: ${owningManifest}`);
|
|
13
|
-
const { effectCompilerDiscoveryFailureStage: effectCompilerDiscoveryFailureStage, resolveInstalledEffectCompiler: resolveInstalledEffectCompiler } = createRequire(owningModuleUrl)(`${owningName}/internal-effect-discovery`);
|
|
14
|
-
const EFFECT_TSGO_RESOLUTION_ERROR = 'Unable to resolve the Effect TS-Go compiler. Install "@effect/tsgo" and a native TypeScript backend for this build config, or set EFFECT_TSGO_BIN.';
|
|
13
|
+
const { effectCompilerDiscoveryFailureStage: effectCompilerDiscoveryFailureStage, resolveInstalledEffectCompiler: resolveInstalledEffectCompiler, validateConfiguredEffectCompiler: validateConfiguredEffectCompiler } = createRequire(owningModuleUrl)(`${owningName}/internal-effect-discovery`);
|
|
14
|
+
const EFFECT_TSGO_RESOLUTION_ERROR = 'Unable to resolve the Effect TS-Go compiler. Install "@effect/tsgo" and a native TypeScript backend for this build config, or set EFFECT_TSGO_BIN. Bundled tsrs requires matching installed tsrs metadata.';
|
|
15
15
|
const executableEffectTsgoCompilers = new Map();
|
|
16
16
|
function effectTsgoResolutionError(cause, stage, from) {
|
|
17
17
|
const detail = cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause);
|
|
@@ -20,10 +20,10 @@ function effectTsgoResolutionError(cause, stage, from) {
|
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
function resolveExecutableEffectTsgoCompiler(compilerPath) {
|
|
23
|
-
if ('win32' === process.platform) return compilerPath;
|
|
24
23
|
const sourcePath = realpathSync(compilerPath);
|
|
25
24
|
const source = lstatSync(sourcePath);
|
|
26
|
-
if (!source.isFile()) throw new Error(`Effect TS-Go compiler path is not a regular file: ${sourcePath}`);
|
|
25
|
+
if (!source.isFile() || 0 === source.size) throw new Error(`Effect TS-Go compiler path is not a nonempty regular file: ${sourcePath}`);
|
|
26
|
+
if ('win32' === process.platform) return compilerPath;
|
|
27
27
|
try {
|
|
28
28
|
accessSync(sourcePath, constants.X_OK);
|
|
29
29
|
return compilerPath;
|
|
@@ -73,7 +73,9 @@ function getBuildConfigEnvironment(name) {
|
|
|
73
73
|
function resolveEffectTsgoCompiler(options) {
|
|
74
74
|
const configuredCompiler = getBuildConfigEnvironment('EFFECT_TSGO_BIN')?.trim();
|
|
75
75
|
if (configuredCompiler) try {
|
|
76
|
-
|
|
76
|
+
const compiler = resolveExecutableEffectTsgoCompiler(configuredCompiler);
|
|
77
|
+
validateConfiguredEffectCompiler(compiler, options.from);
|
|
78
|
+
return compiler;
|
|
77
79
|
} catch (cause) {
|
|
78
80
|
throw effectTsgoResolutionError(cause, 'Configured compiler validation', options.from);
|
|
79
81
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
2
3
|
import { lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -9,7 +10,14 @@ const NATIVE_TYPESCRIPT_PACKAGES = [
|
|
|
9
10
|
"typescript",
|
|
10
11
|
"@typescript/native"
|
|
11
12
|
];
|
|
13
|
+
const EFFECT_TSRS_PROVIDER_VERSION = '0.59.0';
|
|
14
|
+
const EFFECT_TSRS_TARGETS = new Set([
|
|
15
|
+
'@tsrs/darwin-arm64',
|
|
16
|
+
'@tsrs/linux-x64-gnu',
|
|
17
|
+
'@tsrs/linux-arm64-gnu'
|
|
18
|
+
]);
|
|
12
19
|
const failureStages = new WeakMap();
|
|
20
|
+
const reportedUnsupportedTsrsTargets = new Set();
|
|
13
21
|
function effectCompilerDiscoveryFailureStage(error) {
|
|
14
22
|
return error && 'object' == typeof error ? failureStages.get(error) : void 0;
|
|
15
23
|
}
|
|
@@ -27,7 +35,7 @@ function resolveEffectTsgoPackage(from) {
|
|
|
27
35
|
cliPath
|
|
28
36
|
};
|
|
29
37
|
}
|
|
30
|
-
function
|
|
38
|
+
function isAbsentPackage(projectRequire, from, packageName, error) {
|
|
31
39
|
const filename = 'string' != typeof from || from.startsWith('file:') ? fileURLToPath(from) : from;
|
|
32
40
|
if (!(error instanceof Error) || !('code' in error) || 'MODULE_NOT_FOUND' !== error.code || 'path' in error && void 0 !== error.path || error.message.split('\n')[0] !== `Cannot find module '${packageName}/package.json'` || !('requireStack' in error) || !Array.isArray(error.requireStack) || error.requireStack[0] !== filename) return false;
|
|
33
41
|
for (const searchDirectory of projectRequire.resolve.paths(packageName) ?? [])try {
|
|
@@ -48,7 +56,7 @@ function resolveNativeTypeScriptPackage(from) {
|
|
|
48
56
|
try {
|
|
49
57
|
packageJsonPath = projectRequire.resolve(`${packageName}/package.json`);
|
|
50
58
|
} catch (error) {
|
|
51
|
-
if (
|
|
59
|
+
if (isAbsentPackage(projectRequire, from, packageName, error)) continue;
|
|
52
60
|
throw error;
|
|
53
61
|
}
|
|
54
62
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
@@ -72,10 +80,85 @@ function readCompilerArtifact(filename) {
|
|
|
72
80
|
if (0 === stats.size) throw new Error(`Compiler artifact is empty: ${filename}`);
|
|
73
81
|
return canonicalPath;
|
|
74
82
|
}
|
|
83
|
+
function resolveTsrsManifest(from) {
|
|
84
|
+
const projectRequire = createRequire(from);
|
|
85
|
+
try {
|
|
86
|
+
return projectRequire.resolve('tsrs/package.json');
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (isAbsentPackage(projectRequire, from, 'tsrs', error)) return;
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function compilerVersion(compiler) {
|
|
93
|
+
return execFileSync(compiler, [
|
|
94
|
+
'--version'
|
|
95
|
+
], {
|
|
96
|
+
encoding: 'utf8',
|
|
97
|
+
env: {
|
|
98
|
+
...process.env,
|
|
99
|
+
TSRS_CHECKER_ASSIGNMENT: process.env.TSRS_CHECKER_ASSIGNMENT || 'go'
|
|
100
|
+
},
|
|
101
|
+
timeout: 10000,
|
|
102
|
+
maxBuffer: 1048576
|
|
103
|
+
}).trim();
|
|
104
|
+
}
|
|
105
|
+
function tsrsPlatform(manifestPath) {
|
|
106
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
107
|
+
const { getPlatformPackageName } = createRequire(manifestPath)(join(dirname(manifestPath), 'lib/platform.js'));
|
|
108
|
+
const nativeName = getPlatformPackageName();
|
|
109
|
+
const embedded = manifest.tsrs;
|
|
110
|
+
if ('string' != typeof nativeName || !nativeName || !manifest.optionalDependencies || 'object' != typeof manifest.optionalDependencies || Array.isArray(manifest.optionalDependencies) || 0 === Object.keys(manifest.optionalDependencies).length) throw new Error(`Invalid native tsrs capability metadata: ${manifestPath}`);
|
|
111
|
+
if ('string' != typeof manifest.name || !manifest.name || 'string' != typeof manifest.version || !/^\d+\.\d+\.\d+-ts[\w.-]+$/u.test(manifest.version) || 'string' != typeof embedded?.typescriptVersion || !/^7\.\d+\.\d+(?:[-+][\w.-]+)?$/u.test(embedded.typescriptVersion) || !manifest.version.endsWith(`-ts${embedded.typescriptVersion}`) || 'string' != typeof embedded.typescriptCommit || !/^[a-f\d]{40}$/u.test(embedded.typescriptCommit)) throw new Error(`Invalid native tsrs metadata: ${manifestPath}`);
|
|
112
|
+
const supported = Object.hasOwn(manifest.optionalDependencies, nativeName);
|
|
113
|
+
if (supported && manifest.optionalDependencies[nativeName] !== manifest.version) throw new Error(`Native tsrs target version does not match its selected package: ${manifestPath}`);
|
|
114
|
+
return {
|
|
115
|
+
manifest: manifest,
|
|
116
|
+
nativeName,
|
|
117
|
+
supported
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function resolveTsrsCompiler(manifestPath, effect, configuredCompiler, configuredVersion) {
|
|
121
|
+
const { manifest } = tsrsPlatform(manifestPath);
|
|
122
|
+
const embedded = manifest.tsrs;
|
|
123
|
+
const effectName = `@effect/tsgo-${process.platform}-${process.arch}`;
|
|
124
|
+
const effectManifest = createRequire(effect.cliPath).resolve(`${effectName}/package.json`);
|
|
125
|
+
const platform = JSON.parse(readFileSync(effectManifest, 'utf8'));
|
|
126
|
+
const metadataPath = join(dirname(effectManifest), 'lib/upstream.json');
|
|
127
|
+
const metadata = JSON.parse(readFileSync(metadataPath, 'utf8'));
|
|
128
|
+
const component = metadata.components?.tsrs?.[manifest.version];
|
|
129
|
+
if (platform.name !== effectName || platform.version !== effect.packageJson.version || 5 !== metadata.schemaVersion || !/^[a-f\d]{40}$/u.test(component?.gitHead ?? '') || component?.typescript?.version !== embedded.typescriptVersion || component.typescript.gitHead !== embedded.typescriptCommit) throw new Error(`Effect replacement metadata does not match tsrs: ${metadataPath}`);
|
|
130
|
+
const compiler = readCompilerArtifact(configuredCompiler ?? join(dirname(effectManifest), 'artifacts/tsrs', manifest.version, 'win32' === process.platform ? 'tsrs.exe' : 'tsrs'));
|
|
131
|
+
const version = configuredVersion ?? compilerVersion(compiler);
|
|
132
|
+
const stamp = /\+effect-tsgo\.([\d.]+)\s+\(tsrs ([^,]+), microsoft\/TypeScript@([a-f\d]+)\)$/u.exec(version);
|
|
133
|
+
if (!stamp || stamp[1] !== effect.packageJson.version || stamp[2] !== manifest.version || stamp[3].length < 12 || !embedded.typescriptCommit.startsWith(stamp[3])) throw new Error(`Effect-enabled tsrs matching ${manifest.version} and @effect/tsgo@${effect.packageJson.version} is required: ${compiler}. Install matching packages or select a matching Effect-enabled candidate with EFFECT_TSGO_BIN.`);
|
|
134
|
+
return compiler;
|
|
135
|
+
}
|
|
136
|
+
function validateConfiguredEffectCompiler(compiler, from) {
|
|
137
|
+
const version = compilerVersion(compiler);
|
|
138
|
+
if (!/^Version \d+\.\d+\.\d+[\w.-]*\+effect-tsgo\.\d+\.\d+\.\d+(?:\s|$)/u.test(version)) throw new Error(`EFFECT_TSGO_BIN must select an Effect-enabled native compiler: ${compiler}`);
|
|
139
|
+
if (version.includes('(tsrs ')) {
|
|
140
|
+
const manifest = resolveTsrsManifest(from);
|
|
141
|
+
if (!manifest) throw new Error(`Cannot validate tsrs without its installed package metadata for ${String(from)}`);
|
|
142
|
+
resolveTsrsCompiler(manifest, resolveEffectTsgoPackage(from), compiler, version);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
75
145
|
function resolveInstalledEffectCompiler(from) {
|
|
76
146
|
let stage = 'Package CLI resolution';
|
|
77
147
|
try {
|
|
78
148
|
const effect = resolveEffectTsgoPackage(from);
|
|
149
|
+
stage = 'Native tsrs package resolution';
|
|
150
|
+
const tsrsManifest = resolveTsrsManifest(from);
|
|
151
|
+
const tsrsTarget = tsrsManifest ? tsrsPlatform(tsrsManifest) : void 0;
|
|
152
|
+
if (tsrsTarget && effect.packageJson.version !== EFFECT_TSRS_PROVIDER_VERSION) throw new Error(`Unreviewed Effect tsrs capability contract: ${effect.packageJson.version}`);
|
|
153
|
+
const configuredTsrsCompiler = process.env.TSRS_BINARY || void 0;
|
|
154
|
+
if (tsrsManifest && tsrsTarget?.supported && (configuredTsrsCompiler || EFFECT_TSRS_TARGETS.has(tsrsTarget.nativeName))) {
|
|
155
|
+
stage = 'Native tsrs validation';
|
|
156
|
+
return resolveTsrsCompiler(tsrsManifest, effect, configuredTsrsCompiler);
|
|
157
|
+
}
|
|
158
|
+
if (tsrsTarget && !reportedUnsupportedTsrsTargets.has(tsrsTarget.nativeName)) {
|
|
159
|
+
reportedUnsupportedTsrsTargets.add(tsrsTarget.nativeName);
|
|
160
|
+
console.warn(`[UltraModern] ${tsrsTarget.supported ? `@effect/tsgo@${effect.packageJson.version} does not provide a tsrs replacement for ${tsrsTarget.nativeName}` : `tsrs does not declare ${tsrsTarget.nativeName}`}; selecting the Effect-enabled TypeScript-Go compiler.`);
|
|
161
|
+
}
|
|
79
162
|
stage = 'Native TypeScript package resolution';
|
|
80
163
|
const typeScriptManifest = resolveNativeTypeScriptManifest(from);
|
|
81
164
|
const typeScript = JSON.parse(readFileSync(typeScriptManifest, 'utf8'));
|
|
@@ -93,11 +176,13 @@ function resolveInstalledEffectCompiler(from) {
|
|
|
93
176
|
const metadataPath = join(dirname(effectManifest), 'lib', 'upstream.json');
|
|
94
177
|
const metadata = JSON.parse(readFileSync(metadataPath, 'utf8'));
|
|
95
178
|
const component = metadata.components?.typescript?.[native.version];
|
|
96
|
-
|
|
97
|
-
|
|
179
|
+
const latestTypeScript = metadata.tags?.typescript?.latest;
|
|
180
|
+
if (5 !== metadata.schemaVersion || 'string' != typeof latestTypeScript || component?.provider !== "typescript-go" || component.gitHead !== native.gitHead) throw new Error(`Effect replacement metadata does not match the selected native TypeScript backend: ${metadataPath}`);
|
|
181
|
+
const compilerDirectory = native.version === latestTypeScript ? join(dirname(effectManifest), 'lib') : join(dirname(effectManifest), 'artifacts', "typescript", native.version);
|
|
182
|
+
return readCompilerArtifact(join(compilerDirectory, binaryName));
|
|
98
183
|
} catch (error) {
|
|
99
184
|
if (error && 'object' == typeof error && !failureStages.has(error)) failureStages.set(error, stage);
|
|
100
185
|
throw error;
|
|
101
186
|
}
|
|
102
187
|
}
|
|
103
|
-
export { effectCompilerDiscoveryFailureStage, resolveInstalledEffectCompiler };
|
|
188
|
+
export { effectCompilerDiscoveryFailureStage, resolveInstalledEffectCompiler, validateConfiguredEffectCompiler };
|
|
@@ -2,7 +2,8 @@ import "node:module";
|
|
|
2
2
|
import promises from "node:fs/promises";
|
|
3
3
|
import node_path from "node:path";
|
|
4
4
|
import { addIssue } from "./issues.mjs";
|
|
5
|
-
const SOURCE_SCAN_FILE_PATTERN = /\.(?:[cm]?[jt]
|
|
5
|
+
const SOURCE_SCAN_FILE_PATTERN = /\.(?:[cm]?[jt]sx?)$/u;
|
|
6
|
+
const DECLARATION_FILE_PATTERN = /\.d\.[cm]?ts$/u;
|
|
6
7
|
const SOURCE_SCAN_IGNORED_DIRECTORY_NAMES = new Set([
|
|
7
8
|
'.codex',
|
|
8
9
|
'.fastcontext',
|
|
@@ -75,7 +76,7 @@ const walkFiles = async (root)=>{
|
|
|
75
76
|
files.push(...await walkFiles(entryPath));
|
|
76
77
|
continue;
|
|
77
78
|
}
|
|
78
|
-
if (SOURCE_SCAN_FILE_PATTERN.test(entry.name)) files.push(entryPath);
|
|
79
|
+
if ('package.json' === entry.name || SOURCE_SCAN_FILE_PATTERN.test(entry.name) && !DECLARATION_FILE_PATTERN.test(entry.name)) files.push(entryPath);
|
|
79
80
|
}
|
|
80
81
|
return files;
|
|
81
82
|
};
|
|
@@ -92,7 +93,26 @@ const verifyCloudflareOutputMutationPolicy = async (options)=>{
|
|
|
92
93
|
if ('ENOENT' === error.code) continue;
|
|
93
94
|
throw error;
|
|
94
95
|
}
|
|
95
|
-
|
|
96
|
+
let executableSources = [
|
|
97
|
+
source
|
|
98
|
+
];
|
|
99
|
+
if ('package.json' === node_path.basename(file)) try {
|
|
100
|
+
const manifest = JSON.parse(source);
|
|
101
|
+
if (!isRecord(manifest)) throw new TypeError('Package metadata must be an object.');
|
|
102
|
+
const scripts = Object.hasOwn(manifest, "scripts") ? manifest.scripts : {};
|
|
103
|
+
if (!isRecord(scripts)) throw new TypeError("Package scripts must be an object.");
|
|
104
|
+
const commands = Object.values(scripts);
|
|
105
|
+
if (!commands.every((value)=>'string' == typeof value)) throw new TypeError("Package scripts must contain only strings.");
|
|
106
|
+
executableSources = commands;
|
|
107
|
+
} catch {
|
|
108
|
+
addIssue(issues, {
|
|
109
|
+
code: 'invalid-package',
|
|
110
|
+
message: "Cannot verify executable package lifecycle scripts: invalid package metadata.",
|
|
111
|
+
path: file
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
for (const forbidden of FORBIDDEN_MUTATION_PATTERNS)if (executableSources.some((value)=>forbidden.pattern.test(value))) addIssue(issues, {
|
|
96
116
|
code: forbidden.code,
|
|
97
117
|
message: forbidden.message,
|
|
98
118
|
path: file
|
|
@@ -103,4 +123,5 @@ const verifyCloudflareOutputMutationPolicy = async (options)=>{
|
|
|
103
123
|
issues
|
|
104
124
|
};
|
|
105
125
|
};
|
|
126
|
+
const isRecord = (value)=>null !== value && 'object' == typeof value && !Array.isArray(value);
|
|
106
127
|
export { verifyCloudflareOutputMutationPolicy, walkFiles };
|
|
@@ -1,30 +1,60 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
-
import { execFile } from "node:child_process";
|
|
2
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
-
import { existsSync, readFileSync, realpathSync,
|
|
4
|
+
import { existsSync, readFileSync, realpathSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
5
6
|
import node_path from "node:path";
|
|
6
7
|
import { promisify } from "node:util";
|
|
7
8
|
import { refreshTsgoCheckerConfig } from "@modern-js/builder";
|
|
8
9
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
10
|
+
import { resolveEffectTsgoCompiler } from "./build-config/build-environment.mjs";
|
|
9
11
|
const execute = promisify(execFile);
|
|
10
12
|
const native_type_checker_name = 'UltramodernNativeTypeChecker';
|
|
11
|
-
function resolveNativeTypeCheckerCommand(
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
function resolveNativeTypeCheckerCommand(from) {
|
|
14
|
+
const executable = resolveEffectTsgoCompiler({
|
|
15
|
+
from
|
|
16
|
+
});
|
|
17
|
+
const version = execFileSync(executable, [
|
|
18
|
+
'--version'
|
|
19
|
+
], {
|
|
20
|
+
encoding: 'utf8',
|
|
21
|
+
env: {
|
|
22
|
+
...process.env,
|
|
23
|
+
TSRS_CHECKER_ASSIGNMENT: process.env.TSRS_CHECKER_ASSIGNMENT || 'go'
|
|
24
|
+
},
|
|
25
|
+
timeout: 10000
|
|
26
|
+
});
|
|
27
|
+
if (version.includes('(tsrs ')) return {
|
|
28
|
+
executable,
|
|
29
|
+
args: [],
|
|
30
|
+
configApi: createRequire(from).resolve('tsrs/sync')
|
|
31
|
+
};
|
|
21
32
|
return {
|
|
22
|
-
executable
|
|
23
|
-
args: [
|
|
24
|
-
launcher
|
|
25
|
-
]
|
|
33
|
+
executable,
|
|
34
|
+
args: []
|
|
26
35
|
};
|
|
27
36
|
}
|
|
37
|
+
const inspectNativeConfig = `
|
|
38
|
+
import { pathToFileURL } from 'node:url';
|
|
39
|
+
const [modulePath, compiler, configFile] = process.argv.slice(1);
|
|
40
|
+
const { API, formatDiagnostics } = await import(pathToFileURL(modulePath).href);
|
|
41
|
+
const api = new API({ tsserverPath: compiler });
|
|
42
|
+
try {
|
|
43
|
+
const config = api.parseConfigFile(configFile);
|
|
44
|
+
if (config.errors.length) {
|
|
45
|
+
process.stdout.write(formatDiagnostics(config.errors, api));
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
} else {
|
|
48
|
+
process.stdout.write(JSON.stringify({
|
|
49
|
+
compilerOptions: config.options,
|
|
50
|
+
files: config.fileNames,
|
|
51
|
+
references: config.projectReferences,
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
} finally {
|
|
55
|
+
api.close();
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
28
58
|
const watchDependencyPath = (file)=>{
|
|
29
59
|
if ('win32' !== process.platform) return file;
|
|
30
60
|
try {
|
|
@@ -136,19 +166,37 @@ class UltramodernNativeTypeChecker {
|
|
|
136
166
|
executable: selected,
|
|
137
167
|
args: []
|
|
138
168
|
} : selected;
|
|
139
|
-
const
|
|
169
|
+
const inspect = '--showConfig' === args[0] && command.configApi;
|
|
170
|
+
const result = await execute(inspect ? process.execPath : command.executable, inspect ? [
|
|
171
|
+
'--input-type=module',
|
|
172
|
+
'--eval',
|
|
173
|
+
inspectNativeConfig,
|
|
174
|
+
command.configApi,
|
|
175
|
+
command.executable,
|
|
176
|
+
args[args.indexOf('--project') + 1]
|
|
177
|
+
] : [
|
|
140
178
|
...command.args,
|
|
141
179
|
...args
|
|
142
180
|
], {
|
|
143
181
|
cwd: node_path.dirname(this.options.configFile),
|
|
144
|
-
|
|
182
|
+
env: {
|
|
183
|
+
...process.env,
|
|
184
|
+
TSRS_CHECKER_ASSIGNMENT: process.env.TSRS_CHECKER_ASSIGNMENT || 'go'
|
|
185
|
+
},
|
|
186
|
+
maxBuffer: 16777216,
|
|
187
|
+
...inspect ? {
|
|
188
|
+
timeout: 10000
|
|
189
|
+
} : {}
|
|
145
190
|
});
|
|
146
191
|
return result.stdout;
|
|
147
192
|
} catch (cause) {
|
|
148
193
|
const output = cause;
|
|
149
194
|
const diagnostics = `${output.stdout ?? ''}${output.stderr ?? ''}`;
|
|
150
|
-
throw new Error(`${native_type_checker_name} failed:\n${diagnostics || (cause instanceof Error ? cause.message : String(cause))}${missingJsxRuntimeHint(diagnostics)}`, {
|
|
195
|
+
throw Object.assign(new Error(`${native_type_checker_name} failed:\n${diagnostics || (cause instanceof Error ? cause.message : String(cause))}${missingJsxRuntimeHint(diagnostics)}`, {
|
|
151
196
|
cause
|
|
197
|
+
}), {
|
|
198
|
+
code: output.code,
|
|
199
|
+
signal: output.signal
|
|
152
200
|
});
|
|
153
201
|
}
|
|
154
202
|
}
|
|
@@ -210,9 +258,7 @@ class UltramodernNativeTypeChecker {
|
|
|
210
258
|
this.removeProjection(configFile);
|
|
211
259
|
}
|
|
212
260
|
} catch (cause) {
|
|
213
|
-
compilation.errors.push(
|
|
214
|
-
cause
|
|
215
|
-
}));
|
|
261
|
+
compilation.errors.push(cause instanceof Error ? cause : new Error(String(cause)));
|
|
216
262
|
}
|
|
217
263
|
});
|
|
218
264
|
});
|
|
@@ -21,7 +21,7 @@ const gitOutput = (workspaceRoot, args)=>{
|
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
|
-
const createUltramodernReleaseBuildMarker = ({ generationBuildMarker, sourceRevision, unitId })=>createHash('sha256').update(`${BUILD_MARKER_NAMESPACE}:${unitId}:${generationBuildMarker}:${sourceRevision}`).digest('hex')
|
|
24
|
+
const createUltramodernReleaseBuildMarker = ({ generationBuildMarker, sourceRevision, unitId })=>createHash('sha256').update(`${BUILD_MARKER_NAMESPACE}:${unitId}:${generationBuildMarker}:${sourceRevision}`).digest('hex');
|
|
25
25
|
const resolveUltramodernSourceRevision = (workspaceRoot = process.cwd(), configuredSourceRevision, environment = process.env)=>{
|
|
26
26
|
const configuredArgumentValue = configuredSourceRevision?.trim();
|
|
27
27
|
const configuredEnvironmentValue = environment.ULTRAMODERN_SOURCE_REVISION?.trim();
|