@raisenow/tamaro-cli 1.0.10 → 1.0.13

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.
@@ -1,17 +1,15 @@
1
- import resolveBin from 'resolve-bin'
2
- import {
3
- fail,
4
- getIfCoreFns,
5
- logCommand,
6
- logTitle,
7
- resolveOwn,
8
- runCommandSync,
9
- } from 'lib/helpers'
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
+ import {assertEnvValid} from 'lib/env'
10
6
 
11
7
  ///////////////////////////////////////////////////////////////////////////////
12
8
 
13
9
  export type DevOptions = {
14
10
  localCore: boolean
11
+ port: string
12
+ env: string
15
13
  }
16
14
 
17
15
  ///////////////////////////////////////////////////////////////////////////////
@@ -19,13 +17,13 @@ export type DevOptions = {
19
17
  export const dev = async (options: DevOptions): Promise<void> => {
20
18
  assertOptionsValid(options)
21
19
 
22
- const flags = prepareFlags(options)
20
+ const flags = (await prepareFlags([], options)).join(' ')
23
21
  const {ifCore} = getIfCoreFns()
24
22
  const title = ifCore(
25
23
  'Running dev web-server for Tamaro Core …',
26
24
  'Running dev web-server for customer configuration …',
27
25
  )
28
- const wpBin = resolveBin.sync('webpack')
26
+ const wpBin = resolveBin('webpack')
29
27
  const wpConfig = resolveOwn('dist/webpack.config.js')
30
28
  const cmd = `
31
29
  ${wpBin} serve
@@ -41,21 +39,34 @@ export const dev = async (options: DevOptions): Promise<void> => {
41
39
  ///////////////////////////////////////////////////////////////////////////////
42
40
 
43
41
  const assertOptionsValid = (options: DevOptions) => {
44
- const {localCore} = options
42
+ const {localCore, port, env} = options
45
43
  const {ifCore} = getIfCoreFns()
46
44
 
47
45
  if (ifCore() && localCore) {
48
- fail('Flag "--local-core" is redundant if running in Tamaro Core context.')
46
+ fail('Flag “--local-core is redundant if running in Tamaro Core context.')
49
47
  }
48
+
49
+ if (isNaN(Number(port))) {
50
+ fail('Flag “--port” should be a number.')
51
+ }
52
+
53
+ assertEnvValid(env)
50
54
  }
51
55
 
52
56
  ///////////////////////////////////////////////////////////////////////////////
53
57
 
54
- const prepareFlags = (options: DevOptions): string => {
55
- const {localCore} = options
56
- let flags: string[] = []
58
+ const prepareFlags = async (
59
+ flags: string[],
60
+ options: DevOptions,
61
+ ): Promise<string[]> => {
62
+ const {localCore, port: defaultPort} = options
63
+ const port = await getPortPromise({port: Number(defaultPort)})
64
+
65
+ if (localCore) {
66
+ flags.push('--env localCore')
67
+ }
57
68
 
58
- flags = localCore ? [...flags, '--env localCore'] : flags
69
+ flags.push(`--port ${port}`)
59
70
 
60
- return flags.join(' ')
71
+ return flags
61
72
  }
@@ -1,13 +1,13 @@
1
+ import {AWS_S3_BUCKET, CORE_CONFIG_NAME} from 'lib/constants'
1
2
  import {
2
- fail,
3
- getIfCoreFns,
4
- getWidgetUuid,
5
- logCommand,
6
- logTable,
7
- logTitle,
8
- } from 'lib/helpers'
9
- import {assertProfileValid, AwsOptions, runAwsCommandSync} from 'lib/aws'
10
- import {AWS_S3_BUCKET, CORE_CONFIG_NAME} from 'commands/deploy'
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
11
 
12
12
  ///////////////////////////////////////////////////////////////////////////////
13
13
 
@@ -22,6 +22,7 @@ export const listDeployed = async (
22
22
  ): Promise<void> => {
23
23
  assertOptionsValid(options)
24
24
 
25
+ const flags = prepareFlags([], options).join(' ')
25
26
  const {config} = options
26
27
  const {ifCore} = getIfCoreFns()
27
28
  const configName = config ?? ifCore(CORE_CONFIG_NAME, getWidgetUuid())
@@ -31,7 +32,7 @@ export const listDeployed = async (
31
32
  : `Listing deployments of “${configName}” customer configuration …`
32
33
 
33
34
  const deployUrl = `s3://${AWS_S3_BUCKET}/${configName}/`
34
- const cmd = `aws s3 ls ${deployUrl}`
35
+ const cmd = `aws s3 ls ${deployUrl} ${flags}`
35
36
  let out = ''
36
37
 
37
38
  logTitle(title)
@@ -40,7 +41,7 @@ export const listDeployed = async (
40
41
  // If customer configuration folder does not exist on AWS S3,
41
42
  // command fails with blank stderr, so we need to handle this case.
42
43
  try {
43
- const result = runAwsCommandSync(cmd)
44
+ const result = withAwsAuthenticate(() => runCommandSync(cmd), options)
44
45
  out = result.stdout
45
46
  } catch (error: any) {
46
47
  out = error.stdout
@@ -65,7 +66,7 @@ export const listDeployed = async (
65
66
  .join('\n')
66
67
  }
67
68
 
68
- logTable(text)
69
+ console.log(`${text}\n`)
69
70
  }
70
71
 
71
72
  ///////////////////////////////////////////////////////////////////////////////
@@ -87,12 +88,21 @@ const assertConfigValid = (config: string | undefined) => {
87
88
  const regex = /^[a-zA-Z0-9-_]+$/
88
89
 
89
90
  if (!regex.test(config)) {
90
- fail(`Flag "--config" has forbidden format. Allowed format: ${regex}.`)
91
+ fail(`Flag “--config has forbidden format. Allowed format: ${regex}.`)
91
92
  }
92
93
  }
93
94
 
94
95
  ///////////////////////////////////////////////////////////////////////////////
95
96
 
97
+ const prepareFlags = (
98
+ flags: string[],
99
+ options: ListDeployedOptions,
100
+ ): string[] => {
101
+ return prepareAwsFlags(flags, options)
102
+ }
103
+
104
+ ///////////////////////////////////////////////////////////////////////////////
105
+
96
106
  const parseTags = (lines: string[]): string[] => {
97
107
  const regex = /^\s*PRE\s*/
98
108
 
@@ -1,29 +1,53 @@
1
1
  import {getPortPromise} from 'portfinder'
2
- import {
3
- getIfCoreFns,
4
- getPaths,
5
- logCommand,
6
- logTitle,
7
- runCommandSync,
8
- } from 'lib/helpers'
2
+ import {getIfCoreFns, getPaths} from 'lib/resolve'
3
+ import {logCommand, logTitle} from 'lib/logging'
4
+ import {fail, runCommandSync} from 'lib/command'
9
5
 
10
6
  ///////////////////////////////////////////////////////////////////////////////
11
7
 
12
- const DEFAULT_PORT = 1234
8
+ export type ServeOptions = {
9
+ port: string
10
+ }
13
11
 
14
12
  ///////////////////////////////////////////////////////////////////////////////
15
13
 
16
- export const serve = async (): Promise<void> => {
14
+ export const serve = async (options: ServeOptions): Promise<void> => {
15
+ assertOptionsValid(options)
16
+
17
+ const flags = (await prepareFlags([], options)).join(' ')
17
18
  const {ifCore} = getIfCoreFns()
18
19
  const paths = getPaths(ifCore)
19
- const port = await getPortPromise({port: DEFAULT_PORT})
20
20
  const cmd = `
21
- npx -y http-server ${paths.appDist}
21
+ npx -y serve ${paths.appDist}
22
22
  --cors
23
- --port ${port}
23
+ ${flags}
24
24
  `
25
25
 
26
26
  logTitle(`Running web-server for pre-built bundle …`)
27
27
  logCommand(cmd)
28
28
  runCommandSync(cmd, {stdio: 'inherit'})
29
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('InterpolateHtmlPlugin', (compilation) => {
23
+ compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
20
24
  const hooks = HtmlWebpackPlugin.getHooks(compilation)
21
25
 
22
- hooks.alterAssetTagGroups.tap('InterpolateHtmlPlugin', (assets) => {
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('InterpolateHtmlPlugin', (data) => {
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 CHANGED
@@ -1,6 +1,6 @@
1
- import {commandSync, ExecaSyncReturnValue, SyncOptions} from 'execa'
1
+ import {execaCommandSync} from 'execa'
2
2
  import stripIndent from 'strip-indent'
3
- import {fail, prepareCommand} from './helpers'
3
+ import {fail} from 'lib/command'
4
4
 
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
6
 
@@ -10,47 +10,42 @@ export type AwsOptions = {
10
10
 
11
11
  ///////////////////////////////////////////////////////////////////////////////
12
12
 
13
- export const DEFAULT_AWS_PROFILE = 'payments-prod-cs-deployer'
13
+ export const withAwsAuthenticate = <Result = any>(
14
+ fn: () => Result,
15
+ options: AwsOptions,
16
+ ): Result => {
17
+ awsAuthenticate(options)
14
18
 
15
- ///////////////////////////////////////////////////////////////////////////////
16
-
17
- export const runAwsCommandSync = (
18
- command: string,
19
- options?: SyncOptions,
20
- ): ExecaSyncReturnValue => {
21
- authenticate()
22
-
23
- return commandSync(prepareCommand(command), options)
19
+ return fn()
24
20
  }
25
21
 
26
22
  ///////////////////////////////////////////////////////////////////////////////
27
23
 
28
- export const authenticate = () => {
29
- // If "AWS_ACCESS_KEY_ID" and "AWS_SECRET_ACCESS_KEY" environment variables are set,
30
- // then don't try to authenticate the user.
31
- // This may be useful for using this CLI in automated systems like pipelines.
24
+ export const awsAuthenticate = (options: AwsOptions) => {
32
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".
33
29
  return
34
30
  }
35
31
 
36
32
  try {
37
- checkIdentity()
33
+ checkIdentity(options)
38
34
  } catch (error) {
39
- login()
35
+ login(options)
40
36
  }
41
37
  }
42
38
 
43
39
  ///////////////////////////////////////////////////////////////////////////////
44
40
 
45
- const isSetEnv = () => {
41
+ // Returns true if "AWS_ACCESS_KEY_ID" and "AWS_SECRET_ACCESS_KEY" environment variables are set.
42
+ export const isSetEnv = () => {
46
43
  return !!(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY)
47
44
  }
48
45
 
49
46
  ///////////////////////////////////////////////////////////////////////////////
50
47
 
51
48
  export const assertProfileValid = (profile: string) => {
52
- // If "AWS_ACCESS_KEY_ID" and "AWS_SECRET_ACCESS_KEY" environment variables are set,
53
- // then ignore "--profile" flag.
54
49
  if (isSetEnv()) {
55
50
  return
56
51
  }
@@ -81,18 +76,12 @@ export const assertProfileValid = (profile: string) => {
81
76
 
82
77
  fail(message)
83
78
  }
84
-
85
- // Set it here, so we don't need to explicitly pass "--profile" flag
86
- // to all subsequent "aws" commands.
87
- // If "AWS_PROFILE" were set before, it will be overriden by "--profile" flag,
88
- // which has default value, so may be omitted.
89
- process.env.AWS_PROFILE = profile
90
79
  }
91
80
 
92
81
  ///////////////////////////////////////////////////////////////////////////////
93
82
 
94
83
  const getAvailableProfiles = (): string[] => {
95
- const {stdout} = commandSync('aws configure list-profiles')
84
+ const {stdout} = execaCommandSync('aws configure list-profiles')
96
85
 
97
86
  return stdout.split('\n')
98
87
  }
@@ -100,20 +89,39 @@ const getAvailableProfiles = (): string[] => {
100
89
  ///////////////////////////////////////////////////////////////////////////////
101
90
 
102
91
  // Returns caller identity data (output is ignored here).
103
- // Fails in case of expired sso session.
104
- const checkIdentity = () => {
105
- commandSync('aws sts get-caller-identity')
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}`)
106
97
  }
107
98
 
108
99
  ///////////////////////////////////////////////////////////////////////////////
109
100
 
110
- // Opens sso authentication page in the browser.
101
+ // Opens SSO authentication page in the browser.
111
102
  // Fails if user canceled authentication process.
112
- const login = () => {
103
+ const login = (options: AwsOptions) => {
104
+ const flags = prepareFlags([], options).join(' ')
105
+
113
106
  try {
114
- commandSync('aws sso login', {stdio: 'inherit'})
107
+ execaCommandSync(`aws sso login ${flags}`, {stdio: 'inherit'})
115
108
  console.log('')
116
109
  } catch (error) {
117
- fail('Login failed')
110
+ fail('Login failed.')
118
111
  }
119
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'
package/src/lib/env.ts ADDED
@@ -0,0 +1,145 @@
1
+ import {createRequire} from 'module'
2
+ import {basename} from 'path'
3
+ import glob from 'glob'
4
+ import type {IfUtilsFn} from 'webpack-config-utils'
5
+ import {expand as dotenvExpand} from 'dotenv-expand'
6
+ import {config as dotenvConfig} from 'dotenv'
7
+ import stripIndent from 'strip-indent'
8
+ import {getIfCoreFns, getPaths, getWidgetUuid, resolveApp} from 'lib/resolve'
9
+ import {fail} from 'lib/command'
10
+
11
+ ///////////////////////////////////////////////////////////////////////////////
12
+
13
+ export type EnvVars = {
14
+ [key: string]: string | undefined
15
+ }
16
+
17
+ ///////////////////////////////////////////////////////////////////////////////
18
+
19
+ const require = createRequire(import.meta.url)
20
+
21
+ ///////////////////////////////////////////////////////////////////////////////
22
+
23
+ export const getEnvVars = (
24
+ files: string[],
25
+ ifMin: IfUtilsFn,
26
+ ifCore: IfUtilsFn,
27
+ ifLocalCore: IfUtilsFn,
28
+ ) => {
29
+ const filePath = files.find((file) => basename(file) === '.env')
30
+
31
+ dotenvExpand(dotenvConfig({path: filePath}))
32
+
33
+ process.env.NODE_ENV ??= ifMin('production', 'development')
34
+ process.env.BABEL_ENV ??= ifMin('production', 'development')
35
+ process.env.EXPOSE_VAR ??= ifCore('rnw.tamaroCore', 'rnw.tamaro')
36
+ process.env.ELEMENT_ATTRIBUTE_DATA_WIDGET ??= ifCore(
37
+ 'rnw-tamaro-core',
38
+ 'rnw-tamaro',
39
+ )
40
+ process.env.WEBPACK_UNIQUE_NAME ??= ifCore('RnwTamaroCore', 'RnwTamaro')
41
+ process.env.BUILD_DATE = new Date().toISOString()
42
+
43
+ // HMR doesn't work in ie11, it breaks everything.
44
+ // If you want to run dev-server and test in ie11, set HMR_ENABLED to 'false'
45
+ process.env.HMR_ENABLED ??= ifMin('false', 'true')
46
+
47
+ if (ifCore()) {
48
+ process.env.PRODUCT_NAME = 'tamaro'
49
+
50
+ const corePkgPath = resolveApp('package.json')
51
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
52
+ const {version} = require(corePkgPath)
53
+
54
+ process.env.PRODUCT_VERSION = version
55
+ }
56
+
57
+ if (!ifCore()) {
58
+ process.env.WIDGET_UUID = getWidgetUuid()
59
+
60
+ if (ifLocalCore()) {
61
+ process.env.CORE_URL = `http://0.0.0.0:1234/index.js`
62
+ } else {
63
+ let version = process.env.CORE_VERSION
64
+ version = version ? `@${version}` : ''
65
+
66
+ process.env.CORE_URL ??= `https://cdn.jsdelivr.net/npm/@raisenow/tamaro-core${version}/dist/index.js`
67
+ }
68
+ }
69
+
70
+ // var names which will be exposed along with "PUBLIC_*" var names
71
+ const varNames = [
72
+ // common
73
+ 'NODE_ENV',
74
+ 'BABEL_ENV',
75
+ 'EXPOSE_VAR',
76
+ 'ELEMENT_ATTRIBUTE_DATA_WIDGET',
77
+ 'WEBPACK_UNIQUE_NAME',
78
+ 'BUILD_DATE',
79
+
80
+ // core
81
+ 'PRODUCT_NAME',
82
+ 'PRODUCT_VERSION',
83
+
84
+ // config
85
+ 'WIDGET_UUID',
86
+ 'CORE_URL',
87
+ 'CORE_VERSION', // must be explicitly specified in each config
88
+ 'IS_CREDIT_CARD_IFRAME',
89
+ ]
90
+
91
+ // Collect all vars which names start from "PUBLIC_" or present in "varNames" array
92
+ const raw = Object.keys(process.env)
93
+ .filter((key) => /^PUBLIC_/.test(key) || varNames.includes(key))
94
+ .reduce<EnvVars>((env, key) => {
95
+ env[key] = process.env[key]
96
+
97
+ return env
98
+ }, {})
99
+
100
+ // Stringify all values so we can feed into webpack DefinePlugin
101
+ const stringified = {
102
+ 'process.env': Object.keys(raw).reduce<EnvVars>((env, key) => {
103
+ env[key] = JSON.stringify(raw[key])
104
+
105
+ return env
106
+ }, {}),
107
+ }
108
+
109
+ return {raw, stringified}
110
+ }
111
+
112
+ ///////////////////////////////////////////////////////////////////////////////
113
+
114
+ export const assertEnvValid = (env: string) => {
115
+ const {ifCore} = getIfCoreFns()
116
+ const paths = getPaths(ifCore)
117
+ const files = glob.sync(paths.appEnv)
118
+ const envs = files
119
+ .map((file) => basename(file).replace(/^\.env\.?/, ''))
120
+ .filter((v) => !!v)
121
+
122
+ if (envs.length === 0) {
123
+ if (env) {
124
+ console.log('Flag “--env” is ignored.')
125
+ }
126
+ }
127
+
128
+ if (envs.length !== 0) {
129
+ if (!env) {
130
+ fail('Flag “--env” is required.')
131
+ }
132
+
133
+ if (!envs.includes(env)) {
134
+ fail(
135
+ stripIndent(`
136
+ Flag “--env” has wrong value.
137
+ Available values are: ${envs.map((v) => `“${v}”`).join(', ')}.
138
+ `),
139
+ )
140
+ }
141
+
142
+ const filePath = files.find((file) => basename(file) === `.env.${env}`)
143
+ dotenvExpand(dotenvConfig({path: filePath}))
144
+ }
145
+ }
@@ -0,0 +1,32 @@
1
+ import chalk from 'chalk'
2
+ import stripIndent from 'strip-indent'
3
+ import columnify from 'columnify'
4
+
5
+ ///////////////////////////////////////////////////////////////////////////////
6
+
7
+ export const logTitle = (title: string) => {
8
+ console.log(`${chalk.bold(title)}`)
9
+ }
10
+
11
+ ///////////////////////////////////////////////////////////////////////////////
12
+
13
+ export const logError = (message?: string) => {
14
+ if (message) {
15
+ console.log(chalk.red(message))
16
+ }
17
+ }
18
+
19
+ ///////////////////////////////////////////////////////////////////////////////
20
+
21
+ export const logCommand = (cmd: string): void => {
22
+ console.log(`\n${chalk.dim(stripIndent(cmd).trim())}\n`)
23
+ }
24
+
25
+ ///////////////////////////////////////////////////////////////////////////////
26
+
27
+ export const logDataTable = (data: {
28
+ [key: string]: string | undefined
29
+ }): void => {
30
+ console.log(columnify(data, {showHeaders: false}))
31
+ console.log('')
32
+ }
@@ -0,0 +1,22 @@
1
+ import notifier from 'node-notifier'
2
+ import stripIndent from 'strip-indent'
3
+
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+
6
+ export type NotifyArgs = {
7
+ title: string
8
+ message: string
9
+ }
10
+
11
+ ///////////////////////////////////////////////////////////////////////////////
12
+
13
+ export const notify = (args: NotifyArgs) => {
14
+ const {title, message = ''} = args
15
+
16
+ notifier.notify({
17
+ title,
18
+ message: stripIndent(message).trim(),
19
+ contentImage: 'https://assets.raisenow.io/favicon.png',
20
+ sound: 'Funk',
21
+ })
22
+ }