@pipobscure/sea
Version:
Node SEA Builder
454 lines • 16.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
//@ts-ignore
const SEA = __importStar(require("node:sea"));
const ZLib = __importStar(require("node:zlib"));
const Crypto = __importStar(require("node:crypto"));
const Url = __importStar(require("node:url"));
const Path = __importStar(require("node:path"));
const VM = __importStar(require("node:vm"));
const OS = __importStar(require("node:os"));
const FS = __importStar(require("node:fs"));
const node_module_1 = require("node:module");
const node_assert_1 = __importDefault(require("node:assert"));
const VERSION = 1;
const COMPRESSION = {
NONE: 0x00,
DEFLATE: 0x01,
GZIP: 0x02,
BROTLI: 0x03,
};
const HASH = {
NONE: 0x00,
SHA1: 0x10,
SHA2: 0x20,
};
const PROTO = 'sea:';
const resolutions = JSON.parse(SEA.getAsset('resolv', 'utf8'));
const bundle = SEA.getRawAsset('bundle');
(0, node_assert_1.default)(typeof bundle === 'object', 'bundle is not an ArrayBuffer');
const blobs = extractBlobs(bundle);
function extractBlobs(data) {
const index = {};
let item = blobData(data, 0);
while (item) {
index[item.name] = item;
item = blobData(data, item.next);
}
return index;
function blobData(data, offset = 0) {
if (offset >= data.byteLength)
return null;
const header = new DataView(data, offset, 8);
const version = header.getUint8(0);
const flags = header.getUint8(1);
const nlen = header.getUint16(2);
const dlen = header.getUint32(4);
if (version !== VERSION)
throw new Error(`invalid blob-version: ${version}`);
const hashFlag = flags & 0xf0;
const compression = flags & 0x0f;
offset += 8;
const name = Buffer.from(data, offset, nlen).toString('utf-8');
offset += nlen;
let hash = null;
switch (hashFlag) {
case HASH.SHA1:
hash = data.slice(offset, offset + 20);
offset += 20;
break;
case HASH.SHA2:
hash = data.slice(offset, offset + 32);
offset += 32;
break;
}
const blob = data.slice(offset, offset + dlen);
offset += dlen;
return {
name,
blob,
hash,
compression,
next: offset,
};
}
}
function asset(urlStr, encoding) {
const url = new URL(urlStr);
if (url.protocol !== PROTO)
throw new Error(`not an asset-url ${urlStr}`);
const name = url.pathname;
const info = blobs[name];
if (!info)
throw new Error(`not an asset: ${name}`);
let data = Buffer.from(info.blob);
switch (info.compression) {
case COMPRESSION.BROTLI:
data = ZLib.brotliDecompressSync(data);
break;
case COMPRESSION.DEFLATE:
data = ZLib.inflateSync(data);
break;
case COMPRESSION.GZIP:
data = ZLib.gunzipSync(data);
break;
}
switch (info.hash?.byteLength) {
case 20: {
const actual = Crypto.createHash('sha1').update(data).digest('hex');
const wanted = Buffer.from(info.hash).toString('hex');
if (actual !== wanted)
throw new Error('invalid content');
break;
}
case 32: {
const actual = Crypto.createHash('sha256').update(data).digest('hex');
const wanted = Buffer.from(info.hash).toString('hex');
if (actual !== wanted)
throw new Error('invalid content');
break;
}
}
if (encoding)
return data.toString(encoding);
return data;
}
const modules = Object.create(null);
function resolve(parent, specifier) {
if (specifier && node_module_1.Module.isBuiltin(specifier)) {
return new URL(specifier.startsWith('node:') ? specifier : `node:${specifier}`);
}
else if (!specifier || !specifier.startsWith(PROTO)) {
specifier = resolutions[parent?.slice(PROTO.length) ?? '<main>'][specifier ?? '<main>'] ?? specifier;
return new URL(specifier, parent ?? `${PROTO}/`);
}
else {
const url = new URL(specifier);
return url;
}
}
function patch(id, exports) {
try {
// @ts-expect-error
exports.__esModule = true;
}
catch { }
try {
switch (id) {
case 'node:fs':
return patchFS(exports);
case 'node:fs/promises':
return patchFSP(exports);
case 'node:path':
return patchPath(exports);
default:
return exports;
}
}
catch {
return exports;
}
function resolve(one, two, ...rest) {
const url = two ? new URL(two, one) : new URL(one);
if (rest.length)
return resolve(url.toString(), ...rest);
return url.toString().split(/\/\/+/).join('/');
}
function readFileSync(path, ...args) {
if (!path.startsWith(PROTO))
return FS.readFileSync(path, ...args);
path = resolve(path);
const data = asset(path);
if (!data) {
throw Object.assign(new Error(`ENOENT: no such file or directory, open '${path}'`), { errno: -4058, code: 'ENOENT' });
}
else {
const opts = args[0];
const encoding = 'object' === typeof opts ? opts?.encoding : opts;
if (encoding)
return data.toString(encoding);
const copy = Buffer.allocUnsafe(data.byteLength);
data.copy(copy);
return copy;
}
}
function readDir(path, opts) {
if ('string' === typeof opts)
opts = { encoding: opts };
if (opts?.withFileTypes)
throw new Error('unsupported option: withFileTypes');
path = resolve(path).slice(PROTO.length);
path = path[path.length - 1] === '/' ? path : `${path}/`;
const matches = Object.values(blobs).filter((b) => b?.name.startsWith(path) ?? false);
let contents = matches.map((b) => b?.name.slice(path.length));
contents = opts?.recursive ? contents : contents.filter((f) => !f.includes('/'));
if ('string' === typeof (opts?.encoding ?? opts)) {
switch (opts?.encoding) {
case undefined:
case null:
case 'utf8':
case 'utf-8':
return contents;
case 'buffer':
default:
return contents.map((f) => Buffer.from(f).toString(opts.encoding));
}
}
return contents;
}
function patchFS(exports) {
return Object.create(exports, {
readFileSync: {
value: readFileSync,
enumerable: true,
writable: true,
configurable: true,
},
existsSync: {
value: function existsSync(path) {
if (!path.startsWith(PROTO))
return exports.existsSync.call(this, path);
path = resolve(path).slice(PROTO.length);
return !!blobs[path];
},
enumerable: true,
writable: true,
configurable: true,
},
readdirSync: {
value: function readdirSync(path, opts) {
if (!path.startsWith(PROTO)) {
//@ts-ignore
return exports.readdirSync.call(this, path, opts);
}
return readDir(path, opts);
},
enumerable: true,
writable: true,
configurable: true,
},
readFile: {
value: function (path, opts, cb) {
if (!path.startsWith(PROTO)) {
//@ts-ignore
return exports.readFile.call(this, path, opts, cb);
}
if (!cb) {
cb = opts;
opts = undefined;
}
let data = null;
try {
data = readFileSync(path, opts);
}
catch (err) {
cb(err);
return;
}
cb(null, data);
},
enumerable: true,
writable: true,
configurable: true,
},
exists: {
value: function exists(path, cb) {
if (!path.startsWith(PROTO))
return exports.exists.call(this, path, cb);
path = resolve(path).slice(PROTO.length);
let exists = false;
try {
exists = !!blobs[path];
}
catch (err) {
cb(err, undefined);
return;
}
cb(null, exists);
},
enumerable: true,
writable: true,
configurable: true,
},
readdir: {
value: function readdir(path, opts, cb) {
if (!path.startsWith(PROTO))
return exports.readdir.call(this, path, opts, cb);
if (!cb) {
cb = opts;
opts = undefined;
}
let files = [];
try {
files = readDir(path, opts);
}
catch (err) {
cb(err, undefined);
return;
}
cb(null, files);
},
enumerable: true,
writable: true,
configurable: true,
},
});
}
function patchFSP(exports) {
return Object.create(exports, {
readFile: {
value: function readFile(path, opts) {
if (!path.startsWith(PROTO))
return exports.readFile.call(this, path, opts);
try {
return Promise.resolve(readFileSync(path, opts));
}
catch (err) {
return Promise.reject(err);
}
},
enumerable: true,
writable: true,
configurable: true,
},
readdir: {
value: function readFile(path, opts) {
if (!path.startsWith(PROTO))
return exports.readdir.call(this, path, opts);
try {
return Promise.resolve(readDir(path, opts));
}
catch (err) {
return Promise.reject(err);
}
},
enumerable: true,
writable: true,
configurable: true,
},
});
}
function patchPath(exports) {
return Object.create(exports, {
resolve: {
value: function (path, ...rest) {
if (!path.startsWith(PROTO))
return exports.resolve.call(this, path, ...rest);
return resolve(path, ...rest);
},
enumerable: true,
writable: true,
configurable: true,
},
join: {
value: function join(path, ...rest) {
if (!path.startsWith(PROTO))
return exports.join.call(this, path, ...rest);
path = [path, ...rest].join('/');
return resolve(path);
},
enumerable: true,
writable: true,
configurable: true,
},
});
}
}
function load(parent, specifier) {
const url = resolve(parent, specifier);
const id = url.toString();
if (modules[id])
return modules[id].exports;
switch (url.protocol) {
case 'node:': {
const exports = patch(id, require(id));
modules[id] = { id, exports };
return exports;
}
case 'file:': {
const exports = require(Url.fileURLToPath(url));
modules[id] = { id, exports };
return exports;
}
case PROTO:
const module = (modules[id] = { id, exports: {} });
const name = url.pathname;
switch (Path.extname(name).toLowerCase()) {
case '.json':
return (module.exports = JSON.parse(asset(id, 'utf-8')));
case '.js':
case '.cjs': {
const dirname = new URL('./', url).toString();
const exec = new VM.Script(`(function module(module,exports,require,__dirname,__filename) {\n${asset(id, 'utf-8')}\n})`, { filename: id, lineOffset: -1 }).runInThisContext();
const main = modules[resolve().toString()];
exec.call(null, module, module.exports,
//@ts-expect-error
Object.assign((specifier) => load(id, specifier), { main, addon: process.addon }), dirname, id);
return module.exports;
}
case '.node': {
const code = asset(id);
const tmpnam = Path.join(OS.tmpdir(), [crypto.randomUUID(), 'node'].join('.'));
FS.writeFileSync(tmpnam, code);
//@ts-ignore
process.dlopen(module, tmpnam);
return module.exports;
}
default:
throw new Error(`invalid code-type: ${id}`);
}
default: {
throw new Error(`invalid code resource: ${id}`);
}
}
}
//@ts-expect-error
process.addon = function addon(pkgbase) {
if (!pkgbase.startsWith('sea:'))
throw new Error('not inside sea');
const basename = pkgbase.slice(PROTO.length);
const addons = Array.from(Object.keys(blobs))
.filter((name) => name.startsWith(basename) && name.endsWith('.node'))
.sort((a, b) => a.length - b.length);
const url = new URL(addons[0], pkgbase);
return load(pkgbase, url.toString());
};
if (module === require.main)
load();
//# sourceMappingURL=runtime.js.map