@aihu/compiler 0.5.0 → 0.5.2
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/README.md +9 -9
- package/bin/aihu-compile +0 -0
- package/js/postinstall.ts +134 -9
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ npm install @aihu/compiler
|
|
|
21
21
|
bun add @aihu/compiler
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
<sub><i>Auto-generated against `@aihu/compiler@0.5.
|
|
24
|
+
<sub><i>Auto-generated against `@aihu/compiler@0.5.2`.</i></sub>
|
|
25
25
|
|
|
26
26
|
<!-- END_AUTOGEN: install -->
|
|
27
27
|
|
|
@@ -32,12 +32,12 @@ bun add @aihu/compiler
|
|
|
32
32
|
|
|
33
33
|
| | |
|
|
34
34
|
|---|---|
|
|
35
|
-
| **Version** | `0.5.
|
|
35
|
+
| **Version** | `0.5.2` |
|
|
36
36
|
| **Tier** | D — Compiler — Single-File Component (.aihu) → Web Component |
|
|
37
|
-
| **Published files** |
|
|
37
|
+
| **Published files** | 4 entries |
|
|
38
38
|
| **License** | MIT |
|
|
39
39
|
|
|
40
|
-
<sub><i>Auto-generated against `@aihu/compiler@0.5.
|
|
40
|
+
<sub><i>Auto-generated against `@aihu/compiler@0.5.2`.</i></sub>
|
|
41
41
|
|
|
42
42
|
<!-- END_AUTOGEN: stats -->
|
|
43
43
|
|
|
@@ -50,7 +50,7 @@ bun add @aihu/compiler
|
|
|
50
50
|
|---|---|---|
|
|
51
51
|
| `.` | `./dist/index.js` | `—` |
|
|
52
52
|
|
|
53
|
-
<sub><i>Auto-generated against `@aihu/compiler@0.5.
|
|
53
|
+
<sub><i>Auto-generated against `@aihu/compiler@0.5.2`.</i></sub>
|
|
54
54
|
|
|
55
55
|
<!-- END_AUTOGEN: exports -->
|
|
56
56
|
|
|
@@ -62,9 +62,9 @@ bun add @aihu/compiler
|
|
|
62
62
|
**Peer dependencies:**
|
|
63
63
|
|
|
64
64
|
- `vite` — `>=5.0.0`
|
|
65
|
-
- `@aihu/css-engine` — `>=0.2.
|
|
65
|
+
- `@aihu/css-engine` — `>=0.2.3`
|
|
66
66
|
|
|
67
|
-
<sub><i>Auto-generated against `@aihu/compiler@0.5.
|
|
67
|
+
<sub><i>Auto-generated against `@aihu/compiler@0.5.2`.</i></sub>
|
|
68
68
|
|
|
69
69
|
<!-- END_AUTOGEN: deps -->
|
|
70
70
|
|
|
@@ -78,7 +78,7 @@ bun add @aihu/compiler
|
|
|
78
78
|
- [Macro Vocabulary spec](../../docs/superpowers/specs/2026-05-02-spec-macro-vocabulary.md)
|
|
79
79
|
- [Aihu framework root](../../README.md)
|
|
80
80
|
|
|
81
|
-
<sub><i>Auto-generated against `@aihu/compiler@0.5.
|
|
81
|
+
<sub><i>Auto-generated against `@aihu/compiler@0.5.2`.</i></sub>
|
|
82
82
|
|
|
83
83
|
<!-- END_AUTOGEN: see-also -->
|
|
84
84
|
|
|
@@ -89,6 +89,6 @@ bun add @aihu/compiler
|
|
|
89
89
|
|
|
90
90
|
MIT — see [LICENSE](../../LICENSE).
|
|
91
91
|
|
|
92
|
-
<sub><i>Auto-generated against `@aihu/compiler@0.5.
|
|
92
|
+
<sub><i>Auto-generated against `@aihu/compiler@0.5.2`.</i></sub>
|
|
93
93
|
|
|
94
94
|
<!-- END_AUTOGEN: license -->
|
package/bin/aihu-compile
CHANGED
|
Binary file
|
package/js/postinstall.ts
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* Resolution order (first match wins):
|
|
5
5
|
*
|
|
6
6
|
* 1. SCRIBE_SKIP_POSTINSTALL=1 → no-op, exit 0.
|
|
7
|
-
* 2. Binary already present at → no-op
|
|
8
|
-
* bin/aihu-compile<ext> OR
|
|
9
|
-
* target/release/aihu-compile<ext>
|
|
7
|
+
* 2. Binary already present at → arch-validate it; if compatible no-op
|
|
8
|
+
* bin/aihu-compile<ext> OR exit 0, if incompatible (e.g. a Linux
|
|
9
|
+
* target/release/aihu-compile<ext> ELF that leaked into the tarball on a
|
|
10
|
+
* macOS host) delete it and fall through.
|
|
10
11
|
* 3. SCRIBE_COMPILE_BIN=<path> → copy that path → bin/, exit 0.
|
|
11
12
|
* 4. GitHub Releases download → bin/aihu-compile<ext>, verify SHA256,
|
|
12
13
|
* exit 0. (arch-4 §4.3 — sidecar
|
|
@@ -42,10 +43,13 @@ import { spawnSync } from 'node:child_process'
|
|
|
42
43
|
import { createHash } from 'node:crypto'
|
|
43
44
|
import {
|
|
44
45
|
chmodSync,
|
|
46
|
+
closeSync,
|
|
45
47
|
copyFileSync,
|
|
46
48
|
existsSync,
|
|
47
49
|
mkdirSync,
|
|
50
|
+
openSync,
|
|
48
51
|
readFileSync,
|
|
52
|
+
readSync,
|
|
49
53
|
unlinkSync,
|
|
50
54
|
writeFileSync,
|
|
51
55
|
} from 'node:fs'
|
|
@@ -194,6 +198,105 @@ async function verifySha256(
|
|
|
194
198
|
return { ok: true }
|
|
195
199
|
}
|
|
196
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Inspect a binary's file-format magic bytes and (where cheaply available) its
|
|
203
|
+
* architecture field. The point is to catch a wrong-arch binary sitting on disk
|
|
204
|
+
* BEFORE we hand it to spawn() and ENOEXEC the user (see PR description: a
|
|
205
|
+
* Linux x86-64 ELF can ship inside the tarball when the publisher's machine
|
|
206
|
+
* left one in bin/, and arch-blind idempotency then traps it).
|
|
207
|
+
*
|
|
208
|
+
* Returns `null` if the file can't be read; format `'unknown'` for headers we
|
|
209
|
+
* don't recognise (e.g. shell scripts, FAT/universal Mach-O — those callers
|
|
210
|
+
* conservatively treat as compatible).
|
|
211
|
+
*/
|
|
212
|
+
function inspectBinary(
|
|
213
|
+
path: string,
|
|
214
|
+
): { format: 'elf' | 'macho' | 'macho-fat' | 'pe' | 'unknown'; arch: string | null } | null {
|
|
215
|
+
let fd: number | null = null
|
|
216
|
+
try {
|
|
217
|
+
fd = openSync(path, 'r')
|
|
218
|
+
const buf = Buffer.alloc(20)
|
|
219
|
+
const bytesRead = readSync(fd, buf, 0, 20, 0)
|
|
220
|
+
if (bytesRead < 20) return null
|
|
221
|
+
|
|
222
|
+
// ELF: 0x7F 'E' 'L' 'F'
|
|
223
|
+
if (buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46) {
|
|
224
|
+
// e_machine at offset 18 (u16, endianness per EI_DATA at offset 5).
|
|
225
|
+
const littleEndian = buf[5] === 1
|
|
226
|
+
const machine = littleEndian ? buf.readUInt16LE(18) : buf.readUInt16BE(18)
|
|
227
|
+
const arch =
|
|
228
|
+
machine === 0x3e ? 'x64' : machine === 0xb7 ? 'arm64' : machine === 0x03 ? 'ia32' : null
|
|
229
|
+
return { format: 'elf', arch }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const magic = buf.readUInt32LE(0)
|
|
233
|
+
// Mach-O 64-bit LE-on-disk magic. cputype at offset 4 (u32 LE).
|
|
234
|
+
if (magic === 0xfeedfacf) {
|
|
235
|
+
const cputype = buf.readUInt32LE(4)
|
|
236
|
+
const arch = cputype === 0x01000007 ? 'x64' : cputype === 0x0100000c ? 'arm64' : null
|
|
237
|
+
return { format: 'macho', arch }
|
|
238
|
+
}
|
|
239
|
+
// Mach-O FAT/universal (multi-arch). On-disk bytes are big-endian per spec;
|
|
240
|
+
// a little-endian read yields 0xBEBAFECA.
|
|
241
|
+
if (magic === 0xbebafeca || magic === 0xcafebabe) {
|
|
242
|
+
return { format: 'macho-fat', arch: null }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// PE (Windows): 'MZ' at offset 0. Skip detailed machine parse — Windows
|
|
246
|
+
// arch mismatches are rare and not the bug we're fixing here.
|
|
247
|
+
if (buf[0] === 0x4d && buf[1] === 0x5a) {
|
|
248
|
+
return { format: 'pe', arch: null }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return { format: 'unknown', arch: null }
|
|
252
|
+
} catch {
|
|
253
|
+
return null
|
|
254
|
+
} finally {
|
|
255
|
+
if (fd !== null) {
|
|
256
|
+
try {
|
|
257
|
+
closeSync(fd)
|
|
258
|
+
} catch {
|
|
259
|
+
/* swallow */
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function expectedFormatFor(platform: NodeJS.Platform): 'elf' | 'macho' | 'pe' | null {
|
|
266
|
+
if (platform === 'darwin') return 'macho'
|
|
267
|
+
if (platform === 'win32') return 'pe'
|
|
268
|
+
if (platform === 'linux') return 'elf'
|
|
269
|
+
return null
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Decide whether an on-disk binary is safe to keep for the current host.
|
|
274
|
+
* Returns `null` when compatible; otherwise a short reason string suitable for
|
|
275
|
+
* a warn-level log. `'unknown'` format is treated as compatible (avoid breaking
|
|
276
|
+
* exotic but legitimate setups — e.g. a shell wrapper a dev placed here).
|
|
277
|
+
*/
|
|
278
|
+
function incompatibilityReason(
|
|
279
|
+
path: string,
|
|
280
|
+
platform: NodeJS.Platform,
|
|
281
|
+
arch: string,
|
|
282
|
+
): string | null {
|
|
283
|
+
const probe = inspectBinary(path)
|
|
284
|
+
if (!probe) return null
|
|
285
|
+
if (probe.format === 'unknown') return null
|
|
286
|
+
// FAT/universal Mach-O ships multiple slices; trust it on darwin, reject elsewhere.
|
|
287
|
+
if (probe.format === 'macho-fat') {
|
|
288
|
+
return platform === 'darwin' ? null : `universal Mach-O on ${platform}`
|
|
289
|
+
}
|
|
290
|
+
const expected = expectedFormatFor(platform)
|
|
291
|
+
if (expected !== null && probe.format !== expected) {
|
|
292
|
+
return `${probe.format} binary on ${platform} (expected ${expected})`
|
|
293
|
+
}
|
|
294
|
+
if (probe.arch !== null && probe.arch !== arch) {
|
|
295
|
+
return `${probe.arch} binary on ${platform}/${arch}`
|
|
296
|
+
}
|
|
297
|
+
return null
|
|
298
|
+
}
|
|
299
|
+
|
|
197
300
|
function tryLocalBuild(pkgDir: string): boolean {
|
|
198
301
|
// Check for cargo first — quick probe without spawning a build.
|
|
199
302
|
const probe = spawnSync('cargo', ['--version'], {
|
|
@@ -247,15 +350,37 @@ async function main(): Promise<void> {
|
|
|
247
350
|
mkdirSync(binDir, { recursive: true })
|
|
248
351
|
}
|
|
249
352
|
|
|
250
|
-
// Idempotency: nothing to do if a binary is already in place at
|
|
251
|
-
// the released-asset path (bin/) or the local-build path
|
|
353
|
+
// Idempotency: nothing to do if a usable binary is already in place at
|
|
354
|
+
// either the released-asset path (bin/) or the local-build path
|
|
355
|
+
// (target/release). "Usable" means the magic bytes match the host
|
|
356
|
+
// platform/arch — without that arch probe a wrong-arch binary sitting in
|
|
357
|
+
// the tarball (e.g. a Linux ELF that leaked from the publisher's machine)
|
|
358
|
+
// short-circuits the download path and ENOEXECs the user at spawn time.
|
|
252
359
|
if (existsSync(binPath)) {
|
|
253
|
-
|
|
254
|
-
|
|
360
|
+
const reason = incompatibilityReason(binPath, platform, arch)
|
|
361
|
+
if (reason === null) {
|
|
362
|
+
info(`bin already present at ${binPath}, skipping.`)
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
warn(`existing ${binPath} is incompatible (${reason}); removing and re-acquiring.`)
|
|
366
|
+
try {
|
|
367
|
+
unlinkSync(binPath)
|
|
368
|
+
} catch (err) {
|
|
369
|
+
const detail = err instanceof Error ? err.message : String(err)
|
|
370
|
+
warn(
|
|
371
|
+
`could not remove incompatible binary at ${binPath}: ${detail}. Continuing — download will overwrite.`,
|
|
372
|
+
)
|
|
373
|
+
}
|
|
255
374
|
}
|
|
256
375
|
if (existsSync(targetReleaseBin)) {
|
|
257
|
-
|
|
258
|
-
|
|
376
|
+
const reason = incompatibilityReason(targetReleaseBin, platform, arch)
|
|
377
|
+
if (reason === null) {
|
|
378
|
+
info(`local cargo build already present at ${targetReleaseBin}, skipping.`)
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
warn(
|
|
382
|
+
`existing ${targetReleaseBin} is incompatible (${reason}); ignoring and acquiring a fresh binary.`,
|
|
383
|
+
)
|
|
259
384
|
}
|
|
260
385
|
|
|
261
386
|
// Local dev override — copy a locally built binary instead of downloading.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aihu/compiler",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
20
|
"js/postinstall.ts",
|
|
21
|
-
"bin",
|
|
22
21
|
"README.md",
|
|
23
22
|
"LICENSE"
|
|
24
23
|
],
|
|
@@ -32,7 +31,7 @@
|
|
|
32
31
|
},
|
|
33
32
|
"peerDependencies": {
|
|
34
33
|
"vite": ">=5.0.0",
|
|
35
|
-
"@aihu/css-engine": ">=0.2.
|
|
34
|
+
"@aihu/css-engine": ">=0.2.3"
|
|
36
35
|
},
|
|
37
36
|
"peerDependenciesMeta": {
|
|
38
37
|
"vite": {
|
|
@@ -43,7 +42,7 @@
|
|
|
43
42
|
}
|
|
44
43
|
},
|
|
45
44
|
"devDependencies": {
|
|
46
|
-
"@aihu/css-engine": "0.2.
|
|
45
|
+
"@aihu/css-engine": "0.2.3"
|
|
47
46
|
},
|
|
48
47
|
"description": "Single File Component (.aihu) compiler — Rust binary + JS glue.",
|
|
49
48
|
"repository": {
|