33import { spawn } from 'node:child_process'
44import { existsSync } from 'node:fs'
55import { cp , lstat , mkdir , mkdtemp , readFile , readdir , realpath , rm , writeFile } from 'node:fs/promises'
6- import { tmpdir } from 'node:os'
7- import { join , resolve , sep } from 'node:path'
6+ import { join , relative , resolve , sep } from 'node:path'
87import { fileURLToPath } from 'node:url'
98
109const desktopRoot = resolve ( import . meta. dirname , '..' )
@@ -14,6 +13,7 @@ const deployPackage = '@pymodel/pythinker-code'
1413const entry = join ( staging , 'node_modules/@pymodel/pythinker-code/dist/launcher.mjs' )
1514const frontend = join ( staging , 'node_modules/@pymodel/pythinker-code/dist-web/index.html' )
1615const workspaceState = join ( repositoryRoot , 'node_modules/.pnpm-workspace-state-v1.json' )
16+ const stagingParent = join ( repositoryRoot , 'node_modules' , '.pythinker-desktop-staging' )
1717
1818/** Windows characters that make an argument unsafe to hand to `cmd.exe` unquoted. */
1919const WINDOWS_UNSAFE_ARGUMENT = / [ \s " & ( ) < > ^ | ] / u
@@ -42,6 +42,21 @@ export function packageManagerInvocation(platform: string, command: string, args
4242 }
4343}
4444
45+ /**
46+ * Express a deploy target the way pnpm accepts it.
47+ *
48+ * pnpm joins its workspace root with the deploy target rather than resolving
49+ * it, so an absolute path on another volume produces a concatenated,
50+ * non-existent directory such as `D:\repo\C:\Users\…`. A workspace-relative
51+ * target is correct whether pnpm joins or resolves.
52+ * @param workspaceRoot - The pnpm workspace root, and the child process's cwd.
53+ * @param target - The absolute staging directory.
54+ * @returns The target expressed relative to the workspace root.
55+ */
56+ export function deployTargetArgument ( workspaceRoot : string , target : string ) : string {
57+ return relative ( workspaceRoot , target )
58+ }
59+
4560async function run ( command : string , args : readonly string [ ] ) : Promise < void > {
4661 const invocation = packageManagerInvocation ( process . platform , command , args )
4762 await new Promise < void > ( ( accept , reject ) => {
@@ -96,7 +111,8 @@ async function deploy(target: string): Promise<void> {
96111 try {
97112 await run ( 'pnpm' , [
98113 '--config.verify-deps-before-run=false' , '--filter' , deployPackage , 'deploy' , '--legacy' , '--prod' ,
99- '--config.node-linker=hoisted' , '--config.auto-install-peers=false' , '--config.link-workspace-packages=true' , target ,
114+ '--config.node-linker=hoisted' , '--config.auto-install-peers=false' , '--config.link-workspace-packages=true' ,
115+ deployTargetArgument ( repositoryRoot , target ) ,
100116 ] )
101117 } finally {
102118 if ( savedWorkspaceState === undefined ) await rm ( workspaceState , { force : true } )
@@ -105,7 +121,8 @@ async function deploy(target: string): Promise<void> {
105121}
106122
107123async function main ( ) : Promise < void > {
108- const deployed = await mkdtemp ( join ( tmpdir ( ) , 'pythinker-desktop-runtime-' ) )
124+ await mkdir ( stagingParent , { recursive : true } )
125+ const deployed = await mkdtemp ( join ( stagingParent , 'runtime-' ) )
109126 try {
110127 await deploy ( deployed )
111128 await rm ( join ( staging , 'node_modules' ) , { recursive : true , force : true } )
0 commit comments