@okuanb/fab 0.0.0-stage → 0.1.0-rc.10

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 CHANGED
@@ -1,3 +1,15 @@
1
- # Temporary Holding Version
1
+ # FAB
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Agente y fábrica de software con runtime propio. Node instala y lanza el binario de la misma release; no hace falta instalar Bun.
4
+
5
+ ```sh
6
+ npm install -g @okuanb/fab@next
7
+ fab --version
8
+ fab tui
9
+ ```
10
+
11
+ El paquete contiene el manifest de la release con el SHA de origen y el checksum exacto de cada binario. Verifica los bytes antes de ejecutarlos, incluso si se instalaron con `--ignore-scripts`. Descarga exclusivamente desde GitHub mediante HTTPS. Binarios en `~/.cache/fab/<version>/<sha256>/`.
12
+
13
+ Linux glibc y macOS: x64/arm64. Windows: WSL. El sandbox de herramientas requiere bubblewrap en Linux; macOS no tiene sandbox equivalente implementado. Git y la configuración del proveedor se preparan por separado.
14
+
15
+ Código y documentación: https://github.com/DarkCodePE/sw-factory
package/bin/fab.mjs ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process';
3
+ import { ensureBinary } from '../runtime.mjs';
4
+ import { fileURLToPath } from 'node:url';
5
+ try {
6
+ const binary = await ensureBinary();
7
+ const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit', env: { ...process.env, FAB_NPM_PACKAGE_ROOT: fileURLToPath(new URL('../', import.meta.url)) } });
8
+ const forward = signal => () => child.kill(signal);
9
+ const interrupt = forward('SIGINT'), terminate = forward('SIGTERM');
10
+ process.on('SIGINT', interrupt); process.on('SIGTERM', terminate);
11
+ child.on('error', error => { console.error(`FAB: ${error.message}`); process.exitCode = 1; });
12
+ child.on('exit', (code, signal) => {
13
+ process.removeListener('SIGINT', interrupt); process.removeListener('SIGTERM', terminate);
14
+ if (signal) process.kill(process.pid, signal); else process.exitCode = code ?? 1;
15
+ });
16
+ } catch (error) { console.error(`FAB: ${error.message}`); process.exitCode = 1; }
package/package.json CHANGED
@@ -1,6 +1,38 @@
1
1
  {
2
2
  "name": "@okuanb/fab",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-rc.10",
4
+ "type": "module",
5
+ "description": "FAB: agente y fábrica de software con runtime propio",
6
+ "bin": {
7
+ "fab": "bin/fab.mjs"
8
+ },
9
+ "scripts": {
10
+ "postinstall": "node postinstall.mjs"
11
+ },
12
+ "files": [
13
+ "bin",
14
+ "runtime.mjs",
15
+ "postinstall.mjs",
16
+ "release-manifest.json",
17
+ "README.md"
18
+ ],
19
+ "engines": {
20
+ "node": ">=22"
21
+ },
22
+ "os": [
23
+ "linux",
24
+ "darwin"
25
+ ],
26
+ "cpu": [
27
+ "x64",
28
+ "arm64"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/DarkCodePE/sw-factory.git"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public",
36
+ "provenance": true
37
+ }
38
+ }
@@ -0,0 +1,3 @@
1
+ import { ensureBinary } from './runtime.mjs';
2
+ try { console.log(`FAB instalado: ${await ensureBinary()}`); }
3
+ catch (error) { console.error(`FAB: ${error.message}`); process.exitCode = 1; }
@@ -0,0 +1,28 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": "0.1.0-rc.10",
4
+ "tag": "v0.1.0-rc.10",
5
+ "sourceSha": "fb93a01b44db4917c5863e3d35e34eeb7b1e7f0c",
6
+ "assets": {
7
+ "fab-linux-x64": {
8
+ "filename": "fab-linux-x64",
9
+ "sha256": "f0a73a941a870840fc5fc571dc768383ad52b2011c9855dc305af235db7db502",
10
+ "bytes": 95357056
11
+ },
12
+ "fab-linux-arm64": {
13
+ "filename": "fab-linux-arm64",
14
+ "sha256": "48dfd994824fb4cd7a7e0de85db7d117300a3f535926c86fa60241f8eefdd32a",
15
+ "bytes": 94480528
16
+ },
17
+ "fab-darwin-x64": {
18
+ "filename": "fab-darwin-x64",
19
+ "sha256": "33a6bb63a10de3c4f83d5e9093bac120dd0e67c740f94bbb0a89bba92cc03c5a",
20
+ "bytes": 79986768
21
+ },
22
+ "fab-darwin-arm64": {
23
+ "filename": "fab-darwin-arm64",
24
+ "sha256": "5fa0a5df3f2110d204bc7b8598b809b04d0721b02cf4a686a5ed889ea650b0a7",
25
+ "bytes": 69704162
26
+ }
27
+ }
28
+ }
package/runtime.mjs ADDED
@@ -0,0 +1,68 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { createReadStream } from 'node:fs';
3
+ import { mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
5
+ import { dirname, join } from 'node:path';
6
+
7
+ export async function digest(path) {
8
+ const hash = createHash('sha256');
9
+ for await (const chunk of createReadStream(path)) hash.update(chunk);
10
+ return hash.digest('hex');
11
+ }
12
+ export function platformAsset(platform = process.platform, arch = process.arch) {
13
+ if (!['linux', 'darwin'].includes(platform) || !['x64', 'arm64'].includes(arch))
14
+ throw new Error(`FAB no publica binarios para ${platform}/${arch}; en Windows utiliza WSL.`);
15
+ return `fab-${platform}-${arch}`;
16
+ }
17
+ export function validateManifest(manifest) {
18
+ if (manifest.schemaVersion !== 1 || !/^\d+\.\d+\.\d+(?:-[\da-z.-]+)?$/.test(manifest.version)
19
+ || manifest.tag !== `v${manifest.version}` || !/^[a-f0-9]{40}$/.test(manifest.sourceSha)) throw new Error('Manifest de release inválido');
20
+ for (const os of ['linux', 'darwin']) for (const arch of ['x64', 'arm64']) {
21
+ const name = platformAsset(os, arch), asset = manifest.assets?.[name];
22
+ if (!asset || asset.filename !== name || !/^[a-f0-9]{64}$/.test(asset.sha256)
23
+ || !Number.isSafeInteger(asset.bytes) || asset.bytes <= 0 || asset.bytes > 512 * 1024 * 1024) throw new Error(`Artefacto inválido: ${name}`);
24
+ }
25
+ return manifest;
26
+ }
27
+ export function binaryPath(manifest, name, home = homedir()) {
28
+ return join(home, '.cache', 'fab', manifest.version, manifest.assets[name].sha256, name);
29
+ }
30
+ async function download(url, signal, fetcher) {
31
+ for (let redirects = 0; redirects <= 5; redirects++) {
32
+ const target = new URL(url);
33
+ if (target.protocol !== 'https:' || !(target.hostname === 'github.com' || target.hostname.endsWith('.githubusercontent.com')))
34
+ throw new Error('Redirección de descarga fuera de GitHub HTTPS');
35
+ const response = await fetcher(target, { signal, redirect: 'manual' });
36
+ if ([301, 302, 303, 307, 308].includes(response.status)) {
37
+ const location = response.headers.get('location'); await response.body?.cancel();
38
+ if (!location) throw new Error('Redirección sin destino');
39
+ url = new URL(location, target).href; continue;
40
+ }
41
+ if (!response.ok || !response.body) { await response.body?.cancel(); throw new Error(`Descarga de FAB falló: HTTP ${response.status}`); }
42
+ return response;
43
+ }
44
+ throw new Error('Demasiadas redirecciones');
45
+ }
46
+ export async function ensureBinary({ manifest, home = homedir(), platform = process.platform, arch = process.arch, fetcher = fetch } = {}) {
47
+ manifest ??= JSON.parse(await readFile(new URL('./release-manifest.json', import.meta.url), 'utf8'));
48
+ validateManifest(manifest);
49
+ const name = platformAsset(platform, arch), expected = manifest.assets[name], target = binaryPath(manifest, name, home);
50
+ // Revalidate cached bytes, including installations made with --ignore-scripts.
51
+ try { if ((await stat(target)).size === expected.bytes && await digest(target) === expected.sha256) return target; } catch { /* Download below. */ }
52
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
53
+ const temporary = `${target}.${randomUUID()}.tmp`, hash = createHash('sha256'); let size = 0;
54
+ try {
55
+ const response = await download(`https://github.com/DarkCodePE/sw-factory/releases/download/${manifest.tag}/${name}`, AbortSignal.timeout(180_000), fetcher);
56
+ await writeFile(temporary, '', { flag: 'wx', mode: 0o700 });
57
+ const { open } = await import('node:fs/promises'), file = await open(temporary, 'a');
58
+ try {
59
+ for await (const chunk of response.body) {
60
+ size += chunk.length;
61
+ if (size > expected.bytes) throw new Error('Artefacto supera el tamaño sellado');
62
+ hash.update(chunk); await file.writeFile(chunk);
63
+ }
64
+ } finally { await file.close(); }
65
+ if (size !== expected.bytes || hash.digest('hex') !== expected.sha256) throw new Error('Checksum SHA-256 incorrecto; no se ejecutó el artefacto');
66
+ await rename(temporary, target); return target;
67
+ } finally { await rm(temporary, { force: true }); }
68
+ }