fix(deps): upgrade core tooling dependencies and adapt Docker client internals for compatibility

This commit is contained in:
2026-03-28 05:39:48 +00:00
parent 1923837225
commit 645e1fd4a9
19 changed files with 5861 additions and 7164 deletions

View File

@@ -1,390 +1,74 @@
# Docker Module - Development Hints
## New Features (2025-11-25 - v5.1.0)
## Dependency Upgrade Notes (2026-03-28 - v5.2.0)
### 1. Enhanced Network Creation with Full Configuration Support
### Major Upgrades Completed
**Problem:** Users were unable to create non-overlay networks or customize network configuration. The `INetworkCreationDescriptor` interface only had a `Name` property, and `DockerNetwork._create()` hardcoded `Driver: 'overlay'`.
| Package | From | To | Notes |
|---------|------|-----|-------|
| @push.rocks/smartfile | ^11.2.7 | ^13.1.2 | `fs.*`, `fsStream.*` removed; use `node:fs` directly or `SmartFileFactory.nodeFs()` |
| @push.rocks/smartarchive | ^4.2.2 | ^5.2.1 | `SmartArchive.fromArchiveFile()` removed; use `SmartArchive.create().file(path).extract(dir)` |
| @push.rocks/smartbucket | ^3.3.10 | ^4.5.1 | Strict-by-default: `fastPutStream` throws on existing objects instead of overwriting |
| @push.rocks/smartjson | ^5.2.0 | ^6.0.0 | No code changes needed |
| @push.rocks/smartnetwork | ^4.4.0 | ^4.5.2 | v4.5.2 uses Rust bridge for getDefaultGateway; breaks in Deno without --allow-run |
| @tsclass/tsclass | ^9.3.0 | ^9.5.0 | No code changes needed |
| @git.zone/tsbuild | ^3.1.0 | ^4.4.0 | v4.4.0 enforces strict TS checks (strictPropertyInitialization) |
| @git.zone/tstest | ^2.8.2 | ^3.6.3 | No code changes needed |
| @types/node | ^22.15.0 | ^25.5.0 | Major version bump |
**Solution:** Expanded the interface and implementation to support all Docker network configuration options:
### Migration Details
**smartfile v13**: All `smartfile.fs.*` and `smartfile.fsStream.*` APIs were removed. Replaced with:
- `plugins.fs.createReadStream()` / `plugins.fs.createWriteStream()` (from `node:fs`)
- `plugins.fs.promises.rm()` (for file/dir removal)
- `plugins.fs.existsSync()` (for file existence checks)
- `plugins.smartfile.SmartFileFactory.nodeFs().fromFilePath()` (for reading files into SmartFile objects)
**smartarchive v5**: Uses fluent API now:
```typescript
// New interface properties:
export interface INetworkCreationDescriptor {
Name: string;
Driver?: 'bridge' | 'overlay' | 'host' | 'none' | 'macvlan'; // NEW
Attachable?: boolean; // NEW
Labels?: Record<string, string>; // NEW
IPAM?: { // NEW - IP Address Management
Driver?: string;
Config?: Array<{
Subnet?: string;
Gateway?: string;
IPRange?: string;
AuxiliaryAddresses?: Record<string, string>;
}>;
};
Internal?: boolean; // NEW
EnableIPv6?: boolean; // NEW
}
// Old: SmartArchive.fromArchiveFile(path) -> archive.exportToFs(dir)
// New: SmartArchive.create().file(path).extract(dir)
// TarTools: packDirectory() now returns Uint8Array, use getDirectoryPackStream() for streams
```
**Usage Example:**
**smartbucket v4**: `fastPutStream` now throws if object already exists. Must delete first:
```typescript
// Create bridge network with custom IPAM
const network = await docker.createNetwork({
Name: 'custom-bridge',
Driver: 'bridge',
IPAM: {
Config: [{
Subnet: '172.20.0.0/16',
Gateway: '172.20.0.1',
}]
},
Labels: { environment: 'production' },
});
try { await dir.fastRemove({ path }); } catch (e) { /* may not exist */ }
await dir.fastPutStream({ stream, path });
```
**Files Modified:**
- `ts/interfaces/network.ts` - Added all missing properties to interface
- `ts/classes.network.ts` - Updated `_create()` to pass through descriptor properties instead of hardcoding
**tsbuild v4.4.0**: Enforces `strictPropertyInitialization`. All class properties populated via `Object.assign()` from Docker API responses need `!` definite assignment assertions.
### 2. Docker Daemon Version Information
**smartnetwork v4.5.2**: `getDefaultGateway()` now uses a Rust binary bridge. Fails in Deno without `--allow-run` permission. Code wraps the call in try/catch with fallback to empty string (Docker auto-detects advertise address).
**Added:** `dockerHost.getVersion()` method to retrieve Docker daemon version information.
### Config Migration
**Purpose:** Essential for API compatibility checking, debugging, and ensuring minimum Docker version requirements.
- `npmextra.json` renamed to `.smartconfig.json`
- Removed stale `npmdocker` and duplicate `gitzone` sections
- `@push.rocks/smartfs` removed (was imported but never used)
**Returns:**
```typescript
{
Version: string; // e.g., "20.10.21"
ApiVersion: string; // e.g., "1.41"
MinAPIVersion?: string; // Minimum supported API version
GitCommit: string;
GoVersion: string;
Os: string; // e.g., "linux"
Arch: string; // e.g., "amd64"
KernelVersion: string;
BuildTime?: string;
}
```
## OCI Image Format Handling
**Usage Example:**
```typescript
const version = await docker.getVersion();
console.log(`Docker ${version.Version} (API ${version.ApiVersion})`);
console.log(`Platform: ${version.Os}/${version.Arch}`);
```
The `DockerImageStore.storeImage()` method handles optional `repositories` file gracefully. OCI-format image tars may not include this file, so it's checked with `fs.existsSync()` before attempting to read.
**Files Modified:**
- `ts/classes.host.ts` - Added `getVersion()` method after `ping()`
## Architecture
### 3. Image Pruning for Disk Space Management
**Added:** `dockerHost.pruneImages(options?)` method to clean up unused images.
**Purpose:** Automated disk space management, CI/CD cleanup, scheduled maintenance tasks.
**Options:**
```typescript
{
dangling?: boolean; // Remove untagged images
filters?: Record<string, string[]>; // Custom filters (until, label, etc.)
}
```
**Returns:**
```typescript
{
ImagesDeleted: Array<{ Untagged?: string; Deleted?: string }>;
SpaceReclaimed: number; // Bytes freed
}
```
**Usage Example:**
```typescript
// Remove dangling images
const result = await docker.pruneImages({ dangling: true });
console.log(`Reclaimed: ${(result.SpaceReclaimed / 1024 / 1024).toFixed(2)} MB`);
// Remove old images (older than 7 days)
await docker.pruneImages({
filters: {
until: ['168h']
}
});
```
**Files Modified:**
- `ts/classes.host.ts` - Added `pruneImages()` method with filter support
### 4. Exec Command Exit Codes and Inspection
**Problem:** Users could not determine if exec commands succeeded or failed. The `container.exec()` method returned a stream but provided no way to access exit codes, which are essential for:
- Health checks (e.g., `pg_isready` exit code)
- Test automation (npm test success/failure)
- Deployment validation (migration checks)
- Container readiness probes
**Solution:** Added `inspect()` method to `exec()` return value that provides comprehensive execution information.
**New Return Type:**
```typescript
{
stream: Duplex;
close: () => Promise<void>;
inspect: () => Promise<IExecInspectInfo>; // NEW
}
```
**IExecInspectInfo Interface:**
```typescript
export interface IExecInspectInfo {
ExitCode: number; // 0 = success, non-zero = failure
Running: boolean; // Whether exec is still running
Pid: number; // Process ID
ContainerID: string; // Container where exec ran
ID: string; // Exec instance ID
OpenStderr: boolean;
OpenStdin: boolean;
OpenStdout: boolean;
CanRemove: boolean;
DetachKeys: string;
ProcessConfig: {
tty: boolean;
entrypoint: string;
arguments: string[];
privileged: boolean;
};
}
```
**Usage Example:**
```typescript
// Health check with exit code
const { stream, close, inspect } = await container.exec('pg_isready -U postgres');
stream.on('end', async () => {
const info = await inspect();
if (info.ExitCode === 0) {
console.log('✅ Database is ready');
} else {
console.log(`❌ Database check failed (exit code ${info.ExitCode})`);
}
await close();
});
```
**Real-World Use Cases Enabled:**
- Health checks: Verify service readiness with proper exit code handling
- Test automation: Run tests in container and determine pass/fail
- Deployment validation: Execute migration checks and verify success
- CI/CD pipelines: Run build/test commands and get accurate results
**Files Modified:**
- `ts/interfaces/container.ts` - Added `IExecInspectInfo` interface
- `ts/classes.container.ts` - Updated `exec()` return type and added `inspect()` implementation
### Implementation Notes
All changes are non-breaking additions that enhance existing functionality:
- Network creation: New optional properties with sensible defaults
- getVersion(): New method, no changes to existing APIs
- pruneImages(): New method, no changes to existing APIs
- exec() inspect(): Added to return value, existing stream/close properties unchanged
## getContainerById() Bug Fix (2025-11-24 - v5.0.1)
### Problem
The `getContainerById()` method had a critical bug where it would create a DockerContainer object from Docker API error responses when a container didn't exist.
**Symptoms:**
- Calling `docker.getContainerById('invalid-id')` returned a DockerContainer object with `{ message: "No such container: invalid-id" }`
- Calling `.logs()` on this invalid container returned "[object Object]" instead of logs or throwing an error
- No way to detect the error state without checking for a `.message` property
**Root Cause:**
The `DockerContainer._fromId()` method made a direct API call to `/containers/{id}/json` and blindly passed `response.body` to the constructor, even when the API returned a 404 error response.
### Solution
Changed `DockerContainer._fromId()` to use the **list+filter pattern**, matching the behavior of all other resource getter methods (DockerImage, DockerNetwork, DockerService, DockerSecret):
```typescript
// Before (buggy):
public static async _fromId(dockerHostArg: DockerHost, containerId: string): Promise<DockerContainer> {
const response = await dockerHostArg.request('GET', `/containers/${containerId}/json`);
return new DockerContainer(dockerHostArg, response.body); // Creates invalid object from error!
}
// After (fixed):
public static async _fromId(dockerHostArg: DockerHost, containerId: string): Promise<DockerContainer | undefined> {
const containers = await this._list(dockerHostArg);
return containers.find((container) => container.Id === containerId); // Returns undefined if not found
}
```
**Benefits:**
- 100% consistent with all other resource classes
- Type-safe return signature: `Promise<DockerContainer | undefined>`
- Cannot create invalid objects - `.find()` naturally returns undefined
- Users can now properly check for non-existent containers
**Usage:**
```typescript
const container = await docker.getContainerById('abc123');
if (container) {
const logs = await container.logs();
console.log(logs);
} else {
console.log('Container not found');
}
```
## OOP Refactoring - Clean Architecture (2025-11-24)
### Architecture Changes
The module has been restructured to follow a clean OOP Facade pattern:
- **DockerHost** is now the single entry point for all Docker operations
- **DockerHost** is the single entry point (Facade pattern)
- All resource classes extend abstract `DockerResource` base class
- Static methods are prefixed with `_` to indicate internal use
- Public API is exclusively through DockerHost methods
- Static methods prefixed with `_` indicate internal use
- Public API exclusively through DockerHost methods
### Key Changes
### Key Patterns
**1. Factory Pattern**
- All resource creation/retrieval goes through DockerHost:
```typescript
// Old (deprecated):
const container = await DockerContainer.getContainers(dockerHost);
const network = await DockerNetwork.createNetwork(dockerHost, descriptor);
- Factory pattern: All resource creation/retrieval goes through DockerHost
- Stream handling: Web ReadableStreams from smartrequest are converted to Node.js streams via `smartstream.nodewebhelpers`
- Container getter: Uses list+filter pattern (not direct API call) to avoid creating invalid objects from error responses
// New (clean API):
const containers = await dockerHost.listContainers();
const network = await dockerHost.createNetwork(descriptor);
```
## Test Notes
**2. Container Management Methods Added**
The DockerContainer class now has full CRUD and streaming operations:
**Lifecycle:**
- `container.start()` - Start container
- `container.stop(options?)` - Stop container
- `container.remove(options?)` - Remove container
- `container.refresh()` - Reload state
**Information:**
- `container.inspect()` - Get detailed info
- `container.logs(options)` - Get logs as string (one-shot)
- `container.stats(options)` - Get stats
**Streaming & Interactive:**
- `container.streamLogs(options)` - Stream logs continuously (follow mode)
- `container.attach(options)` - Attach to main process (PID 1) with bidirectional stream
- `container.exec(command, options)` - Execute commands in container interactively
**Example - Stream Logs:**
```typescript
const container = await dockerHost.getContainerById('abc123');
const logStream = await container.streamLogs({ timestamps: true });
logStream.on('data', (chunk) => {
console.log(chunk.toString());
});
```
**Example - Attach to Container:**
```typescript
const { stream, close } = await container.attach({
stdin: true,
stdout: true,
stderr: true
});
// Pipe to/from process
process.stdin.pipe(stream);
stream.pipe(process.stdout);
// Later: detach
await close();
```
**Example - Execute Command:**
```typescript
const { stream, close } = await container.exec('ls -la /app', {
tty: true
});
stream.on('data', (chunk) => {
console.log(chunk.toString());
});
stream.on('end', async () => {
await close();
});
```
**3. DockerResource Base Class**
All resource classes now extend `DockerResource`:
- Consistent `dockerHost` property (not `dockerHostRef`)
- Required `refresh()` method
- Standardized constructor pattern
**4. ImageStore Encapsulation**
- `dockerHost.imageStore` is now private
- Use `dockerHost.storeImage(name, stream)` instead
- Use `dockerHost.retrieveImage(name)` instead
**5. Creation Descriptors Support Both Primitives and Instances**
Interfaces now accept both strings and class instances:
```typescript
// Both work:
await dockerHost.createService({
image: 'nginx:latest', // String
networks: ['my-network'], // String array
secrets: ['my-secret'] // String array
});
await dockerHost.createService({
image: imageInstance, // DockerImage instance
networks: [networkInstance], // DockerNetwork array
secrets: [secretInstance] // DockerSecret array
});
```
### Migration Guide
Replace all static method calls with dockerHost methods:
- `DockerContainer.getContainers(host)` → `dockerHost.listContainers()`
- `DockerImage.createFromRegistry(host, opts)` → `dockerHost.createImageFromRegistry(opts)`
- `DockerService.createService(host, desc)` → `dockerHost.createService(desc)`
- `dockerHost.imageStore.storeImage(...)` → `dockerHost.storeImage(...)`
## smartrequest v5+ Migration (2025-11-17)
### Breaking Change
smartrequest v5.0.0+ returns web `ReadableStream` objects (Web Streams API) instead of Node.js streams.
### Solution Implemented
All streaming methods now convert web ReadableStreams to Node.js streams using:
```typescript
plugins.smartstream.nodewebhelpers.convertWebReadableToNodeReadable(webStream)
```
### Files Modified
- `ts/classes.host.ts`:
- `requestStreaming()` - Converts web stream to Node.js stream before returning
- `getEventObservable()` - Works with converted Node.js stream
- `ts/classes.image.ts`:
- `createFromTarStream()` - Uses converted Node.js stream for event handling
- `exportToTarStream()` - Uses converted Node.js stream for backpressure management
### Testing
- Build:  All 11 type errors resolved
- Tests:  Node.js tests pass (DockerHost, DockerContainer, DockerImage, DockerImageStore)
### Notes
- The conversion maintains backward compatibility with existing code expecting Node.js stream methods (`.on()`, `.emit()`, `.pause()`, `.resume()`)
- smartstream's `nodewebhelpers` module provides bidirectional conversion utilities between web and Node.js streams
- Tests are `nonci` (require Docker daemon)
- S3 imagestore test can take 2-3 minutes depending on network
- Exec tests use 5s safety timeout due to buildkit container not always emitting stream 'end' events
- Test timeout is 600s to accommodate slow S3 uploads
- Deno tests crash with smartnetwork v4.5.2 due to Rust binary spawn permissions (not a code bug)