oxygen-core
Version:
Oxygen game engine (Xenon Core for browsers)
186 lines (168 loc) • 4.71 kB
JavaScript
import fs from 'fs';
import fp from 'path';
import process from 'process';
import { WritableStreamBuffer } from 'stream-buffers';
import { mkdirp } from './utils';
function toUnixPath(path, input) {
path = fp.relative(input, path);
path = fp.normalize(path);
return path.replace(/\\/g, '/');
}
function merge(source, target) {
for (const s of source) {
const t = !!target ? target.find(i => i.name === s.name) : null;
if (!!t) {
if (!!t.file) {
t.content = s.content;
t.size = s.size;
} else if (!!t.dir) {
merge(s.content, t.content);
}
} else {
target.push(s);
}
}
}
function listSubassets(path, result = []) {
path = path.replace(/<([\w-]+)>/g, (m, n) => `node_modules/${n}`);
const ppath = `${path}/package.json`;
if (fs.existsSync(ppath) && fs.statSync(ppath).isFile()) {
const content = fs.readFileSync(ppath);
const json = JSON.parse(content);
if ('oxygen' in json && 'subassets' in json.oxygen) {
for (const p of json.oxygen.subassets) {
listSubassets(
`${path}/${p.replace(/<([\w-]+)>/g, (m, n) => `node_modules/${n}`)}`,
result
);
}
}
} else {
result.push(path);
}
return result;
}
function listDirectories(paths) {
const sources = paths
// TODO: causes bug within nested modules.
// .reduce((a, v) => listSubassets(v, a), [])
.map(p => {
p = p.replace(/<([\w-]+)>/g, (m, n) => `node_modules/${n}`);
return listDirectory(p, p);
});
const files = [];
for (const s of sources) {
merge(s, files);
}
return files;
}
function listDirectory(path, input) {
const list = fs.readdirSync(path);
if (!list) {
throw new Error(`Cannot list path: ${path}`);
}
return list.map(name => {
const fpath = `${path}/${name}`;
const fname = toUnixPath(fpath, input);
const stat = fs.statSync(fpath);
if (stat.isDirectory()) {
return {
dir: true,
name,
path: fname,
content: listDirectory(fpath, input)
};
} else {
const content = fs.readFileSync(fpath);
if (!content) {
throw new Error(`Cannot read file: ${fpath}`);
}
return {
file: true,
name,
path: fname,
size: content.byteLength,
content
};
}
});
}
function toDescriptor(files, state) {
state = state || { offset: 0 };
return files.map(item => {
const { file, dir } = item;
if (!!file) {
const { name, path, size } = item;
const offset = state.offset;
state.offset += item.size;
return {
file,
name,
path,
offset,
size
};
} else if (!!dir) {
const { name, path } = item;
return {
dir,
name,
path,
content: toDescriptor(item.content, state)
};
}
});
}
function writeFiles(stream, files) {
for (const item of files) {
if (!!item.file) {
stream.write(item.content);
item.content = null;
} else if (!!item.dir) {
writeFiles(stream, item.content);
}
}
}
export function packToBuffer(input, options = {}) {
if (!Array.isArray(input)) {
input = [ input ];
}
for (const i of input) {
if (typeof i !== 'string') {
throw new Error('One of `input` elements is not type of String!');
}
}
const files = listDirectories(input);
const descriptor = toDescriptor(files);
const descriptorBinary = Buffer.from(JSON.stringify(descriptor));
const headerBinary = Buffer.alloc(4);
const stream = new WritableStreamBuffer();
if (!stream) {
throw new Error('Cannot create output stream!');
}
headerBinary.writeUInt32BE(descriptorBinary.length | 0, 0);
stream.write(headerBinary);
stream.write(descriptorBinary);
writeFiles(stream, files);
stream.end();
return stream.getContents();
}
export function pack(input, output, options = {}) {
if (!Array.isArray(input)) {
input = [ input ];
}
for (const i of input) {
if (typeof i !== 'string') {
throw new Error('One of `input` elements is not type of String!');
}
}
if (typeof output !== 'string') {
throw new Error('`output` is not type of String!');
}
const { silent } = options;
!silent && console.log(`Packing "${input.join(';')}" into "${output}"...`);
const result = packToBuffer(input);
mkdirp(fp.dirname(output));
fs.writeFileSync(output, result, { encoding: 'binary' });
!silent && console.log('Done!');
}