1+ const util = require ( 'util' )
12const IPFSFactory = require ( 'ipfsd-ctl' )
23const which = require ( 'which' )
34const clipboardy = require ( 'clipboardy' )
@@ -7,13 +8,20 @@ const updateCloudflareDnslink = require('dnslink-cloudflare')
78const ora = require ( 'ora' )
89const chalk = require ( 'chalk' )
910const 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
4755async 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 ( / 1 2 7 \. 0 \. 0 \. 1 / ) &&
107- ! multiaddress . match ( / 1 9 2 \. 1 6 8 / )
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 ( / 1 2 7 \. 0 \. 0 \. 1 / ) &&
107+ ! multiaddress . match ( / 1 9 2 \. 1 6 8 / )
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
174159module . exports = deploy
0 commit comments