182 lines
5.5 KiB
JavaScript
Executable File
182 lines
5.5 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/* eslint-disable no-console */
|
|
'use strict';
|
|
|
|
// Filer's path messes with process.cwd(), cache the real one
|
|
const cwd = process.cwd();
|
|
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const SerializableMemoryProvider = require('../tests/lib/serializable-memory-provider');
|
|
const unusedFilename = require('unused-filename');
|
|
const prettyBytes = require('pretty-bytes');
|
|
const walk = require('walk');
|
|
const meow = require('meow');
|
|
|
|
const cli = meow(`
|
|
Usage
|
|
$ fs-image <input-dir> [<output-filename>] [--verbose] [--filer path/to/filer.js]
|
|
|
|
Options
|
|
--filer, -f Specify a Filer module path to use. Defaults to current.
|
|
--verbose, -v Verbose logging
|
|
|
|
Examples
|
|
$ fs-image files/ files.json
|
|
$ fs-image files/
|
|
$ fs-image files/ existing.json --verbose
|
|
$ fs-image files/ --filer ./versions/filer-0.44.js
|
|
`, {
|
|
description: 'Create a Filer Filesystem Image from a directory',
|
|
flags: {
|
|
verbose: {
|
|
type: 'boolean',
|
|
alias: 'v',
|
|
default: false
|
|
},
|
|
filer: {
|
|
type: 'string',
|
|
alias: 'f'
|
|
}
|
|
}
|
|
});
|
|
|
|
// Get arg list, make sure we get a dir path argument
|
|
cli.flags.app = cli.input.slice(1);
|
|
if(!(cli.input && cli.input.length >= 1)) {
|
|
console.error('Specify a directory path to use as the image source');
|
|
process.exit(1);
|
|
}
|
|
const dirPath = path.normalize(cli.input[0]);
|
|
const exportFilePath = cli.input[1] ? path.normalize(cli.input[1]) : null;
|
|
|
|
const log = msg => {
|
|
if(cli.flags.verbose) {
|
|
console.log(msg);
|
|
}
|
|
};
|
|
|
|
// Load the version of Filer specified, or use current version in tree (default).
|
|
let filerModulePath = cli.flags.filer ? path.resolve(cwd, cli.flags.filer) : '../';
|
|
|
|
// Make sure we have an existing dir as our root
|
|
fs.stat(dirPath, (err, stats) => {
|
|
if(err || !(stats && stats.isDirectory())) {
|
|
console.error(`Expected existing directory for dirpath: ${dirPath}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
let Filer;
|
|
try {
|
|
Filer = require(filerModulePath);
|
|
log(`Using Filer module at path ${filerModulePath}`);
|
|
} catch(e) {
|
|
console.error(`Unable to load Filer module at ${filerModulePath}: ${e.message}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
// Create a filer instance with serializable provider, and start walking dir path
|
|
const provider = new SerializableMemoryProvider();
|
|
const filer = new Filer.FileSystem({provider: provider});
|
|
const walker = walk.walk(dirPath);
|
|
|
|
// Convert a filesystem path into a Filer path, rooted in /
|
|
const toFilerPath = fsPath => path.join('/', fsPath.replace(dirPath, ''));
|
|
|
|
// Write a file to Filer, including various metadata from the file node
|
|
const filerWriteFile = (filerPath, stats, data, callback) => {
|
|
const error = err => console.error(`[Filer] ${err}`);
|
|
// Convert date to ms
|
|
const toDateMS = d => (new Date(d)).getTime();
|
|
|
|
const mode = stats.mode;
|
|
const atime = toDateMS(stats.atime);
|
|
const mtime = toDateMS(stats.mtime);
|
|
const uid = stats.uid;
|
|
const gid = stats.gid;
|
|
|
|
filer.writeFile(filerPath, data, { mode }, (err) => {
|
|
if(err) {
|
|
error(`Error writing ${filerPath}: ${err.message}`);
|
|
return callback(err);
|
|
}
|
|
|
|
filer.utimes(filerPath, atime, mtime, err => {
|
|
if(err) {
|
|
error(`Error writing ${filerPath}: ${err.message}`);
|
|
return callback(err);
|
|
}
|
|
|
|
// Not all shipped versions of Filer had chown(), bail if not present
|
|
if(typeof filer.chown !== 'function') {
|
|
log(` File Node: mode=${mode} atime=${atime} mtime=${mtime}`);
|
|
return callback();
|
|
}
|
|
|
|
filer.chown(filerPath, stats.uid, stats.gid, err => {
|
|
if(err) {
|
|
error(`Error writing ${filerPath}: ${err.message}`);
|
|
return callback(err);
|
|
}
|
|
|
|
log(` File Node: mode=${mode} uid=${uid} gid=${gid} atime=${atime} mtime=${mtime}`);
|
|
callback();
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
// Process every file we find in dirpath
|
|
walker.on('file', (root, fileStats, next) => {
|
|
const filePath = path.join(root, fileStats.name);
|
|
const filerPath = toFilerPath(filePath);
|
|
log(`Writing file ${filePath} to ${filerPath} [${prettyBytes(fileStats.size)}]`);
|
|
|
|
fs.readFile(filePath, null, (err, data) => {
|
|
if(err) {
|
|
console.error(`Error reading file ${filePath}: ${err.message}`);
|
|
return next(err);
|
|
}
|
|
|
|
filerWriteFile(filerPath, stats, data, next);
|
|
});
|
|
});
|
|
|
|
// Process every dir we find in dirpath
|
|
walker.on('directory', (root, dirStats, next) => {
|
|
const dirPath = path.join(root, dirStats.name);
|
|
const filerPath = toFilerPath(dirPath);
|
|
|
|
log(`Creating dir ${dirPath} in ${filerPath}`);
|
|
filer.mkdir(filerPath, err => {
|
|
if(err && err.code !== 'EEXIST') {
|
|
console.error(`[Filer] Unable to create dir ${filerPath}: ${err.message}`);
|
|
return next(err);
|
|
}
|
|
next();
|
|
});
|
|
});
|
|
|
|
// When we're done processing entries, serialize filesystem to .json
|
|
walker.on('end', () => {
|
|
const writeFile = filename => {
|
|
fs.writeFile(filename, provider.export(), err => {
|
|
if(err) {
|
|
console.error(`Error writing exported filesystem: ${err.message}`);
|
|
process.exit(1);
|
|
}
|
|
console.log(`Wrote filesystem JSON to ${filename}`);
|
|
process.exit(0);
|
|
});
|
|
};
|
|
|
|
// If we have an explicit filename to use, use it. Otherwise
|
|
// generate a new one like `filesystem (2).json`
|
|
if(exportFilePath) {
|
|
writeFile(exportFilePath);
|
|
} else {
|
|
unusedFilename('filesystem.json').then(filename => writeFile(filename));
|
|
}
|
|
});
|
|
});
|