@oazmi/kitchensink
Version:
a collection of personal utility functions
333 lines • 17.4 kB
TypeScript
/** a submodule for cross-runtime environment utility functions.
*
* the functions in this submodule do not place a "hard" dependency on the runtime environments.
* instead, the various runtime-globals are defined as "soft" objects/symbols which may or may not exist.
* as a result, some of the functions (such as {@link getRuntime}) are very weakly/generically typed,
* and their results are not narrowed based on your input arguments.
* the advantage of such is that this submodule will not choke your LSP.
* not to mention that you'll probably be aware of what runtime you're working with anyway,
* so you can always use the `as` assertions to confine the return value to a certain runtime's feature.
*
* for most functions in here, you will need to provide the {@link RUNTIME} enum that you are querying for.
*
* to identify your script's current {@link RUNTIME} environment, you'd want to use the {@link identifyCurrentRuntime} function.
*
* TODO: additional features to add in the future:
* - [x] filesystem read/writing on system runtimes (deno, bun, and node).
* - [ ] filesystem read/writing on web/extension runtimes will use the browser's FileAccess API (and prompt the user to select the folder)
* - [ ] filesystem read/writing on web/extension runtimes will use `window.navigator.storage.getDirectory()`.
* and for web workers, we may use `self.navigator.storage.getDirectory()` instead.
* - [ ] persistent key-value storage: such as `localStorage` or `sessionStorage` or `chrome.storage.sync` or kv-storage of `window.navigator.storage.persist()`.
* copy these from your github-aid browser extension project.
* - [x] system environment variables.
* - [ ] shell/commandline/terminal command execution.
* - [ ] (breaking change) consider using a class based approach to calling these functions as methods,
* where the currently selected runtime will be known by the class instance,
* so that the user will not have to pass down which runtime they are querying for all the time.
* - [ ] (RISKY) add a `setEnvVariable` function.
* 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.
*
* @module
*/
import "./_dnt.polyfills.js";
/** javascript runtime enums. */
export declare const enum RUNTIME {
/** deno runtime.
*
* since deno also supports `process` for node compatibility, you will want to check for deno runtime before checking for node runtime.
*/
DENO = 0,
/** bunjs runtime.
*
* since bun also supports `process` for node compatibility, you will want to check for bun runtime before checking for node runtime.
*/
BUN = 1,
/** nodejs runtime. */
NODE = 2,
/** chrome-extension runtime.
*
* 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.
*/
CHROMIUM = 3,
/** firefox (or any non-chromium) extension runtime.
*
* 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.
*/
EXTENSION = 4,
/** web-browser runtime. */
WEB = 5,
/** worker-script runtime. */
WORKER = 6
}
/** a map/record of runtime validation functions that determine if the current javascript environment matches a specific runtime.
*
* each key represents a runtime type defined by the {@link RUNTIME} enum,
* and each value is a function that returns a boolean indicating whether your current environment
* satisfies the global-object requirements of the given {@link RUNTIME}.
*
* @example
* ```ts ignore
* if (currentRuntimeValidationFnMap[RUNTIME.NODE]()) {
* // execute nodejs-specific logic
* console.log("current nodejs working directory is:", process.cwd())
* }
* if (currentRuntimeValidationFnMap[RUNTIME.DENO]()) {
* // execute deno-specific logic
* console.log("current deno working directory is:", Deno.cwd())
* }
* if (currentRuntimeValidationFnMap[RUNTIME.WEB]()) {
* // execute web-specific logic
* console.log("current webpage's url is:", globalThis.location?.href)
* }
* ```
*/
export declare const currentRuntimeValidationFnMap: Record<RUNTIME, (() => boolean)>;
/** identifies the current javascript runtime environment as a {@link RUNTIME} enum.
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* assertEquals(identifyCurrentRuntime(), RUNTIME.DENO)
* ```
*/
export declare const identifyCurrentRuntime: () => RUNTIME;
/** get the global-runtime-object of the given javascript environment {@link RUNTIME} enum.
*
* > [!note]
* > if you acquire the global-runtime-object of an environment that is not supported by your actual current environment,
* > then the returned value will be `undefined`.
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
* import process from "node:process" // this works in deno 2.0
*
* assertEquals(getRuntime(RUNTIME.DENO), Deno)
* assertEquals(getRuntime(RUNTIME.NODE), process)
* assertEquals(getRuntime(identifyCurrentRuntime()), Deno)
* ```
*/
export declare const getRuntime: (runtime_enum: RUNTIME) => any;
/** retrieves the current working directory or URL based on the specified {@link RUNTIME} enum.
*
* > [!note]
* > - the returned directory path may or may not end in a trailing slash.
* > this is intentional, as it is possible for the path to actually point towards a file.
* > (such as in the case of `chrome.runtime.getURL("")`)
* > - however, the returned path will always use posix separators (only forward slashes, no windows backslashes).
* > - if you try to query the working directory of a runtime enum that your current environment does not support,
* > then an error will be thrown, because you'll be accessing an `undefined` object.
*
* depending on the provided `runtime_enum`, the current working directory is defined as the following:
* - for `DENO`, `BUN`, and `NODE`: the result will be that of the runtime's `cwd()` method.
* - for `CHROMIUM` and `EXTENSION`: the result will be the url string obtained from `runtime.getURL("")`.
* - for `WEB` and `WORKER`: a url string will be returned that will vary based on the `current_path` flag (`true` by default):
* - if `current_path == false`: `location.origin` will be returned (i.e. the root of your webpage, which is your domain-name + subdomain-name).
* - if `current_path == true`: the directory path of `location.href` will be returned.
*
* @param runtime_enum the runtime enum indicating which runtime's working-directory/url-path to retrieve.
* @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`.
* @returns a posix string path of the working-directory/url-path of the specified runtime.
* @throws an error is thrown if the runtime associated with the provided enum is undefined, or if an invalid enum is provided.
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
* import process from "node:process" // this works in deno 2.0
*
* assertEquals(getRuntimeCwd(RUNTIME.DENO), getRuntimeCwd(RUNTIME.NODE))
* assertEquals(getRuntimeCwd(RUNTIME.DENO), Deno.cwd().replaceAll(/\\\\?/g, "/"))
* assertEquals(getRuntimeCwd(RUNTIME.NODE), process.cwd().replaceAll(/\\\\?/g, "/"))
* ```
*/
export declare const getRuntimeCwd: (runtime_enum: RUNTIME, current_path?: boolean) => string;
/** retrieves the value of an environment variable on system runtimes (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}).
* otherwise an error gets thrown on all other environments, since they do not support environment variables.
*
* > [!tip]
* > - environment variables are case-insensitive.
* > - you will probably want to normalize path variables to posix path via {@link pathToPosixPath}.
* > - if `env_var = ""` (an empty string) then `undefined` will always be returned on system-runtime environments.
*
* @param runtime_enum the runtime enum indicating which runtime should be used for querying the environment variable.
* @param env_var the name of the environment variable to fetch.
* @returns the environment variable's value.
* @throws for js-workers, extensions, and web environments, an error gets thrown, as environment variables are not available.
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* const my_path_env_var = getEnvVariable(identifyCurrentRuntime(), "path")!
*
* assertEquals(typeof my_path_env_var, "string")
* assertEquals(my_path_env_var.length > 0, true)
* ```
*/
export declare const getEnvVariable: (runtime_enum: RUNTIME, env_var: string) => string | undefined;
/** configuration options for the {@link execShellCommand} function. */
export interface ExecShellCommandConfig {
/** cli-arguments to pass to the process.
*
* @defaultValue `[]` (empty array)
*/
args: string[];
/** set the working directory of the process.
*
* if not specified, the `cwd` of the parent process is inherited.
*
* @defaultValue `undefined`
*/
cwd?: string | URL | undefined;
/** provide an optional abort signal to force close the process, by sending a "SIGTERM" os-signal to it.
*
* @defaultValue `undefined`
*/
signal?: AbortSignal;
}
export interface ExecShellCommandResult {
stdout: string;
stderr: string;
}
/** execute a shell/terminal command on system runtimes (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}).
* otherwise an error gets thrown on all other environments, since they do not support shell command execution.
*
* > [!note]
* > we don't use `Deno.Command` for deno here, because it does not default to your os's native preferred terminal,
* > and instead you will need to provide one yourself (such as "bash", "cmd", "shell", etc...).
* > which is why we use `node:child_process` for all three runtimes.
*
* @param runtime_enum the runtime enum indicating which runtime should be used for querying the environment variable.
* @param command the shell command to execute.
* @param config optional configuration to apply onto the shell child-process.
* @returns a promise that is resolved when the child process that executed the command has closed.
*
* @example
* ```ts
* import { assertEquals, assertStringIncludes } from "jsr:@std/assert"
*
* {
* const { stdout, stderr } = await execShellCommand(identifyCurrentRuntime(), "echo Hello World!")
* assertStringIncludes(stdout, "Hello World!")
* assertEquals(stderr, "")
* }
*
* {
* const { stdout, stderr } = await execShellCommand(identifyCurrentRuntime(), "echo", { args: ["Hello", "World!"] })
* assertStringIncludes(stdout, "Hello World!")
* assertEquals(stderr, "")
* }
* ```
*/
export declare const execShellCommand: (runtime_enum: RUNTIME, command: string, config?: Partial<ExecShellCommandConfig>) => Promise<ExecShellCommandResult>;
/** configuration options for the {@link writeTextFile} and {@link writeFile} function. */
export interface WriteFileConfig {
/** when set to `true`, the new text will be appended to the file, instead of overwriting the previous contents.
*
* @defaultValue `false`
*/
append: boolean;
/** allow the creation of a new file if one does not already exist at the specified path.
* when set to `false`, and a file does not already exist at the specified path, then an error will be thrown,
* causing the write operation promise to be rejected.
*
* @defaultValue `true`
*/
create: boolean;
/** supply an optional unix r/w/x-permission mode to apply to the file.
*
* > [!note]
* > setting file chmod-permissions on windows does nothing!
* > (is that a limitation of deno? I don't know)
*
* - if you're unfamiliar with this, check out this stack-exchange answer: [link](https://unix.stackexchange.com/a/184005).
* - if you'd like to calculate the permission number, try this online permission calculator: [link](https://chmod-calculator.com/).
* - REMEMBER: the file permission number is in octal-representation!
* so for setting an "allow all" permission, which is denoted as "777", you would pass `0o777`.
*
* @defaultValue `0o644` (this is an assumption, since that's the linux default) (but node's docs mentions the default to be `0o666`)
*/
mode: number | undefined;
/** provide an optional abort signal to allow the cancellation of the file write operation.
*
* if the signal becomes aborted, the write file operation will be stopped and the promise returned will be rejected with an `AbortError`.
*
* @defaultValue `undefined`
*/
signal?: AbortSignal | undefined;
}
/** configuration options for the {@link readTextFile} and {@link readFile} functions. */
export interface ReadFileConfig {
/** provide an optional abort signal to allow the cancellation of the file reading operation.
*
* if the signal becomes aborted, the read file operation will be stopped and the promise returned will be rejected with an `AbortError`.
*
* @defaultValue `undefined`
*/
signal?: AbortSignal;
}
/** writes text data to a file on supported runtimes (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}).
* for unsupported runtimes, an error is thrown.
*
* TODO: in the future, I would like to create a unified cross-runtime filesystem class under `./crossfs.ts`,
* which would support read and write operations, along with memory (internal state) of the current working directory,
* in addition to a path resolver method that will rely on `./pathman.ts`'s `resolvePathFactory` function.
*
* @param runtime_enum the runtime enum indicating which runtime should be used for writing onto the filesystem.
* @param file_path the destination file path.
* @param text the string content to write.
* @param config provide optional configuration on how the writing should be performed.
* @throws an error is thrown if an unsupported runtime uses this function,
* or if `config.create` is `false`, and no pre-existing file resides at the specified `file_path`.
*/
export declare const writeTextFile: (runtime_enum: RUNTIME, file_path: string | URL, text: string, config?: Partial<WriteFileConfig>) => Promise<void>;
/** writes binary/buffer data to a file on supported runtimes (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}).
* for unsupported runtimes, an error is thrown.
*
* @param runtime_enum the runtime enum indicating which runtime should be used for writing onto the filesystem.
* @param file_path the destination file path.
* @param data the byte/buffer data to write to the file.
* @param config provide optional configuration on how the writing should be performed.
* @throws an error is thrown if an unsupported runtime uses this function,
* or if `config.create` is `false`, and no pre-existing file resides at the specified `file_path`.
*/
export declare const writeFile: (runtime_enum: RUNTIME, file_path: string | URL, data: ArrayBufferView, config?: Partial<WriteFileConfig>) => Promise<void>;
/** reads and returns text data from a file on supported runtimes (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}).
* for unsupported runtimes, an error is thrown.
*
* @param runtime_enum the runtime enum indicating which runtime should be used for reading the filesystem.
* @param file_path the source file path to read from.
* @param config provide optional configuration on how the reading should be performed.
* @throws an error is thrown if an unsupported runtime uses this function.
*
* @example
* ```ts
* import { assertStringIncludes } from "jsr:@std/assert"
*
* const my_deno_json = await readTextFile(identifyCurrentRuntime(), new URL(import.meta.resolve("../deno.json")))
* assertStringIncludes(my_deno_json, `"name": "@oazmi/kitchensink"`)
* ```
*/
export declare const readTextFile: (runtime_enum: RUNTIME, file_path: string | URL, config?: Partial<ReadFileConfig>) => Promise<string>;
/** reads and returns binary data from a file on supported runtimes (i.e. {@link RUNTIME.DENO}, {@link RUNTIME.BUN}, or {@link RUNTIME.NODE}).
* for unsupported runtimes, an error is thrown.
*
* @param runtime_enum the runtime enum indicating which runtime should be used for reading the filesystem.
* @param file_path the source file path to read from.
* @param config provide optional configuration on how the reading should be performed.
* @throws an error is thrown if an unsupported runtime uses this function.
*
* @example
* ```ts
* import { assertInstanceOf, assertStringIncludes } from "jsr:@std/assert"
*
* const my_deno_json_bytes = await readFile(identifyCurrentRuntime(), new URL(import.meta.resolve("../deno.json")))
* assertInstanceOf(my_deno_json_bytes, Uint8Array)
*
* const my_deno_json = (new TextDecoder()).decode(my_deno_json_bytes)
* assertStringIncludes(my_deno_json, `"name": "@oazmi/kitchensink"`)
* ```
*/
export declare const readFile: (runtime_enum: RUNTIME, file_path: string | URL, config?: Partial<ReadFileConfig>) => Promise<Uint8Array>;
//# sourceMappingURL=crossenv.d.ts.map