Skip to content

Commit 000d323

Browse files
committed
feat: Remove friction from first use
* Add -C option to binary to prevent copying public gateway url to clipboard * Add -P option to binary to pin only to local IPFS daemon, not remote services * Update CLI help examples * Add -h alias for --help * Refactor nested callbacks with async/await BREAKING CHANGE: * Make 'public' default deploy path for binary * Open public gateway URL on browser by default, use -O to opt-out * Don't require signing up for anything with default options * Don't upload to pinata by default * Don't update cloudflare DNS by default, use -d cloudflare * Keep uploading to infura, which doesn't require signup * Rework API options for deploy()
1 parent 5400262 commit 000d323

File tree

3 files changed

+147
-157
lines changed

3 files changed

+147
-157
lines changed

bin/ipfs-deploy.js

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,97 +8,101 @@ require('dotenv').config()
88
const argv = require('yargs')
99
.scriptName('ipfs-deploy')
1010
.usage(
11-
'$0 [options] path',
11+
'$0 [options] [path]',
1212
'Pin path locally, upload to pinning service, and update DNS',
1313
yargs => {
1414
yargs
1515
.positional('path', {
1616
type: 'string',
17-
default: './public/',
18-
describe: 'The path to deploy',
17+
describe: 'The local directory or file to be deployed',
18+
default: 'public',
19+
normalize: true,
1920
})
2021
.env('IPFS_DEPLOY')
2122
.options({
22-
D: {
23-
type: 'boolean',
24-
default: false,
25-
describe: "DON'T update Cloudflare DNS' TXT dnslink",
23+
C: {
24+
alias: 'no-clipboard',
25+
describe: "DON'T copy ipfs.io/ipfs/<hash> to clipboard",
2626
},
27-
o: {
27+
d: {
28+
alias: 'dns',
29+
choices: ['cloudflare'],
30+
describe: 'DNS provider whose dnslink TXT field will be updated',
31+
},
32+
O: {
2833
type: 'boolean',
29-
default: false,
30-
describe: 'Open URL after deploying',
34+
describe: "DON'T open URL after deploying",
3135
},
3236
p: {
3337
alias: 'pinner',
3438
choices: ['pinata', 'infura'],
35-
default: ['pinata', 'infura'],
39+
default: ['infura'],
3640
describe: `Pinning services to which ${chalk.whiteBright(
3741
'path'
3842
)} will be uploaded`,
3943
},
4044
P: {
45+
alias: 'no-remote-pin',
4146
type: 'boolean',
4247
default: false,
4348
describe:
4449
"DON'T pin remotely, only to local daemon (overrides -p)",
4550
},
4651
})
4752
.example(
48-
'$0 _site',
53+
'$0',
54+
`# Deploys relative path "${chalk.whiteBright('public')}" to
55+
${chalk.whiteBright('ipfs.infura.io/ipfs/<hash>')}; doesn't ` +
56+
'update DNS; copies and opens URL. These defaults are chosen ' +
57+
'so as not to require signing up for any service or ' +
58+
'setting up environment variables on first use.'
59+
)
60+
.example(
61+
'$0 -p pinata _site',
4962
`# Deploys path "${chalk.whiteBright(
5063
'_site'
51-
)}" to ${chalk.whiteBright('pinata')} and ${chalk.whiteBright(
52-
'infura'
53-
)}, and updates ${chalk.whiteBright('cloudflare')} DNS`
64+
)}" ONLY to ${chalk.whiteBright('pinata')} and doesn't update DNS`
5465
)
5566
.example(
56-
'$0 -p infura -p pinata',
67+
'$0 -p infura -p pinata -d cloudflare',
5768
`# Deploys path "${chalk.whiteBright(
58-
'./public/'
69+
'public'
5970
)}" to ${chalk.whiteBright('pinata')} and ${chalk.whiteBright(
6071
'infura'
61-
)}, and updates ${chalk.whiteBright('cloudflare')} DNS`
62-
)
63-
.example(
64-
'$0 -p pinata static',
65-
`# Deploys path "${chalk.whiteBright(
66-
'static'
67-
)}" ONLY to ${chalk.whiteBright(
68-
'pinata'
69-
)} and updates ${chalk.whiteBright('cloudflare')} DNS`
72+
)}, and updates cloudflare DNS`
7073
)
7174
.example(
72-
'$0 -D docs',
73-
`# Deploys path "${chalk.whiteBright(
74-
'docs'
75-
)}" to ${chalk.whiteBright('pinata')} and ${chalk.whiteBright(
76-
'infura'
77-
)}, and ${chalk.whiteBright("DON'T")} update DNS`
75+
'$0 -COP docs',
76+
`# Pins path "${chalk.whiteBright('docs')}" to local daemon ` +
77+
'only and does nothing else. Same as ' +
78+
`${chalk.whiteBright('ipfs add -r docs')}`
7879
)
7980
}
8081
)
81-
.help().argv
82+
.help()
83+
.alias('h', 'help').argv
8284

