mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 08:35:07 +08:00
Fix scriptc run on Windows
- Normalize Windows paths passed through the TS7 virtual filesystem. - Emit and launch .exe outputs by default on Windows. - Add targeted regressions and a Windows GitHub Actions smoke test. Co-authored-by: Khaled Ghallab <171967089+Khaled-Ghallab@users.noreply.github.com>
This commit is contained in:
co-authored by
Khaled Ghallab
parent
4b2c775965
commit
d7b5891b4f
@@ -88,11 +88,46 @@ jobs:
|
||||
pnpm test packages/compiler/test/cc-driver.test.ts
|
||||
--testNamePattern "host-native clang static build"
|
||||
|
||||
# Exercises the installed Windows toolchain and the built CLI end to end:
|
||||
# TS7 must open its synthetic project, ambient files must resolve across
|
||||
# slash styles, and the default executable must use the .exe suffix.
|
||||
windows_cli:
|
||||
name: test (windows CLI smoke)
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
SCRIPTC_NO_CACHE: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 11
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: pnpm
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm build
|
||||
- name: Windows path regressions
|
||||
run: pnpm exec vitest run packages/compiler/test/ts7/program.test.ts packages/cli/test/paths.test.ts
|
||||
- name: Windows scriptc run smoke
|
||||
shell: pwsh
|
||||
run: |
|
||||
clang --version
|
||||
$output = & node packages/cli/dist/main.js run tests/corpus/001-hello.ts --backend c
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
$actual = ($output -join "`n").Trim()
|
||||
if ($actual -ne "hello world") {
|
||||
throw "unexpected scriptc output: $actual"
|
||||
}
|
||||
|
||||
# Aggregate gate with the pre-matrix job's name, so anything keyed on the
|
||||
# single "test" check (branch protection, badges) keeps resolving. Fails
|
||||
# unless every matrix shard and the native Linux/glibc build succeeded.
|
||||
# unless every matrix shard and both platform integration jobs succeeded.
|
||||
test:
|
||||
needs: [tests, linux_host_clang]
|
||||
needs: [tests, linux_host_clang, windows_cli]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -100,3 +135,4 @@ jobs:
|
||||
run: |
|
||||
test "${{ needs.tests.result }}" = success
|
||||
test "${{ needs.linux_host_clang.result }}" = success
|
||||
test "${{ needs.windows_cli.result }}" = success
|
||||
|
||||
@@ -5,6 +5,7 @@ import { basename, dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parseArgs } from "node:util";
|
||||
import { analyze, compile, compileC, compileLibrary, renderAll, renderCoverage, resolveProvenanceSources, setProvenanceSources } from "@scriptc/compiler";
|
||||
import { defaultExecutableName } from "./paths.js";
|
||||
|
||||
const USAGE = `scriptc — TypeScript/JavaScript to native executables (experimental)
|
||||
|
||||
@@ -226,7 +227,7 @@ async function main(): Promise<number> {
|
||||
|
||||
const outDir = values.out ? dirname(resolve(values.out)) : join(dirname(input), ".scriptc");
|
||||
const stem = basename(input).replace(/\.(ts|js|mjs|cjs|c|ll)$/, "");
|
||||
const outPath = values.out ? resolve(values.out) : join(outDir, stem);
|
||||
const outPath = values.out ? resolve(values.out) : join(outDir, defaultExecutableName(stem));
|
||||
|
||||
const build = async (): Promise<string> => {
|
||||
if (values["from-c"]) {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
/** Default executable filename for the host. Explicit --out paths stay
|
||||
* exact; only scriptc's generated default needs the Windows PE suffix. */
|
||||
export function defaultExecutableName(stem: string, platform: NodeJS.Platform = process.platform): string {
|
||||
return platform === "win32" ? `${stem}.exe` : stem;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { defaultExecutableName } from "../src/paths.js";
|
||||
|
||||
test("default executable names use the Windows PE suffix", () => {
|
||||
expect(defaultExecutableName("main", "win32")).toBe("main.exe");
|
||||
expect(defaultExecutableName("main", "linux")).toBe("main");
|
||||
expect(defaultExecutableName("main", "darwin")).toBe("main");
|
||||
});
|
||||
@@ -9,11 +9,18 @@ import { createRequire } from "node:module";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
/** tsgo uses slash-normalized file names on Windows (for SourceFile names
|
||||
* and virtual-FS callbacks), while Node's path APIs use backslashes there.
|
||||
* POSIX backslashes stay literal: they are valid filename characters. */
|
||||
export function tsgoPath(path: string, platform: NodeJS.Platform = process.platform): string {
|
||||
return platform === "win32" ? path.replaceAll("\\", "/") : path;
|
||||
}
|
||||
|
||||
/** Path of the shipped ambient declarations — the always-shipped CORE
|
||||
* (comptime/__island_eval, setTimeout). Part of EVERY program scriptc
|
||||
* builds, the project-world preflight program included. */
|
||||
export function ambientDtsPath(): string {
|
||||
return require.resolve("@scriptc/compiler/scriptc.d.ts");
|
||||
return tsgoPath(require.resolve("@scriptc/compiler/scriptc.d.ts"));
|
||||
}
|
||||
|
||||
/** Path of the shipped divergence/precision OVERRIDES (JSON.parse():
|
||||
@@ -22,7 +29,7 @@ export function ambientDtsPath(): string {
|
||||
* so a project that typechecks under its own tsc never fails preflight over
|
||||
* an override-manufactured error (checkPreflight). */
|
||||
export function overridesDtsPath(): string {
|
||||
return require.resolve("@scriptc/compiler/scriptc-overrides.d.ts");
|
||||
return tsgoPath(require.resolve("@scriptc/compiler/scriptc-overrides.d.ts"));
|
||||
}
|
||||
|
||||
/** Path of the shipped FALLBACK declarations (console, process, node:fs) —
|
||||
@@ -30,7 +37,7 @@ export function overridesDtsPath(): string {
|
||||
* With @types/node, the project's real Node types stand in and this file
|
||||
* stands down (its declaration forms would collide). */
|
||||
export function fallbackDtsPath(): string {
|
||||
return require.resolve("@scriptc/compiler/scriptc-node-fallback.d.ts");
|
||||
return tsgoPath(require.resolve("@scriptc/compiler/scriptc-node-fallback.d.ts"));
|
||||
}
|
||||
|
||||
/** True for files belonging to the adopted Node type surface: the
|
||||
|
||||
@@ -27,6 +27,7 @@ import type {
|
||||
import type { SourceFile } from "typescript/unstable/ast";
|
||||
import { CheckerFacade } from "./checker.js";
|
||||
import { enumKeyOf, ModuleKind, ModuleResolutionKind, ScriptTarget } from "./enums.js";
|
||||
import { tsgoPath } from "../shared.js";
|
||||
|
||||
/** The compiler options our createProgram accepts: TS7's CompilerOptions
|
||||
* shape (numeric enums for target/module/moduleResolution — the enums module
|
||||
@@ -110,19 +111,19 @@ export class Ts7Host {
|
||||
// string => virtual (or shadowed) hit; null => shadowed out of
|
||||
// existence; undefined => real-FS fallthrough.
|
||||
readFile: (fileName) => {
|
||||
const virtual = virtualFiles.get(fileName);
|
||||
const virtual = virtualFiles.get(tsgoPath(fileName));
|
||||
if (virtual !== undefined) return virtual;
|
||||
if (shadow === null) return undefined;
|
||||
if (shadow.hideFile(fileName)) return null;
|
||||
return shadow.readFile(fileName);
|
||||
},
|
||||
fileExists: (fileName) => {
|
||||
if (virtualFiles.has(fileName)) return true;
|
||||
if (virtualFiles.has(tsgoPath(fileName))) return true;
|
||||
if (shadow !== null && shadow.hideFile(fileName)) return false;
|
||||
return undefined;
|
||||
},
|
||||
directoryExists: () => undefined,
|
||||
realpath: (path) => (virtualFiles.has(path) ? path : undefined),
|
||||
realpath: (path) => (virtualFiles.has(tsgoPath(path)) ? path : undefined),
|
||||
getAccessibleEntries: () => undefined,
|
||||
},
|
||||
});
|
||||
@@ -130,7 +131,7 @@ export class Ts7Host {
|
||||
|
||||
/** Registers an in-memory file served to tsgo by the virtual-FS hooks. */
|
||||
addVirtualFile(path: string, content: string): void {
|
||||
this.virtualFiles.set(path, content);
|
||||
this.virtualFiles.set(tsgoPath(path), content);
|
||||
}
|
||||
|
||||
/** tsgo's own tsconfig parser (extends chains resolved server-side) — the
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { tsgoPath } from "../../src/frontend/shared.js";
|
||||
|
||||
describe("tsgo virtual filesystem paths", () => {
|
||||
test("matches slash-normalized Windows callback paths", () => {
|
||||
expect(tsgoPath("C:\\Users\\Alice\\project\\tsconfig.json", "win32"))
|
||||
.toBe("C:/Users/Alice/project/tsconfig.json");
|
||||
expect(tsgoPath("C:/Users/Alice/project/tsconfig.json", "win32"))
|
||||
.toBe("C:/Users/Alice/project/tsconfig.json");
|
||||
});
|
||||
|
||||
test("preserves backslashes that are literal POSIX filename characters", () => {
|
||||
expect(tsgoPath("/tmp/project\\name/tsconfig.json", "linux"))
|
||||
.toBe("/tmp/project\\name/tsconfig.json");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user