-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathpost-install.js
executable file
·157 lines (128 loc) · 4.49 KB
/
post-install.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
#!/usr/bin/env node
"use strict";
const file = "package.json";
const key = "atomTestRunner";
const value = "atom-mocha";
const fs = require("fs");
const path = require("path");
process.chdir(path.join(process.cwd(), "..", ".."));
if(!fs.existsSync(file))
die("Not found");
read(file)
.catch(error => die("Not readable", error))
.then(data => {
// No update required
if(value === JSON.parse(data)[key])
return false;
// Field already defined in data
const pattern = new RegExp(`("${key}"\\s*:\\s*)(?:null|"(?:[^\\\\"]|\\.)*")`);
if(pattern.test(data)) return [
data.replace(pattern, `$1"${value}"`),
`Updated "${key}" field in ${file}.`,
];
// Insert new field in a relevant-looking spot, retaining formatting style if possible
for(const beforeKey of ["dependencies|devDependencies", "description", "version", '[^"]+']){
const pattern = new RegExp(`([\\n{,])([ \\t]*)"(${beforeKey})"([ \\t]*)(:[ \\t]*)?`);
const match = data.match(pattern);
if(match){
const [, start, indent,, beforeColon, beforeValue] = match;
const insert = `${start + indent}"${key}"`
+ beforeColon + (beforeValue || ": ")
+ `"${value}"`
+ ("," !== start ? "," : "");
const {index} = match;
const before = data.substring(0, index);
const after = data.substring(index);
return [before + insert + after];
}
}
// If preservation of style isn't possible, just shove the field wherever.
const object = JSON.parse(data);
object[key] = value;
return [JSON.stringify(object)];
})
.catch(error => die(`Could not parse ${file}`, error))
.then(result => {
if(false === result) return;
const [data, message = `Added "${key}" field to ${file}.`] = result;
// Make sure invalid data doesn't get written.
if(value !== JSON.parse(data)[key])
die("Could not update " + file);
write(file, data);
process.stdout.write(`\x1B[38;5;2m${message}\x1B[0m\n\n`);
})
.catch(error => die("Not writable", error));
/**
* Print an error message to the standard error stream, then quit.
*
* @param {String} [reason=""] - Brief description of the error.
* @param {Error} [error=null] - Possible error object preceding output
* @param {Number} [exitCode=1] - Error code to exit with.
* @private
*/
function die(reason = "", error = null, exitCode = 0){
reason = (reason || "").trim();
// ANSI escape sequences (disabled if output is redirected)
const [reset,, underline,, noUnderline, red] = process.stderr.isTTY
? [0, 1, 4, 22, 24, [31, 9, 38]].map(s => `\x1B[${ Array.isArray(s) ? s.join(";") : s}m`)
: Array.of("", 40);
if(error){
const {inspect} = require("util");
process.stderr.write(red + inspect(error) + reset + "\n\n");
}
// Underline all occurrences of target-file's name
const target = underline + file + noUnderline;
// "Not found" -> "package.json not found"
if(reason && !reason.match(file))
reason = (file + " ")
+ reason[0].toLowerCase()
+ reason.substr(1);
// Pedantic polishes
reason = reason
.replace(/(?:\r\n|\s)+/g, " ")
.replace(/^\s+|[.!]*\s*$/g, "")
.replace(/^(?!\.$)/, ": ")
.replace(file, target);
const output = `${red}Unable to finish installing Atom-Mocha${reason}${reset}
The following field must be added to your project's ${target} file:
"${key}": "${value}"
See ${underline}README.md${reset} for setup instructions.
`.replace(/^\t/gm, "");
process.stderr.write(output);
process.exit(exitCode);
}
/**
* Promise-aware version of `fs.readFile`.
*
* @param {String} filePath - File to read
* @param {Object} [options] - Options passed to `fs.readFile`
* @return {Promise} Resolves with stringified data.
* @see {@link https://nodejs.org/api/fs.html#fs_fs_readfile_file_options_callback|`fs.readFile`}
*/
function read(filePath, options){
return new Promise((resolve, reject) => {
fs.readFile(filePath, options, (error, data) => {
error
? reject(error)
: resolve(data.toString());
});
});
}
/**
* Promise-aware version of `fs.writeFile`.
*
* @param {String} filePath - File to write to
* @param {String} fileData - Data to be written
* @param {Object} [options] - Options passed to `fs.writeFile`
* @return {Promise} Resolves with input parameter for easier chaining
* @see {@link https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback|`fs.writeFile`}
*/
function write(filePath, fileData, options){
return new Promise((resolve, reject) => {
fs.writeFile(filePath, fileData, options, error => {
error
? reject(error)
: resolve(fileData);
});
});
}