1 | const path = require("path");
|
2 | const childProcess = require("child_process");
|
3 |
|
4 | const isBinaryPath = require("is-binary-path");
|
5 | const fse = require("fs-extra");
|
6 |
|
7 | const logger = require("./logger");
|
8 |
|
9 | const resources = new Map;
|
10 | const cache = new Map;
|
11 |
|
12 | const PATH_LIKE = new Set(["file", "text", "raw"]);
|
13 |
|
14 | const DEFAULT_READERS = [{
|
15 | name: "file",
|
16 | read: readFile,
|
17 | getSign: getFileSign
|
18 | }, {
|
19 | name: "text",
|
20 | read: (...args) => readFile(...args, false),
|
21 | getSign: getFileSign
|
22 | }, {
|
23 | name: "raw",
|
24 | read: (...args) => readFile(...args, true),
|
25 | getSign: getFileSign
|
26 | }, {
|
27 | name: "cmd",
|
28 | read: readCmd,
|
29 | getSign: getCmdSign
|
30 | }];
|
31 |
|
32 | DEFAULT_READERS.forEach(add);
|
33 |
|
34 | function getFileSign(source, target) {
|
35 | return JSON.stringify(target);
|
36 | }
|
37 |
|
38 | function getCmdSign(source, target) {
|
39 | return JSON.stringify([target, getDir(source)]);
|
40 | }
|
41 |
|
42 | function readFile(source, target, isBinary = isBinaryPath(target.args[0])) {
|
43 | return fse.readFile(
|
44 | path.resolve(getDir(source), target.args[0]),
|
45 | isBinary ? null : "utf8"
|
46 | );
|
47 | }
|
48 |
|
49 | function readCmd(source, target) {
|
50 | return new Promise((resolve, reject) => {
|
51 | const options = {
|
52 | cwd: getDir(source),
|
53 | stdio: [0, "pipe", 2],
|
54 | shell: true
|
55 | };
|
56 | const output = [];
|
57 | const cp = childProcess.spawn(target.args[0], options);
|
58 | cp.on("error", reject);
|
59 | cp.stdout.pipe(logger.getLogStream());
|
60 | cp.stdout.on("data", chunk => output.push(chunk));
|
61 | cp.on("close", exitCode => {
|
62 | if (exitCode) {
|
63 | reject(new Error(`Non-zero exit code: ${exitCode}`));
|
64 | return;
|
65 | }
|
66 | let data = Buffer.concat(output);
|
67 | if (target.args[1] !== "buffer") {
|
68 | data = data.toString(target.args[1]);
|
69 | }
|
70 | resolve(data);
|
71 | });
|
72 | });
|
73 | }
|
74 |
|
75 | function getDir(source) {
|
76 | return path.resolve(isPath(source) ? path.dirname(source.args[0]) : ".");
|
77 | }
|
78 |
|
79 | function add(reader) {
|
80 | resources.set(reader.name, reader);
|
81 | }
|
82 |
|
83 | function read(source, target) {
|
84 | const reader = resources.get(target.name);
|
85 | const sign = reader.getSign && reader.getSign(source, target);
|
86 | let pending;
|
87 | if (sign && cache.has(sign)) {
|
88 | pending = cache.get(sign);
|
89 | } else {
|
90 | pending = reader.read(source, target);
|
91 | if (sign) {
|
92 | cache.set(sign, pending);
|
93 | }
|
94 | }
|
95 | if (!pending.then) {
|
96 | pending = Promise.resolve(pending);
|
97 | }
|
98 | return pending;
|
99 | }
|
100 |
|
101 | function resolve(source, target) {
|
102 | if (isPath(target)) {
|
103 | target.args[0] = path.resolve(getDir(source), target.args[0]);
|
104 | }
|
105 | }
|
106 |
|
107 | function isPath(source) {
|
108 | return source && PATH_LIKE.has(source.name);
|
109 | }
|
110 |
|
111 | function has(name) {
|
112 | return resources.has(name);
|
113 | }
|
114 |
|
115 | module.exports = {add, read, resolve, PATH_LIKE, has};
|