Skip to content

Commit 8adfa6f

Browse files
committed
add addon:config & update addon:create command
1 parent aa7c6ac commit 8adfa6f

File tree

12 files changed

+492
-39
lines changed

12 files changed

+492
-39
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"clean-deep": "^3.0.2",
6161
"cli-spinners": "^1.3.1",
6262
"cli-ux": "^5.1.0",
63+
"concordance": "^4.0.0",
6364
"configstore": "^4.0.0",
6465
"dot-prop": "^4.2.0",
6566
"find-up": "^3.0.0",
@@ -69,6 +70,7 @@
6970
"is-docker": "^1.1.0",
7071
"lodash.get": "^4.4.2",
7172
"lodash.isempty": "^4.4.0",
73+
"lodash.isequal": "^4.5.0",
7274
"lodash.merge": "^4.6.1",
7375
"lodash.mergewith": "^4.6.1",
7476
"lodash.snakecase": "^4.1.1",

src/commands/addons/config.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,16 @@ class AddonsConfigCommand extends Command {
4949
// Get Existing Config
5050
const currentConfig = currentAddon.config || {}
5151

52-
console.log(`Current "${addonName} add-on" config values:\n`)
53-
render.configValues(currentConfig)
54-
console.log()
52+
const words = `Current "${addonName} add-on" Settings:`
53+
console.log(` ${chalk.yellowBright.bold(words)}`)
54+
if (manifest.config) {
55+
render.configValues(addonName, manifest.config, currentConfig)
56+
} else {
57+
// For addons without manifest. TODO remove once we enfore manifests
58+
Object.keys(currentConfig).forEach((key) => {
59+
console.log(`${key} - ${currentConfig[key]}`)
60+
})
61+
}
5562

5663
if (manifest.config) {
5764
const required = requiredConfigValues(manifest.config)

src/commands/addons/create.js

Lines changed: 129 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,36 @@
11
const Command = require('../../base')
2-
const { getAddons, createAddon } = require('netlify/src/addons')
2+
const {
3+
getAddons,
4+
createAddon
5+
} = require('netlify/src/addons')
36
const parseRawFlags = require('../../utils/parseRawFlags')
7+
const getAddonManifest = require('../../utils/addons/api')
8+
const {
9+
requiredConfigValues,
10+
missingConfigValues,
11+
updateConfigValues
12+
} = require('../../utils/addons/validation')
13+
const generatePrompts = require('../../utils/addons/prompts')
14+
const render = require('../../utils/addons/render')
15+
const chalk = require('chalk')
16+
const inquirer = require('inquirer')
417

518
class addonsCreateCommand extends Command {
619
async run() {
720
const accessToken = await this.authenticate()
8-
const { args, raw } = this.parse(addonsCreateCommand)
9-
const { api, site } = this.netlify
21+
const {
22+
args,
23+
raw
24+
} = this.parse(addonsCreateCommand)
25+
const {
26+
api,
27+
site
28+
} = this.netlify
1029

1130
const addonName = args.name
1231

1332
if (!addonName) {
14-
this.log('Please provide an addon name to provision')
33+
this.log('Please provide an add-on name to provision')
1534
// this.log(util.inspect(myObject, false, null, true /* enable colors */))
1635
this.exit()
1736
}
@@ -23,56 +42,135 @@ class addonsCreateCommand extends Command {
2342
return false
2443
}
2544

45+
const siteData = await api.getSite({
46+
siteId
47+
})
48+
// this.log(site)
2649
const addons = await getAddons(siteId, accessToken)
2750

2851
if (typeof addons === 'object' && addons.error) {
2952
this.log('API Error', addons)
3053
return false
3154
}
3255

33-
const siteData = await api.getSite({ siteId })
34-
3556
// Filter down addons to current args.name
3657
const currentAddon = addons.find((addon) => addon.service_path === `/.netlify/${addonName}`)
3758

38-
if (currentAddon.id) {
39-
this.log(`Addon ${addonName} already exists for ${siteData.name}`)
40-
this.log(`> Run \`netlify addons:update ${addonName}\` to update settings`)
41-
this.log(`> Run \`netlify addons:delete ${addonName}\` to delete this addon`)
59+
// GET flags from `raw` data
60+
const rawFlags = parseRawFlags(raw)
61+
62+
if (currentAddon && currentAddon.id) {
63+
this.log(`The "${addonName} add-on" already exists for ${siteData.name}`)
64+
this.log()
65+
const cmd = chalk.cyan(`\`netlify addons:config ${addonName}\``)
66+
this.log(`- To update this add-on run: ${cmd}`)
67+
const deleteCmd = chalk.cyan(`\`netlify addons:delete ${addonName}\``)
68+
this.log(`- To remove this add-on run: ${deleteCmd}`)
69+
this.log()
4270
return false
4371
}
4472

45-
const settings = {
46-
siteId: siteId,
47-
addon: addonName,
48-
config: parseRawFlags(raw)
73+
const manifest = await getAddonManifest(addonName, accessToken)
74+
75+
let configValues = rawFlags
76+
if (manifest.config) {
77+
const required = requiredConfigValues(manifest.config)
78+
const missingValues = missingConfigValues(required, rawFlags)
79+
console.log(`Starting the setup for "${addonName} add-on"`)
80+
console.log()
81+
82+
if (Object.keys(rawFlags).length) {
83+
const newConfig = updateConfigValues(manifest.config, {}, rawFlags)
84+
85+
if (missingValues.length) {
86+
/* Warn user of missing required values */
87+
console.log(`${chalk.redBright.underline.bold(`Error: Missing required configuration for "${addonName} add-on"`)}`)
88+
console.log(`Please supply the configuration values as CLI flags`)
89+
console.log()
90+
render.missingValues(missingValues, manifest)
91+
console.log()
92+
}
93+
94+
95+
await createSiteAddon({
96+
addonName,
97+
settings: {
98+
siteId: siteId,
99+
addon: addonName,
100+
config: newConfig
101+
},
102+
accessToken,
103+
siteData
104+
})
105+
return false
106+
}
107+
108+
const words = `The ${addonName} add-on has the following configurable options:`
109+
console.log(` ${chalk.yellowBright.bold(words)}`)
110+
render.configValues(addonName, manifest.config)
111+
console.log()
112+
console.log(` ${chalk.greenBright.bold('Lets configure those!')}`)
113+
114+
console.log()
115+
console.log(` - Hit ${chalk.white.bold('enter')} to confirm value or set empty value`)
116+
console.log(` - Hit ${chalk.white.bold('ctrl + C')} to cancel & exit configuration`)
117+
console.log()
118+
119+
const prompts = generatePrompts({
120+
config: manifest.config,
121+
configValues: rawFlags,
122+
})
123+
124+
const userInput = await inquirer.prompt(prompts)
125+
// Merge user input with the flags specified
126+
configValues = updateConfigValues(manifest.config, rawFlags, userInput)
49127
}
50-
const addonResponse = await createAddon(settings, accessToken)
51128

52-
if (addonResponse.code === 404) {
53-
this.log(`No addon "${addonName}" found. Please double check your addon name and try again`)
54-
return false
55-
}
56-
this.log(`Addon "${addonName}" created for ${siteData.name}`)
129+
await createSiteAddon({
130+
addonName,
131+
settings: {
132+
siteId: siteId,
133+
addon: addonName,
134+
config: configValues
135+
},
136+
accessToken,
137+
siteData
138+
})
57139
}
58140
}
59141

60-
addonsCreateCommand.description = `Add an addon extension to your site
142+
async function createSiteAddon({
143+
addonName,
144+
settings,
145+
accessToken,
146+
siteData
147+
}) {
148+
const addonResponse = await createAddon(settings, accessToken)
149+
150+
if (addonResponse.code === 404) {
151+
console.log(`No add-on "${addonName}" found. Please double check your add-on name and try again`)
152+
return false
153+
}
154+
console.log(`Add-on "${addonName}" created for ${siteData.name}`)
155+
if (addonResponse.config && addonResponse.config.message) {
156+
console.log()
157+
console.log(`${addonResponse.config.message}`)
158+
}
159+
return addonResponse
160+
}
161+
162+
addonsCreateCommand.description = `Add an add-on extension to your site
61163
...
62-
Addons are a way to extend the functionality of your Netlify site
164+
Add-ons are a way to extend the functionality of your Netlify site
63165
`
64166

65-
addonsCreateCommand.args = [
66-
{
67-
name: 'name',
68-
required: true,
69-
description: 'addon namespace'
70-
}
71-
]
167+
addonsCreateCommand.args = [{
168+
name: 'name',
169+
required: true,
170+
description: 'Add-on namespace'
171+
}]
72172

73173
// allow for any flags. Handy for variadic configuration options
74174
addonsCreateCommand.strict = false
75175

76-
addonsCreateCommand.hidden = true
77-
78176
module.exports = addonsCreateCommand

src/commands/open/admin.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,19 @@ Run \`netlify link\` to connect to this folder to a site`)
2020
this.log(`Opening "${siteData.name}" site admin UI:`)
2121
this.log(`> ${siteData.admin_url}`)
2222
} catch (e) {
23-
console.log('e', e)
2423
if (e.status === 401 /* unauthorized*/) {
2524
this.warn(`Log in with a different account or re-link to a site you have permission for`)
2625
this.error(`Not authorized to view the currently linked site (${siteId})`)
2726
}
2827
if (e.status === 404 /* site not found */) {
29-
this.log(`No site with id ${siteId} found.`)
28+
29+
this.log()
3030
this.log('Please double check this ID and verify you are logged in with the correct account')
3131
this.log()
3232
this.log('To fix this, run `netlify unlink` then `netlify link` to reconnect to the correct site ID')
3333
this.log()
34-
this.error(`Site (${siteId}) not found in account`)
34+
this.error(`Site "${siteId}" not found in account`)
35+
3536
}
3637
this.error(e)
3738
}

src/commands/unlink.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class UnlinkCommand extends Command {
77
const siteId = site.id
88

99
if (!siteId) {
10-
this.log(`Folder is not linked to a Netlify site`)
10+
this.log(`Folder is not linked to a Netlify site. Run 'netlify link' to link it`)
1111
return this.exit()
1212
}
1313

@@ -24,7 +24,12 @@ class UnlinkCommand extends Command {
2424
siteId: siteData.id || siteId,
2525
})
2626

27-
this.log(`Unlinked ${site.configPath} from ${siteData ? siteData.name : siteId}`)
27+
if (site) {
28+
this.log(`Unlinked ${site.configPath} from ${siteData ? siteData.name : siteId}`)
29+
} else {
30+
this.log('Unlinked site')
31+
}
32+
2833
}
2934
}
3035

