fix(studio): make local source launches reliable

This commit is contained in:
Ma
2026-03-30 12:02:02 +08:00
parent 2aeb4d2fb7
commit dda66825fd
4 changed files with 100 additions and 22 deletions

View File

@@ -12,6 +12,32 @@ describe("studio runtime resolution", () => {
vi.resetModules();
});
it("prefers the repository-local tsx loader for monorepo sources", async () => {
accessMock.mockImplementation(async (path: string) => {
if (
path === "/repo/packages/studio/src/api/index.ts" ||
path === "/repo/packages/studio/node_modules/tsx/dist/loader.mjs"
) {
return;
}
throw new Error(`missing: ${path}`);
});
const { resolveStudioLaunch } = await import("../commands/studio.js");
const launch = await resolveStudioLaunch("/repo/test-project");
expect(launch).toEqual({
studioEntry: "/repo/packages/studio/src/api/index.ts",
command: "node",
args: [
"--import",
"/repo/packages/studio/node_modules/tsx/dist/loader.mjs",
"/repo/packages/studio/src/api/index.ts",
"/repo/test-project",
],
});
});
it("finds monorepo packages/studio sources from a project directory", async () => {
accessMock.mockImplementation(async (path: string) => {
if (path === "/repo/packages/studio/src/api/index.ts") {

View File

@@ -1,12 +1,12 @@
import { Command } from "commander";
import { findProjectRoot, log, logError } from "../utils.js";
import { spawn } from "node:child_process";
import { join } from "node:path";
import { dirname, join } from "node:path";
import { access } from "node:fs/promises";
export interface StudioLaunchSpec {
readonly studioEntry: string;
readonly command: "npx" | "node";
readonly command: string;
readonly args: string[];
}
@@ -29,6 +29,28 @@ export async function resolveStudioLaunch(root: string): Promise<StudioLaunchSpe
join(root, "..", "studio", "src", "api", "index.ts"),
]);
if (sourceEntry) {
const studioPackageRoot = dirname(dirname(dirname(sourceEntry)));
const localTsxLoader = await firstAccessiblePath([
join(studioPackageRoot, "node_modules", "tsx", "dist", "loader.mjs"),
]);
if (localTsxLoader) {
return {
studioEntry: sourceEntry,
command: "node",
args: ["--import", localTsxLoader, sourceEntry, root],
};
}
const localTsx = await firstAccessiblePath([
join(studioPackageRoot, "node_modules", ".bin", "tsx"),
]);
if (localTsx) {
return {
studioEntry: sourceEntry,
command: localTsx,
args: [sourceEntry, root],
};
}
return {
studioEntry: sourceEntry,
command: "npx",

View File

@@ -35,6 +35,7 @@
"@vitejs/plugin-react": "^4.4.0",
"autoprefixer": "^10.4.0",
"tailwindcss": "^4.0.0",
"tsx": "^4.20.6",
"typescript": "^5.8.0",
"vite": "^6.0.0",
"vitest": "^3.0.0"

69
pnpm-lock.yaml generated
View File

@@ -34,7 +34,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.13(@types/node@22.19.15)(typescript@5.9.3))
version: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.13(@types/node@22.19.15)(typescript@5.9.3))(tsx@4.21.0)
packages/core:
dependencies:
@@ -62,7 +62,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.13(@types/node@22.19.15)(typescript@5.9.3))
version: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.13(@types/node@22.19.15)(typescript@5.9.3))(tsx@4.21.0)
packages/studio:
dependencies:
@@ -111,7 +111,7 @@ importers:
devDependencies:
'@tailwindcss/vite':
specifier: ^4.0.0
version: 4.2.1(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1))
version: 4.2.1(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))
'@types/node':
specifier: ^22.0.0
version: 22.19.15
@@ -123,22 +123,25 @@ importers:
version: 19.2.3(@types/react@19.2.14)
'@vitejs/plugin-react':
specifier: ^4.4.0
version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1))
version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))
autoprefixer:
specifier: ^10.4.0
version: 10.4.27(postcss@8.5.8)
tailwindcss:
specifier: ^4.0.0
version: 4.2.1
tsx:
specifier: ^4.20.6
version: 4.21.0
typescript:
specifier: ^5.8.0
version: 5.9.3
vite:
specifier: ^6.0.0
version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)
version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.13(@types/node@22.19.15)(typescript@5.9.3))
version: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.13(@types/node@22.19.15)(typescript@5.9.3))(tsx@4.21.0)
packages:
@@ -1668,6 +1671,9 @@ packages:
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
engines: {node: '>=18'}
get-tsconfig@4.13.7:
resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -2318,6 +2324,9 @@ packages:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
restore-cursor@5.1.0:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
@@ -2557,6 +2566,11 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tsx@4.21.0:
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
engines: {node: '>=18.0.0'}
hasBin: true
tw-animate-css@1.4.0:
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
@@ -3484,12 +3498,12 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.1
'@tailwindcss/oxide-win32-x64-msvc': 4.2.1
'@tailwindcss/vite@4.2.1(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1))':
'@tailwindcss/vite@4.2.1(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))':
dependencies:
'@tailwindcss/node': 4.2.1
'@tailwindcss/oxide': 4.2.1
tailwindcss: 4.2.1
vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)
vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)
'@ts-morph/common@0.27.0':
dependencies:
@@ -3554,7 +3568,7 @@ snapshots:
'@types/validate-npm-package-name@4.0.2': {}
'@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1))':
'@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
@@ -3562,7 +3576,7 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-beta.27
'@types/babel__core': 7.20.5
react-refresh: 0.17.0
vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)
vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)
transitivePeerDependencies:
- supports-color
@@ -3574,14 +3588,14 @@ snapshots:
chai: 5.3.3
tinyrainbow: 2.0.0
'@vitest/mocker@3.2.4(msw@2.12.13(@types/node@22.19.15)(typescript@5.9.3))(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1))':
'@vitest/mocker@3.2.4(msw@2.12.13(@types/node@22.19.15)(typescript@5.9.3))(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
msw: 2.12.13(@types/node@22.19.15)(typescript@5.9.3)
vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)
vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)
'@vitest/pretty-format@3.2.4':
dependencies:
@@ -4224,6 +4238,10 @@ snapshots:
'@sec-ant/readable-stream': 0.4.1
is-stream: 4.0.1
get-tsconfig@4.13.7:
dependencies:
resolve-pkg-maps: 1.0.0
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@@ -4767,6 +4785,8 @@ snapshots:
resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
restore-cursor@5.1.0:
dependencies:
onetime: 7.0.0
@@ -5061,6 +5081,13 @@ snapshots:
tslib@2.8.1: {}
tsx@4.21.0:
dependencies:
esbuild: 0.27.3
get-tsconfig: 4.13.7
optionalDependencies:
fsevents: 2.3.3
tw-animate-css@1.4.0: {}
type-fest@5.4.4:
@@ -5105,13 +5132,13 @@ snapshots:
vary@1.1.2: {}
vite-node@3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1):
vite-node@3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)
vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -5126,7 +5153,7 @@ snapshots:
- tsx
- yaml
vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1):
vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0):
dependencies:
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.3)
@@ -5139,8 +5166,9 @@ snapshots:
fsevents: 2.3.3
jiti: 2.6.1
lightningcss: 1.31.1
tsx: 4.21.0
vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1):
vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0):
dependencies:
esbuild: 0.27.3
fdir: 6.5.0(picomatch@4.0.3)
@@ -5153,12 +5181,13 @@ snapshots:
fsevents: 2.3.3
jiti: 2.6.1
lightningcss: 1.31.1
tsx: 4.21.0
vitest@3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.13(@types/node@22.19.15)(typescript@5.9.3)):
vitest@3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.13(@types/node@22.19.15)(typescript@5.9.3))(tsx@4.21.0):
dependencies:
'@types/chai': 5.2.3
'@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(msw@2.12.13(@types/node@22.19.15)(typescript@5.9.3))(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1))
'@vitest/mocker': 3.2.4(msw@2.12.13(@types/node@22.19.15)(typescript@5.9.3))(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
@@ -5176,8 +5205,8 @@ snapshots:
tinyglobby: 0.2.15
tinypool: 1.1.1
tinyrainbow: 2.0.0
vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)
vite-node: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)
vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)
vite-node: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 22.19.15