feat(core,tooling): Windows support — shell selection, install.ps1, win-x64 release package, Windows CI (#79)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
"node": ">=24"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "node ../../scripts/dev-prebuild.mjs && PENGUIN_HOME=\"${PENGUIN_HOME:-$HOME/.penguin/dev-data}\" tsx watch src/index.ts",
|
||||
"dev": "node ../../scripts/dev-prebuild.mjs && node ../../scripts/run-with-env.mjs PENGUIN_HOME=~/.penguin/dev-data -- tsx watch src/index.ts",
|
||||
"start": "node --disable-warning=ExperimentalWarning dist/index.js",
|
||||
"typecheck": "tsc --noEmit -p tsconfig.json",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
|
||||
@@ -262,6 +262,15 @@ export class WorkspaceFilesService {
|
||||
}
|
||||
const { dir, name } = await this.resolveWriteParent(workspace, rel);
|
||||
const file = path.join(dir, name);
|
||||
// Windows has no O_NOFOLLOW (the `?? 0` below erases it), so the atomic ELOOP guard never
|
||||
// fires there — refuse a final-segment symlink via lstat instead. Best effort (a link
|
||||
// created between this check and the open wins the race), but it closes the practical
|
||||
// "preset a symlink, overwrite an outside file by upload" escape; POSIX keeps the atomic
|
||||
// open-time guarantee.
|
||||
if (process.platform === "win32") {
|
||||
const st = await fs.lstat(file).catch(() => null);
|
||||
if (st?.isSymbolicLink()) throw badRequest("path must not be a symlink.");
|
||||
}
|
||||
// O_NOFOLLOW: open reports ELOOP if the final segment is a symlink, refusing to use it as leverage to overwrite a file outside the sandbox.
|
||||
const flags = fsc.O_WRONLY | fsc.O_CREAT | fsc.O_TRUNC | (fsc.O_NOFOLLOW ?? 0);
|
||||
let handle;
|
||||
|
||||
@@ -82,7 +82,10 @@ describe("models preset & catalog enrichment", () => {
|
||||
expect(cfgRaw).toContain('provider = "custom"');
|
||||
expect(cfgRaw).toContain('model_id = "m-inline"');
|
||||
expect(cfgRaw).not.toContain("custom/m-inline");
|
||||
expect((await stat(cfgFile)).mode & 0o777).toBe(0o600);
|
||||
// POSIX-only: Windows has no owner-only mode bits (chmod maps to the read-only attribute).
|
||||
if (process.platform !== "win32") {
|
||||
expect((await stat(cfgFile)).mode & 0o777).toBe(0o600);
|
||||
}
|
||||
// No more separate .credentials.toml / project_config.toml files.
|
||||
await expect(readFile(path.join(projectDir, ".credentials.toml"), "utf8")).rejects.toThrow();
|
||||
await expect(readFile(path.join(projectDir, "project_config.toml"), "utf8")).rejects.toThrow();
|
||||
|
||||
Reference in New Issue
Block a user