Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
fail-fast: false
matrix:
package:
- appstash
- casing
- fetch-api-client
- find-pkg
Expand Down
253 changes: 253 additions & 0 deletions packages/appstash/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
# appstash

Simple, clean application directory resolution for Node.js applications.

## Installation

```bash
npm install appstash
# or
pnpm add appstash
```

## Features

- **Simple API**: Just one function to get all your app directories
- **Clean structure**: `~/.<tool>/{config,cache,data,logs}` + `/tmp/<tool>`
- **Graceful fallback**: XDG directories → tmp if home fails
- **No throws**: Always returns valid paths, never throws errors
- **TypeScript**: Full type definitions included
- **Zero dependencies**: Pure Node.js implementation

## Usage

### Basic Usage

```typescript
import { appstash } from 'appstash';

// Get directories for your tool
const dirs = appstash('pgpm');

console.log(dirs.config); // ~/.pgpm/config
console.log(dirs.cache); // ~/.pgpm/cache
console.log(dirs.data); // ~/.pgpm/data
console.log(dirs.logs); // ~/.pgpm/logs
console.log(dirs.tmp); // /tmp/pgpm
```

### Create Directories

```typescript
import { appstash } from 'appstash';

// Get directories and create them
const dirs = appstash('pgpm', { ensure: true });

// All directories now exist
// dirs.usedFallback will be true if XDG or tmp fallback was used
```

### Resolve Paths

```typescript
import { appstash, resolve } from 'appstash';

const dirs = appstash('pgpm');

// Resolve paths within directories
const configFile = resolve(dirs, 'config', 'settings.json');
// Returns: ~/.pgpm/config/settings.json

const cacheDir = resolve(dirs, 'cache', 'repos', 'my-repo');
// Returns: ~/.pgpm/cache/repos/my-repo
```

### Manual Ensure

```typescript
import { appstash, ensure } from 'appstash';

const dirs = appstash('pgpm');

// Create directories later
const result = ensure(dirs);

console.log(result.created); // ['~/.pgpm', '~/.pgpm/config', ...]
console.log(result.usedFallback); // false (or true if fallback was used)
```

## API

### `appstash(tool, options?)`

Get application directories for a tool.

**Parameters:**
- `tool` (string): Tool name (e.g., 'pgpm', 'lql')
- `options` (object, optional):
- `baseDir` (string): Base directory (defaults to `os.homedir()`)
- `useXdgFallback` (boolean): Use XDG fallback if home fails (default: `true`)
- `ensure` (boolean): Automatically create directories (default: `false`)
- `tmpRoot` (string): Root for temp directory (defaults to `os.tmpdir()`)

**Returns:** `AppStashResult`
```typescript
{
root: string; // ~/.<tool>
config: string; // ~/.<tool>/config
cache: string; // ~/.<tool>/cache
data: string; // ~/.<tool>/data
logs: string; // ~/.<tool>/logs
tmp: string; // /tmp/<tool>
usedFallback?: boolean; // true if XDG or tmp fallback was used
}
```

### `ensure(dirs)`

Create directories if they don't exist. Never throws.

**Parameters:**
- `dirs` (AppStashResult): Directory paths from `appstash()`

**Returns:** `EnsureResult`
```typescript
{
created: string[]; // Directories that were created
usedFallback: boolean; // true if XDG or tmp fallback was used
}
```

### `resolve(dirs, kind, ...parts)`

Resolve a path within a specific directory.

**Parameters:**
- `dirs` (AppStashResult): Directory paths from `appstash()`
- `kind` ('config' | 'cache' | 'data' | 'logs' | 'tmp'): Directory kind
- `parts` (string[]): Path parts to join

**Returns:** `string` - Resolved path

## Directory Structure

### Primary (POSIX-style)

```
~/.<tool>/
├── config/ # Configuration files
├── cache/ # Cached data
├── data/ # Application data
└── logs/ # Log files

/tmp/<tool>/ # Temporary files
```

### Fallback (XDG)

If home directory is unavailable or creation fails, falls back to XDG:

```
~/.config/<tool>/ # Config
~/.cache/<tool>/ # Cache
~/.local/share/<tool>/ # Data
~/.local/state/<tool>/logs/ # Logs
```

### Final Fallback (tmp)

If XDG also fails, falls back to system temp:

```
/tmp/<tool>/
├── config/
├── cache/
├── data/
└── logs/
```

## Examples

### Configuration File

```typescript
import { appstash, resolve } from 'appstash';
import fs from 'fs';

const dirs = appstash('myapp', { ensure: true });
const configPath = resolve(dirs, 'config', 'settings.json');

// Write config
fs.writeFileSync(configPath, JSON.stringify({ theme: 'dark' }));

// Read config
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
```

### Cache Management

```typescript
import { appstash, resolve } from 'appstash';
import fs from 'fs';

const dirs = appstash('myapp', { ensure: true });
const cacheFile = resolve(dirs, 'cache', 'data.json');

// Check if cached
if (fs.existsSync(cacheFile)) {
const cached = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
console.log('Using cached data:', cached);
} else {
// Fetch and cache
const data = await fetchData();
fs.writeFileSync(cacheFile, JSON.stringify(data));
}
```

### Logging

```typescript
import { appstash, resolve } from 'appstash';
import fs from 'fs';

const dirs = appstash('myapp', { ensure: true });
const logFile = resolve(dirs, 'logs', 'app.log');

function log(message: string) {
const timestamp = new Date().toISOString();
fs.appendFileSync(logFile, `[${timestamp}] ${message}\n`);
}

log('Application started');
```

### Custom Base Directory

```typescript
import { appstash } from 'appstash';

// Use a custom base directory
const dirs = appstash('myapp', {
baseDir: '/opt/myapp',
ensure: true
});

console.log(dirs.config); // /opt/myapp/.myapp/config
```

## Design Philosophy

- **Simple**: One function, clear structure
- **Clean**: No pollution of exports, minimal API surface
- **Graceful**: Never throws, always returns valid paths
- **Fallback**: XDG only as absolute fallback, not primary
- **Focused**: Just directory resolution, no state management

## License

MIT

## Contributing

See the main [hyperweb-io/dev-utils repository](https://github.com/hyperweb-io/dev-utils) for contribution guidelines.
Loading