Mirror: Best-effort discovery of the machine's local network using just Node.js dgram sockets
1import fs from 'node:fs/promises';
2import path from 'node:path/posix';
3import { fileURLToPath } from 'node:url';
4import { readFileSync } from 'node:fs';
5import { createRequire, isBuiltin } from 'node:module';
6
7import * as prettier from 'prettier';
8import commonjs from '@rollup/plugin-commonjs';
9import resolve from '@rollup/plugin-node-resolve';
10import babel from '@rollup/plugin-babel';
11import terser from '@rollup/plugin-terser';
12import cjsCheck from 'rollup-plugin-cjs-check';
13import dts from 'rollup-plugin-dts';
14
15const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
17const normalize = name => []
18 .concat(name)
19 .join(' ')
20 .replace(/[@\s/.]+/g, ' ')
21 .trim()
22 .replace(/\s+/, '-')
23 .toLowerCase();
24
25const extension = name => {
26 if (/\.d.ts$/.test(name)) {
27 return '.d.ts';
28 } else {
29 return path.extname(name);
30 }
31};
32
33const meta = JSON.parse(readFileSync('package.json'));
34const name = normalize(meta.name);
35
36const externalModules = [
37 ...Object.keys(meta.dependencies || {}),
38 ...Object.keys(meta.peerDependencies || {}),
39];
40
41const moduleRe = /^(?!node:|[.{1,2}\/])(@[\w.-]+\/)?[\w.-]+/;
42const externalRe = new RegExp(`^(${externalModules.join('|')})($|/)`);
43
44const exports = {};
45for (const key in meta.exports) {
46 const entry = meta.exports[key];
47 if (typeof entry === 'object' && !!entry.source) {
48 const entryPath = normalize(key);
49 const entryName = normalize([name, entryPath]);
50 exports[entryName] = {
51 path: entryPath,
52 ...entry,
53 };
54 }
55}
56
57const externals = new Set();
58
59const commonConfig = {
60 input: Object.entries(exports).reduce((input, [exportName, entry]) => {
61 input[exportName] = entry.source;
62 return input;
63 }, {}),
64 onwarn: () => {},
65 external(id) {
66 const isExternal = isBuiltin(id) || (externalModules.length && externalRe.test(id));
67 if (!isExternal && moduleRe.test(id))
68 externals.add(id);
69 return isExternal;
70 },
71 treeshake: {
72 unknownGlobalSideEffects: false,
73 tryCatchDeoptimization: false,
74 moduleSideEffects: false,
75 },
76};
77
78const commonPlugins = [
79 resolve({
80 extensions: ['.mjs', '.js', '.ts'],
81 mainFields: ['module', 'jsnext', 'main'],
82 preferBuiltins: false,
83 browser: true,
84 }),
85
86 commonjs({
87 ignoreGlobal: true,
88 include: /\/node_modules\//,
89 }),
90];
91
92const commonOutput = {
93 dir: './',
94 exports: 'auto',
95 sourcemap: true,
96 sourcemapExcludeSources: false,
97 hoistTransitiveImports: false,
98 indent: false,
99 freeze: false,
100 strict: false,
101 generatedCode: {
102 preset: 'es5',
103 reservedNamesAsProps: false,
104 objectShorthand: false,
105 constBindings: false,
106 },
107};
108
109const outputPlugins = [
110 {
111 name: 'outputPackageJsons',
112 async writeBundle() {
113 for (const key in exports) {
114 const entry = exports[key];
115 if (entry.path) {
116 const output = path.relative(entry.path, process.cwd());
117 const json = JSON.stringify({
118 name: key,
119 private: true,
120 version: '0.0.0',
121 main: path.join(output, entry.require),
122 types: path.join(output, entry.types),
123 source: path.join(output, entry.source),
124 exports: {
125 '.': {
126 types: path.join(output, entry.types),
127 require: path.join(output, entry.require),
128 source: path.join(output, entry.source),
129 },
130 },
131 }, null, 2);
132
133 await fs.mkdir(entry.path, { recursive: true });
134 await fs.writeFile(path.join(entry.path, 'package.json'), json);
135 }
136 }
137 },
138 },
139
140 {
141 name: 'outputBundledLicenses',
142 async writeBundle() {
143 const require = createRequire(import.meta.url);
144 const rootLicense = path.join(__dirname, '../LICENSE.md');
145 const outputLicense = path.resolve('LICENSE.md');
146 if (rootLicense === outputLicense) return;
147 const licenses = new Map();
148 for (const packageName of [...externals].sort()) {
149 let license;
150 let metaPath;
151 let meta;
152 try {
153 metaPath = require.resolve(path.join(packageName, '/package.json'));
154 meta = require(metaPath);
155 } catch (_error) {
156 continue;
157 }
158 const packagePath = path.dirname(metaPath);
159 let licenseName = (await fs.readdir(packagePath).catch(() => []))
160 .find((name) => /^licen[sc]e/i.test(name));
161 if (!licenseName) {
162 const match = /^SEE LICENSE IN (.*)/i.exec(meta.license || '');
163 licenseName = match ? match[1] : meta.license;
164 }
165 try {
166 license = await fs.readFile(path.join(packagePath, licenseName), 'utf8');
167 } catch (_error) {
168 license = meta.author
169 ? `${licenseName}, Copyright (c) ${meta.author.name || meta.author}`
170 : `${licenseName}, See license at: ${meta.repository.url || meta.repository}`;
171 }
172 licenses.set(packageName, license);
173 }
174 let output = (await fs.readFile(rootLicense, 'utf8')).trim();
175 for (const [packageName, licenseText] of licenses)
176 output += `\n\n## ${packageName}\n\n${licenseText.trim()}`;
177 await fs.writeFile(outputLicense, output);
178 },
179 },
180
181 cjsCheck(),
182
183 terser({
184 warnings: true,
185 ecma: 2015,
186 keep_fnames: true,
187 ie8: false,
188 compress: {
189 pure_getters: true,
190 toplevel: true,
191 booleans_as_integers: false,
192 keep_fnames: true,
193 keep_fargs: true,
194 if_return: false,
195 ie8: false,
196 sequences: false,
197 loops: false,
198 conditionals: false,
199 join_vars: false,
200 },
201 mangle: {
202 module: true,
203 keep_fnames: true,
204 },
205 output: {
206 beautify: true,
207 braces: true,
208 indent_level: 2,
209 },
210 }),
211];
212
213export default [
214 {
215 ...commonConfig,
216 plugins: [
217 ...commonPlugins,
218 babel({
219 babelrc: false,
220 babelHelpers: 'bundled',
221 extensions: ['mjs', 'js', 'jsx', 'ts', 'tsx'],
222 exclude: 'node_modules/**',
223 presets: [],
224 plugins: [
225 '@babel/plugin-transform-typescript',
226 '@babel/plugin-transform-block-scoping',
227 ],
228 }),
229 ],
230 output: {
231 ...commonOutput,
232 format: 'cjs',
233 esModule: true,
234 externalLiveBindings: true,
235 chunkFileNames(chunk) {
236 return `dist/chunks/[name]-chunk${extension(chunk.name) || '.js'}`;
237 },
238 entryFileNames(chunk) {
239 return chunk.isEntry
240 ? path.normalize(exports[chunk.name].require)
241 : `dist/[name].js`;
242 },
243 plugins: outputPlugins,
244 },
245 },
246
247 {
248 ...commonConfig,
249 plugins: [
250 ...commonPlugins,
251 dts(),
252 ],
253 output: {
254 ...commonOutput,
255 sourcemap: false,
256 format: 'dts',
257 chunkFileNames(chunk) {
258 return `dist/chunks/[name]-chunk${extension(chunk.name) || '.d.ts'}`;
259 },
260 entryFileNames(chunk) {
261 return chunk.isEntry
262 ? path.normalize(exports[chunk.name].types)
263 : `dist/[name].d.ts`;
264 },
265 plugins: [
266 {
267 renderChunk(code, chunk) {
268 if (chunk.fileName.endsWith('d.ts')) {
269 return prettier.format(code, {
270 filepath: chunk.fileName,
271 parser: 'typescript',
272 singleQuote: true,
273 tabWidth: 2,
274 printWidth: 100,
275 trailingComma: 'es5',
276 });
277 }
278 },
279 },
280 ],
281 },
282 },
283];