-
Notifications
You must be signed in to change notification settings - Fork 154
/
fs-image.js
executable file
·185 lines (158 loc) · 5.64 KB
/
fs-image.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/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 { S_IFMT } = fs.constants;
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;
// Log in verbose mode
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});
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}`);
// Mask out the type, we only care about the various permission bits
const mode = stats.mode & ~S_IFMT;
// Prefer Unix Timestamps ms
const atimeMs = stats.atimeMs;
const mtimeMs = stats.mtimeMs;
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, atimeMs, mtimeMs, 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.toString('8')} atime=${atimeMs} mtime=${mtimeMs}`);
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.toString('8')} uid=${uid} gid=${gid} atime=${atimeMs} mtime=${mtimeMs}`);
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)}]`);
// TODO: deal with symlinks...
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));
}
});
});