Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Every command supports `-h` for full usage details.
```bash
npx foc-cli upload <path> # Upload with auto provider/dataset
npx foc-cli upload <path> --withCDN --copies 3 # CDN + 3 redundant copies
npx foc-cli multi-upload ./a.pdf,./b.pdf # Batch upload
npx foc-cli multi-upload ./a.pdf,./b.pdf # Batch upload; all paths must be readable
```

### Wallet
Expand Down
20 changes: 13 additions & 7 deletions cli/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
},
"dependencies": {
"@clack/prompts": "^1.0.0",
"@filoz/synapse-core": "^0.3.1",
"@filoz/synapse-sdk": "^0.40.0",
"@filoz/synapse-core": "^0.4.1",
"@filoz/synapse-sdk": "^0.40.4",
"@remix-run/fs": "^0.4.1",
"conf": "^15.0.2",
"incur": "^0.3.1",
Expand Down
5 changes: 1 addition & 4 deletions cli/src/commands/dataset/details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,7 @@ export const detailsCommand = {
cid,
scannerUrl: pieceScannerUrl(cid, chain),
url: piece.url,
metadata:
Object.keys(piece.metadata).length > 0
? piece.metadata
: 'No metadata',
metadata: piece.metadata,
}
})

Expand Down
25 changes: 22 additions & 3 deletions cli/src/commands/multi-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ type CopyResult = {

export const multiUploadCommand = {
description:
'Upload multiple files to Filecoin warm storage (high-level, recommended)',
'Upload multiple readable files to Filecoin warm storage (high-level, recommended)',
args: z.object({
paths: z
.preprocess(
(val) => (typeof val === 'string' ? val.split(',') : val),
z.array(z.string())
)
.describe('File paths to upload (comma-separated for CLI)'),
.describe('File paths to upload. All paths must be readable.'),
}),
options: z.object({
chain: z
Expand Down Expand Up @@ -72,7 +72,7 @@ export const multiUploadCommand = {
{
args: { paths: ['./myfile.pdf', './myfile2.pdf'] },
options: { copies: 3, withCDN: true },
description: 'Upload with auto provider/dataset selection',
description: 'Upload readable files with auto provider/dataset selection',
},
{
args: { paths: ['./data.bin', './data2.bin'] },
Expand All @@ -99,6 +99,25 @@ export const multiUploadCommand = {
const fileResultsSettled = await Promise.allSettled(
absolutePaths.map((filePath: string) => readFile(filePath))
)
const fileReadRejected = fileResultsSettled
.map((result, index) => ({ result, path: absolutePaths[index] }))
.filter(({ result }) => result.status === 'rejected')

if (fileReadRejected.length > 0) {
return out.fail(
'FILE_READ_FAILED',
fileReadRejected
.map(({ result, path }) => {
const reason =
result.status === 'rejected' ? result.reason : undefined
return `${path}: ${
reason instanceof Error ? reason.message : String(reason)
}`
})
.join(', ')
)
}

const fileResults = fileResultsSettled
.filter((result) => result.status === 'fulfilled')
.map((result) => result.value)
Expand Down
2 changes: 1 addition & 1 deletion cli/src/commands/piece/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const listCommand = {

return out.done(
{
dataSetId: c.args.dataSetId,
dataSetId: c.args.dataSetId.toString(),
datasetScannerUrl: datasetScannerUrl(c.args.dataSetId, chain),
pieces: piecesList,
},
Expand Down
59 changes: 49 additions & 10 deletions cli/tests/synapse-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,21 @@ describe('top-level upload commands', () => {
])
})

test.todo(
'multi-upload should fail when any requested file cannot be read instead of silently uploading the readable subset'
)
test('multi-upload fails when any requested file cannot be read instead of silently uploading the readable subset', async () => {
const readable = await tempFile('readable.txt', 'ok')
const missing = path.join(path.dirname(readable), 'missing.txt')

const result = await multiUploadCommand.run(
commandContext({
args: { paths: [readable, missing] },
})
)

expect(result.error.code).toBe('FILE_READ_FAILED')
expect(result.error.message).toContain(missing)
expect(synapseStorage.createContexts).not.toHaveBeenCalled()
expect(synapseStorage.upload).not.toHaveBeenCalled()
})
})

describe('wallet commands', () => {
Expand Down Expand Up @@ -609,9 +621,32 @@ describe('dataset commands', () => {
})
})

test.todo(
'dataset details should return an object for empty piece metadata instead of the string "No metadata"'
)
test('dataset details returns an object for empty piece metadata', async () => {
getPiecesWithMetadata.mockImplementationOnce(async () => ({
pieces: [
{
id: 8n,
cid: cid('baga-empty-metadata'),
url: 'https://provider.example/piece/baga-empty-metadata',
metadata: {},
},
],
}))

const result = await datasetDetailsCommand.run(
commandContext({ options: { dataSetId: 42 } })
)

expect(result.pieces).toEqual([
{
id: '8',
cid: 'baga-empty-metadata',
scannerUrl: 'https://pdp.vxb.ai/calibration/piece/baga-empty-metadata',
url: 'https://provider.example/piece/baga-empty-metadata',
metadata: {},
},
])
})
})

describe('piece commands', () => {
Expand All @@ -628,7 +663,7 @@ describe('piece commands', () => {
address: fakeWalletClient.account.address,
})
expect(result).toMatchObject({
dataSetId: 42,
dataSetId: '42',
datasetScannerUrl: 'https://pdp.vxb.ai/calibration/dataset/42',
pieces: [
{
Expand Down Expand Up @@ -663,7 +698,11 @@ describe('piece commands', () => {
})
})

test.todo(
'piece list should return dataSetId as a string to match its schema'
)
test('piece list returns dataSetId as a string to match its schema', async () => {
const result = await pieceListCommand.run(
commandContext({ args: { dataSetId: 42 } })
)

expect(result.dataSetId).toBe('42')
})
})
6 changes: 3 additions & 3 deletions skills/foc-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ All commands accept these — not repeated per-command below:
| Command | Description |
|---------|-------------|
| `upload <path> [--copies N] [--withCDN]` | Upload file. Auto-selects provider, creates dataset. Default 2 copies. |
| `multi-upload <paths> [--copies N] [--withCDN]` | Batch upload. Comma-separated paths. |
| `multi-upload <paths> [--copies N] [--withCDN]` | Batch upload. Comma-separated paths; all paths must be readable. |

```bash
npx foc-cli upload ./file.pdf # simplest
npx foc-cli upload ./file.pdf --withCDN --copies 3
npx foc-cli multi-upload ./a.pdf,./b.pdf
npx foc-cli multi-upload ./a.pdf,./b.pdf # all paths must be readable
```

### Wallet & Payments
Expand Down Expand Up @@ -122,7 +122,7 @@ npx foc-cli wallet balance
```bash
npx foc-cli upload ./myfile.pdf # auto everything
npx foc-cli upload ./myfile.pdf --withCDN # with CDN
npx foc-cli multi-upload ./a.pdf,./b.pdf --copies 3 # batch, 3 copies
npx foc-cli multi-upload ./a.pdf,./b.pdf --copies 3 # batch, 3 copies; all paths must be readable
npx foc-cli wallet costs --extraBytes 1000000 --extraRunway 1 # check costs first
```

Expand Down
Loading