vivth 1.1.2 → 1.2.1

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.
Files changed (107) hide show
  1. package/README.md +1215 -433
  2. package/README.src.md +5 -2
  3. package/bun.lock +228 -0
  4. package/index.mjs +17 -4
  5. package/package.json +6 -3
  6. package/src/bundler/CompileJS.mjs +258 -0
  7. package/src/bundler/CreateESPlugin.mjs +24 -0
  8. package/src/bundler/EsBundler.mjs +27 -13
  9. package/src/bundler/FSInline.mjs +57 -0
  10. package/src/bundler/FSInlineAnalyzer.mjs +197 -0
  11. package/src/bundler/FSInlineBundled.mjs +34 -0
  12. package/src/bundler/adds/ToBundledJSPlugin.mjs +77 -0
  13. package/src/bundler/adds/externals.mjs +8 -0
  14. package/src/bundler/adds/pluginVivthBundle.mjs +5 -0
  15. package/src/class/Console.mjs +48 -27
  16. package/src/class/Derived.mjs +55 -7
  17. package/src/class/Effect.mjs +100 -39
  18. package/src/class/EnvSignal.mjs +5 -5
  19. package/src/class/EventSignal.mjs +55 -5
  20. package/src/class/FileSafe.mjs +124 -0
  21. package/src/class/ListDerived.mjs +6 -3
  22. package/src/class/ListSignal.mjs +11 -11
  23. package/src/class/LitExp.mjs +405 -0
  24. package/src/class/Paths.mjs +27 -1
  25. package/src/class/QChannel.mjs +79 -28
  26. package/src/class/SafeExit.mjs +31 -11
  27. package/src/class/Setup.mjs +5 -2
  28. package/src/class/Signal.mjs +26 -24
  29. package/src/class/WorkerMainThread.mjs +100 -133
  30. package/src/class/WorkerMainThreadBundled.mjs +216 -0
  31. package/src/class/WorkerThread.mjs +38 -32
  32. package/src/common/Base64URL.mjs +10 -5
  33. package/src/common/Base64URLFromFile.mjs +24 -0
  34. package/src/common/keys.mjs +3 -0
  35. package/src/doc/JSautoDOC.mjs +32 -56
  36. package/src/doc/parsedFile.mjs +37 -36
  37. package/src/function/CreateImmutable.mjs +9 -9
  38. package/src/function/EventCheck.mjs +2 -2
  39. package/src/function/EventObject.mjs +5 -5
  40. package/src/function/GetRuntime.mjs +38 -0
  41. package/src/function/IsAsync.mjs +2 -2
  42. package/src/function/LazyFactory.mjs +13 -13
  43. package/src/function/Timeout.mjs +2 -2
  44. package/src/function/Try.mjs +17 -12
  45. package/src/function/TryAsync.mjs +5 -5
  46. package/src/function/TrySync.mjs +5 -5
  47. package/src/function/TsToMjs.mjs +5 -4
  48. package/src/types/LitExpKeyType.mjs +5 -0
  49. package/src/types/QCBReturn.mjs +1 -1
  50. package/src/types/Runtime.mjs +7 -0
  51. package/types/dev/fsInline.d.mts +1 -0
  52. package/types/dev/test.d.mts +1 -0
  53. package/types/dev/testWorker.d.mts +7 -0
  54. package/types/dev/testbundle.d.mts +1 -0
  55. package/types/dev/workerThreadClass.d.mts +13 -0
  56. package/types/index.d.mts +13 -4
  57. package/types/src/bundler/CompileJS.d.mts +78 -0
  58. package/types/src/bundler/CreateESPlugin.d.mts +17 -0
  59. package/types/src/bundler/EsBundler.d.mts +33 -4
  60. package/types/src/bundler/FSInline.d.mts +43 -0
  61. package/types/src/bundler/FSInlineAnalyzer.d.mts +36 -0
  62. package/types/src/bundler/FSInlineBundled.d.mts +22 -0
  63. package/types/src/bundler/adds/ToBundledJSPlugin.d.mts +16 -0
  64. package/types/src/bundler/adds/externals.d.mts +1 -0
  65. package/types/src/bundler/adds/pluginVivthBundle.d.mts +1 -0
  66. package/types/src/class/Console.d.mts +36 -5
  67. package/types/src/class/Derived.d.mts +132 -5
  68. package/types/src/class/Effect.d.mts +106 -7
  69. package/types/src/class/EnvSignal.d.mts +8 -8
  70. package/types/src/class/EventSignal.d.mts +151 -7
  71. package/types/src/class/FileSafe.d.mts +90 -0
  72. package/types/src/class/ListDerived.d.mts +8 -5
  73. package/types/src/class/ListSignal.d.mts +123 -18
  74. package/types/src/class/LitExp.d.mts +361 -0
  75. package/types/src/class/Paths.d.mts +18 -1
  76. package/types/src/class/QChannel.d.mts +69 -22
  77. package/types/src/class/SafeExit.d.mts +24 -9
  78. package/types/src/class/Setup.d.mts +6 -5
  79. package/types/src/class/Signal.d.mts +73 -17
  80. package/types/src/class/WorkerMainThread.d.mts +39 -37
  81. package/types/src/class/WorkerMainThreadBundled.d.mts +85 -0
  82. package/types/src/class/WorkerThread.d.mts +32 -27
  83. package/types/src/common/Base64URL.d.mts +22 -1
  84. package/types/src/common/Base64URLFromFile.d.mts +16 -0
  85. package/types/src/common/keys.d.mts +1 -0
  86. package/types/src/doc/JSautoDOC.d.mts +3 -19
  87. package/types/src/doc/parsedFile.d.mts +72 -13
  88. package/types/src/function/CreateImmutable.d.mts +27 -2
  89. package/types/src/function/EventCheck.d.mts +15 -0
  90. package/types/src/function/EventObject.d.mts +17 -2
  91. package/types/src/function/GetRuntime.d.mts +2 -0
  92. package/types/src/function/IsAsync.d.mts +18 -0
  93. package/types/src/function/LazyFactory.d.mts +35 -2
  94. package/types/src/function/Timeout.d.mts +16 -0
  95. package/types/src/function/Try.d.mts +52 -1
  96. package/types/src/function/TryAsync.d.mts +22 -1
  97. package/types/src/function/TrySync.d.mts +16 -1
  98. package/types/src/function/TsToMjs.d.mts +19 -0
  99. package/types/src/types/LitExpKeyType.d.mts +1 -0
  100. package/types/src/types/QCBReturn.d.mts +1 -1
  101. package/types/src/types/Runtime.d.mts +1 -0
  102. package/dev/index.mjs +0 -28
  103. package/src/bundler/CompileMJS.mjs +0 -110
  104. package/src/function/WriteFileSafe.mjs +0 -37
  105. package/types/src/bundler/A.d.mts +0 -1
  106. package/types/src/bundler/CompileMJS.d.mts +0 -8
  107. package/types/src/function/WriteFileSafe.d.mts +0 -2
