UNPKG

21.9 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
6
7var minimist = _interopDefault(require('minimist'));
8var core = require('@dprint/core');
9var fs = require('fs');
10var path = require('path');
11
12function parseCommandLineArgs(args) {
13 const argv = minimist(args, {
14 string: ["config"],
15 boolean: ["help", "version", "outputFilePaths", "outputResolvedConfig", "allowNodeModuleFiles", "duration", "init"],
16 });
17 return {
18 allowNodeModuleFiles: argv["allowNodeModuleFiles"],
19 config: getConfigFilePath(),
20 init: argv["init"],
21 showHelp: argv["h"] || argv["help"],
22 showVersion: argv["v"] || argv["version"],
23 outputFilePaths: argv["outputFilePaths"],
24 outputResolvedConfig: argv["outputResolvedConfig"],
25 duration: argv["duration"],
26 filePatterns: argv._,
27 };
28 function getConfigFilePath() {
29 return argv["c"] || argv["config"] || undefined;
30 }
31}
32
33/*! *****************************************************************************
34Copyright (c) Microsoft Corporation. All rights reserved.
35Licensed under the Apache License, Version 2.0 (the "License"); you may not use
36this file except in compliance with the License. You may obtain a copy of the
37License at http://www.apache.org/licenses/LICENSE-2.0
38
39THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
40KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
41WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
42MERCHANTABLITY OR NON-INFRINGEMENT.
43
44See the Apache Version 2.0 License for specific language governing permissions
45and limitations under the License.
46***************************************************************************** */
47
48function __awaiter(thisArg, _arguments, P, generator) {
49 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
50 return new (P || (P = Promise))(function (resolve, reject) {
51 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
52 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
53 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
54 step((generator = generator.apply(thisArg, _arguments || [])).next());
55 });
56}
57
58const projectTypeInfo = {
59 values: [{
60 name: "openSource",
61 description: "Dprint is formatting an open source project.",
62 }, {
63 name: "commercialSponsored",
64 description: "Dprint is formatting a closed source commercial project and your company sponsored dprint.",
65 }, {
66 name: "commercialDidNotSponsor",
67 description: "Dprint is formatting a closed source commercial project and you want to forever enshrine your name "
68 + "in source control for having specified this.",
69 }],
70};
71function getMissingProjectTypeDiagnostic(config) {
72 const validProjectTypes = projectTypeInfo.values.map(v => v.name);
73 if (validProjectTypes.includes(config.projectType || ""))
74 return undefined;
75 const propertyName = "projectType";
76 const largestValueName = validProjectTypes.map(s => s.length).sort().pop();
77 return {
78 propertyName,
79 message: `The "${propertyName}" field is missing. You may specify any of the following possible values in the configuration file according to your `
80 + `conscience and that will supress this warning.\n\n`
81 + projectTypeInfo.values.map(value => ` * ${value.name} ${" ".repeat(largestValueName - value.name.length)}${value.description}`).join("\n"),
82 };
83}
84
85function getPackageVersion() {
86 return "0.11.1";
87}
88
89function getHelpText(plugins) {
90 return `dprint v${getPackageVersion()}
91
92Syntax: dprint [options] [...file patterns]
93Examples: dprint
94 dprint "src/**/*.ts"
95Options:
96-h, --help Outputs this message.
97-v, --version Outputs the version of the library and plugins.
98--init Creates a dprint.config.js file in the current directory.
99-c, --config Configuration file to use (default: dprint.config.js)
100--outputFilePaths Outputs the list of file paths found for formatting without formatting the files.
101--outputResolvedConfig Outputs the resolved configuration from the configuration file.
102--duration Outputs how long the format took.
103--allowNodeModuleFiles Allows including files that have a node_modules directory in their path.
104${getPluginTexts()}`;
105 function getPluginTexts() {
106 const prefix = "Plugins:";
107 let result = prefix;
108 if (plugins.length === 0)
109 result += " [No plugins]";
110 else {
111 for (const plugin of plugins)
112 result += `\n* ${plugin.name} v${plugin.version}`;
113 }
114 return result;
115 }
116}
117
118function getVersionText(plugins) {
119 return `dprint v${getPackageVersion()}${getPluginTexts()}`;
120 function getPluginTexts() {
121 let result = "";
122 if (plugins.length === 0)
123 result += " (No plugins)";
124 else {
125 for (const plugin of plugins)
126 result += `\n${plugin.name} v${plugin.version}`;
127 }
128 return result;
129 }
130}
131
132class KillSafeFileWriter {
133 constructor(environment) {
134 this.environment = environment;
135 this.tempFiles = new Set();
136 this.crashed = false;
137 this.crashCleanup = () => {
138 this.crashed = true;
139 for (const filePath of this.tempFiles.values())
140 this.tryDeleteFileSync(filePath);
141 this.tempFiles.clear();
142 };
143 process.on("SIGINT", this.crashCleanup);
144 process.on("SIGUSR1", this.crashCleanup);
145 process.on("SIGUSR2", this.crashCleanup);
146 }
147 dispose() {
148 return __awaiter(this, void 0, void 0, function* () {
149 process.off("SIGINT", this.crashCleanup);
150 process.off("SIGUSR1", this.crashCleanup);
151 process.off("SIGUSR2", this.crashCleanup);
152 const tempFiles = Array.from(this.tempFiles.values());
153 return Promise.all(tempFiles.map(filePath => this.tryDeleteFile(filePath)));
154 });
155 }
156 writeFile(filePath, fileText) {
157 return __awaiter(this, void 0, void 0, function* () {
158 const tempFilePath = this.getTempFileName(filePath);
159 this.tempFiles.add(tempFilePath);
160 yield this.environment.writeFile(tempFilePath, fileText);
161 if (this.crashed) {
162 this.tryDeleteFileSync(tempFilePath);
163 return;
164 }
165 yield this.environment.rename(tempFilePath, filePath);
166 this.tempFiles.delete(tempFilePath);
167 });
168 }
169 tryDeleteFile(filePath) {
170 return __awaiter(this, void 0, void 0, function* () {
171 try {
172 yield this.environment.unlink(filePath);
173 }
174 catch (_a) {
175 }
176 });
177 }
178 tryDeleteFileSync(filePath) {
179 try {
180 this.environment.unlinkSync(filePath);
181 }
182 catch (_a) {
183 }
184 }
185 getTempFileName(filePath) {
186 return filePath + ".dprint_temp";
187 }
188}
189
190function throwError(message) {
191 throw getError(message);
192}
193function getError(message) {
194 return new Error(`[dprint]: ${message}`);
195}
196
197function readFile(filePath) {
198 return new Promise((resolve, reject) => {
199 fs.readFile(filePath, { encoding: "utf8" }, (err, text) => {
200 if (err)
201 reject(err);
202 else
203 resolve(text);
204 });
205 });
206}
207function writeFile(filePath, text) {
208 return new Promise((resolve, reject) => {
209 fs.writeFile(filePath, text, { encoding: "utf8" }, err => {
210 if (err)
211 reject(err);
212 else
213 resolve();
214 });
215 });
216}
217function rename(oldFilePath, newFilePath) {
218 return new Promise((resolve, reject) => {
219 fs.rename(oldFilePath, newFilePath, err => {
220 if (err)
221 reject(err);
222 else
223 resolve();
224 });
225 });
226}
227function exists(fileOrDirPath) {
228 return new Promise((resolve, reject) => {
229 try {
230 fs.exists(fileOrDirPath, result => {
231 resolve(result);
232 });
233 }
234 catch (err) {
235 reject(err);
236 }
237 });
238}
239function unlink(filePath) {
240 return new Promise((resolve, reject) => {
241 fs.unlink(filePath, err => {
242 if (err)
243 reject(err);
244 else
245 resolve();
246 });
247 });
248}
249function unlinkSync(filePath) {
250 fs.unlinkSync(filePath);
251}
252
253function resolveConfigFile(filePath, environment) {
254 return __awaiter(this, void 0, void 0, function* () {
255 const resolvedFilePath = resolveConfigFilePath(filePath, environment);
256 return {
257 filePath: resolvedFilePath,
258 config: yield getConfig(),
259 };
260 function getConfig() {
261 var _a, _b;
262 return __awaiter(this, void 0, void 0, function* () {
263 let config;
264 try {
265 try {
266 config = yield environment.require(resolvedFilePath);
267 }
268 catch (err) {
269 if (yield environment.exists(resolvedFilePath))
270 return throwError(`Error loading configuration file '${resolvedFilePath}'.\n\n${err}`);
271 else if (filePath == null) {
272 return throwError(`Could not find configuration file at '${resolvedFilePath}'. `
273 + `Did you mean to create one (dprint --init) or specify a --config option?\n\n`
274 + err);
275 }
276 else {
277 return throwError(`Could not find specified configuration file at '${resolvedFilePath}'. Did you mean to create it?\n\n` + err);
278 }
279 }
280 if (typeof config !== "object" || typeof config.config !== "object")
281 return throwError(`Expected an object to be exported on the 'config' named export of the configuration at '${resolvedFilePath}'.`);
282 else
283 return config.config;
284 }
285 catch (err) {
286 const plugins = (_b = (_a = config) === null || _a === void 0 ? void 0 : _a.config) === null || _b === void 0 ? void 0 : _b.plugins;
287 if (plugins instanceof Array)
288 plugins.forEach(p => { var _a, _b; return (_b = (_a = p) === null || _a === void 0 ? void 0 : _a.dispose) === null || _b === void 0 ? void 0 : _b.call(_a); });
289 throw err;
290 }
291 });
292 }
293 });
294}
295function resolveConfigFilePath(filePath, environment) {
296 return environment.resolvePath(filePath || "dprint.config.js");
297}
298
299function runCli(args, environment) {
300 return __awaiter(this, void 0, void 0, function* () {
301 const options = parseCommandLineArgs(args);
302 yield runCliWithOptions(options, environment);
303 });
304}
305function runCliWithOptions(options, environment) {
306 return __awaiter(this, void 0, void 0, function* () {
307 const startDate = new Date();
308 if (options.showHelp) {
309 environment.log(getHelpText(yield safeGetPlugins()));
310 return;
311 }
312 else if (options.showVersion) {
313 environment.log(getVersionText(yield safeGetPlugins()));
314 return;
315 }
316 else if (options.init) {
317 yield createConfigFile(environment);
318 return;
319 }
320 const { unresolvedConfiguration, configFilePath, plugins } = yield getUnresolvedConfigAndPlugins();
321 try {
322 yield runCliWithPlugins();
323 }
324 finally {
325 plugins.forEach(p => { var _a, _b; return (_b = (_a = p).dispose) === null || _b === void 0 ? void 0 : _b.call(_a); });
326 }
327 if (options.duration) {
328 const durationInSeconds = ((new Date()).getTime() - startDate.getTime()) / 1000;
329 environment.log(`Duration: ${durationInSeconds.toFixed(2)}s`);
330 }
331 function runCliWithPlugins() {
332 return __awaiter(this, void 0, void 0, function* () {
333 const globalConfig = resolveGlobalConfigurationInternal();
334 updatePluginsWithConfiguration();
335 const filePaths = yield getFilePaths();
336 if (options.outputResolvedConfig) {
337 outputResolvedConfiguration();
338 return;
339 }
340 else if (options.outputFilePaths) {
341 if (filePaths.length > 0) {
342 for (const filePath of filePaths)
343 environment.log(filePath);
344 }
345 else {
346 environment.log("Found 0 files.");
347 }
348 return;
349 }
350 const promises = [];
351 const killSafeFileWriter = new KillSafeFileWriter(environment);
352 try {
353 for (const filePath of filePaths) {
354 const promise = environment.readFile(filePath).then(fileText => {
355 const result = core.formatFileText({
356 filePath,
357 fileText,
358 plugins,
359 });
360 return result === fileText ? Promise.resolve() : killSafeFileWriter.writeFile(filePath, result);
361 }).catch(err => {
362 const errorText = err.toString().replace("[dprint]: ", "");
363 environment.error(`Error formatting file: ${filePath}\n\n${errorText}`);
364 });
365 promises.push(promise);
366 }
367 yield Promise.all(promises);
368 }
369 finally {
370 yield killSafeFileWriter.dispose();
371 }
372 function updatePluginsWithConfiguration() {
373 for (const plugin of plugins) {
374 plugin.initialize({
375 environment,
376 globalConfig,
377 });
378 for (const diagnostic of plugin.getConfigurationDiagnostics())
379 warnForConfigurationDiagnostic(diagnostic);
380 }
381 }
382 function outputResolvedConfiguration() {
383 environment.log(getText());
384 function getText() {
385 let text = `Global configuration: ${prettyPrintAsJson(globalConfig)}`;
386 for (const plugin of plugins)
387 text += `\n${plugin.name}: ${prettyPrintAsJson(plugin.getConfiguration())}`;
388 return text;
389 }
390 function prettyPrintAsJson(obj) {
391 const numSpaces = 2;
392 return JSON.stringify(obj, null, numSpaces);
393 }
394 }
395 });
396 }
397 function resolveGlobalConfigurationInternal() {
398 const missingProjectTypeDiagnostic = getMissingProjectTypeDiagnostic(unresolvedConfiguration);
399 const configResult = core.resolveConfiguration(getUnresolvedConfigStrippedOfCliSpecificConfig());
400 for (const diagnostic of configResult.diagnostics)
401 warnForConfigurationDiagnostic(diagnostic);
402 if (missingProjectTypeDiagnostic)
403 warnForConfigurationDiagnostic(missingProjectTypeDiagnostic);
404 return configResult.config;
405 function getUnresolvedConfigStrippedOfCliSpecificConfig() {
406 const obj = Object.assign({}, unresolvedConfiguration);
407 delete obj.excludes;
408 delete obj.includes;
409 return obj;
410 }
411 }
412 function getFilePaths() {
413 return __awaiter(this, void 0, void 0, function* () {
414 const isInNodeModules = /[\/|\\]node_modules[\/|\\]/i;
415 const allFilePaths = yield environment.glob(getFileGlobs());
416 return options.allowNodeModuleFiles ? allFilePaths : allFilePaths.filter(filePath => !isInNodeModules.test(filePath));
417 function getFileGlobs() {
418 return [...getIncludes(), ...getExcludes()];
419 function getIncludes() {
420 if (options.filePatterns.length > 0) {
421 if (!options.outputFilePaths && unresolvedConfiguration.includes && unresolvedConfiguration.includes.length > 0)
422 environment.warn("Ignoring the configuration file's includes because file patterns were provided to the command line.");
423 return options.filePatterns;
424 }
425 return unresolvedConfiguration.includes || [];
426 }
427 function getExcludes() {
428 if (!unresolvedConfiguration.excludes)
429 return [];
430 return unresolvedConfiguration.excludes.map(pattern => {
431 if (pattern.startsWith("!"))
432 return pattern;
433 return "!" + pattern;
434 });
435 }
436 }
437 });
438 }
439 function warnForConfigurationDiagnostic(diagnostic) {
440 environment.warn(`[${environment.basename(configFilePath)}]: ${diagnostic.message}`);
441 }
442 function safeGetPlugins() {
443 return __awaiter(this, void 0, void 0, function* () {
444 try {
445 return (yield getUnresolvedConfigAndPlugins()).plugins;
446 }
447 catch (err) {
448 return [];
449 }
450 });
451 }
452 function getUnresolvedConfigAndPlugins() {
453 return __awaiter(this, void 0, void 0, function* () {
454 const { config: unresolvedConfiguration, filePath: configFilePath } = yield resolveConfigFile(options.config, environment);
455 return {
456 unresolvedConfiguration,
457 configFilePath,
458 plugins: unresolvedConfiguration.plugins || [],
459 };
460 });
461 }
462 });
463}
464function createConfigFile(environment) {
465 return __awaiter(this, void 0, void 0, function* () {
466 const filePath = resolveConfigFilePath(undefined, environment);
467 if (yield environment.exists(filePath)) {
468 environment.warn(`Skipping initialization because a configuration file already exists at: ${filePath}`);
469 return;
470 }
471 environment.writeFile(filePath, getDefaultConfigFileText());
472 environment.log(`Created ${filePath}`);
473 function getDefaultConfigFileText() {
474 return `// @ts-check
475const { TypeScriptPlugin } = require("dprint-plugin-typescript");
476const { JsoncPlugin } = require("dprint-plugin-jsonc");
477
478/** @type { import("dprint").Configuration } */
479module.exports.config = {
480 projectType: "openSource",
481 plugins: [
482 new TypeScriptPlugin({
483 }),
484 new JsoncPlugin({
485 indentWidth: 2,
486 }),
487 ],
488 includes: [
489 "**/*.{ts,tsx,json,js,jsx}",
490 ],
491};
492`;
493 }
494 });
495}
496
497class CliEnvironment extends core.CliLoggingEnvironment {
498 constructor() {
499 super(...arguments);
500 this.fastGlob = require("fast-glob");
501 }
502 basename(fileOrDirPath) {
503 return path.basename(fileOrDirPath);
504 }
505 resolvePath(fileOrDirPath) {
506 return path.normalize(path.resolve(fileOrDirPath));
507 }
508 readFile(filePath) {
509 return readFile(filePath);
510 }
511 writeFile(filePath, text) {
512 return writeFile(filePath, text);
513 }
514 exists(filePath) {
515 return exists(filePath);
516 }
517 glob(patterns) {
518 return this.fastGlob(backSlashesToForward(patterns), {
519 absolute: true,
520 cwd: path.resolve("."),
521 });
522 }
523 require(filePath) {
524 return new Promise((resolve, reject) => {
525 try {
526 resolve(require(filePath));
527 }
528 catch (err) {
529 reject(err);
530 }
531 });
532 }
533 rename(oldFilePath, newFilePath) {
534 return rename(oldFilePath, newFilePath);
535 }
536 unlink(filePath) {
537 return unlink(filePath);
538 }
539 unlinkSync(filePath) {
540 unlinkSync(filePath);
541 }
542}
543function backSlashesToForward(patterns) {
544 return patterns.map(p => p.replace(/\\/g, "/"));
545}
546
547exports.CliEnvironment = CliEnvironment;
548exports.runCli = runCli;