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.
@@ -0,0 +1,94 @@
1
+ /*!
2
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3
+ Copyright (C) 2022 jeffy-g <hirotom1107@gmail.com>
4
+ Released under the MIT license
5
+ https://opensource.org/licenses/mit-license.php
6
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
7
+ */
8
+ /** [usage]
9
+ > node -i
10
+ let t = require("./progress/test");
11
+ let p = t.init();
12
+ p.run();
13
+ p.stop();
14
+ p = t.init();
15
+ p.run();
16
+ p.stop();
17
+ */
18
+
19
+ const p = require("./index.js");
20
+ const rndspinner = require("./rnd-spinner.js");
21
+
22
+ let fired = 0;
23
+ let pending = 0;
24
+
25
+ /** @type {() => { fired: number; pending: number; errors?: number; }} */
26
+ const cb = () => {
27
+ return {
28
+ fired: ++fired,
29
+ pending: ++pending,
30
+ };
31
+ };
32
+
33
+ let tag = "progress test";
34
+
35
+ /**
36
+ * progress callback
37
+ * @type {() => string}
38
+ */
39
+ const pcb = () => {
40
+ if (cb) {
41
+ const { fired, pending, errors } = cb();
42
+ return `${tag} | error: ${(errors + "").padEnd(3)} | send: ${(fired + "").padStart(3)}, pending: ${(pending + "").padEnd(5)}`;
43
+ } else {
44
+ // error
45
+ return "- - error - -";
46
+ }
47
+ };
48
+
49
+ /** @type {ReturnType<typeof p.createProgressObject>} */
50
+ let progress;
51
+ /**
52
+ * @param {string} frameName
53
+ */
54
+ const getFormatOpt = (frameName) => {
55
+ return {
56
+ fmt: "{frameName}: {msg} | {tick}",
57
+ payload: { frameName },
58
+ };
59
+ };
60
+ /**
61
+ * create progress options as randomly
62
+ */
63
+ const createRandomlyOptions = () => {
64
+ const { name, frames } = rndspinner.getRandomFrame();
65
+ return {
66
+ frames,
67
+ formatOpt: getFormatOpt(name.padStart(rndspinner.MAX_PAD)),
68
+ };
69
+ };
70
+ /**
71
+ * @param {number} fps
72
+ */
73
+ const init = (fps = 30) => {
74
+ /**
75
+ * @typedef {ReturnType<typeof createRandomlyOptions>} TcreateRandomlyOptionsReturn
76
+ */
77
+ // /** @type {string[]} */
78
+ // let frames;
79
+ // /** @type {TcreateRandomlyOptionsReturn["formatOpt"]} */
80
+ // let formatOpt;
81
+ // // TIP: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring
82
+ // (
83
+ // { frames, formatOpt } = createRandomlyOptions()
84
+ // );
85
+ const { frames, formatOpt } = createRandomlyOptions();
86
+ progress = p.createProgressObject(frames, formatOpt, pcb);
87
+ progress.setFPS(fps);
88
+
89
+ return progress;
90
+ };
91
+
92
+ module.exports = {
93
+ init,
94
+ };
package/regex.d.ts ADDED
@@ -0,0 +1,154 @@
1
+ /*!
2
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3
+ // Copyright (C) 2025 jeffy-g <hirotom1107@gmail.com>
4
+ // Released under the MIT license
5
+ // https://opensource.org/licenses/mit-license.php
6
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
7
+ */
8
+ /**
9
+ * @file js-dev-scripts/regex.d.ts
10
+ */
11
+
12
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
13
+ // Basics
14
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
15
+ /**
16
+ * Extracts the literal type from the source property of a RegExp.
17
+ * @template R - A RegExp type.
18
+ */
19
+ export type ReSource<R extends RegExp> = R extends RegExp & { readonly source: infer T } ? T : never;
20
+ export declare function createRegExp<P extends string>(pattern: P, flags?: string): RegExp & {
21
+ readonly source: P;
22
+ };
23
+
24
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
25
+ // Named Capture Groups
26
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
27
+ type FirstChar<S extends string> = S extends `${infer F}${infer _}` ? F : never;
28
+ // Modified ExtractGroupNames:
29
+ // First, extract the part of S after "(?<" into Rest
30
+ // If the first character of Rest is "=" or "!", it is judged to be Lookbehind and recurses without treating it as a capture
31
+ // Otherwise, split Rest into the group name (GroupName) and the ">" separator and extract it,
32
+ // Apply the same process to the remaining After part
33
+ /**
34
+ * Recursively extracts parts that match the format (?<GroupName>...) from a string pattern,
35
+ * without considering nesting, and unions them.
36
+ * @template S - A string pattern.
37
+ */
38
+ export type ExtractGroupNames<S extends string> =
39
+ S extends `${infer _Before}(?<${infer Rest}`
40
+ ? FirstChar<Rest> extends "=" | "!"
41
+ ? ExtractGroupNames<Rest>
42
+ : Rest extends `${infer GroupName}>${infer After}`
43
+ ? GroupName | ExtractGroupNames<After>
44
+ : never
45
+ : never;
46
+ /**
47
+ * Creates an object type with keys as the extracted group names and values as strings.
48
+ * If no groups are found, it results in an empty object.
49
+ * @template R - A RegExp type.
50
+ */
51
+ export type ReGroups<R extends RegExp> = {
52
+ [K in ExtractGroupNames<ReSource<R>>]?: string;
53
+ };
54
+
55
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
56
+ // Capture Groups
57
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
58
+ // CountCaptureGroups type: Get the exact number of capture groups
59
+ /**
60
+ * Counts the exact number of capture groups in a string pattern.
61
+ * @template S - A string pattern.
62
+ * @template Counter - An array used to count the capture groups.
63
+ *
64
+ * @todo FIXME: 多く count してしまう場合あり. 2025/3/18 15:47:57
65
+ * @deprecated text base の parse には限界があり cost が見合わないので、この type は将来削除予定!
66
+ */
67
+ export type CountCaptureGroups<S extends string, Counter extends unknown[] = []> =
68
+ // // まず、エスケープされた左括弧 \(
69
+ // S extends `${infer _Before}\\(${infer Rest}`
70
+ // ? CountCaptureGroups<Rest, Counter> :
71
+ // 次に、通常の左括弧 ( を検出
72
+ S extends `${infer _Before}(${infer Rest}`
73
+ // 非キャプチャグループ (?:...) をスキップ
74
+ ? Rest extends `?:${infer After}`
75
+ ? CountCaptureGroups<After, Counter>
76
+ // Lookahead/Lookbehind (?=...), (?!...), (?<=...), (?<!...) をスキップ
77
+ : Rest extends `?=${infer After}`
78
+ | `?!${infer After}`
79
+ | `?<=${infer After}`
80
+ | `?<!${infer After}`
81
+ ? CountCaptureGroups<After, Counter>
82
+ // それ以外は通常のキャプチャグループとしてカウント
83
+ : CountCaptureGroups<Rest, [...Counter, unknown]>
84
+ : Counter['length'];
85
+
86
+ // Fixed RegExpExecArray type
87
+ /**
88
+ * Represents a fixed version of RegExpExecArray that includes the matched string,
89
+ * captures, and optionally named groups.
90
+ * @template R - A RegExp type.
91
+ * @template S - The source string of the RegExp.
92
+ * @template GroupCount - The number of capture groups.
93
+ */
94
+ export type RegExpExecArrayFixed<
95
+ R extends RegExp,
96
+ S extends ReSource<R> = ReSource<R>,
97
+ GroupCount extends number = CountCaptureGroups<S>
98
+ > = [match: string, ...ExtractCaptures<GroupCount>] & {
99
+ groups?: ReGroups<R>;
100
+ };
101
+
102
+ // Helper type: Generates an array according to the number of capture groups
103
+ /**
104
+ * Generates an array type with a length corresponding to the number of capture groups.
105
+ * @template Count - The number of capture groups.
106
+ * @template Result - The resulting array type.
107
+ */
108
+ export type ExtractCaptures<Count extends number, Result extends unknown[] = []> =
109
+ Result['length'] extends Count
110
+ ? Result : ExtractCaptures<Count, [...Result, string]>;
111
+
112
+ /* ctt
113
+ type Dummy = ExtractCaptures<4>;
114
+ // [string, string, string, string]
115
+ /*/
116
+ //*/
117
+
118
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
119
+ // The Examples
120
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
121
+ // Example 1: Anonymous capture groups
122
+ const re1 = createRegExp("(test)(group)", "g");
123
+ type ExecResult1 = RegExpExecArrayFixed<typeof re1>;
124
+ // ExecResult1 is [string, string, string] & { groups?: Record<string, string | undefined> };
125
+
126
+ // Example 2: Named capture groups
127
+ const re2 = createRegExp("(?<name1>test)(?<name2>group)", "g");
128
+ type ExecResult2 = RegExpExecArrayFixed<typeof re2>;
129
+ // ExecResult2 is [string, string, string] & { groups?: { name1: string; name2: string } };
130
+
131
+ // Verify that the exec result matches the type
132
+ const result = re2.exec("test group") as RegExpExecArrayFixed<typeof re2>;
133
+ if (result) {
134
+ console.log(result[1]); // "test"
135
+ console.log(result.groups?.name1); // "test"
136
+ }
137
+
138
+ export as namespace XRegex;
139
+ /* ctt sample code
140
+ // 補助関数 createRegExp を使って型レベルの情報を保持する RegExp を生成
141
+ function createRegExp<P extends string>(
142
+ pattern: P,
143
+ flags?: string
144
+ ): RegExp & { readonly source: P } {
145
+ return new RegExp(pattern, flags) as RegExp & { readonly source: P };
146
+ }
147
+ const re = createRegExp("(?<token>test)", "ig");
148
+ // type a は "token" というグループ名から導出された型になりますが、
149
+ // groups オブジェクトとしては { token: string } になる
150
+ type Groups = ReGroups<typeof re>; // → { token: string }
151
+ const m = re.exec("this is test")!;
152
+ console.log(m, m.groups);
153
+ /*/
154
+ //*/
@@ -0,0 +1,3 @@
1
+ {
2
+ "version": "1.0.0"
3
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ const fs = require("fs");
3
+ const fflate = require("fflate");
4
+ const { unzip } = fflate;
5
+ /**
6
+ * @param {string} fileName
7
+ */
8
+ const decompress = (fileName) => {
9
+ fs.readFile(fileName, null, (err, data) => {
10
+ if (err) {
11
+ console.error(err);
12
+ } else {
13
+ console.time(fileName);
14
+ unzip(data, (err, unzipped) => {
15
+ // [2022/5/10 3:05:43] webpack.zip: 159.833ms
16
+ console.timeEnd(fileName);
17
+ if (err) {
18
+ console.error(err);
19
+ return;
20
+ }
21
+ Object.entries(unzipped).forEach(([filename, data]) => {
22
+ fs.writeFile(filename, data, null, () => {
23
+ console.log("write:", filename);
24
+ });
25
+ });
26
+ });
27
+ }
28
+ });
29
+ };
30
+ module.exports = decompress;
package/scripts/zip.js ADDED
@@ -0,0 +1,138 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const fflate = require("fflate");
4
+ const { TextEncoder } = require("util");
5
+ // const b = require("node:buffer");
6
+
7
+ // fflate's ZIP API is asynchronous and parallelized (multithreaded)
8
+ const ALREADY_COMPRESSED = [
9
+ "zip",
10
+ "gz",
11
+ "png",
12
+ "jpg",
13
+ "jpeg",
14
+ "pdf",
15
+ "doc",
16
+ "docx",
17
+ "ppt",
18
+ "pptx",
19
+ "xls",
20
+ "xlsx",
21
+ "heic",
22
+ "heif",
23
+ "7z",
24
+ "bz2",
25
+ "rar",
26
+ "gif",
27
+ "webp",
28
+ "webm",
29
+ "mp4",
30
+ "mov",
31
+ "mp3",
32
+ "aifc",
33
+ ];
34
+
35
+ /**
36
+ *
37
+ * @param {string} fileName
38
+ * @param {(out: Uint8Array | NodeJS.ErrnoException) => void} cb
39
+ */
40
+ const fileToU8 = (fileName, cb /* : (out: Uint8Array) => void */) => {
41
+ fs.readFile(fileName, "utf8", (err, data) => {
42
+ if (err) {
43
+ cb(err);
44
+ } else {
45
+ cb(new TextEncoder().encode(data));
46
+ }
47
+ });
48
+ };
49
+
50
+ // Yet again, this is necessary for parallelization.
51
+ /**
52
+ * @param {path.ParsedPath} parsedPath
53
+ * @param {(out: Uint8Array | null) => void} callback
54
+ * @param {string} [comment]
55
+ */
56
+ const processFile = function (parsedPath, callback, comment) {
57
+ const filePath = `${parsedPath.dir}/${parsedPath.base}`;
58
+ fileToU8(filePath, function (data) {
59
+ if (data instanceof Uint8Array) {
60
+ /** @type {fflate.AsyncZippable} */
61
+ const zipObj = {
62
+ [parsedPath.base]: [
63
+ data, {
64
+ // With fflate, we can choose which files we want to compress
65
+ level: ALREADY_COMPRESSED.indexOf(parsedPath.ext) === -1 ? 6 : 0,
66
+ comment,
67
+ },
68
+ ],
69
+ };
70
+
71
+ // If we didn't want to specify options:
72
+ // zipObj[fileName] = buf;
73
+ console.time(filePath);
74
+ fflate.zip(
75
+ zipObj,
76
+ {
77
+ // If you want to control options for every file, you can do so here
78
+ // They are merged with the per-file options (if they exist)
79
+ // mem: 9
80
+ },
81
+ function (err, out) {
82
+ if (err) {
83
+ console.log(err);
84
+ callback(null);
85
+ console.timeEnd(filePath);
86
+ } else {
87
+ // [2022/5/10 3:06:16] webpack.js: 399.832ms
88
+ console.timeEnd(filePath);
89
+ // You may want to try downloading to see that fflate actually works:
90
+ // download(out, 'fflate-demo.zip');
91
+ callback(out);
92
+ }
93
+ },
94
+ );
95
+ } else {
96
+ console.error(data);
97
+ }
98
+ });
99
+ };
100
+
101
+ /**
102
+ * @param {string} inputPath
103
+ * @param {string} [comment]
104
+ * @param {string} [dest]
105
+ */
106
+ module.exports = function zip(inputPath, comment, dest) {
107
+ let actualOutput = dest;
108
+ const pp = path.parse(inputPath);
109
+ processFile(
110
+ pp,
111
+ (out) => {
112
+ if (out) {
113
+ if (!actualOutput) {
114
+ actualOutput = `${pp.dir ? pp.dir + "/" : ""}${pp.name}.zip`;
115
+ }
116
+ fs.writeFile(actualOutput, out, "binary", () => {
117
+ console.log("done!!");
118
+ });
119
+ }
120
+ },
121
+ comment,
122
+ );
123
+ };
124
+ /*
125
+ node -i
126
+ const zip = require("js-dev-scripts/scripts/zip");
127
+ const comment = "sha384-XSSUboqeWltJhmqrtg5XP9Svrg5NFArrGX0Nm4+c6NUtaRXOiidVCaWFbSQXdVFC";
128
+ zip("./lib/webpack.js", comment);
129
+ const comment2 = "sha384-Y7FclS+/O1tb8AfLynrvaZRzlgEfzNlqOB2iX/tQ/BQBu6lK3MsxBlDDeqtFd+2X";
130
+ zip("./lib/typeid-map.js", comment2);
131
+
132
+ [2022/5/11 10:07:34]
133
+ b64=$(echo -n "sha384-XSSUboqeWltJhmqrtg5XP9Svrg5NFArrGX0Nm4+c6NUtaRXOiidVCaWFbSQXdVFC" | base64)
134
+ echo "$b64"
135
+ echo -n "$b64" | base64 -d # OK
136
+
137
+ echo -n "XSSUboqeWltJhmqrtg5XP9Svrg5NFArrGX0Nm4+c6NUtaRXOiidVCaWFbSQXdVFC" | base64 -d # binary data
138
+ */
@@ -0,0 +1,239 @@
1
+ /*!
2
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3
+ Copyright (C) 2023 jeffy-g <hirotom1107@gmail.com>
4
+ Released under the MIT license
5
+ https://opensource.org/licenses/mit-license.php
6
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
7
+ */
8
+ /// <reference path="./tools.d.ts"/>
9
+ /// <reference path="../regex.d.ts"/>
10
+ // @ts-check
11
+ /**
12
+ * @file (C)onvert (J)S to (B)rowser (M)odule
13
+ */
14
+
15
+ /**
16
+ * @import * as Regex from "../regex.d.ts";
17
+ */
18
+
19
+ // [2023-10-30] from https://regex101.com/r/EpuQLT/37
20
+ // CAUTION: PCRE style では、js では使える assertion が使えない場合があるので、適宜書き換えないといけない.
21
+ // 詳細は、 https://regex101.com/r/EpuQLT/38 の regex string と以下を見比べて確認
22
+ // const RE_TEXT = `(?<!\\/\\/\\s*)(?:import|export) # comments
23
+ // \\s*
24
+ // (?:
25
+ // "(?=[.\\/]+)| # character ["] \\x22 e.g - import "../global/index"
26
+ // (?:
27
+ // [\\w_]+\\s+| # default import, e.g - import fs from "./file";
28
+ // (?:[\\w_]+\\s*,\\s*)?\\{[^}]+\\}\\s*| # e.g - import View, { TitiledSphereMesh } from "./view";
29
+ // \\*\\s*as\\s+[\\w_]+\\s+| # e.g - export * as OO from "./rateLimiter";
30
+ // \\*\\s* # e.g - export * from "./rateLimiter";
31
+ // )from\\s*"(?:[.\\/]+) # Positive Lookahead (./|../|../../) ...
32
+ // )
33
+ // (?:[^"]*?)(?:\\.((?:c|m)?js))? # extenstion replaceable
34
+ // (?="\\s*;?)`;
35
+
36
+ /* TODO: 2025/2/15 19:39:29 - regex group name を抽出する utility type
37
+ (?<!\\/\\/|\\/\\/\\s)(?:import|export)
38
+ \\s*
39
+ (?:
40
+ "(?!https?:)(?:
41
+ (?<globalName>(?:[.\\/]+)?[^"]+?)(?:\\.(?<globalExt>(?:c|m)?jsx?))?
42
+ )"|
43
+ \\(\\s*
44
+ "(?<dynamicName>(?:[.\\/]+)?[^"]+?)(?:\\.(?<dynamicExt>(?:c|m)?jsx?))?"
45
+ \\s*\\)|
46
+ (?:(?<clause>.+?|(?:\\w+\\s*,)?\\s*\\{[^}]+\\})\\s*from)\\s*"(?!https?:)
47
+ (?:
48
+ (?<moduleName>(?:[.\\/]+)?[^"]+?)(?:\\.(?<moduleExt>(?:c|m)?jsx?))?
49
+ )"
50
+ )
51
+ \\s*;?
52
+ */
53
+ // /**
54
+ // * @date 2025/2/15 20:11:22
55
+ // * @see https://regex101.com/r/uMMsD4/22
56
+ // */
57
+ const RE_TEXT = String.raw`
58
+ # NOTE: PCRE での検証時は line comment assertion を (?<!\/\/|\/\/\s) とすること (regex101.com)
59
+ (?<!\/\/+\s*)(?:import|export)
60
+ \s*
61
+ (?:
62
+ "(?!https?:)(?=[.\/]+)(?:
63
+ (?<globalName>(?:[.\/]+)?[^"]+?)(?:\.(?<globalExt>(?:c|m)?jsx?))?
64
+ )"|
65
+ \(\s*
66
+ "(?!https?:)(?=[.\/]+)(?<dynamicName>(?:[.\/]+)?[^"]+?)(?:\.(?<dynamicExt>(?:c|m)?jsx?))?"
67
+ \s*\)|
68
+ (?:(?<clause>.+?|(?:\w+\s*,)?\s*\{[^}]+\})\s*from)\s*"(?!https?:)(?=[.\/]+)
69
+ (?:
70
+ (?<moduleName>(?:[.\/]+)?[^"]+?)(?:\.(?<moduleExt>(?:c|m)?jsx?))?
71
+ )"
72
+ )
73
+ (?=\s*;)?
74
+ `;
75
+ // const RE_TEXT = `
76
+ // # NOTE: PCRE での検証時は line comment assertion を (?<!\\/\\/|\\/\\/\\s) とすること
77
+ // (?<!\\/\\/+\\s*)(?:import|export) # line comment でない import/export statement
78
+ // \\s*
79
+ // (?:
80
+ // "(?!https?:)(?=[.\\/]+)(?:
81
+ // (?<globalName>(?:[.\\/]+)?[^"]+?)(?:\\.(?<globalExt>(?:c|m)?jsx?))?
82
+ // )"|
83
+ // \\(\\s*
84
+ // "(?!https?:)(?=[.\\/]+)(?<dynamicName>(?:[.\\/]+)?[^"]+?)(?:\\.(?<dynamicExt>(?:c|m)?jsx?))?"
85
+ // \\s*\\)|
86
+ // (?:(?<clause>.+?|(?:\\w+\\s*,)?\\s*\\{[^}]+\\})\\s*from)\\s*"(?!https?:)(?=[.\\/]+)
87
+ // (?:
88
+ // (?<moduleName>(?:[.\\/]+)?[^"]+?)(?:\\.(?<moduleExt>(?:c|m)?jsx?))?
89
+ // )"
90
+ // )
91
+ // (?=\\s*;)?
92
+ // `;
93
+
94
+ /**
95
+ * @date 2025/2/15 20:11:22
96
+ * @see https://regex101.com/r/uMMsD4/22
97
+ */
98
+ /* ctt
99
+ const reImportExportDetection = createRegExp(
100
+ `(?<!\\/\\/+\\s*)(?:import|export)\\s*(?:"(?!https?:)(?=[.\\/]+)\
101
+ (?:(?<globalName>(?:[.\\/]+)?[^"]+?)(?:\\.(?<globalExt>(?:c|m)?jsx?))?)"|\
102
+ \\(\\s*"(?!https?:)(?=[.\\/]+)(?<dynamicName>(?:[.\\/]+)?[^"]+?)(?:\\.(?<dynamicExt>(?:c|m)?jsx?))?"\\s*\\)|\
103
+ (?:(?<clause>.+?|(?:\\w+\\s*,)?\\s*\\{[^}]+\\})\\s*from)\\s*"(?!https?:)(?=[.\\/]+)(?:(?<moduleName>(?:[.\\/]+)?[^"]+?)\
104
+ (?:\\.(?<moduleExt>(?:c|m)?jsx?))?)")(?=\\s*;)?`, "g"
105
+ );
106
+ /*/
107
+ const reImportExportDetection = new RegExp(
108
+ RE_TEXT.replace(/\s*\(\?\#.*\)\s*$|(?<!\\)\#\s*.*$|\s+/gm, ""), "g",
109
+ );
110
+ //*/
111
+
112
+ // /**
113
+ // * @type {typeof XRegex["createRegExp"]}
114
+ // */
115
+ // function createRegExp(pattern, flags) {
116
+ // return /** @type {RegExp & { readonly source: typeof pattern }} */(new RegExp(pattern, flags));
117
+ // }
118
+ /* ctt 2025/3/18 13:46:03 OK, works
119
+ // @ts-expect-error
120
+ let gg = /** @type {XRegex.RegExpExecArrayFixed<typeof reImportExportDetection>} * /(reImportExportDetection.exec("test"));
121
+ gg.groups?.dynamicExt
122
+ /*/
123
+ //*/
124
+ // function createRegExp<P extends string>(
125
+ // pattern: P,
126
+ // flags?: string
127
+ // ): RegExp & { readonly source: P } {
128
+ // return new RegExp(pattern, flags) as RegExp & { readonly source: P };
129
+ // }
130
+
131
+ /**
132
+ * Generates a replacer function to update import/export statements with a new file extension.
133
+ *
134
+ * @param {string} ext - The new file extension to use.
135
+ * @returns {TRegexImportExportDetectorReplacer} A function to update import/export statements with the specified file extension.
136
+ * @date 2025/2/16 18:38:39
137
+ */
138
+ function getReplacer(ext) {
139
+ /**
140
+ * NOTE: Replacement is only performed with the syntax "$` is $1".
141
+ * String concatenation is not allowed.
142
+ */
143
+ // before match: $` NOTE: "$` is $1" のような使い方でないと置換されない (string concatnation is not allowed)
144
+ // after match: $'
145
+ // TIP: debug on chrome, snippet: detect-import-export-regex.js
146
+ /** @type {TRegexImportExportDetectorReplacer} */
147
+ const replacer = (
148
+ $0, _1, _2, _3, _4, _5, _6, _7, _o, _s, { clause, dynamicName, globalName, moduleName }
149
+ // $0, $1, $2, $3, $4, $5, $6, $7, offset, source, groups
150
+ ) => {
151
+ // const {
152
+ // clause, // dynamicExt,
153
+ // dynamicName,// globalExt,
154
+ // globalName, // moduleExt,
155
+ // moduleName,
156
+ // } = groups;
157
+ const kw = $0.startsWith("import") ? "import" : "export";
158
+ let whichName = /** @type {string} */ (
159
+ globalName || moduleName || dynamicName
160
+ );
161
+ if (whichName[whichName.length - 1] === "/") {
162
+ whichName += "index";
163
+ }
164
+ return `${kw}${globalName ? ` "${whichName}.${ext}"` : clause ? ` ${clause} from "${whichName}.${ext}"` : `("${whichName}.${ext}")`}`;
165
+ };
166
+ return replacer;
167
+ }
168
+
169
+ /**
170
+ * @returns {TJSToolEntry}
171
+ */
172
+ module.exports = () => {
173
+ return {
174
+ taskName: "(C)onvert (J)S to (B)rowser (M)odule",
175
+ /**
176
+ * about regex: see [[tools.js] regex - (C)onvert (J)S to (B)rowser (M)odule](https://regex101.com/r/EpuQLT/37)
177
+ */
178
+ fn() {
179
+ // DEVNOTE: 2020/5/27 12:11:11 - https://regex101.com/r/EpuQLT/21
180
+ // DEVNOTE: 2022/4/19 7:28:28 - https://regex101.com/r/EpuQLT/23
181
+ // DEVNOTE: 2023/10/30 - https://regex101.com/r/EpuQLT/24
182
+ // -> supports [import*as e from"../"] etc
183
+ // DEVNOTE: 2023/11/06 - https://regex101.com/r/EpuQLT/32
184
+ // -> fix unstable detection
185
+ // const bp = params.basePath;
186
+ // const bases = Array.isArray(bp)? bp: typeof bp === "string" && bp.length ? [bp] : void 0;
187
+ const bases = getBasePaths();
188
+ const ext = params.ext || "js";
189
+ /** ### regex summary
190
+ * ```perl
191
+ * (?:import|export) # comments
192
+ * \s*
193
+ * (?:
194
+ * "(?=[.\/]+)| # character ["] \x22 e.g - import "../global/index"
195
+ * (?:
196
+ * [\w_]+\s+| # default import, e.g - import fs from "./file";
197
+ * (?:[\w_]+\s*,\s*)?\{[^}]+\}\s*| # e.g - import View, { TitiledSphereMesh } from "./view";
198
+ * \*\s*as\s+[\w_]+\s+| # e.g - export * as OO from "./rateLimiter";
199
+ * \*\s* # e.g - export * from "./rateLimiter";
200
+ * )from\s*"(?:[.\/]+) # Positive Lookahead (./|../|../../) ...
201
+ * )
202
+ * (?:[^"]*)
203
+ * (?<!\.js) # Negative Lookbehind not(/\.(?:c|m)?js/)
204
+ * (?="\s*;?)
205
+ * ```
206
+ * @see regex details see {@link https://regex101.com/r/EpuQLT/32 regex101.com}
207
+ */
208
+ // const replacer = (/** @type {string} */$0, /** @type {string} */$1) => {
209
+ // if ($0[$0.length - 1] === "/") {
210
+ // return `${$0}index.${ext}`;
211
+ // }
212
+ // return $1? $0.replace(`.${$1}`, `.${ext}`): `${$0}.${ext}`;
213
+ // };
214
+ // const bs = isArray(params.basePath)? params.basePath[0]: params.basePath;
215
+ processSources(
216
+ /** @type {string} */ (this.taskName), (data) => {
217
+ reImportExportDetection.lastIndex = 0;
218
+ return data.replace(reImportExportDetection, getReplacer(ext));
219
+ }, {
220
+ bases,
221
+ // base: bs || ""
222
+ },
223
+ );
224
+ },
225
+ get help() {
226
+ return `${this.taskName}
227
+ ex - jstool -cmd cjbm [-root ./build | -basePath "./dist/esm,extra-tests/mini-semaphore"] [-ext js] [-targets "['core.js', 'object.js']"]
228
+ note:
229
+ root - Recursively searches for files.
230
+ This option is useful, but if there are directories that need to be avoided, use the 'basePath' option
231
+ basePath - can be "<path>,<path>,..." (array type arg)
232
+ ext - specifies the module extension for import clauses. default is "js"
233
+ targets - specify this if you want to apply it only to a specific file.
234
+ the file specified here should be directly under \`basePath\`!
235
+ value must be array type arg, "['<path>', '<path>',...]" or "<path>,<path>,..."
236
+ `;
237
+ },
238
+ };
239
+ };