@@ -0,0 +1,258 @@
1
+ // @ts-check
2
+
3
+ import { readFile, readdir } from 'node:fs/promises';
4
+ import { join, extname, basename, dirname } from 'node:path';
5
+ import { platform } from 'node:os';
6
+
7
+ import { exec } from 'pkg';
8
+
9
+ import { EsBundler } from './EsBundler.mjs';
10
+ import { FileSafe } from '../class/FileSafe.mjs';
11
+ import { TryAsync } from '../function/TryAsync.mjs';
12
+ import { FSInlineAnalyzer } from './FSInlineAnalyzer.mjs';
13
+
14
+ /**
15
+ * @typedef {'win32' | 'linux' | 'darwin' | string} PlatformKey
16
+ */
17
+
18
+ let binaryExtension = undefined;
19
+
20
+ /**
21
+ * @param {Record<string, string[]|string>} compilerOptions
22
+ * @returns {string[]}
23
+ */
24
+ const generateFlagsValue = (compilerOptions) => {
25
+ const options = [];
26
+ for (const flag in compilerOptions) {
27
+ const value = compilerOptions[flag];
28
+ options.push(`--${flag}`);
29
+ if (!Array.isArray(value)) {
30
+ if (value) {
31
+ options.push(value);
32
+ }
33
+ continue;
34
+ }
35
+ options.push(value.filter((val) => !!val).join(','));
36
+ }
37
+ return options;
38
+ };
39
+ /**
40
+ * Maps Node.js platform to binary file extension.
41
+ *
42
+ * @returns {string} extension including dot (e.g. '.exe', '')
43
+ */
44
+ const getBinaryExtension = () => {
45
+ if (!binaryExtension) {
46
+ switch (platform()) {
47
+ case 'win32':
48
+ binaryExtension = '.exe';
49
+ break;
50
+ case 'linux':
51
+ binaryExtension = ''; // Linux binaries typically have no extension
52
+ break;
53
+ case 'darwin':
54
+ binaryExtension = ''; // macOS binaries typically have no extension
55
+ break;
56
+ default:
57
+ binaryExtension = ''; // fallback for unknown platforms
58
+ break;
59
+ }
60
+ }
61
+ return binaryExtension;
62
+ };
63
+ /**
64
+ * @description
65
+ * - function to compile `.ts`|`.mts`|`.mjs` file, into a single executable;
66
+ * - also generate js representation;
67
+ * - uses [pkg](https://www.npmjs.com/package/pkg), [bun](https://bun.com/docs/bundler/executables), and [deno](https://docs.deno.com/runtime/reference/cli/compile/) compiler under the hood;
68
+ * >- they are used only as packaging agent, and doesn't necessarily supports their advanced feature, such as, assets bundling(use [FSInline](#fsinline) instead);
69
+ * >- `WorkerThread` will be converted to inline using `FSInline` too;
70
+ *
71
+ * !!!WARNING!!!
72
+ * !!!WARNING!!!
73
+ * !!!WARNING!!!
74
+ *
75
+ * - This function does not obfuscate and will not prevent decompilation. Do not embed environment variables or sensitive information inside `options.entryPoint`;
76
+ * - It is designed for quick binarization, allowing execution on machines without `Node.js`, `Bun`, or `Deno` installed;
77
+ * - The resulting binary will contain `FSInline` and `WorkerMainThread` target paths Buffers, which are loaded into memory at runtime. If your logic depends on the file system, use `node:fs` or `node:fs/promises` APIs and ship external files alongside the binary (not compiled);
78
+ *
79
+ * !!!WARNING!!!
80
+ * !!!WARNING!!!
81
+ * !!!WARNING!!!
82
+ *
83
+ * @param {Object} options
84
+ * @param {string} options.entryPoint
85
+ * - need to be manually prefixed;
86
+ * @param {BufferEncoding} [options.encoding]
87
+ * - write and read encoding for the sources;
88
+ * - default: `utf-8`;
89
+ * @param {boolean} options.minifyFirst
90
+ * - minify the bundle before compilation;
91
+ * @param {string} options.outDir
92
+ * - need manual prefix;
93
+ * @param {'pkg'|'bun'|'deno'} [options.compiler]
94
+ * - default: no comilation, just bundling;
95
+ * - `bun` and `pkg` is checked, if there's bug on `deno`, please report on github for issues;
96
+ * @param {Record<string, string>} [options.compilerArguments]
97
+ * - `key` are to used as `--keyName`;
98
+ * - value are the following value of the key;
99
+ * - no need to add the output/outdir, as it use the `options.outDir`;
100
+ * @return {ReturnType<TryAsync<{compileResult:Promise<any>,
101
+ * commandCalled: string;
102
+ * compiledBinFile: string;
103
+ * bundledJSFile:string
104
+ * }>>}
105
+ * @example
106
+ * import { join } from 'node:path';
107
+ *
108
+ * import { CompileJS, Paths } from 'vivth';
109
+ *
110
+ * const [[resultPkg, errorPkg], [resultBun, errorBun]] = await Promise.all([
111
+ * CompileJS({
112
+ * entryPoint: join(Paths.root, '/dev'),
113
+ * minifyFirst: true,
114
+ * outDir: join(Paths.root, '/dev-pkg'),
115
+ * compiler: 'pkg',
116
+ * compilerArguments: {
117
+ * target: ['node18-win-x64'],
118
+ * }
119
+ * }),
120
+ * CompileJS({
121
+ * entryPoint: join(Paths.root, '/dev'),
122
+ * minifyFirst: true,
123
+ * outDir: join(Paths.root, '/dev-pkg'),
124
+ * compiler: 'bun',
125
+ * compilerArguments: {
126
+ * target: ['bun-win-x64'],
127
+ * }
128
+ * }),
129
+ * ])
130
+ */
131
+ export async function CompileJS({
132
+ entryPoint,
133
+ minifyFirst,
134
+ encoding = 'utf-8',
135
+ outDir,
136
+ compiler = undefined,
137
+ compilerArguments = undefined,
138
+ }) {
139
+ return await TryAsync(async () => {
140
+ /**
141
+ * @type {'cjs'|'esm'|'iife'}
142
+ */
143
+ let format;
144
+ switch (compiler) {
145
+ case 'pkg':
146
+ format = 'cjs';
147
+ break;
148
+ case 'bun':
149
+ case 'deno':
150
+ format = 'esm';
151
+ break;
152
+ default:
153
+ minifyFirst = true;
154
+ format = 'esm';
155
+ break;
156
+ }
157
+ const extOfSource = extname(entryPoint);
158
+ switch (extOfSource) {
159
+ case '.mts':
160
+ case '.ts':
161
+ case '.mjs':
162
+ break;
163
+ default:
164
+ throw new Error(
165
+ `extention mismatch: "${extOfSource}", should be one of:".mjs"|".mts"|".ts"`
166
+ );
167
+ }
168
+ const sourceText = await readFile(entryPoint, { encoding });
169
+ const [bundledPrep, errorPrep] = await EsBundler(
170
+ {
171
+ content: sourceText,
172
+ extension: extOfSource,
173
+ root: dirname(entryPoint),
174
+ withBinHeader: compiler ? true : false,
175
+ },
176
+ {
177
+ minify: minifyFirst,
178
+ format,
179
+ }
180
+ );
181
+ if (errorPrep) {
182
+ throw errorPrep;
183
+ }
184
+ const [analyzedBundled, errorAnalyze] = await FSInlineAnalyzer.finalContent(
185
+ bundledPrep,
186
+ format
187
+ );
188
+ if (errorAnalyze) {
189
+ throw errorAnalyze;
190
+ }
191
+ const outputBaseNameNoExt = join(outDir, basename(entryPoint).replace(extOfSource, ''));
192
+ const bundledJSFile = `${outputBaseNameNoExt}${format === 'cjs' ? '.cjs' : '.mjs'}`;
193
+ await FileSafe.write(bundledJSFile, analyzedBundled, { encoding });
194
+ const compiledBinFile = `${outputBaseNameNoExt}${getBinaryExtension()}`;
195
+ switch (compiler) {
196
+ case 'pkg': {
197
+ const commandCalled = [
198
+ bundledJSFile,
199
+ ...generateFlagsValue(compilerArguments),
200
+ '--output',
201
+ compiledBinFile,
202
+ ];
203
+ return {
204
+ compileResult: await exec(commandCalled),
205
+ commandCalled: `pkg ${commandCalled.join(' ')}`,
206
+ compiledBinFile,
207
+ bundledJSFile,
208
+ };
209
+ }
210
+ case 'bun': {
211
+ const commandCalled = [
212
+ 'build',
213
+ bundledJSFile,
214
+ '--compile',
215
+ ...generateFlagsValue(compilerArguments),
216
+ '--outfile',
217
+ compiledBinFile,
218
+ ];
219
+ return {
220
+ compileResult: await Bun.spawn(['bun', ...commandCalled]),
221
+ commandCalled: `bun ${commandCalled.join(' ')}`,
222
+ compiledBinFile,
223
+ bundledJSFile,
224
+ };
225
+ }
226
+ case 'deno': {
227
+ const commandCalled = [
228
+ 'compile',
229
+ ...generateFlagsValue(compilerArguments),
230
+ '--output',
231
+ compiledBinFile,
232
+ bundledJSFile,
233
+ '--verbose',
234
+ ];
235
+ // @ts-expect-error
236
+ const command = new Deno.Command('deno', {
237
+ args: commandCalled,
238
+ stdout: 'piped',
239
+ stderr: 'piped',
240
+ });
241
+ return {
242
+ compileResult: await command.output(),
243
+ commandCalled: `deno ${commandCalled.join(' ')}`,
244
+ compiledBinFile,
245
+ bundledJSFile,
246
+ };
247
+ }
248
+ default: {
249
+ return {
250
+ compileResult: undefined,
251
+ commandCalled: undefined,
252
+ compiledBinFile: undefined,
253
+ bundledJSFile,
254
+ };
255
+ }
256
+ }
257
+ });
258
+ }
@@ -0,0 +1,24 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @description
5
+ * - typed esbuild Plugin generator;
6
+ * @param {string} name
7
+ * @param {import('esbuild').Plugin["setup"]} setup
8
+ * @returns {import('esbuild').Plugin}
9
+ * @example
10
+ * import { CreateESPlugin } from 'vivth';
11
+ *
12
+ * export const pluginAddCopyRight = CreateESPlugin(
13
+ * 'MyCopyrightDeclaration',
14
+ * async (build) => {
15
+ * // build script;
16
+ * }
17
+ * );
18
+ */
19
+ export function CreateESPlugin(name, setup) {
20
+ return {
21
+ name: JSON.stringify({ 'vivth:CustomESBuildPlugin': name }),
22
+ setup,
23
+ };
24
+ }
@@ -5,6 +5,8 @@ import { build } from 'esbuild';
5
5
  import { Console } from '../class/Console.mjs';
