Skip to content
Merged
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
34 changes: 28 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
name: test
on:
push:
pull_request:
branches:
- master
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4

- name: Start MinIO
run: |
docker run -d --name minio \
-p 9000:9000 \
-e MINIO_ROOT_USER=testAccessKey \
-e MINIO_ROOT_PASSWORD=testSecretKey \
minio/minio server /data
until curl -sf http://localhost:9000/minio/health/live; do sleep 1; done
docker exec minio mkdir -p /data/s3p-test

- name: Use Node.js
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: 16.x
- name: Install NPM dependencies
node-version: 20

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

- name: Run tests
run: npm test
env:
S3_ENDPOINT: http://localhost:9000
AWS_ACCESS_KEY_ID: testAccessKey
AWS_SECRET_ACCESS_KEY: testSecretKey
AWS_REGION: us-east-1
run: npm run test:ci
95 changes: 95 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# s3p — notes for Claude

Source is CaffeineScript (`.caf`). Build compiles to `build/`. Tests run from `build/` via Jest.

## Workflow
- `npm run build` — compile `source/**.caf` → `build/**.js`. Errors from CaffeineScript are clear; read the caret location.
- `npm test` — Jest against `build/`. Needs `NODE_OPTIONS=--experimental-vm-modules` (wired in) for AWS SDK v3 dynamic imports.
- `npm run test:coverage` — same + coverage report.
- MinIO: `docker compose up -d`. Integration tests require env: `S3_ENDPOINT=http://localhost:9000 AWS_ACCESS_KEY_ID=testAccessKey AWS_SECRET_ACCESS_KEY=testSecretKey AWS_REGION=us-east-1`. Without `S3_ENDPOINT`, integration tests auto-skip.
- Test scenarios share `source/test/MinioTestHelper.caf` — use `setupTestScenario` which handles bucket cleanup in `afterAll`.

## CLI → API
Every CLI command has `--api-example` that prints the equivalent `require('s3p').xxx({...})` call. Use this to understand how to drive the API from tests.

## CaffeineScript gotchas (things I've hit)

**Skim this before writing `.caf` — the parser is strict about layout.**

### Chained method calls
- Multi-line: leading dot on each continuation line.
```
foo
.replace a, b
.replace c, d
```
- One line: all but the last call need explicit parens.
```
foo.replace(a, b).replace c, d
```
- WRONG: `foo.replace a, b .replace c, d` — parser chokes mid-line.

### `typeof` in string interpolation is a parse error
```
"got: #{typeof x}" # ERROR
```
Work around by assigning first or dropping the type name from the message.

### Implicit-block args ≠ array
```
fn
itemA
itemB
itemC
```
compiles to `fn(itemA, itemB, itemC)` — three separate arguments. If the callee expects a single array, prefix with `[]`:
```
fn []
itemA
itemB
itemC
```
This bit me in `Sync.test.caf` calling `putTestObjects` (which takes one array).

### `merge x` + indented block
```
merge x
key: value # WRONG — parses as x(key: value)
```
Use either inline form `merge x, key: value`, or block form with `merge` alone on its line:
```
merge
x
key: value
```

### Object literal shorthand with trailing bare identifier
```
{} Bucket: bucket, Key, Body
```
does NOT expand to `{Bucket: bucket, Key: Key, Body: Body}`. It's parsed as `{Bucket: [bucket, Key, Body]}`. Use explicit colons or a block form:
```
{} Bucket: bucket, Key: Key, Body: Body
```
or
```
Bucket: bucket
Key: Key
Body: Body
```

### `describe.skip` is not a thing here
Gate integration tests with `if endpoint` wrapping the whole `describe` block; in the `else`, register a `test "skipped"` so Jest sees the file as a test file.

### Smart import
`import &@aws-sdk/client-s3` auto-resolves any later-referenced unassigned names (`S3Client`, `PutObjectCommand`, `ListObjectsV2Command`, etc.) to properties of that module. No need to destructure up front.

### Everything returns a value
Functions return the last expression. The last statement in a file is auto-exported. Useful, but means stray expressions at the end of a function become its return value.

### Scopes
`if` / `unless` do NOT create a new scope. Functions, classes, comprehensions, and `while`/`until` do. Variables are auto-let at the topmost scope where they're assigned.

## Known existing-code pitfalls
- `normalizeOptions` in `S3Comprehensions.caf` strips `returning:` from options without mapping it to `returnValue`. Callers must use `returnValue:` directly (see `S3P.list` fix).
- `syncObject` in `S3.caf` calls `@copyObject` which doesn't exist — probably should be `@copy`. Not exercised by tests; the higher-level `S3P.sync` flow doesn't go through `syncObject`.
16 changes: 16 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
services:
s3:
image: minio/minio
volumes:
- minio-data:/data
entrypoint: sh
command: -c 'mkdir -p /data/s3p-test && /usr/bin/minio server --console-address=0.0.0.0:9002 /data'
ports:
- '9000:9000'
- '9002:9002'
environment:
MINIO_ROOT_USER: testAccessKey
MINIO_ROOT_PASSWORD: testSecretKey

volumes:
minio-data:
Loading
Loading