Add command to fix templates (#21)

* add command to fix templates

* replaceAll and lint fixes

* fixed menu

* fix lint command and run it

* add basic error handling for listing of templates

* CR suggestions

---------

Co-authored-by: Arwid Thornström <arwidt@gmail.com>
This commit is contained in:
Anders Gustafsson
2024-08-16 09:34:15 +02:00
committed by GitHub
co-authored by Arwid Thornström
parent bc9ba08e76
commit 724f3905ab
28 changed files with 248 additions and 14310 deletions
+3 -3
View File
@@ -11,16 +11,16 @@ async function run(argv) {
.plugins('./node_modules', { matching: 'pwcli-*', hidden: true })
.plugin(__dirname + '/../node_modules/@lenne.tech/gluegun-menu/dist', {
commandFilePattern: '*.js',
extensionFilePattern: '*.js'
extensionFilePattern: '*.js',
})
.help({
name: 'help',
alias: 'h',
hidden: false,
dashed: true,
run: toolbox => {
run: (toolbox) => {
toolbox.print.info('This is the help');
}
},
}) // provides default for help, h, --help, -h
// .version() // provides default for version, v, --version, -v
.create();
+1 -1
View File
@@ -8,5 +8,5 @@ module.exports = {
hidden: false,
run: async (toolbox: GluegunMenuToolbox) => {
await toolbox.menu.showMenu('aws', defaultMenuSettings);
}
},
};
+7 -7
View File
@@ -16,7 +16,7 @@ module.exports = {
strings.trim(await system.run(`aws logs describe-log-groups`))
);
const options = groups.logGroups.map(item => {
const options = groups.logGroups.map((item) => {
return item.logGroupName;
});
@@ -25,7 +25,7 @@ module.exports = {
message: 'Choose a logGroup',
limit: 30,
initial: options.length - 1,
choices: options
choices: options,
});
const logGroup = await prompt.run();
@@ -36,16 +36,16 @@ module.exports = {
`${logGroup}`,
'--follow',
'--format',
'short'
'short',
]);
child.stdout.setEncoding('utf8');
child.stdout.on('data', function(data) {
child.stdout.on('data', function (data) {
print.info(data);
});
child.stderr.on('data', data => {
child.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
child.on('close', code => {
child.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
}
@@ -53,5 +53,5 @@ module.exports = {
if (toolbox.fromMenu()) {
await toolbox.menu.showMenu('aws', defaultMenuSettings);
}
}
},
};
@@ -48,7 +48,7 @@ module.exports = {
filesystem.file('/tmp/.pwcli_parametercache', {
mode: '600',
content: JSON.stringify(valueNames)
content: JSON.stringify(valueNames),
});
await sleep(2000);
@@ -62,7 +62,7 @@ module.exports = {
message: 'Choose a parameter (esc to exit)',
limit: 30,
initial: 10,
choices: valueNames
choices: valueNames,
});
try {
@@ -87,5 +87,5 @@ module.exports = {
if (toolbox.fromMenu()) {
await toolbox.menu.showMenu('aws', defaultMenuSettings);
}
}
},
};
+1 -1
View File
@@ -11,5 +11,5 @@ module.exports = {
hidden: false,
run: async (toolbox: GluegunMenuToolbox) => {
await toolbox.menu.showMenu('aws pipeline', defaultMenuSettings);
}
},
};
+3 -3
View File
@@ -35,7 +35,7 @@ module.exports = {
// });
const curratedPipes: Pipeline[] = pipelines.pipelines.filter(Boolean);
const options = curratedPipes.map(item => {
const options = curratedPipes.map((item) => {
return item.name;
});
@@ -44,7 +44,7 @@ module.exports = {
message: 'Choose a pipeline',
limit: options.length - 1,
initial: options.length - 1,
choices: options
choices: options,
});
try {
@@ -104,5 +104,5 @@ module.exports = {
} else {
exit();
}
}
},
};
+1 -1
View File
@@ -8,5 +8,5 @@ module.exports = {
hidden: false,
run: async (toolbox: GluegunMenuToolbox) => {
await toolbox.menu.showMenu('deployment', defaultMenuSettings);
}
},
};
@@ -12,7 +12,7 @@ const getConfirmation = async (branchName: string, stackName: string) => {
name: 'confirm',
message: `Will create ${chalk.yellow(
url + '.photowall-test.xx'
)} and publish ${chalk.green(branchName)} to it, continue?`
)} and publish ${chalk.green(branchName)} to it, continue?`,
});
try {
@@ -49,27 +49,27 @@ module.exports = {
'Photowall',
'photowall'
);
const branchOptions = branches.map(item => item.name);
const branchOptions = branches.map((item) => item.name);
const promptBranch = new AutoComplete({
name: 'branchName',
message: 'Select a branch',
limit: 10,
initial: 10,
choices: branchOptions
choices: branchOptions,
});
const branchName = await promptBranch.run();
const testStacks = await getTestStacksInfo(toolbox);
const testStacks = await getTestStacksInfo(toolbox);
const options = testStacks.map(item => {
const options = testStacks.map((item) => {
return item.name;
});
const input = new Input({
type: 'input',
name: 'stack',
message: `Subdomain of new server (${chalk.yellow('test-xxx')})`
message: `Subdomain of new server (${chalk.yellow('test-xxx')})`,
});
const stacksuffix = await input.run();
const stackName = `photowall-${stacksuffix}`;
@@ -117,7 +117,7 @@ module.exports = {
);
const output = await system.run(cmd, {
cwd: `${config.photowall_repo}/cloudformation/`,
trim: true
trim: true,
});
print.fancy(output);
}
@@ -131,5 +131,5 @@ module.exports = {
defaultMenuSettings
);
}
}
},
};
@@ -10,7 +10,7 @@ const getConfirmation = async (stackName: string) => {
name: 'confirm',
message: `The stack ${chalk.yellow(stackName)} will be ${chalk.red.bold(
'deleted'
)}`
)}`,
});
try {
@@ -23,7 +23,7 @@ const getConfirmation = async (stackName: string) => {
const confirmCommand = async (cmd: string) => {
const confirm = new Confirm({
name: 'confirm',
message: `RUN COMMAND > [${chalk.yellow(cmd)}] ?`
message: `RUN COMMAND > [${chalk.yellow(cmd)}] ?`,
});
try {
@@ -54,7 +54,7 @@ module.exports = {
const testStacks = await getTestStacksInfo(toolbox);
const options = testStacks.map(item => {
const options = testStacks.map((item) => {
return item.name;
});
@@ -63,7 +63,7 @@ module.exports = {
message: 'Choose a server',
limit: options.length - 1,
initial: options.length - 1,
choices: options
choices: options,
});
const stackName = await prompt.run();
@@ -119,5 +119,5 @@ module.exports = {
defaultMenuSettings
);
}
}
},
};
@@ -60,7 +60,7 @@ module.exports = {
return {
issue: issueNumber,
status: getStatusIcon(issueData ? issueData.state : 'null'),
issueUpdated: issueData ? issueData.updated_at : null
issueUpdated: issueData ? issueData.updated_at : null,
};
};
@@ -69,7 +69,7 @@ module.exports = {
issueNumber: string
) => {
const issueStatus = issueStatuses.find(
issueStatus => issueStatus.issue === issueNumber
(issueStatus) => issueStatus.issue === issueNumber
);
return issueStatus ? issueStatus.status : getStatusIcon('null');
};
@@ -125,7 +125,7 @@ module.exports = {
'branch',
'issue status',
'issue updated',
'stack updated'
'stack updated',
// 'url'
])
.line('-');
@@ -161,19 +161,19 @@ module.exports = {
ellipsis(stack.branch, 24),
printIssueStatus(issueStatuses, branchIssueNumber),
issueStatuses.find(
issueStatus => issueStatus.issue === branchIssueNumber
(issueStatus) => issueStatus.issue === branchIssueNumber
)
? timeSince(
new Date(
issueStatuses.find(
issueStatus => issueStatus.issue === branchIssueNumber
(issueStatus) => issueStatus.issue === branchIssueNumber
).issueUpdated
).getTime()
)
: '-',
!isNaN(t.getTime())
? timeSince(t.getTime())
: timeSince(new Date(stack.created).getTime())
: timeSince(new Date(stack.created).getTime()),
// stackUrl(stack.name)
]);
}
@@ -185,5 +185,5 @@ module.exports = {
defaultMenuSettings
);
}
}
},
};
@@ -11,5 +11,5 @@ module.exports = {
hidden: false,
run: async (toolbox: GluegunMenuToolbox) => {
await toolbox.menu.showMenu('deployment testservers', defaultMenuSettings);
}
},
};
@@ -8,7 +8,7 @@ import {
defaultMenuSettings,
getCurrentBranch,
getSettings,
sleep
sleep,
} from '../../../../globals';
type Stack = {
@@ -30,7 +30,7 @@ const getBranchName = async (
type: 'input',
name: 'branch',
message: 'What github branch should be deployed?',
initial: currentBranch
initial: currentBranch,
});
const responseBranch = await input.run();
@@ -43,7 +43,7 @@ const getBranchName = async (
{
owner: 'photowall',
repo: 'photowall',
branch: responseBranch
branch: responseBranch,
}
);
if (response.status === 200) {
@@ -60,7 +60,7 @@ const getConfirmation = async (branchName: string, stackName: string) => {
name: 'confirm',
message: `The branch ${chalk.green(
branchName
)} will deploy to ${chalk.yellow(stackName)}`
)} will deploy to ${chalk.yellow(stackName)}`,
});
try {
@@ -101,7 +101,7 @@ module.exports = {
}
const testStacks = await getTestStacksInfo(toolbox);
const options = testStacks.map(item => {
const options = testStacks.map((item) => {
return item.name;
});
@@ -110,7 +110,7 @@ module.exports = {
message: 'Choose a server',
limit: options.length - 1,
initial: options.length - 1,
choices: options
choices: options,
});
try {
@@ -128,7 +128,7 @@ module.exports = {
);
const output = await system.run(cmd, {
cwd: `${config.photowall_repo}/cloudformation/`,
trim: true
trim: true,
});
print.fancy(output);
@@ -185,5 +185,5 @@ module.exports = {
} else {
exit();
}
}
},
};
@@ -0,0 +1,86 @@
import { GluegunMenuToolbox } from '@lenne.tech/gluegun-menu';
import {
confirmMessage,
defaultMenuSettings,
getSettings,
} from '../../../globals';
import mailchimpFactory from '@mailchimp/mailchimp_transactional';
const { AutoComplete } = require('enquirer');
module.exports = {
name: 'fix_template',
alias: ['ft'],
description:
'Remove any incorrect artifacts caused by Mailchimp export to Mandrill from selected template',
hidden: false,
run: async (toolbox: GluegunMenuToolbox) => {
const { print } = toolbox;
// Load settings
const config = await getSettings(toolbox);
if (!config || !config.mandrill_api_key || config.mandrill_api_key === '') {
print.error(`
This commands requires that you set a Mandrill API key.
`);
await toolbox.menu.showMenu('setup', defaultMenuSettings);
return;
}
const mailchimp = mailchimpFactory(config.mandrill_api_key);
const fixTemplate = async ({ name, code }) => {
const regexPreviewText = /\*\|MC_PREVIEW_TEXT\|\*/gi;
const regexRemoveDoubleHttp = /http(s?):\/\/\{\{/gi;
const anyMailChimpTemplateLanguageStart = /\*\|}/g;
const anyMailChimpTemplateLanguageEnd = /{\|\*/g;
const fixedBody = code
.replace(regexPreviewText, '')
.replaceAll(regexRemoveDoubleHttp, '{{')
.replaceAll(anyMailChimpTemplateLanguageStart, '{{')
.replaceAll(anyMailChimpTemplateLanguageEnd, '}}');
console.log(`Updating ${name}`);
return mailchimp.templates.update({
name,
code: fixedBody,
});
};
const spinner = print.spin(`Fetching available templates...`);
const templates = await mailchimp.templates.list();
spinner.stop();
if (typeof templates !== 'object' || !templates.length) {
print.error(
'Could not fetch templates, does your API key have the right permissions or restricts access for your current IP?'
);
return;
}
const templateNames = templates.map(({ name }) => name);
const prompt = new AutoComplete({
name: 'templates',
message: 'Choose a template',
limit: 10,
initial: 10,
choices: templateNames,
});
try {
const templateName = await prompt.run();
const templateToFix = templates.find(({ name }) => name === templateName);
if (await confirmMessage(`Run fixer on ${templateToFix.name}?`)) {
await fixTemplate(templateToFix);
}
} catch (error) {
print.error(error);
}
print.success('Done');
if (toolbox.fromMenu()) {
await toolbox.menu.showMenu('mandrill', defaultMenuSettings);
}
},
};
+12
View File
@@ -0,0 +1,12 @@
import { GluegunMenuToolbox } from '@lenne.tech/gluegun-menu';
import { defaultMenuSettings } from '../../globals';
module.exports = {
name: 'mandrill',
alias: ['m'],
description: 'Mandrill (m)',
hidden: false,
run: async (toolbox: GluegunMenuToolbox) => {
await toolbox.menu.showMenu('mandrill', defaultMenuSettings);
},
};
+2 -3
View File
@@ -1,6 +1,5 @@
import { GluegunMenuToolbox } from '@lenne.tech/gluegun-menu';
import { GluegunCommand } from 'gluegun'
import { GluegunCommand } from 'gluegun';
const command: GluegunCommand = {
name: 'pwcli',
@@ -9,7 +8,7 @@ const command: GluegunCommand = {
const { print } = toolbox;
print.info('Welcome to photowall CLI for everything, almost 😬');
await toolbox.menu.showMenu(null);
}
},
};
module.exports = command;
+15 -9
View File
@@ -24,13 +24,13 @@ module.exports = {
print.info(
chalk.yellow(`
This will help you setup everything needed to run pwcli on a mac (maby linux).
All settings will be saved to ${chalk.cyan('~/.pwcli_settings')}
First step is to install aws cli [https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html]
and configuring it. Install it either with brew or do as specified in the link.
`)
);
@@ -108,7 +108,8 @@ module.exports = {
if (!config) {
config = {
gh_token: '',
photowall_repo: ''
photowall_repo: '',
mandrill_api_key: '',
};
}
@@ -116,24 +117,29 @@ module.exports = {
message: `A github accesstoken is required with ${chalk.yellow(
'Full control of private repositories'
)}, please insert the key >`,
initial: config.gh_token
initial: config.gh_token,
}).run();
config.photowall_repo = await new Input({
message: `If you want to be able to use current branch in your photowall repository\nor the create and update commands for testservers, please insert\nthe absolute path to the project or leave blank >`,
initial: config.photowall_repo
initial: config.photowall_repo,
}).run();
config.mandrill_api_key = await new Input({
message: `If you want to be able to use the mailchimp commands, please insert\nyour mailchimp api key. Otherwise leave blank >`,
initial: config.mandrill_api_key,
}).run();
print.info(`
${JSON.stringify(config)}
`);
if (
await new Confirm({
message: `Ok to save the these settings to ${chalk.yellow(
`~/.pwcli_settings`
)}?`
)}?`,
}).run()
) {
await system.run(`echo '${JSON.stringify(config)}' > ~/.pwcli_settings`);
@@ -145,5 +151,5 @@ module.exports = {
if (toolbox.fromMenu())
await toolbox.menu.showMenu(null, defaultMenuSettings);
}
},
};
+1 -1
View File
@@ -11,5 +11,5 @@ module.exports = {
hidden: false,
run: async (toolbox: GluegunMenuToolbox) => {
await toolbox.menu.showMenu('setup', defaultMenuSettings);
}
},
};
@@ -2,7 +2,7 @@ import { GluegunMenuToolbox } from '@lenne.tech/gluegun-menu';
import {
confirmMessage,
defaultMenuSettings,
getSettings
getSettings,
} from '../../../../globals';
module.exports = {
@@ -50,12 +50,9 @@ module.exports = {
// If there is a match, then prompt user and ask to remove it
const regex = /\[.*: gone\]/;
const branchesToDelete = localBranchesArray
.filter(branch => regex.test(branch))
.map(branch => {
return strings
.trim(branch)
.replace(' ', '{space}')
.split('{space}')[0];
.filter((branch) => regex.test(branch))
.map((branch) => {
return strings.trim(branch).replace(' ', '{space}').split('{space}')[0];
});
if (branchesToDelete.length > 0) {
@@ -72,5 +69,5 @@ module.exports = {
if (toolbox.fromMenu()) {
await toolbox.menu.showMenu('workflow git', defaultMenuSettings);
}
}
},
};
+1 -1
View File
@@ -8,5 +8,5 @@ module.exports = {
hidden: false,
run: async (toolbox: GluegunMenuToolbox) => {
await toolbox.menu.showMenu('workflow git', defaultMenuSettings);
}
},
};
@@ -6,7 +6,7 @@ import {
confirmMessage,
defaultMenuSettings,
getSettings,
issueToBranchName
issueToBranchName,
} from '../../../globals';
import { getIssueFromRepo, getOrgIssues } from '../../../services/github_rest';
const { AutoComplete } = require('enquirer');
@@ -27,9 +27,9 @@ module.exports = {
}
const repos = await getOrgIssues(toolbox, config.gh_token);
const issueOptions = repos.flatMap(repo => {
const issueOptions = repos.flatMap((repo) => {
return repo.issues.map(
issue => `${issue.number}-${repo.repo}-${issue.title}`
(issue) => `${issue.number}-${repo.repo}-${issue.title}`
);
});
@@ -38,7 +38,7 @@ module.exports = {
message: 'Select issue',
limit: 5,
initial: 5,
choices: issueOptions
choices: issueOptions,
});
const issueResponse = await promptIssues.run();
const issueNumber = issueResponse.split('-')[0];
@@ -59,15 +59,15 @@ module.exports = {
) {
await system.run(`git checkout -b ${branchName} origin/master`, {
cwd: config.photowall_repo,
trim: true
trim: true,
});
await system.run(`git push -u origin ${branchName}`, {
cwd: config.photowall_repo,
trim: true
trim: true,
});
}
if (toolbox.fromMenu())
await toolbox.menu.showMenu('workflow', defaultMenuSettings);
}
},
};
+1 -1
View File
@@ -11,5 +11,5 @@ module.exports = {
hidden: false,
run: async (toolbox: GluegunMenuToolbox) => {
await toolbox.menu.showMenu('workflow', defaultMenuSettings);
}
},
};
+7 -7
View File
@@ -18,8 +18,8 @@ export const getTestStacksInfo = async (
strings.trim(await system.run(`aws cloudformation list-stacks`))
);
const allStacks = await Promise.all(
stacks.StackSummaries.map(async item => {
const allStacks = (await Promise.all(
stacks.StackSummaries.map(async (item) => {
if (
item.StackName.indexOf('photowall-test-') > -1 &&
[
@@ -29,7 +29,7 @@ export const getTestStacksInfo = async (
'UPDATE_FAILED',
'CREATE_FAILED',
'CREATE_IN_PROGRESS',
'ROLLBACK_COMPLETE'
'ROLLBACK_COMPLETE',
].includes(item.StackStatus)
) {
try {
@@ -40,7 +40,7 @@ export const getTestStacksInfo = async (
)
)
);
const sourceStage = pipeline.pipeline.stages.filter(stage => {
const sourceStage = pipeline.pipeline.stages.filter((stage) => {
return stage.name === 'Source';
});
const branch =
@@ -53,7 +53,7 @@ export const getTestStacksInfo = async (
drift_status: item.DriftInformation.StackDriftStatus,
branch: branch,
updated: lastUpdated,
created: item.CreationTime
created: item.CreationTime,
};
} catch (e) {
return {
@@ -62,12 +62,12 @@ export const getTestStacksInfo = async (
drift_status: '',
branch: '',
updated: '',
created: item.CreationTime
created: item.CreationTime,
};
}
}
return Promise.resolve(null);
})
) as Stack[] | null;
)) as Stack[] | null;
return allStacks.filter(Boolean);
};
+8 -7
View File
@@ -4,12 +4,13 @@ const snakeCase = require('lodash.snakecase');
const { Confirm } = require('enquirer');
export const defaultMenuSettings = {
showHelp: false
showHelp: false,
};
export type TConfig = {
gh_token: string;
photowall_repo: string;
mandrill_api_key: string;
};
export const getSettings = async (toolbox): Promise<TConfig | null> => {
const { filesystem, print } = toolbox;
@@ -24,7 +25,7 @@ export const getSettings = async (toolbox): Promise<TConfig | null> => {
No config found, please run [ ${chalk.cyan(
'pwcli s se'
)} ] or setup > settings from the menu.
`);
return null;
}
@@ -40,7 +41,7 @@ export const getCurrentBranch = async (
// git rev-parse --abbrev-ref HEAD
const branch = await system.run('git rev-parse --abbrev-ref HEAD', {
cwd: config.photowall_repo,
trim: true
trim: true,
});
console.log(`branch`, branch);
return branch;
@@ -48,14 +49,14 @@ export const getCurrentBranch = async (
return null;
};
export const issueToBranchName = issue => {
export const issueToBranchName = (issue) => {
return `${issue.number}-${snakeCase(issue.title).substr(0, 40)}`;
};
export const confirmMessage = async (message: string) => {
const confirm = new Confirm({
name: 'confirm',
message: message
message: message,
});
try {
@@ -66,6 +67,6 @@ export const confirmMessage = async (message: string) => {
}
};
export const sleep = milliseconds => {
return new Promise(resolve => setTimeout(resolve, milliseconds));
export const sleep = (milliseconds) => {
return new Promise((resolve) => setTimeout(resolve, milliseconds));
};
+8 -8
View File
@@ -6,7 +6,7 @@ export const getOrgRepos = async (toolbox, token, org) => {
try {
const response = await octokit.request('GET /orgs/{org}/repos', {
org: org,
per_page: 100
per_page: 100,
});
if (response.status === 200) {
return response.data;
@@ -23,7 +23,7 @@ export const getRepo = async (toolbox, token, owner, repo) => {
try {
const response = await octokit.request('GET /repos/{owner}/{repo}', {
owner: owner,
repo: repo
repo: repo,
});
if (response.status === 200) {
return response.data;
@@ -45,7 +45,7 @@ export const getRepoBranches = async (toolbox, token, owner, repo) => {
owner: owner,
repo: repo,
page: page,
per_page: 100
per_page: 100,
}
);
return response;
@@ -70,9 +70,9 @@ export const getRepoBranches = async (toolbox, token, owner, repo) => {
export const getOrgIssues = async (toolbox, token) => {
// const { print } = toolbox;
const repos = await getOrgRepos(toolbox, token, 'Photowall');
const repoNames = repos.map(item => item.name);
const repoNames = repos.map((item) => item.name);
const allIssues = await Promise.all(
repoNames.map(async repoName => {
repoNames.map(async (repoName) => {
const issues = await getOpenIssuesFromRepo(toolbox, token, repoName);
// console.log(`${repoName} - issues`, issues.length);
// return issues.map(item => `${item.number}-${item.title.substring(0, 10)}`);
@@ -89,7 +89,7 @@ export const getOpenIssuesFromRepo = async (toolbox, token, repo) => {
const response = await octokit.request('GET /repos/{owner}/{repo}/issues', {
owner: 'Photowall',
repo: repo,
per_page: 100
per_page: 100,
});
if (response.status === 200) {
return response.data;
@@ -109,7 +109,7 @@ export const getIssueFromRepo = async (toolbox, token, repo, issueNumber) => {
{
owner: 'Photowall',
repo: repo,
issue_number: issueNumber
issue_number: issueNumber,
}
);
if (response.status === 200) {
@@ -134,7 +134,7 @@ export const createPullRequest = async (
owner: 'Photowall',
repo: repo,
head: branch,
base: 'master'
base: 'master',
});
console.log(`response`, response);
// if (response.status === 200) {