Browse Source

fix(runtime): simplify executable entry dispatch

pku-xht 1 month ago
parent
commit
423412b7bf

+ 2 - 2
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md
-2026-07-10-single-file-executable-sdk-runtime-distribution.md: 6ab54fe40007808cc8b9a3c89061738df647a6dd
-2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 2dde9b51c456b234b628f0460e54816b9fd32faf
+2026-07-10-single-file-executable-sdk-runtime-distribution.md: 9c7f48607c15841515c1aa26e5569799ec6ef847
+2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 2e12a0495e231dd4bbc7d54e3a49c4bf04be6296

File diff suppressed because it is too large
+ 0 - 0
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md


File diff suppressed because it is too large
+ 0 - 0
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md


+ 4 - 7
apps/cli/src/bin.ts

@@ -7,8 +7,7 @@
 /* v8 ignore file -- built-bin acceptance exercises this self-executing dispatch. */
 
 import { readFileSync } from 'node:fs'
-import { resolve } from 'node:path'
-import { fileURLToPath, pathToFileURL } from 'node:url'
+import { fileURLToPath } from 'node:url'
 import { loadLayeredEnv } from '@deepseek-ai/dsh-app-boot'
 import { parseDshArgs } from './args.ts'
 
@@ -24,11 +23,10 @@ function readVersion(): string {
 
 /**
  * Run the public dsh command-line interface.
- * @param argv - user arguments after the executable name.
  * @returns a promise that settles when the selected command mode finishes.
  */
-export async function runCli(argv: string[] = process.argv.slice(2)): Promise<void> {
-  const invocation = parseDshArgs(argv, readVersion())
+export async function runCli(): Promise<void> {
+  const invocation = parseDshArgs(process.argv.slice(2), readVersion())
 
   switch (invocation.mode) {
     case 'profile': {
@@ -57,7 +55,6 @@ export async function runCli(argv: string[] = process.argv.slice(2)): Promise<vo
   }
 }
 
-const invokedPath = process.argv[1]
-if (invokedPath !== undefined && import.meta.url === pathToFileURL(resolve(invokedPath)).href) {
+if (import.meta.main) {
   await runCli()
 }

+ 20 - 1
apps/cli/tests/built-bin.e2e.ts

@@ -1,4 +1,4 @@
-import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
+import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
 import { tmpdir } from 'node:os'
 import { join } from 'node:path'
 import { createInterface } from 'node:readline'
@@ -602,6 +602,25 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
     }
   })
 
+  it.skipIf(process.platform === 'win32')('runs through an installed-style symlink', async () => {
+    const installation = mkdtempSync(join(tmpdir(), 'dsh-bin-link-'))
+    const installedBin = join(installation, 'dsh')
+    symlinkSync(dshBin, installedBin)
+    try {
+      const result = await execa(process.execPath, [installedBin, '--version'], {
+        input: '',
+        timeout: SPAWN_TIMEOUT_MS,
+        killSignal: 'SIGKILL',
+        reject: false,
+      })
+      expect(result.exitCode).toBe(0)
+      expect(result.stdout).toBe(cliVersion)
+      expect(result.stderr).toBe('')
+    } finally {
+      rmSync(installation, { recursive: true, force: true })
+    }
+  })
+
   it('fails loud on a nonexistent profile with the plugin-command hint', async () => {
     const home = mkdtempSync(join(tmpdir(), 'dsh-missing-profile-'))
     try {

+ 1 - 1
apps/cli/tsdown.config.ts

@@ -14,5 +14,5 @@ export default defineConfig({
   target: 'es2024',
   fixedExtension: false,
   dts: false,
-  clean: false,
+  clean: ['lib/*.js'],
 })

+ 3 - 14
packages/subprocess/subprocess-local/src/bin.ts

@@ -1,32 +1,21 @@
 /** Thin executable/importable entry for the provider-private runner core. */
 
-import { resolve } from 'node:path'
-import { pathToFileURL } from 'node:url'
 import { consumeRunnerSelection } from './runner-launch.ts'
 import { reportSpawnRunnerFailure, runSpawnRunner } from './spawn-runner.ts'
 
 /**
  * Run a selector already removed by a packaging bootstrap.
  * @param selection - private runner selector or Linux launch-request locator.
- * @param argv - private runner arguments beginning with the target delimiter.
  */
-export async function runSelectedSubprocessRunner(
-  selection: string,
-  argv: readonly string[] = process.argv.slice(2),
-): Promise<void> {
+export async function runSelectedSubprocessRunner(selection: string): Promise<void> {
   try {
-    await runSpawnRunner(selection, argv)
+    await runSpawnRunner(selection, process.argv.slice(2))
   } catch (error) {
     await reportSpawnRunnerFailure(selection, error)
   }
 }
 
-function isExecutedEntry(): boolean {
-  const entry = process.argv[1]
-  return entry !== undefined && pathToFileURL(resolve(entry)).href === import.meta.url
-}
-
-if (isExecutedEntry()) {
+if (import.meta.main) {
   const selection = consumeRunnerSelection()
   if (selection === undefined) {
     process.exitCode = 127

+ 3 - 2
scripts/build-exe-for-python-sdk.spec.ts

@@ -38,6 +38,7 @@ describe('Python runtime executable builder CLI', () => {
 
     expect(existsSync(resolve(root, 'apps/cli/src/runtime-bootstrap.ts'))).toBe(false)
     expect(cliConfig).not.toContain('runtime-bootstrap')
+    expect(cliConfig).toContain("clean: ['lib/*.js']")
     expect(cliTsconfig).not.toContain('packages/subprocess/subprocess-local')
     expect(cliManifest.dependencies).not.toHaveProperty('@deepseek-ai/dsh-subprocess-local')
     expect(cliManifest.devDependencies).toHaveProperty('@deepseek-ai/dsh-subprocess-local')
@@ -59,8 +60,8 @@ describe('Python runtime executable builder CLI', () => {
     expect(result.status).toBe(0)
     expect(result.stdout).toContain(`${process.execPath} C:\\tools\\pnpm.cjs run verify-runtime-closure`)
     expect(result.stdout).toContain(`${process.execPath} C:\\tools\\pnpm.cjs --filter dsh-python-runtime-closure deploy`)
-    expect(result.stdout).toContain('python/sdk-runtime/runtime-bootstrap.mjs')
-    expect(result.stdout).toContain('runtime/node/runtime-bootstrap.mjs')
+    expect(result.stdout).not.toContain(resolve(root, 'python/sdk-runtime/runtime-bootstrap.mjs'))
+    expect(result.stdout).toContain('"bin":"runtime-bootstrap.mjs"')
     expect(result.stdout).toContain(`${process.execPath} C:\\tools\\pnpm.cjs exec pkg`)
     expect(result.stdout).not.toMatch(/pnpm\.cmd/i)
   })

+ 0 - 19
scripts/build-exe-for-python-sdk.ts

@@ -17,8 +17,6 @@ const root = resolve(import.meta.dirname, '..')
 
 /** The closure manifest whose dependencies define the executable. */
 const DEPLOY_ROOT_PACKAGE = 'dsh-python-runtime-closure'
-/** The Python runtime-owned source staged as the single-file entry. */
-const ENTRY_SOURCE = 'python/sdk-runtime/runtime-bootstrap.mjs'
 /** The sole executable entry inside the deployed closure. */
 const ENTRY_BIN = 'runtime-bootstrap.mjs'
 /** Python-visible executable basename. */
@@ -308,22 +306,6 @@ class SingleExeBuild {
     }
   }
 
-  /** Copy the Python runtime-owned dispatcher into the deployed closure root. */
-  async stageRuntimeBootstrap(): Promise<void> {
-    const source = resolve(root, ENTRY_SOURCE)
-    const destination = join(this.staging, ENTRY_BIN)
-    if (!existsSync(source)) {
-      throw new Error(`build-exe-for-python-sdk: packaging bootstrap is missing at ${source}.`)
-    }
-    if (this.cli.dryRun) {
-      console.log(`build-exe-for-python-sdk: [dry-run] cp ${source} ${destination}`)
-      return
-    }
-    await copyFile(source, destination)
-    await chmod(destination, 0o755)
-    console.log(`build-exe-for-python-sdk: staged ${destination}`)
-  }
-
   /**
    * Restore direct packages that pnpm's legacy hoister places beside the deploy
    * source instead of in the target. The runtime manifest supplies every peer,
@@ -631,7 +613,6 @@ async function main(): Promise<void> {
   await pipeline.verifyClosure()
   await pipeline.build()
   await pipeline.deployStaging()
-  await pipeline.stageRuntimeBootstrap()
   await pipeline.injectPkgConfig()
   const products: string[] = []
   for (const target of cli.targets) products.push(...await pipeline.pack(target))

Some files were not shown because too many files changed in this diff