UNPKG

4.99 kBJavaScriptView Raw
1#!/usr/bin/env node
2"use strict";
3
4const file = "package.json";
5const key = "atomTestRunner";
6const value = "atom-mocha";
7
8const fs = require("fs");
9const path = require("path");
10
11process.chdir(path.join(process.cwd(), "..", ".."));
12
13if(!fs.existsSync(file))
14 die("Not found");
15
16
17read(file)
18 .catch(error => die("Not readable", error))
19 .then(data => {
20
21 // No update required
22 if(value === JSON.parse(data)[key])
23 return false;
24
25 // Field already defined in data
26 const pattern = new RegExp(`("${key}"\\s*:\\s*)(?:null|"(?:[^\\\\"]|\\.)*")`);
27 if(pattern.test(data)) return [
28 data.replace(pattern, `$1"${value}"`),
29 `Updated "${key}" field in ${file}.`
30 ];
31
32 // Insert new field in a relevant-looking spot, retaining formatting style if possible
33 for(const beforeKey of ["dependencies|devDependencies", "description", "version", '[^"]+']){
34 const pattern = new RegExp(`([\\n{,])([ \\t]*)"(${beforeKey})"([ \\t]*)(:[ \\t]*)?`);
35 const match = data.match(pattern);
36 if(match){
37 // NB: APM embeds an outdated version of Node that lacks destructuring support
38 const line = match[0];
39 const start = match[1];
40 const indent = match[2];
41 const matchedKey = match[3];
42 const beforeColon = match[4];
43 const beforeValue = match[5];
44
45 const insert = `${start + indent}"${key}"`
46 + beforeColon + (beforeValue || ": ")
47 + `"${value}"`
48 + ("," !== start ? "," : "");
49 const index = match.index;
50 const before = data.substring(0, index);
51 const after = data.substring(index);
52 return [before + insert + after];
53 }
54 }
55
56 // If preservation of style isn't possible, just shove the field wherever.
57 const object = JSON.parse(data);
58 object[key] = value;
59 return [JSON.stringify(object)];
60 })
61 .catch(error => die(`Could not parse ${file}`, error))
62 .then(result => {
63 if(false === result) return;
64 const data = result[0];
65 const message = result[1] || `Added "${key}" field to ${file}.`;
66
67 // Make sure invalid data doesn't get written.
68 if(value !== JSON.parse(data)[key])
69 die("Could not update " + file);
70
71 write(file, data);
72 process.stdout.write(`\x1B[38;5;2m${message}\x1B[0m\n\n`);
73 })
74 .catch(error => die("Not writable", error));
75
76
77
78/**
79 * Print an error message to the standard error stream, then quit.
80 *
81 * @param {String} [reason=""] - Brief description of the error.
82 * @param {Error} [error=null] - Possible error object preceding output
83 * @param {Number} [exitCode=1] - Error code to exit with.
84 * @private
85 */
86function die(reason, error, exitCode){
87 error = error || null;
88 exitCode = exitCode || 0;
89
90 reason = (reason || "").trim();
91
92 // ANSI escape sequences (disabled if output is redirected)
93 let reset = "\x1B[0m";
94 let bold = "\x1B[1m";
95 let underline = "\x1B[4m";
96 let noBold = "\x1B[22m";
97 let noUnderline = "\x1B[24m";
98 let red = "\x1B[31;9;38m";
99 if(!process.stderr.isTTY)
100 reset=bold=underline=noBold=noUnderline=red = "";
101
102 if(error){
103 const inspect = require("util").inspect;
104 process.stderr.write(red + inspect(error) + reset + "\n\n");
105 }
106
107 // Underline all occurrences of target-file's name
108 const target = underline + file + noUnderline;
109
110 // "Not found" -> "package.json not found"
111 if(reason && !reason.match(file))
112 reason = (file + " ")
113 + reason[0].toLowerCase()
114 + reason.substr(1);
115
116 // Pedantic polishes
117 reason = reason
118 .replace(/(?:\r\n|\s)+/g, " ")
119 .replace(/^\s+|[.!]*\s*$/g, "")
120 .replace(/^(?!\.$)/, ": ")
121 .replace(file, target);
122
123 const output = `${red}Unable to finish installing Atom-Mocha${reason}${reset}
124
125 The following field must be added to your project's ${target} file:
126
127 "${key}": "${value}"
128
129 See ${underline}README.md${reset} for setup instructions.
130
131 `.replace(/^\t/gm, "");
132 process.stderr.write(output);
133 process.exit(exitCode);
134}
135
136
137/**
138 * Promise-aware version of `fs.readFile`.
139 *
140 * @param {String} filePath - File to read
141 * @param {Object} [options] - Options passed to `fs.readFile`
142 * @return {Promise} Resolves with stringified data.
143 * @see {@link https://nodejs.org/api/fs.html#fs_fs_readfile_file_options_callback|`fs.readFile`}
144 */
145function read(filePath, options){
146 return new Promise((resolve, reject) => {
147 fs.readFile(filePath, options, (error, data) => {
148 error
149 ? reject(error)
150 : resolve(data.toString());
151 });
152 });
153}
154
155
156/**
157 * Promise-aware version of `fs.writeFile`.
158 *
159 * @param {String} filePath - File to write to
160 * @param {String} fileData - Data to be written
161 * @param {Object} [options] - Options passed to `fs.writeFile`
162 * @return {Promise} Resolves with input parameter for easier chaining
163 * @see {@link https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback|`fs.writeFile`}
164 */
165function write(filePath, fileData, options){
166 return new Promise((resolve, reject) => {
167 fs.writeFile(filePath, fileData, options, error => {
168 error
169 ? reject(error)
170 : resolve(fileData);
171 });
172 });
173}