Просмотр исходного кода

Merge pull request #324 from deepseek-harness/worktree-windows-support

Fix: Windows build-path bootstrap: LF checkout, .cmd shims, tsc via node
Tianyi Cui 2 месяцев назад
Родитель
Сommit
c2a0ca1927

+ 8 - 0
.editorconfig

@@ -0,0 +1,8 @@
+# Editor-side declaration of the repo's text conventions. Pairs with
+# .gitattributes: that file pins what GIT produces (LF checkouts), this one
+# pins what EDITORS write to disk — the one path git's filters cannot reach.
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true

+ 7 - 0
.gitattributes

@@ -0,0 +1,7 @@
+# The repo's canonical text form is LF, enforced at checkout too: no smudge
+# boundary between working tree and repo, so byte-level gates (verify-*
+# comparisons, blob hashing, coverage offsets) see one form on every host.
+# If a file class ever genuinely needs CRLF in the working tree (.bat/.cmd
+# for cmd.exe), add a `*.bat text eol=crlf` override AFTER this line — the
+# in-repo form stays LF; CRLF becomes checkout-time presentation only.
+* text=auto eol=lf

+ 24 - 1
.github/workflows/ci.yml

@@ -160,6 +160,29 @@ jobs:
       - name: Run complete keyless Python suite
         run: uv run --python 3.10 --group test --project python/sdk pytest
 
+  # Windows build lane: install + `pnpm run build` (tsc -b + tsdown) on native
+  # Windows. Windows path/shell support is still partial, so this lane covers
+  # the build surface only — tests and gates are not run here yet. Wired into
+  # all-checks-passed so a native-Windows build regression cannot land silently.
+  windows-build:
+    runs-on: windows-2025
+    name: windows / build
+    steps:
+      - uses: actions/checkout@v6
+
+      - uses: actions/setup-node@v6
+        with:
+          node-version: ${{ env.PRIMARY_NODE_VERSION }}
+
+      - name: Enable corepack (pnpm)
+        run: corepack enable
+
+      - name: Install (immutable)
+        run: pnpm install --frozen-lockfile
+
+      - name: Build (tsc -b + tsdown)
+        run: pnpm run build
+
   # Single stable required check for branch protection: require "all checks
   # passed" instead of enumerating matrix legs whose names change as lanes and
   # node versions evolve. Every other job in THIS workflow must be listed in
@@ -171,7 +194,7 @@ jobs:
   all-checks-passed:
     name: all checks passed
     runs-on: ubuntu-latest
-    needs: [node-24, node-compat, python-sdk]
+    needs: [node-24, node-compat, python-sdk, windows-build]
     if: always()
     steps:
       - name: Fail if any needed job did not succeed

+ 6 - 1
scripts/doc-typecheck.ts

@@ -125,7 +125,12 @@ try {
   })
 
   try {
-    execFileSync('node_modules/.bin/tsc', ['-b', join(tmp, 'tsconfig.json')], { cwd: root, stdio: 'pipe' })
+    // tsc's JS entry via the current node, not the .bin shim: the extensionless
+    // shim is not spawnable on Windows (the CVE-2024-27980 class the sibling
+    // scripts hit), and the .cmd variant would need shell:true, which
+    // concatenates args UNESCAPED — a hazard for the temp project path. The JS
+    // entry behaves identically on every platform.
+    execFileSync(process.execPath, ['node_modules/typescript/bin/tsc', '-b', join(tmp, 'tsconfig.json')], { cwd: root, stdio: 'pipe' })
   } catch (error: unknown) {
     const failed = error as { stdout?: Buffer; stderr?: Buffer }
     const out = `${failed.stdout?.toString() ?? ''}${failed.stderr?.toString() ?? ''}`

+ 10 - 2
scripts/install-lefthook.mjs

@@ -6,8 +6,16 @@ import { join } from 'node:path'
 const git = spawnSync('git', ['rev-parse', '--git-dir'], { stdio: 'ignore' })
 if (git.status !== 0) process.exit(0)
 
-const lefthook = join(process.cwd(), 'node_modules', '.bin', process.platform === 'win32' ? 'lefthook.cmd' : 'lefthook')
+const isWindows = process.platform === 'win32'
+const lefthook = join(process.cwd(), 'node_modules', '.bin', isWindows ? 'lefthook.cmd' : 'lefthook')
 if (!existsSync(lefthook)) process.exit(0)
 
-const result = spawnSync(lefthook, ['install', '--force'], { stdio: 'inherit' })
+// On Windows the bin shim is a `.cmd` file, and recent Node (CVE-2024-27980)
+// refuses to launch `.cmd`/`.bat` via spawn without `shell: true` — it returns
+// `EINVAL` with a null status, which would otherwise fail postinstall. Quote
+// the path because a shell re-parses the command line and the path may contain
+// spaces. POSIX needs no shell: the extensionless shim is directly executable.
+const result = isWindows
+  ? spawnSync(`"${lefthook}"`, ['install', '--force'], { stdio: 'inherit', shell: true })
+  : spawnSync(lefthook, ['install', '--force'], { stdio: 'inherit' })
 process.exit(result.status ?? 1)

+ 8 - 1
scripts/publint-all.ts

@@ -12,6 +12,13 @@ const CONCURRENCY_ENV = 'DSH_PUBLINT_CONCURRENCY'
 const root = resolve(import.meta.dirname, '..')
 const packagesRoot = resolve(root, 'packages')
 
+// Run publint's JS CLI through the current node, not the .bin shim: the
+// extensionless shim isn't spawnable on Windows (CVE-2024-27980) and the .cmd
+// variant needs shell:true, which space-joins args UNESCAPED (DEP0190) and
+// breaks when the repo path contains spaces. The JS entry is identical on every
+// platform (`bin` is `./src/cli.js` per publint's package.json).
+const publintCli = resolve(root, 'node_modules/publint/src/cli.js')
+
 type PublintResult =
   | { path: string; status: 'passed'; stdout: string; stderr: string }
   | { path: string; status: 'failed'; stdout: string; stderr: string; message: string }
@@ -50,7 +57,7 @@ function outputText(value: unknown): string {
 
 async function runPublint(path: string): Promise<PublintResult> {
   try {
-    const { stdout, stderr } = await execFileAsync('node_modules/.bin/publint', [path], {
+    const { stdout, stderr } = await execFileAsync(process.execPath, [publintCli, path], {
       cwd: root,
       encoding: 'utf8',
       maxBuffer: 10 * 1024 * 1024,

+ 5 - 1
scripts/verify-node-next-types.ts

@@ -143,7 +143,11 @@ try {
     .join('\n')
   writeFileSync(resolve(tmp, 'index.ts'), `${imports}\n`)
 
-  execFileSync(resolve(root, 'node_modules/.bin/tsc'), ['-p', resolve(tmp, 'tsconfig.json'), '--pretty', 'false'], {
+  // tsc's JS entry via the current node, not the .bin shim: the extensionless
+  // shim isn't spawnable on Windows (CVE-2024-27980) and the .cmd variant needs
+  // shell:true, which space-joins args UNESCAPED (DEP0190) — a hazard for the
+  // temp tsconfig path. The JS entry behaves identically on every platform.
+  execFileSync(process.execPath, ['node_modules/typescript/bin/tsc', '-p', resolve(tmp, 'tsconfig.json'), '--pretty', 'false'], {
     cwd: root,
     stdio: 'pipe',
   })