@oazmi/superbuild 0.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.
Files changed (118) hide show
  1. package/esm/_dnt.shims.d.ts +2 -0
  2. package/esm/_dnt.shims.d.ts.map +1 -0
  3. package/esm/_dnt.shims.js +57 -0
  4. package/esm/deps/jsr.io/@oazmi/esbuild-types/0.28.0/src/mod.d.ts +598 -0
  5. package/esm/deps/jsr.io/@oazmi/esbuild-types/0.28.0/src/mod.d.ts.map +1 -0
  6. package/esm/deps/jsr.io/@oazmi/esbuild-types/0.28.0/src/mod.js +1 -0
  7. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/alias.d.ts +617 -0
  8. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/alias.d.ts.map +1 -0
  9. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/alias.js +671 -0
  10. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/array1d.d.ts +487 -0
  11. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/array1d.d.ts.map +1 -0
  12. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/array1d.js +536 -0
  13. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/array2d.d.ts +266 -0
  14. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/array2d.d.ts.map +1 -0
  15. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/array2d.js +318 -0
  16. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/binder.d.ts +368 -0
  17. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/binder.d.ts.map +1 -0
  18. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/binder.js +380 -0
  19. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/crossenv.d.ts +711 -0
  20. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/crossenv.d.ts.map +1 -0
  21. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/crossenv.js +1173 -0
  22. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/cryptoman.d.ts +171 -0
  23. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/cryptoman.d.ts.map +1 -0
  24. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/cryptoman.js +308 -0
  25. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/deps.d.ts +10 -0
  26. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/deps.d.ts.map +1 -0
  27. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/deps.js +10 -0
  28. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/eightpack.d.ts +168 -0
  29. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/eightpack.d.ts.map +1 -0
  30. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/eightpack.js +236 -0
  31. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/eightpack_varint.d.ts +90 -0
  32. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/eightpack_varint.d.ts.map +1 -0
  33. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/eightpack_varint.js +237 -0
  34. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/numericarray.d.ts +213 -0
  35. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/numericarray.d.ts.map +1 -0
  36. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/numericarray.js +363 -0
  37. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/numericmethods.d.ts +233 -0
  38. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/numericmethods.d.ts.map +1 -0
  39. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/numericmethods.js +256 -0
  40. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/pathman.d.ts +1436 -0
  41. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/pathman.d.ts.map +1 -0
  42. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/pathman.js +1730 -0
  43. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/promiseman.d.ts +647 -0
  44. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/promiseman.d.ts.map +1 -0
  45. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/promiseman.js +762 -0
  46. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/stringman.d.ts +538 -0
  47. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/stringman.d.ts.map +1 -0
  48. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/stringman.js +626 -0
  49. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/struct.d.ts +734 -0
  50. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/struct.d.ts.map +1 -0
  51. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/struct.js +764 -0
  52. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/typedbuffer.d.ts +137 -0
  53. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/typedbuffer.d.ts.map +1 -0
  54. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/typedbuffer.js +234 -0
  55. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/typedefs.d.ts +391 -0
  56. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/typedefs.d.ts.map +1 -0
  57. package/esm/deps/jsr.io/@oazmi/kitchensink/0.10.0/src/typedefs.js +8 -0
  58. package/esm/deps.d.ts +56 -0
  59. package/esm/deps.d.ts.map +1 -0
  60. package/esm/deps.js +37 -0
  61. package/esm/esbuild/metafile.d.ts +90 -0
  62. package/esm/esbuild/metafile.d.ts.map +1 -0
  63. package/esm/esbuild/metafile.js +275 -0
  64. package/esm/esbuild/native.d.ts +95 -0
  65. package/esm/esbuild/native.d.ts.map +1 -0
  66. package/esm/esbuild/native.js +127 -0
  67. package/esm/esbuild/outputfile.d.ts +86 -0
  68. package/esm/esbuild/outputfile.d.ts.map +1 -0
  69. package/esm/esbuild/outputfile.js +221 -0
  70. package/esm/esbuild/strongtypes.d.ts +90 -0
  71. package/esm/esbuild/strongtypes.d.ts.map +1 -0
  72. package/esm/esbuild/strongtypes.js +8 -0
  73. package/esm/esbuild/typedefs.d.ts +121 -0
  74. package/esm/esbuild/typedefs.d.ts.map +1 -0
  75. package/esm/esbuild/typedefs.js +55 -0
  76. package/esm/esbuild/weaktypes.d.ts +46 -0
  77. package/esm/esbuild/weaktypes.d.ts.map +1 -0
  78. package/esm/esbuild/weaktypes.js +8 -0
  79. package/esm/funcdefs.d.ts +89 -0
  80. package/esm/funcdefs.d.ts.map +1 -0
  81. package/esm/funcdefs.js +145 -0
  82. package/esm/mod.d.ts +4 -0
  83. package/esm/mod.d.ts.map +1 -0
  84. package/esm/mod.js +2 -0
  85. package/esm/package.json +3 -0
  86. package/esm/plugins/emissions_driver.d.ts +33 -0
  87. package/esm/plugins/emissions_driver.d.ts.map +1 -0
  88. package/esm/plugins/emissions_driver.js +260 -0
  89. package/esm/plugins/long_build.d.ts +138 -0
  90. package/esm/plugins/long_build.d.ts.map +1 -0
  91. package/esm/plugins/long_build.js +288 -0
  92. package/esm/plugins/native_replica.d.ts +48 -0
  93. package/esm/plugins/native_replica.d.ts.map +1 -0
  94. package/esm/plugins/native_replica.js +122 -0
  95. package/esm/super/build.d.ts +53 -0
  96. package/esm/super/build.d.ts.map +1 -0
  97. package/esm/super/build.js +39 -0
  98. package/esm/super/build_context.d.ts +83 -0
  99. package/esm/super/build_context.d.ts.map +1 -0
  100. package/esm/super/build_context.js +124 -0
  101. package/esm/super/mod.d.ts +12 -0
  102. package/esm/super/mod.d.ts.map +1 -0
  103. package/esm/super/mod.js +9 -0
  104. package/esm/super/plugin.d.ts +23 -0
  105. package/esm/super/plugin.d.ts.map +1 -0
  106. package/esm/super/plugin.js +31 -0
  107. package/esm/super/plugin_build.d.ts +38 -0
  108. package/esm/super/plugin_build.d.ts.map +1 -0
  109. package/esm/super/plugin_build.js +198 -0
  110. package/esm/super/typedefs.d.ts +323 -0
  111. package/esm/super/typedefs.d.ts.map +1 -0
  112. package/esm/super/typedefs.js +10 -0
  113. package/esm/typedefs.d.ts +21 -0
  114. package/esm/typedefs.d.ts.map +1 -0
  115. package/esm/typedefs.js +5 -0
  116. package/license.md +37 -0
  117. package/package.json +88 -0
  118. package/readme.md +181 -0
