Skip to content

Commit

Permalink
feat: delegate upload and store capabilities
Browse files Browse the repository at this point in the history
  • Loading branch information
Alan Shaw committed Nov 12, 2022
1 parent 90da73f commit f1e506d
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 11 deletions.
4 changes: 4 additions & 0 deletions packages/access/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"exports": {
".": "./src/index.js",
"./capabilities/*": "./src/capabilities/*.js",
"./cli/*": "./src/cli/*.js",
"./stores/*": "./src/stores/*.js",
"./connection": "./src/connection.js",
"./types": "./src/types.js",
Expand All @@ -47,6 +48,9 @@
"capabilities/*": [
"dist/src/capabilities/*"
],
"cli/*": [
"dist/src/cli/*"
],
"encoding": [
"dist/src/encoding"
]
Expand Down
4 changes: 2 additions & 2 deletions packages/access/src/cli/cmd-create-account.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ export async function cmdCreateAccount(opts) {
const { email } = await inquirer.prompt({
type: 'input',
name: 'email',
default: 'hugomrdias@gmail.com',
default: 'you@example.com',
message: 'Input your email to validate:',
})
spinner.start('Waiting for email validation...')
try {
await agent.createAccount(email)
spinner.succeed('Account has been created and register with the service.')
spinner.succeed('Account has been created and registered with the service.')
} catch (error) {
console.error(error)
// @ts-ignore
Expand Down
2 changes: 1 addition & 1 deletion packages/access/src/cli/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Verifier } from '@ucanto/principal/ed25519'

/** @type {Record<string,string>} */
const envs = {
production: 'https://access.web3.storage',
production: 'https://access-api.web3.storage',
staging: 'https://w3access-staging.protocol-labs.workers.dev',
dev: 'https://w3access-dev.protocol-labs.workers.dev',
local: 'http://127.0.0.1:8787',
Expand Down
26 changes: 26 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@web3-storage/cli",
"version": "0.0.0",
"description": "CLI for web3.storage",
"main": "index.js",
"type": "module",
"bin": {
"w3cli": "./src/index.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Alan Shaw",
"license": "Apache-2.0 OR MIT",
"dependencies": {
"@web3-storage/access": "workspace:^",
"@web3-storage/upload-client": "workspace:^",
"conf": "^10.1.2",
"files-from-path": "^0.2.6",
"ora": "^6.1.2",
"sade": "^1.7.4"
},
"devDependencies": {
"@types/node": "^18.11.7"
}
}
111 changes: 111 additions & 0 deletions packages/cli/src/cmd-upload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* eslint-disable no-console */
import fs from 'fs'
import path from 'path'
import { uploadFile, uploadDirectory, delegateCapabilities } from '@web3-storage/upload-client'
import { Agent } from '@web3-storage/access'
import { getService } from '@web3-storage/access/cli/utils'
import { StoreConf } from '@web3-storage/access/stores/store-conf'
import ora from 'ora'
import { filesFromPath } from 'files-from-path'

/**
* Add 1 or more files/directories to web3.storage
*
* @param {string} firstPath the first file path to store
* @param {object} opts
* @param {string} [opts.env]
* @param {string} [opts.profile]
* @param {string} [opts.wrap] wrap with directory
* @param {string} [opts.name] upload name
* @param {boolean} [opts.hidden] include paths that start with .
* @param {boolean|number} [opts.retry] set maxRetries for client.put
* @param {string[]} opts._ additonal paths to add
*/
export async function cmdUpload (firstPath, opts) {
const paths = checkPathsExist([firstPath, ...opts._])
// @ts-ignore
const store = new StoreConf({ profile: opts.profile })

const exists = await store.exists()
if (!exists) {
console.error('run setup command first.')
process.exit(1)
}

const { url } = await getService(opts.env ?? 'production')
const agent = await Agent.create({ store, url })
if (!agent.data.accounts.length) {
console.error('run account create command first.')
process.exit(1)
}

const delegation = await delegateCapabilities(agent.data.accounts[0], agent.issuer)
await agent.addDelegation(delegation)

const conf = {
issuer: agent.issuer,
proofs: agent.data.delegations.received
}

for (const d of agent.data.delegations.received) {
console.log(d.capabilities)
}

// pass either --no-retry or --retry <number>
const retries = Number.isInteger(Number(opts.retry))
? Number(opts.retry)
: opts.retry === false ? 0 : undefined
if (retries !== undefined) {
console.log(`⁂ maxRetries: ${retries}`)
}

const hidden = !!opts.hidden
const files = []
let totalSize = 0
let totalSent = 0
const spinner = ora('Packing files').start()
for (const p of paths) {
for await (const file of filesFromPath(p, { hidden })) {
totalSize += file.size
files.push(file)
spinner.text = `Packing ${files.length} file${files.length === 1 ? '' : 's'} (${filesize(totalSize)})`
}
}
spinner.stopAndPersist({ symbol: '#', text: `Packed ${files.length} file${files.length === 1 ? '' : 's'} (${filesize(totalSize)})` })

let rootCid
/** @type {import('@web3-storage/upload-client').StoredShardCallback} */
const onStoredShard = ({ cid, size }) => {
totalSent += size
spinner.stopAndPersist({ symbol: '#', text: `Stored shard ${cid} (${filesize(size)})` })
spinner.start('Storing')
}

spinner.start('Storing')
if (files.length > 1 || opts.wrap) {
rootCid = await uploadDirectory(conf, files, { retries, onStoredShard })
} else {
// @ts-ignore
rootCid = await uploadFile(conf, files[0], { retries, onStoredShard })
}
spinner.stopAndPersist({ symbol: '⁂', text: `Stored ${files.length} file${files.length === 1 ? '' : 's'} (${filesize(totalSent)})` })
console.log(`⁂ https://w3s.link/ipfs/${rootCid}`)
}

/** @param {number} bytes */
function filesize (bytes) {
const size = bytes / 1024 / 1024
return `${size.toFixed(1)}MB`
}

/** @param {string|string[]} paths */
function checkPathsExist (paths) {
paths = Array.isArray(paths) ? paths : [paths]
for (const p of paths) {
if (!fs.existsSync(p)) {
console.error(`The path ${path.resolve(p)} does not exist`)
process.exit(1)
}
}
return paths
}
8 changes: 8 additions & 0 deletions packages/cli/src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import fs from 'fs'

export const pkg = JSON.parse(
// eslint-disable-next-line unicorn/prefer-json-parse-buffer
fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')
)

export const NAME = pkg.name.split('/').pop()
33 changes: 33 additions & 0 deletions packages/cli/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env node
import sade from 'sade'
import { cmdSetup } from '@web3-storage/access/cli/cmd-setup'
import { cmdCreateAccount } from '@web3-storage/access/cli/cmd-create-account'
import { NAME, pkg } from './config.js'
import { cmdUpload } from './cmd-upload.js'

const prog = sade(NAME)

prog
.version(pkg.version)
.option('--env', 'Env', 'staging')

prog
.command('setup')
.option('--reset', 'Reset current store.', false)
.describe('Setup the web3.storage CLI tool.')
.action(cmdSetup)

prog
.command('account create')
.describe('Create a new account.')
.action(cmdCreateAccount)

prog
.command('upload <path>')
.describe('Upload a file or directory to web3.storage')
.option('--no-wrap', 'Dont wrap input files with a directory')
.option('-H, --hidden', 'Include paths that start with "."')
.option('--no-retry', 'Don\'t try the upload again if it fails')
.action(cmdUpload)

prog.parse(process.argv)
10 changes: 10 additions & 0 deletions packages/cli/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"lib": ["ESNext", "DOM"],
"emitDeclarationOnly": true
},
"include": ["src", "scripts", "test", "package.json"],
"exclude": ["**/node_modules/**"]
}
Loading

0 comments on commit f1e506d

Please sign in to comment.