-
Notifications
You must be signed in to change notification settings - Fork 0
/
add-dist-header.ts
123 lines (116 loc) · 5.81 KB
/
add-dist-header.ts
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
// Add Dist Header ~~ MIT License
// Imports
import { isBinary } from 'istextorbinary';
import chalk from 'chalk';
import fs from 'fs';
import log from 'fancy-log';
import path from 'path';
import slash from 'slash';
// Types
export type Settings = {
allFiles: boolean, //add headers to text files and just copy binary files
dist: string, //output folder
extension: string | null, //rename with new file extension (with dot), example: '.css'
delimiter: string, //character separating the parts of the header comment
replaceComment: boolean, //delete the original first line comment
setVersion: boolean, //substitute occurances of "{{package.version}}" with the package.json version number
};
export type Result = {
valid: boolean, //true if the input file is a text file or if allFiles is enabled
text: boolean, //true if input file is a text file and false if binary
dist: string, //absolute path to distribution folder
header: string | null, //text prepended to output file
source: string, //input filename
file: string, //output filename
length: number | null, //number of characters in output file
size: string | null, //formatted file size, example: '1,233.70 KB'
};
export type ReporterSettings = {
quite: boolean, //suppress informational messages
};
const addDistHeader = {
prepend(filename: string, options?: Partial<Settings>): Result {
const defaults = {
allFiles: false,
dist: 'dist',
extension: null,
delimiter: '~~',
replaceComment: true,
setVersion: true,
};
const settings = { ...defaults, ...options };
if (!filename)
throw new Error('[add-dist-header] Must specify the "filename" option.');
const doctypeLine = /^<(!doctype|\?xml).*\n/i; //matches: '<!doctype html>' and '<?xml version="1.0" ?>'
const commentStyle = {
js: { start: '//! ', end: '' },
ml: { start: '<!-- ', end: ' -->' },
other: { start: '/*! ', end: ' */' },
};
const firstLine = {
js: /^(\/\/[^!].*|\/[*][^!].*[*]\/)\n/, //matches: '// ...' or '/* ... */'
ml: /^<!--.*-->\n/, //matches: '<!-- ... -->'
other: /^\/[*][^!].*[*]\/\n/, //matches: '/* ... */'
};
type Pkg = { [key: string]: string };
const pkg = <Pkg>JSON.parse(fs.readFileSync('package.json', 'utf-8'));
const inputFile = path.parse(filename);
const fileExt = settings.extension ?? inputFile.ext;
const jsStyle = /\.(js|ts|cjs|mjs)$/.test(fileExt);
const mlStyle = /\.(html|htm|sgml|xml|php)$/.test(fileExt);
const type = jsStyle ? 'js' : mlStyle ? 'ml' : 'other';
const isTextFile = !isBinary(filename);
const input = fs.readFileSync(filename, 'utf-8').trimStart();
const normalizeEol = /\r/g;
const normalizeEof = /\s*$(?!\n)/;
const out1 = input.replace(normalizeEol, '').replace(normalizeEof, '\n');
const out2 = settings.replaceComment ? out1.replace(firstLine[type], '') : out1;
const doctype = mlStyle && out2.match(doctypeLine)?.[0] || '';
const out3 = mlStyle && doctype ? out2.replace(doctype, '') : out2;
const versionPattern = /{{package[.]version}}/g;
const out4 = settings.setVersion ? out3.replace(versionPattern, pkg.version!) : out3;
const info = pkg.homepage ?? pkg.docs ?? pkg.repository;
const unlicensed = !pkg.license || pkg.license === 'UNLICENSED';
const license = unlicensed ? 'All Rights Reserved' : `${pkg.license} License`;
const delimiter = ' ' + settings.delimiter + ' ';
const banner = [`${pkg.name} v${pkg.version}`, info, license].join(delimiter);
const header = commentStyle[type].start + banner + commentStyle[type].end;
const fixedDigits = { minimumFractionDigits: 2, maximumFractionDigits: 2 };
const distFolder = fs.mkdirSync(settings.dist, { recursive: true }) ?? settings.dist;
const formatOptions = { dir: settings.dist, name: inputFile.name, ext: fileExt };
const outputPath = slash(path.format(formatOptions));
const isMinified = outputPath.includes('.min.') || out4.indexOf('\n') === out4.length - 1;
const spacerLines = isMinified || mlStyle ? '\n' : '\n\n';
const leadingBlanks = /^\s*\n/;
const final = doctype + header + spacerLines + out4.replace(leadingBlanks, '');
if (isTextFile)
fs.writeFileSync(outputPath, final);
else if (settings.allFiles)
fs.copyFileSync(filename, outputPath);
return {
valid: isTextFile || settings.allFiles,
text: isTextFile,
dist: distFolder,
header: isTextFile ? header : null,
source: slash(filename),
file: outputPath,
length: isTextFile ? final.length : null,
size: isTextFile ? (final.length / 1024).toLocaleString([], fixedDigits) + ' KB' : null,
};
},
reporter(result: Result, options?: ReporterSettings): Result {
const defaults = {
quiet: false,
};
const settings = { ...defaults, ...options };
const name = chalk.gray('add-dist-header');
const arrow = chalk.gray.bold('→');
const source = chalk.blue.bold(result.source);
const target = chalk.magenta(result.file);
const size = chalk.white('(' + (result.size || 'binary') + ')');
if (!settings.quiet && result.valid)
log(name, source, arrow, target, size);
return result;
},
};
export { addDistHeader };