@llman-sdd/core 0.1.3 → 0.1.4
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/archive/freeze.ts +6 -4
- package/src/archive/sevenzip.ts +34 -5
- package/src/index.ts +7 -1
- package/src/templates/embedded.ts +13 -12
package/package.json
CHANGED
package/src/archive/freeze.ts
CHANGED
|
@@ -90,10 +90,12 @@ export async function runList(io: FreezeIo, sz: SevenZipPort, rootAbs: string):
|
|
|
90
90
|
if (!io.exists(archiveAbs)) {
|
|
91
91
|
return [`No freeze archive found at ./${ARCHIVE_DIR_REL}/${FREEZE_ARCHIVE_NAME}`];
|
|
92
92
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
93
|
+
// Real 7z lists file paths under their directory (`<dir>/proposal.md`);
|
|
94
|
+
// directory entries themselves are skipped by parseListNames. Derive the
|
|
95
|
+
// archived change names from the leading path segment.
|
|
96
|
+
const entries = (await sz.listEntries(archiveAbs)).map((n) => n.replace(/\/$/u, ''));
|
|
97
|
+
const top = (n: string): string => n.split('/')[0] ?? n;
|
|
98
|
+
const unique = [...new Set(entries.map(top).filter((n) => DATED_RE.test(n)))].toSorted();
|
|
97
99
|
if (unique.length === 0) {
|
|
98
100
|
return [
|
|
99
101
|
`Freeze archive ${ARCHIVE_DIR_REL}/${FREEZE_ARCHIVE_NAME} contains no archived changes`,
|
package/src/archive/sevenzip.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
1
2
|
import { mkdirSync } from 'node:fs';
|
|
2
3
|
import { basename, join } from 'node:path';
|
|
3
4
|
|
|
@@ -7,6 +8,13 @@ import { basename, join } from 'node:path';
|
|
|
7
8
|
* instance per operation; extraction target must be a fresh directory
|
|
8
9
|
* (WASM 7z refuses existing -o dirs). Spike-proven on Bun: compress / list /
|
|
9
10
|
* extract keep directory structure.
|
|
11
|
+
*
|
|
12
|
+
* Compiled single-file binaries have no on-disk 7zz.wasm (Emscripten would
|
|
13
|
+
* probe $bunfs and abort), so build-binary.ts additionally injects the wasm
|
|
14
|
+
* base64 via the literal define `process.env.LLMAN_SDD_EMBEDDED_7ZZ_WASM_B64`
|
|
15
|
+
* (define only rewrites literal member access — do not route through a
|
|
16
|
+
* variable). Unset in source/npm/Node runs → glue loads the .wasm from disk
|
|
17
|
+
* as before.
|
|
10
18
|
*/
|
|
11
19
|
import SevenZip from '7z-wasm';
|
|
12
20
|
|
|
@@ -35,14 +43,34 @@ type ModuleFactory = (opts: {
|
|
|
35
43
|
noInitialRun: boolean;
|
|
36
44
|
print?: (text: string) => void;
|
|
37
45
|
printErr?: (text: string) => void;
|
|
46
|
+
wasmBinary?: Uint8Array;
|
|
38
47
|
}) => Promise<EmscriptenModule>;
|
|
39
48
|
|
|
40
|
-
|
|
49
|
+
const WASM_MAGIC = [0x00, 0x61, 0x73, 0x6d] as const;
|
|
50
|
+
|
|
51
|
+
/** Decode a base64 wasm blob; undefined when absent or not a WASM binary.
|
|
52
|
+
* Base64 decoding itself never throws, so the magic bytes are the guard. */
|
|
53
|
+
export function resolveEmbeddedWasmB64(value: unknown): Uint8Array | undefined {
|
|
54
|
+
if (typeof value !== 'string' || value === '') return undefined;
|
|
55
|
+
const bytes = Buffer.from(value, 'base64');
|
|
56
|
+
if (bytes.length < 4 || WASM_MAGIC.some((b, i) => bytes[i] !== b)) return undefined;
|
|
57
|
+
return new Uint8Array(bytes);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Read the build-injected wasm; undefined when absent or malformed. */
|
|
61
|
+
export function embeddedWasmBinary(): Uint8Array | undefined {
|
|
62
|
+
return resolveEmbeddedWasmB64(process.env.LLMAN_SDD_EMBEDDED_7ZZ_WASM_B64);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function initModule(
|
|
66
|
+
print?: (text: string) => void,
|
|
67
|
+
wasmBinary?: Uint8Array,
|
|
68
|
+
): Promise<EmscriptenModule> {
|
|
41
69
|
const sink = (text: string): void => {
|
|
42
70
|
if (print) print(text);
|
|
43
71
|
};
|
|
44
72
|
const factory = SevenZip as unknown as ModuleFactory;
|
|
45
|
-
return factory({ noInitialRun: true, print: sink, printErr: sink });
|
|
73
|
+
return factory({ noInitialRun: true, print: sink, printErr: sink, wasmBinary });
|
|
46
74
|
}
|
|
47
75
|
|
|
48
76
|
function mount(mod: EmscriptenModule, seq: number, hostDir: string): string {
|
|
@@ -78,9 +106,10 @@ function parseListNames(lines: string[]): string[] {
|
|
|
78
106
|
}
|
|
79
107
|
|
|
80
108
|
export async function makeWasmSevenZip(): Promise<SevenZipPort> {
|
|
109
|
+
const wasmBinary = embeddedWasmBinary();
|
|
81
110
|
return {
|
|
82
111
|
async add(archivePath: string, baseDir: string, entries: string[]): Promise<void> {
|
|
83
|
-
const mod = await initModule();
|
|
112
|
+
const mod = await initModule(undefined, wasmBinary);
|
|
84
113
|
const archMount = mount(mod, 1, join(archivePath, '..'));
|
|
85
114
|
const baseMount = mount(mod, 2, baseDir);
|
|
86
115
|
mod.FS.chdir(baseMount);
|
|
@@ -90,7 +119,7 @@ export async function makeWasmSevenZip(): Promise<SevenZipPort> {
|
|
|
90
119
|
|
|
91
120
|
async listEntries(archivePath: string): Promise<string[]> {
|
|
92
121
|
const lines: string[] = [];
|
|
93
|
-
const mod = await initModule((text) => lines.push(text));
|
|
122
|
+
const mod = await initModule((text) => lines.push(text), wasmBinary);
|
|
94
123
|
const archMount = mount(mod, 1, join(archivePath, '..'));
|
|
95
124
|
const rc = mod.callMain(['l', `${archMount}/${basename(archivePath)}`]);
|
|
96
125
|
if (rc !== 0) throw new SevenZipError(`7z list failed (rc=${rc})`);
|
|
@@ -98,7 +127,7 @@ export async function makeWasmSevenZip(): Promise<SevenZipPort> {
|
|
|
98
127
|
},
|
|
99
128
|
|
|
100
129
|
async extractAll(archivePath: string, destDir: string): Promise<void> {
|
|
101
|
-
const mod = await initModule();
|
|
130
|
+
const mod = await initModule(undefined, wasmBinary);
|
|
102
131
|
const archMount = mount(mod, 1, join(archivePath, '..'));
|
|
103
132
|
mkdirSync(destDir, { recursive: true });
|
|
104
133
|
const destMount = mount(mod, 2, destDir);
|
package/src/index.ts
CHANGED
|
@@ -144,7 +144,13 @@ export {
|
|
|
144
144
|
type SpecMorphology,
|
|
145
145
|
type SpecSummary,
|
|
146
146
|
} from './report/specs.ts';
|
|
147
|
-
export {
|
|
147
|
+
export {
|
|
148
|
+
SevenZipError,
|
|
149
|
+
embeddedWasmBinary,
|
|
150
|
+
makeWasmSevenZip,
|
|
151
|
+
resolveEmbeddedWasmB64,
|
|
152
|
+
type SevenZipPort,
|
|
153
|
+
} from './archive/sevenzip.ts';
|
|
148
154
|
export {
|
|
149
155
|
ARCHIVE_DIR_REL,
|
|
150
156
|
FREEZE_ARCHIVE_NAME,
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Embedded template table for single-file binaries.
|
|
3
3
|
*
|
|
4
|
-
* Bun
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* the
|
|
4
|
+
* Bun 1.4.x offers no embedding that survives our Node >= 24 dual-runtime
|
|
5
|
+
* constraint: `with { type: "text" }` works in compiled output (probed on
|
|
6
|
+
* 1.4.2) but Node cannot load that attribute, and `assets`/`?raw`/`?asset`
|
|
7
|
+
* never land in the bundle — so templates are injected through the same
|
|
8
|
+
* build-time `define` mechanism as LLMAN_SDD_VERSION: build-binary.ts
|
|
9
|
+
* collects packages/core/templates into a root-relative path→content map and
|
|
10
|
+
* sets process.env.LLMAN_SDD_EMBEDDED_TEMPLATES to that JSON. Bun inlines
|
|
11
|
+
* define values as raw expressions — the JSON is a valid object literal, so
|
|
12
|
+
* in the compiled binary the expression IS the table object itself, while a
|
|
13
|
+
* JSON string reaches these callers through the same expression in other
|
|
14
|
+
* engines or when force-set via env. Both forms are accepted below; every
|
|
15
|
+
* non-compiled run (source, npm package, Node) leaves the define unset and
|
|
16
|
+
* falls back to the real filesystem via TEMPLATES_ROOT.
|
|
16
17
|
*/
|
|
17
18
|
import type { TemplateIo } from './skills.ts';
|
|
18
19
|
|