6
6
  import { Paths } from '../class/Paths.mjs';
7
7
  import { TryAsync } from '../function/TryAsync.mjs';
8
+ import { pluginVivthBundle } from './adds/pluginVivthBundle.mjs';
9
+ import { externals } from './adds/externals.mjs';
8
10
 
9
11
  /**
10
12
  * @description
@@ -13,15 +15,18 @@ import { TryAsync } from '../function/TryAsync.mjs';
13
15
  * @param {Object} options
14
16
  * @param {string} options.content
15
17
  * - the code can also uses composites from the result from multiple readFiles;
16
- * - the import statements on the content should use absolute path from project root, prefixed with forward slash;
17
- * @param {string} options.extension
18
- * @param {boolean} [options.asBinary]
19
- * @param {Omit<Parameters<build>[0], 'entryPoints'|'bundle'|'write'|'format'|'sourcemap'|'external'|'stdin'>} [esbuildOptions]
20
- * @returns {Promise<ReturnType<typeof TryAsync<string>>>}
18
+ * @param {string} options.root
19
+ * - use dirname of said fileString path;
20
+ * @param {'.mts'|'.ts'|'.mjs'} options.extension
21
+ * @param {boolean} [options.withBinHeader]
22
+ * @param {Omit<Parameters<build>[0],
23
+ * 'entryPoints'|'bundle'|'write'|'sourcemap'>
24
+ * } [esbuildOptions]
25
+ * @returns {ReturnType<typeof TryAsync<string>>}
21
26
  * @example
22
27
  * import { EsBundler } from 'vivth';
23
28
  *
24
- * const bundledString = EsBundler(,
29
+ * const bundledString = EsBundler(
25
30
  * {
26
31
  * content: ``,
27
32
  * extension: '.mts',
@@ -31,7 +36,10 @@ import { TryAsync } from '../function/TryAsync.mjs';
31
36
  * ...esbuildOptions,
32
37
  * });
33
38
  */
