@raisenow/tamaro-cli 1.6.0-dev.1 → 1.6.0-dev.3
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/README.md +84 -0
- package/{.history/dist/chunk-FICM3HUI_20230912201610.js → dist/chunk-FI7KZAG6.js} +141 -45
- package/dist/cli.js +1436 -562
- package/dist/webpack.config.js +441 -310
- package/package.json +16 -4
- package/.history/.eslintignore_20220609190420 +0 -4
- package/.history/.eslintignore_20230911190227 +0 -6
- package/.history/.eslintignore_20230911211247 +0 -7
- package/.history/.eslintignore_20230911211250 +0 -7
- package/.history/.eslintrc_20230911201601.js +0 -49
- package/.history/.eslintrc_20230911201643.js +0 -49
- package/.history/.eslintrc_20230911201735.js +0 -49
- package/.history/.eslintrc_20230911201913.js +0 -64
- package/.history/.eslintrc_20230911202002.js +0 -62
- package/.history/.eslintrc_20230911202138.js +0 -133
- package/.history/.eslintrc_20230911202140.js +0 -133
- package/.history/.eslintrc_20230911202145.js +0 -133
- package/.history/.eslintrc_20230911202147.js +0 -133
- package/.history/.eslintrc_20230911202406.js +0 -133
- package/.history/.eslintrc_20230911202447.js +0 -133
- package/.history/.eslintrc_20230911202453.js +0 -133
- package/.history/.eslintrc_20230911202511.js +0 -133
- package/.history/.eslintrc_20230911202527.js +0 -133
- package/.history/.eslintrc_20230911202639.js +0 -125
- package/.history/.eslintrc_20230911202721.js +0 -149
- package/.history/.eslintrc_20230911202746.js +0 -149
- package/.history/.eslintrc_20240423125702.cjs +0 -148
- package/.history/.gitignore_20220609190420 +0 -14
- package/.history/.gitignore_20230911185959 +0 -4
- package/.history/.gitignore_20230911190006 +0 -4
- package/.history/.gitignore_20230911190007 +0 -4
- package/.history/.nmprc_20241004144025 +0 -0
- package/.history/.nmprc_20241004144041 +0 -1
- package/.history/.npmrc_20241004144040 +0 -1
- package/.history/.npmrc_20241004144152 +0 -1
- package/.history/.npmrc_20241004145639 +0 -2
- package/.history/.npmrc_20241004145649 +0 -1
- package/.history/.nvmrc_20250729181039 +0 -1
- package/.history/.prettierignore_20220609190420 +0 -5
- package/.history/.prettierignore_20230911190242 +0 -7
- package/.history/.prettierignore_20230911190245 +0 -7
- package/.history/.prettierignore_20230911211301 +0 -8
- package/.history/CHANGELOG_20250729195139.md +0 -28
- package/.history/CHANGELOG_20250729195148.md +0 -29
- package/.history/CHANGELOG_20250729195156.md +0 -28
- package/.history/CHANGELOG_20250729195218.md +0 -29
- package/.history/CHANGELOG_20250729195223.md +0 -27
- package/.history/CHANGELOG_20250729195506.md +0 -29
- package/.history/CHANGELOG_20250729224404.md +0 -30
- package/.history/CHANGELOG_20250729224411.md +0 -30
- package/.history/CHANGELOG_20250730124538.md +0 -41
- package/.history/CHANGELOG_20250730124603.md +0 -38
- package/.history/CHANGELOG_20250730124627.md +0 -36
- package/.history/CHANGELOG_20250730124638.md +0 -36
- package/.history/CHANGELOG_20250730124640.md +0 -36
- package/.history/README_20230911145033.md +0 -245
- package/.history/README_20230911151749.md +0 -245
- package/.history/README_20230911151844.md +0 -245
- package/.history/README_20230911153040.md +0 -245
- package/.history/README_20230911202959.md +0 -245
- package/.history/bitbucket-pipelines_20250729111540.yml +0 -46
- package/.history/bitbucket-pipelines_20250729210338.yml +0 -46
- package/.history/bitbucket-pipelines_20250729210340.yml +0 -46
- package/.history/bitbucket-pipelines_20250729210342.yml +0 -46
- package/.history/bitbucket-pipelines_20250729222234.yml +0 -47
- package/.history/bitbucket-pipelines_20250729222554.yml +0 -47
- package/.history/bitbucket-pipelines_20250729222555.yml +0 -47
- package/.history/bitbucket-pipelines_20250729223022.yml +0 -46
- package/.history/bitbucket-pipelines_20250729223024.yml +0 -46
- package/.history/cli_20241005014539 +0 -3
- package/.history/cli_20241005014542 +0 -3
- package/.history/cli_20241005014848 +0 -3
- package/.history/dist/chunk-FICM3HUI_20230912183408.js +0 -302
- package/.history/package_20250729182417.json +0 -111
- package/.history/package_20250729182419.json +0 -111
- package/.history/package_20250729182618.json +0 -110
- package/.history/package_20250729182834.json +0 -110
- package/.history/package_20250729183032.json +0 -110
- package/.history/package_20250729183033.json +0 -110
- package/.history/package_20250729185750.json +0 -110
- package/.history/package_20250729185815.json +0 -110
- package/.history/package_20250729185846.json +0 -110
- package/.history/package_20250729185854.json +0 -110
- package/.history/package_20250729185943.json +0 -110
- package/.history/package_20250729190645.json +0 -110
- package/.history/package_20250729195934.json +0 -110
- package/.history/package_20250729200013.json +0 -110
- package/.history/package_20250729200056.json +0 -110
- package/.history/package_20250729200434.json +0 -110
- package/.history/package_20250729200808.json +0 -110
- package/.history/package_20250729200944.json +0 -110
- package/.history/package_20250729214420.json +0 -114
- package/.history/package_20250729223112.json +0 -110
- package/.history/package_20250729223127.json +0 -110
- package/.history/package_20250729223134.json +0 -110
- package/.history/package_20250730124647.json +0 -114
- package/.history/tsconfig_20241005001442.json +0 -34
- package/.history/tsconfig_20241005001500.json +0 -34
- package/.history/tsconfig_20241005001540.json +0 -34
- package/.history/tsconfig_20241005014429.json +0 -42
- package/.history/tsconfig_20241005014436.json +0 -42
- package/.history/tsconfig_20241005014439.json +0 -38
- package/.history/tsconfig_20241005014455.json +0 -38
- package/.history/tsconfig_20241005014521.json +0 -38
- package/.history/vitest.config_20250729214539.ts +0 -0
- package/.history/vitest.config_20250729214659.ts +0 -24
- package/.history/vitest.config_20250729214748.ts +0 -21
- package/.history/vitest.config_20250729214825.ts +0 -21
- package/.history/vitest.config_20250729214902.ts +0 -21
- package/.history/vitest.config_20250729222125.ts +0 -20
- package/.history/vitest.config_20250729222213.ts +0 -20
- package/.idea/codeStyles/Project.xml +0 -131
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/inspectionProfiles/Project_Default.xml +0 -6
- package/.idea/misc.xml +0 -17
- package/.idea/modules.xml +0 -8
- package/.idea/prettier.xml +0 -6
- package/.idea/tamaro-cli.iml +0 -12
- package/.idea/vcs.xml +0 -6
- package/dist/env-Cpc7KHou.js +0 -245
- package/tsdown.config.ts +0 -11
package/dist/cli.js
CHANGED
|
@@ -1,315 +1,445 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AWS_CLOUDFRONT_DISTRIBUTION_ID,
|
|
4
|
+
AWS_S3_BUCKET_TAMARO,
|
|
5
|
+
AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD,
|
|
6
|
+
AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE,
|
|
7
|
+
CACHE_DIR,
|
|
8
|
+
CORE_CONFIG_NAME,
|
|
9
|
+
DEFAULT_PORT,
|
|
10
|
+
DEFAULT_TAG,
|
|
11
|
+
EPMS_API_BASE_URL_STAGE,
|
|
12
|
+
HTTPS_CRT_FILE,
|
|
13
|
+
HTTPS_KEY_FILE,
|
|
14
|
+
applyEnv,
|
|
15
|
+
assertEnvValid,
|
|
16
|
+
getIfCoreFns,
|
|
17
|
+
getPaths,
|
|
18
|
+
getWidgetUuid,
|
|
19
|
+
halt,
|
|
20
|
+
logCommand,
|
|
21
|
+
logDataTable,
|
|
22
|
+
logError,
|
|
23
|
+
logSuccess,
|
|
24
|
+
logTitle,
|
|
25
|
+
promptConfirmation,
|
|
26
|
+
resolveAccountUuidFromConfig,
|
|
27
|
+
resolveBin,
|
|
28
|
+
resolveOwn,
|
|
29
|
+
runCommandSync
|
|
30
|
+
} from "./chunk-FI7KZAG6.js";
|
|
31
|
+
|
|
32
|
+
// src/cli.ts
|
|
3
33
|
import { createCommand } from "commander";
|
|
4
|
-
import { globSync } from "glob";
|
|
5
|
-
import { execaCommandSync } from "execa";
|
|
6
|
-
import stripIndent from "strip-indent";
|
|
7
|
-
import fs from "node:fs";
|
|
8
|
-
import Handlebars from "handlebars";
|
|
9
|
-
import helpers from "handlebars-helpers";
|
|
10
|
-
import prompts from "prompts";
|
|
11
|
-
import { resolve } from "path";
|
|
12
|
-
import { existsSync } from "fs";
|
|
13
|
-
import notifier from "node-notifier";
|
|
14
|
-
import { getPortPromise } from "portfinder";
|
|
15
34
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* Make sure to update it if new helpers are added in the Email Service.
|
|
20
|
-
*/
|
|
21
|
-
helpers.comparison({ handlebars: Handlebars });
|
|
22
|
-
/**
|
|
23
|
-
* Recurse through the expected email templates folder and attempt to parse
|
|
24
|
-
* handlebars template to validate their syntax.
|
|
25
|
-
*/
|
|
26
|
-
const validateEmailTemplates = (templatePaths) => {
|
|
27
|
-
const errors = [];
|
|
28
|
-
for (const templatePath of templatePaths) try {
|
|
29
|
-
const template = fs.readFileSync(templatePath, "utf-8");
|
|
30
|
-
assureOnlySupportedHelpers(template);
|
|
31
|
-
compileEmailTemplate(template);
|
|
32
|
-
console.log(`${templatePath} is a valid email template`);
|
|
33
|
-
} catch (e) {
|
|
34
|
-
errors.push(`${templatePath} is invalid`);
|
|
35
|
-
errors.push(e);
|
|
36
|
-
}
|
|
37
|
-
return errors;
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* Validate that only supported Handlebars helpers are used in the template.
|
|
41
|
-
* This function extracts helpers from the template AST and checks them
|
|
42
|
-
* against a predefined set of supported helpers.
|
|
43
|
-
*/
|
|
44
|
-
const assureOnlySupportedHelpers = (template) => {
|
|
45
|
-
/**
|
|
46
|
-
* Recursively extract helpers from the AST
|
|
47
|
-
*/
|
|
48
|
-
const extractHelpers = (ast) => {
|
|
49
|
-
const usedHelpers = /* @__PURE__ */ new Set();
|
|
50
|
-
const extractHelperName = (node) => {
|
|
51
|
-
if (node.path?.type === "PathExpression" && (node.params?.length > 0 || node.hash?.pairs?.length > 0)) return node.path?.original;
|
|
52
|
-
};
|
|
53
|
-
const visit = (node) => {
|
|
54
|
-
if (!node) return;
|
|
55
|
-
switch (node.type) {
|
|
56
|
-
case "MustacheStatement":
|
|
57
|
-
case "BlockStatement": {
|
|
58
|
-
const name = extractHelperName(node);
|
|
59
|
-
if (name) usedHelpers.add(name);
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Recursively visit children of the AST node.
|
|
65
|
-
*/
|
|
66
|
-
for (const key in node) {
|
|
67
|
-
const value = node[key];
|
|
68
|
-
if (Array.isArray(value)) value.forEach(visit);
|
|
69
|
-
else if (value && typeof value === "object" && value.type) visit(value);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
visit(ast);
|
|
73
|
-
return usedHelpers;
|
|
74
|
-
};
|
|
75
|
-
const usedHelpers = extractHelpers(Handlebars.parse(template));
|
|
76
|
-
const supportedHelpers = new Set([
|
|
77
|
-
"if",
|
|
78
|
-
"unless",
|
|
79
|
-
"each",
|
|
80
|
-
"with",
|
|
81
|
-
"log",
|
|
82
|
-
"lookup",
|
|
83
|
-
"and",
|
|
84
|
-
"compare",
|
|
85
|
-
"contains",
|
|
86
|
-
"default",
|
|
87
|
-
"eq",
|
|
88
|
-
"gt",
|
|
89
|
-
"gte",
|
|
90
|
-
"has",
|
|
91
|
-
"isFalsey",
|
|
92
|
-
"isTruthy",
|
|
93
|
-
"ifEven",
|
|
94
|
-
"ifNth",
|
|
95
|
-
"ifOdd",
|
|
96
|
-
"is",
|
|
97
|
-
"isnt",
|
|
98
|
-
"lt",
|
|
99
|
-
"lte",
|
|
100
|
-
"neither",
|
|
101
|
-
"not",
|
|
102
|
-
"or",
|
|
103
|
-
"unlessEq",
|
|
104
|
-
"unlessGt",
|
|
105
|
-
"unlessLt",
|
|
106
|
-
"unlessGteq",
|
|
107
|
-
"unlessLteq"
|
|
108
|
-
]);
|
|
109
|
-
const unsupportedHelpers = Array.from(usedHelpers).filter((h) => !supportedHelpers.has(h));
|
|
110
|
-
if (unsupportedHelpers.length > 0) throw new Error(`Email template uses unsupported Handlebars helpers: ${unsupportedHelpers.join(", ")}`);
|
|
111
|
-
};
|
|
112
|
-
/**
|
|
113
|
-
* Compile Email template and pass an empty context to validate syntax.
|
|
114
|
-
*/
|
|
115
|
-
const compileEmailTemplate = (template) => {
|
|
116
|
-
Handlebars.compile(template)({});
|
|
117
|
-
};
|
|
35
|
+
// src/commands/archive.ts
|
|
36
|
+
import { existsSync, mkdirSync, renameSync } from "fs";
|
|
37
|
+
import { basename, dirname, resolve } from "path";
|
|
118
38
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
39
|
+
// src/lib/aws.ts
|
|
40
|
+
import { execaCommandSync } from "execa";
|
|
41
|
+
import prompts from "prompts";
|
|
42
|
+
import stripIndent from "strip-indent";
|
|
43
|
+
var awsAuthenticate = (options) => {
|
|
44
|
+
if (options.ci) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
checkIdentity(options);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
login(options);
|
|
51
|
+
}
|
|
132
52
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Don't try to authenticate the user.
|
|
140
|
-
* This is done when CLI is running within an automated system like pipeline,
|
|
141
|
-
* In this case "AWS_ACCESS_KEY_ID" and "AWS_SECRET_ACCESS_KEY" are used instead of "AWS_PROFILE".
|
|
142
|
-
*/
|
|
143
|
-
return;
|
|
144
|
-
try {
|
|
145
|
-
checkIdentity(options);
|
|
146
|
-
} catch (error) {
|
|
147
|
-
login(options);
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
const assertProfilesPresent = () => {
|
|
151
|
-
if (getAvailableProfiles().length === 0) halt(stripIndent(`
|
|
53
|
+
var assertProfilesPresent = () => {
|
|
54
|
+
const profiles = getAvailableProfiles();
|
|
55
|
+
if (profiles.length === 0) {
|
|
56
|
+
halt(
|
|
57
|
+
stripIndent(`
|
|
152
58
|
No AWS profiles found.
|
|
153
59
|
Run "aws configure sso" to set up SSO-enabled profile.
|
|
154
60
|
Check the wiki for more information:
|
|
155
61
|
https://raisenow.atlassian.net/wiki/x/lIrWvg
|
|
156
|
-
`)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (!profiles.includes(profile)) halt(stripIndent(`
|
|
62
|
+
`)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var getAvailableProfiles = () => {
|
|
67
|
+
const { stdout } = execaCommandSync("aws configure list-profiles");
|
|
68
|
+
return stdout.split("\n");
|
|
69
|
+
};
|
|
70
|
+
var checkIdentity = (options) => {
|
|
71
|
+
const flags = prepareFlags(options);
|
|
72
|
+
execaCommandSync(`aws sts get-caller-identity ${flags}`);
|
|
73
|
+
};
|
|
74
|
+
var login = (options) => {
|
|
75
|
+
const flags = prepareFlags(options);
|
|
76
|
+
try {
|
|
77
|
+
execaCommandSync(`aws sso login ${flags}`, { stdio: "inherit" });
|
|
78
|
+
console.log("");
|
|
79
|
+
} catch (error) {
|
|
80
|
+
halt("Login failed.");
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var prepareFlags = (options) => {
|
|
84
|
+
const flags = [];
|
|
85
|
+
const { profile } = options;
|
|
86
|
+
if (profile) {
|
|
87
|
+
flags.push(`--profile ${profile}`);
|
|
88
|
+
}
|
|
89
|
+
return flags.join(" ");
|
|
90
|
+
};
|
|
91
|
+
var assertProfileValid = (profile) => {
|
|
92
|
+
const profiles = getAvailableProfiles();
|
|
93
|
+
if (!profiles.includes(profile)) {
|
|
94
|
+
halt(
|
|
95
|
+
stripIndent(`
|
|
191
96
|
AWS profile "${profile}" is not found.
|
|
192
97
|
Available profiles are: ${profiles.map((v) => `"${v}"`).join(", ")}.
|
|
193
|
-
`)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
98
|
+
`)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var promptProfile = async () => {
|
|
103
|
+
const profiles = getAvailableProfiles().filter((v) => !!v).map((v) => ({ title: v, value: v }));
|
|
104
|
+
const { profile } = await prompts(
|
|
105
|
+
[
|
|
106
|
+
{
|
|
107
|
+
type: "select",
|
|
108
|
+
name: "profile",
|
|
109
|
+
message: "Select AWS profile",
|
|
110
|
+
choices: profiles
|
|
111
|
+
}
|
|
112
|
+
],
|
|
113
|
+
{
|
|
114
|
+
onCancel: () => halt()
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
return profile;
|
|
206
118
|
};
|
|
207
119
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
120
|
+
// src/lib/notifier.ts
|
|
121
|
+
import notifier from "node-notifier";
|
|
122
|
+
import stripIndent2 from "strip-indent";
|
|
123
|
+
var notify = (args) => {
|
|
124
|
+
const { title, message = "" } = args;
|
|
125
|
+
notifier.notify({
|
|
126
|
+
title,
|
|
127
|
+
message: stripIndent2(message).trim(),
|
|
128
|
+
contentImage: "https://assets.raisenow.io/favicon.png",
|
|
129
|
+
sound: "Funk"
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// src/commands/archive.ts
|
|
134
|
+
var archive = (options) => {
|
|
135
|
+
const { ifCore } = getIfCoreFns();
|
|
136
|
+
assertIsNotCore(ifCore);
|
|
137
|
+
assertIsNotArchived();
|
|
138
|
+
const configName = getWidgetUuid();
|
|
139
|
+
const cwd = process.cwd();
|
|
140
|
+
const parentDir = dirname(cwd);
|
|
141
|
+
const archiveDir = resolve(parentDir, "_archived");
|
|
142
|
+
const archiveTarget = resolve(archiveDir, configName);
|
|
143
|
+
assertArchiveTargetDoesNotExist(archiveTarget);
|
|
144
|
+
assertOptionsValid(options);
|
|
145
|
+
if (options.dryrun) {
|
|
146
|
+
logTitle("\u{1F9EA} DRY RUN MODE - No actual changes will be made \u{1F9EA}");
|
|
147
|
+
}
|
|
148
|
+
if (!options.dryrun) {
|
|
149
|
+
if (!existsSync(archiveDir)) {
|
|
150
|
+
mkdirSync(archiveDir, { recursive: true });
|
|
151
|
+
}
|
|
152
|
+
renameSync(cwd, archiveTarget);
|
|
153
|
+
} else {
|
|
154
|
+
logTitle(`Would move ${cwd} \u2192 ${archiveTarget}`);
|
|
155
|
+
}
|
|
156
|
+
logSuccess(`
|
|
157
|
+
\u2705 "${configName}" has been archived and moved to ${archiveTarget}`);
|
|
158
|
+
process.on("exit", () => {
|
|
159
|
+
notify({
|
|
160
|
+
title: "archive",
|
|
161
|
+
message: `"${configName}" has been archived.`
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
var assertOptionsValid = (options) => {
|
|
166
|
+
const { profile } = options;
|
|
167
|
+
if (profile) {
|
|
168
|
+
assertProfileValid(profile);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
var assertIsNotCore = (ifCore) => {
|
|
172
|
+
if (ifCore()) {
|
|
173
|
+
halt("You cannot archive Tamaro Core.");
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
var assertIsNotArchived = () => {
|
|
177
|
+
const parentDirName = basename(dirname(process.cwd()));
|
|
178
|
+
if (parentDirName === "_archived") {
|
|
179
|
+
halt("This configuration is already archived.");
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
var assertArchiveTargetDoesNotExist = (archiveTarget) => {
|
|
183
|
+
if (existsSync(archiveTarget)) {
|
|
184
|
+
halt(
|
|
185
|
+
`Archive target already exists: ${archiveTarget}. Please remove it first.`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// src/commands/validate.ts
|
|
191
|
+
import { globSync } from "glob";
|
|
192
|
+
|
|
193
|
+
// src/lib/validators/validateEmailTemplates.ts
|
|
194
|
+
import fs from "fs";
|
|
195
|
+
import Handlebars from "handlebars";
|
|
196
|
+
import helpers from "handlebars-helpers";
|
|
197
|
+
helpers.comparison({
|
|
198
|
+
handlebars: Handlebars
|
|
199
|
+
});
|
|
200
|
+
var validateEmailTemplates = (templatePaths) => {
|
|
201
|
+
const errors = [];
|
|
202
|
+
for (const templatePath of templatePaths) {
|
|
203
|
+
try {
|
|
204
|
+
const template = fs.readFileSync(templatePath, "utf-8");
|
|
205
|
+
assureOnlySupportedHelpers(template);
|
|
206
|
+
compileEmailTemplate(template);
|
|
207
|
+
console.log(`${templatePath} is a valid email template`);
|
|
208
|
+
} catch (e) {
|
|
209
|
+
errors.push(`${templatePath} is invalid`);
|
|
210
|
+
errors.push(e);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return errors;
|
|
214
|
+
};
|
|
215
|
+
var assureOnlySupportedHelpers = (template) => {
|
|
216
|
+
const extractHelpers = (ast2) => {
|
|
217
|
+
const usedHelpers2 = /* @__PURE__ */ new Set();
|
|
218
|
+
const extractHelperName = (node) => {
|
|
219
|
+
if (node.path?.type === "PathExpression" && (node.params?.length > 0 || node.hash?.pairs?.length > 0)) {
|
|
220
|
+
return node.path?.original;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const visit = (node) => {
|
|
224
|
+
if (!node) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
switch (node.type) {
|
|
228
|
+
case "MustacheStatement":
|
|
229
|
+
case "BlockStatement": {
|
|
230
|
+
const name = extractHelperName(node);
|
|
231
|
+
if (name) {
|
|
232
|
+
usedHelpers2.add(name);
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
for (const key in node) {
|
|
238
|
+
const value = node[key];
|
|
239
|
+
if (Array.isArray(value)) {
|
|
240
|
+
value.forEach(visit);
|
|
241
|
+
} else if (value && typeof value === "object" && value.type) {
|
|
242
|
+
visit(value);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
visit(ast2);
|
|
247
|
+
return usedHelpers2;
|
|
248
|
+
};
|
|
249
|
+
const ast = Handlebars.parse(template);
|
|
250
|
+
const usedHelpers = extractHelpers(ast);
|
|
251
|
+
const supportedHelpers = /* @__PURE__ */ new Set([
|
|
252
|
+
// Built-in Handlebars helpers
|
|
253
|
+
"if",
|
|
254
|
+
"unless",
|
|
255
|
+
"each",
|
|
256
|
+
"with",
|
|
257
|
+
"log",
|
|
258
|
+
"lookup",
|
|
259
|
+
// Comparison helpers, provided by "handlebars-helpers" lib
|
|
260
|
+
"and",
|
|
261
|
+
"compare",
|
|
262
|
+
"contains",
|
|
263
|
+
"default",
|
|
264
|
+
"eq",
|
|
265
|
+
"gt",
|
|
266
|
+
"gte",
|
|
267
|
+
"has",
|
|
268
|
+
"isFalsey",
|
|
269
|
+
"isTruthy",
|
|
270
|
+
"ifEven",
|
|
271
|
+
"ifNth",
|
|
272
|
+
"ifOdd",
|
|
273
|
+
"is",
|
|
274
|
+
"isnt",
|
|
275
|
+
"lt",
|
|
276
|
+
"lte",
|
|
277
|
+
"neither",
|
|
278
|
+
"not",
|
|
279
|
+
"or",
|
|
280
|
+
"unlessEq",
|
|
281
|
+
"unlessGt",
|
|
282
|
+
"unlessLt",
|
|
283
|
+
"unlessGteq",
|
|
284
|
+
"unlessLteq"
|
|
285
|
+
]);
|
|
286
|
+
const unsupportedHelpers = Array.from(usedHelpers).filter(
|
|
287
|
+
(h) => !supportedHelpers.has(h)
|
|
288
|
+
);
|
|
289
|
+
if (unsupportedHelpers.length > 0) {
|
|
290
|
+
throw new Error(
|
|
291
|
+
`Email template uses unsupported Handlebars helpers: ${unsupportedHelpers.join(", ")}`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
var compileEmailTemplate = (template) => {
|
|
296
|
+
const compiled = Handlebars.compile(template);
|
|
297
|
+
compiled({});
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// src/commands/validate.ts
|
|
301
|
+
var validate = async () => {
|
|
302
|
+
const errors = validateEmailTemplates(
|
|
303
|
+
globSync("email-config/templates/**/*.hbs").sort()
|
|
304
|
+
);
|
|
305
|
+
if (errors.length) {
|
|
306
|
+
halt(errors.map((el) => el.toString()).join("\n"));
|
|
307
|
+
}
|
|
308
|
+
logTitle("Validated successfully");
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// src/lib/https.ts
|
|
312
|
+
import { existsSync as existsSync2 } from "fs";
|
|
313
|
+
import { resolve as resolve2 } from "path";
|
|
314
|
+
import stripIndent3 from "strip-indent";
|
|
315
|
+
var assertCrtExists = () => {
|
|
316
|
+
const { ifCore } = getIfCoreFns();
|
|
317
|
+
const paths = getPaths(ifCore);
|
|
318
|
+
const certFile = resolve2(paths.root, HTTPS_CRT_FILE);
|
|
319
|
+
const keyFile = resolve2(paths.root, HTTPS_KEY_FILE);
|
|
320
|
+
if (existsSync2(certFile) && existsSync2(keyFile)) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
halt(
|
|
324
|
+
stripIndent3(`
|
|
217
325
|
Flag "--https" is used, but "${HTTPS_CRT_FILE}" and/or "${HTTPS_KEY_FILE}" files are not found.
|
|
218
326
|
You need to generate certificate first.
|
|
219
327
|
Check docs: https://unpkg.com/@raisenow/tamaro-cli/readme.html#/?id=use-transport-level-security-https
|
|
220
|
-
`)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
//#endregion
|
|
224
|
-
//#region src/lib/notifier.ts
|
|
225
|
-
const notify = (args) => {
|
|
226
|
-
const { title, message = "" } = args;
|
|
227
|
-
notifier.notify({
|
|
228
|
-
title,
|
|
229
|
-
message: stripIndent(message).trim(),
|
|
230
|
-
contentImage: "https://assets.raisenow.io/favicon.png",
|
|
231
|
-
sound: "Funk"
|
|
232
|
-
});
|
|
328
|
+
`)
|
|
329
|
+
);
|
|
233
330
|
};
|
|
234
331
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
332
|
+
// src/commands/build.ts
|
|
333
|
+
var build = async (options) => {
|
|
334
|
+
const { env } = options;
|
|
335
|
+
applyEnv(env);
|
|
336
|
+
if (options.deploy && !options.profile && !options.ci) {
|
|
337
|
+
assertProfilesPresent();
|
|
338
|
+
options.profile = await promptProfile();
|
|
339
|
+
}
|
|
340
|
+
assertOptionsValid2(options);
|
|
341
|
+
await validate();
|
|
342
|
+
const flags = prepareFlags2(options);
|
|
343
|
+
const { ifCore } = getIfCoreFns();
|
|
344
|
+
const paths = getPaths(ifCore);
|
|
345
|
+
const configName = ifCore(CORE_CONFIG_NAME, getWidgetUuid());
|
|
346
|
+
const title = ifCore(
|
|
347
|
+
"Building optimised (minified) bundle of Tamaro Core \u2026",
|
|
348
|
+
`Building optimised (minified) bundle of "${configName}" customer configuration \u2026`
|
|
349
|
+
);
|
|
350
|
+
const wpBin = resolveBin("webpack");
|
|
351
|
+
const wpConfig = resolveOwn("dist/webpack.config.js");
|
|
352
|
+
const cmd = `
|
|
353
|
+
${wpBin}
|
|
354
|
+
--config ${wpConfig}
|
|
254
355
|
--env min
|
|
255
356
|
${flags}
|
|
256
357
|
`;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
358
|
+
logTitle(title);
|
|
359
|
+
logCommand(cmd);
|
|
360
|
+
runCommandSync(cmd, { stdio: "inherit" });
|
|
361
|
+
logTitle(`
|
|
362
|
+
Bundle for "${configName}" customer configuration is done.`);
|
|
363
|
+
console.log(`Path: ${paths.appDist}
|
|
364
|
+
`);
|
|
365
|
+
process.on("exit", () => {
|
|
366
|
+
notify({
|
|
367
|
+
title: "build",
|
|
368
|
+
message: `Bundle for "${configName}" customer configuration is done.`
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
};
|
|
372
|
+
var assertOptionsValid2 = (options) => {
|
|
373
|
+
const { localCore, https, deploy: deploy2, env, profile } = options;
|
|
374
|
+
const { ifCore } = getIfCoreFns();
|
|
375
|
+
if (ifCore() && localCore) {
|
|
376
|
+
halt('Flag "--local-core" is redundant if running in Tamaro Core context.');
|
|
377
|
+
}
|
|
378
|
+
if (localCore && deploy2) {
|
|
379
|
+
halt('Flags "--local-core" and "--deploy" must not be used together.');
|
|
380
|
+
}
|
|
381
|
+
if (https) {
|
|
382
|
+
assertCrtExists();
|
|
383
|
+
}
|
|
384
|
+
if (profile) {
|
|
385
|
+
assertProfileValid(profile);
|
|
386
|
+
}
|
|
387
|
+
assertEnvValid(env);
|
|
388
|
+
};
|
|
389
|
+
var prepareFlags2 = (options) => {
|
|
390
|
+
const flags = [];
|
|
391
|
+
const { localCore, analyze, https, nolint } = options;
|
|
392
|
+
if (localCore) {
|
|
393
|
+
flags.push("--env localCore");
|
|
394
|
+
}
|
|
395
|
+
if (analyze) {
|
|
396
|
+
flags.push("--env analyze");
|
|
397
|
+
}
|
|
398
|
+
if (https) {
|
|
399
|
+
flags.push("--env https");
|
|
400
|
+
}
|
|
401
|
+
if (nolint) {
|
|
402
|
+
flags.push("--env nolint");
|
|
403
|
+
}
|
|
404
|
+
return flags.join(" ");
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// src/commands/deploy.ts
|
|
408
|
+
import { existsSync as existsSync3 } from "fs";
|
|
409
|
+
import stripIndent4 from "strip-indent";
|
|
410
|
+
|
|
411
|
+
// src/lib/validators/assertTagValid.ts
|
|
412
|
+
var assertTagValid = (tag) => {
|
|
413
|
+
const regex = /^[a-zA-Z0-9-_.]+$/;
|
|
414
|
+
if (!regex.test(tag)) {
|
|
415
|
+
halt(
|
|
416
|
+
`Flag "--tag" has forbidden format. Allowed format: ${regex.toString()}.`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
287
419
|
};
|
|
288
420
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
*/
|
|
312
|
-
const cmdSyncAll = `
|
|
421
|
+
// src/commands/deploy.ts
|
|
422
|
+
var deploy = async (options) => {
|
|
423
|
+
const { ifCore } = getIfCoreFns();
|
|
424
|
+
const paths = getPaths(ifCore);
|
|
425
|
+
assertDistPathExists(paths.appDist);
|
|
426
|
+
if (!options.profile && !options.ci) {
|
|
427
|
+
assertProfilesPresent();
|
|
428
|
+
options.profile = await promptProfile();
|
|
429
|
+
}
|
|
430
|
+
assertOptionsValid3(options);
|
|
431
|
+
await validate();
|
|
432
|
+
awsAuthenticate(options);
|
|
433
|
+
const flags = prepareFlags(options);
|
|
434
|
+
const { tag, dryrun } = options;
|
|
435
|
+
const dryRunFlag = dryrun ? `--dryrun` : "";
|
|
436
|
+
const configName = ifCore(CORE_CONFIG_NAME, getWidgetUuid());
|
|
437
|
+
const deployUrl = `s3://${AWS_S3_BUCKET_TAMARO}/${configName}/${tag}`;
|
|
438
|
+
const entryFilename = ifCore("index.js", "widget.js");
|
|
439
|
+
if (dryRunFlag) {
|
|
440
|
+
logTitle("\u{1F9EA} DRY RUN MODE - No actual changes will be made \u{1F9EA}");
|
|
441
|
+
}
|
|
442
|
+
const cmdSyncAll = `
|
|
313
443
|
aws s3 sync ${paths.appDist} ${deployUrl}
|
|
314
444
|
--acl public-read
|
|
315
445
|
--exclude ${paths.appDist}/${entryFilename}
|
|
@@ -317,330 +447,1074 @@ const deploy = async (options) => {
|
|
|
317
447
|
${flags}
|
|
318
448
|
${dryRunFlag}
|
|
319
449
|
`;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
* Copy entrypoint
|
|
329
|
-
*/
|
|
330
|
-
const cmdCpEntry = `
|
|
450
|
+
logTitle("Deploying to AWS S3 \u2026");
|
|
451
|
+
logCommand(cmdSyncAll);
|
|
452
|
+
try {
|
|
453
|
+
runCommandSync(cmdSyncAll, { stdout: "inherit" });
|
|
454
|
+
} catch (error) {
|
|
455
|
+
halt(error.stderr);
|
|
456
|
+
}
|
|
457
|
+
const cmdCpEntry = `
|
|
331
458
|
aws s3 cp ${paths.appDist}/${entryFilename} ${deployUrl}/${entryFilename}
|
|
332
459
|
--acl public-read
|
|
333
460
|
--cache-control max-age=64800
|
|
334
461
|
${flags}
|
|
335
462
|
${dryRunFlag}
|
|
336
463
|
`;
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
* Invalidate cloudfront cache
|
|
346
|
-
*/
|
|
347
|
-
const cmdInvalidateCache = `
|
|
464
|
+
logCommand(cmdCpEntry);
|
|
465
|
+
try {
|
|
466
|
+
runCommandSync(cmdCpEntry, { stdout: "inherit" });
|
|
467
|
+
} catch (error) {
|
|
468
|
+
halt(error.stderr);
|
|
469
|
+
}
|
|
470
|
+
if (!dryRunFlag) {
|
|
471
|
+
const cmdInvalidateCache = `
|
|
348
472
|
aws cloudfront create-invalidation
|
|
349
473
|
--distribution-id ${AWS_CLOUDFRONT_DISTRIBUTION_ID}
|
|
350
474
|
--paths /${configName}/${tag}/*
|
|
351
475
|
${flags}
|
|
352
476
|
`;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
477
|
+
logTitle("\nInvalidating edge cache \u2026");
|
|
478
|
+
logCommand(cmdInvalidateCache);
|
|
479
|
+
try {
|
|
480
|
+
runCommandSync(cmdInvalidateCache);
|
|
481
|
+
} catch (error) {
|
|
482
|
+
halt(error.stderr);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const demoPage = `https://${AWS_S3_BUCKET_TAMARO}/${configName}/${tag}/index.html`;
|
|
486
|
+
const entryPoint = `https://${AWS_S3_BUCKET_TAMARO}/${configName}/${tag}/${entryFilename}`;
|
|
487
|
+
logTitle(`
|
|
488
|
+
Bundle for "${configName}" is deployed with tag "${tag}".`);
|
|
489
|
+
logDataTable({
|
|
490
|
+
"Demo page:": demoPage,
|
|
491
|
+
"Entry point:": entryPoint
|
|
492
|
+
});
|
|
493
|
+
process.on("exit", () => {
|
|
494
|
+
notify({
|
|
495
|
+
title: "deploy",
|
|
496
|
+
message: `
|
|
372
497
|
Bundle for "${configName}" is deployed with tag "${tag}".
|
|
373
498
|
Demo page: ${demoPage}
|
|
374
499
|
Entry point: ${entryPoint}
|
|
375
500
|
`
|
|
376
|
-
|
|
377
|
-
|
|
501
|
+
});
|
|
502
|
+
});
|
|
378
503
|
};
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
504
|
+
var assertOptionsValid3 = (options) => {
|
|
505
|
+
const { profile, tag } = options;
|
|
506
|
+
if (profile) {
|
|
507
|
+
assertProfileValid(profile);
|
|
508
|
+
}
|
|
509
|
+
assertTagValid(tag);
|
|
383
510
|
};
|
|
384
|
-
|
|
385
|
-
|
|
511
|
+
var assertDistPathExists = (distPath) => {
|
|
512
|
+
if (!existsSync3(distPath)) {
|
|
513
|
+
halt(
|
|
514
|
+
stripIndent4(`
|
|
386
515
|
Dist folder does not exists.
|
|
387
516
|
Make sure you have built the bundle before trying to deploy it.
|
|
388
|
-
`)
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const regex = /^[a-zA-Z0-9-_.]+$/;
|
|
392
|
-
if (!regex.test(tag)) halt(`Flag "--tag" has forbidden format. Allowed format: ${regex.toString()}.`);
|
|
517
|
+
`)
|
|
518
|
+
);
|
|
519
|
+
}
|
|
393
520
|
};
|
|
394
521
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
522
|
+
// src/commands/deploy-email-config.ts
|
|
523
|
+
import { existsSync as existsSync4 } from "fs";
|
|
524
|
+
import prompts2 from "prompts";
|
|
525
|
+
var deployEmailConfig = async (options) => {
|
|
526
|
+
const { ifCore } = getIfCoreFns();
|
|
527
|
+
const paths = getPaths(ifCore);
|
|
528
|
+
const emailConfigPath = `${paths.app}/email-config`;
|
|
529
|
+
assertIsNotCore2();
|
|
530
|
+
assertEmailConfigPathExists(emailConfigPath);
|
|
531
|
+
if (!options.profile && !options.ci) {
|
|
532
|
+
assertProfilesPresent();
|
|
533
|
+
options.profile = await promptProfile();
|
|
534
|
+
}
|
|
535
|
+
if (!options.bucket) {
|
|
536
|
+
options.bucket = options.ci ? AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD : await promptBucketTamaroEmailConfig();
|
|
537
|
+
}
|
|
538
|
+
assertOptionsValid4(options);
|
|
539
|
+
await validate();
|
|
540
|
+
awsAuthenticate(options);
|
|
541
|
+
const flags = prepareFlags(options);
|
|
542
|
+
const dryRunFlag = options.dryrun ? `--dryrun` : "";
|
|
543
|
+
const configName = getWidgetUuid();
|
|
544
|
+
const deployUrl = `s3://${options.bucket}/${configName}`;
|
|
545
|
+
if (dryRunFlag) {
|
|
546
|
+
logTitle("\u{1F9EA} DRY RUN MODE - No actual changes will be made \u{1F9EA}");
|
|
547
|
+
}
|
|
548
|
+
const cmd = `
|
|
416
549
|
aws s3 sync ${emailConfigPath} ${deployUrl}
|
|
417
550
|
--delete
|
|
418
551
|
--exact-timestamps
|
|
419
552
|
${flags}
|
|
420
553
|
${dryRunFlag}
|
|
421
554
|
`;
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
555
|
+
logTitle(
|
|
556
|
+
`Deploying email configuration for "${configName}" customer configuration to AWS S3 \u2026`
|
|
557
|
+
);
|
|
558
|
+
logCommand(cmd);
|
|
559
|
+
try {
|
|
560
|
+
runCommandSync(cmd, { stdout: "inherit" });
|
|
561
|
+
} catch (error) {
|
|
562
|
+
halt(error.stderr);
|
|
563
|
+
}
|
|
564
|
+
logTitle(
|
|
565
|
+
`Email configuration for "${configName}" customer configuration is deployed.`
|
|
566
|
+
);
|
|
567
|
+
logDataTable({ "Deploy URL:": deployUrl });
|
|
568
|
+
process.on("exit", () => {
|
|
569
|
+
notify({
|
|
570
|
+
title: "deploy-email-config",
|
|
571
|
+
message: `Email configuration for "${configName}" customer configuration is deployed.`
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
};
|
|
575
|
+
var assertOptionsValid4 = (options) => {
|
|
576
|
+
const { profile, bucket } = options;
|
|
577
|
+
if (profile) {
|
|
578
|
+
assertProfileValid(profile);
|
|
579
|
+
}
|
|
580
|
+
if (bucket) {
|
|
581
|
+
assertBucketValid(bucket);
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
var assertEmailConfigPathExists = (distPath) => {
|
|
585
|
+
if (!existsSync4(distPath)) {
|
|
586
|
+
halt("Email config folder does not exists. Nothing to deploy.");
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
var assertIsNotCore2 = () => {
|
|
590
|
+
const { ifCore } = getIfCoreFns();
|
|
591
|
+
if (ifCore()) {
|
|
592
|
+
halt("You cannot deploy widget email configuration for Tamaro Core.");
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
var assertBucketValid = (bucket) => {
|
|
596
|
+
const buckets = [
|
|
597
|
+
AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE,
|
|
598
|
+
AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD
|
|
599
|
+
];
|
|
600
|
+
if (!buckets.includes(bucket)) {
|
|
601
|
+
halt("Invalid bucket name.");
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
var promptBucketTamaroEmailConfig = async () => {
|
|
605
|
+
const buckets = [
|
|
606
|
+
{ title: "stage", value: AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE },
|
|
607
|
+
{ title: "prod", value: AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD }
|
|
608
|
+
];
|
|
609
|
+
const { bucket } = await prompts2(
|
|
610
|
+
[
|
|
611
|
+
{
|
|
612
|
+
type: "select",
|
|
613
|
+
name: "bucket",
|
|
614
|
+
message: "Select environment",
|
|
615
|
+
choices: buckets
|
|
616
|
+
}
|
|
617
|
+
],
|
|
618
|
+
{
|
|
619
|
+
onCancel: () => halt()
|
|
620
|
+
}
|
|
621
|
+
);
|
|
622
|
+
return bucket;
|
|
467
623
|
};
|
|
468
624
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
625
|
+
// src/commands/dev.ts
|
|
626
|
+
import { getPortPromise } from "portfinder";
|
|
627
|
+
var dev = async (options) => {
|
|
628
|
+
const { env } = options;
|
|
629
|
+
applyEnv(env);
|
|
630
|
+
assertOptionsValid5(options);
|
|
631
|
+
const flags = await prepareFlags3(options);
|
|
632
|
+
const { ifCore } = getIfCoreFns();
|
|
633
|
+
const title = ifCore(
|
|
634
|
+
"Running dev web-server for Tamaro Core \u2026",
|
|
635
|
+
"Running dev web-server for customer configuration \u2026"
|
|
636
|
+
);
|
|
637
|
+
const wpBin = resolveBin("webpack");
|
|
638
|
+
const wpConfig = resolveOwn("dist/webpack.config.js");
|
|
639
|
+
const cmd = `
|
|
640
|
+
${wpBin} serve
|
|
641
|
+
--config ${wpConfig}
|
|
481
642
|
${flags}
|
|
482
643
|
`;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
644
|
+
logTitle(title);
|
|
645
|
+
logCommand(cmd);
|
|
646
|
+
runCommandSync(cmd, { stdio: "inherit" });
|
|
647
|
+
};
|
|
648
|
+
var assertOptionsValid5 = (options) => {
|
|
649
|
+
const { localCore, https, port, env } = options;
|
|
650
|
+
const { ifCore } = getIfCoreFns();
|
|
651
|
+
if (ifCore() && localCore) {
|
|
652
|
+
halt('Flag "--local-core" is redundant if running in Tamaro Core context.');
|
|
653
|
+
}
|
|
654
|
+
if (isNaN(Number(port))) {
|
|
655
|
+
halt('Flag "--port" should be a number.');
|
|
656
|
+
}
|
|
657
|
+
if (https) {
|
|
658
|
+
assertCrtExists();
|
|
659
|
+
}
|
|
660
|
+
assertEnvValid(env);
|
|
661
|
+
};
|
|
662
|
+
var prepareFlags3 = async (options) => {
|
|
663
|
+
const flags = [];
|
|
664
|
+
const { localCore, port: defaultPort, https, nolint } = options;
|
|
665
|
+
const port = await getPortPromise({ port: Number(defaultPort) });
|
|
666
|
+
if (localCore) {
|
|
667
|
+
flags.push("--env localCore");
|
|
668
|
+
}
|
|
669
|
+
flags.push(`--port ${port}`);
|
|
670
|
+
if (https) {
|
|
671
|
+
flags.push("--env https");
|
|
672
|
+
}
|
|
673
|
+
if (nolint) {
|
|
674
|
+
flags.push("--env nolint");
|
|
675
|
+
}
|
|
676
|
+
return flags.join(" ");
|
|
505
677
|
};
|
|
506
678
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
};
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
679
|
+
// src/commands/list-deployed.ts
|
|
680
|
+
var listDeployed = async (options) => {
|
|
681
|
+
if (!options.profile && !options.ci) {
|
|
682
|
+
assertProfilesPresent();
|
|
683
|
+
options.profile = await promptProfile();
|
|
684
|
+
}
|
|
685
|
+
assertOptionsValid6(options);
|
|
686
|
+
awsAuthenticate(options);
|
|
687
|
+
const flags = prepareFlags(options);
|
|
688
|
+
const { config } = options;
|
|
689
|
+
const { ifCore } = getIfCoreFns();
|
|
690
|
+
const configName = config ?? ifCore(CORE_CONFIG_NAME, getWidgetUuid());
|
|
691
|
+
const title = !config && ifCore() ? `Listing deployments of Tamaro Core \u2026` : `Listing deployments of "${configName}" customer configuration \u2026`;
|
|
692
|
+
const deployUrl = `s3://${AWS_S3_BUCKET_TAMARO}/${configName}/`;
|
|
693
|
+
const cmd = `aws s3 ls ${deployUrl} ${flags}`;
|
|
694
|
+
let out = "";
|
|
695
|
+
logTitle(title);
|
|
696
|
+
logCommand(cmd);
|
|
697
|
+
try {
|
|
698
|
+
const result = runCommandSync(cmd);
|
|
699
|
+
out = result.stdout;
|
|
700
|
+
} catch (error) {
|
|
701
|
+
out = error.stdout;
|
|
702
|
+
if (error.stderr) {
|
|
703
|
+
halt(error.stderr);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const lines = out.split("\n");
|
|
707
|
+
const tags = parseTags(lines);
|
|
708
|
+
let text = "";
|
|
709
|
+
if (tags.length === 0) {
|
|
710
|
+
text = "No deployments found.";
|
|
711
|
+
} else {
|
|
712
|
+
text = tags.map((tag, idx) => {
|
|
713
|
+
return `${idx + 1}. https://${AWS_S3_BUCKET_TAMARO}/${configName}/${tag}/index.html`;
|
|
714
|
+
}).join("\n");
|
|
715
|
+
}
|
|
716
|
+
console.log(`${text}
|
|
717
|
+
`);
|
|
718
|
+
};
|
|
719
|
+
var assertOptionsValid6 = (options) => {
|
|
720
|
+
const { config, profile } = options;
|
|
721
|
+
assertConfigValid(config);
|
|
722
|
+
if (profile) {
|
|
723
|
+
assertProfileValid(profile);
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
var assertConfigValid = (config) => {
|
|
727
|
+
if (!config) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const regex = /^[a-zA-Z0-9-_]+$/;
|
|
731
|
+
if (!regex.test(config)) {
|
|
732
|
+
halt(
|
|
733
|
+
`Flag "--config" has forbidden format. Allowed format: ${regex.toString()}.`
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
var parseTags = (lines) => {
|
|
738
|
+
const regex = /^\s*PRE\s*/;
|
|
739
|
+
return lines.filter((line) => regex.test(line)).map((line) => line.replace(regex, "").replace(/\/$/, ""));
|
|
552
740
|
};
|
|
553
741
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
742
|
+
// src/commands/serve.ts
|
|
743
|
+
import { resolve as resolve3 } from "path";
|
|
744
|
+
import { getPortPromise as getPortPromise2 } from "portfinder";
|
|
745
|
+
var serve = async (options) => {
|
|
746
|
+
assertOptionsValid7(options);
|
|
747
|
+
const flags = await prepareFlags4(options);
|
|
748
|
+
const { ifCore } = getIfCoreFns();
|
|
749
|
+
const paths = getPaths(ifCore);
|
|
750
|
+
const cmd = `
|
|
751
|
+
npx -y serve ${paths.appDist}
|
|
562
752
|
--cors
|
|
563
753
|
${flags}
|
|
564
754
|
`;
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
755
|
+
logTitle(`Running web-server for pre-built bundle \u2026`);
|
|
756
|
+
logCommand(cmd);
|
|
757
|
+
runCommandSync(cmd, { stdio: "inherit" });
|
|
758
|
+
};
|
|
759
|
+
var assertOptionsValid7 = (options) => {
|
|
760
|
+
const { port, https } = options;
|
|
761
|
+
if (isNaN(Number(port))) {
|
|
762
|
+
halt('Flag "--port" should be a number.');
|
|
763
|
+
}
|
|
764
|
+
if (https) {
|
|
765
|
+
assertCrtExists();
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
var prepareFlags4 = async (options) => {
|
|
769
|
+
const flags = [];
|
|
770
|
+
const { port: defaultPort, https } = options;
|
|
771
|
+
const port = await getPortPromise2({ port: Number(defaultPort) });
|
|
772
|
+
flags.push(`-p ${port}`);
|
|
773
|
+
if (https) {
|
|
774
|
+
const { ifCore } = getIfCoreFns();
|
|
775
|
+
const paths = getPaths(ifCore);
|
|
776
|
+
const certFile = resolve3(paths.root, HTTPS_CRT_FILE);
|
|
777
|
+
const keyFile = resolve3(paths.root, HTTPS_KEY_FILE);
|
|
778
|
+
flags.push(`--ssl-cert ${certFile}`);
|
|
779
|
+
flags.push(`--ssl-key ${keyFile}`);
|
|
780
|
+
}
|
|
781
|
+
return flags.join(" ");
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
// src/commands/unarchive.ts
|
|
785
|
+
import { existsSync as existsSync5, renameSync as renameSync2 } from "fs";
|
|
786
|
+
import { basename as basename2, dirname as dirname2, resolve as resolve4 } from "path";
|
|
787
|
+
var unarchive = (options) => {
|
|
788
|
+
const { ifCore } = getIfCoreFns({ allowArchived: true });
|
|
789
|
+
assertIsNotCore3(ifCore);
|
|
790
|
+
assertIsArchived();
|
|
791
|
+
const configName = getWidgetUuid();
|
|
792
|
+
const cwd = process.cwd();
|
|
793
|
+
const archivedDir = dirname2(cwd);
|
|
794
|
+
const configsDir = dirname2(archivedDir);
|
|
795
|
+
const restoreTarget = resolve4(configsDir, configName);
|
|
796
|
+
assertRestoreTargetDoesNotExist(restoreTarget);
|
|
797
|
+
assertOptionsValid8(options);
|
|
798
|
+
if (options.dryrun) {
|
|
799
|
+
logTitle("\u{1F9EA} DRY RUN MODE - No actual changes will be made \u{1F9EA}");
|
|
800
|
+
}
|
|
801
|
+
if (!options.dryrun) {
|
|
802
|
+
renameSync2(cwd, restoreTarget);
|
|
803
|
+
process.chdir(restoreTarget);
|
|
804
|
+
} else {
|
|
805
|
+
logTitle(`Would move ${cwd} \u2192 ${restoreTarget}`);
|
|
806
|
+
}
|
|
807
|
+
logTitle(`\u2705 "${configName}" has been unarchived and moved to ${restoreTarget}`);
|
|
808
|
+
process.on("exit", () => {
|
|
809
|
+
notify({
|
|
810
|
+
title: "unarchive",
|
|
811
|
+
message: `"${configName}" has been unarchived.`
|
|
812
|
+
});
|
|
813
|
+
});
|
|
814
|
+
};
|
|
815
|
+
var assertOptionsValid8 = (options) => {
|
|
816
|
+
const { profile } = options;
|
|
817
|
+
if (profile) {
|
|
818
|
+
assertProfileValid(profile);
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
var assertIsArchived = () => {
|
|
822
|
+
const parentDirName = basename2(dirname2(process.cwd()));
|
|
823
|
+
if (parentDirName !== "_archived") {
|
|
824
|
+
halt(
|
|
825
|
+
"This configuration is not archived. Only archived configurations can be unarchived."
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
var assertIsNotCore3 = (ifCore) => {
|
|
830
|
+
if (ifCore()) {
|
|
831
|
+
halt("You cannot unarchive Tamaro Core.");
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
var assertRestoreTargetDoesNotExist = (restoreTarget) => {
|
|
835
|
+
if (existsSync5(restoreTarget)) {
|
|
836
|
+
halt(
|
|
837
|
+
`Restore target already exists: ${restoreTarget}. Please remove it first.`
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
// src/commands/undeploy.ts
|
|
843
|
+
var undeploy = async (options) => {
|
|
844
|
+
const { ifCore } = getIfCoreFns();
|
|
845
|
+
const configName = ifCore(CORE_CONFIG_NAME, getWidgetUuid());
|
|
846
|
+
if (!options.profile && !options.ci) {
|
|
847
|
+
assertProfilesPresent();
|
|
848
|
+
options.profile = await promptProfile();
|
|
849
|
+
}
|
|
850
|
+
assertOptionsValid9(options);
|
|
851
|
+
awsAuthenticate(options);
|
|
852
|
+
const flags = prepareFlags(options);
|
|
853
|
+
const { tag, all, dryrun } = options;
|
|
854
|
+
const dryRunFlag = dryrun ? `--dryrun` : "";
|
|
855
|
+
const deployUrl = all ? `s3://${AWS_S3_BUCKET_TAMARO}/${configName}/` : `s3://${AWS_S3_BUCKET_TAMARO}/${configName}/${tag}/`;
|
|
856
|
+
const description = all ? `all tags of "${configName}"` : `tag "${tag}" of "${configName}"`;
|
|
857
|
+
if (dryRunFlag) {
|
|
858
|
+
logTitle("\u{1F9EA} DRY RUN MODE - No actual changes will be made \u{1F9EA}");
|
|
859
|
+
}
|
|
860
|
+
await promptConfirmation(
|
|
861
|
+
`Are you sure you want to undeploy ${description}?`,
|
|
862
|
+
options.ci
|
|
863
|
+
);
|
|
864
|
+
const cmd = `
|
|
865
|
+
aws s3 rm ${deployUrl}
|
|
866
|
+
--recursive
|
|
867
|
+
${flags}
|
|
868
|
+
${dryRunFlag}
|
|
869
|
+
`;
|
|
870
|
+
logTitle(`Undeploying ${description} from AWS S3 \u2026`);
|
|
871
|
+
logCommand(cmd);
|
|
872
|
+
try {
|
|
873
|
+
runCommandSync(cmd, { stdout: "inherit" });
|
|
874
|
+
} catch (error) {
|
|
875
|
+
halt(error.stderr);
|
|
876
|
+
}
|
|
877
|
+
if (!dryRunFlag) {
|
|
878
|
+
const invalidationPath = all ? `/${configName}/*` : `/${configName}/${tag}/*`;
|
|
879
|
+
const cmdInvalidateCache = `
|
|
880
|
+
aws cloudfront create-invalidation
|
|
881
|
+
--distribution-id ${AWS_CLOUDFRONT_DISTRIBUTION_ID}
|
|
882
|
+
--paths ${invalidationPath}
|
|
883
|
+
${flags}
|
|
884
|
+
`;
|
|
885
|
+
logTitle("\nInvalidating edge cache \u2026");
|
|
886
|
+
logCommand(cmdInvalidateCache);
|
|
887
|
+
try {
|
|
888
|
+
runCommandSync(cmdInvalidateCache);
|
|
889
|
+
} catch (error) {
|
|
890
|
+
halt(error.stderr);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
logTitle(`
|
|
894
|
+
${description} has been undeployed.`);
|
|
895
|
+
logDataTable({ "Removed URL:": deployUrl });
|
|
896
|
+
process.on("exit", () => {
|
|
897
|
+
notify({
|
|
898
|
+
title: "undeploy",
|
|
899
|
+
message: `${description} has been undeployed.`
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
};
|
|
903
|
+
var assertOptionsValid9 = (options) => {
|
|
904
|
+
const { profile, tag, all } = options;
|
|
905
|
+
const { ifCore } = getIfCoreFns();
|
|
906
|
+
if (ifCore() && all) {
|
|
907
|
+
halt('Flag "--all" must not be used in Tamaro Core context.');
|
|
908
|
+
}
|
|
909
|
+
if (ifCore() && tag === DEFAULT_TAG) {
|
|
910
|
+
halt("You cannot undeploy the default tag for Tamaro Core.");
|
|
911
|
+
}
|
|
912
|
+
if (profile) {
|
|
913
|
+
assertProfileValid(profile);
|
|
914
|
+
}
|
|
915
|
+
if (all && tag) {
|
|
916
|
+
halt('Flags "--tag" and "--all" must not be used together.');
|
|
917
|
+
}
|
|
918
|
+
if (!all && tag) {
|
|
919
|
+
assertTagValid(tag);
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
// src/commands/undeploy-email-config.ts
|
|
924
|
+
import prompts3 from "prompts";
|
|
925
|
+
var undeployEmailConfig = async (options) => {
|
|
926
|
+
const { ifCore } = getIfCoreFns();
|
|
927
|
+
assertIsNotCore4(ifCore);
|
|
928
|
+
if (!options.profile && !options.ci) {
|
|
929
|
+
assertProfilesPresent();
|
|
930
|
+
options.profile = await promptProfile();
|
|
931
|
+
}
|
|
932
|
+
if (!options.bucket) {
|
|
933
|
+
options.bucket = options.ci ? AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD : await promptBucketTamaroEmailConfig2();
|
|
934
|
+
}
|
|
935
|
+
assertOptionsValid10(options);
|
|
936
|
+
awsAuthenticate(options);
|
|
937
|
+
const flags = prepareFlags(options);
|
|
938
|
+
const dryRunFlag = options.dryrun ? `--dryrun` : "";
|
|
939
|
+
const configName = getWidgetUuid();
|
|
940
|
+
const deployUrl = `s3://${options.bucket}/${configName}/`;
|
|
941
|
+
await promptConfirmation(
|
|
942
|
+
`Are you sure you want to undeploy the email configuration for "${configName}" from "${options.bucket}"?`,
|
|
943
|
+
options.ci
|
|
944
|
+
);
|
|
945
|
+
if (dryRunFlag) {
|
|
946
|
+
logTitle("\u{1F9EA} DRY RUN MODE - No actual changes will be made \u{1F9EA}");
|
|
947
|
+
}
|
|
948
|
+
const cmd = `
|
|
949
|
+
aws s3 rm ${deployUrl}
|
|
950
|
+
--recursive
|
|
951
|
+
${flags}
|
|
952
|
+
${dryRunFlag}
|
|
953
|
+
`;
|
|
954
|
+
logTitle(`Undeploying email configuration for "${configName}" from AWS S3 \u2026`);
|
|
955
|
+
logCommand(cmd);
|
|
956
|
+
try {
|
|
957
|
+
runCommandSync(cmd, { stdout: "inherit" });
|
|
958
|
+
} catch (error) {
|
|
959
|
+
halt(error.stderr);
|
|
960
|
+
}
|
|
961
|
+
logTitle(
|
|
962
|
+
`Email configuration for "${configName}" has been undeployed from "${options.bucket}".`
|
|
963
|
+
);
|
|
964
|
+
logDataTable({ "Removed URL:": deployUrl });
|
|
965
|
+
process.on("exit", () => {
|
|
966
|
+
notify({
|
|
967
|
+
title: "undeploy-email-config",
|
|
968
|
+
message: `Email configuration for "${configName}" has been undeployed.`
|
|
969
|
+
});
|
|
970
|
+
});
|
|
971
|
+
};
|
|
972
|
+
var assertOptionsValid10 = (options) => {
|
|
973
|
+
const { profile, bucket } = options;
|
|
974
|
+
if (profile) {
|
|
975
|
+
assertProfileValid(profile);
|
|
976
|
+
}
|
|
977
|
+
if (bucket) {
|
|
978
|
+
assertBucketValid2(bucket);
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
var assertIsNotCore4 = (ifCore) => {
|
|
982
|
+
if (ifCore()) {
|
|
983
|
+
halt("You cannot undeploy widget email configuration for Tamaro Core.");
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
var assertBucketValid2 = (bucket) => {
|
|
987
|
+
const buckets = [
|
|
988
|
+
AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE,
|
|
989
|
+
AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD
|
|
990
|
+
];
|
|
991
|
+
if (!buckets.includes(bucket)) {
|
|
992
|
+
halt("Invalid bucket name.");
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
var promptBucketTamaroEmailConfig2 = async () => {
|
|
996
|
+
const buckets = [
|
|
997
|
+
{ title: "stage", value: AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE },
|
|
998
|
+
{ title: "prod", value: AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD }
|
|
999
|
+
];
|
|
1000
|
+
const { bucket } = await prompts3(
|
|
1001
|
+
[
|
|
1002
|
+
{
|
|
1003
|
+
type: "select",
|
|
1004
|
+
name: "bucket",
|
|
1005
|
+
message: "Select environment",
|
|
1006
|
+
choices: buckets
|
|
1007
|
+
}
|
|
1008
|
+
],
|
|
1009
|
+
{
|
|
1010
|
+
onCancel: () => halt()
|
|
1011
|
+
}
|
|
1012
|
+
);
|
|
1013
|
+
return bucket;
|
|
588
1014
|
};
|
|
589
1015
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
1016
|
+
// src/commands/update-epms.ts
|
|
1017
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1018
|
+
import { basename as basename3 } from "path";
|
|
1019
|
+
import { config as dotenvConfig } from "dotenv";
|
|
1020
|
+
import { globSync as globSync2 } from "glob";
|
|
1021
|
+
import yaml2 from "js-yaml";
|
|
594
1022
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
1023
|
+
// src/lib/epms/assert.ts
|
|
1024
|
+
import { existsSync as existsSync6, readFileSync } from "fs";
|
|
1025
|
+
import yaml from "js-yaml";
|
|
1026
|
+
import stripIndent5 from "strip-indent";
|
|
1027
|
+
var assertEpmsCredentials = (configPath) => {
|
|
1028
|
+
if (!existsSync6(configPath)) {
|
|
1029
|
+
throw new Error(`Config file not found at path: ${configPath}`);
|
|
1030
|
+
}
|
|
1031
|
+
try {
|
|
1032
|
+
const configContent = yaml.load(readFileSync(configPath, "utf8"));
|
|
1033
|
+
assertEpmsCredentialsPresentStage(configContent);
|
|
1034
|
+
assertEpmsCredentialsPresentProd(configContent);
|
|
1035
|
+
} catch (e) {
|
|
1036
|
+
throw new Error(
|
|
1037
|
+
`Error parsing config file at path: ${configPath}. Details: ${e}`
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
var assertEpmsCredentialsPresentStage = (rawConfig) => {
|
|
1042
|
+
const accountsUuidsStage = resolveAccountUuidFromConfig(
|
|
1043
|
+
rawConfig,
|
|
1044
|
+
"epms_stage"
|
|
1045
|
+
);
|
|
1046
|
+
if (!accountsUuidsStage) {
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
if (!process.env.EPMS_CLIENT_ID_STAGE || !process.env.EPMS_CLIENT_SECRET_STAGE) {
|
|
1050
|
+
halt(
|
|
1051
|
+
stripIndent5(
|
|
1052
|
+
`EPMS credentials for stage environment are missing.
|
|
1053
|
+
- Please set EPMS_CLIENT_ID_STAGE and EPMS_CLIENT_SECRET_STAGE in your environment variables.
|
|
1054
|
+
- Alternatively, remove the "epms_stage" fields from your config.yml
|
|
1055
|
+
`
|
|
1056
|
+
)
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
var assertEpmsCredentialsPresentProd = (rawConfig) => {
|
|
1061
|
+
const accountsUuidsProd = resolveAccountUuidFromConfig(rawConfig, "epms");
|
|
1062
|
+
if (!accountsUuidsProd) {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
if (!process.env.EPMS_CLIENT_ID || !process.env.EPMS_CLIENT_SECRET) {
|
|
1066
|
+
halt(
|
|
1067
|
+
stripIndent5(
|
|
1068
|
+
`EPMS credentials for prod environment are missing.
|
|
1069
|
+
- Please set EPMS_CLIENT_ID and EPMS_CLIENT_SECRET in your environment variables.
|
|
1070
|
+
- Alternatively, remove the "epms" fields from your config.yml
|
|
1071
|
+
`
|
|
1072
|
+
)
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
// src/lib/epms/auth.ts
|
|
1078
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
1079
|
+
import { dirname as dirname3, join } from "path";
|
|
1080
|
+
import ky from "ky";
|
|
1081
|
+
var DEFAULT_TIMEOUT = 1e4;
|
|
1082
|
+
function getCacheFilePath() {
|
|
1083
|
+
return join(CACHE_DIR, "token.json");
|
|
1084
|
+
}
|
|
1085
|
+
function readCachedToken() {
|
|
1086
|
+
try {
|
|
1087
|
+
const filePath = getCacheFilePath();
|
|
1088
|
+
if (!existsSync7(filePath)) {
|
|
1089
|
+
return void 0;
|
|
1090
|
+
}
|
|
1091
|
+
const data = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
1092
|
+
if (typeof data.token === "string" && typeof data.expirationTime === "number") {
|
|
1093
|
+
return data;
|
|
1094
|
+
}
|
|
1095
|
+
} catch {
|
|
1096
|
+
}
|
|
1097
|
+
return void 0;
|
|
1098
|
+
}
|
|
1099
|
+
function writeCachedToken(cached) {
|
|
1100
|
+
try {
|
|
1101
|
+
const filePath = getCacheFilePath();
|
|
1102
|
+
mkdirSync2(dirname3(filePath), { recursive: true });
|
|
1103
|
+
writeFileSync(filePath, JSON.stringify(cached), { mode: 384 });
|
|
1104
|
+
} catch {
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
var createAuthorizer = (baseUrl, clientId, clientSecret) => {
|
|
1108
|
+
const apiUnauthorized = ky.extend({
|
|
1109
|
+
headers: {
|
|
1110
|
+
"Access-Control-Allow-Origin": "*"
|
|
1111
|
+
},
|
|
1112
|
+
prefixUrl: baseUrl,
|
|
1113
|
+
timeout: DEFAULT_TIMEOUT
|
|
1114
|
+
});
|
|
1115
|
+
let expirationTime = void 0;
|
|
1116
|
+
let token = void 0;
|
|
1117
|
+
const cached = readCachedToken();
|
|
1118
|
+
if (cached) {
|
|
1119
|
+
token = cached.token;
|
|
1120
|
+
expirationTime = cached.expirationTime;
|
|
1121
|
+
}
|
|
1122
|
+
const authorize = async () => {
|
|
1123
|
+
const isExpired = (expirationTime ?? 0) - Date.now() < 5 * 60 * 1e3;
|
|
1124
|
+
if (token && !isExpired) {
|
|
1125
|
+
return token;
|
|
1126
|
+
}
|
|
1127
|
+
const data = await apiUnauthorized.post("oauth2/token", {
|
|
1128
|
+
json: {
|
|
1129
|
+
client_id: clientId,
|
|
1130
|
+
client_secret: clientSecret,
|
|
1131
|
+
grant_type: "client_credentials"
|
|
1132
|
+
}
|
|
1133
|
+
}).json();
|
|
1134
|
+
token = `${data.token_type} ${data.access_token}`;
|
|
1135
|
+
expirationTime = Date.now() + data.expires_in * 1e3;
|
|
1136
|
+
writeCachedToken({ token, expirationTime });
|
|
1137
|
+
return token;
|
|
1138
|
+
};
|
|
1139
|
+
return apiUnauthorized.extend({
|
|
1140
|
+
hooks: {
|
|
1141
|
+
beforeRequest: [
|
|
1142
|
+
async (request) => {
|
|
1143
|
+
request.headers.set("Authorization", await authorize());
|
|
1144
|
+
}
|
|
1145
|
+
]
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
// src/lib/epms/client.ts
|
|
1151
|
+
var EpmsClient = class {
|
|
1152
|
+
#epmsClient;
|
|
1153
|
+
constructor(baseUrl, clientId, clientSecret) {
|
|
1154
|
+
this.#epmsClient = createAuthorizer(baseUrl, clientId, clientSecret);
|
|
1155
|
+
}
|
|
1156
|
+
async getOrganisationIdByAccountUuid(accountUuid) {
|
|
1157
|
+
const query = {
|
|
1158
|
+
query: {
|
|
1159
|
+
$and: [
|
|
1160
|
+
{ $term: { object_uuid: accountUuid } },
|
|
1161
|
+
{ $term: { object: "account" } }
|
|
1162
|
+
]
|
|
1163
|
+
},
|
|
1164
|
+
size: 1,
|
|
1165
|
+
from: 0
|
|
1166
|
+
};
|
|
1167
|
+
const response = await this.#epmsClient.post("search/events", {
|
|
1168
|
+
json: query
|
|
1169
|
+
}).json();
|
|
1170
|
+
if (response.hits.length === 0) {
|
|
1171
|
+
throw new Error(`No events found for account UUID: ${accountUuid}`);
|
|
1172
|
+
}
|
|
1173
|
+
const organisationId = response.hits[0].organisation_uuid;
|
|
1174
|
+
if (!organisationId) {
|
|
1175
|
+
throw new Error(
|
|
1176
|
+
`Organisation UUID not found for account UUID: ${accountUuid}`
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
return organisationId;
|
|
1180
|
+
}
|
|
1181
|
+
async updateTamaro(configName, tag, json) {
|
|
1182
|
+
return this.#epmsClient.put(`products/tamaro/${configName}/tags/${tag}`, {
|
|
1183
|
+
json
|
|
1184
|
+
}).json();
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
// src/commands/update-epms.ts
|
|
1189
|
+
var updateEpms = async (options) => {
|
|
1190
|
+
const { ifCore } = getIfCoreFns();
|
|
1191
|
+
assertIsNotCore5(ifCore);
|
|
1192
|
+
const paths = getPaths(ifCore);
|
|
1193
|
+
const { tag, dryrun } = options;
|
|
1194
|
+
const dryRunFlag = dryrun ? `--dryrun` : "";
|
|
1195
|
+
const configName = getWidgetUuid();
|
|
1196
|
+
const appEnvFiles = globSync2(paths.appEnv);
|
|
1197
|
+
const filePath = appEnvFiles.find((file) => basename3(file) === ".env");
|
|
1198
|
+
if (!filePath) {
|
|
1199
|
+
halt(
|
|
1200
|
+
'No ".env" file found. Please make sure to have a ".env" file with EPMS credentials in the root of your widget project.'
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
dotenvConfig({ path: filePath, quiet: true });
|
|
1204
|
+
if (!("configYml" in paths)) {
|
|
1205
|
+
halt("Fatal error, this should never happen");
|
|
1206
|
+
throw new Error();
|
|
1207
|
+
}
|
|
1208
|
+
if (dryRunFlag) {
|
|
1209
|
+
logTitle("\u{1F9EA} DRY RUN MODE - No actual changes will be made \u{1F9EA}");
|
|
1210
|
+
}
|
|
1211
|
+
assertTagValid(tag);
|
|
1212
|
+
assertEpmsCredentials(paths.configYml);
|
|
1213
|
+
const configContent = yaml2.load(readFileSync3(paths.configYml, "utf8"));
|
|
1214
|
+
const accountUuidStage = resolveAccountUuidFromConfig(
|
|
1215
|
+
configContent,
|
|
1216
|
+
"epms_stage"
|
|
1217
|
+
);
|
|
1218
|
+
if (dryrun) {
|
|
1219
|
+
logTitle(
|
|
1220
|
+
`[DRY RUN] Would update EPMS for "${configName}" with tag "${tag}"`
|
|
1221
|
+
);
|
|
1222
|
+
} else {
|
|
1223
|
+
if (accountUuidStage) {
|
|
1224
|
+
const epmsClient = new EpmsClient(
|
|
1225
|
+
EPMS_API_BASE_URL_STAGE,
|
|
1226
|
+
process.env.EPMS_CLIENT_ID_STAGE,
|
|
1227
|
+
process.env.EPMS_CLIENT_SECRET_STAGE
|
|
1228
|
+
);
|
|
1229
|
+
const organisationUuid = await epmsClient.getOrganisationIdByAccountUuid(accountUuidStage);
|
|
1230
|
+
logTitle("Updating EPMS with the following details:");
|
|
1231
|
+
const body = {
|
|
1232
|
+
account_uuid: accountUuidStage,
|
|
1233
|
+
organisation_uuid: organisationUuid,
|
|
1234
|
+
configured_version: process.env.CORE_VERSION ?? "unknown",
|
|
1235
|
+
read_only: false
|
|
1236
|
+
};
|
|
1237
|
+
logDataTable({
|
|
1238
|
+
name: configName,
|
|
1239
|
+
tag,
|
|
1240
|
+
...body
|
|
1241
|
+
});
|
|
1242
|
+
await epmsClient.updateTamaro(configName, tag, body);
|
|
1243
|
+
logSuccess("\u2705 EPMS Stage is updated.");
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
var assertIsNotCore5 = (ifCore) => {
|
|
1248
|
+
if (ifCore()) {
|
|
1249
|
+
halt("You cannot update EPMS in the context of Tamaro Core.");
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
// package.json
|
|
1254
|
+
var package_default = {
|
|
1255
|
+
name: "@raisenow/tamaro-cli",
|
|
1256
|
+
version: "1.6.0-dev.3",
|
|
1257
|
+
author: {
|
|
1258
|
+
name: "RaiseNow",
|
|
1259
|
+
email: "development@raisenow.com"
|
|
1260
|
+
},
|
|
1261
|
+
type: "module",
|
|
1262
|
+
bin: "dist/cli.js",
|
|
1263
|
+
packageManager: "pnpm@10.28.2",
|
|
1264
|
+
engines: {
|
|
1265
|
+
node: ">=22.22.0",
|
|
1266
|
+
pnpm: ">=10.28.2",
|
|
1267
|
+
npm: "please-use-pnpm",
|
|
1268
|
+
yarn: "please-use-pnpm"
|
|
1269
|
+
},
|
|
1270
|
+
scripts: {
|
|
1271
|
+
prepare: "pnpm build",
|
|
1272
|
+
typecheck: "tsc --noEmit",
|
|
1273
|
+
build: "tsup src/cli.ts src/webpack.config.ts --clean --shims --format esm",
|
|
1274
|
+
"build:watch": "pnpm build --watch --ignore-watch .history --ignore-watch .idea",
|
|
1275
|
+
dev: "pnpm build:watch",
|
|
1276
|
+
readme: "pnpx http-server . --cors -o /readme.html",
|
|
1277
|
+
lint: "DEBUG=eslint:cli-engine eslint .",
|
|
1278
|
+
"lint:fix": "DEBUG=eslint:cli-engine eslint --fix .",
|
|
1279
|
+
format: "prettier --write .",
|
|
1280
|
+
test: "vitest",
|
|
1281
|
+
"test:watch": "vitest --watch",
|
|
1282
|
+
"test:coverage": "vitest --coverage"
|
|
1283
|
+
},
|
|
1284
|
+
dependencies: {
|
|
1285
|
+
"@babel/cli": "^7.28.6",
|
|
1286
|
+
"@babel/core": "^7.29.0",
|
|
1287
|
+
"@babel/plugin-proposal-decorators": "^7.29.0",
|
|
1288
|
+
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
|
1289
|
+
"@babel/plugin-transform-runtime": "^7.29.0",
|
|
1290
|
+
"@babel/preset-env": "^7.29.0",
|
|
1291
|
+
"@babel/preset-react": "^7.28.5",
|
|
1292
|
+
"@babel/preset-typescript": "^7.28.5",
|
|
1293
|
+
"@babel/runtime-corejs3": "^7.29.0",
|
|
1294
|
+
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
|
|
1295
|
+
"@statoscope/webpack-plugin": "^5.29.0",
|
|
1296
|
+
"@types/columnify": "^1.5.4",
|
|
1297
|
+
"@types/handlebars-helpers": "^0.5.6",
|
|
1298
|
+
"@types/lodash": "^4.17.23",
|
|
1299
|
+
"@types/node": "^22.19.8",
|
|
1300
|
+
"@types/node-notifier": "^8.0.5",
|
|
1301
|
+
"@types/prompts": "^2.4.9",
|
|
1302
|
+
"@types/resolve-bin": "^0.4.3",
|
|
1303
|
+
"@types/webpack-config-utils": "^2.3.4",
|
|
1304
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
1305
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
1306
|
+
autoprefixer: "^10.4.24",
|
|
1307
|
+
"babel-loader": "^10.0.0",
|
|
1308
|
+
"babel-plugin-istanbul": "^7.0.1",
|
|
1309
|
+
"babel-plugin-lodash": "^3.3.4",
|
|
1310
|
+
chalk: "^5.6.2",
|
|
1311
|
+
"clean-webpack-plugin": "^4.0.0",
|
|
1312
|
+
columnify: "^1.6.0",
|
|
1313
|
+
commander: "^14.0.3",
|
|
1314
|
+
"copy-webpack-plugin": "^13.0.1",
|
|
1315
|
+
"core-js": "^3.48.0",
|
|
1316
|
+
"css-loader": "^7.1.3",
|
|
1317
|
+
"css-minimizer-webpack-plugin": "^7.0.4",
|
|
1318
|
+
dotenv: "^17.2.3",
|
|
1319
|
+
"env-paths": "^4.0.0",
|
|
1320
|
+
"escape-string-regexp": "^5.0.0",
|
|
1321
|
+
eslint: "^8.57.1",
|
|
1322
|
+
"eslint-config-prettier": "^10.1.8",
|
|
1323
|
+
"eslint-import-resolver-typescript": "^3.10.1",
|
|
1324
|
+
"eslint-plugin-deprecation": "^3.0.0",
|
|
1325
|
+
"eslint-plugin-import": "^2.32.0",
|
|
1326
|
+
"eslint-plugin-markdownlint": "^0.9.0",
|
|
1327
|
+
"eslint-plugin-promise": "^7.2.1",
|
|
1328
|
+
"eslint-plugin-unused-imports": "^3.2.0",
|
|
1329
|
+
"eslint-plugin-yml": "^1.19.1",
|
|
1330
|
+
"eslint-webpack-plugin": "^5.0.2",
|
|
1331
|
+
execa: "^9.6.1",
|
|
1332
|
+
"file-loader": "^6.2.0",
|
|
1333
|
+
"fork-ts-checker-webpack-plugin": "^9.1.0",
|
|
1334
|
+
glob: "^13.0.1",
|
|
1335
|
+
handlebars: "^4.7.8",
|
|
1336
|
+
"handlebars-helpers": "^0.10.0",
|
|
1337
|
+
"html-loader": "^5.1.0",
|
|
1338
|
+
"html-webpack-plugin": "^5.6.6",
|
|
1339
|
+
"js-yaml": "^4.1.1",
|
|
1340
|
+
"json-loader": "^0.5.7",
|
|
1341
|
+
ky: "^1.14.3",
|
|
1342
|
+
lodash: "^4.17.23",
|
|
1343
|
+
"mini-css-extract-plugin": "^2.10.0",
|
|
1344
|
+
"node-notifier": "^10.0.1",
|
|
1345
|
+
portfinder: "^1.0.38",
|
|
1346
|
+
postcss: "^8.5.6",
|
|
1347
|
+
"postcss-custom-properties": "^14.0.6",
|
|
1348
|
+
"postcss-import": "^16.1.1",
|
|
1349
|
+
"postcss-loader": "^8.2.0",
|
|
1350
|
+
"postcss-nested": "^7.0.2",
|
|
1351
|
+
prettier: "^3.8.1",
|
|
1352
|
+
prompts: "^2.4.2",
|
|
1353
|
+
"react-refresh": "^0.18.0",
|
|
1354
|
+
"resolve-url-loader": "^5.0.0",
|
|
1355
|
+
sass: "^1.97.3",
|
|
1356
|
+
"sass-loader": "^16.0.6",
|
|
1357
|
+
"strip-indent": "^4.1.1",
|
|
1358
|
+
"style-loader": "^4.0.0",
|
|
1359
|
+
"terser-webpack-plugin": "^5.3.16",
|
|
1360
|
+
"tsconfig-paths-webpack-plugin": "^4.2.0",
|
|
1361
|
+
tsup: "^8.5.1",
|
|
1362
|
+
typescript: "^5.9.3",
|
|
1363
|
+
"url-loader": "^4.1.1",
|
|
1364
|
+
vitest: "^4.0.18",
|
|
1365
|
+
webpack: "^5.105.0",
|
|
1366
|
+
"webpack-cli": "^6.0.1",
|
|
1367
|
+
"webpack-config-utils": "^2.3.1",
|
|
1368
|
+
"webpack-dev-server": "^5.2.3",
|
|
1369
|
+
"yaml-loader": "^0.9.0",
|
|
1370
|
+
zod: "^4.3.6"
|
|
1371
|
+
},
|
|
1372
|
+
devDependencies: {
|
|
1373
|
+
"@types/js-yaml": "^4.0.9"
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
// src/cli.ts
|
|
602
1378
|
process.on("unhandledRejection", (error) => {
|
|
603
|
-
|
|
1379
|
+
throw error;
|
|
604
1380
|
});
|
|
605
1381
|
process.on("SIGINT", () => {
|
|
606
|
-
|
|
1382
|
+
halt();
|
|
607
1383
|
});
|
|
608
|
-
|
|
609
|
-
cli.command("dev").description(
|
|
610
|
-
|
|
1384
|
+
var cli = createCommand().name(package_default.name).version(package_default.version, "-v, --version");
|
|
1385
|
+
cli.command("dev").description(
|
|
1386
|
+
"Run the local development server for Tamaro Core or a particular customer configuration"
|
|
1387
|
+
).option(
|
|
1388
|
+
"--local-core",
|
|
1389
|
+
"Load Tamaro Core from localhost:1234 instead of the CDN",
|
|
1390
|
+
false
|
|
1391
|
+
).option(
|
|
1392
|
+
"--https",
|
|
1393
|
+
"Let local web server serve Tamaro with SSL encryption",
|
|
1394
|
+
false
|
|
1395
|
+
).option("--port <port>", "Web server port", `${DEFAULT_PORT}`).option("--env <env>", "Environment (dev, stage, prod)").option("--nolint", "Disable ESLint", false).action(async (options) => {
|
|
1396
|
+
await dev(options);
|
|
611
1397
|
});
|
|
612
|
-
cli.command("build").description(
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
1398
|
+
cli.command("build").description(
|
|
1399
|
+
"Build an optimised (minified) bundle of Tamaro Core or a customer configuration"
|
|
1400
|
+
).option(
|
|
1401
|
+
"--local-core",
|
|
1402
|
+
"Load Tamaro Core from localhost:1234 instead of the CDN",
|
|
1403
|
+
false
|
|
1404
|
+
).option("--analyze", 'Generate bundle statistics to "reports" folder', false).option("--serve", "Run the generated bundle with a local web server", false).option(
|
|
1405
|
+
"--https",
|
|
1406
|
+
"Let local web server serve Tamaro with SSL encryption",
|
|
1407
|
+
false
|
|
1408
|
+
).option("--port <port>", "Web server port", `${DEFAULT_PORT}`).option("--env <env>", "Environment (dev, stage, prod)").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option(
|
|
1409
|
+
"--deploy",
|
|
1410
|
+
"Deploy Tamaro Core or a customer configuration bundle to AWS S3",
|
|
1411
|
+
false
|
|
1412
|
+
).option(
|
|
1413
|
+
"--tag <tag>",
|
|
1414
|
+
"Tag which should be used for the deployment of the bundle",
|
|
1415
|
+
DEFAULT_TAG
|
|
1416
|
+
).option("--nolint", "Disable ESLint", false).action(async (options) => {
|
|
1417
|
+
await build(options);
|
|
1418
|
+
if (options.serve) {
|
|
1419
|
+
await serve(options);
|
|
1420
|
+
}
|
|
1421
|
+
if (options.deploy) {
|
|
1422
|
+
await deploy(options);
|
|
1423
|
+
}
|
|
616
1424
|
});
|
|
617
|
-
cli.command("serve").description("Run a local web server for pre-built bundle").option("--port <port>", "Web server port", `${DEFAULT_PORT}`).option(
|
|
618
|
-
|
|
1425
|
+
cli.command("serve").description("Run a local web server for pre-built bundle").option("--port <port>", "Web server port", `${DEFAULT_PORT}`).option(
|
|
1426
|
+
"--https",
|
|
1427
|
+
"Let local web server serve Tamaro with SSL encryption",
|
|
1428
|
+
false
|
|
1429
|
+
).action(async (options) => {
|
|
1430
|
+
await serve(options);
|
|
619
1431
|
});
|
|
620
|
-
cli.command("deploy").description(
|
|
621
|
-
|
|
1432
|
+
cli.command("deploy").description(
|
|
1433
|
+
"Deploy a pre-built Tamaro Core or customer configuration bundle to AWS S3"
|
|
1434
|
+
).option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option(
|
|
1435
|
+
"--tag <tag>",
|
|
1436
|
+
"Tag which should be used for the deployment of the bundle",
|
|
1437
|
+
DEFAULT_TAG
|
|
1438
|
+
).option(
|
|
1439
|
+
"--dryrun",
|
|
1440
|
+
"Displays the operations that would be performed without actually running them",
|
|
1441
|
+
false
|
|
1442
|
+
).action(async (options) => {
|
|
1443
|
+
await deploy(options);
|
|
622
1444
|
});
|
|
623
|
-
cli.command("list-deployed").description(
|
|
624
|
-
|
|
1445
|
+
cli.command("list-deployed").description(
|
|
1446
|
+
"List deployments of Tamaro Core or a particular customer configuration"
|
|
1447
|
+
).option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option(
|
|
1448
|
+
"--config <config>",
|
|
1449
|
+
"Configuration name (default: current customer configuration)"
|
|
1450
|
+
).action(async (options) => {
|
|
1451
|
+
await listDeployed(options);
|
|
625
1452
|
});
|
|
626
|
-
cli.command("deploy-email-config").description("Deploy a widget's email configuration to AWS S3").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--bucket <bucket>", "AWS bucket for email configs").option(
|
|
627
|
-
|
|
1453
|
+
cli.command("deploy-email-config").description("Deploy a widget's email configuration to AWS S3").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--bucket <bucket>", "AWS bucket for email configs").option(
|
|
1454
|
+
"--dryrun",
|
|
1455
|
+
"Displays the operations that would be performed without actually running them",
|
|
1456
|
+
false
|
|
1457
|
+
).action(async (options) => {
|
|
1458
|
+
await deployEmailConfig(options);
|
|
628
1459
|
});
|
|
629
|
-
cli.command("
|
|
630
|
-
|
|
1460
|
+
cli.command("undeploy").description(
|
|
1461
|
+
"Undeploy a Tamaro Core or customer configuration bundle from AWS S3"
|
|
1462
|
+
).option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--tag <tag>", "Tag which should be undeployed", DEFAULT_TAG).option("--all", "Undeploy all tags", false).option(
|
|
1463
|
+
"--dryrun",
|
|
1464
|
+
"Displays the operations that would be performed without actually running them",
|
|
1465
|
+
false
|
|
1466
|
+
).action(async (options) => {
|
|
1467
|
+
await undeploy(options);
|
|
1468
|
+
});
|
|
1469
|
+
cli.command("undeploy-email-config").description("Undeploy a widget's email configuration from AWS S3").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--bucket <bucket>", "AWS bucket for email configs").option(
|
|
1470
|
+
"--dryrun",
|
|
1471
|
+
"Displays the operations that would be performed without actually running them",
|
|
1472
|
+
false
|
|
1473
|
+
).action(async (options) => {
|
|
1474
|
+
await undeployEmailConfig(options);
|
|
1475
|
+
});
|
|
1476
|
+
cli.command("archive").description(
|
|
1477
|
+
"Archive a customer configuration by moving it to the _archived folder"
|
|
1478
|
+
).option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option(
|
|
1479
|
+
"--dryrun",
|
|
1480
|
+
"Displays the operations that would be performed without actually running them",
|
|
1481
|
+
false
|
|
1482
|
+
).action((options) => {
|
|
1483
|
+
archive(options);
|
|
1484
|
+
});
|
|
1485
|
+
cli.command("unarchive").description(
|
|
1486
|
+
"Unarchive a customer configuration by restoring it from the _archived folder"
|
|
1487
|
+
).option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option(
|
|
1488
|
+
"--dryrun",
|
|
1489
|
+
"Displays the operations that would be performed without actually running them",
|
|
1490
|
+
false
|
|
1491
|
+
).action((options) => {
|
|
1492
|
+
unarchive(options);
|
|
1493
|
+
});
|
|
1494
|
+
cli.command("update-epms").description("Update EPMS with the currently configured widget version.").option("--tag <tag>", "Tag which should be used for the update", DEFAULT_TAG).option(
|
|
1495
|
+
"--dryrun",
|
|
1496
|
+
"Displays the operations that would be performed without actually running them",
|
|
1497
|
+
false
|
|
1498
|
+
).action(async (options) => {
|
|
1499
|
+
await updateEpms(options);
|
|
1500
|
+
});
|
|
1501
|
+
cli.command("validate").description(
|
|
1502
|
+
"Validate the current Tamaro configuration to avoid common errors"
|
|
1503
|
+
).action(async () => {
|
|
1504
|
+
await validate();
|
|
631
1505
|
});
|
|
632
1506
|
(async () => {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
1507
|
+
try {
|
|
1508
|
+
logTitle(`${package_default.name} ${package_default.version}
|
|
1509
|
+
`);
|
|
1510
|
+
await cli.parseAsync(process.argv);
|
|
1511
|
+
} catch (error) {
|
|
1512
|
+
if (error.signal === "SIGINT" && error.signalDescription) {
|
|
1513
|
+
logError(`
|
|
1514
|
+
${error.signalDescription}`);
|
|
1515
|
+
} else {
|
|
1516
|
+
console.log("");
|
|
1517
|
+
logError(error.stack);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
643
1520
|
})();
|
|
644
|
-
|
|
645
|
-
//#endregion
|
|
646
|
-
export { };
|