src/utils/addons/api.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const fetch = require('node-fetch')
2+
3+
async function getAddonManifest(addonName, netlifyApiToken) {
4+
const url = `https://api.netlify.com/api/v1/services/${addonName}/manifest`
5+
const response = await fetch(url, {
6+
method: 'GET',
7+
headers: {
8+
'Content-Type': 'application/json',
9+
Authorization: `Bearer ${netlifyApiToken}`
10+
}
11+
})
12+
13+
const data = await response.json()
14+
15+
if (response.status === 422) {
16+
throw new Error(`Error ${JSON.stringify(data)}`)
17+
}
18+
19+
return data
20+
}
21+
22+
module.exports = getAddonManifest

src/utils/addons/compare.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
const isEqual = require('lodash.isequal')
2+
3+
module.exports = function compare(oldValues, newValues) {
4+
const initialData = {
5+
// default everything is equal
6+
isEqual: true,
7+
// Keys that are different
8+
keys: [],
9+
// Values of the keys that are different
10+
diffs: {}
11+
}
12+
13+
const oldKeys = Object.keys(oldValues)
14+
const newKeys = Object.keys(newValues)
15+
const set = new Set(newKeys.concat(oldKeys))
16+
17+
return Array.from(set).reduce((acc, current) => {
18+
// if values not deep equal. There are changes
19+
if (!isEqual(newValues[current], oldValues[current])) {
20+
return {
21+
isEqual: false,
22+
keys: acc.keys.concat(current),
23+
diffs: Object.assign({}, acc.diffs, {
24+
[`${current}`]: {
25+
newValue: newValues[current],
26+
oldValue: oldValues[current]
27+
}
28+
})
29+
}
30+
}
31+
return acc
32+
}, initialData)
33+
}
34+
35+
// const newValues = {
36+
// lol: 'byelol',
37+
// }
38+
// const oldValues = {
39+
// lol: 'bye',
40+
// cool: 'dddd'
41+
// }
42+
43+
// const diffs = compareNewToOld(newValues, oldValues)
44+
// console.log('diffs', diffs)
45+
46+
// const diff = diffValues(oldValues, newValues)
47+
// if (diff) {
48+
// console.log(`Config updates\n`)
49+
// console.log(`${diff}\n`)
50+
// // from '${currentState}' to '${newInput}'
51+
// }

src/utils/addons/diffs/index.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const concordance = require('concordance')
2+
const { concordanceOptions, concordanceDiffOptions } = require('./options')
3+
4+
function formatDescriptorDiff(actualDescriptor, expectedDescriptor, options) {
5+
const diffOptions = Object.assign({}, options, concordanceDiffOptions)
6+
return concordance.diffDescriptors(actualDescriptor, expectedDescriptor, diffOptions)
7+
}
8+
9+
module.exports = function diffValues(actual, expected) {
10+
const result = concordance.compare(actual, expected, concordanceOptions)
11+
if (result.pass) {
12+
return null
13+
}
14+
const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions)
15+
const expectedDescriptor = result.expected || concordance.describe(expected, concordanceOptions)
16+
17+
return formatDescriptorDiff(actualDescriptor, expectedDescriptor)
18+
}

0 commit comments

Comments
 (0)