js-dev-tool 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/utils.js ADDED
@@ -0,0 +1,643 @@
1
+ /*!
2
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3
+ <one line to give the program's name and a brief idea of what it does.>
4
+ Copyright (C) 2017 jeffy-g hirotom1107@gmail.com
5
+
6
+ This program is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Affero General Public License as
8
+ published by the Free Software Foundation, either version 3 of the
9
+ License, or (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Affero General Public License for more details.
15
+
16
+ You should have received a copy of the GNU Affero General Public License
17
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
19
+ */
20
+ /// <reference types="./basic-types"/>
21
+ // @ts-check
22
+
23
+ // NOTE: fs-extra are bit slower.
24
+ const fs = require("fs");
25
+ // const util = require('util');
26
+ const path = require("path");
27
+
28
+ const lib = require("./common");
29
+ const getExtraArgs = require("tin-args");
30
+
31
+ const CI = !!process.env.CI;
32
+ const log = (() => {
33
+ return CI ? () => ({}) : console.log;
34
+ })();
35
+
36
+ /**
37
+ * get node version at runtime.
38
+ *
39
+ * format must be `v\d+.\d+.\d+`
40
+ *
41
+ * ex:
42
+ * ```
43
+ * const utils = require("./utils");
44
+ * const nv = utils.extractVersion();
45
+ * console.log(nv); // => {major: 10, minor: 9, patch: 0}
46
+ * ```
47
+ *
48
+ * @param {string} versionString default is process.version
49
+ */
50
+ function extractVersion(versionString = process.version) {
51
+ const RE_VERSION = /v(\d+).(\d+).(\d+)/;
52
+ // NOTE: pv is Array.isArray(pv), extend Array
53
+ let pv = RE_VERSION.exec(versionString);
54
+ const [major = 0, minor = 0, patch = 0] = pv
55
+ ? pv
56
+ .map((value, i) => {
57
+ return (i > 0 && +value) || void 0;
58
+ })
59
+ .slice(1)
60
+ : [];
61
+ console.log("result:", major, minor, patch);
62
+ return { major, minor, patch };
63
+ }
64
+
65
+ /**
66
+ * use toLocaleString
67
+ * @param {any} ymd use simple year month day formant? default `false`
68
+ * + should be truthy/falsy value
69
+ */
70
+ function dateStringForFile(ymd = false) {
71
+ // return new Date().toLocaleString().replace(/\//g, "-").replace(/:/g, "_").replace(/ /g, "@");
72
+ return new Date()
73
+ .toLocaleString(void 0, {
74
+ year: "2-digit",
75
+ month: "2-digit",
76
+ day: "2-digit",
77
+ hour: ymd ? void 0 : "2-digit",
78
+ minute: ymd ? void 0 : "2-digit",
79
+ second: ymd ? void 0 : "2-digit",
80
+ // DEVNOTE: 191215 - "-" character appeared in node v13.3.0 (maybe
81
+ })
82
+ .replace(/(-|\/|:| )/g, (match, $1) => {
83
+ switch ($1) {
84
+ case "-":
85
+ case "/":
86
+ case ":":
87
+ return "";
88
+ case " ":
89
+ return "@";
90
+ }
91
+ return match;
92
+ });
93
+ // return new Date().toLocaleString().replace(/(\/|:| )/g, (match, $1) => {
94
+ // switch($1) {
95
+ // case "/": return "-";
96
+ // case ":": return "_";
97
+ // case " ": return "@";
98
+ // }
99
+ // return match;
100
+ // });
101
+ }
102
+
103
+ /**
104
+ *
105
+ * @param {string} path
106
+ * @param {(dirent: import("fs").Dirent) => void} handler
107
+ */
108
+ function walkDirSync(path, handler) {
109
+ fs.readdirSync(path, { withFileTypes: true }).forEach(handler);
110
+ }
111
+
112
+ /**
113
+ * write text content to dest path.
114
+ * when not exists parent directory, creat it.
115
+ *
116
+ * @param {string|NodeJS.ReadableStream|Buffer} content text? content.
117
+ * @param {string} dest content output path
118
+ * @param {() => void} [callback] the callback function
119
+ */
120
+ function writeTextUTF8(content, dest, callback) {
121
+ // need dest parent dir check.
122
+ lib.checkParentDirectory(dest);
123
+
124
+ // actually, can write as binary
125
+ const ws = fs.createWriteStream(dest);
126
+ ws.on("error", function (err) {
127
+ log("WriteStream.error evnet!", arguments);
128
+ }).on("close", function (/*no args*/) {
129
+ // DEVNOTE: this event never occurs when WriteStream.write returned `true`
130
+ // log("[close] %s, stream closed", dest);
131
+ callback && callback();
132
+ });
133
+
134
+ if (content instanceof Buffer) {
135
+ content = content.toString();
136
+ }
137
+
138
+ if (typeof content === "string") {
139
+ // chunk <string> | <Buffer> | <Uint8Array> | <any>
140
+ const success = ws.write(content);
141
+ // const success = ws.write(content, function (/*no args*/) {
142
+ // // log(arguments);
143
+ // log("callback of WriteStream.write");
144
+ // });
145
+ // log("writeTextUTF8: write: %s,", dest, success);
146
+ // DEVNOTE: "drain" event callback -> WriteStream.write callback
147
+ // -> WriteStream.end() => "close" event callback.
148
+ if (!success) {
149
+ ws.once("drain", function () {
150
+ // log("[drain] file written: %s,", dest, ws.bytesWritten);
151
+ ws.end(); // -> call close()
152
+ });
153
+ } else {
154
+ // process.nextTick(callback);
155
+ callback && callback();
156
+ }
157
+ }
158
+ // NOTE: see https://nodejs.org/dist/latest-v6.x/docs/api/stream.html#stream_readable_pipe_destination_options
159
+ else if ("readable" in content) {
160
+ // Readable stream?
161
+ content.pipe(ws);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * @typedef {(err: any, data: string) => void} TFsCallback
167
+ */
168
+ /**
169
+ * @template {TFsCallback | undefined} C description
170
+ * @template {Conditional<C, string, void>} R description
171
+ * @param {string} from file path.
172
+ * @param {C} [callback]
173
+ * @returns {R} description
174
+ */
175
+ function readTextUTF8(from, callback) {
176
+ if (typeof callback === "function") {
177
+ fs.readFile(from, "utf8", callback);
178
+ } else {
179
+ return /** @type {R} */ (fs.readFileSync(from, "utf8"));
180
+ }
181
+ return /** @type {R} */ (undefined);
182
+ }
183
+
184
+ /**
185
+ * @template T
186
+ * @typedef {Record<string, T>} TypedRecord<T>
187
+ */
188
+ /**
189
+ * @template T
190
+ * @typedef {TBD<(err: any, data: TypedRecord<T>) => void>} TReadJsonCallback
191
+ */
192
+ /**
193
+ * NOTE: when callback specified, returns undefined
194
+ *
195
+ * @template T
196
+ * @template {TReadJsonCallback<T>} C description
197
+ * @template {Conditional<C, TypedRecord<T>, void>} R description
198
+ * @param {string} path file path.
199
+ * @param {C} [callback]
200
+ * @returns {R} description
201
+ */
202
+ function readJson(path, callback) {
203
+ if (typeof callback === "function") {
204
+ readTextUTF8(path, (err, data) => {
205
+ callback(err, JSON.parse(data));
206
+ });
207
+ } else {
208
+ const data = readTextUTF8(path);
209
+ return JSON.parse(data);
210
+ }
211
+ return /** @type {R} */ (undefined);
212
+ }
213
+ // // OK
214
+ // /** @type {Record<string, {}>} */
215
+ // const config = readJson("path");
216
+ // // OK
217
+ // readJson("path", (err, data) => err);
218
+ // // OK
219
+ // const config2 = readTextUTF8("path");
220
+ // // OK
221
+ // readTextUTF8("path", (err, data) => err);
222
+
223
+ /**
224
+ * use "rm-cstyle-cmts"
225
+ *
226
+ * @param {string} source
227
+ */
228
+ function removeJsonComments(source) {
229
+ if (typeof source !== "string") {
230
+ throw new TypeError("invalid text content!");
231
+ }
232
+ return require("rm-cstyle-cmts")(source);
233
+ }
234
+
235
+ /** @type {(options: any) => void} */
236
+ let nodeReplace;
237
+ /**
238
+ * @param {RegExp} regex
239
+ * @param {string | Function} replacement
240
+ * @param {string[]} paths Paths that do not exist are ignored
241
+ * @param {boolean} [async]
242
+ *
243
+ * @date 2019-4-26
244
+ */
245
+ // DEVNOTE: 2020/9/20 - Added exception handling code
246
+ function fireReplace(regex, replacement, paths, async = false) {
247
+ // @ts-ignore "replace" is peerDependencies
248
+ nodeReplace === void 0 && (nodeReplace = require("replace"));
249
+ // replace init.tsx init.js ./lib/index.html && replace \\.\\/pkg\\.json ../package.json ./lib/app-properties.js
250
+ if (Array.isArray(paths)) {
251
+ // DEVNOTE: 2020/5/11 22:41:06 - exclude non exists files
252
+ paths = paths.filter((path) => fs.existsSync(path));
253
+ nodeReplace({
254
+ regex, //: /init\.tsx/g,
255
+ replacement, //: "init.js",
256
+ paths, //: ["./lib/index.html"],
257
+ recursive: false,
258
+ silent: false,
259
+ // for test?
260
+ preview: false,
261
+ // replace function.
262
+ // funcFile: "js source path",
263
+ //
264
+ async,
265
+ // regexp flags, if "regex" are plain string then needed.
266
+ ignoreCase: false,
267
+ multiline: false,
268
+ });
269
+ } else {
270
+ throw new Error(`invalid paths parameter: paths=[${paths}]`);
271
+ }
272
+ }
273
+
274
+ /**
275
+ * create sourceName zip. (using zip.min.js
276
+ *
277
+ * @param {string} scriptPath simple script file name. e.g - webpack (original path are "./lib/webpack.js")
278
+ * @param {string} comment the zip file comment.
279
+ */
280
+ function compressScript(scriptPath, comment = "") {
281
+ // @ts-ignore
282
+ const zlibZip = require("./lib/zip.min").Zlib.Zip;
283
+ // const zlibZip = require("zlibjs/bin/zip.min").Zlib.Zip;
284
+ const zip = new zlibZip();
285
+
286
+ const scriptBin = fs.readFileSync(scriptPath);
287
+ // plainData1
288
+ zip.addFile(scriptBin, {
289
+ // filename: stringToByteArray("webpack.js"),
290
+ filename: stringToByteArray(path.basename(scriptPath)),
291
+ comment: stringToByteArray(comment),
292
+ // STORE or DEFLATE, default: DEFLATE...?
293
+ compressionMethod: zlibZip.CompressionMethod.DEFLATE,
294
+ os: zlibZip.OperatingSystem.MSDOS,
295
+ });
296
+
297
+ console.log(`added ${scriptPath} to zip`);
298
+ console.log("start compress...");
299
+ console.time("zip:compress");
300
+ const compressed = zip.compress();
301
+ console.timeEnd("zip:compress");
302
+ console.log("compress done.\n");
303
+
304
+ const pp = path.parse(scriptPath);
305
+ const output = `${pp.dir ? pp.dir + "/" : ""}${pp.name}.zip`;
306
+ fs.writeFile(output, compressed, (err) => {
307
+ console.log(`\nzip file created, error: ${err}\n => ${output}`);
308
+ });
309
+
310
+ /**
311
+ * @param {string} str
312
+ */
313
+ function stringToByteArray(str) {
314
+ const array = new Uint8Array(str.length);
315
+ for (let i = 0, il = str.length; i < il; ++i) {
316
+ array[i] = str.charCodeAt(i) & 0xff;
317
+ }
318
+ return array;
319
+ }
320
+ }
321
+
322
+ /**
323
+ * DEVNOTE: 10/21/2018, 9:15:00 PM - using "archiver" package, this is too fast!.
324
+ *
325
+ * @param {string} scriptPath
326
+ * @param {string} comment
327
+ */
328
+ function compressScript2(scriptPath, comment = "") {
329
+ // DEVNOTE: if want use "archiver" then install to npm global
330
+ const archiver = require("archiver");
331
+ const archive = archiver("zip", {
332
+ // comment, deplicated, unzip.min will fail decompress...
333
+ zlib: { level: 9 }, // Sets the compression level.
334
+ });
335
+ const pp = path.parse(scriptPath);
336
+ const output = fs.createWriteStream(`${pp.dir}/${pp.name}.zip`);
337
+ output.on("close", function () {
338
+ console.log(archive.pointer() + " total bytes");
339
+ console.log(
340
+ "archiver has been finalized and the output file descriptor has closed.",
341
+ );
342
+ });
343
+ output.on("end", function () {
344
+ console.log("Data has been drained");
345
+ });
346
+ // TODO: write code.
347
+ archive.on("progress", (progress) => {
348
+ console.log(progress.entries.processed);
349
+ });
350
+ archive.pipe(output);
351
+ // option name は必ず指定 (entry name となるため
352
+ archive.file(scriptPath, {
353
+ name: path.basename(scriptPath),
354
+ // DEVNOTE: "comment" are not entry at @types. however, it is entered in zip-stream.
355
+ // @ts-ignore ignore mistake of type definition
356
+ comment,
357
+ });
358
+ archive.finalize();
359
+ }
360
+
361
+ /**
362
+ * it is bundled in webpack.js, other code becomes unnecessary.(at webpack
363
+ *
364
+ * + 📝 using "exec" internally
365
+ * * 🆗️ can use pipe command
366
+ *
367
+ * @param {string} command
368
+ * @param {string=} cwd 2025/2/1 current working directory
369
+ * @param {(result: string) => void} doneCallbackWithArgs gulp callback function.
370
+ */
371
+ function execWithOutputResult(command, doneCallbackWithArgs, cwd) {
372
+ // const process = require("child_process");
373
+ console.log();
374
+ const { exec } = require("child_process");
375
+ return exec(command, { cwd }, (err, stdout /* , stderr */) => {
376
+ if (err) {
377
+ console.error(err);
378
+ } else {
379
+ doneCallbackWithArgs(stdout);
380
+ }
381
+ });
382
+ }
383
+
384
+ /**
385
+ *
386
+ * @param {string} list
387
+ */
388
+ const NPM_INSTALL_SCRIPT = (list) => `
389
+ @echo off
390
+ rem REVISION LOG ENTRY
391
+ rem
392
+ rem File name : npm-upg.cmd
393
+ rem Revision By: Copyright 2020 Hiroyuki Tominaga, All Rights Reserved.
394
+ rem
395
+ rem 9:26 2018/06/27 When executed in batch - npm ERR! Maximum call stack size exceeded
396
+ rem
397
+
398
+ set opt=
399
+ if not "%1" == "" (
400
+ set opt=%1
401
+ ) else (
402
+ set opt=i
403
+ )
404
+
405
+ rem workbox-cli@4 is too large
406
+ rem \`node-sass\` will complete more smoothly if installed separately
407
+ rem \`windows-build-tools\` - It is better to start PowerShell with administrator privileges and install
408
+
409
+ REM TODO: automation script by nodejs
410
+ REM + child_process("npm -g outdated") -> get output
411
+ REM + extract outdated packages by regex
412
+ REM + output install script
413
+
414
+ set LIST=${list}
415
+
416
+ echo packages="%LIST%"
417
+ npm %opt% -g %LIST%
418
+ `;
419
+ /**
420
+ * ### generate npm global package update script (windows command)
421
+ *
422
+ * ```js
423
+ * const utils = require("./utils");
424
+ * // ...
425
+ * // execute normally
426
+ * utils.genGlobalNpmUpdateScript("electron", "workbox-cli");
427
+ * // debug
428
+ * utils.genGlobalNpmUpdateScript("--debug", "electron", "workbox-cli");
429
+ * ```
430
+ *
431
+ * @param {string[]} excludes
432
+ */
433
+ function genGlobalNpmUpdateScript(...excludes) {
434
+ let debug = false;
435
+ if (excludes.length && excludes[0] === "--debug") {
436
+ debug = true;
437
+ excludes.shift();
438
+ }
439
+
440
+ console.log("run `npm -g outdated`");
441
+ // const progress = createProgressObject(aa, aa, aa);
442
+ // progress.run();
443
+ execWithOutputResult("npm -g outdated", (output) => {
444
+ console.log("done `npm -g outdated`");
445
+ /** @type {RegExpExecArray | null} */
446
+ let m;
447
+ /** @type {string[]} */
448
+ const packages = [];
449
+ const re = /^([\w-]+)/gm; // DEVNOTE: must use multi line flag
450
+ while ((m = re.exec(output))) {
451
+ const name = m[1];
452
+ if (name !== "Package" && !excludes.includes(name)) {
453
+ packages.push(name);
454
+ }
455
+ }
456
+ const packageList = packages.join("^\n ");
457
+ const source = NPM_INSTALL_SCRIPT(packageList);
458
+ if (!debug) {
459
+ writeTextUTF8(source, "./tmp/npm-upg.cmd");
460
+ } else {
461
+ console.log(source);
462
+ }
463
+ });
464
+ }
465
+ /* [simple test of `globalNpmUpdate`]
466
+ const dummy = (output: string) => {
467
+ let m: RegExpExecArray | null;
468
+ const packages: string[] = [];
469
+ const re = /^([\w-]+)/gm;
470
+ while (m = re.exec(output)) {
471
+ const name = m[1];
472
+ if (name !== "Package" && !excludes.includes(name)) {
473
+ packages.push(name);
474
+ }
475
+ }
476
+ const packageList = packages.join("^\n ");
477
+ const source = NPM_INSTALL_SCRIPT(packageList);
478
+ // if (!debug) {
479
+ // writeTextUTF8(source, "./tmp/npm-upg.cmd");
480
+ // } else {
481
+ // console.log(source);
482
+ // }
483
+ console.log(source);
484
+ };
485
+
486
+ //
487
+ // for TEST code
488
+ //
489
+ const excludes = [
490
+ "electron", "workbox-cli"
491
+ ];
492
+ const outdatedResult = `Package Current Wanted Latest Location
493
+ create-react-app 3.4.0 3.4.1 3.4.1 global
494
+ electron 8.0.1 8.2.0 8.2.0 global
495
+ firebase 7.12.0 7.13.1 7.13.1 global
496
+ firebase-tools 7.15.1 7.16.1 7.16.1 global
497
+ npm-check-updates 4.0.4 4.1.0 4.1.0 global
498
+ ts-node 8.7.0 8.8.1 8.8.1 global
499
+ typedoc 0.17.1 0.17.3 0.17.3 global
500
+ webpack 4.42.0 4.42.1 4.42.1 global
501
+ workbox-cli 4.3.1 4.3.1 5.1.2 global
502
+ `;
503
+ // run test
504
+ dummy(outdatedResult);
505
+ */
506
+
507
+ /**
508
+ * ### command:
509
+ *
510
+ * + windows - chcp 65001 && clip
511
+ * + others - xclip
512
+ *
513
+ * @param {string} content the copy terget content as string.
514
+ * @param {string} [message] default: "text copied!"
515
+ * @param {boolean} [chcp65001] default `true`
516
+ */
517
+ function copyText(content, message = "text copied!", chcp65001 = true) {
518
+ const os = require("os");
519
+ const cp = require("child_process");
520
+ const command =
521
+ os.platform() === "win32"
522
+ ? `${chcp65001 ? "chcp 65001 && " : ""}clip`
523
+ : "xclip";
524
+ const clipTask = () => {
525
+ const childProc = cp.exec(command, (err, stdout) => {
526
+ if (err) {
527
+ console.error(err);
528
+ } else {
529
+ console.log(message, stdout);
530
+ }
531
+ });
532
+ // @ts-ignore
533
+ childProc.stdin.write(content, (error) => {
534
+ if (error) {
535
+ console.error(error);
536
+ }
537
+ // DEVNOTE: do noting...
538
+ // util.inspect("content_for_the_clipboard");
539
+ // @ts-ignore
540
+ childProc.stdin.end();
541
+ });
542
+ };
543
+
544
+ clipTask();
545
+ // if (os.platform() === "win32") {
546
+ // // clipTask();
547
+ // cp.exec("chcp", (err, stdout) => {
548
+ // if (err) {
549
+ // console.error(err);
550
+ // } else {
551
+ // if (!stdout.includes("65001")) {
552
+ // console.log("OKKKK");
553
+ // clipTask();
554
+ // }
555
+ // }
556
+ // }
557
+ // );
558
+ // } else {
559
+ // clipTask();
560
+ // }
561
+ }
562
+
563
+ /**
564
+ * use for gulp.dest(...)
565
+ *
566
+ * **useful when glob pattern can not be used (when path must be explicitly specified).**
567
+ *
568
+ * ```js
569
+ * gulp.src([
570
+ * "./src/app-config.ts",
571
+ * "./src/auth/{eve-sso,eve-sso-v2e}.php"
572
+ * ]).pipe(
573
+ * ...
574
+ * ).pipe(gulp.dest((vinyl) => {
575
+ * return convertRelativeDir(vinyl);
576
+ * })).on("end", () => {
577
+ * console.log("done");
578
+ * });
579
+ * ```
580
+ * @param {import("vinyl")} vinyl
581
+ * @param {string} dest default is "." -> node launched directory. (cwd?)
582
+ */
583
+ function convertRelativeDir(vinyl, dest = ".") {
584
+ // NOTE: vinyl is https://github.com/gulpjs/vinyl
585
+ // if (false) {
586
+ // console.log("convertRelativeDir::debug log");
587
+ // console.log(vinyl.cwd);
588
+ // console.log(vinyl.base);
589
+ // }
590
+ let x = vinyl.cwd.length + 1;
591
+ let relative_dir = vinyl.base.substring(x);
592
+ return `${dest}/${relative_dir}`;
593
+ }
594
+
595
+ /**
596
+ * prepend `content` to the beginning of each element of `str_array`
597
+ *
598
+ * form:
599
+ * ```js
600
+ * `${content}${suffix}${<str_array element>}`
601
+ * ```
602
+ *
603
+ * @param {string[]} str_array the string array
604
+ * @param {string} content prepend content
605
+ * @param {string} [suffix]
606
+ * @date 2020/2/16
607
+ * @version 2.0 rename `appendStringTo` -> `prependStringTo`
608
+ */
609
+ function prependStringTo(str_array, content, suffix = "") {
610
+ /** @type {string} */
611
+ let target;
612
+ for (let i = 0; (target = str_array[i]); ) {
613
+ str_array[i++] = `${content}${suffix}${target}`;
614
+ }
615
+ }
616
+
617
+ module.exports = {
618
+ prependStringTo,
619
+
620
+ extractVersion,
621
+ dateStringForFile,
622
+ getExtraArgs,
623
+ removeJsonComments,
624
+ writeTextUTF8,
625
+ readTextUTF8,
626
+ readJson,
627
+ walkDirSync,
628
+
629
+ compressScript,
630
+ compressScript2,
631
+ execWithOutputResult,
632
+ genGlobalNpmUpdateScript,
633
+ convertRelativeDir,
634
+ /**
635
+ *
636
+ * @param {string} content the copy terget content as string.
637
+ * @param {string} message default: "text copied!"
638
+ */
639
+ copyText,
640
+ fireReplace,
641
+ CI,
642
+ log,
643
+ };