@@ -0,0 +1,1173 @@
1
+ /** a submodule for cross-runtime environment utility functions.
2
+ *
3
+ * the functions in this submodule do not place a "hard" dependency on the runtime environments.
4
+ * instead, the various runtime-globals are defined as "soft" objects/symbols which may or may not exist.
5
+ * as a result, some of the functions (such as {@link getRuntime}) are very weakly/generically typed,
6
+ * and their results are not narrowed based on your input arguments.
7
+ * the advantage of such is that this submodule will not choke your LSP.
8
+ * not to mention that you'll probably be aware of what runtime you're working with anyway,
9
+ * so you can always use the `as` assertions to confine the return value to a certain runtime's feature.
10
+ *
11
+ * for most functions in here, you will need to provide the {@link RUNTIME} enum that you are querying for.
12
+ *
13
+ * to identify your script's current {@link RUNTIME} environment, you'd want to use the {@link identifyCurrentRuntime} function.
14
+ *
15
+ * > [!important]
16
+ * > when using a bundler like `esbuild`, if you place a dependency on this submodule,
17
+ * > you **will** have to declare `"node:child_process"` and `"node:fs/promises"` in your array of `external` dependencies.
18
+ * > because otherwise, your bundler will complain about not being able to find these imports.
19
+ * > alternatively, you can also set the `platform` to `"node"`, so that the bundler will treat imports with the `"node:"` prefix as external.
20
+ *
21
+ * TODO: additional features to add in the future:
22
+ * - [x] filesystem read/writing on system runtimes (deno, bun, and node).
23
+ * - [ ] filesystem read/writing on web/extension runtimes will use the browser's FileAccess API (and prompt the user to select the folder)
24
+ * - [ ] filesystem read/writing on web/extension runtimes will use `window.navigator.storage.getDirectory()`.
25
+ * and for web workers, we may use `self.navigator.storage.getDirectory()` instead.
26
+ * - [ ] persistent key-value storage: such as `localStorage` or `sessionStorage` or `chrome.storage.sync` or kv-storage of `window.navigator.storage.persist()`.
27
+ * copy these from your github-aid browser extension project.
28
+ * - [x] system environment variables.
29
+ * - [x] shell/commandline/terminal command execution.
30
+ * - [x] subprocess command execution.
31
+ * - [x] add a function for querying the operating system's platform and architecture.
32
+ * - [ ] (breaking change) consider using a class based approach to calling these functions as methods,
33
+ * where the currently selected runtime will be known by the class instance,
34
+ * so that the user will not have to pass down which runtime they are querying for all the time.
35
+ * - [ ] (RISKY) add a `setEnvVariable` function.
36
+ * but it may corrupt the user's variables if they're not careful, so I don't want to implement it unless I find myself needing it.
37
+ * - [ ] (EASY) add `copyEntry` and `moveEntry` for copying and moving filesystem entries on system-bound runtimes.
38
+ *
39
+ * @module
40
+ */
41
+ import * as dntShim from "../../../../../../_dnt.shims.js";
42
+ import { array_isEmpty, noop, object_entries, object_fromEntries, promise_all, promise_outside, promise_race, string_toLowerCase, string_toUpperCase } from "./alias.js";
43
+ import { DEBUG } from "./deps.js";
44
+ import { textDecoder, textEncoder } from "./eightpack.js";
45
+ import { ensureEndSlash, ensureFileUrlIsLocalPath, parseFilepathInfo, pathToPosixPath } from "./pathman.js";
46
+ import { isComplex, isObject, isString } from "./struct.js";
47
+ import { concatBytes } from "./typedbuffer.js";
48
+ /** javascript runtime enums. */
49
+ export var RUNTIME;
50
+ (function (RUNTIME) {
51
+ /** deno runtime.
52
+ *
53
+ * since deno also supports `process` for node compatibility, you will want to check for deno runtime before checking for node runtime.
54
+ */
55
+ RUNTIME[RUNTIME["DENO"] = 0] = "DENO";
56
+ /** bunjs runtime.
57
+ *
58
+ * since bun also supports `process` for node compatibility, you will want to check for bun runtime before checking for node runtime.
59
+ */
60
+ RUNTIME[RUNTIME["BUN"] = 1] = "BUN";
61
+ /** nodejs runtime. */
62
+ RUNTIME[RUNTIME["NODE"] = 2] = "NODE";
63
+ /** chrome-extension runtime.
64
+ *
65
+ * since the `window` context is also available in chrome extensions, it is better to check for this runtime before checking for client-web-page runtime.
66
+ */
67
+ RUNTIME[RUNTIME["CHROMIUM"] = 3] = "CHROMIUM";
68
+ /** firefox (or any non-chromium) extension runtime.
69
+ *
70
+ * since the `window` context is also available in chrome extensions, it is better to check for this runtime before checking for client-web-page runtime.
71
+ */
72
+ RUNTIME[RUNTIME["EXTENSION"] = 4] = "EXTENSION";
73
+ /** web-browser runtime. */
74
+ RUNTIME[RUNTIME["WEB"] = 5] = "WEB";
75
+ /** worker-script runtime. */
76
+ RUNTIME[RUNTIME["WORKER"] = 6] = "WORKER";
77
+ /** the [txiki.js](https://github.com/saghul/txiki.js/) runtime, that's based on quickjs. */
78
+ RUNTIME[RUNTIME["TXIKI"] = 7] = "TXIKI";
79
+ })(RUNTIME || (RUNTIME = {}));
80
+ const global_this_object = dntShim.dntGlobalThis;
81
+ /** a map/record of runtime validation functions that determine if the current javascript environment matches a specific runtime.
82
+ *
83
+ * each key represents a runtime type defined by the {@link RUNTIME} enum,
84
+ * and each value is a function that returns a boolean indicating whether your current environment
85
+ * satisfies the global-object requirements of the given {@link RUNTIME}.
86
+ *
87
+ * @example
88
+ * ```ts ignore
89
+ * if (currentRuntimeValidationFnMap[RUNTIME.NODE]()) {
90
+ * // execute nodejs-specific logic
91
+ * console.log("current nodejs working directory is:", process.cwd())
92
+ * }
93
+ * if (currentRuntimeValidationFnMap[RUNTIME.DENO]()) {
94
+ * // execute deno-specific logic
95
+ * console.log("current deno working directory is:", Deno.cwd())
96
+ * }
97
+ * if (currentRuntimeValidationFnMap[RUNTIME.WEB]()) {
98
+ * // execute web-specific logic
99
+ * console.log("current webpage's url is:", globalThis.location?.href)
100
+ * }
101
+ * ```
102
+ */
103
+ export const currentRuntimeValidationFnMap = {
104
+ [RUNTIME.DENO]: () => ((global_this_object.Deno?.version) ? true : false),
105
+ [RUNTIME.BUN]: () => ((global_this_object.Bun?.version) ? true : false),
106
+ [RUNTIME.NODE]: () => ((global_this_object.process?.versions) ? true : false),
107
+ [RUNTIME.CHROMIUM]: () => ((global_this_object.chrome?.runtime) ? true : false),
108
+ [RUNTIME.EXTENSION]: () => ((global_this_object.browser?.runtime) ? true : false),
109
+ [RUNTIME.WEB]: () => ((global_this_object.window?.document) ? true : false),
110
+ [RUNTIME.TXIKI]: () => ((global_this_object.tjs?.version) ? true : false),
111
+ [RUNTIME.WORKER]: () => ((isObject(global_this_object.self)
112
+ && isComplex(global_this_object.WorkerGlobalScope)
113
+ && global_this_object.self instanceof global_this_object.WorkerGlobalScope) ? true : false),
114
+ };
115
+ /** this array declares the ordering in which your runtime environment is tested against,
116
+ * to {@link identifyCurrentRuntime | automatically identify} the current runtime.
117
+ *
118
+ * modifying this array (or re-ordering it) is useful in situations where you would like to expand support to a new runtime.
119
+ * do note that modifying this array will only affect the output of {@link identifyCurrentRuntime}, and nothing else.
120
+ */
121
+ export const currentRuntimeIdentificationOrdering = [
122
+ RUNTIME.DENO, RUNTIME.BUN, RUNTIME.TXIKI, RUNTIME.NODE,
123
+ RUNTIME.CHROMIUM, RUNTIME.EXTENSION, RUNTIME.WEB, RUNTIME.WORKER,
124
+ ];
125
+ /** identifies the current javascript runtime environment as a {@link RUNTIME} enum.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * import { assertEquals } from "jsr:@std/assert"
130
+ *
131
+ * assertEquals(identifyCurrentRuntime(), RUNTIME.DENO)
132
+ * ```
133
+ */
134
+ export const identifyCurrentRuntime = () => {
135
+ for (const runtime of currentRuntimeIdentificationOrdering) {
136
+ if (currentRuntimeValidationFnMap[runtime]()) {
137
+ return runtime;
138
+ }
139
+ }
140
+ throw new Error(DEBUG.ERROR ? `failed to detect current javascript runtime!\nplease report this issue to "https://github.com/omar-azmi/kitchensink_ts/issues", along with information on your runtime environment.` : "");
141
+ };
142
+ /** get the global-runtime-object of the given javascript environment {@link RUNTIME} enum.
143
+ *
144
+ * > [!note]
145
+ * > if you acquire the global-runtime-object of an environment that is not supported by your actual current environment,
146
+ * > then the returned value will be `undefined`.
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * import { assertEquals } from "jsr:@std/assert"
151
+ * import process from "node:process" // this works in deno 2.0
152
+ *
153
+ * assertEquals(getRuntime(RUNTIME.DENO), Deno)
154
+ * assertEquals(getRuntime(RUNTIME.NODE), process)
155
+ * assertEquals(getRuntime(identifyCurrentRuntime()), Deno)
156
+ * ```
157
+ */
158
+ export const getRuntime = (runtime_enum) => {
159
+ switch (runtime_enum) {
160
+ case RUNTIME.DENO: return global_this_object.Deno;
161
+ case RUNTIME.BUN: return global_this_object.Bun;
162
+ case RUNTIME.NODE: return global_this_object.process;
163
+ case RUNTIME.CHROMIUM: return global_this_object.chrome;
164
+ case RUNTIME.EXTENSION: return global_this_object.browser;
165
+ case RUNTIME.WEB: return global_this_object.window;
166
+ case RUNTIME.WORKER: return global_this_object.self;
167
+ case RUNTIME.TXIKI: return global_this_object.tjs;
168
+ default: throw new Error(DEBUG.ERROR ? `an invalid runtime enum was provided: "${runtime_enum}".` : "");
169
+ }
170
+ };
171
+ /** retrieves the current working directory or URL based on the specified {@link RUNTIME} enum.
172
+ *
173
+ * > [!note]
174
+ * > - the returned directory path may or may not end in a trailing slash.
175
+ * > this is intentional, as it is possible for the path to actually point towards a file.
176
+ * > (such as in the case of `chrome.runtime.getURL("")`)
177
+ * > - however, the returned path will always use posix separators (only forward slashes, no windows backslashes).
178
+ * > - if you try to query the working directory of a runtime enum that your current environment does not support,
179
+ * > then an error will be thrown, because you'll be accessing an `undefined` object.
180
+ *
181
+ * depending on the provided `runtime_enum`, the current working directory is defined as the following:
182
+ * - for `DENO`, `BUN`, and `NODE`: the result will be that of the runtime's `cwd()` method.
183
+ * - for `CHROMIUM` and `EXTENSION`: the result will be the url string obtained from `runtime.getURL("")`.
184
+ * - for `WEB` and `WORKER`: a url string will be returned that will vary based on the `current_path` flag (`true` by default):
185
+ * - if `current_path == false`: `location.origin` will be returned (i.e. the root of your webpage, which is your domain-name + subdomain-name).
186
+ * - if `current_path == true`: the directory path of `location.href` will be returned.
187
+ *
188
+ * @param runtime_enum the runtime enum indicating which runtime's working-directory/url-path to retrieve.
189
+ * @param current_path a boolean flag that, when true, returns the full `href` url of the runtime, rather than the root `origin`. defaults to `true`.
190
+ * @returns a posix string path of the working-directory/url-path of the specified runtime.
191
+ * @throws an error is thrown if the runtime associated with the provided enum is undefined, or if an invalid enum is provided.
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * import { assertEquals } from "jsr:@std/assert"
196
+ * import process from "node:process" // this works in deno 2.0
197
+ *
198
+ * assertEquals(getRuntimeCwd(RUNTIME.DENO), getRuntimeCwd(RUNTIME.NODE))
199
+ * assertEquals(getRuntimeCwd(RUNTIME.DENO), Deno.cwd().replaceAll(/\\\\?/g, "/"))
200
+ * assertEquals(getRuntimeCwd(RUNTIME.NODE), process.cwd().replaceAll(/\\\\?/g, "/"))
201
+ * ```
202
+ */
203
+ export const getRuntimeCwd = (runtime_enum, current_path = true) => {
204
+ const runtime = getRuntime(runtime_enum);
205
+ if (!runtime) {
206
+ throw new Error(DEBUG.ERROR ? `the requested runtime associated with the enum "${runtime_enum}" is undefined (i.e. you're running on a different runtime from the provided enum).` : "");
207
+ }
208
+ switch (runtime_enum) {
209
+ case RUNTIME.DENO:
210
+ case RUNTIME.BUN:
211
+ case RUNTIME.NODE:
212
+ return pathToPosixPath(runtime.cwd());
213
+ case RUNTIME.TXIKI:
214
+ return pathToPosixPath(runtime.cwd);
215
+ case RUNTIME.CHROMIUM:
216
+ case RUNTIME.EXTENSION:
217
+ return runtime.runtime.getURL("");
218
+ case RUNTIME.WEB:
219
+ case RUNTIME.WORKER:
220
+ return new URL("./", current_path ? runtime.location.href : runtime.location.origin).href;
221
+ // the default case is unreachable, because `runtime` wouldn't be defined unless a valid `runtime_enum` was passed to `getRuntime` anyway.
222
+ // default:
223
+ // throw new Error(DEBUG.ERROR ? `an invalid runtime enum was provided: "${runtime_enum}".` : "")
224
+ }
225
+ };
226
+ /** retrieves the value of an environment variable on system runtimes
227
+ * (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}, {@link RUNTIME.TXIKI}).
228
+ * otherwise an error gets thrown on all other environments, since they do not support environment variables.
229
+ *
230
+ * > [!tip]
231
+ * > - environment variables are case-insensitive.
232
+ * > - you will probably want to normalize path variables to posix path via {@link pathToPosixPath}.
233
+ * > - if `env_var = ""` (an empty string) then `undefined` will always be returned on system-runtime environments.
234
+ *
235
+ * @param runtime_enum the runtime enum indicating which runtime should be used for querying the environment variable.
236
+ * @param env_var the name of the environment variable to fetch.
237
+ * @returns the environment variable's value.
238
+ * @throws for js-workers, extensions, and web environments, an error gets thrown, as environment variables are not available.
239
+ *
240
+ * @example
241
+ * ```ts
242
+ * import { assertEquals } from "jsr:@std/assert"
243
+ *
244
+ * const my_path_env_var = getEnvVariable(identifyCurrentRuntime(), "path")!
245
+ *
246
+ * assertEquals(typeof my_path_env_var, "string")
247
+ * assertEquals(my_path_env_var.length > 0, true)
248
+ * ```
249
+ */
250
+ export const getEnvVariable = (runtime_enum, env_var) => {
251
+ const runtime = getRuntime(runtime_enum);
252
+ if (!runtime) {
253
+ throw new Error(DEBUG.ERROR ? `the requested runtime associated with the enum "${runtime_enum}" is undefined (i.e. you're running on a different runtime from the provided enum).` : "");
254
+ }
255
+ if (!env_var) {
256
+ return;
257
+ }
258
+ switch (runtime_enum) {
259
+ case RUNTIME.DENO:
260
+ return runtime.env.get(env_var);
261
+ case RUNTIME.BUN:
262
+ case RUNTIME.NODE:
263
+ case RUNTIME.TXIKI:
264
+ return runtime.env[env_var];
265
+ default:
266
+ throw new Error(DEBUG.ERROR ? `your non-system runtime environment enum ("${runtime_enum}") does not support environment variables` : "");
267
+ }
268
+ };
269
+ const defaultExecShellCommandConfig = {
270
+ args: []
271
+ };
272
+ /** execute a shell/terminal command on system runtimes
273
+ * (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}, {@link RUNTIME.TXIKI}).
274
+ * otherwise an error gets thrown on all other environments, since they do not support shell command execution.
275
+ *
276
+ * > [!note]
277
+ * > we don't use `Deno.Command` for deno here, because it does not default to your os's native preferred terminal,
278
+ * > and instead you will need to provide one yourself (such as "bash", "cmd", "shell", etc...).
279
+ * > which is why we use `node:child_process` for all three runtimes.
280
+ *
281
+ * TODO: add support for txiki.js. this is non-trivial, because, just like `Deno.command`,
282
+ * the `tjs.spawn` function only spawns processes, and not your default terminal.
283
+ * but that's not the only issue; spawning a terminal on txiki.js also hasn't quite worked consistiently for me.
284
+ * the problem lies in sending `stdin`, while also capturing `stdout` without the echoed `stdin`.
285
+ *
286
+ * TODO: add support for adding custom env-variables to the child shell process.
287
+ *
288
+ * @param runtime_enum the runtime enum indicating which runtime should be used for executing the shell command.
289
+ * @param command the shell command to execute.
290
+ * @param config optional configuration to apply onto the shell child-process.
291
+ * @returns a promise that is resolved when the child process that executed the command has closed.
292
+ *
293
+ * @example
294
+ * ```ts
295
+ * import { assertEquals, assertStringIncludes } from "jsr:@std/assert"
296
+ *
297
+ * {
298
+ * const { stdout, stderr } = await execShellCommand(identifyCurrentRuntime(), "echo Hello World!")
299
+ * assertStringIncludes(stdout, "Hello World!")
300
+ * assertEquals(stderr, "")
301
+ * }
302
+ *
303
+ * {
304
+ * const { stdout, stderr } = await execShellCommand(identifyCurrentRuntime(), "echo", { args: ["Hello", "World!"] })
305
+ * assertStringIncludes(stdout, "Hello World!")
306
+ * assertEquals(stderr, "")
307
+ * }
308
+ * ```
309
+ */
310
+ export const execShellCommand = async (runtime_enum, command, config = {}) => {
311
+ const { args, cwd: _cwd, env, signal } = { ...defaultExecShellCommandConfig, ...config }, args_are_empty = array_isEmpty(args), cwd = _cwd ? ensureFileUrlIsLocalPath(_cwd) : undefined, runtime = getRuntime(runtime_enum);
312
+ if (!runtime) {
313
+ throw new Error(DEBUG.ERROR ? `the requested runtime associated with the enum "${runtime_enum}" is undefined (i.e. you're running on a different runtime from the provided enum).` : "");
314
+ }
315
+ if (!command && args_are_empty) {
316
+ return { stdout: "", stderr: "" };
317
+ }
318
+ switch (runtime_enum) {
319
+ case RUNTIME.TXIKI:
320
+ throw new Error(DEBUG.ERROR ? `shell commands for txiki.js is currently not supported.` : "");
321
+ case RUNTIME.DENO:
322
+ case RUNTIME.BUN:
323
+ case RUNTIME.NODE: {
324
+ const { exec } = await get_node_child_process(), full_command = args_are_empty ? command : `${command} ${args.join(" ")}`, [promise, resolve, reject] = promise_outside();
325
+ exec(full_command, { cwd, env, signal }, (error, stdout, stderr) => {
326
+ if (error) {
327
+ reject(error.message);
328
+ }
329
+ resolve({ stdout, stderr });
330
+ });
331
+ return promise;
332
+ }
333
+ default:
334
+ throw new Error(DEBUG.ERROR ? `your non-system runtime environment enum ("${runtime_enum}") does not support shell commands` : "");
335
+ }
336
+ };
337
+ /** execute an executable process (such as `deno`, `node`, `winget`, `apt`, `curl`, `cmd`, `./bin/main.exe`, etc...,
338
+ * but not including shell commands, such as `echo`, `ls`, `cp`, etc...), and then exit it.
339
+ *
340
+ * TODO: in the future, add a `spawnProcess` function which will keep the process alive after executing it,
341
+ * in addition to also permitting it to accept a user's `stdin`.
342
+ *
343
+ * @param runtime_enum the runtime enum indicating which runtime should be used for executing/spawning the subprocess.
344
+ * @param process_name the name of the process to spawn.
345
+ * this could be either something in your environment's PATH variable,
346
+ * or an executable in your current directory (which will require you to prepend a leading `"./"` or `"../"` in its path).
347
+ * @param config optional configuration to apply onto the child-process.
348
+ * @returns a promise that is resolved when the spawned child process exits that executed the command has closed.
349
+ *
350
+ * @example
351
+ * ```ts
352
+ * import { assertEquals, assertStringIncludes } from "jsr:@std/assert"
353
+ *
354
+ * const toText = (bytes: Uint8Array) => (new TextDecoder().decode(bytes))
355
+ *
356
+ * const runTestWithRuntime = async (runtime_enum: RUNTIME) => {
357
+ * {
358
+ * const { stdout, stderr } = await spawnCommand(runtime_enum, "deno", {
359
+ * args: ["eval", `console.log("child-process says hello!")`],
360
+ * })
361
+ * assertStringIncludes(toText(stdout), "child-process says hello!")
362
+ * assertEquals(toText(stderr), "")
363
+ * }
364
+ *
365
+ * if (Deno.build.os === "windows") {
366
+ * const { stdout, stderr } = await spawnCommand(runtime_enum, "ipconfig")
367
+ * assertStringIncludes(toText(stdout).toLowerCase(), "windows ip configuration")
368
+ * assertEquals(toText(stderr), "")
369
+ * }
370
+ *
371
+ * if (Deno.build.os === "linux" || Deno.build.os === "darwin") {
372
+ * // `echo` is apparently a process, and not a shell utility. (insert surprised pikachu face)
373
+ * const { stdout, stderr } = await spawnCommand(runtime_enum, "echo", { args: ["Hello", "World!"] })
374
+ * assertStringIncludes(toText(stdout), "Hello World!")
375
+ * assertEquals(toText(stderr), "")
376
+ * }
377
+ * }
378
+ *
379
+ * await runTestWithRuntime(identifyCurrentRuntime()) // deno runtime test
380
+ * await runTestWithRuntime(RUNTIME.NODE) // deno with node-compatibility runtime test
381
+ * ```
382
+ */
383
+ export const spawnCommand = async (runtime_enum, process_name, config = {}) => {
384
+ const { cwd: _cwd, env: _env, ...rest_config } = { ...defaultExecShellCommandConfig, ...config }, cwd = _cwd ? ensureFileUrlIsLocalPath(_cwd) : undefined, runtime = getRuntime(runtime_enum), env = _env && (runtime_enum === RUNTIME.DENO || runtime_enum === RUNTIME.TXIKI)
385
+ ? object_fromEntries(object_entries(_env).map(([key, value]) => ([key, value ?? ""])))
386
+ : _env, full_config = { ...rest_config, cwd, env };
387
+ if (!runtime) {
388
+ throw new Error(DEBUG.ERROR ? `the requested runtime associated with the enum "${runtime_enum}" is undefined (i.e. you're running on a different runtime from the provided enum).` : "");
389
+ }
390
+ if (!process_name) {
391
+ return { stdout: new Uint8Array(0), stderr: new Uint8Array(0) };
392
+ }
393
+ switch (runtime_enum) {
394
+ case RUNTIME.DENO: {
395
+ return deno_spawnCommand(runtime, process_name, full_config);
396
+ }
397
+ case RUNTIME.BUN: {
398
+ return bun_spawnCommand(runtime, process_name, full_config);
399
+ }
400
+ case RUNTIME.TXIKI: {
401
+ return txiki_spawnCommand(runtime, process_name, full_config);
402
+ }
403
+ case RUNTIME.NODE: {
404
+ return node_spawnCommand(runtime, process_name, full_config);
405
+ }
406
+ default:
407
+ throw new Error(DEBUG.ERROR ? `your non-system runtime environment enum ("${runtime_enum}") does not support shell commands` : "");
408
+ }
409
+ };
410
+ const deno_spawnCommand = async (runtime, process_name, config) => {
411
+ const { args, cwd, env: _env = {}, detached, signal } = config, env = object_fromEntries(object_entries(_env).map(([key, value]) => ([key, value ?? ""]))), command = new runtime.Command(process_name, { args, cwd, env, detached, signal, stdin: "null", stdout: "piped", stderr: "piped" }), { success, code, stdout, stderr } = await command.output();
412
+ if (!success) {
413
+ throw new Error(DEBUG.ERROR ? `[deno_spawnCommand]: failed while executing command/process: "${process_name}", with error code: "${code}". cli arguments used:\n\t${args.join(" ")}` : "");
414
+ }
415
+ return { stdout, stderr };
416
+ };
417
+ const bun_spawnCommand = async (runtime, process_name, config) => {
418
+ const { args, cwd, env, detached, signal } = config, cmd = [process_name, ...args], [promise, resolve, reject] = promise_outside();
419
+ runtime.spawn(cmd, {
420
+ cwd, env, detached, signal, stdout: "pipe", stderr: "pipe",
421
+ onExit(subprocess, exit_code, signal_code, error) {
422
+ if (error) {
423
+ reject(error);
424
+ }
425
+ const stdout_promise = new Response(subprocess.stdout).bytes(), stderr_promise = new Response(subprocess.stderr).bytes();
426
+ promise_all([stdout_promise, stderr_promise]).then(([stdout, stderr]) => { resolve({ stdout, stderr }); }, (err) => { reject(err); });
427
+ }
428
+ });
429
+ return promise;
430
+ };
431
+ const txiki_spawnCommand = async (runtime, process_name, config) => {
432
+ let subprocess, subprocess_ended = false;
433
+ const { args, cwd, detached, env, signal } = config, cmd = [process_name, ...args],
434
+ // TODO: EXTERNAL-ISSUE: because of [issue#471](https://github.com/saghul/txiki.js/issues/471), below is the only pattern that works without garbage-collection.
435
+ // otherwise, I always get the error: "tjs_process_wait: Assertion `!p->closed' failed." when I call `subprocess.wait()` afterwards (possibly because the process ends early).
436
+ exit_status_promise = (subprocess = runtime.spawn(cmd, { cwd, env, stdout: "pipe", stderr: "pipe" })).wait(), subprocess_discarded = exit_status_promise.then((status) => { subprocess_ended = true; });
437
+ signal?.addEventListener("abort", () => {
438
+ // txiki.js will throw an error if the process was already aborted,
439
+ // so we must make sure that we only terminate under the condition that it hasn't exited already.
440
+ // this is done by performing a race promise, which will ignore the next function if the first one has already resolved.
441
+ const kill_subprocess = async () => { if (!subprocess_ended) {
442
+ await subprocess.kill("SIGTERM");
443
+ } }; // fake awaiting here to prevent optimization and potential discarding of the promise.
444
+ Promise.race([subprocess_discarded, kill_subprocess()]);
445
+ });
446
+ const collect_reader = async (reader) => {
447
+ const buf = new Uint8Array(4096), bufs = [];
448
+ let bytes_read = 0;
449
+ while ((bytes_read = (await reader.read(buf) ?? -1)) >= 0) {
450
+ bufs.push(buf.slice(0, bytes_read));
451
+ }
452
+ return concatBytes(...bufs);
453
+ };
454
+ await subprocess_discarded;
455
+ const stdout = await collect_reader(subprocess.stdout), stderr = await collect_reader(subprocess.stderr);
456
+ return { stdout, stderr };
457
+ };
458
+ const node_spawnCommand = async (runtime, process_name, config) => {
459
+ const { spawn } = await get_node_child_process(), { args, cwd, env, detached, signal } = config, stdouts = [], stderrs = [], [promise, resolve, reject] = promise_outside(), subprocess = spawn(process_name, args, { shell: false, cwd, env, detached, signal, stdio: ["ignore", "pipe", "pipe"] });
460
+ // believe it or not, `subprocess.stdout` and `subprocess.stderr` are socket :/ ... a big facepalm.
461
+ subprocess.once("close", (exit_code, term_signal) => {
462
+ const stdout = concatBytes(...stdouts), stderr = concatBytes(...stderrs);
463
+ resolve({ stdout, stderr });
464
+ });
465
+ subprocess.stdout.on("data", (chunk) => { stdouts.push(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength)); });
466
+ subprocess.stderr.on("data", (chunk) => { stderrs.push(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength)); });
467
+ subprocess.once("error", (err) => { reject(err); });
468
+ return promise;
469
+ };
470
+ const platform_aliases = {
471
+ "win32": "windows",
472
+ "win64": "windows",
473
+ "wow64": "windows",
474
+ "cygwin": "windows",
475
+ "sun": "sunos",
476
+ "mac": "darwin",
477
+ "macos": "darwin",
478
+ "macintosh": "darwin",
479
+ };
480
+ const arch_aliases = {
481
+ "x86_64": "x64",
482
+ "x86-64": "x64",
483
+ "amd64": "x64",
484
+ "x86_32": "x86",
485
+ "x86-32": "x86",
486
+ "ia32": "x86",
487
+ "i386": "x86",
488
+ "i686": "x86",
489
+ "aarch64": "arm64",
490
+ "armv7l": "arm",
491
+ "armv6l": "arm",
492
+ "mipsel": "mips",
493
+ "ppc64le": "ppc64",
494
+ "loongarch64": "loong64",
495
+ };
496
+ const getSystemInfo_resolve_aliases = (sys_info) => {
497
+ const platform = sys_info.platform, arch = sys_info.arch,
498
+ // release = sys_info.release,
499
+ userAgent = get_user_agent_string();
500
+ return {
501
+ platform: platform_aliases[platform] ?? platform,
502
+ arch: arch_aliases[arch] ?? arch,
503
+ userAgent,
504
+ };
505
+ };
506
+ const string_includes = (str, ...substrings) => {
507
+ return substrings.some((substring) => (str.includes(substring)));
508
+ }, get_user_agent_string = () => { return global_this_object.navigator.userAgent; };
509
+ const getSystemInfo_parseUserAgentString = (user_agent) => {
510
+ user_agent = string_toLowerCase(user_agent);
511
+ let platform = "unknown", arch = "unknown";
512
+ // matching the platform
513
+ if (string_includes(user_agent, "windows")) {
514
+ platform = "windows";
515
+ }
516
+ else if (string_includes(user_agent, "linux", "x11", "ubuntu", "raspberry")) {
517
+ platform = "linux";
518
+ }
519
+ else if (string_includes(user_agent, "macintosh", "macos", "mac os", "iphone", "ipad")) {
520
+ platform = "darwin";
521
+ }
522
+ else if (string_includes(user_agent, "android", "mobile", "sm-g")) {
523
+ platform = "android";
524
+ }
525
+ else if (string_includes(user_agent, "freebsd")) {
526
+ platform = "freebsd";
527
+ }
528
+ else if (string_includes(user_agent, "netbsd")) {
529
+ platform = "netbsd";
530
+ }
531
+ else if (string_includes(user_agent, "openbsd")) {
532
+ platform = "openbsd";
533
+ }
534
+ else if (string_includes(user_agent, "haiku")) {
535
+ platform = "haiku";
536
+ }
537
+ else if (string_includes(user_agent, "illumos")) {
538
+ platform = "illumos";
539
+ }
540
+ else if (string_includes(user_agent, "sunos")) {
541
+ platform = "sunos";
542
+ }
543
+ else if (string_includes(user_agent, "solaris")) {
544
+ platform = "solaris";
545
+ }
546
+ else if (string_includes(user_agent, "aix")) {
547
+ platform = "aix";
548
+ }
549
+ // matching the architecture
550
+ if (string_includes(user_agent, "x64", "win64", "wow64", "x86_64", "x86-64", "amd64")) {
551
+ arch = "x64";
552
+ }
553
+ else if (string_includes(user_agent, "x86", "i386", "i686", "x86_32", "x86-32")) {
554
+ arch = "x86";
555
+ }
556
+ else if (string_includes(user_agent, "aarch64", "arm64")) {
557
+ arch = "arm64";
558
+ }
559
+ else if (string_includes(user_agent, "arm", "armv7l", "armv6l")) {
560
+ arch = "arm";
561
+ }
562
+ else if (string_includes(user_agent, "mips64")) {
563
+ arch = "mips64";
564
+ }
565
+ else if (string_includes(user_agent, "mips", "mipsel")) {
566
+ arch = "mips";
567
+ }
568
+ else if (string_includes(user_agent, "riscv64")) {
569
+ arch = "riscv64";
570
+ }
571
+ else if (string_includes(user_agent, "riscv32")) {
572
+ arch = "riscv32";
573
+ }
574
+ else if (string_includes(user_agent, "ppc64", "ppc64le")) {
575
+ arch = "ppc64";
576
+ }
577
+ return { platform, arch };
578
+ };
579
+ /** get information about host system, such as its platform (os), architecture, and user-agent string.
580
+ *
581
+ * @example
582
+ * ```ts
583
+ * import { assertEquals } from "jsr:@std/assert"
584
+ *
585
+ * const actual_system_info: SystemInfo = {
586
+ * platform: Deno.build.os.toLowerCase() as any,
587
+ * // deno writes the amd64 architecture as "x86_64", so we convert it to "x64" below.
588
+ * arch: Deno.build.arch.toLowerCase().replace("x86_", "x") as any,
589
+ * userAgent: navigator.userAgent,
590
+ * }
591
+ *
592
+ * assertEquals(getSystemInfo(RUNTIME.DENO), actual_system_info)
593
+ * assertEquals(getSystemInfo(RUNTIME.NODE), actual_system_info)
594
+ * ```
595
+ */
596
+ export const getSystemInfo = (runtime_enum) => {
597
+ const runtime = getRuntime(runtime_enum);
598
+ let platform, arch;
599
+ switch (runtime_enum) {
600
+ case RUNTIME.DENO: {
601
+ platform = runtime.build.os;
602
+ arch = runtime.build.arch;
603
+ break;
604
+ }
605
+ case RUNTIME.NODE:
606
+ case RUNTIME.BUN: {
607
+ platform = runtime.platform;
608
+ arch = runtime.arch;
609
+ break;
610
+ }
611
+ case RUNTIME.TXIKI: {
612
+ platform = runtime.system.platform;
613
+ arch = runtime.system.arch;
614
+ break;
615
+ }
616
+ case RUNTIME.WEB:
617
+ case RUNTIME.CHROMIUM:
618
+ case RUNTIME.EXTENSION:
619
+ case RUNTIME.WORKER: {
620
+ // TODO: what about workers _inside_ a standalone js-runtime?
621
+ // parsing the user agent string for these workers will result in less useful info beinig extracted.
622
+ ({ platform, arch } = getSystemInfo_parseUserAgentString(get_user_agent_string()));
623
+ break;
624
+ }
625
+ // the default case is unreachable, because `runtime` wouldn't be defined unless a valid `runtime_enum` was passed to `getRuntime` anyway.
626
+ // default:
627
+ // throw new Error(DEBUG.ERROR ? `an invalid runtime enum was provided: "${runtime_enum}".` : "")
628
+ }
629
+ return getSystemInfo_resolve_aliases({ platform, arch });
630
+ };
631
+ const defaultWriteFileConfig = {
632
+ append: false,
633
+ create: true,
634
+ mode: undefined,
635
+ };
636
+ const defaultReadFileConfig = {};
637
+ /** writes text data to a file on supported runtimes
638
+ * (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}, {@link RUNTIME.TXIKI}).
639
+ * for unsupported runtimes, an error is thrown.
640
+ *
641
+ * TODO: in the future, I would like to create a unified cross-runtime filesystem class under `./crossfs.ts`,
642
+ * which would support read and write operations, along with memory (internal state) of the current working directory,
643
+ * in addition to a path resolver method that will rely on `./pathman.ts`'s `resolvePathFactory` function.
644
+ *
645
+ * @param runtime_enum the runtime enum indicating which runtime should be used for writing onto the filesystem.
646
+ * @param file_path the destination file path.
647
+ * @param text the string content to write.
648
+ * @param config provide optional configuration on how the writing should be performed.
649
+ * @throws an error is thrown if an unsupported runtime uses this function,
650
+ * or if `config.create` is `false`, and no pre-existing file resides at the specified `file_path`.
651
+ */
652
+ export const writeTextFile = async (runtime_enum, file_path, text, config = {}) => {
653
+ // even though both node and deno accept file URL objects, if you pass a file-url string, they will fail to read/write to the provided path.
654
+ // this is why we're forced to convert all file_paths to local-fs-paths during all read/write operations for all system-bound js-runtimes.
655
+ file_path = ensureFileUrlIsLocalPath(file_path);
656
+ const { append, create, mode, signal } = { ...defaultWriteFileConfig, ...config }, node_config = { encoding: "utf8", append, create, mode, signal }, deno_config = { append, create, mode, signal }, runtime = getRuntime(runtime_enum);
657
+ switch (runtime_enum) {
658
+ case RUNTIME.DENO:
659
+ return runtime.writeTextFile(file_path, text, deno_config);
660
+ case RUNTIME.BUN:
661
+ case RUNTIME.NODE:
662
+ return node_writeFile(file_path, text, node_config);
663
+ case RUNTIME.TXIKI:
664
+ return txiki_writeFile(file_path, text, deno_config);
665
+ default:
666
+ throw new Error(DEBUG.ERROR ? `your non-system runtime environment enum ("${runtime_enum}") does not support filesystem writing operations` : "");
667
+ }
668
+ };
669
+ /** writes binary/buffer data to a file on supported runtimes
670
+ * (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}, {@link RUNTIME.TXIKI}).
671
+ * for unsupported runtimes, an error is thrown.
672
+ *
673
+ * @param runtime_enum the runtime enum indicating which runtime should be used for writing onto the filesystem.
674
+ * @param file_path the destination file path.
675
+ * @param data the byte/buffer data to write to the file.
676
+ * @param config provide optional configuration on how the writing should be performed.
677
+ * @throws an error is thrown if an unsupported runtime uses this function,
678
+ * or if `config.create` is `false`, and no pre-existing file resides at the specified `file_path`.
679
+ */
680
+ export const writeFile = async (runtime_enum, file_path, data, config = {}) => {
681
+ file_path = ensureFileUrlIsLocalPath(file_path);
682
+ const { append, create, mode, signal } = { ...defaultWriteFileConfig, ...config }, { buffer, byteLength, byteOffset } = data, bytes = data instanceof Uint8Array ? data : new Uint8Array(buffer, byteOffset, byteLength), node_config = { encoding: "binary", append, create, mode, signal }, deno_config = { append, create, mode, signal }, runtime = getRuntime(runtime_enum);
683
+ switch (runtime_enum) {
684
+ case RUNTIME.DENO:
685
+ return runtime.writeFile(file_path, bytes, deno_config);
686
+ case RUNTIME.BUN:
687
+ case RUNTIME.NODE:
688
+ return node_writeFile(file_path, bytes, node_config);
689
+ case RUNTIME.TXIKI:
690
+ return txiki_writeFile(file_path, bytes, deno_config);
691
+ default:
692
+ throw new Error(DEBUG.ERROR ? `your non-system runtime environment enum ("${runtime_enum}") does not support filesystem writing operations` : "");
693
+ }
694
+ };
695
+ let node_fs, node_child_process;
696
+ const import_node_fs = async () => { return import("node:fs/promises"); }, get_node_fs = async () => { return (node_fs ??= await import_node_fs()); }, import_node_child_process = async () => { return import("node:child_process"); }, get_node_child_process = async () => { return (node_child_process ??= await import_node_child_process()); };
697
+ const node_writeFile = async (file_path, data, config = {}) => {
698
+ const fs = await get_node_fs(), { append, create, mode, signal, encoding } = { ...defaultWriteFileConfig, ...config }, fs_config = { encoding: encoding, mode, signal };
699
+ // if we are permitted to write on top of existing files, then only a single call to `fs.writeFile` suffices.
700
+ if (create) {
701
+ return fs.writeFile(file_path, data, { ...fs_config, flag: (append ? "a" : "w") });
702
+ }
703
+ // if we must assert the pre-existence of the file, then the process is a little more involved.
704
+ const file = await fs.open(file_path, "r+", mode);
705
+ if (!append) {
706
+ await file.truncate(0);
707
+ }
708
+ await file.appendFile(data, fs_config);
709
+ return file.close();
710
+ };
711
+ const txiki_writeFile = async (file_path, data, config) => {
712
+ const runtime = getRuntime(RUNTIME.TXIKI), { append, create, mode, signal } = config,
713
+ // if we are permitted to write on top of existing files, then the "a" or "w" flags will simplify things.
714
+ // otherwise, we will have to open the file in "update-mode" (i.e. "r+"), then discard all of the file manually (i.e. truncate at `0`).
715
+ flag = create
716
+ ? (append ? "a" : "w")
717
+ : (append ? "a+" : "r+"), file = await runtime.open(file_path, flag, mode);
718
+ let aborted = false;
719
+ signal?.addEventListener("abort", () => {
720
+ aborted = true;
721
+ file.close();
722
+ });
723
+ if (!create && !append) {
724
+ await file.truncate(0);
725
+ }
726
+ const bytes = data instanceof Uint8Array ? data
727
+ : isString(data) ? textEncoder.encode(data)
728
+ : new Uint8Array(data.buffer, data.byteOffset);
729
+ await file.write(bytes);
730
+ await file.close();
731
+ if (aborted) {
732
+ throw new Error("AbortError");
733
+ }
734
+ };
735
+ /** reads and returns text data from a file on supported runtimes
736
+ * (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}, {@link RUNTIME.TXIKI}).
737
+ * for unsupported runtimes, an error is thrown.
738
+ *
739
+ * @param runtime_enum the runtime enum indicating which runtime should be used for reading the filesystem.
740
+ * @param file_path the source file path to read from.
741
+ * @param config provide optional configuration on how the reading should be performed.
742
+ * @throws an error is thrown if an unsupported runtime uses this function.
743
+ *
744
+ * @example
745
+ * ```ts
746
+ * import { assertStringIncludes } from "jsr:@std/assert"
747
+ *
748
+ * const my_deno_json = await readTextFile(identifyCurrentRuntime(), new URL(import.meta.resolve("../deno.json")))
749
+ * assertStringIncludes(my_deno_json, `"name": "@oazmi/kitchensink"`)
750
+ * ```
751
+ */
752
+ export const readTextFile = async (runtime_enum, file_path, config = {}) => {
753
+ file_path = ensureFileUrlIsLocalPath(file_path);
754
+ const { signal } = { ...defaultReadFileConfig, ...config }, node_config = { encoding: "utf8", signal }, deno_config = { signal }, runtime = getRuntime(runtime_enum);
755
+ switch (runtime_enum) {
756
+ case RUNTIME.DENO:
757
+ return runtime.readTextFile(file_path, deno_config);
758
+ case RUNTIME.BUN:
759
+ case RUNTIME.NODE:
760
+ return (await get_node_fs()).readFile(file_path, node_config);
761
+ case RUNTIME.TXIKI:
762
+ return textDecoder.decode(await readFile(runtime_enum, file_path, config));
763
+ default:
764
+ throw new Error(DEBUG.ERROR ? `your non-system runtime environment enum ("${runtime_enum}") does not support filesystem reading operations` : "");
765
+ }
766
+ };
767
+ /** reads and returns binary data from a file on supported runtimes
768
+ * (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}, {@link RUNTIME.TXIKI}).
769
+ * for unsupported runtimes, an error is thrown.
770
+ *
771
+ * @param runtime_enum the runtime enum indicating which runtime should be used for reading the filesystem.
772
+ * @param file_path the source file path to read from.
773
+ * @param config provide optional configuration on how the reading should be performed.
774
+ * @throws an error is thrown if an unsupported runtime uses this function.
775
+ *
776
+ * @example
777
+ * ```ts
778
+ * import { assertInstanceOf, assertStringIncludes } from "jsr:@std/assert"
779
+ *
780
+ * const my_deno_json_bytes = await readFile(identifyCurrentRuntime(), new URL(import.meta.resolve("../deno.json")))
781
+ * assertInstanceOf(my_deno_json_bytes, Uint8Array)
782
+ *
783
+ * const my_deno_json = (new TextDecoder()).decode(my_deno_json_bytes)
784
+ * assertStringIncludes(my_deno_json, `"name": "@oazmi/kitchensink"`)
785
+ * ```
786
+ */
787
+ export const readFile = async (runtime_enum, file_path, config = {}) => {
788
+ file_path = ensureFileUrlIsLocalPath(file_path);
789
+ const { signal } = { ...defaultReadFileConfig, ...config }, node_and_deno_config = { signal }, runtime = getRuntime(runtime_enum);
790
+ switch (runtime_enum) {
791
+ case RUNTIME.DENO:
792
+ return runtime.readFile(file_path, node_and_deno_config);
793
+ case RUNTIME.BUN:
794
+ case RUNTIME.NODE:
795
+ return new Uint8Array((await (await get_node_fs()).readFile(file_path, node_and_deno_config)).buffer);
796
+ case RUNTIME.TXIKI: {
797
+ const promise = runtime.readFile(file_path);
798
+ if (!signal) {
799
+ return promise;
800
+ }
801
+ const [abort_promise, abort_resolver, abort_rejector] = promise_outside();
802
+ signal.addEventListener("abort", () => { abort_rejector("AbortError"); });
803
+ return promise_race([abort_promise, promise]);
804
+ }
805
+ default:
806
+ throw new Error(DEBUG.ERROR ? `your non-system runtime environment enum ("${runtime_enum}") does not support filesystem reading operations` : "");
807
+ }
808
+ };
809
+ const fs_entry_info_fields = ["size", "mtime", "atime", "birthtime", "ctime", "dev", "mode"], fs_entry_info_all_fields = ["isFile", "isDirectory", "isSymlink", ...fs_entry_info_fields], object_assign_fields = (target, source, fields) => {
810
+ fields.forEach((prop) => { target[prop] = source[prop]; });
811
+ return target;
812
+ }, capture_nonexistent_fs_entry = (error) => {
813
+ // capture the case where the syscall declares that the file or directory does not exist.
814
+ if (string_toUpperCase(error.code) === "ENOENT") {
815
+ return undefined;
816
+ }
817
+ // otherwise, propagate the error.
818
+ throw error;
819
+ };
820
+ const node_statEntry = async (path) => {
821
+ const fs = await get_node_fs(), stat = await fs
822
+ .stat(path)
823
+ .catch(capture_nonexistent_fs_entry);
824
+ if (!stat) {
825
+ return undefined;
826
+ }
827
+ const result = object_assign_fields({
828
+ isFile: stat.isFile(),
829
+ isDirectory: stat.isDirectory(),
830
+ isSymlink: stat.isSymbolicLink(),
831
+ }, stat, fs_entry_info_fields);
832
+ return result;
833
+ };
834
+ const node_lstatEntry = async (path) => {
835
+ const fs = await get_node_fs(), stat = await fs
836
+ .lstat(path)
837
+ .catch(capture_nonexistent_fs_entry);
838
+ if (!stat) {
839
+ return undefined;
840
+ }
841
+ const result = object_assign_fields({
842
+ isFile: stat.isFile(),
843
+ isDirectory: stat.isDirectory(),
844
+ isSymlink: stat.isSymbolicLink(),
845
+ }, stat, fs_entry_info_fields);
846
+ return result;
847
+ };
848
+ const tjs_statEntry = async (path) => {
849
+ const runtime = getRuntime(RUNTIME.TXIKI), stat = await runtime
850
+ .stat(path)
851
+ .catch(capture_nonexistent_fs_entry);
852
+ if (!stat) {
853
+ return undefined;
854
+ }
855
+ const { atim: atime, birthtim: birthtime, ctim: ctime, dev, isDirectory, isFile, isSymbolicLink: isSymlink, mode, mtim: mtime, size } = stat;
856
+ return { atime, birthtime, ctime, dev, isDirectory, isFile, isSymlink, mode, mtime, size };
857
+ };
858
+ const tjs_lstatEntry = async (path) => {
859
+ const runtime = getRuntime(RUNTIME.TXIKI), stat = await runtime
860
+ .lstat(path)
861
+ .catch(capture_nonexistent_fs_entry);
862
+ if (!stat) {
863
+ return undefined;
864
+ }
865
+ const { atim: atime, birthtim: birthtime, ctim: ctime, dev, isDirectory, isFile, isSymbolicLink: isSymlink, mode, mtim: mtime, size } = stat;
866
+ return { atime, birthtime, ctime, dev, isDirectory, isFile, isSymlink, mode, mtime, size };
867
+ };
868
+ /** provides metadata information about a filesystem entry (file, folder) on supported runtimes
869
+ * (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}, {@link RUNTIME.TXIKI}).
870
+ * any symbolic links encountered at the provided `path` will be followed, and the referenced path will instead be examined.
871
+ *
872
+ * if the provided `path` does not exist on the filesystem, then `undefined` will be returned.
873
+ *
874
+ * > [!note]
875
+ * > only the fields that are common to windows, linux, and mac systems have been kept,
876
+ * > while the stat fields specific to only a subset of the common platforms have been omitted.
877
+ *
878
+ * @example
879
+ * ```ts
880
+ * import { assertEquals, assertInstanceOf, assertObjectMatch } from "jsr:@std/assert"
881
+ *
882
+ * const
883
+ * time_fields: (keyof FsEntryInfo)[] = ["mtime", "atime", "birthtime", "ctime"],
884
+ * numeric_fields: (keyof FsEntryInfo)[] = ["size", "dev", "mode"]
885
+ *
886
+ * const my_deno_json_stats = (await statEntry(identifyCurrentRuntime(), new URL(import.meta.resolve("../deno.json"))))!
887
+ *
888
+ * assertObjectMatch(my_deno_json_stats, {
889
+ * isFile: true,
890
+ * isDirectory: false,
891
+ * isSymlink: false,
892
+ * })
893
+ *
894
+ * time_fields.forEach((prop) => {
895
+ * assertInstanceOf(my_deno_json_stats[prop], Date)
896
+ * })
897
+ *
898
+ * numeric_fields.forEach((prop: keyof FsEntryInfo) => {
899
+ * assertEquals(typeof my_deno_json_stats[prop], "number")
900
+ * })
901
+ *
902
+ * // unlike node and deno, non-existing paths do not error, and instead `undefined` is returned.
903
+ * const non_existing_path_stat = await statEntry(identifyCurrentRuntime(), new URL(import.meta.resolve("../hello/world/file.txt")))
904
+ * assertEquals(non_existing_path_stat, undefined)
905
+ * ```
906
+ */
907
+ export const statEntry = async (runtime_enum, path) => {
908
+ switch (runtime_enum) {
909
+ case RUNTIME.DENO: {
910
+ const stat = await getRuntime(runtime_enum).stat(path).catch(capture_nonexistent_fs_entry);
911
+ if (!stat) {
912
+ return undefined;
913
+ }
914
+ const result = object_assign_fields({}, stat, fs_entry_info_all_fields);
915
+ return result;
916
+ }
917
+ case RUNTIME.BUN:
918
+ case RUNTIME.NODE:
919
+ return node_statEntry(ensureFileUrlIsLocalPath(path));
920
+ case RUNTIME.TXIKI:
921
+ return tjs_statEntry(ensureFileUrlIsLocalPath(path));
922
+ default:
923
+ throw new Error(DEBUG.ERROR ? `your non-system runtime environment enum ("${runtime_enum}") does not support filesystem stat-query operations` : "");
924
+ }
925
+ };
926
+ /** similar to {@link statEntry}, but any symbolic links encountered at the provided `path` will not be followed,
927
+ * and instead you will receive the stats of the symbolic link itself.
928
+ *
929
+ * read the documentation comments of {@link statEntry} for usage details.
930
+ * ```
931
+ */
932
+ export const lstatEntry = async (runtime_enum, path) => {
933
+ switch (runtime_enum) {
934
+ case RUNTIME.DENO: {
935
+ const stat = await getRuntime(runtime_enum).lstat(path).catch(capture_nonexistent_fs_entry);
936
+ if (!stat) {
937
+ return undefined;
938
+ }
939
+ const result = object_assign_fields({}, stat, fs_entry_info_all_fields);
940
+ return result;
941
+ }
942
+ case RUNTIME.BUN:
943
+ case RUNTIME.NODE:
944
+ return node_lstatEntry(ensureFileUrlIsLocalPath(path));
945
+ case RUNTIME.TXIKI:
946
+ return tjs_lstatEntry(ensureFileUrlIsLocalPath(path));
947
+ default:
948
+ throw new Error(DEBUG.ERROR ? `your non-system runtime environment enum ("${runtime_enum}") does not support filesystem lstat-query operations` : "");
949
+ }
950
+ };
951
+ /** creates a nested directory if it does not already exist. only supported on system runtime
952
+ * (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}, {@link RUNTIME.TXIKI}).
953
+ *
954
+ * @throws an error is thrown if something other than a folder already existed at the provided path.
955
+ *
956
+ * @example
957
+ * ```ts
958
+ * import { assertEquals, assertObjectMatch } from "jsr:@std/assert"
959
+ *
960
+ * const
961
+ * runtime_id = identifyCurrentRuntime(),
962
+ * my_dir = new URL(import.meta.resolve("../temp/a/b/c/")),
963
+ * my_dir2 = new URL(import.meta.resolve("../temp/a/"))
964
+ *
965
+ * await ensureDir(runtime_id, my_dir)
966
+ *
967
+ * // the directory now exists
968
+ * assertObjectMatch((await statEntry(runtime_id, my_dir))!, {
969
+ * isFile: false,
970
+ * isDirectory: true,
971
+ * isSymlink: false,
972
+ * })
973
+ *
974
+ * // deleting the base directory (recursively)
975
+ * assertEquals(await removeEntry(runtime_id, my_dir2, { recursive: true }), true)
976
+ *
977
+ * // the directory no longer exists
978
+ * assertEquals(await statEntry(runtime_id, my_dir), undefined)
979
+ * assertEquals(await statEntry(runtime_id, my_dir2), undefined)
980
+ * ```
981
+ */
982
+ export const ensureDir = async (runtime_enum, dir_path) => {
983
+ dir_path = ensureEndSlash(ensureFileUrlIsLocalPath(dir_path));
984
+ const existing_entry_stats = await statEntry(runtime_enum, dir_path);
985
+ if (existing_entry_stats?.isDirectory) {
986
+ return;
987
+ }
988
+ const runtime = getRuntime(runtime_enum);
989
+ switch (runtime_enum) {
990
+ case RUNTIME.DENO:
991
+ return runtime.mkdir(dir_path, { recursive: true });
992
+ case RUNTIME.BUN:
993
+ case RUNTIME.NODE:
994
+ return get_node_fs()
995
+ .then((fs) => fs.mkdir(dir_path, { recursive: true }))
996
+ .then(noop);
997
+ case RUNTIME.TXIKI:
998
+ return runtime.makeDir(dir_path, { recursive: true });
999
+ default:
1000
+ throw new Error(DEBUG.ERROR ? `your non-system runtime environment enum ("${runtime_enum}") does not support filesystem writing operations` : "");
1001
+ }
1002
+ };
1003
+ /** ensures that the file exists on system-bound runtimes
1004
+ * (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}, {@link RUNTIME.TXIKI}).
1005
+ *
1006
+ * if the file already exists, this function does nothing.
1007
+ * if the parent directories for the file do not exist yet, they are created recursively.
1008
+ *
1009
+ * @throws an error is thrown if something other than a file already existed at the provided path,
1010
+ * or if creating the parent directory had failed.
1011
+ */
1012
+ export const ensureFile = async (runtime_enum, file_path) => {
1013
+ file_path = ensureFileUrlIsLocalPath(file_path);
1014
+ const existing_entry_stats = await statEntry(runtime_enum, file_path);
1015
+ if (existing_entry_stats?.isFile) {
1016
+ return;
1017
+ }
1018
+ // if the file does not already exist, then ensure that its path's parent directory exists, and then create the file.
1019
+ const parent_dir = parseFilepathInfo(file_path).dirpath;
1020
+ await ensureDir(runtime_enum, parent_dir);
1021
+ return writeFile(runtime_enum, file_path, new Uint8Array(0));
1022
+ };
1023
+ /** deletes a filesystem-entry (file, folder, symlink) at the provided `path`, on system-bound runtimes
1024
+ * (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}, {@link RUNTIME.TXIKI}).
1025
+ * the return value dictates if anything was deleted.
1026
+ *
1027
+ * > [!note]
1028
+ * > trying to remove a non-existing entry will not throw an error.
1029
+ *
1030
+ * > [!warning]
1031
+ * > the `recursive` option is always enabled for {@link RUNTIME.TXIKI | `txiki.js`}.
1032
+ *
1033
+ * @param runtime_enum the runtime enum indicating which runtime should be used for reading the filesystem.
1034
+ * @param path the path to the filesystem entity that is to be deleted.
1035
+ * @param config provide optional configuration on what _type_s of filesystem-entries should be deleted,
1036
+ * and if folder type entries should be deleted `recursive`ly (i.e. delete child entries as well).
1037
+ * see the {@link RemoveEntryConfig} interface for more details.
1038
+ * @returns `true` if an entry (or a folder tree's entries) was deleted, otherwise `false` is returned when nothing is deleted.
1039
+ */
1040
+ export const removeEntry = async (runtime_enum, path, config = {}) => {
1041
+ const { recursive = false, ...is_types } = config, existing_entry_stats = await statEntry(runtime_enum, path);
1042
+ // if the `path` does not exist, then there is nothing to delete
1043
+ if (!existing_entry_stats) {
1044
+ return false;
1045
+ }
1046
+ // if there is a mismatch between the existing fs-entry's type and the permitted/forbidden list of fs-types (`is_types`), then delete nothing and return.
1047
+ for (const [entry_type, is_permitted] of object_entries(is_types)) {
1048
+ if (existing_entry_stats[entry_type] !== is_permitted) {
1049
+ return false;
1050
+ }
1051
+ }
1052
+ const runtime = getRuntime(runtime_enum);
1053
+ // now, if there's no mismatch, then it is time for the deletion to take place.
1054
+ switch (runtime_enum) {
1055
+ case RUNTIME.DENO:
1056
+ return runtime.remove(path, { recursive }).then(() => true);
1057
+ case RUNTIME.BUN:
1058
+ case RUNTIME.NODE:
1059
+ return get_node_fs()
1060
+ .then((fs) => fs.rm(ensureFileUrlIsLocalPath(path), { recursive }))
1061
+ .then(() => true);
1062
+ case RUNTIME.TXIKI:
1063
+ return runtime.remove(ensureFileUrlIsLocalPath(path))
1064
+ .then(() => true);
1065
+ default:
1066
+ throw new Error(DEBUG.ERROR ? `your non-system runtime environment enum ("${runtime_enum}") does not support filesystem writing operations` : "");
1067
+ }
1068
+ };
1069
+ /** read a directory's immediate child entries (files, folders, and symbolic links).
1070
+ * this operation is not recursive, and so, subdirectories will not be traversed.
1071
+ *
1072
+ * @throws an error is thrown if the directory does not exist, or if it is a file/symbolic-link.
1073
+ *
1074
+ * > [!note]
1075
+ * > each directory entry's name is stripped off of any trailing slash, and any leading dot-slash.
1076
+ * > this means, you will not be able to distinguish files from folders and symbolic links based on their
1077
+ * > {@link FsEntryRecord.name | name} alone, and instead, you will have to rely on the `isXYZ` flags.
1078
+ *
1079
+ * TODO: what about symbolic links? should I follow them until I hit a "real" entry?
1080
+ * TODO: should I also give an option for recursive traversal? I think it might lead to more complexity,
1081
+ * but at the same time, it'll be faster since we won't have to double check the any subdir `dir_path`'s existence via `statEntry`.
1082
+ *
1083
+ * @example
1084
+ * ```ts
1085
+ * import { assertEquals, assertObjectMatch } from "jsr:@std/assert"
1086
+ *
1087
+ * const
1088
+ * runtime_id = identifyCurrentRuntime(),
1089
+ * base_dir = new URL(import.meta.resolve("../temp/a/")),
1090
+ * my_dir = new URL(import.meta.resolve("../temp/a/b/c/")),
1091
+ * my_file1 = new URL(import.meta.resolve("../temp/a/b/x.txt")),
1092
+ * my_file2 = new URL(import.meta.resolve("../temp/a/y.txt")),
1093
+ * my_file3 = new URL(import.meta.resolve("../temp/a/z.txt"))
1094
+ *
1095
+ * await ensureDir(runtime_id, my_dir)
1096
+ * await ensureFile(runtime_id, my_file1)
1097
+ * await ensureFile(runtime_id, my_file2)
1098
+ * await ensureFile(runtime_id, my_file3)
1099
+ *
1100
+ * // the directories and files should now exist.
1101
+ * // so now, we'll iterate over them.
1102
+ * const entries: Record<string, FsEntryRecord> = {}
1103
+ * for await (const entry of readDir(runtime_id, base_dir)) {
1104
+ * entries[entry.name] = entry
1105
+ * }
1106
+ *
1107
+ * assertEquals(Object.keys(entries).length, 3)
1108
+ *
1109
+ * assertObjectMatch(entries["b"], {
1110
+ * name: "b",
1111
+ * isFile: false,
1112
+ * isDirectory: true,
1113
+ * isSymlink: false,
1114
+ * })
1115
+ *
1116
+ * assertObjectMatch(entries["y.txt"], {
1117
+ * name: "y.txt",
1118
+ * isFile: true,
1119
+ * isDirectory: false,
1120
+ * isSymlink: false,
1121
+ * })
1122
+ *
1123
+ * assertObjectMatch(entries["z.txt"], {
1124
+ * name: "z.txt",
1125
+ * isFile: true,
1126
+ * isDirectory: false,
1127
+ * isSymlink: false,
1128
+ * })
1129
+ *
1130
+ * // deleting the base directory (recursively)
1131
+ * assertEquals(await removeEntry(runtime_id, base_dir, { recursive: true }), true)
1132
+ *
1133
+ * // the directory no longer exists
1134
+ * assertEquals(await statEntry(runtime_id, base_dir), undefined)
1135
+ * ```
1136
+ */
1137
+ export async function* readDir(runtime_enum, dir_path) {
1138
+ dir_path = ensureEndSlash(ensureFileUrlIsLocalPath(dir_path));
1139
+ const existing_entry_stats = await statEntry(runtime_enum, dir_path);
1140
+ // if the folder does not exist, we will throw an error.
1141
+ if (existing_entry_stats === undefined || !existing_entry_stats.isDirectory) {
1142
+ throw new Error(DEBUG.ERROR ? `[readDir]: the directory "${dir_path}" does not exist (it might be a file).` : "");
1143
+ }
1144
+ const runtime = getRuntime(runtime_enum);
1145
+ switch (runtime_enum) {
1146
+ case RUNTIME.DENO: {
1147
+ yield* runtime.readDir(dir_path);
1148
+ break;
1149
+ }
1150
+ case RUNTIME.BUN:
1151
+ case RUNTIME.NODE: {
1152
+ const fs = await get_node_fs(), dir_iterator = await fs.readdir(dir_path, { recursive: false, withFileTypes: true });
1153
+ for await (const dir_entry of dir_iterator) {
1154
+ yield {
1155
+ isDirectory: dir_entry.isDirectory(),
1156
+ isFile: dir_entry.isFile(),
1157
+ isSymlink: dir_entry.isSymbolicLink(),
1158
+ name: dir_entry.name,
1159
+ };
1160
+ }
1161
+ break;
1162
+ }
1163
+ case RUNTIME.TXIKI: {
1164
+ const dir_iterator = await runtime.readDir(dir_path);
1165
+ for await (const { isDirectory, isFile, isSymbolicLink: isSymlink, name } of dir_iterator) {
1166
+ yield { isDirectory, isFile, isSymlink, name };
1167
+ }
1168
+ break;
1169
+ }
1170
+ default:
1171
+ throw new Error(DEBUG.ERROR ? `your non-system runtime environment enum ("${runtime_enum}") does not support filesystem reading operations` : "");
1172
+ }
1173
+ }