34
- export const EsBundler = async ({ content, extension, asBinary = false }, esbuildOptions = {}) => {
39
+ export async function EsBundler(
40
+ { content, extension, root, withBinHeader = false },
41
+ esbuildOptions = {}
42
+ ) {
35
43
  return await TryAsync(async () => {
36
44
  /** @type {Parameters<build>[0]['stdin']['loader']} */
37
45
  let loader;
@@ -52,28 +60,34 @@ export const EsBundler = async ({ content, extension, asBinary = false }, esbuil
52
60
  Console.error(error);
53
61
  throw new Error(JSON.stringify(error));
54
62
  }
63
+ esbuildOptions.external = [...(esbuildOptions?.external ?? []), ...externals];
64
+ esbuildOptions.plugins = [...(esbuildOptions?.plugins ?? []), pluginVivthBundle];
55
65
  const result = await build({
56
66
  target: 'esnext',
57
67
  platform: 'node',
68
+ format: 'esm',
58
69
  ...esbuildOptions,
59
70
  stdin: {
60
71
  contents: content,
61
72
  loader,
62
- resolveDir: Paths.root,
73
+ resolveDir: root ?? Paths.root,
74
+ ...esbuildOptions.banner,
63
75
  },
64
- format: 'esm',
65
76
  bundle: true,
66
77
  write: false,
67
78
  sourcemap: false,
68
- external: [],
69
79
  banner: {
70
- js: asBinary ? '#!/usr/bin/env node' : '',
80
+ js: withBinHeader ? '#!/usr/bin/env node' : '',
81
+ ...esbuildOptions.banner,
71
82
  },
72
83
  });
73
84
  if (result.warnings?.length) {
74
85
  Console.warn(result.warnings.map((w) => w.text).join('\n'));
75
86
  }
76
87
  const resString = result.outputFiles?.[0]?.text;
77
- return resString ?? '';
88
+ if (!resString) {
89
+ return '';
90
+ }
91
+ return resString;
78
92
  });
79
- };
93
+ }
@@ -0,0 +1,57 @@
1
+ // @ts-check
2
+
3
+ import { join } from 'node:path';
4
+ import { readFile } from 'node:fs/promises';
5
+
6
+ import { Paths } from '../class/Paths.mjs';
7
+
8
+ /**
9
+ * @description
10
+ * - class helper to inline file;
11
+ * - use only if you are planning to use [CompileJS](#compilejs);
12
+ */
13
+ export class FSInline {
14
+ /**
15
+ * @description
16
+ * - declare entrypoint of file inlining;
17
+ * >- on the dev time, it's just regullar `readFile` from `node:fs/promises`;
18
+ * >- on the compiled, it will read file from `FSInline.vivthFSInlinelists`
19
+ * @param {string} filePathFromProject
20
+ * - doesn't require prefix;
21
+ * @returns {Promise<Buffer<ArrayBuffer>>}
22
+ * @example
23
+ * import { FSInline } from 'vivth';
24
+ *
25
+ * (await FSInline.vivthFSInlineFile('/assets/text.txt')).toString('utf-8');
26
+ */
27
+ static vivthFSInlineFile = async (filePathFromProject) => {
28
+ filePathFromProject = Paths.normalizesForRoot(filePathFromProject);
29
+ const fullAbsolutePath = join(Paths.root, filePathFromProject);
30
+ return Buffer.from(await readFile(fullAbsolutePath));
31
+ };
32
+ /**
33
+ * @description
34
+ * - declare entrypoint of file inlining, include all files on `dir` and `subdir` that match the `fileRule`;
35
+ * @param {string} dirPathFromProject
36
+ * - doesn't require prefix;
37
+ * @param {RegExp} fileRule
38
+ * @returns {Promise<typeof FSInline["vivthFSInlineFile"]>}
39
+ * @example
40
+ * import { FSInline } from 'vivth';
41
+ *
42
+ * export const pngAssets = await FSInline.vivthFSInlineDir('/assets', /.png$/g);
43
+ */
44
+ static vivthFSInlineDir = async (dirPathFromProject, fileRule) => {
45
+ dirPathFromProject = Paths.normalizesForRoot(dirPathFromProject);
46
+ return (path_) => FSInline.vivthFSInlineFile(join(dirPathFromProject, path_));
47
+ };
48
+ /**
49
+ * @description
50
+ * - placeholder for FSInline;
51
+ * - it's remain publicly accessible so it doesn't mess with regex analyze on bundle;
52
+ * - shouldn't be manually accessed;
53
+ * >- access via `FSInline.vivthFSInlineFile` or `FSInline.vivthFSInlineDir`;
54
+ * @type {Record<string, Buffer<ArrayBuffer>>}
55
+ */
56
+ static vivthFSInlinelists = {};
57
+ }
@@ -0,0 +1,197 @@
1
+ // @ts-check
2
+
3
+ import { dirname, extname, join, relative } from 'node:path';
4
+ import { readFile, readdir } from 'node:fs/promises';
5
+
6
+ import { Paths } from '../class/Paths.mjs';
7
+ import { LitExp } from '../class/LitExp.mjs';
8
+ import { TryAsync } from '../function/TryAsync.mjs';
9
+ import { FSInline } from './FSInline.mjs';
10
+ import { EsBundler } from './EsBundler.mjs';
11
+
12
+ /**
13
+ * @param {string} str
14
+ * @returns {RegExp}
15
+ */
16
+ const hydrateRegex = (str) => {
17
+ const match = str.match(/^\/(.*)\/([a-z]*)$/);
18
+ if (!match) throw new Error('Invalid regex string format');
19
+
20
+ const [, pattern, flags] = match;
21
+ return new RegExp(pattern, flags);
22
+ };
23
+
24
+ /**
25
+ * @description
26
+ * - collections of static method to analyze content for `FSInline`;
27
+ */
28
+ export class FSInlineAnalyzer {
29
+ /**
30
+ * @description
31
+ * - to be used on bundled content;
32
+ * @param {string} content
33
+ * @param {'cjs'|'esm'} format
34
+ * @returns {ReturnType<typeof TryAsync<string>>}
35
+ * @example
36
+ * import { readFile } from 'node:fs/promises';
37
+ *
38
+ * import { FSInlineAnalyzer } from 'vivth';
39
+ *
40
+ * const [resultFinalContent, errorFinalContent] = await FSInlineAnalyzer.finalContent(
41
+ * await readFile('./resultESBunlded.mjs', {encoding: 'utf-8'}),
42
+ * 'esm'
43
+ * );
44
+ */
45
+ static finalContent = async (content, format) => {
46
+ return await TryAsync(async () => {
47
+ const [literalFile, errorPrepareFile] = LitExp.prepare({
48
+ FSInline: /\w*/,
49
+ method: /\.vivthFSInlineFile\s*?\(\s*?['"]/,
50
+ path: false,
51
+ closing: /['"]\s*?\)(?:;|)/,
52
+ });
53
+ const [literalWorker, errorPreparWorker] = LitExp.prepare({
54
+ ref: /\w*/,
55
+ method: /\.newVivthWorker\s*?\(\s*?['"]/,
56
+ path: false,
57
+ closing: /['"]/,
58
+ });
59
+ const [literalDir, errorPrepareDir] = LitExp.prepare({
60
+ FSInline: /\w*/,
61
+ method: /\.vivthFSInlineDir\s*?\(\s*?['"]/,
62
+ path: false,
63
+ methodClosing: /['"]\s*?,/,
64
+ rule: false,
65
+ functionClosing: /\)(?:;|)/,
66
+ });
67
+ if (errorPrepareFile || errorPrepareDir || errorPreparWorker) {
68
+ throw { errorPrepareFile, errorPrepareDir, errorPreparWorker };
69
+ }
70
+ const templateFile = literalFile`${'FSInline'}${'method'}${'path'}${'closing'}`;
71
+ const templateWorker = literalWorker`${'ref'}${'method'}${'path'}${'closing'}`;
72
+ const templateDir = literalDir`${'FSInline'}${'method'}${'path'}${'methodClosing'}${'rule'}${'functionClosing'}`;
73
+ const [resultMatchingFile, errorMatchingFile] = templateFile.evaluate.matchedAllAndGrouped(
74
+ content,
75
+ {
76
+ flags: 'gm',
77
+ whiteSpaceSensitive: false,
78
+ }
79
+ );
80
+ const [resultMatchingWorker, errorMatchingWorker] =
81
+ templateWorker.evaluate.matchedAllAndGrouped(content, {
82
+ flags: 'gm',
83
+ whiteSpaceSensitive: false,
84
+ });
85
+ const [resultMatchingDir, errorMatchingDir] = templateDir.evaluate.matchedAllAndGrouped(
86
+ content,
87
+ {
88
+ flags: 'gm',
89
+ whiteSpaceSensitive: false,
90
+ }
91
+ );
92
+ if (errorMatchingFile || errorMatchingDir || errorMatchingWorker) {
93
+ throw { errorMatchingFile, errorMatchingDir, errorMatchingWorker };
94
+ }
95
+ const {
96
+ result: { named: namedFile },
97
+ } = resultMatchingFile;
98
+ for (let i = 0; i < namedFile.length; i++) {
99
+ const { path } = namedFile[i];
100
+ FSInline.vivthFSInlinelists[path] = Buffer.from(await readFile(join(Paths.root, path)));
101
+ }
102
+ const {
103
+ result: { named: namedWorker },
104
+ regexp,
105
+ } = resultMatchingWorker;
106
+
107
+ for (let i = 0; i < namedWorker.length; i++) {
108
+ const { path } = namedWorker[i];
109
+ const fullPath = join(Paths.root, path);
110
+ const content = await readFile(fullPath, { encoding: 'utf-8' });
111
+ const [contentBundled, errorBundled] = await EsBundler(
112
+ {
113
+ content,
114
+ // @ts-expect-error
115
+ extension: extname(path),
116
+ root: dirname(fullPath),
117
+ },
118
+ {
119
+ minify: true,
120
+ format,
121
+ }
122
+ );
123
+ if (errorBundled) {
124
+ continue;
125
+ }
126
+ const [trueContent, errorFinal] = await FSInlineAnalyzer.finalContent(
127
+ contentBundled,
128
+ format
129
+ );
130
+ if (errorFinal) {
131
+ continue;
132
+ }
133
+ FSInline.vivthFSInlinelists[path] = Buffer.from(trueContent);
134
+ }
135
+ const {
136
+ result: { named: namedDir },
137
+ } = resultMatchingDir;
138
+ for (let i = 0; i < namedDir.length; i++) {
139
+ let { path, rule } = namedDir[i];
140
+ rule = rule.trim();
141
+ const results = await FSInlineAnalyzer.#dir(join(Paths.root, path), hydrateRegex(rule));
142
+ for (let j = 0; j < results.length; j++) {
143
+ const { path, buffer } = results[j];
144
+ if (path in FSInline.vivthFSInlinelists) {
145
+ continue;
146
+ }
147
+ FSInline.vivthFSInlinelists[path] = buffer;
148
+ }
149
+ }
150
+ return content.replace(
151
+ /static\s*vivthFSInlinelists(?:;|)/,
152
+ `static vivthFSInlinelists=${JSON.stringify(FSInline.vivthFSInlinelists)}`
153
+ );
154
+ });
155
+ };
156
+ /**
157
+ * @typedef {{path:string, buffer:Buffer<ArrayBuffer>}[]} _dirReturn
158
+ */
159
+ /**
160
+ * @param {string} dirName
161
+ * @param {RegExp} ruleForFileFullPath
162
+ * @returns {Promise<_dirReturn>}
163
+ */
164
+ static #dir = async (dirName, ruleForFileFullPath) => {
165
+ /**
166
+ * @type {_dirReturn}
167
+ */
168
+ const result = [];
169
+ /**
170
+ * @param {string} current
171
+ * @returns {Promise<void>}
172
+ */
173
+ const walk = async (current) => {
174
+ const entries = await readdir(current, {
175
+ recursive: false,
176
+ withFileTypes: true,
177
+ encoding: 'utf-8',
178
+ });
179
+ for (const entry of entries) {
180
+ const fullPath = Paths.normalize(join(current, entry.name));
181
+ const relativePath = Paths.normalizesForRoot(relative(Paths.root, fullPath));
182
+ if (entry.isDirectory()) {
183
+ await walk(fullPath);
184
+ continue;
185
+ }
186
+ if (entry.isFile() && ruleForFileFullPath.test(fullPath)) {
187
+ result.push({
188
+ path: relativePath,
189
+ buffer: Buffer.from(await readFile(fullPath)),
190
+ });
191
+ }
192
+ }
193
+ };
194
+ await walk(dirName);
195
+ return result;
196
+ };
197
+ }
@@ -0,0 +1,34 @@
1
+ // @ts-check
2
+
3
+ import { join } from 'node:path';
4
+
5
+ import { Paths } from '../class/Paths.mjs';
6
+
7
+ export class FSInline {
8
+ static prefix = '';
9
+ /**
10
+ * @param {string} filePathFromProject
11
+ * @returns {Promise<Buffer<ArrayBuffer>>}
12
+ */
13
+ static vivthFSInlineFile = async (filePathFromProject) => {
14
+ filePathFromProject = Paths.normalizesForRoot(filePathFromProject);
15
+ return Buffer.from(FSInline.vivthFSInlinelists[filePathFromProject]);
16
+ };
17
+ /**
18
+ * @param {string} dirPathFromProject
19
+ * @param {Object} regexRule
20
+ * @param {RegExp} regexRule.dir
21
+ * @param {RegExp} regexRule.file
22
+ * @returns {Promise<typeof FSInline["vivthFSInlineFile"]>}
23
+ * - relative to the `dirPathFromProject`
24
+ */
25
+ static vivthFSInlineDir = async (dirPathFromProject) => {
26
+ dirPathFromProject = Paths.normalizesForRoot(dirPathFromProject);
27
+ return (path_) => FSInline.vivthFSInlineFile(join(dirPathFromProject, path_));
28
+ };
29
+ /**
30
+ * - to be used as embed placeholder on `bundled` and `compiled`;
31
+ * @type {Record<string, Buffer<ArrayBufferLike>>}
32
+ */
33
+ static vivthFSInlinelists;
34
+ }