diff --git a/bin/clever.js b/bin/clever.js index e1b1dfbd..6d766979 100755 --- a/bin/clever.js +++ b/bin/clever.js @@ -27,9 +27,9 @@ if (hasParam('--autocomplete-index')) { const colors = require('colors'); const colorExplicitFalse = hasParam('--no-color') || hasParam('--color', 'false'); const colorExplicitTrue = hasParam('--color', 'true'); -if (colorExplicitFalse || (!process.stdout.isTTY && !colorExplicitTrue)) { - colors.disable(); -} +// if (colorExplicitFalse || (!process.stdout.isTTY && !colorExplicitTrue)) { +// colors.disable(); +// } // These need to be set before Logger and other stuffs const pkg = require('../package.json'); @@ -158,6 +158,7 @@ function run () { logsFormat: getOutputFormatOption(['json-stream']), activityFormat: getOutputFormatOption(['json-stream']), envFormat: getOutputFormatOption(['shell']), + domainListFormat: getOutputFormatOption(['csv']), accesslogsFollow: cliparse.flag('follow', { aliases: ['f'], description: 'Display access logs continuously (ignores before/until, after/since)', @@ -754,10 +755,14 @@ function run () { description: 'Manage the favourite domain name for an application', commands: [domainSetFavouriteCommand, domainUnsetFavouriteCommand], }, domain('getFavourite')); + const domainListAllCommand = cliparse.command('list-all', { + description: 'TODO', + options: [opts.domainListFormat], + }, domain('listAll')); const domainCommands = cliparse.command('domain', { description: 'Manage domain names for an application', options: [opts.alias, opts.appIdOrName], - commands: [domainCreateCommand, domainFavouriteCommands, domainRemoveCommand], + commands: [domainCreateCommand, domainFavouriteCommands, domainRemoveCommand, domainListAllCommand], }, domain('list')); // DRAIN COMMANDS diff --git a/package-lock.json b/package-lock.json index 2fa76496..7823e699 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "cliparse": "0.4.0", "colors": "1.4.0", "common-env": "6.4.0", + "csv-stringify": "6.5.1", "curlconverter": "3.21.0", "duration-js": "4.0.0", "eventsource": "1.1.0", @@ -27,6 +28,7 @@ "string-length": "4.0.2", "superagent": "6.1.0", "text-table": "0.2.0", + "tldts": "6.1.46", "update-notifier": "5.1.0", "uuid": "8.3.2", "ws": "7.4.6", @@ -2169,6 +2171,11 @@ "node": ">=8" } }, + "node_modules/csv-stringify": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.5.1.tgz", + "integrity": "sha512-+9lpZfwpLntpTIEpFbwQyWuW/hmI/eHuJZD1XzeZpfZTqkf1fyvBbBLXTJJMsBuuS11uTShMqPwzx4A6ffXgRQ==" + }, "node_modules/curlconverter": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/curlconverter/-/curlconverter-3.21.0.tgz", @@ -7818,6 +7825,22 @@ "node": ">= 6" } }, + "node_modules/tldts": { + "version": "6.1.46", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.46.tgz", + "integrity": "sha512-fw81lXV2CijkNrZAZvee7wegs+EOlTyIuVl/z4q6OUzZHQ1jGL2xQzKXq9geYf/1tzo9LZQLrkcko2m8HLh+rg==", + "dependencies": { + "tldts-core": "^6.1.46" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.46", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.46.tgz", + "integrity": "sha512-zA3ai/j4aFcmbqTvTONkSBuWs0Q4X4tJxa0gV9sp6kDbq5dAhQDSg0WUkReEm0fBAKAGNj+wPKCCsR8MYOYmwA==" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -10092,6 +10115,11 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" }, + "csv-stringify": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.5.1.tgz", + "integrity": "sha512-+9lpZfwpLntpTIEpFbwQyWuW/hmI/eHuJZD1XzeZpfZTqkf1fyvBbBLXTJJMsBuuS11uTShMqPwzx4A6ffXgRQ==" + }, "curlconverter": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/curlconverter/-/curlconverter-3.21.0.tgz", @@ -14273,6 +14301,19 @@ } } }, + "tldts": { + "version": "6.1.46", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.46.tgz", + "integrity": "sha512-fw81lXV2CijkNrZAZvee7wegs+EOlTyIuVl/z4q6OUzZHQ1jGL2xQzKXq9geYf/1tzo9LZQLrkcko2m8HLh+rg==", + "requires": { + "tldts-core": "^6.1.46" + } + }, + "tldts-core": { + "version": "6.1.46", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.46.tgz", + "integrity": "sha512-zA3ai/j4aFcmbqTvTONkSBuWs0Q4X4tJxa0gV9sp6kDbq5dAhQDSg0WUkReEm0fBAKAGNj+wPKCCsR8MYOYmwA==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", diff --git a/package.json b/package.json index e8f45d5c..178cb420 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "cliparse": "0.4.0", "colors": "1.4.0", "common-env": "6.4.0", + "csv-stringify": "6.5.1", "curlconverter": "3.21.0", "duration-js": "4.0.0", "eventsource": "1.1.0", @@ -43,6 +44,7 @@ "string-length": "4.0.2", "superagent": "6.1.0", "text-table": "0.2.0", + "tldts": "6.1.46", "update-notifier": "5.1.0", "uuid": "8.3.2", "ws": "7.4.6", diff --git a/src/commands/domain.js b/src/commands/domain.js index 3b357cc1..a878012b 100644 --- a/src/commands/domain.js +++ b/src/commands/domain.js @@ -11,6 +11,12 @@ const { removeDomain, } = require('@clevercloud/client/cjs/api/v2/application.js'); const { sendToApi } = require('../models/send-to-api.js'); +const { getSummary } = require('@clevercloud/client/cjs/api/v2/user.js'); +const { getAllDomains } = require('@clevercloud/client/cjs/api/v2/application.js'); +const { parse: parseDomain } = require('tldts'); +const _ = require('lodash'); +const colors = require('colors/safe.js'); +const { stringify: stringifyCsv } = require('csv-stringify/sync'); function getFavouriteDomain ({ ownerId, appId }) { return getFavouriteDomainWithError({ id: ownerId, appId }) @@ -89,4 +95,142 @@ async function rm (params) { Logger.println('Your domain has been successfully removed'); } -module.exports = { list, add, getFavourite, setFavourite, unsetFavourite, rm }; +async function listAll (params) { + + const { format } = params.options; + const filter = ''; + + const summary = await getSummary().then(sendToApi); + const consoleUrl = summary.user.partnerConsoleUrl; + + const applications = [ + ...summary.user.applications.map((app) => { + return { ownerName: summary.user.name, ownerId: summary.user.id, ...app }; + }), + ...summary.organisations.flatMap((o) => { + return o.applications.map((app) => { + return { ownerName: o.name, ownerId: o.id, ...app }; + }); + }), + ]; + + const applicationsWithDomains = await Promise.all( + applications.map(async (app) => { + const domains = await getAllDomains({ id: app.ownerId, appId: app.id }).then(sendToApi); + return { app, domains }; + }), + ); + + const applicationsWithParsedDomain = applicationsWithDomains + .flatMap(({ app, domains }) => { + return domains + .filter((domain) => domain.fqdn.includes(filter)) + .map((domain) => { + + const parsedDomain = parseDomain(domain.fqdn); + const pathname = new URL('https://' + domain.fqdn).pathname; + const subdomains = parsedDomain.subdomain !== '' ? parsedDomain.subdomain.split('.') : []; + + // We're trying to create an propertyPath for lodash to create a tree structure object, + // the propertyPath for `aaa.bbb.ccc.example.com/the-path` would be: + // ["example.com", "example.com.ccc", "example.com.ccc.bbb", "example.com.ccc.bbb.aaa", "/path-aaa"]", + + const sortSegments = [parsedDomain.domain, ...subdomains.reverse()]; + const propetyPath = sortSegments.map((item, i, all) => { + return all.slice(0, i + 1).reverse().join('.'); + }); + propetyPath.push(pathname); + + return { + ownerId: app.ownerId, + ownerName: app.ownerName, + appId: app.id, + appName: app.name, + appConsoleUrl: `${consoleUrl}/${app.id}`, + appVariantSlug: app.variantSlug, + domain: domain.fqdn, + propetyPath, + }; + }); + }); + + const applicationsWithParsedDomainAsTree = {}; + for (const { propetyPath, ...appWithDomain } of applicationsWithParsedDomain) { + _.set(applicationsWithParsedDomainAsTree, propetyPath, appWithDomain); + } + + const applicationsWithParsedDomainAsSortedTree = recursiveSort(applicationsWithParsedDomainAsTree); + + switch (format) { + case 'json': + Logger.printJson(applicationsWithParsedDomainAsSortedTree); + break; + case 'csv': + + // TODO improve + const applicationsWithParsedDomainCsv = stringifyCsv(recursiveFlatten(applicationsWithParsedDomainAsSortedTree) + .map((app) => { + return [ + app.ownerId, + app.ownerName, + app.appId, + app.appName, + app.domain, + ]; + })); + + Logger.println(applicationsWithParsedDomainCsv); + break; + case 'human': + default: + // TODO empty message (with filter) + recursiveDisplay(applicationsWithParsedDomainAsSortedTree); + break; + } +} + +function recursiveFlatten (obj) { + if (typeof obj === 'object' && obj.appId != null) { + return [obj]; + } + return Object.values(obj).flatMap((value) => { + return recursiveFlatten(value); + }); +} + +function recursiveSort (obj) { + + if (typeof obj === 'object' && obj.appId != null) { + return obj; + } + + const sortedObj = {}; + Object.keys(obj).sort((a, b) => a.localeCompare(b)).forEach((key) => { + sortedObj[key] = recursiveSort(obj[key]); + }); + + return sortedObj; +} + +function recursiveDisplay (obj, indentLevel = 0, isLast) { + + if (typeof obj === 'object' && obj.appId != null) { + Logger.println(' '.repeat(indentLevel) + `${obj.ownerName} / ${obj.appName} / ${obj.appVariantSlug}`); + // TODO implement this console route + Logger.println(' '.repeat(indentLevel) + colors.blue(obj.appConsoleUrl)); + return; + } + + for (const [propertyPath, subObj] of Object.entries(obj)) { + if (propertyPath !== '/') { + Logger.println(''); + Logger.println(' '.repeat(indentLevel) + colors.yellow(propertyPath)); + recursiveDisplay(subObj, indentLevel + 2); + } + else { + recursiveDisplay(subObj, indentLevel); + } + } +} + +module.exports = { list, add, getFavourite, setFavourite, unsetFavourite, rm, listAll }; diff --git a/src/logger.js b/src/logger.js index de9e682c..ea232c01 100644 --- a/src/logger.js +++ b/src/logger.js @@ -29,6 +29,7 @@ function formatLines (prefixLength, lines) { } function consoleErrorWithoutColor (line) { + console.error(line); process.stderr.write(line + '\n'); }