@oh-my-pi/hashline 16.2.7 → 16.2.8
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/CHANGELOG.md +6 -0
- package/dist/types/fs.d.ts +3 -0
- package/package.json +1 -1
- package/src/fs.ts +12 -0
- package/src/patcher.ts +12 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.2.8] - 2026-06-30
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed hashline writes preserving UTF-8 BOM bytes when the host text decoder hides the leading `U+FEFF`. ([#3867](https://github.com/can1357/oh-my-pi/issues/3867))
|
|
10
|
+
|
|
5
11
|
## [16.2.6] - 2026-06-29
|
|
6
12
|
|
|
7
13
|
### Fixed
|
package/dist/types/fs.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export declare function isNotFound(error: unknown): boolean;
|
|
|
37
37
|
export declare abstract class Filesystem {
|
|
38
38
|
/** Read the file's full text content. Throw on missing file. */
|
|
39
39
|
abstract readText(path: string): Promise<string>;
|
|
40
|
+
/** Read raw bytes for backends whose text is a direct decode of persisted bytes. */
|
|
41
|
+
readBinary?(path: string): Promise<Uint8Array | undefined>;
|
|
40
42
|
/** Validate that `path` is writable before a prepared batch starts committing. */
|
|
41
43
|
preflightWrite(_path: string, _options?: PreflightWriteOptions): Promise<void>;
|
|
42
44
|
/** Persist `content` at `path`. Returns the actual final text that was written. */
|
|
@@ -98,6 +100,7 @@ export declare class InMemoryFilesystem extends Filesystem {
|
|
|
98
100
|
*/
|
|
99
101
|
export declare class NodeFilesystem extends Filesystem {
|
|
100
102
|
readText(path: string): Promise<string>;
|
|
103
|
+
readBinary(path: string): Promise<Uint8Array>;
|
|
101
104
|
writeText(path: string, content: string): Promise<WriteResult>;
|
|
102
105
|
delete(path: string): Promise<void>;
|
|
103
106
|
move(from: string, to: string, content?: string): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/hashline",
|
|
4
|
-
"version": "16.2.
|
|
4
|
+
"version": "16.2.8",
|
|
5
5
|
"description": "Hashline: a compact, line-anchored patch language and applier. Pluggable FS/IO so it works over disk, in-memory, or any custom backend.",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
package/src/fs.ts
CHANGED
|
@@ -65,6 +65,9 @@ export abstract class Filesystem {
|
|
|
65
65
|
/** Read the file's full text content. Throw on missing file. */
|
|
66
66
|
abstract readText(path: string): Promise<string>;
|
|
67
67
|
|
|
68
|
+
/** Read raw bytes for backends whose text is a direct decode of persisted bytes. */
|
|
69
|
+
readBinary?(path: string): Promise<Uint8Array | undefined>;
|
|
70
|
+
|
|
68
71
|
/** Validate that `path` is writable before a prepared batch starts committing. */
|
|
69
72
|
async preflightWrite(_path: string, _options?: PreflightWriteOptions): Promise<void> {}
|
|
70
73
|
|
|
@@ -196,6 +199,15 @@ export class NodeFilesystem extends Filesystem {
|
|
|
196
199
|
return file.text();
|
|
197
200
|
}
|
|
198
201
|
|
|
202
|
+
async readBinary(path: string): Promise<Uint8Array> {
|
|
203
|
+
try {
|
|
204
|
+
return await fs.readFile(path);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (isNotFound(error)) throw new NotFoundError(path, error);
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
199
211
|
async writeText(path: string, content: string): Promise<WriteResult> {
|
|
200
212
|
await Bun.write(path, content);
|
|
201
213
|
return { text: content };
|
package/src/patcher.ts
CHANGED
|
@@ -148,6 +148,10 @@ function mergeWarnings(...sources: ReadonlyArray<readonly string[] | undefined>)
|
|
|
148
148
|
return out;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
function hasUtf8Bom(bytes: Uint8Array | undefined): boolean {
|
|
152
|
+
return bytes !== undefined && bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf;
|
|
153
|
+
}
|
|
154
|
+
|
|
151
155
|
function assertUniqueCanonicalPaths(prepared: readonly PreparedSection[]): void {
|
|
152
156
|
const seen = new Map<string, string>();
|
|
153
157
|
for (const entry of prepared) {
|
|
@@ -295,7 +299,8 @@ export class Patcher {
|
|
|
295
299
|
throw new Error(`MV destination is the same as ${target.path}.`);
|
|
296
300
|
}
|
|
297
301
|
|
|
298
|
-
const { bom, text } = stripBom(read.rawContent);
|
|
302
|
+
const { bom: bomFromText, text } = stripBom(read.rawContent);
|
|
303
|
+
const bom = bomFromText || (await this.#readBinaryBom(target.path));
|
|
299
304
|
const lineEnding = detectLineEnding(text);
|
|
300
305
|
const normalized = normalizeToLF(text);
|
|
301
306
|
|
|
@@ -453,6 +458,12 @@ export class Patcher {
|
|
|
453
458
|
};
|
|
454
459
|
}
|
|
455
460
|
|
|
461
|
+
async #readBinaryBom(path: string): Promise<string> {
|
|
462
|
+
if (!this.fs.readBinary) return "";
|
|
463
|
+
const bytes = await this.fs.readBinary(path);
|
|
464
|
+
return hasUtf8Bom(bytes) ? "\uFEFF" : "";
|
|
465
|
+
}
|
|
466
|
+
|
|
456
467
|
async #tryRead(path: string): Promise<{ exists: boolean; rawContent: string }> {
|
|
457
468
|
try {
|
|
458
469
|
const content = await this.fs.readText(path);
|