@a2f0/agent-tool 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/images/build.ts +34 -7
package/package.json
CHANGED
package/src/images/build.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { initWasm, Resvg } from "@resvg/resvg-wasm";
|
|
2
2
|
import wasm from "@resvg/resvg-wasm/index_bg.wasm" with { type: "file" };
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { closeSync, constants, existsSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { composeSvg, type Canvas } from "./compose";
|
|
7
7
|
import { expand, format, SOURCE_DIRECTORY, verbatim, type ImageOutput, type ImagesConfig, type ImageTarget } from "./config";
|
|
@@ -96,6 +96,38 @@ function assertNoLinks(base: string, destination: string): void {
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Replaces a file atomically through an unpredictable, exclusively created
|
|
101
|
+
* temporary name, so no committed link can stand in for it. Sandboxed build
|
|
102
|
+
* phases, such as Xcode's, may write only their declared outputs; there the
|
|
103
|
+
* file is rewritten in place, refusing to follow a link.
|
|
104
|
+
*/
|
|
105
|
+
export function replaceFile(destination: string, bytes: Uint8Array): void {
|
|
106
|
+
const temporary = `${destination}.${randomUUID()}.tmp`;
|
|
107
|
+
let created = false;
|
|
108
|
+
try {
|
|
109
|
+
writeFileSync(temporary, bytes, { flag: "wx" });
|
|
110
|
+
created = true;
|
|
111
|
+
renameSync(temporary, destination);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (created || !["EACCES", "EPERM"].includes((error as NodeJS.ErrnoException).code ?? "")) {
|
|
114
|
+
// Best effort: the original error matters more than a temporary file left behind.
|
|
115
|
+
try { rmSync(temporary, { force: true }); } catch {}
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
// Nothing was created, and a sandbox may refuse even an attempt to remove an undeclared path.
|
|
119
|
+
const descriptor = openSync(destination, constants.O_WRONLY | constants.O_CREAT | constants.O_NOFOLLOW);
|
|
120
|
+
try {
|
|
121
|
+
// A hard link would share the file with a path outside the output, so only a sole link is rewritten.
|
|
122
|
+
const stats = fstatSync(descriptor);
|
|
123
|
+
if (!stats.isFile() || stats.nlink !== 1) throw new Error(`Refusing to rewrite ${destination}, which is not a file with a single link.`);
|
|
124
|
+
ftruncateSync(descriptor, 0);
|
|
125
|
+
// writeFileSync on a descriptor repeats short writes until every byte is written.
|
|
126
|
+
writeFileSync(descriptor, bytes);
|
|
127
|
+
} finally { closeSync(descriptor); }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
99
131
|
function directory(name: string, target: ImageTarget, out: string | undefined): string {
|
|
100
132
|
const dir = out ?? target.dir;
|
|
101
133
|
if (dir === null) throw new Error(`Image target '${name}' has no dir; pass --out <directory>.`);
|
|
@@ -146,12 +178,7 @@ export async function buildImages(root: string, config: ImagesConfig, names: str
|
|
|
146
178
|
for (const { relative, destination, bytes } of files) {
|
|
147
179
|
if (existsSync(destination) && Buffer.compare(readFileSync(destination), bytes) === 0) { unchanged.push(relative); continue; }
|
|
148
180
|
mkdirSync(path.dirname(destination), { recursive: true });
|
|
149
|
-
|
|
150
|
-
const temporary = `${destination}.${randomUUID()}.tmp`;
|
|
151
|
-
try {
|
|
152
|
-
writeFileSync(temporary, bytes, { flag: "wx" });
|
|
153
|
-
renameSync(temporary, destination);
|
|
154
|
-
} catch (error) { rmSync(temporary, { force: true }); throw error; }
|
|
181
|
+
replaceFile(destination, bytes);
|
|
155
182
|
written.push(relative);
|
|
156
183
|
}
|
|
157
184
|
return { name, dir, written, unchanged };
|