@amekusa/util.js 1.2.1 → 2.1.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.
@@ -0,0 +1,1150 @@
1
+ 'use strict';var os=require('node:os'),fs=require('node:fs'),fsp=require('node:fs/promises'),path=require('node:path'),node_stream=require('node:stream'),node_process=require('node:process'),node_child_process=require('node:child_process'),assert=require('node:assert');function _interopNamespaceDefault(e){var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var fsp__namespace=/*#__PURE__*/_interopNamespaceDefault(fsp);/*!
2
+ * === @amekusa/util.js/gen === *
3
+ * MIT License
4
+ *
5
+ * Copyright (c) 2024 Satoshi Soma
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in all
15
+ * copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ * SOFTWARE.
24
+ */
25
+
26
+ /**
27
+ * Coerces the given value into an array.
28
+ * @param {any} x
29
+ * @return {any[]}
30
+ */
31
+ function arr(x) {
32
+ return Array.isArray(x) ? x : [x];
33
+ }
34
+
35
+ /**
36
+ * Checks the type of the given value matches with one of the given types.
37
+ * If a constructor is given to `types`, it checks if `x` is `instanceof` the constructor.
38
+ * @param {any} x
39
+ * @param {...string|function} types - Type or Constructor
40
+ * @return {boolean}
41
+ */
42
+ function is(x, ...types) {
43
+ let t = typeof x;
44
+ for (let i = 0; i < types.length; i++) {
45
+ let v = types[i];
46
+ if (typeof v == 'string') {
47
+ if (v == 'array') {
48
+ if (Array.isArray(x)) return true;
49
+ } else if (t == v) return true;
50
+ } else if (x instanceof v) return true;
51
+ }
52
+ return false;
53
+ }
54
+
55
+ /**
56
+ * Returns whether the given value can be considered as "empty".
57
+ * @param {any} x
58
+ * @return {boolean}
59
+ */
60
+ function isEmpty(x) {
61
+ if (Array.isArray(x)) return x.length == 0;
62
+ switch (typeof x) {
63
+ case 'string':
64
+ return !x;
65
+ case 'object':
66
+ for (let _ in x) return false;
67
+ return true;
68
+ case 'undefined':
69
+ return true;
70
+ }
71
+ return false;
72
+ }
73
+
74
+ /**
75
+ * Returns whether the given value can be considered as "empty" or "falsy".
76
+ * Faster than {@link isEmpty}.
77
+ * @param {any} x
78
+ * @return {boolean}
79
+ */
80
+ function isEmptyOrFalsy(x) {
81
+ if (!x) return true;
82
+ if (Array.isArray(x)) return x.length == 0;
83
+ if (typeof x == 'object') {
84
+ for (let _ in x) return false;
85
+ }
86
+ return false;
87
+ }
88
+
89
+ /**
90
+ * @function isEmptyOrFalsey
91
+ * Alias of {@link isEmptyOrFalsy}.
92
+ */
93
+ const isEmptyOrFalsey = isEmptyOrFalsy;
94
+
95
+ /**
96
+ * Removes "empty" values from the given object or array.
97
+ * @param {object|any[]} x
98
+ * @param {number} recurse - Recursion limit
99
+ * @return {object|any[]} modified `x`
100
+ */
101
+ function clean$1(x, recurse = 8) {
102
+ if (recurse) {
103
+ if (Array.isArray(x)) {
104
+ let r = [];
105
+ for (let i = 0; i < x.length; i++) {
106
+ let v = clean$1(x[i], recurse - 1);
107
+ if (!isEmpty(v)) r.push(v);
108
+ }
109
+ return r;
110
+ }
111
+ if (typeof x == 'object') {
112
+ let r = {};
113
+ for (let k in x) {
114
+ let v = clean$1(x[k], recurse - 1);
115
+ if (!isEmpty(v)) r[k] = v;
116
+ }
117
+ return r;
118
+ }
119
+ }
120
+ return x;
121
+ }
122
+
123
+ /**
124
+ * Merges the 2nd object into the 1st object recursively (deep-merge). The 1st object will be modified.
125
+ * @param {object} x - The 1st object
126
+ * @param {object} y - The 2nd object
127
+ * @param {object} [opts] - Options
128
+ * @param {number} opts.recurse=8 - Recurstion limit. Negative number means unlimited
129
+ * @param {boolean|string} opts.mergeArrays - How to merge arrays
130
+ * - `true`: merge x with y
131
+ * - 'push': push y elements to x
132
+ * - 'concat': concat x and y
133
+ * - other: replace x with y
134
+ * @return {object} The 1st object
135
+ */
136
+ function merge$1(x, y, opts = {}) {
137
+ if (!('recurse' in opts)) opts.recurse = 8;
138
+ switch (Array.isArray(x) + Array.isArray(y)) {
139
+ case 0: // no array
140
+ if (opts.recurse && x && y && typeof x == 'object' && typeof y == 'object') {
141
+ opts.recurse--;
142
+ for (let k in y) x[k] = merge$1(x[k], y[k], opts);
143
+ opts.recurse++;
144
+ return x;
145
+ }
146
+ case 1: // 1 array
147
+ return y;
148
+ }
149
+ // 2 arrays
150
+ switch (opts.mergeArrays) {
151
+ case true:
152
+ for (let i = 0; i < y.length; i++) {
153
+ if (!x.includes(y[i])) x.push(y[i]);
154
+ }
155
+ return x;
156
+ case 'push':
157
+ x.push(...y);
158
+ return x;
159
+ case 'concat':
160
+ return x.concat(y);
161
+ }
162
+ return y;
163
+ }
164
+
165
+ /**
166
+ * Gets a property from the given object by the given string path.
167
+ * @param {object} obj - Object to traverse
168
+ * @param {string} path - Property names separated with '.'
169
+ * @return {any} value of the found property, or undefined if it's not found
170
+ */
171
+ function dig(obj, path) {
172
+ path = path.split('.');
173
+ for (let i = 0; i < path.length; i++) {
174
+ let p = path[i];
175
+ if (typeof obj == 'object' && p in obj) obj = obj[p];
176
+ else return undefined;
177
+ }
178
+ return obj;
179
+ }
180
+
181
+ /**
182
+ * Substitutes the properties of the given data for the references in the given string.
183
+ * @param {string} str - String that contains references to the properties
184
+ * @param {object} data - Object that contains properties to replace the references
185
+ * @param {object} [opts] - Options
186
+ * @return {string} a modified `str`
187
+ */
188
+ function subst(str, data, opts = {}) {
189
+ let {
190
+ modifier = null,
191
+ start = '{{',
192
+ end = '}}',
193
+ } = opts;
194
+ let ref = new RegExp(start + '\\s*([-.\\w]+)\\s*' + end, 'g');
195
+ return str.replaceAll(ref, modifier
196
+ ? (_, m1) => (modifier(dig(data, m1), m1, data) || '')
197
+ : (_, m1) => (dig(data, m1) || '')
198
+ );
199
+ }var gen=/*#__PURE__*/Object.freeze({__proto__:null,arr:arr,clean:clean$1,dig:dig,is:is,isEmpty:isEmpty,isEmptyOrFalsey:isEmptyOrFalsey,isEmptyOrFalsy:isEmptyOrFalsy,merge:merge$1,subst:subst});/*!
200
+ * === @amekusa/util.js/web === *
201
+ * MIT License
202
+ *
203
+ * Copyright (c) 2024 Satoshi Soma
204
+ *
205
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
206
+ * of this software and associated documentation files (the "Software"), to deal
207
+ * in the Software without restriction, including without limitation the rights
208
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
209
+ * copies of the Software, and to permit persons to whom the Software is
210
+ * furnished to do so, subject to the following conditions:
211
+ *
212
+ * The above copyright notice and this permission notice shall be included in all
213
+ * copies or substantial portions of the Software.
214
+ *
215
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
216
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
217
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
218
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
219
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
220
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
221
+ * SOFTWARE.
222
+ */
223
+
224
+ /**
225
+ * Converts non-safe chars in the given string into HTML entities.
226
+ * @param {string} str
227
+ * @return {string}
228
+ */
229
+ function escHTML(str) {
230
+ return `${str}`.replace(escHTML_find, escHTML_replace);
231
+ }
232
+
233
+ const escHtml = escHTML; // alias
234
+
235
+ const escHTML_map = {
236
+ '&': 'amp',
237
+ '"': 'quot',
238
+ "'": 'apos',
239
+ '<': 'lt',
240
+ '>': 'gt'
241
+ };
242
+
243
+ const escHTML_find = new RegExp(`["'<>]|(&(?!${Object.values(escHTML_map).join('|')};))`, 'g');
244
+ // NOTE:
245
+ // - This avoids double-escaping '&' symbols
246
+ // - Regex negative match: (?!word)
247
+
248
+ const escHTML_replace = found => `&${escHTML_map[found]};`;var web=/*#__PURE__*/Object.freeze({__proto__:null,escHTML:escHTML,escHtml:escHtml});/*!
249
+ * === @amekusa/util.js/time === *
250
+ * MIT License
251
+ *
252
+ * Copyright (c) 2024 Satoshi Soma
253
+ *
254
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
255
+ * of this software and associated documentation files (the "Software"), to deal
256
+ * in the Software without restriction, including without limitation the rights
257
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
258
+ * copies of the Software, and to permit persons to whom the Software is
259
+ * furnished to do so, subject to the following conditions:
260
+ *
261
+ * The above copyright notice and this permission notice shall be included in all
262
+ * copies or substantial portions of the Software.
263
+ *
264
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
265
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
266
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
267
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
268
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
269
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
270
+ * SOFTWARE.
271
+ */
272
+
273
+ /**
274
+ * Coerces the given value into a `Date` object.
275
+ * @param {...any} args - A `Date` object or args to pass to `Date()`
276
+ * @return {Date}
277
+ */
278
+ function date(...args) {
279
+ if (!args.length || !args[0]) return new Date();
280
+ if (args[0] instanceof Date) return args[0];
281
+ return new Date(...args);
282
+ }
283
+
284
+ /**
285
+ * Coerces the given value into a number of milliseconds.
286
+ * @param {...args} args - A number or args to pass to `Date()`
287
+ * @return {number} milliseconds
288
+ */
289
+ function ms(...args) {
290
+ if (!args.length || !args[0]) return Date.now();
291
+ let x = args[0];
292
+ if (typeof x == 'number') return x;
293
+ if (x instanceof Date) return x.getTime();
294
+ return (new Date(...args)).getTime();
295
+ }
296
+
297
+ /**
298
+ * Adds the given amount of time to a `Date` object.
299
+ * @param {Date} d - Date object to modify
300
+ * @param {number} amount - Millieconds to add
301
+ * @return {Date} modified Date
302
+ */
303
+ function addTime(d, amount) {
304
+ d.setTime(d.getTime() + amount);
305
+ return d;
306
+ }
307
+
308
+ /**
309
+ * Subtracts the timezone offset from a `Date` object.
310
+ * @param {Date} d - Date object to modify
311
+ * @return {Date} modified Date
312
+ */
313
+ function localize(d) {
314
+ d.setTime(d.getTime() - d.getTimezoneOffset() * 60000);
315
+ return d;
316
+ }
317
+
318
+ /**
319
+ * Quantizes a `Date` object with the given amount of time.
320
+ * @param {Date} d - Date object to modify
321
+ * @param {number} step - Quantization step size
322
+ * @param {string} [method='round'] - `Math` method to apply
323
+ * @return {Date} modified Date
324
+ */
325
+ function quantize(d, step, method = 'round') {
326
+ d.setTime(Math[method](d.getTime() / step) * step);
327
+ return d;
328
+ }
329
+
330
+ /**
331
+ * Alias of `quantize(d, step, 'round')`.
332
+ */
333
+ function round(d, step) {
334
+ return quantize(d, step, 'round');
335
+ }
336
+
337
+ /**
338
+ * Alias of `quantize(d, step, 'floor')`.
339
+ */
340
+ function floor(d, step) {
341
+ return quantize(d, step, 'floor');
342
+ }
343
+
344
+ /**
345
+ * Alias of `quantize(d, step, 'ceil')`.
346
+ */
347
+ function ceil(d, step) {
348
+ return quantize(d, step, 'ceil');
349
+ }
350
+
351
+ /**
352
+ * Returns `YYYY`, `MM`, and `DD` representations of a `Date` object.
353
+ * @param {Date} d - Date object
354
+ * @param {string|object} [format]
355
+ * - If omitted, the return value will be an array consists of the three parts.
356
+ * - If a string is passed, the three parts will be joined with the string as a separator.
357
+ * - If an object is passed, the three parts will be assigned as `Y`, `M`, and `D` properties.
358
+ * @return {string|string[]|object}
359
+ */
360
+ function ymd(d, format = null) {
361
+ let r = [
362
+ d.getFullYear().toString(),
363
+ (d.getMonth() + 1).toString().padStart(2, '0'),
364
+ d.getDate().toString().padStart(2, '0'),
365
+ ];
366
+ switch (typeof format) {
367
+ case 'string':
368
+ return r.join(format);
369
+ case 'object':
370
+ if (!format) return r;
371
+ format.Y = r[0];
372
+ format.M = r[1];
373
+ format.D = r[2];
374
+ return format;
375
+ default:
376
+ if (!format) return r;
377
+ throw `invalid type`;
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Returns `hh`, `mm`, and `ss` representations of a `Date` object.
383
+ * @param {Date} d - Date object
384
+ * @param {string|object} [format]
385
+ * - If omited, the return value will be an array consists of the three parts.
386
+ * - If a string is passed, the three parts will be joined with the string as a separator.
387
+ * - If an object is passed, the three parts will be assigned as `h`, `m`, and `s` properties.
388
+ * @return {string|string[]|object}
389
+ */
390
+ function hms(d, format = null) {
391
+ let r = [
392
+ d.getHours().toString().padStart(2, '0'),
393
+ d.getMinutes().toString().padStart(2, '0'),
394
+ d.getSeconds().toString().padStart(2, '0'),
395
+ ];
396
+ switch (typeof format) {
397
+ case 'string':
398
+ return r.join(format);
399
+ case 'object':
400
+ if (!format) return r;
401
+ format.h = r[0];
402
+ format.m = r[1];
403
+ format.s = r[2];
404
+ return format;
405
+ default:
406
+ if (!format) return r;
407
+ throw `invalid type`;
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Returns a string representation of the given `Date` in ISO 9075 format, which is standard for MySQL.
413
+ * @param {Date} d - Date object
414
+ * @return {string} a string like `YYYY-MM-DD hh:mm:ss`
415
+ */
416
+ function iso9075(d) {
417
+ return ymd(d, '-') + ' ' + hms(d, ':');
418
+ }var time=/*#__PURE__*/Object.freeze({__proto__:null,addTime:addTime,ceil:ceil,date:date,floor:floor,hms:hms,iso9075:iso9075,localize:localize,ms:ms,quantize:quantize,round:round,ymd:ymd});/*!
419
+ * === @amekusa/util.js/sh === *
420
+ * MIT License
421
+ *
422
+ * Copyright (c) 2024 Satoshi Soma
423
+ *
424
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
425
+ * of this software and associated documentation files (the "Software"), to deal
426
+ * in the Software without restriction, including without limitation the rights
427
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
428
+ * copies of the Software, and to permit persons to whom the Software is
429
+ * furnished to do so, subject to the following conditions:
430
+ *
431
+ * The above copyright notice and this permission notice shall be included in all
432
+ * copies or substantial portions of the Software.
433
+ *
434
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
435
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
436
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
437
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
438
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
439
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
440
+ * SOFTWARE.
441
+ */
442
+
443
+ /**
444
+ * Executes the given shell command, and returns a Promise that resolves the stdout
445
+ * @param {string} cmd
446
+ * @param {object} [opts]
447
+ * @return {Promise}
448
+ */
449
+ function exec(cmd, opts = {}) {
450
+ opts = Object.assign({
451
+ dryRun: false,
452
+ }, opts);
453
+ return new Promise((resolve, reject) => {
454
+ if (opts.dryRun) {
455
+ console.log(`[DRYRUN] ${cmd}`);
456
+ return resolve();
457
+ }
458
+ node_child_process.exec(cmd, (err, stdout) => {
459
+ return err ? reject(err) : resolve(stdout);
460
+ });
461
+ });
462
+ }
463
+
464
+ /**
465
+ * Converts the given objects to shell arguments in a string form
466
+ * @param {object} args
467
+ * @param {object} [opts]
468
+ * @return {string}
469
+ */
470
+ function args(args, opts = {}) {
471
+ opts = Object.assign({
472
+ sep: ' ', // key-value separator
473
+ }, opts);
474
+ let r = [];
475
+ for (let key in args) {
476
+ let value = args[key];
477
+ if (isNaN(key)) { // non-numeric key
478
+ switch (typeof value) {
479
+ case 'boolean':
480
+ if (value) r.push(key);
481
+ break;
482
+ case 'number':
483
+ r.push(key + opts.sep + value);
484
+ break;
485
+ case 'string':
486
+ r.push(key + opts.sep + `"${value}"`);
487
+ break;
488
+ }
489
+ } else { // numeric key
490
+ r.push(value);
491
+ }
492
+ }
493
+ return r.join(' ');
494
+ }
495
+
496
+ /**
497
+ * Returns if NODE_ENV is 'production'
498
+ * @param {any} [set]
499
+ * @return {bool}
500
+ */
501
+ function prod(set = undefined) {
502
+ let value = 'production';
503
+ if (set != undefined) node_process.env.NODE_ENV = set ? value : '';
504
+ return node_process.env.NODE_ENV == value;
505
+ }
506
+
507
+ /**
508
+ * Returns if NODE_ENV is 'development'
509
+ * @param {any} [set]
510
+ * @return {bool}
511
+ */
512
+ function dev(set = undefined) {
513
+ let value = 'development';
514
+ if (set != undefined) node_process.env.NODE_ENV = set ? value : '';
515
+ return node_process.env.NODE_ENV == value;
516
+ }var sh=/*#__PURE__*/Object.freeze({__proto__:null,args:args,dev:dev,exec:exec,prod:prod});/*!
517
+ * === @amekusa/util.js/io/AssetImporter === *
518
+ * MIT License
519
+ *
520
+ * Copyright (c) 2024 Satoshi Soma
521
+ *
522
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
523
+ * of this software and associated documentation files (the "Software"), to deal
524
+ * in the Software without restriction, including without limitation the rights
525
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
526
+ * copies of the Software, and to permit persons to whom the Software is
527
+ * furnished to do so, subject to the following conditions:
528
+ *
529
+ * The above copyright notice and this permission notice shall be included in all
530
+ * copies or substantial portions of the Software.
531
+ *
532
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
533
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
534
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
535
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
536
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
537
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
538
+ * SOFTWARE.
539
+ */
540
+
541
+ /**
542
+ * This is for copying styles or scripts to a certain HTML directory.
543
+ * @author Satoshi Soma (github.com/amekusa)
544
+ */
545
+ class AssetImporter {
546
+ /**
547
+ * @param {object} config
548
+ * @param {boolean} [config.minify=false] - Prefer `*.min.*` version
549
+ * @param {string} config.src - Source dir to search
550
+ * @param {string} config.dst - Destination dir
551
+ */
552
+ constructor(config) {
553
+ this.config = Object.assign({
554
+ minify: false,
555
+ src: '', // source dir to search
556
+ dst: '', // destination dir
557
+ }, config);
558
+ this.queue = [];
559
+ this.results = {
560
+ script: [],
561
+ style: [],
562
+ asset: [],
563
+ };
564
+ }
565
+ /**
566
+ * Adds a new item to import.
567
+ * @param {string|string[]|object|object[]} newImport
568
+ */
569
+ add(newImport) {
570
+ if (!Array.isArray(newImport)) newImport = [newImport];
571
+ for (let i = 0; i < newImport.length; i++) {
572
+ let item = newImport[i];
573
+ switch (typeof item) {
574
+ case 'string':
575
+ item = {src: item};
576
+ break;
577
+ case 'object':
578
+ if (Array.isArray(item)) throw `invalid type: array`;
579
+ break;
580
+ default:
581
+ throw `invalid type: ${typeof item}`;
582
+ }
583
+ if (!('src' in item)) throw `'src' property is missing`;
584
+ this.queue.push(Object.assign({
585
+ order: 0,
586
+ resolve: 'local',
587
+ private: false,
588
+ }, item));
589
+ }
590
+ }
591
+ /**
592
+ * Resolves the location of the given file path
593
+ * @param {string} file - File path
594
+ * @param {string} method - Resolution method
595
+ * @return {string} Resolved file path
596
+ */
597
+ resolve(file, method) {
598
+ let find = [];
599
+ if (this.config.minify) {
600
+ let _ext = ext(file);
601
+ find.push(ext(file, '.min' + _ext));
602
+ }
603
+ find.push(file);
604
+ for (let i = 0; i < find.length; i++) {
605
+ let r;
606
+ switch (method) {
607
+ case 'module':
608
+ try {
609
+ r = require.resolve(find[i]);
610
+ } catch (e) {
611
+ if (e.code == 'MODULE_NOT_FOUND') continue;
612
+ throw e;
613
+ }
614
+ return r;
615
+ case 'local':
616
+ r = path.join(this.config.src, find[i]);
617
+ if (fs.existsSync(r)) return r;
618
+ break;
619
+ case 'local:absolute':
620
+ case 'local:abs':
621
+ r = find[i];
622
+ if (fs.existsSync(r)) return r;
623
+ break;
624
+ default:
625
+ throw `invalid resolution method: ${method}`;
626
+ }
627
+ }
628
+ throw `cannot resolve '${file}'`;
629
+ }
630
+ /**
631
+ * Imports all items in the queue at once.
632
+ */
633
+ import() {
634
+ let typeMap = {
635
+ '.css': 'style',
636
+ '.js': 'script',
637
+ };
638
+ this.queue.sort((a, b) => (Number(a.order) - Number(b.order))); // sort by order
639
+ while (this.queue.length) {
640
+ let item = this.queue.shift();
641
+ let {type, src} = item;
642
+ let url;
643
+
644
+ if (!item.resolve) { // no resolution
645
+ url = src;
646
+ if (!type) type = typeMap[ext(src)] || 'asset';
647
+ console.log('---- File Link ----');
648
+ console.log(' type:', type);
649
+ console.log(' src:', src);
650
+
651
+ } else { // needs resolution
652
+ let {dst:dstDir, as:dstFile} = item;
653
+ let create = item.resolve == 'create'; // needs creation?
654
+ if (create) {
655
+ if (!dstFile) throw `'as' property is required with {resolve: 'create'}`;
656
+ } else {
657
+ src = this.resolve(src, item.resolve);
658
+ if (!dstFile) dstFile = path.basename(src);
659
+ }
660
+ if (!type) type = typeMap[ext(dstFile)] || 'asset';
661
+ if (!dstDir) dstDir = type + 's';
662
+
663
+ // absolute destination
664
+ url = path.join(dstDir, dstFile);
665
+ let dst = path.join(this.config.dst, url);
666
+ dstDir = path.dirname(dst);
667
+ if (!fs.existsSync(dstDir)) fs.mkdirSync(dstDir, {recursive:true});
668
+ if (create) {
669
+ fs.writeFileSync(dst, src);
670
+ console.log('---- File Creation ----');
671
+ console.log(' type:', type);
672
+ console.log(' dst:', dst);
673
+ } else {
674
+ fs.copyFileSync(src, dst);
675
+ console.log('---- File Import ----');
676
+ console.log(' type:', type);
677
+ console.log(' src:', src);
678
+ console.log(' dst:', dst);
679
+ }
680
+ }
681
+
682
+ if (!item.private) {
683
+ if (!(type in this.results)) this.results[type] = [];
684
+ this.results[type].push({type, url});
685
+ }
686
+ }
687
+ }
688
+ /**
689
+ * Outputs HTML tags for imported items.
690
+ * @param {string} [type] - Type
691
+ * @return {string} HTML
692
+ */
693
+ toHTML(type = null) {
694
+ let r;
695
+ if (type) {
696
+ let tmpl = templates[type];
697
+ if (!tmpl) return '';
698
+ let items = this.results[type];
699
+ r = new Array(items.length);
700
+ for (let i = 0; i < items.length; i++) {
701
+ r[i] = tmpl.replaceAll('%s', items[i].url || '');
702
+ }
703
+ } else {
704
+ let keys = Object.keys(this.results);
705
+ r = new Array(keys.length);
706
+ for (let i = 0; i < keys.length; i++) {
707
+ r[i] = this.toHTML(keys[i]);
708
+ }
709
+ }
710
+ return r.join('\n');
711
+ }
712
+ }
713
+
714
+ const templates = {
715
+ 'script':
716
+ `<script src="%s"></script>`,
717
+
718
+ 'script:module':
719
+ `<script type="module" src="%s"></script>`,
720
+
721
+ 'style':
722
+ `<link rel="stylesheet" href="%s">`,
723
+ };/**
724
+ * Alias of `os.homedir()`.
725
+ * @type {string}
726
+ */
727
+ const home = os.homedir();
728
+
729
+ /**
730
+ * Returns or overwrites the extension of the given file path.
731
+ * @param {string} file - File path
732
+ * @param {string} [set] - New extension
733
+ * @return {string} the extension, or a modified file path with the new extension
734
+ */
735
+ function ext(file, set = null) {
736
+ let dot = file.lastIndexOf('.');
737
+ return typeof set == 'string'
738
+ ? (dot < 0 ? (file + set) : (file.substring(0, dot) + set))
739
+ : (dot < 0 ? '' : file.substring(dot));
740
+ }
741
+
742
+ /**
743
+ * Searches the given file path in the given directories.
744
+ * @param {string} file - File to find
745
+ * @param {string[]} dirs - Array of directories to search
746
+ * @param {object} [opts] - Options
747
+ * @param {boolean} [opts.allowAbsolute=true] - If true, `file` can be an absolute path
748
+ * @return {string|boolean} found file path, or false if not found
749
+ */
750
+ function find(file, dirs = [], opts = {}) {
751
+ let {allowAbsolute = true} = opts;
752
+ if (allowAbsolute && path.isAbsolute(file)) return fs.existsSync(file) ? file : false;
753
+ for (let i = 0; i < dirs.length; i++) {
754
+ let find = path.join(dirs[i], file);
755
+ if (fs.existsSync(find)) return find;
756
+ }
757
+ return false;
758
+ }
759
+
760
+ /**
761
+ * Replaces the beginning `~` character with `os.homedir()`.
762
+ * @param {string} file - File path
763
+ * @param {string} [replace=os.homedir()] - Replacement
764
+ * @return {string} modified `file`
765
+ */
766
+ function untilde(file, replace = home) {
767
+ if (!file.startsWith('~')) return file;
768
+ if (file.length == 1) return replace;
769
+ if (file.startsWith(path.sep, 1)) return replace + file.substring(1);
770
+ return file;
771
+ }
772
+
773
+ /**
774
+ * Deletes the files in the given directory.
775
+ * @param {string} dir - Directory to clean
776
+ * @param {string|RegExp} [pattern] - File pattern
777
+ * @param {object} [opts] - Options
778
+ * @param {boolean} [opts.recursive=false] - Searches recursively
779
+ * @param {object} [opts.types] - File types to delete
780
+ * @param {boolean} [opts.types.any=false] - Any type
781
+ * @param {boolean} [opts.types.file=true] - Regular file
782
+ * @param {boolean} [opts.types.dir=false] - Directory
783
+ * @param {boolean} [opts.types.symlink=false] - Symbolic link
784
+ * @return {Promise} a promise resolved with the deleted file paths
785
+ */
786
+ function clean(dir, pattern = null, opts = {}) {
787
+ if (pattern && typeof pattern == 'string') pattern = new RegExp(pattern);
788
+ let {
789
+ recursive = false,
790
+ types = {file: true},
791
+ } = opts;
792
+ return fsp__namespace.readdir(dir, {recursive, withFileTypes: true}).then(files => {
793
+ let tasks = [];
794
+ for (let i = 0; i < files.length; i++) {
795
+ let f = files[i];
796
+ if (!types.any) {
797
+ if (f.isFile()) {
798
+ if (!types.file) continue;
799
+ } else if (f.isDirectory()) {
800
+ if (!types.dir) continue;
801
+ } else if (f.isSymbolicLink()) {
802
+ if (!types.symlink) continue;
803
+ }
804
+ }
805
+ f = path.join(dir, f.name);
806
+ if (pattern && !f.match(pattern)) continue;
807
+ tasks.push(fsp__namespace.rm(f, {force: true, recursive: true}).then(() => f));
808
+ }
809
+ return tasks.length ? Promise.all(tasks) : false;
810
+ });
811
+ }
812
+
813
+ /**
814
+ * Copies the given file(s) to another directory
815
+ * @param {string|object|string[]|object[]} src
816
+ * @param {string} dst Base destination directory
817
+ * @return {Promise}
818
+ */
819
+ function copy(src, dst) {
820
+ return Promise.all((Array.isArray(src) ? src : [src]).map(item => {
821
+ let _src, _dst;
822
+ switch (typeof item) {
823
+ case 'object':
824
+ _src = item.src;
825
+ _dst = item.dst;
826
+ break;
827
+ case 'string':
828
+ _src = item;
829
+ break;
830
+ default:
831
+ throw 'invalid type';
832
+ }
833
+ _dst = path.join(dst, _dst || path.basename(_src));
834
+ return fsp__namespace.mkdir(path.dirname(_dst), {recursive: true}).then(fsp__namespace.copyFile(_src, _dst));
835
+ }));
836
+ }
837
+
838
+ /**
839
+ * Returns a Transform stream object with the given function as its transform() method.
840
+ * `fn` must return a string which is to be the new content, or a Promise which resolves a string.
841
+ *
842
+ * @example
843
+ * return gulp.src(src)
844
+ * .pipe(modifyStream((data, enc) => {
845
+ * // do stuff
846
+ * return newData;
847
+ * }));
848
+ *
849
+ * @param {function} fn
850
+ * @return {Transform}
851
+ */
852
+ function modifyStream(fn) {
853
+ return new node_stream.Transform({
854
+ objectMode: true,
855
+ transform(file, enc, done) {
856
+ let r = fn(file.contents.toString(enc), enc);
857
+ if (r instanceof Promise) {
858
+ r.then(modified => {
859
+ file.contents = Buffer.from(modified, enc);
860
+ this.push(file);
861
+ done();
862
+ });
863
+ } else {
864
+ file.contents = Buffer.from(r, enc);
865
+ this.push(file);
866
+ done();
867
+ }
868
+ }
869
+ });
870
+ }var io=/*#__PURE__*/Object.freeze({__proto__:null,AssetImporter:AssetImporter,clean:clean,copy:copy,ext:ext,find:find,home:home,modifyStream:modifyStream,untilde:untilde});const merge = Object.assign;
871
+
872
+ /*!
873
+ * === @amekusa/util.js/test === *
874
+ * MIT License
875
+ *
876
+ * Copyright (c) 2024 Satoshi Soma
877
+ *
878
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
879
+ * of this software and associated documentation files (the "Software"), to deal
880
+ * in the Software without restriction, including without limitation the rights
881
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
882
+ * copies of the Software, and to permit persons to whom the Software is
883
+ * furnished to do so, subject to the following conditions:
884
+ *
885
+ * The above copyright notice and this permission notice shall be included in all
886
+ * copies or substantial portions of the Software.
887
+ *
888
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
889
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
890
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
891
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
892
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
893
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
894
+ * SOFTWARE.
895
+ */
896
+
897
+ /**
898
+ * @private
899
+ */
900
+ function invalid(...args) {
901
+ throw new InvalidTest(...args);
902
+ }
903
+
904
+ class InvalidTest extends Error {
905
+ }
906
+
907
+ function assertProps(obj, props, opts = {}) {
908
+ if (typeof props != 'object') invalid(`'props' must be an object`);
909
+ for (let k in props) {
910
+ let v = props[k];
911
+ if (!(k in obj)) assert.fail(`no such property as '${k}'`);
912
+ assertEqual(obj[k], v, merge({msg: `property '${k}' failed`}, opts));
913
+ }
914
+ }
915
+
916
+ function assertEqual(actual, expected, opts = {}) {
917
+ let equal, deepEqual;
918
+ if (opts.strict) {
919
+ equal = assert.strictEqual;
920
+ deepEqual = assert.deepStrictEqual;
921
+ } else {
922
+ equal = assert.equal;
923
+ deepEqual = assert.deepEqual;
924
+ }
925
+ try {
926
+ if (expected) {
927
+ switch (typeof expected) {
928
+ case 'object':
929
+ let proto = Object.getPrototypeOf(expected);
930
+ if (proto === Object.prototype || proto === Array.prototype)
931
+ return deepEqual(actual, expected);
932
+ return equal(actual, expected);
933
+ }
934
+ }
935
+ return equal(actual, expected);
936
+ } catch (e) {
937
+ if (opts.msg) e.message = opts.msg + '\n' + e.message;
938
+ throw e;
939
+ }
940
+ }
941
+
942
+ function assertType(value, type, msg = '') {
943
+ try {
944
+ if (typeof type == 'string') assert.equal(typeof value, type);
945
+ else assert.ok(value instanceof type);
946
+ } catch (e) {
947
+ if (msg) e.message = msg + '\n' + e.message;
948
+ throw e;
949
+ }
950
+ }
951
+
952
+ /**
953
+ * @param {function} fn
954
+ * @param {Array|object} cases
955
+ * @param {string|function} [assertFn]
956
+ */
957
+ function testFn(fn, cases, opts = {}) {
958
+ let testCase = (c, title) => {
959
+ it(title, () => {
960
+ if (typeof c != 'object') invalid(`a test case must be an object`);
961
+
962
+ // ---- call function ----
963
+ let args = [];
964
+ if ('args' in c) { // args to pass
965
+ if (!Array.isArray(c.args)) invalid(`'args' must be an array`);
966
+ args = c.args;
967
+ delete c.args;
968
+ }
969
+ let r = fn(...args);
970
+
971
+ // ---- check the result ----
972
+ let check = {
973
+ returnType() {
974
+ assertType(r, c.returnType, `return type failed`);
975
+ },
976
+ return() {
977
+ assertEqual(r, c.return, merge({msg: `return value failed`}, opts));
978
+ },
979
+ test() {
980
+ if (typeof c.test != 'function') invalid(`'test' must be a function`);
981
+ c.test(r, ...args);
982
+ }
983
+ };
984
+ for (let k in c) {
985
+ if (check[k]) check[k]();
986
+ else invalid(`invalid property: '${k}' (available properties: ${Object.keys(check).join(', ')})`);
987
+ }
988
+ });
989
+ };
990
+ describe('function: ' + (fn.displayName || fn.name), () => {
991
+ if (Array.isArray(cases)) {
992
+ for (let i = 0; i < cases.length; i++) {
993
+ let c = cases[i];
994
+ let title = `#${i}`;
995
+ if (Array.isArray(c.args)) title += ' ' + c.args.join(', ');
996
+ testCase(c, title);
997
+ }
998
+ } else {
999
+ let keys = Object.keys(cases);
1000
+ for (let i = 0; i < keys.length; i++) {
1001
+ testCase(cases[keys[i]], `#${i} ${keys[i]}`);
1002
+ }
1003
+ }
1004
+ });
1005
+ }
1006
+
1007
+ /**
1008
+ * @param {function} construct - Constructor or function that returns an instance
1009
+ * @param {string} method - Method name
1010
+ * @param {object|object[]} cases - Cases
1011
+ * @param {object} [opts] - Options
1012
+ */
1013
+ function testMethod(construct, method, cases, opts = {}) {
1014
+ let testCase = (c, title) => {
1015
+ it(title, () => {
1016
+ if (typeof c != 'object') invalid(`a test case must be an object`);
1017
+
1018
+ // ---- instantiate ----
1019
+ let obj;
1020
+ if (opts.static) {
1021
+ if ('initArgs' in c) invalid(`'initArgs' is not available for a static method`);
1022
+ if ('prepare' in c) invalid(`'prepare' is not available for a static method`);
1023
+ obj = construct;
1024
+ } else {
1025
+ let initArgs = [];
1026
+ if ('initArgs' in c) {
1027
+ if (!Array.isArray(c.initArgs)) invalid(`'initArgs' must be an array`);
1028
+ initArgs = c.initArgs;
1029
+ delete c.initArgs;
1030
+ }
1031
+ try {
1032
+ obj = new construct(...initArgs);
1033
+ } catch (e) {
1034
+ obj = construct(...initArgs);
1035
+ }
1036
+ if ('prepare' in c) {
1037
+ if (typeof c.prepare != 'function') invalid(`'prepare' must be a function`);
1038
+ c.prepare(obj);
1039
+ delete c.prepare;
1040
+ }
1041
+ }
1042
+
1043
+ // ---- call method ----
1044
+ if (!(method in obj)) invalid(`no such method as '${method}'`);
1045
+ let args = [];
1046
+ if ('args' in c) { // args to pass
1047
+ if (!Array.isArray(c.args)) invalid(`'args' must be an array`);
1048
+ args = c.args;
1049
+ delete c.args;
1050
+ }
1051
+ let r = obj[method](...args);
1052
+
1053
+ // ---- check the result ----
1054
+ let check = {
1055
+ returnsSelf() { // check if returns itself
1056
+ assert.strictEqual(r, obj, `must return self`);
1057
+ },
1058
+ returnType() { // check return type
1059
+ assertType(r, c.returnType, `return type failed`);
1060
+ },
1061
+ return() { // check return value
1062
+ assertEqual(r, c.return, merge({msg: `return failed`}, opts));
1063
+ },
1064
+ props() { // check properties
1065
+ assertProps(obj, c.props, opts);
1066
+ },
1067
+ test() { // custom test
1068
+ if (typeof c.test != 'function') invalid(`'test' must be a function`);
1069
+ c.test(r, obj, ...args);
1070
+ }
1071
+ };
1072
+ for (let k in c) {
1073
+ if (check[k]) check[k]();
1074
+ else invalid(`invalid property: '${k}' (available properties: ${Object.keys(check).join(', ')})`);
1075
+ }
1076
+ });
1077
+ };
1078
+ describe('method: ' + method, () => {
1079
+ if (Array.isArray(cases)) {
1080
+ for (let i = 0; i < cases.length; i++) {
1081
+ let c = cases[i];
1082
+ let title = `#${i}`;
1083
+ if (Array.isArray(c.args)) title += ' ' + c.args.join(', ');
1084
+ testCase(c, title);
1085
+ }
1086
+ } else {
1087
+ let keys = Object.keys(cases);
1088
+ for (let i = 0; i < keys.length; i++) {
1089
+ testCase(cases[keys[i]], `#${i} ${keys[i]}`);
1090
+ }
1091
+ }
1092
+ });
1093
+ }
1094
+
1095
+ /**
1096
+ * @param {function} construct - Constructor or function that returns an instance
1097
+ * @param {object|object[]} cases - Cases
1098
+ * @param {object} [opts] - Options
1099
+ */
1100
+ function testInstance(construct, cases, opts = {}) {
1101
+ let testCase = (c, title) => {
1102
+ it(title, () => {
1103
+ if (typeof c != 'object') invalid(`a test case must be an object`);
1104
+
1105
+ // ---- instantiate ----
1106
+ let args = [];
1107
+ if ('args' in c) {
1108
+ if (!Array.isArray(c.args)) invalid(`'args' must be an array`);
1109
+ args = c.args;
1110
+ delete c.args;
1111
+ }
1112
+ let obj;
1113
+ try {
1114
+ obj = new construct(...args);
1115
+ } catch (e) {
1116
+ obj = construct(...args);
1117
+ }
1118
+
1119
+ // ---- check the result ----
1120
+ let check = {
1121
+ props() { // check properties
1122
+ assertProps(obj, c.props, opts);
1123
+ },
1124
+ test() { // custom check
1125
+ if (typeof c.test != 'function') invalid(`'test' must be a function`);
1126
+ c.test(obj, ...args);
1127
+ }
1128
+ };
1129
+ for (let k in c) {
1130
+ if (check[k]) check[k]();
1131
+ else invalid(`invalid property: '${k}' (available properties: ${Object.keys(check).join(', ')})`);
1132
+ }
1133
+ });
1134
+ };
1135
+ describe(construct.name, () => {
1136
+ if (Array.isArray(cases)) {
1137
+ for (let i = 0; i < cases.length; i++) {
1138
+ let c = cases[i];
1139
+ let title = `#${i}`;
1140
+ if (Array.isArray(c.args)) title += ' ' + c.args.join(', ');
1141
+ testCase(c, title);
1142
+ }
1143
+ } else {
1144
+ let keys = Object.keys(cases);
1145
+ for (let i = 0; i < keys.length; i++) {
1146
+ testCase(cases[keys[i]], `#${i} ${keys[i]}`);
1147
+ }
1148
+ }
1149
+ });
1150
+ }var test=/*#__PURE__*/Object.freeze({__proto__:null,InvalidTest:InvalidTest,assertEqual:assertEqual,assertProps:assertProps,assertType:assertType,testFn:testFn,testInstance:testInstance,testMethod:testMethod});exports.arr=arr;exports.clean=clean$1;exports.dig=dig;exports.gen=gen;exports.io=io;exports.is=is;exports.isEmpty=isEmpty;exports.isEmptyOrFalsey=isEmptyOrFalsey;exports.isEmptyOrFalsy=isEmptyOrFalsy;exports.merge=merge$1;exports.sh=sh;exports.subst=subst;exports.test=test;exports.time=time;exports.web=web;