@raisenow/tamaro-cli 1.0.8 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +1 -0
- package/{.eslintrc.js → .eslintrc.cjs} +2 -14
- package/.gitignore +4 -5
- package/.nvmrc +1 -1
- package/.prettierignore +1 -0
- package/{.prettierrc.js → .prettierrc.cjs} +0 -0
- package/README.md +229 -0
- package/dist/chunk-PIEFLCDU.js +144 -0
- package/dist/cli.js +486 -194
- package/dist/webpack.config.js +119 -227
- package/package.json +38 -47
- package/readme.html +1 -1
- package/src/cli.ts +109 -21
- package/src/commands/build.ts +99 -0
- package/src/commands/deploy-email-config.ts +115 -0
- package/src/commands/deploy.ts +163 -0
- package/src/commands/dev.ts +68 -0
- package/src/commands/list-deployed.ts +112 -0
- package/src/commands/serve.ts +53 -0
- package/src/lib/InterpolateHtmlPlugin.ts +10 -8
- package/src/lib/aws.ts +127 -0
- package/src/lib/command.ts +27 -0
- package/src/lib/constants.ts +7 -0
- package/src/lib/env.ts +102 -0
- package/src/lib/logging.ts +32 -0
- package/src/lib/notifier.ts +22 -0
- package/src/lib/resolve.ts +144 -0
- package/src/webpack.config.ts +36 -28
- package/tsconfig.json +1 -1
- package/readme.md +0 -201
- package/src/assets/rnw-logo.png +0 -0
- package/src/commands/build/index.ts +0 -42
- package/src/commands/deploy/index.ts +0 -24
- package/src/commands/dev/index.ts +0 -32
- package/src/commands/serve/index.ts +0 -12
- package/src/lib/helpers.ts +0 -279
- package/src/lib/polyfills.js +0 -8
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {existsSync} from 'fs'
|
|
2
|
+
import {
|
|
3
|
+
AWS_CLOUDFRONT_DISTRIBUTION_ID,
|
|
4
|
+
AWS_S3_BUCKET,
|
|
5
|
+
CORE_CONFIG_NAME,
|
|
6
|
+
} from 'lib/constants'
|
|
7
|
+
import {
|
|
8
|
+
assertProfileValid,
|
|
9
|
+
AwsOptions,
|
|
10
|
+
withAwsAuthenticate,
|
|
11
|
+
prepareFlags as prepareAwsFlags,
|
|
12
|
+
} from 'lib/aws'
|
|
13
|
+
import {getIfCoreFns, getPaths, getWidgetUuid} from 'lib/resolve'
|
|
14
|
+
import {logCommand, logDataTable, logTitle} from 'lib/logging'
|
|
15
|
+
import {notify} from 'lib/notifier'
|
|
16
|
+
import {fail, runCommandSync} from 'lib/command'
|
|
17
|
+
|
|
18
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
19
|
+
|
|
20
|
+
export type DeployOptions = AwsOptions & {
|
|
21
|
+
tag: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
25
|
+
|
|
26
|
+
export const deploy = async (options: DeployOptions): Promise<void> => {
|
|
27
|
+
const {ifCore} = getIfCoreFns()
|
|
28
|
+
const paths = getPaths(ifCore)
|
|
29
|
+
|
|
30
|
+
assertOptionsValid(options)
|
|
31
|
+
assertDistPathExists(paths.appDist)
|
|
32
|
+
|
|
33
|
+
const flags = prepareFlags([], options).join(' ')
|
|
34
|
+
const {tag} = options
|
|
35
|
+
const configName = ifCore(CORE_CONFIG_NAME, getWidgetUuid())
|
|
36
|
+
const deployUrl = `s3://${AWS_S3_BUCKET}/${configName}/${tag}`
|
|
37
|
+
const entryFilename = ifCore('index.js', 'widget.js')
|
|
38
|
+
|
|
39
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
40
|
+
// Sync all files except entrypoint (without deleting old files)
|
|
41
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
42
|
+
|
|
43
|
+
const cmdSyncAll = `
|
|
44
|
+
aws s3 sync ${paths.appDist} ${deployUrl}
|
|
45
|
+
--acl public-read
|
|
46
|
+
--size-only
|
|
47
|
+
--exclude ${paths.appDist}/${entryFilename}
|
|
48
|
+
--cache-control max-age=31536000
|
|
49
|
+
${flags}
|
|
50
|
+
`
|
|
51
|
+
logTitle('Deploying to AWS S3 …')
|
|
52
|
+
logCommand(cmdSyncAll)
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
withAwsAuthenticate(
|
|
56
|
+
() => runCommandSync(cmdSyncAll, {stdout: 'inherit'}),
|
|
57
|
+
options,
|
|
58
|
+
)
|
|
59
|
+
} catch (error: any) {
|
|
60
|
+
fail(error.stderr)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
64
|
+
// Copy entrypoint
|
|
65
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
66
|
+
|
|
67
|
+
const cmdCpEntry = `
|
|
68
|
+
aws s3 cp ${paths.appDist}/${entryFilename} ${deployUrl}/${entryFilename}
|
|
69
|
+
--acl public-read
|
|
70
|
+
--cache-control max-age=64800
|
|
71
|
+
${flags}
|
|
72
|
+
`
|
|
73
|
+
logCommand(cmdCpEntry)
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
withAwsAuthenticate(
|
|
77
|
+
() => runCommandSync(cmdCpEntry, {stdout: 'inherit'}),
|
|
78
|
+
options,
|
|
79
|
+
)
|
|
80
|
+
} catch (error: any) {
|
|
81
|
+
fail(error.stderr)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
85
|
+
// Invalidate cloudfront cache
|
|
86
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
87
|
+
|
|
88
|
+
const cmdInvalidateCache = `
|
|
89
|
+
aws cloudfront create-invalidation
|
|
90
|
+
--distribution-id ${AWS_CLOUDFRONT_DISTRIBUTION_ID}
|
|
91
|
+
--paths /${configName}/${tag}/*
|
|
92
|
+
${flags}
|
|
93
|
+
`
|
|
94
|
+
logTitle('\nInvalidating edge cache …')
|
|
95
|
+
logCommand(cmdInvalidateCache)
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// ignore output
|
|
99
|
+
withAwsAuthenticate(() => runCommandSync(cmdInvalidateCache), options)
|
|
100
|
+
} catch (error: any) {
|
|
101
|
+
fail(error.stderr)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
105
|
+
|
|
106
|
+
const demoPage = `https://${AWS_S3_BUCKET}/${configName}/${tag}/index.html`
|
|
107
|
+
const entryPoint = `https://${AWS_S3_BUCKET}/${configName}/${tag}/${entryFilename}`
|
|
108
|
+
|
|
109
|
+
logTitle(`\nBundle for “${configName}” is deployed with tag “${tag}”.`)
|
|
110
|
+
logDataTable({
|
|
111
|
+
'Demo page:': demoPage,
|
|
112
|
+
'Entry point:': entryPoint,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
116
|
+
|
|
117
|
+
process.on('exit', () => {
|
|
118
|
+
notify({
|
|
119
|
+
title: 'deploy',
|
|
120
|
+
message: `
|
|
121
|
+
Bundle for “${configName}” is deployed with tag “${tag}”.
|
|
122
|
+
Demo page: ${demoPage}
|
|
123
|
+
Entry point: ${entryPoint}
|
|
124
|
+
`,
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
130
|
+
|
|
131
|
+
const assertOptionsValid = (options: DeployOptions) => {
|
|
132
|
+
const {tag, profile} = options
|
|
133
|
+
|
|
134
|
+
assertTagValid(tag)
|
|
135
|
+
assertProfileValid(profile)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
139
|
+
|
|
140
|
+
const assertDistPathExists = (distPath: string) => {
|
|
141
|
+
if (!existsSync(distPath)) {
|
|
142
|
+
fail(`
|
|
143
|
+
Dist folder does not exists.
|
|
144
|
+
Make sure you have built the bundle before trying to deploy it.
|
|
145
|
+
`)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
150
|
+
|
|
151
|
+
const assertTagValid = (tag: string) => {
|
|
152
|
+
const regex = /^[a-zA-Z0-9-_.]+$/
|
|
153
|
+
|
|
154
|
+
if (!regex.test(tag)) {
|
|
155
|
+
fail(`Flag "--tag" has forbidden format. Allowed format: ${regex}.`)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
160
|
+
|
|
161
|
+
const prepareFlags = (flags: string[], options: DeployOptions): string[] => {
|
|
162
|
+
return prepareAwsFlags(flags, options)
|
|
163
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {getPortPromise} from 'portfinder'
|
|
2
|
+
import {getIfCoreFns, resolveBin, resolveOwn} from 'lib/resolve'
|
|
3
|
+
import {logCommand, logTitle} from 'lib/logging'
|
|
4
|
+
import {fail, runCommandSync} from 'lib/command'
|
|
5
|
+
|
|
6
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7
|
+
|
|
8
|
+
export type DevOptions = {
|
|
9
|
+
localCore: boolean
|
|
10
|
+
port: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
14
|
+
|
|
15
|
+
export const dev = async (options: DevOptions): Promise<void> => {
|
|
16
|
+
assertOptionsValid(options)
|
|
17
|
+
|
|
18
|
+
const flags = (await prepareFlags([], options)).join(' ')
|
|
19
|
+
const {ifCore} = getIfCoreFns()
|
|
20
|
+
const title = ifCore(
|
|
21
|
+
'Running dev web-server for Tamaro Core …',
|
|
22
|
+
'Running dev web-server for customer configuration …',
|
|
23
|
+
)
|
|
24
|
+
const wpBin = resolveBin('webpack')
|
|
25
|
+
const wpConfig = resolveOwn('dist/webpack.config.js')
|
|
26
|
+
const cmd = `
|
|
27
|
+
${wpBin} serve
|
|
28
|
+
--config ${wpConfig}
|
|
29
|
+
${flags}
|
|
30
|
+
`
|
|
31
|
+
|
|
32
|
+
logTitle(title)
|
|
33
|
+
logCommand(cmd)
|
|
34
|
+
runCommandSync(cmd, {stdio: 'inherit'})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
38
|
+
|
|
39
|
+
const assertOptionsValid = (options: DevOptions) => {
|
|
40
|
+
const {localCore, port} = options
|
|
41
|
+
const {ifCore} = getIfCoreFns()
|
|
42
|
+
|
|
43
|
+
if (ifCore() && localCore) {
|
|
44
|
+
fail('Flag "--local-core" is redundant if running in Tamaro Core context.')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (isNaN(Number(port))) {
|
|
48
|
+
fail('Flag "--port" should be a number.')
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
53
|
+
|
|
54
|
+
const prepareFlags = async (
|
|
55
|
+
flags: string[],
|
|
56
|
+
options: DevOptions,
|
|
57
|
+
): Promise<string[]> => {
|
|
58
|
+
const {localCore, port: defaultPort} = options
|
|
59
|
+
const port = await getPortPromise({port: Number(defaultPort)})
|
|
60
|
+
|
|
61
|
+
if (localCore) {
|
|
62
|
+
flags.push('--env localCore')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
flags.push(`--port ${port}`)
|
|
66
|
+
|
|
67
|
+
return flags
|
|
68
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {AWS_S3_BUCKET, CORE_CONFIG_NAME} from 'lib/constants'
|
|
2
|
+
import {
|
|
3
|
+
assertProfileValid,
|
|
4
|
+
AwsOptions,
|
|
5
|
+
withAwsAuthenticate,
|
|
6
|
+
prepareFlags as prepareAwsFlags,
|
|
7
|
+
} from 'lib/aws'
|
|
8
|
+
import {getIfCoreFns, getWidgetUuid} from 'lib/resolve'
|
|
9
|
+
import {logCommand, logTitle} from 'lib/logging'
|
|
10
|
+
import {fail, runCommandSync} from 'lib/command'
|
|
11
|
+
|
|
12
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
13
|
+
|
|
14
|
+
export type ListDeployedOptions = AwsOptions & {
|
|
15
|
+
config?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
19
|
+
|
|
20
|
+
export const listDeployed = async (
|
|
21
|
+
options: ListDeployedOptions,
|
|
22
|
+
): Promise<void> => {
|
|
23
|
+
assertOptionsValid(options)
|
|
24
|
+
|
|
25
|
+
const flags = prepareFlags([], options).join(' ')
|
|
26
|
+
const {config} = options
|
|
27
|
+
const {ifCore} = getIfCoreFns()
|
|
28
|
+
const configName = config ?? ifCore(CORE_CONFIG_NAME, getWidgetUuid())
|
|
29
|
+
const title =
|
|
30
|
+
!config && ifCore()
|
|
31
|
+
? `Listing deployments of Tamaro Core …`
|
|
32
|
+
: `Listing deployments of “${configName}” customer configuration …`
|
|
33
|
+
|
|
34
|
+
const deployUrl = `s3://${AWS_S3_BUCKET}/${configName}/`
|
|
35
|
+
const cmd = `aws s3 ls ${deployUrl} ${flags}`
|
|
36
|
+
let out = ''
|
|
37
|
+
|
|
38
|
+
logTitle(title)
|
|
39
|
+
logCommand(cmd)
|
|
40
|
+
|
|
41
|
+
// If customer configuration folder does not exist on AWS S3,
|
|
42
|
+
// command fails with blank stderr, so we need to handle this case.
|
|
43
|
+
try {
|
|
44
|
+
const result = withAwsAuthenticate(() => runCommandSync(cmd), options)
|
|
45
|
+
out = result.stdout
|
|
46
|
+
} catch (error: any) {
|
|
47
|
+
out = error.stdout
|
|
48
|
+
|
|
49
|
+
if (error.stderr) {
|
|
50
|
+
fail(error.stderr)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const lines = out.split('\n')
|
|
55
|
+
const tags = parseTags(lines)
|
|
56
|
+
let text = ''
|
|
57
|
+
|
|
58
|
+
if (tags.length === 0) {
|
|
59
|
+
text = 'No deployments found.'
|
|
60
|
+
} else {
|
|
61
|
+
text = tags
|
|
62
|
+
.map((tag, idx) => {
|
|
63
|
+
// prettier-ignore
|
|
64
|
+
return `${idx + 1}. https://${AWS_S3_BUCKET}/${configName}/${tag}/index.html`
|
|
65
|
+
})
|
|
66
|
+
.join('\n')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(`${text}\n`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
73
|
+
|
|
74
|
+
const assertOptionsValid = (options: ListDeployedOptions) => {
|
|
75
|
+
const {config, profile} = options
|
|
76
|
+
|
|
77
|
+
assertConfigValid(config)
|
|
78
|
+
assertProfileValid(profile)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
82
|
+
|
|
83
|
+
const assertConfigValid = (config: string | undefined) => {
|
|
84
|
+
if (!config) {
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const regex = /^[a-zA-Z0-9-_]+$/
|
|
89
|
+
|
|
90
|
+
if (!regex.test(config)) {
|
|
91
|
+
fail(`Flag "--config" has forbidden format. Allowed format: ${regex}.`)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
96
|
+
|
|
97
|
+
const prepareFlags = (
|
|
98
|
+
flags: string[],
|
|
99
|
+
options: ListDeployedOptions,
|
|
100
|
+
): string[] => {
|
|
101
|
+
return prepareAwsFlags(flags, options)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
105
|
+
|
|
106
|
+
const parseTags = (lines: string[]): string[] => {
|
|
107
|
+
const regex = /^\s*PRE\s*/
|
|
108
|
+
|
|
109
|
+
return lines
|
|
110
|
+
.filter((line) => regex.test(line))
|
|
111
|
+
.map((line) => line.replace(regex, '').replace(/\/$/, ''))
|
|
112
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {getPortPromise} from 'portfinder'
|
|
2
|
+
import {getIfCoreFns, getPaths} from 'lib/resolve'
|
|
3
|
+
import {logCommand, logTitle} from 'lib/logging'
|
|
4
|
+
import {fail, runCommandSync} from 'lib/command'
|
|
5
|
+
|
|
6
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7
|
+
|
|
8
|
+
export type ServeOptions = {
|
|
9
|
+
port: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
13
|
+
|
|
14
|
+
export const serve = async (options: ServeOptions): Promise<void> => {
|
|
15
|
+
assertOptionsValid(options)
|
|
16
|
+
|
|
17
|
+
const flags = (await prepareFlags([], options)).join(' ')
|
|
18
|
+
const {ifCore} = getIfCoreFns()
|
|
19
|
+
const paths = getPaths(ifCore)
|
|
20
|
+
const cmd = `
|
|
21
|
+
npx -y serve ${paths.appDist}
|
|
22
|
+
--cors
|
|
23
|
+
${flags}
|
|
24
|
+
`
|
|
25
|
+
|
|
26
|
+
logTitle(`Running web-server for pre-built bundle …`)
|
|
27
|
+
logCommand(cmd)
|
|
28
|
+
runCommandSync(cmd, {stdio: 'inherit'})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
32
|
+
|
|
33
|
+
const assertOptionsValid = (options: ServeOptions) => {
|
|
34
|
+
const {port} = options
|
|
35
|
+
|
|
36
|
+
if (isNaN(Number(port))) {
|
|
37
|
+
fail('Flag "--port" should be a number.')
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
42
|
+
|
|
43
|
+
const prepareFlags = async (
|
|
44
|
+
flags: string[],
|
|
45
|
+
options: ServeOptions,
|
|
46
|
+
): Promise<string[]> => {
|
|
47
|
+
const {port: defaultPort} = options
|
|
48
|
+
const port = await getPortPromise({port: Number(defaultPort)})
|
|
49
|
+
|
|
50
|
+
flags.push(`-p ${port}`)
|
|
51
|
+
|
|
52
|
+
return flags
|
|
53
|
+
}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import HtmlWebpackPlugin from 'html-webpack-plugin'
|
|
2
1
|
import {Compiler} from 'webpack'
|
|
2
|
+
import HtmlWebpackPlugin from 'html-webpack-plugin'
|
|
3
3
|
import escapeStringRegexp from 'escape-string-regexp'
|
|
4
4
|
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
|
|
7
|
-
type Replacements = {[key: string]: any}
|
|
7
|
+
export type Replacements = {[key: string]: any}
|
|
8
|
+
|
|
9
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
10
|
+
|
|
11
|
+
const PLUGIN_NAME = 'InterpolateHtmlPlugin'
|
|
8
12
|
|
|
9
13
|
///////////////////////////////////////////////////////////////////////////////
|
|
10
14
|
|
|
11
|
-
class InterpolateHtmlPlugin {
|
|
15
|
+
export class InterpolateHtmlPlugin {
|
|
12
16
|
replacements: Replacements
|
|
13
17
|
|
|
14
18
|
constructor(replacements: Replacements) {
|
|
@@ -16,16 +20,16 @@ class InterpolateHtmlPlugin {
|
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
apply(compiler: Compiler): void {
|
|
19
|
-
compiler.hooks.compilation.tap(
|
|
23
|
+
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
|
|
20
24
|
const hooks = HtmlWebpackPlugin.getHooks(compilation)
|
|
21
25
|
|
|
22
|
-
hooks.alterAssetTagGroups.tap(
|
|
26
|
+
hooks.alterAssetTagGroups.tap(PLUGIN_NAME, (assets) => {
|
|
23
27
|
this.replacements['ENTRY'] = assets.headTags[0].attributes['src']
|
|
24
28
|
|
|
25
29
|
return assets
|
|
26
30
|
})
|
|
27
31
|
|
|
28
|
-
hooks.afterTemplateExecution.tap(
|
|
32
|
+
hooks.afterTemplateExecution.tap(PLUGIN_NAME, (data) => {
|
|
29
33
|
// Run HTML through a series of user-specified string replacements.
|
|
30
34
|
Object.entries(this.replacements).forEach(([key, value]) => {
|
|
31
35
|
data.html = data.html.replace(
|
|
@@ -39,5 +43,3 @@ class InterpolateHtmlPlugin {
|
|
|
39
43
|
})
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
|
-
|
|
43
|
-
export default InterpolateHtmlPlugin
|
package/src/lib/aws.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {execaCommandSync} from 'execa'
|
|
2
|
+
import stripIndent from 'strip-indent'
|
|
3
|
+
import {fail} from 'lib/command'
|
|
4
|
+
|
|
5
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
+
|
|
7
|
+
export type AwsOptions = {
|
|
8
|
+
profile: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
12
|
+
|
|
13
|
+
export const withAwsAuthenticate = <Result = any>(
|
|
14
|
+
fn: () => Result,
|
|
15
|
+
options: AwsOptions,
|
|
16
|
+
): Result => {
|
|
17
|
+
awsAuthenticate(options)
|
|
18
|
+
|
|
19
|
+
return fn()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
23
|
+
|
|
24
|
+
export const awsAuthenticate = (options: AwsOptions) => {
|
|
25
|
+
if (isSetEnv()) {
|
|
26
|
+
// Don't try to authenticate the user.
|
|
27
|
+
// This may be useful if CLI is running within an automated system like pipeline,
|
|
28
|
+
// where "AWS_ACCESS_KEY_ID" and "AWS_SECRET_ACCESS_KEY" are used instead of "AWS_PROFILE".
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
checkIdentity(options)
|
|
34
|
+
} catch (error) {
|
|
35
|
+
login(options)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
40
|
+
|
|
41
|
+
// Returns true if "AWS_ACCESS_KEY_ID" and "AWS_SECRET_ACCESS_KEY" environment variables are set.
|
|
42
|
+
export const isSetEnv = () => {
|
|
43
|
+
return !!(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
47
|
+
|
|
48
|
+
export const assertProfileValid = (profile: string) => {
|
|
49
|
+
if (isSetEnv()) {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const profiles = getAvailableProfiles()
|
|
54
|
+
|
|
55
|
+
if (!profiles.includes(profile)) {
|
|
56
|
+
console.log(`Using AWS profile: “${profile}”`)
|
|
57
|
+
|
|
58
|
+
let message
|
|
59
|
+
|
|
60
|
+
if (profiles.length === 0) {
|
|
61
|
+
message = stripIndent(`
|
|
62
|
+
No AWS profiles found.
|
|
63
|
+
`)
|
|
64
|
+
} else {
|
|
65
|
+
message = stripIndent(`
|
|
66
|
+
AWS profile “${profile}” has not been found.
|
|
67
|
+
Available profiles are: ${profiles.map((v) => `“${v}”`).join(', ')}.
|
|
68
|
+
`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
message += stripIndent(`
|
|
72
|
+
Run “aws configure sso” to set up SSO-enabled profile.
|
|
73
|
+
Check the wiki for more information:
|
|
74
|
+
https://raisenow.atlassian.net/wiki/x/lIrWvg
|
|
75
|
+
`)
|
|
76
|
+
|
|
77
|
+
fail(message)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
82
|
+
|
|
83
|
+
const getAvailableProfiles = (): string[] => {
|
|
84
|
+
const {stdout} = execaCommandSync('aws configure list-profiles')
|
|
85
|
+
|
|
86
|
+
return stdout.split('\n')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
90
|
+
|
|
91
|
+
// Returns caller identity data (output is ignored here).
|
|
92
|
+
// Fails in case of expired SSO session.
|
|
93
|
+
const checkIdentity = (options: AwsOptions) => {
|
|
94
|
+
const flags = prepareFlags([], options).join(' ')
|
|
95
|
+
|
|
96
|
+
execaCommandSync(`aws sts get-caller-identity ${flags}`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
100
|
+
|
|
101
|
+
// Opens SSO authentication page in the browser.
|
|
102
|
+
// Fails if user canceled authentication process.
|
|
103
|
+
const login = (options: AwsOptions) => {
|
|
104
|
+
const flags = prepareFlags([], options).join(' ')
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
execaCommandSync(`aws sso login ${flags}`, {stdio: 'inherit'})
|
|
108
|
+
console.log('')
|
|
109
|
+
} catch (error) {
|
|
110
|
+
fail('Login failed.')
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
115
|
+
|
|
116
|
+
export const prepareFlags = (
|
|
117
|
+
flags: string[],
|
|
118
|
+
options: AwsOptions,
|
|
119
|
+
): string[] => {
|
|
120
|
+
const {profile} = options
|
|
121
|
+
|
|
122
|
+
if (!isSetEnv()) {
|
|
123
|
+
flags.push(`--profile ${profile}`)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return flags
|
|
127
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {execaCommandSync, ExecaSyncReturnValue, SyncOptions} from 'execa'
|
|
2
|
+
import {logError} from 'lib/logging'
|
|
3
|
+
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
|
|
6
|
+
export const fail = (message?: string) => {
|
|
7
|
+
logError(message)
|
|
8
|
+
process.exit(1)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
12
|
+
|
|
13
|
+
export const prepareCommand = (cmd: string): string => {
|
|
14
|
+
return cmd
|
|
15
|
+
.replace(/\n/gm, ' ')
|
|
16
|
+
.replace(/[ \t]{2,}/gm, ' ')
|
|
17
|
+
.trim()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
21
|
+
|
|
22
|
+
export const runCommandSync = (
|
|
23
|
+
cmd: string,
|
|
24
|
+
options?: SyncOptions,
|
|
25
|
+
): ExecaSyncReturnValue => {
|
|
26
|
+
return execaCommandSync(prepareCommand(cmd), options)
|
|
27
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const DEFAULT_TAG = 'latest'
|
|
2
|
+
export const DEFAULT_PORT = 1234
|
|
3
|
+
export const DEFAULT_AWS_PROFILE = 'payments-prod-cs-deployer'
|
|
4
|
+
export const DEFAULT_AWS_S3_EMAIL_CONFIG_BUCKET = 'rnw-email-service'
|
|
5
|
+
export const AWS_S3_BUCKET = 'tamaro.raisenow.com'
|
|
6
|
+
export const CORE_CONFIG_NAME = 'tamaro-core'
|
|
7
|
+
export const AWS_CLOUDFRONT_DISTRIBUTION_ID = 'EHJ1OM458YQ0I'
|