# @rickosborne/term

Rick Osborne's collection of utilities for working at the terminal, with local files and processes, etc.
Built on:

- [@rickosborne/typical](https://www.npmjs.com/package/@rickosborne/typical) for types
- [@rickosborne/guard](https://www.npmjs.com/package/@rickosborne/guard) for guards
- [@rickosborne/foundation](https://www.npmjs.com/package/@rickosborne/foundation) for basic data structures and algorithms

## Usage

Install via your favorite package manager.

Each package supports CommonJS `require`, ESM `import`, and TypeScript usage.

You also have a choice: barrel imports or direct imports.

Barrel imports mean you're going to require/import everything from the same package-level namespace:

```typescript
// CommonJS
const { isPlainObject, isListOf } = require("@rickosborne/guard");
// ESM / TypeScript
import { isPlainObject, isListOf } from "@rickosborne/guard";
```

Implications:

- Nice and simple.
- Your build system needs to do tree-shaking well ... or you'll end up adding the entire package even if you only import two functions.

The other option is to use direct imports:

```typescript
// CommonJS
const { isPlainObject } = require("@rickosborne/guard/is-object");
const { isListOf } = require("@rickosborne/guard/is-list-of");
// ESM / TypeScript
import { isPlainObject } from "@rickosborne/guard/is-object.js";
import { isListOf } from "@rickosborne/guard/is-list-of.js";
```

Implications:

- You (probably) don't have to worry about tree-shaking as your build (likely) ends up with only the functions you need.

If you're using a modern build system, there aren't any strong reasons to prefer one way over the other.
It's really just down to your personal preference.

### A quick note about file extensions

Do you need to use file extensions?
And if so, which extensions?

Honestly ... this is a dumpster fire question.
It really comes down to your own setup and configuration.

Within each package itself:

- The CommonJS files all have `.cjs` extensions.
- The ESM files all have `.mjs` extensions.
- Node subpath exports have been set up to send `.js` imports to the `.cjs` (via `require`) or `.mjs` (via `import`) files, depending on your setup.

So, in theory, the only extension which _won't_ work would be `.ts` because the source isn't included.

If you run into a problem with a particular configuration, file a GitHub issue with:

- Your `tsconfig.json`'s `module`, `moduleResolution`, and `target` settings.
- Your `package.json`'s `type` and `imports` settings.
- An example of another package which imports correctly for you.

## License

This package is licensed as [CC-BY-NC-SA-4.0] unless otherwise noted.
That is, Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

[CC-BY-NC-SA-4.0]: https://creativecommons.org/licenses/by-nc-sa/4.0/


***

## API

### Classes

#### FileExistsError

<a id="api-fileexistserror"></a>

```typescript
 class FileExistsError extends Error 
```

An error thrown when a file is expected to not exist, but does.


#### FileMissingError

<a id="api-filemissingerror"></a>

```typescript
 class FileMissingError extends Error 
```

Error thrown if a file does not exist.


#### NotFileError

<a id="api-notfileerror"></a>

```typescript
 class NotFileError extends Error 
```

Error thrown when a filesystem entry is expected to be a file, but is not.


### Functions

#### assertFileExists

<a id="api-assertfileexists"></a>

```typescript
assertFileExists: (filePath: string, config?: AssertFileExistsConfig) => Stats
```

#### compareExportsKeys

<a id="api-compareexportskeys"></a>

```typescript
compareExportsKeys: (a: string, b: string) => number
```

Comparator for the keys of a <kbd>package.json</kbd>'s <kbd>exports</kbd> map, to ensure they are serialized in the correct order.


#### copyRecursiveFilterDefault

<a id="api-copyrecursivefilterdefault"></a>

```typescript
copyRecursiveFilterDefault: (dirEnt: Dirent) => boolean
```

#### copyRecursiveSync

<a id="api-copyrecursivesync"></a>

```typescript
copyRecursiveSync: (source: string, destination: string, options?: CopyRecursiveOptions) => CopyRecursiveResult
```

#### dirExists

<a id="api-direxists"></a>

```typescript
dirExists: (...parts: string[]) => boolean
```

Synchronous check to see if the given path exists and is a directory.


#### envOrArg

<a id="api-envorarg"></a>

```typescript
envOrArg: (nameOrNames: string | string[], options?: EnvOrArgOptions) => string | undefined
```

Best-effort attempt to find a configuration arg in both the environment and the command-line, when you don't want to define the entire parameter set. Tries its best to do what you mean, such as converting `SNAKE_CASE` environment params to `--kebab-case` command line params and vice versa. Returns [ARG_FLAG](#api-arg-flag) if it appears the command line param is present, but is actually a flag.


#### fileExists

<a id="api-fileexists"></a>

```typescript
 function fileExists(...parts: string[]): boolean;
```

Synchronous check to see whether the given path exists and is a file.


#### fileExists

<a id="api-fileexists"></a>

```typescript
 function fileExists(statSync: StatSyncLike, ...parts: string[]): boolean;
```

Synchronous check to see whether the given path exists and is a file. This form accepts an override for `fs.statSync`.


#### gitInfo

<a id="api-gitinfo"></a>

```typescript
gitInfo: () => GitInfo
```

Get very basic git status.


#### pathWithPackageJson

<a id="api-pathwithpackagejson"></a>

```typescript
pathWithPackageJson: (...parts: string[]) => string
```

Ensure the given path points to a <kbd>package.json</kbd> file and not just a directory.


#### positionalArgs

<a id="api-positionalargs"></a>

```typescript
positionalArgs: (args?: string[], config?: PositionalArgsConfig) => string[]
```

Try to find command line arguments which look like positionals. This is super, duper basic and only useful in very simple scenarios where you expect mostly positionals and no interesting flag configurations.


#### preparePackageJsonForSerialization

<a id="api-preparepackagejsonforserialization"></a>

```typescript
preparePackageJsonForSerialization: (pkg: PackageJsonLike) => PackageJsonLike
```

Mangle a <kbd>package.json</kbd> structure to make it serialize properly.


#### readFile

<a id="api-readfile"></a>

```typescript
readFile: (path: string) => string
```

Synchronous read of a file as text using UTF-8 encoding.


#### readJson

<a id="api-readjson"></a>

```typescript
readJson: <T>(path: string) => T
```

Synchronous read of a file as UTF-8 text, parsing it as JSON.


#### readPackageJson

<a id="api-readpackagejson"></a>

```typescript
readPackageJson: (pathOrModuleName: string) => PackageJsonLike
```

Read a <kbd>package.json</kbd> from the given path, returning it as structured data instead of just text.


#### remapExports

<a id="api-remapexports"></a>

```typescript
remapExports: <R extends Record<string, string>>(exports: R) => R
```

Given a <kbd>package.json</kbd>'s <kbd>exports</kbd> map, generate a version which should serialize in the correct order.


#### statsForFile

<a id="api-statsforfile"></a>

```typescript
statsForFile: (filePath: string, config?: StatsForFileConfig) => fs.Stats | undefined
```

Get the stats for a path, if it exists. Throws if the path does not point to a file.


#### writeJson

<a id="api-writejson"></a>

```typescript
writeJson: (filePath: string, value: unknown, config?: WriteJsonConfig) => void
```

Serialize and synchronously write out JSON to a UTF-8 text file. Has some extra special handling for <kbd>package.json</kbd> files.


#### writeText

<a id="api-writetext"></a>

```typescript
writeText: (filePath: string, text: string, config?: WriteTextConfig) => void
```

Synchronously write a file as UTF-8 text.


### Interfaces

#### AssertFileExistsConfig

<a id="api-assertfileexistsconfig"></a>

```typescript
export interface AssertFileExistsConfig 
```

Configuration for a [assertFileExists](#api-assertfileexists) call.


#### FileConflictErrorOptions

<a id="api-fileconflicterroroptions"></a>

```typescript
export interface FileConflictErrorOptions extends ErrorOptions 
```

Options for the [FileExistsError](#api-fileexistserror) constructor.


#### FileMissingErrorOptions

<a id="api-filemissingerroroptions"></a>

```typescript
export interface FileMissingErrorOptions extends ErrorOptions 
```

Options for the [FileMissingError](#api-filemissingerror) constructor.


#### GitInfo

<a id="api-gitinfo"></a>

```typescript
export interface GitInfo extends RawGitInfo 
```

Information about the current state of git.


#### NotFileErrorOptions

<a id="api-notfileerroroptions"></a>

```typescript
export interface NotFileErrorOptions extends ErrorOptions 
```

Options for the [NotFileError](#api-notfileerror) constructor.


#### RawGitInfo

<a id="api-rawgitinfo"></a>

```typescript
export interface RawGitInfo 
```

The text values which can be obtained from a `git log` invocation.


### TypeAliases

#### CopyRecursiveOptions

<a id="api-copyrecursiveoptions"></a>

```typescript
type CopyRecursiveOptions = {
    copyFileSync?: (source: string, destination: string) => void;
    keepIf?: (dirEnt: Dirent) => boolean;
    log?: (message: string) => void;
    mkdirSync?: (path: string, options: {
        recursive: true;
    }) => void;
    onCopy?: (sourceDirEnt: Dirent, destination: string) => void;
    overwrite?: boolean;
    readdirSync?: (path: string, options: {
        recursive: false;
        encoding: "utf8";
        withFileTypes: true;
    }) => Dirent[];
    sort?: Comparator<Dirent>;
    verbose?: boolean;
};
```

Options for [copyRecursiveSync](#api-copyrecursivesync).


#### CopyRecursiveResult

<a id="api-copyrecursiveresult"></a>

```typescript
type CopyRecursiveResult = {
    fileCount: number;
    dirCount: number;
    totalCount: number;
};
```

#### EnvOrArgOptions

<a id="api-envorargoptions"></a>

```typescript
type EnvOrArgOptions = {
    allowBlank?: boolean;
    argv?: string[];
    env?: Record<string, string | undefined>;
};
```

Options for a call to [envOrArg](#api-envorarg).


#### PackageJsonLike

<a id="api-packagejsonlike"></a>

```typescript
type PackageJsonLike = {
    dependencies?: Record<string, string>;
    devDependencies?: Record<string, string>;
    exports?: {
        [key: string]: {
            default?: string;
            import?: string;
            require?: string;
            types?: string;
        } & Record<string, string>;
    };
    files?: string[];
    git?: string | Record<string, string>;
    main?: string;
    module?: string;
    name: string;
    peerDependencies?: Record<string, string>;
    private?: boolean;
    publishConfig?: {
        access: "public";
    };
    readme?: string;
    repository?: {
        directory?: string;
        type?: string;
        url?: string;
    };
    scripts?: Record<string, string>;
    types?: string;
    typings?: string;
    version: string;
};
```

Oversimplified <kbd>package.json</kbd> type definition. Doesn't have absolutely everything, and is more of a "reasonable default" view.


#### PositionalArgsConfig

<a id="api-positionalargsconfig"></a>

```typescript
type PositionalArgsConfig = {
    ignoreDot?: boolean;
};
```

Configuration for a [positionalArgs](#api-positionalargs) call.


#### StatsForFileConfig

<a id="api-statsforfileconfig"></a>

```typescript
type StatsForFileConfig = {
    statSync?: (path: string, options: {
        throwIfNoEntry: false;
    }) => (undefined | fs.Stats);
    throwIfNotFile?: boolean;
};
```

Configuration for a [statsForFile](#api-statsforfile) call.


#### StatSyncLike

<a id="api-statsynclike"></a>

```typescript
type StatSyncLike = (path: string, options: {
    throwIfNoEntry: false;
}) => {
    isFile: () => boolean;
};
```

#### WriteJsonConfig

<a id="api-writejsonconfig"></a>

```typescript
type WriteJsonConfig = WriteTextConfig & {
    modifyJson?: UnaryOperator<string>;
    modifySorted?: <T>(value: T) => (void | undefined | T);
    indent?: string | number;
    writeText?: typeof writeText;
};
```

Configuration for a [writeJson](#api-writejson) call.


#### WriteTextConfig

<a id="api-writetextconfig"></a>

```typescript
type WriteTextConfig = {
    consoleLog?: Consumer<string>;
    finalNewline?: boolean;
    relativeTo?: string;
    silent?: boolean;
    writeFileSync?: TriConsumer<string, string, {
        encoding: "utf-8";
    }>;
};
```

Configuration for a [writeText](#api-writetext) call.


### Variables

#### ARG_FLAG

<a id="api-arg-flag"></a>

```typescript
ARG_FLAG = "<flag>"
```

Value returned from [envOrArg](#api-envorarg) when an arg is present, but it appears to be a flag instead of a param.


#### DEPENDENCIES_KEYS

<a id="api-dependencies-keys"></a>

```typescript
DEPENDENCIES_KEYS: readonly ["dependencies", "devDependencies", "peerDependencies"]
```

Property names for the parts of a <kbd>package.json</kbd> which may include dependency information.


#### EXPORTS_ORDER

<a id="api-exports-order"></a>

```typescript
EXPORTS_ORDER: readonly ["types", "import", "require", "default"]
```

Reasonable order for the keys of a <kbd>package.json</kbd>'s <kbd>exports</kbd> map.


#### isDryRun

<a id="api-isdryrun"></a>

```typescript
isDryRun: boolean
```

Flag to indicate whether <kbd>--dry-run</kbd> was provided on the command line.


