Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions packages/solutions/app-tools/src/utils/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ interface TsRuntimeSetupOptions {
preferTsNodeForServerRuntime?: boolean;
}

// Describes final runtime selection policy.
// Prefer Node.js native TypeScript support when available, otherwise fall back to ts-node; skip setup when neither exists.
export const resolveTsRuntimeRegisterMode = (
hasTsNode: boolean,
): TsRuntimeRegisterMode => {
Expand Down Expand Up @@ -51,16 +53,13 @@ export const setupTsRuntime = async (
const tsconfigPath = path.resolve(appDir, TS_CONFIG_FILENAME);
const isTsProject = await fs.pathExists(tsconfigPath);
const hasTsNode = isDepExists(appDir, 'ts-node');
const preferredRegisterMode = resolveTsRuntimeRegisterMode(hasTsNode);
const registerMode =
options.preferTsNodeForServerRuntime && hasTsNode
? 'ts-node'
: preferredRegisterMode;

if (!isTsProject) {
return;
}

const registerMode = resolveTsRuntimeRegisterMode(hasTsNode);

const aliasConfig = getAliasConfig(alias, {
appDirectory: appDir,
tsconfigPath,
Expand Down Expand Up @@ -94,9 +93,7 @@ export const setupTsRuntime = async (
}, {});

if (registerMode === 'unsupported') {
throw new Error(
'TypeScript runtime loading requires Node.js native TypeScript support (Node.js 22+) or `ts-node`. Please upgrade Node.js or install `ts-node` to continue.',
);
return;
}

if (registerMode === 'ts-node') {
Expand Down
83 changes: 77 additions & 6 deletions packages/solutions/app-tools/tests/utils/register.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@ describe('setupTsRuntime', () => {
expect(resolveTsRuntimeRegisterMode(false)).toBe(expected);
});

it('should use node loader when native capability exists without ts-node', async () => {
setNativeTypeScriptSupport('strip');
const { resolveTsRuntimeRegisterMode, setupTsRuntime } = await import(
'../../src/utils/register'
);
mockIsDepExists.mockReturnValue(false);

expect(resolveTsRuntimeRegisterMode(false)).toBe('node-loader');

await setupTsRuntime('/project', '/project/dist', []);

expect(mockRegisterPathsLoader).toBeCalledTimes(1);
expect(mockTsconfigPathsRegister).toBeCalledWith({
baseUrl: '/project',
paths: {
'@/*': ['src/*'],
},
});
});

it('should prefer native capability over node version', async () => {
setNativeTypeScriptSupport(true);
const { resolveTsRuntimeRegisterMode } = await import(
Expand Down Expand Up @@ -105,13 +125,14 @@ describe('setupTsRuntime', () => {
setNativeTypeScriptSupport(originalTypeScriptFeature);
});

it('should register tsconfig-paths when native support is available', async () => {
it('should use node loader when ts-node does not exist but native capability is available', async () => {
mockIsDepExists.mockReturnValue(false);
const { setupTsRuntime } = await import('../../src/utils/register');

await setupTsRuntime('/project', '/project/dist', []);

expect(mockRegisterPathsLoader).toBeCalledTimes(1);
expect(mockRegisterModuleHooks).not.toBeCalled();
expect(mockTsconfigPathsRegister).toBeCalledWith({
baseUrl: '/project',
paths: {
Expand All @@ -122,28 +143,78 @@ describe('setupTsRuntime', () => {
expect(mockReadTsConfigByFile).not.toBeCalled();
});

it('should throw when no TypeScript runtime support is available', async () => {
it('should skip runtime setup when native capability is disabled and ts-node does not exist', async () => {
setNativeTypeScriptSupport(false);
mockIsDepExists.mockReturnValue(false);
const { setupTsRuntime } = await import('../../src/utils/register');

await expect(
setupTsRuntime('/project', '/project/dist', []),
).rejects.toThrow('requires Node.js native TypeScript support');
await expect(setupTsRuntime('/project', '/project/dist', [])).resolves.toBe(
undefined,
);

expect(mockRegisterPathsLoader).not.toBeCalled();
expect(mockRegisterModuleHooks).not.toBeCalled();
expect(mockTsconfigPathsRegister).not.toBeCalled();
expect(mockLoadFromProject).not.toBeCalled();
});

it('should use node loader when native capability is strip mode', async () => {
it('should use node loader when native capability is strip mode but ts-node does not exist', async () => {
setNativeTypeScriptSupport('strip');
mockIsDepExists.mockReturnValue(false);
const { setupTsRuntime } = await import('../../src/utils/register');

await setupTsRuntime('/project', '/project/dist', []);

expect(mockRegisterPathsLoader).toBeCalledTimes(1);
expect(mockRegisterModuleHooks).not.toBeCalled();
expect(mockTsconfigPathsRegister).toBeCalledWith({
baseUrl: '/project',
paths: {
'@/*': ['src/*'],
},
});
expect(mockLoadFromProject).not.toBeCalled();
});

it('should still use node loader when preferring ts-node but ts-node does not exist', async () => {
setNativeTypeScriptSupport('strip');
mockIsDepExists.mockReturnValue(false);
const { setupTsRuntime } = await import('../../src/utils/register');

await setupTsRuntime('/project', '/project/dist', [], {
preferTsNodeForServerRuntime: true,
moduleType: 'module',
});

expect(mockRegisterPathsLoader).toBeCalledTimes(1);
expect(mockRegisterModuleHooks).not.toBeCalled();
expect(mockTsconfigPathsRegister).toBeCalledWith({
baseUrl: '/project',
paths: {
'@/*': ['src/*'],
},
});
expect(mockLoadFromProject).not.toBeCalled();
expect(mockReadTsConfigByFile).not.toBeCalled();
});

it('should prefer node loader when native capability exists and ts-node also exists', async () => {
setNativeTypeScriptSupport('strip');
mockIsDepExists.mockReturnValue(true);
const { setupTsRuntime } = await import('../../src/utils/register');

await setupTsRuntime('/project', '/project/dist', []);

expect(mockRegisterPathsLoader).toBeCalledTimes(1);
expect(mockTsconfigPathsRegister).toBeCalledWith({
baseUrl: '/project',
paths: {
'@/*': ['src/*'],
},
});
expect(mockLoadFromProject).not.toBeCalled();
expect(mockReadTsConfigByFile).not.toBeCalled();
expect(mockRegisterModuleHooks).not.toBeCalled();
});

it('should register ts-node when ts-node exists', async () => {
Expand Down
Loading