101 lines
3.5 KiB
TypeScript
101 lines
3.5 KiB
TypeScript
import { existsSync as defaultExistsSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
/**
|
|
* Returns the correct Node.js type-stripping flag for subprocess spawning.
|
|
*
|
|
* Node v26 enforces ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING for files
|
|
* resolved under `node_modules/`. When SF is installed globally via npm,
|
|
* all source files live under `node_modules/sf-run/src/...`, so
|
|
* `--experimental-strip-types` fails deterministically.
|
|
*
|
|
* `--experimental-transform-types` applies a full TypeScript transform that
|
|
* works regardless of whether the file is under `node_modules/`. SF requires
|
|
* Node 26+, so this flag is always available.
|
|
*/
|
|
export function resolveTypeStrippingFlag(packageRoot: string): string {
|
|
return isUnderNodeModules(packageRoot)
|
|
? "--experimental-transform-types"
|
|
: "--experimental-strip-types";
|
|
}
|
|
|
|
/**
|
|
* Returns true when the given path sits inside a `node_modules/` directory.
|
|
* Handles both Unix and Windows path separators.
|
|
*/
|
|
export function isUnderNodeModules(filePath: string): boolean {
|
|
const normalized = filePath.replace(/\\/g, "/");
|
|
return normalized.includes("/node_modules/");
|
|
}
|
|
|
|
export interface SubprocessModuleResolution {
|
|
/** Absolute path to the module file (either src/.ts or dist/.js). */
|
|
modulePath: string;
|
|
/** When true the module is pre-compiled JS — skip TS flags and loader. */
|
|
useCompiledJs: boolean;
|
|
}
|
|
|
|
/**
|
|
* Resolves a subprocess module path, preferring compiled `dist/*.js` when the
|
|
* package root is under `node_modules/`.
|
|
*
|
|
* Node v26 unconditionally refuses `.ts` files under `node_modules/` — even
|
|
* with `--experimental-transform-types`. When SF is installed globally via
|
|
* npm, every subprocess that loads a `.ts` extension module crashes with
|
|
* `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`.
|
|
*
|
|
* The compiled JS files already ship in the npm package (`dist/` is in the
|
|
* `files` array in package.json) and are the correct artefacts to use when
|
|
* running from a packaged install.
|
|
*
|
|
* @param packageRoot Absolute path to the SF package root.
|
|
* @param relPath Path relative to `src/`, e.g.
|
|
* `"resources/extensions/sf/workspace-index.ts"`.
|
|
* @param checkExists Optional `existsSync` override (for testing).
|
|
*/
|
|
export function resolveSubprocessModule(
|
|
packageRoot: string,
|
|
relPath: string,
|
|
checkExists: (path: string) => boolean = defaultExistsSync,
|
|
): SubprocessModuleResolution {
|
|
const jsRelPath = relPath.replace(/\.ts$/, ".js");
|
|
const distPath = join(packageRoot, "dist", jsRelPath);
|
|
if (checkExists(distPath)) {
|
|
return { modulePath: distPath, useCompiledJs: true };
|
|
}
|
|
|
|
const sourceJsPath = join(packageRoot, "src", jsRelPath);
|
|
if (checkExists(sourceJsPath)) {
|
|
return { modulePath: sourceJsPath, useCompiledJs: true };
|
|
}
|
|
|
|
return {
|
|
modulePath: join(packageRoot, "src", relPath),
|
|
useCompiledJs: false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Builds the Node.js subprocess prefix args for running a SF extension module.
|
|
*
|
|
* When the module resolved to compiled JS (`useCompiledJs === true`), returns
|
|
* only `["--input-type=module"]` — no TS loader, no TS stripping flag.
|
|
*
|
|
* When the module is TypeScript source, returns the full prefix:
|
|
* `["--import", <loaderHref>, <tsFlag>, "--input-type=module"]`.
|
|
*/
|
|
export function buildSubprocessPrefixArgs(
|
|
packageRoot: string,
|
|
resolution: SubprocessModuleResolution,
|
|
tsLoaderHref: string,
|
|
): string[] {
|
|
if (resolution.useCompiledJs) {
|
|
return ["--input-type=module"];
|
|
}
|
|
return [
|
|
"--import",
|
|
tsLoaderHref,
|
|
resolveTypeStrippingFlag(packageRoot),
|
|
"--input-type=module",
|
|
];
|
|
}
|