8385
function main() {
84-
deploy({
85-
updateDns: !argv.D,
86-
open: argv.o,
87-
// pinners: argv.p, TODO
88-
// pinRemotely: !argv.P, TODO
86+
const deployOptions = {
8987
publicDirPath: argv.path,
90-
remote: {
91-
siteDomain: argv.siteDomain,
88+
copyPublicGatewayUrlToClipboard: !argv.noClipboard,
89+
open: !argv.O,
90+
localPinOnly: argv.P,
91+
remotePinners: argv.p,
92+
dnsProviders: argv.d,
93+
siteDomain: argv.siteDomain,
94+
credentials: {
9295
cloudflare: {
93-
apiKey: argv.cloudflare.apiKey,
94-
apiEmail: argv.cloudflare.apiEmail,
96+
apiKey: argv.cloudflare && argv.cloudflare.apiKey,
97+
apiEmail: argv.cloudflare && argv.cloudflare.apiEmail,
9598
},
9699
pinata: {
97-
apiKey: argv.pinata.apiKey,
98-
secretApiKey: argv.pinata.secretApiKey,
100+
apiKey: argv.pinata && argv.pinata.apiKey,
101+
secretApiKey: argv.pinata && argv.pinata.secretApiKey,
99102
},
100103
},
101-
})
104+
}
105+
deploy(deployOptions)
102106
}
103107

104108
main()

index.js

Lines changed: 97 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const util = require('util')
12
const IPFSFactory = require('ipfsd-ctl')
23
const which = require('which')
34
const clipboardy = require('clipboardy')
@@ -7,13 +8,20 @@ const updateCloudflareDnslink = require('dnslink-cloudflare')
78
const ora = require('ora')
89
const chalk = require('chalk')
910
const openUrl = require('open')
11+
const _ = require('lodash')
1012

11-
async function doUpdateDns({ siteDomain, cloudflare }, hash) {
13+
// # Pure functions
14+
function publicGatewayUrl(hash) {
15+
return `https://ipfs.io/ipfs/${hash}`
16+
}
17+
18+
// Effectful functions
19+
20+
async function updateCloudflareDns(siteDomain, { apiEmail, apiKey }, hash) {
1221
const spinner = ora()
1322

14-
const { apiEmail, apiKey } = cloudflare
1523
if (!apiKey || !apiEmail || !siteDomain || !hash) {
16-
throw new Error('Missing information for doUpdateDns()')
24+
throw new Error('Missing information for updateCloudflareDns()')
1725
}
1826

1927
const api = {
@@ -28,30 +36,31 @@ async function doUpdateDns({ siteDomain, cloudflare }, hash) {
2836
}
2937

3038
try {
31-
spinner.info(
39+
spinner.start(
3240
`📡 Beaming new hash to DNS provider ${chalk.whiteBright(
3341
'Cloudflare'
3442
)}...`
3543
)
3644
const content = await updateCloudflareDnslink(api, opts)
3745
spinner.succeed('🙌 SUCCESS!')
38-
spinner.info(`Updated TXT ${chalk.whiteBright(opts.record)} to:`)
39-
spinner.info(`${chalk.whiteBright(content)}.`)
40-
spinner.succeed('🌐 Your website is deployed now.')
46+
spinner.info(`🔄 Updated DNS TXT ${chalk.whiteBright(opts.record)} to:`)
47+
spinner.info(`🔗 ${chalk.whiteBright(content)}.`)
48+
spinner.succeed('🌎 Your website is deployed now.')
4149
} catch (err) {
4250
console.error(err)
4351
process.exit(1)
4452
}
4553
}
4654

4755
async function deploy({
48-
updateDns = true,
56+
publicDirPath,
57+
copyPublicGatewayUrlToClipboard = false,
4958
open = false,
50-
// pinners = ['pinata', 'infura'], TODO
51-
// pinRemotely = true, TODO
52-
publicDirPath = 'public',
53-
remote = {
54-
siteDomain,
59+
localPinOnly = false,
60+
remotePinners = ['infura'],
61+
dnsProviders = [],
62+
siteDomain,
63+
credentials = {
5564
cloudflare: {
5665
apiEmail,
5766
apiKey,
@@ -69,106 +78,82 @@ async function deploy({
6978
const df = IPFSFactory.create({ exec: ipfsBinAbsPath })
7079

7180
const spinner = ora()
72-
spinner.start()
73-
spinner.info('☎️ Connecting to local IPFS daemon...')
74-
75-
df.spawn({ disposable: false, init: false, start: false }, (err, ipfsd) => {
76-
if (err) throw err
77-
78-
ipfsd.start([], (err2, ipfsClient) => {
79-
if (err2) throw err2
80-
// spinner.succeed('📶 Connected.')
81-
82-
spinner.info(
83-
`💾 Adding and pinning ${chalk.blue(publicDirPath)} locally...`
84-
)
85-
86-
ipfsClient.addFromFs(
87-
publicDirPath,
88-
{ recursive: true },
89-
(err3, localPinResult) => {
90-
if (err3) {
91-
spinner.fail(
92-
"☠ Couldn't connect to local ipfs daemon. Is it running?"
93-
)
94-
throw err3
95-
}
96-
97-
const { hash } = localPinResult[localPinResult.length - 1]
98-
spinner.succeed(`🔗 Added locally as ${chalk.green(hash)}.`)
99-
100-
ipfsClient.id((err4, { addresses }) => {
101-
if (err4) throw err4
102-
103-
const publicMultiaddresses = addresses.filter(
104-
multiaddress =>
105-
!multiaddress.match(/\/::1\//) &&
106-
!multiaddress.match(/127\.0\.0\.1/) &&
107-
!multiaddress.match(/192\.168/)
108-
)
109-
110-
const pinataOptions = {
111-
host_nodes: publicMultiaddresses,
112-
pinataMetadata: {
113-
name: remote.siteDomain,
114-
keyvalues: {
115-
gitCommitHash: 'TODO',
116-
},
117-
},
118-
}
119-
120-
const pinata = pinataSDK(
121-
remote.pinata.apiKey,
122-
remote.pinata.secretApiKey
123-
)
124-
125-
spinner.info(
126-
`📠 Requesting remote pin to ${chalk.whiteBright(
127-
'pinata.cloud'
128-
)}...`
129-
)
130-
pinata
131-
.pinHashToIPFS(hash, pinataOptions)
132-
.then(async _pinataPinResult => {
133-
spinner.succeed("📌 It's pinned to Pinata now.")
134-
135-
try {
136-
spinner.info(
137-
`📠 Requesting remote pin to ${chalk.whiteBright(
138-
'infura.io'
139-
)}...`
140-
)
141-
const infuraResponse = await got(
142-
`https://ipfs.infura.io:5001/api/v0/pin/add?arg=${hash}` +
143-
'&recursive=true'
144-
)
145-
146-
if (infuraResponse.statusCode === 200) {
147-
spinner.succeed("📌 It's pinned to Infura now.")
148-
} else {
149-
spinner.fail("Pinning to Infura didn't work.")
150-
}
151-
} catch (e) {
152-
console.error(e)
153-
}
154-
155-
clipboardy.writeSync(hash)
156-
spinner.succeed(
157-
`📋 Hash ${chalk.green(hash)} copied to clipboard.`
158-
)
159-
160-
if (updateDns) doUpdateDns(remote, hash)
161-
162-
if (open) openUrl(`https://${remote.siteDomain}`)
163-
})
164-
.catch(err5 => {
165-
throw err5
166-
})
167-
})
168-
}
169-
)
170-
})
81+
spinner.start('☎️ Connecting to local IPFS daemon…')
82+
83+
const spawn = util.promisify(df.spawn.bind(df))
84+
ipfsd = await spawn({ disposable: false, init: false, start: false })
85+
86+
const start = util.promisify(ipfsd.start.bind(ipfsd))
87+
const ipfsClient = await start([])
88+
spinner.succeed('☎️ Connected to local IPFS daemon.')
89+
90+
spinner.start('🔗 Pinning to local IPFS…')
91+
const localPinResult = await ipfsClient.addFromFs(publicDirPath, {
92+
recursive: true,
17193
})
94+
const { hash } = localPinResult[localPinResult.length - 1]
95+
spinner.succeed(`📌 Pinned locally as ${chalk.green(hash)}.`)
96+
97+
if (!localPinOnly && remotePinners.includes('pinata')) {
98+
spinner.start(
99+
`📠 Requesting remote pin to ${chalk.whiteBright('pinata.cloud')}…`
100+
)
101+
const { addresses } = await ipfsClient.id()
102+
103+
const publicMultiaddresses = addresses.filter(
104+
multiaddress =>
105+
!multiaddress.match(/\/::1\//) &&
106+
!multiaddress.match(/127\.0\.0\.1/) &&
107+
!multiaddress.match(/192\.168/)
108+
)
109+
110+
const pinataOptions = {
111+
host_nodes: publicMultiaddresses,
112+
pinataMetadata: {
113+
name: siteDomain,
114+
// keyvalues: {
115+
// gitCommitHash: 'TODO',
116+
// },
117+
},
118+
}
119+
120+
const pinata = pinataSDK(
121+
credentials.pinata.apiKey,
122+
credentials.pinata.secretApiKey
123+
)
124+
125+
await pinata.pinHashToIPFS(hash, pinataOptions)
126+
127+
spinner.succeed("📌 It's pinned to Pinata now.")
128+
}
129+
130+
if (!localPinOnly && remotePinners.includes('infura')) {
131+
spinner.start(
132+
`📠 Requesting remote pin to ${chalk.whiteBright('infura.io')}…`
133+
)
134+
const infuraResponse = await got(
135+
`https://ipfs.infura.io:5001/api/v0/pin/add?arg=${hash}` +
136+
'&recursive=true'
137+
)
138+
139+
if (infuraResponse.statusCode === 200) {
140+
spinner.succeed("📌 It's pinned to Infura now.")
141+
} else {
142+
spinner.fail("Pinning to Infura didn't work.")
143+
}
144+
}
145+
146+
if (copyPublicGatewayUrlToClipboard)
147+
clipboardy.writeSync(publicGatewayUrl(hash))
148+
spinner.succeed('📋 Public gateway URL copied to clipboard.')
149+
150+
if (dnsProviders.includes('cloudflare'))
151+
await updateCloudflareDns(siteDomain, credentials.cloudflare, hash)
152+
153+
if (open && !localPinOnly && !_.isEmpty(dnsProviders))
154+
openUrl(`https://${siteDomain}`)
155+
if (open && (localPinOnly || _.isEmpty(dnsProviders)))
156+
openUrl(publicGatewayUrl(hash))
172157
}
173158

174159
module.exports = deploy

0 commit comments

Comments
 (0)