UNPKG

14.1 kBJavaScriptView Raw
1"use strict";
2/*
3 * @adonisjs/sink
4 *
5 * (c) Harminder Virk <virk@adonisjs.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10Object.defineProperty(exports, "__esModule", { value: true });
11exports.PackageJsonFile = void 0;
12const mrm_core_1 = require("mrm-core");
13const child_process_1 = require("child_process");
14const File_1 = require("../Base/File");
15/**
16 * Exposes the API to work with `package.json` file. The file is
17 * same as a standard JSON file, but with some special methods
18 * related to package file itself.
19 */
20class PackageJsonFile extends File_1.File {
21 constructor(basePath, installerOutput = 'pipe') {
22 super(basePath);
23 this.installerOutput = installerOutput;
24 /**
25 * Collection of actions to be executed on package file
26 */
27 this.actions = [];
28 /**
29 * A copy of install instructions
30 */
31 this.packages = {
32 install: [],
33 uninstall: [],
34 };
35 this.cdIn();
36 this.filePointer = (0, mrm_core_1.packageJson)();
37 this.cdOut();
38 }
39 /**
40 * Run hooks for action or uninstall action
41 */
42 runHooks(action, list, dev) {
43 if (action === 'install' && typeof this.beforeInstallHooks === 'function') {
44 this.beforeInstallHooks(list, dev);
45 }
46 else if (action === 'uninstall' && typeof this.beforeUninstallHooks === 'function') {
47 this.beforeUninstallHooks(list, dev);
48 }
49 }
50 /**
51 * Sets installation client
52 */
53 setClient(options) {
54 if (this.packageManager === 'yarn') {
55 options.yarn = true;
56 }
57 else if (this.packageManager === 'pnpm') {
58 options.pnpm = true;
59 }
60 }
61 /**
62 * Executes the installer `install` or `uninstall` action. Use
63 * `this.installerFnAsync` for async version
64 */
65 installerFn(action, list, options) {
66 if (!list.length) {
67 return;
68 }
69 this.setClient(options);
70 this.runHooks(action, list, options.dev);
71 const fn = action === 'install' ? mrm_core_1.install : mrm_core_1.uninstall;
72 return fn(list, options, (command, args) => {
73 return (0, child_process_1.spawnSync)(command, args, { stdio: this.installerOutput });
74 });
75 }
76 /**
77 * Executes the installer `install` or `uninstall` action. Use
78 * `this.installerFn` for sync version
79 */
80 installerFnAsync(action, list, options) {
81 return new Promise((resolve) => {
82 if (!list.length) {
83 resolve(undefined);
84 return;
85 }
86 this.setClient(options);
87 this.runHooks(action, list, options.dev);
88 let response;
89 const fn = action === 'install' ? mrm_core_1.install : mrm_core_1.uninstall;
90 let callbackInvoked = false;
91 fn(list, options, (command, args) => {
92 callbackInvoked = true;
93 const runner = (0, child_process_1.spawn)(command, args, { stdio: 'pipe' });
94 response = {
95 pid: runner.pid,
96 output: [],
97 stdout: Buffer.from(''),
98 stderr: Buffer.from(''),
99 status: null,
100 signal: null,
101 };
102 runner.stdout.on('data', (chunk) => {
103 response.stdout = Buffer.concat([response.stdout, chunk]);
104 });
105 runner.stderr.on('data', (chunk) => {
106 response.stderr = Buffer.concat([response.stderr, chunk]);
107 });
108 runner.on('close', (code, signal) => {
109 response.status = code;
110 response.signal = signal;
111 resolve(response);
112 });
113 });
114 if (!callbackInvoked) {
115 resolve(undefined);
116 }
117 });
118 }
119 /**
120 * Install and uninstall packages defined via `this.install`
121 * and `this.uninstall`
122 */
123 commitDependencies(installs, uninstalls) {
124 let response;
125 for (let { list, versions, dev } of installs) {
126 response = this.installerFn('install', list, { versions, dev });
127 if (response && response.status === 1) {
128 return response;
129 }
130 }
131 for (let { list, dev } of uninstalls) {
132 response = this.installerFn('uninstall', list, { dev });
133 if (response && response.status === 1) {
134 return response;
135 }
136 }
137 }
138 /**
139 * Performing uninstalling as a rollback step. Which means, this method
140 * will remove packages marked for installation.
141 */
142 rollbackDependencies(installs) {
143 let response;
144 for (let { list, dev } of installs) {
145 response = this.installerFn('uninstall', list, { dev });
146 if (response && response.status === 1) {
147 return response;
148 }
149 }
150 }
151 /**
152 * Same as `commitInstalls` but async
153 */
154 async commitDependenciesAsync(installs, uninstalls) {
155 let response;
156 for (let { list, versions, dev } of installs) {
157 response = await this.installerFnAsync('install', list, { versions, dev });
158 if (response && response.status === 1) {
159 return response;
160 }
161 }
162 for (let { list, dev } of uninstalls) {
163 response = await this.installerFnAsync('uninstall', list, { dev });
164 if (response && response.status === 1) {
165 return response;
166 }
167 }
168 }
169 /**
170 * Same as `rollbackInstalls` but async.
171 */
172 async rollbackDependenciesAsync(installs) {
173 let response;
174 for (let { list, dev } of installs) {
175 response = await this.installerFnAsync('uninstall', list, { dev });
176 if (response && response.status === 1) {
177 return response;
178 }
179 }
180 }
181 /**
182 * Commits actions defined on the given file
183 */
184 commitActions() {
185 const actions = this.getCommitActions();
186 const deleteFile = actions.find(({ action }) => action === 'delete');
187 /**
188 * In case of `delete` action. There is no point running
189 * other actions and we can simply delete the file
190 */
191 if (deleteFile) {
192 this.filePointer.delete();
193 this.cdOut();
194 return false;
195 }
196 /**
197 * Executing all actions
198 */
199 actions.forEach(({ action, body }) => {
200 if (['set', 'unset'].indexOf(action) > -1) {
201 this.filePointer[action](body.key, body.value);
202 return;
203 }
204 if (['prependScript', 'appendScript', 'setScript', 'removeScript'].indexOf(action) > -1) {
205 this.filePointer[action](body.name, body.script);
206 return;
207 }
208 });
209 /**
210 * Save the file to the disk before starting install process.
211 */
212 this.filePointer.save();
213 return true;
214 }
215 /**
216 * Rollsback actions defined on the package file
217 */
218 rollbackActions() {
219 const actions = this.getCommitActions();
220 /**
221 * Executing actions in reverse.
222 */
223 actions.forEach(({ action, body }) => {
224 if (action === 'set') {
225 this.filePointer.unset(body.key);
226 return;
227 }
228 if (action === 'setScript') {
229 this.filePointer.removeScript(body.name);
230 return;
231 }
232 if (['prependScript', 'appendScript'].indexOf(action) > -1) {
233 this.filePointer.removeScript(body.name, new RegExp(body.script));
234 return;
235 }
236 });
237 /**
238 * Write file to the disk
239 */
240 this.filePointer.save();
241 return true;
242 }
243 /**
244 * Set key/value pair in the package.json file
245 */
246 set(key, value) {
247 this.addAction('set', { key, value });
248 return this;
249 }
250 /**
251 * Set a specific client to be used
252 */
253 useClient(client) {
254 this.packageManager = client;
255 return this;
256 }
257 /**
258 * Enable/disable use of yarn
259 * @deprecated The "yarn" method is deprecated. Please use "useClient('yarn')" instead.
260 */
261 yarn(_useYarn) {
262 this.packageManager = 'yarn';
263 return this;
264 }
265 /**
266 * Unset key/value pair from the package.json file
267 */
268 unset(key) {
269 this.addAction('unset', { key });
270 return this;
271 }
272 /**
273 * Set package.json script
274 */
275 setScript(name, script) {
276 this.addAction('setScript', { name, script });
277 return this;
278 }
279 /**
280 * Append to existing package.json script
281 */
282 appendScript(name, script) {
283 this.addAction('appendScript', { name, script });
284 return this;
285 }
286 /**
287 * Prepend to existing package.json script
288 */
289 prependScript(name, script) {
290 this.addAction('prependScript', { name, script });
291 return this;
292 }
293 /**
294 * Remove existing script or remove a given action from an
295 * existing script
296 */
297 removeScript(name, script) {
298 this.addAction('removeScript', { name, script });
299 return this;
300 }
301 /**
302 * Install dependencies
303 */
304 install(dependency, version = 'latest', dev = true) {
305 this.packages.install.push({ dependency, version, dev });
306 return this;
307 }
308 /**
309 * Uninstall dependencies
310 */
311 uninstall(dependency, dev = true) {
312 this.packages.uninstall.push({ dependency, dev });
313 return this;
314 }
315 /**
316 * Remove file
317 */
318 delete() {
319 this.addAction('delete');
320 return this;
321 }
322 get(address, defaultValue) {
323 return address ? this.filePointer.get(address, defaultValue) : this.filePointer.get();
324 }
325 /**
326 * A boolean telling if the file already exists
327 */
328 exists() {
329 return this.filePointer.exists();
330 }
331 /**
332 * Returns a list of dependencies along with specific versions (if any)
333 */
334 getInstalls(dev = true) {
335 const dependencies = { versions: {}, list: [], dev };
336 return this.packages.install.reduce((result, dependency) => {
337 if (dependency.dev && dev) {
338 result.list.push(dependency.dependency);
339 if (dependency.version !== 'latest') {
340 result.versions[dependency.dependency] = dependency.version;
341 }
342 }
343 else if (!dependency.dev && !dev) {
344 result.list.push(dependency.dependency);
345 if (dependency.version !== 'latest') {
346 result.versions[dependency.dependency] = dependency.version;
347 }
348 }
349 return result;
350 }, dependencies);
351 }
352 /**
353 * Returns uninstalls list for prod or development
354 * dependencies.
355 */
356 getUninstalls(dev) {
357 const dependencies = { list: [], dev };
358 return this.packages.uninstall.reduce((result, dependency) => {
359 if (dependency.dev && dev) {
360 result.list.push(dependency.dependency);
361 }
362 else if (!dependency.dev && !dev) {
363 result.list.push(dependency.dependency);
364 }
365 return result;
366 }, dependencies);
367 }
368 /**
369 * Define a function to be called before installing dependencies
370 */
371 beforeInstall(callback) {
372 this.beforeInstallHooks = callback;
373 return this;
374 }
375 /**
376 * Define a function to be called before uninstalling dependencies
377 */
378 beforeUninstall(callback) {
379 this.beforeUninstallHooks = callback;
380 return this;
381 }
382 /**
383 * Commit mutations
384 */
385 commit() {
386 this.cdIn();
387 const success = this.commitActions();
388 if (!success) {
389 return;
390 }
391 /**
392 * Install/uninstall dependencies
393 */
394 const response = this.commitDependencies([this.getInstalls(true), this.getInstalls(false)], [this.getUninstalls(true), this.getUninstalls(false)]);
395 this.cdOut();
396 return response;
397 }
398 /**
399 * Commits async. The files are still written using synchronous
400 * API. However, the install and uninstall becomes async.
401 */
402 async commitAsync() {
403 this.cdIn();
404 const success = this.commitActions();
405 if (!success) {
406 return;
407 }
408 /**
409 * Install/uninstall dependencies
410 */
411 const response = await this.commitDependenciesAsync([this.getInstalls(true), this.getInstalls(false)], [this.getUninstalls(true), this.getUninstalls(false)]);
412 this.cdOut();
413 return response;
414 }
415 /**
416 * Rollback mutations
417 */
418 rollback() {
419 this.cdIn();
420 const success = this.rollbackActions();
421 if (!success) {
422 return;
423 }
424 /**
425 * Uninstalling installed packages
426 */
427 const response = this.rollbackDependencies([this.getInstalls(true), this.getInstalls(false)]);
428 this.cdOut();
429 return response;
430 }
431 /**
432 * Rollsback async. The files are still written using synchronous
433 * API. However, the uninstall becomes async.
434 */
435 async rollbackAsync() {
436 this.cdIn();
437 const success = this.rollbackActions();
438 if (!success) {
439 return;
440 }
441 /**
442 * Uninstalling installed packages
443 */
444 const response = await this.rollbackDependenciesAsync([
445 this.getInstalls(true),
446 this.getInstalls(false),
447 ]);
448 this.cdOut();
449 return response;
450 }
451}
452exports.PackageJsonFile = PackageJsonFile;