@raisenow/tamaro-cli 1.5.0 → 1.6.0-beta.0

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/dist/cli.js CHANGED
@@ -18,17 +18,415 @@ import {
18
18
  logCommand,
19
19
  logDataTable,
20
20
  logError,
21
+ logSuccess,
21
22
  logTitle,
22
23
  resolveBin,
23
24
  resolveOwn,
24
25
  runCommandSync
25
- } from "./chunk-HGPQTHLY.js";
26
+ } from "./chunk-MCUAYEGK.js";
26
27
 
27
28
  // src/cli.ts
28
29
  import { createCommand } from "commander";
29
30
 
31
+ // src/commands/archive.ts
32
+ import { existsSync, mkdirSync, renameSync } from "fs";
33
+ import { basename, dirname, resolve } from "path";
34
+ import chalk from "chalk";
35
+
36
+ // src/lib/aws.ts
37
+ import { execaCommandSync } from "execa";
38
+ import prompts from "prompts";
39
+ import stripIndent from "strip-indent";
40
+ var awsAuthenticate = (options) => {
41
+ if (options.ci) {
42
+ return;
43
+ }
44
+ try {
45
+ checkIdentity(options);
46
+ } catch (error) {
47
+ login(options);
48
+ }
49
+ };
50
+ var assertProfilesPresent = () => {
51
+ const profiles = getAvailableProfiles();
52
+ if (profiles.length === 0) {
53
+ halt(
54
+ stripIndent(`
55
+ No AWS profiles found.
56
+ Run "aws configure sso" to set up SSO-enabled profile.
57
+ Check the wiki for more information:
58
+ https://raisenow.atlassian.net/wiki/x/lIrWvg
59
+ `)
60
+ );
61
+ }
62
+ };
63
+ var getAvailableProfiles = () => {
64
+ const { stdout } = execaCommandSync("aws configure list-profiles");
65
+ return stdout.split("\n");
66
+ };
67
+ var checkIdentity = (options) => {
68
+ const flags = prepareFlags(options);
69
+ execaCommandSync(`aws sts get-caller-identity ${flags}`);
70
+ };
71
+ var login = (options) => {
72
+ const flags = prepareFlags(options);
73
+ try {
74
+ execaCommandSync(`aws sso login ${flags}`, { stdio: "inherit" });
75
+ console.log("");
76
+ } catch (error) {
77
+ halt("Login failed.");
78
+ }
79
+ };
80
+ var prepareFlags = (options) => {
81
+ const flags = [];
82
+ const { profile } = options;
83
+ if (profile) {
84
+ flags.push(`--profile ${profile}`);
85
+ }
86
+ return flags.join(" ");
87
+ };
88
+ var assertProfileValid = (profile) => {
89
+ const profiles = getAvailableProfiles();
90
+ if (!profiles.includes(profile)) {
91
+ halt(
92
+ stripIndent(`
93
+ AWS profile "${profile}" is not found.
94
+ Available profiles are: ${profiles.map((v) => `"${v}"`).join(", ")}.
95
+ `)
96
+ );
97
+ }
98
+ };
99
+ var promptProfile = async () => {
100
+ const profiles = getAvailableProfiles().filter((v) => !!v).map((v) => ({ title: v, value: v }));
101
+ const { profile } = await prompts(
102
+ [
103
+ {
104
+ type: "select",
105
+ name: "profile",
106
+ message: "Select AWS profile",
107
+ choices: profiles
108
+ }
109
+ ],
110
+ {
111
+ onCancel: () => halt()
112
+ }
113
+ );
114
+ return profile;
115
+ };
116
+ var promptConfirmation = async (message, skipConfirmation) => {
117
+ if (skipConfirmation) {
118
+ return;
119
+ }
120
+ const { confirmed } = await prompts(
121
+ [
122
+ {
123
+ type: "confirm",
124
+ name: "confirmed",
125
+ message,
126
+ initial: false
127
+ }
128
+ ],
129
+ {
130
+ onCancel: () => halt()
131
+ }
132
+ );
133
+ if (!confirmed) {
134
+ halt("Operation cancelled.");
135
+ }
136
+ };
137
+ var assertTagValid = (tag) => {
138
+ const regex = /^[a-zA-Z0-9-_.]+$/;
139
+ if (!regex.test(tag)) {
140
+ halt(
141
+ `Flag "--tag" has forbidden format. Allowed format: ${regex.toString()}.`
142
+ );
143
+ }
144
+ };
145
+
146
+ // src/lib/notifier.ts
147
+ import notifier from "node-notifier";
148
+ import stripIndent2 from "strip-indent";
149
+ var notify = (args) => {
150
+ const { title, message = "" } = args;
151
+ notifier.notify({
152
+ title,
153
+ message: stripIndent2(message).trim(),
154
+ contentImage: "https://assets.raisenow.io/favicon.png",
155
+ sound: "Funk"
156
+ });
157
+ };
158
+
159
+ // src/commands/undeploy.ts
160
+ var undeploy = async (options) => {
161
+ const { ifCore } = getIfCoreFns();
162
+ const configName = ifCore(CORE_CONFIG_NAME, getWidgetUuid());
163
+ if (!options.profile && !options.ci) {
164
+ assertProfilesPresent();
165
+ options.profile = await promptProfile();
166
+ }
167
+ assertOptionsValid(options);
168
+ awsAuthenticate(options);
169
+ const flags = prepareFlags(options);
170
+ const { tag, all, dryrun } = options;
171
+ const dryRunFlag = dryrun ? `--dryrun` : "";
172
+ const deployUrl = all ? `s3://${AWS_S3_BUCKET_TAMARO}/${configName}/` : `s3://${AWS_S3_BUCKET_TAMARO}/${configName}/${tag}/`;
173
+ const description = all ? `all tags of "${configName}"` : `tag "${tag}" of "${configName}"`;
174
+ if (dryRunFlag) {
175
+ logTitle("\u{1F9EA} DRY RUN MODE - No actual changes will be made \u{1F9EA}");
176
+ }
177
+ await promptConfirmation(
178
+ `Are you sure you want to undeploy ${description}?`,
179
+ options.yes
180
+ );
181
+ const cmd = `
182
+ aws s3 rm ${deployUrl}
183
+ --recursive
184
+ ${flags}
185
+ ${dryRunFlag}
186
+ `;
187
+ logTitle(`Undeploying ${description} from AWS S3 \u2026`);
188
+ logCommand(cmd);
189
+ try {
190
+ runCommandSync(cmd, { stdout: "inherit" });
191
+ } catch (error) {
192
+ halt(error.stderr);
193
+ }
194
+ if (!dryRunFlag) {
195
+ const invalidationPath = all ? `/${configName}/*` : `/${configName}/${tag}/*`;
196
+ const cmdInvalidateCache = `
197
+ aws cloudfront create-invalidation
198
+ --distribution-id ${AWS_CLOUDFRONT_DISTRIBUTION_ID}
199
+ --paths ${invalidationPath}
200
+ ${flags}
201
+ `;
202
+ logTitle("\nInvalidating edge cache \u2026");
203
+ logCommand(cmdInvalidateCache);
204
+ try {
205
+ runCommandSync(cmdInvalidateCache);
206
+ } catch (error) {
207
+ halt(error.stderr);
208
+ }
209
+ }
210
+ logTitle(`
211
+ ${description} has been undeployed.`);
212
+ logDataTable({ "Removed URL:": deployUrl });
213
+ process.on("exit", () => {
214
+ notify({
215
+ title: "undeploy",
216
+ message: `${description} has been undeployed.`
217
+ });
218
+ });
219
+ };
220
+ var assertOptionsValid = (options) => {
221
+ const { profile, tag, all } = options;
222
+ const { ifCore } = getIfCoreFns();
223
+ if (ifCore() && all) {
224
+ halt('Flag "--all" must not be used in Tamaro Core context.');
225
+ }
226
+ if (ifCore() && tag === DEFAULT_TAG) {
227
+ halt("You cannot undeploy the default tag for Tamaro Core.");
228
+ }
229
+ if (profile) {
230
+ assertProfileValid(profile);
231
+ }
232
+ if (all && tag) {
233
+ halt('Flags "--tag" and "--all" must not be used together.');
234
+ }
235
+ if (!all && tag) {
236
+ assertTagValid(tag);
237
+ }
238
+ };
239
+
240
+ // src/commands/undeploy-email-config.ts
241
+ import prompts2 from "prompts";
242
+ var undeployEmailConfig = async (options) => {
243
+ const { ifCore } = getIfCoreFns();
244
+ assertIsNotCore(ifCore);
245
+ if (!options.profile && !options.ci) {
246
+ assertProfilesPresent();
247
+ options.profile = await promptProfile();
248
+ }
249
+ if (!options.bucket) {
250
+ options.bucket = options.ci ? AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD : await promptBucketTamaroEmailConfig();
251
+ }
252
+ assertOptionsValid2(options);
253
+ awsAuthenticate(options);
254
+ const flags = prepareFlags(options);
255
+ const dryRunFlag = options.dryrun ? `--dryrun` : "";
256
+ const configName = getWidgetUuid();
257
+ const deployUrl = `s3://${options.bucket}/${configName}/`;
258
+ await promptConfirmation(
259
+ `Are you sure you want to undeploy the email configuration for "${configName}" from "${options.bucket}"?`,
260
+ options.yes
261
+ );
262
+ if (dryRunFlag) {
263
+ logTitle("\u{1F9EA} DRY RUN MODE - No actual changes will be made \u{1F9EA}");
264
+ }
265
+ const cmd = `
266
+ aws s3 rm ${deployUrl}
267
+ --recursive
268
+ ${flags}
269
+ ${dryRunFlag}
270
+ `;
271
+ logTitle(
272
+ `Undeploying email configuration for "${configName}" from AWS S3 \u2026`
273
+ );
274
+ logCommand(cmd);
275
+ try {
276
+ runCommandSync(cmd, { stdout: "inherit" });
277
+ } catch (error) {
278
+ halt(error.stderr);
279
+ }
280
+ logTitle(
281
+ `Email configuration for "${configName}" has been undeployed from "${options.bucket}".`
282
+ );
283
+ logDataTable({ "Removed URL:": deployUrl });
284
+ process.on("exit", () => {
285
+ notify({
286
+ title: "undeploy-email-config",
287
+ message: `Email configuration for "${configName}" has been undeployed.`
288
+ });
289
+ });
290
+ };
291
+ var assertOptionsValid2 = (options) => {
292
+ const { profile, bucket } = options;
293
+ if (profile) {
294
+ assertProfileValid(profile);
295
+ }
296
+ if (bucket) {
297
+ assertBucketValid(bucket);
298
+ }
299
+ };
300
+ var assertIsNotCore = (ifCore) => {
301
+ if (ifCore()) {
302
+ halt("You cannot undeploy widget email configuration for Tamaro Core.");
303
+ }
304
+ };
305
+ var assertBucketValid = (bucket) => {
306
+ const buckets = [
307
+ AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE,
308
+ AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD
309
+ ];
310
+ if (!buckets.includes(bucket)) {
311
+ halt("Invalid bucket name.");
312
+ }
313
+ };
314
+ var promptBucketTamaroEmailConfig = async () => {
315
+ const buckets = [
316
+ { title: "stage", value: AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE },
317
+ { title: "prod", value: AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD }
318
+ ];
319
+ const { bucket } = await prompts2(
320
+ [
321
+ {
322
+ type: "select",
323
+ name: "bucket",
324
+ message: "Select environment",
325
+ choices: buckets
326
+ }
327
+ ],
328
+ {
329
+ onCancel: () => halt()
330
+ }
331
+ );
332
+ return bucket;
333
+ };
334
+
335
+ // src/commands/archive.ts
336
+ var archive = async (options) => {
337
+ const { ifCore } = getIfCoreFns();
338
+ assertIsNotCore2(ifCore);
339
+ assertIsNotArchived();
340
+ const configName = getWidgetUuid();
341
+ const cwd = process.cwd();
342
+ const parentDir = dirname(cwd);
343
+ const archiveDir = resolve(parentDir, "_archived");
344
+ const archiveTarget = resolve(archiveDir, configName);
345
+ assertArchiveTargetDoesNotExist(archiveTarget);
346
+ if (!options.profile && !options.ci) {
347
+ assertProfilesPresent();
348
+ options.profile = await promptProfile();
349
+ }
350
+ assertOptionsValid3(options);
351
+ if (options.dryrun) {
352
+ logTitle("\u{1F9EA} DRY RUN MODE - No actual changes will be made \u{1F9EA}");
353
+ }
354
+ await promptConfirmation(
355
+ `Are you sure you want to archive "${configName}"? This will ${chalk.red(`undeploy all tags and email configurations of "${configName}"`)}.`,
356
+ options.yes
357
+ );
358
+ logTitle(`
359
+ Undeploying all tags for "${configName}" \u2026`);
360
+ await undeploy({
361
+ all: true,
362
+ yes: true,
363
+ profile: options.profile,
364
+ ci: options.ci,
365
+ dryrun: options.dryrun
366
+ });
367
+ logSuccess(`
368
+ \u2705 Undeployed all tags for "${configName}" \u2026`);
369
+ logTitle(`
370
+ Undeploying email configuration for "${configName}" \u2026`);
371
+ await undeployEmailConfig({
372
+ yes: true,
373
+ profile: options.profile,
374
+ ci: options.ci,
375
+ dryrun: options.dryrun,
376
+ bucket: AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD
377
+ });
378
+ logSuccess(`
379
+ \u2705 Undeployed email configuration for "${configName}" \u2026`);
380
+ logTitle(`
381
+ Archiving "${configName}" \u2026`);
382
+ if (!options.dryrun) {
383
+ if (!existsSync(archiveDir)) {
384
+ mkdirSync(archiveDir, { recursive: true });
385
+ }
386
+ renameSync(cwd, archiveTarget);
387
+ logSuccess(`\u2705 Moved to ${archiveTarget}`);
388
+ } else {
389
+ logTitle(`Would move ${cwd} \u2192 ${archiveTarget}`);
390
+ }
391
+ logSuccess(`
392
+ "\u2705 ${configName}" has been archived.`);
393
+ logDataTable({
394
+ "Archived to:": archiveTarget
395
+ });
396
+ process.on("exit", () => {
397
+ notify({
398
+ title: "archive",
399
+ message: `"${configName}" has been archived.`
400
+ });
401
+ });
402
+ };
403
+ var assertOptionsValid3 = (options) => {
404
+ const { profile } = options;
405
+ if (profile) {
406
+ assertProfileValid(profile);
407
+ }
408
+ };
409
+ var assertIsNotCore2 = (ifCore) => {
410
+ if (ifCore()) {
411
+ halt("You cannot archive Tamaro Core.");
412
+ }
413
+ };
414
+ var assertIsNotArchived = () => {
415
+ const parentDirName = basename(dirname(process.cwd()));
416
+ if (parentDirName === "_archived") {
417
+ halt("This configuration is already archived.");
418
+ }
419
+ };
420
+ var assertArchiveTargetDoesNotExist = (archiveTarget) => {
421
+ if (existsSync(archiveTarget)) {
422
+ halt(
423
+ `Archive target already exists: ${archiveTarget}. Please remove it first.`
424
+ );
425
+ }
426
+ };
427
+
30
428
  // src/commands/validate.ts
31
- import { glob } from "glob";
429
+ import { globSync } from "glob";
32
430
 
33
431
  // src/lib/validators/validateEmailTemplates.ts
34
432
  import fs from "fs";
@@ -140,7 +538,7 @@ var compileEmailTemplate = (template) => {
140
538
  // src/commands/validate.ts
141
539
  var validate = async () => {
142
540
  const errors = validateEmailTemplates(
143
- (await glob("email-config/templates/**/*.hbs")).sort()
541
+ globSync("email-config/templates/**/*.hbs").sort()
144
542
  );
145
543
  if (errors.length) {
146
544
  halt(errors.map((el) => el.toString()).join("\n"));
@@ -148,101 +546,20 @@ var validate = async () => {
148
546
  logTitle("Validated successfully");
149
547
  };
150
548
 
151
- // src/lib/aws.ts
152
- import { execaCommandSync } from "execa";
153
- import prompts from "prompts";
154
- import stripIndent from "strip-indent";
155
- var awsAuthenticate = (options) => {
156
- if (options.ci) {
157
- return;
158
- }
159
- try {
160
- checkIdentity(options);
161
- } catch (error) {
162
- login(options);
163
- }
164
- };
165
- var assertProfilesPresent = () => {
166
- const profiles = getAvailableProfiles();
167
- if (profiles.length === 0) {
168
- halt(
169
- stripIndent(`
170
- No AWS profiles found.
171
- Run "aws configure sso" to set up SSO-enabled profile.
172
- Check the wiki for more information:
173
- https://raisenow.atlassian.net/wiki/x/lIrWvg
174
- `)
175
- );
176
- }
177
- };
178
- var getAvailableProfiles = () => {
179
- const { stdout } = execaCommandSync("aws configure list-profiles");
180
- return stdout.split("\n");
181
- };
182
- var checkIdentity = (options) => {
183
- const flags = prepareFlags(options);
184
- execaCommandSync(`aws sts get-caller-identity ${flags}`);
185
- };
186
- var login = (options) => {
187
- const flags = prepareFlags(options);
188
- try {
189
- execaCommandSync(`aws sso login ${flags}`, { stdio: "inherit" });
190
- console.log("");
191
- } catch (error) {
192
- halt("Login failed.");
193
- }
194
- };
195
- var prepareFlags = (options) => {
196
- const flags = [];
197
- const { profile } = options;
198
- if (profile) {
199
- flags.push(`--profile ${profile}`);
200
- }
201
- return flags.join(" ");
202
- };
203
- var assertProfileValid = (profile) => {
204
- const profiles = getAvailableProfiles();
205
- if (!profiles.includes(profile)) {
206
- halt(
207
- stripIndent(`
208
- AWS profile "${profile}" is not found.
209
- Available profiles are: ${profiles.map((v) => `"${v}"`).join(", ")}.
210
- `)
211
- );
212
- }
213
- };
214
- var promptProfile = async () => {
215
- const profiles = getAvailableProfiles().filter((v) => !!v).map((v) => ({ title: v, value: v }));
216
- const { profile } = await prompts(
217
- [
218
- {
219
- type: "select",
220
- name: "profile",
221
- message: "Select AWS profile",
222
- choices: profiles
223
- }
224
- ],
225
- {
226
- onCancel: () => halt()
227
- }
228
- );
229
- return profile;
230
- };
231
-
232
549
  // src/lib/https.ts
233
- import { existsSync } from "fs";
234
- import { resolve } from "path";
235
- import stripIndent2 from "strip-indent";
550
+ import { existsSync as existsSync2 } from "fs";
551
+ import { resolve as resolve2 } from "path";
552
+ import stripIndent3 from "strip-indent";
236
553
  var assertCrtExists = () => {
237
554
  const { ifCore } = getIfCoreFns();
238
555
  const paths = getPaths(ifCore);
239
- const certFile = resolve(paths.root, HTTPS_CRT_FILE);
240
- const keyFile = resolve(paths.root, HTTPS_KEY_FILE);
241
- if (existsSync(certFile) && existsSync(keyFile)) {
556
+ const certFile = resolve2(paths.root, HTTPS_CRT_FILE);
557
+ const keyFile = resolve2(paths.root, HTTPS_KEY_FILE);
558
+ if (existsSync2(certFile) && existsSync2(keyFile)) {
242
559
  return;
243
560
  }
244
561
  halt(
245
- stripIndent2(`
562
+ stripIndent3(`
246
563
  Flag "--https" is used, but "${HTTPS_CRT_FILE}" and/or "${HTTPS_KEY_FILE}" files are not found.
247
564
  You need to generate certificate first.
248
565
  Check docs: https://unpkg.com/@raisenow/tamaro-cli/readme.html#/?id=use-transport-level-security-https
@@ -250,19 +567,6 @@ var assertCrtExists = () => {
250
567
  );
251
568
  };
252
569
 
253
- // src/lib/notifier.ts
254
- import notifier from "node-notifier";
255
- import stripIndent3 from "strip-indent";
256
- var notify = (args) => {
257
- const { title, message = "" } = args;
258
- notifier.notify({
259
- title,
260
- message: stripIndent3(message).trim(),
261
- contentImage: "https://assets.raisenow.io/favicon.png",
262
- sound: "Funk"
263
- });
264
- };
265
-
266
570
  // src/commands/build.ts
267
571
  var build = async (options) => {
268
572
  const { env } = options;
@@ -271,7 +575,7 @@ var build = async (options) => {
271
575
  assertProfilesPresent();
272
576
  options.profile = await promptProfile();
273
577
  }
274
- assertOptionsValid(options);
578
+ assertOptionsValid4(options);
275
579
  await validate();
276
580
  const flags = prepareFlags2(options);
277
581
  const { ifCore } = getIfCoreFns();
@@ -303,7 +607,7 @@ Bundle for "${configName}" customer configuration is done.`);
303
607
  });
304
608
  });
305
609
  };
306
- var assertOptionsValid = (options) => {
610
+ var assertOptionsValid4 = (options) => {
307
611
  const { localCore, https, deploy: deploy2, env, profile } = options;
308
612
  const { ifCore } = getIfCoreFns();
309
613
  if (ifCore() && localCore) {
@@ -339,7 +643,7 @@ var prepareFlags2 = (options) => {
339
643
  };
340
644
 
341
645
  // src/commands/deploy.ts
342
- import { existsSync as existsSync2 } from "fs";
646
+ import { existsSync as existsSync3 } from "fs";
343
647
  import stripIndent4 from "strip-indent";
344
648
  var deploy = async (options) => {
345
649
  const { ifCore } = getIfCoreFns();
@@ -349,7 +653,7 @@ var deploy = async (options) => {
349
653
  assertProfilesPresent();
350
654
  options.profile = await promptProfile();
351
655
  }
352
- assertOptionsValid2(options);
656
+ assertOptionsValid5(options);
353
657
  await validate();
354
658
  awsAuthenticate(options);
355
659
  const flags = prepareFlags(options);
@@ -423,7 +727,7 @@ Bundle for "${configName}" is deployed with tag "${tag}".`);
423
727
  });
424
728
  });
425
729
  };
426
- var assertOptionsValid2 = (options) => {
730
+ var assertOptionsValid5 = (options) => {
427
731
  const { profile, tag } = options;
428
732
  if (profile) {
429
733
  assertProfileValid(profile);
@@ -431,7 +735,7 @@ var assertOptionsValid2 = (options) => {
431
735
  assertTagValid(tag);
432
736
  };
433
737
  var assertDistPathExists = (distPath) => {
434
- if (!existsSync2(distPath)) {
738
+ if (!existsSync3(distPath)) {
435
739
  halt(
436
740
  stripIndent4(`
437
741
  Dist folder does not exists.
@@ -440,32 +744,24 @@ var assertDistPathExists = (distPath) => {
440
744
  );
441
745
  }
442
746
  };
443
- var assertTagValid = (tag) => {
444
- const regex = /^[a-zA-Z0-9-_.]+$/;
445
- if (!regex.test(tag)) {
446
- halt(
447
- `Flag "--tag" has forbidden format. Allowed format: ${regex.toString()}.`
448
- );
449
- }
450
- };
451
747
 
452
748
  // src/commands/deploy-email-config.ts
453
- import { existsSync as existsSync3 } from "fs";
454
- import prompts2 from "prompts";
749
+ import { existsSync as existsSync4 } from "fs";
750
+ import prompts3 from "prompts";
455
751
  var deployEmailConfig = async (options) => {
456
752
  const { ifCore } = getIfCoreFns();
457
753
  const paths = getPaths(ifCore);
458
754
  const emailConfigPath = `${paths.app}/email-config`;
459
- assertIsNotCore();
755
+ assertIsNotCore3();
460
756
  assertEmailConfigPathExists(emailConfigPath);
461
757
  if (!options.profile && !options.ci) {
462
758
  assertProfilesPresent();
463
759
  options.profile = await promptProfile();
464
760
  }
465
761
  if (!options.bucket) {
466
- options.bucket = options.ci ? AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD : await promptBucketTamaroEmailConfig();
762
+ options.bucket = options.ci ? AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD : await promptBucketTamaroEmailConfig2();
467
763
  }
468
- assertOptionsValid3(options);
764
+ assertOptionsValid6(options);
469
765
  await validate();
470
766
  awsAuthenticate(options);
471
767
  const flags = prepareFlags(options);
@@ -502,27 +798,27 @@ var deployEmailConfig = async (options) => {
502
798
  });
503
799
  });
504
800
  };
505
- var assertOptionsValid3 = (options) => {
801
+ var assertOptionsValid6 = (options) => {
506
802
  const { profile, bucket } = options;
507
803
  if (profile) {
508
804
  assertProfileValid(profile);
509
805
  }
510
806
  if (bucket) {
511
- assertBucketValid(bucket);
807
+ assertBucketValid2(bucket);
512
808
  }
513
809
  };
514
810
  var assertEmailConfigPathExists = (distPath) => {
515
- if (!existsSync3(distPath)) {
811
+ if (!existsSync4(distPath)) {
516
812
  halt("Email config folder does not exists. Nothing to deploy.");
517
813
  }
518
814
  };
519
- var assertIsNotCore = () => {
815
+ var assertIsNotCore3 = () => {
520
816
  const { ifCore } = getIfCoreFns();
521
817
  if (ifCore()) {
522
818
  halt("You cannot deploy widget email configuration for Tamaro Core.");
523
819
  }
524
820
  };
525
- var assertBucketValid = (bucket) => {
821
+ var assertBucketValid2 = (bucket) => {
526
822
  const buckets = [
527
823
  AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE,
528
824
  AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD
@@ -531,12 +827,12 @@ var assertBucketValid = (bucket) => {
531
827
  halt("Invalid bucket name.");
532
828
  }
533
829
  };
534
- var promptBucketTamaroEmailConfig = async () => {
830
+ var promptBucketTamaroEmailConfig2 = async () => {
535
831
  const buckets = [
536
832
  { title: "stage", value: AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE },
537
833
  { title: "prod", value: AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD }
538
834
  ];
539
- const { bucket } = await prompts2(
835
+ const { bucket } = await prompts3(
540
836
  [
541
837
  {
542
838
  type: "select",
@@ -557,7 +853,7 @@ import { getPortPromise } from "portfinder";
557
853
  var dev = async (options) => {
558
854
  const { env } = options;
559
855
  applyEnv(env);
560
- assertOptionsValid4(options);
856
+ assertOptionsValid7(options);
561
857
  const flags = await prepareFlags3(options);
562
858
  const { ifCore } = getIfCoreFns();
563
859
  const title = ifCore(
@@ -575,7 +871,7 @@ var dev = async (options) => {
575
871
  logCommand(cmd);
576
872
  runCommandSync(cmd, { stdio: "inherit" });
577
873
  };
578
- var assertOptionsValid4 = (options) => {
874
+ var assertOptionsValid7 = (options) => {
579
875
  const { localCore, https, port, env } = options;
580
876
  const { ifCore } = getIfCoreFns();
581
877
  if (ifCore() && localCore) {
@@ -612,7 +908,7 @@ var listDeployed = async (options) => {
612
908
  assertProfilesPresent();
613
909
  options.profile = await promptProfile();
614
910
  }
615
- assertOptionsValid5(options);
911
+ assertOptionsValid8(options);
616
912
  awsAuthenticate(options);
617
913
  const flags = prepareFlags(options);
618
914
  const { config } = options;
@@ -646,7 +942,7 @@ var listDeployed = async (options) => {
646
942
  console.log(`${text}
647
943
  `);
648
944
  };
649
- var assertOptionsValid5 = (options) => {
945
+ var assertOptionsValid8 = (options) => {
650
946
  const { config, profile } = options;
651
947
  assertConfigValid(config);
652
948
  if (profile) {
@@ -670,10 +966,10 @@ var parseTags = (lines) => {
670
966
  };
671
967
 
672
968
  // src/commands/serve.ts
673
- import { resolve as resolve2 } from "path";
969
+ import { resolve as resolve3 } from "path";
674
970
  import { getPortPromise as getPortPromise2 } from "portfinder";
675
971
  var serve = async (options) => {
676
- assertOptionsValid6(options);
972
+ assertOptionsValid9(options);
677
973
  const flags = await prepareFlags4(options);
678
974
  const { ifCore } = getIfCoreFns();
679
975
  const paths = getPaths(ifCore);
@@ -686,7 +982,7 @@ var serve = async (options) => {
686
982
  logCommand(cmd);
687
983
  runCommandSync(cmd, { stdio: "inherit" });
688
984
  };
689
- var assertOptionsValid6 = (options) => {
985
+ var assertOptionsValid9 = (options) => {
690
986
  const { port, https } = options;
691
987
  if (isNaN(Number(port))) {
692
988
  halt('Flag "--port" should be a number.');
@@ -703,28 +999,138 @@ var prepareFlags4 = async (options) => {
703
999
  if (https) {
704
1000
  const { ifCore } = getIfCoreFns();
705
1001
  const paths = getPaths(ifCore);
706
- const certFile = resolve2(paths.root, HTTPS_CRT_FILE);
707
- const keyFile = resolve2(paths.root, HTTPS_KEY_FILE);
1002
+ const certFile = resolve3(paths.root, HTTPS_CRT_FILE);
1003
+ const keyFile = resolve3(paths.root, HTTPS_KEY_FILE);
708
1004
  flags.push(`--ssl-cert ${certFile}`);
709
1005
  flags.push(`--ssl-key ${keyFile}`);
710
1006
  }
711
1007
  return flags.join(" ");
712
1008
  };
713
1009
 
1010
+ // src/commands/unarchive.ts
1011
+ import { existsSync as existsSync5, renameSync as renameSync2 } from "fs";
1012
+ import { basename as basename2, dirname as dirname2, resolve as resolve4 } from "path";
1013
+ var unarchive = async (options) => {
1014
+ const { ifCore } = getIfCoreFns({ allowArchived: true });
1015
+ assertIsNotCore4(ifCore);
1016
+ assertIsArchived();
1017
+ const configName = getWidgetUuid();
1018
+ const cwd = process.cwd();
1019
+ const archivedDir = dirname2(cwd);
1020
+ const configsDir = dirname2(archivedDir);
1021
+ const restoreTarget = resolve4(configsDir, configName);
1022
+ assertRestoreTargetDoesNotExist(restoreTarget);
1023
+ if (!options.profile && !options.ci) {
1024
+ assertProfilesPresent();
1025
+ options.profile = await promptProfile();
1026
+ }
1027
+ assertOptionsValid10(options);
1028
+ await promptConfirmation(
1029
+ `Are you sure you want to unarchive "${configName}"? This will restore, build, and deploy the configuration.`,
1030
+ options.yes
1031
+ );
1032
+ if (options.dryrun) {
1033
+ logTitle("\u{1F9EA} DRY RUN MODE - No actual changes will be made \u{1F9EA}");
1034
+ }
1035
+ logTitle(`
1036
+ Unarchiving "${configName}" \u2026`);
1037
+ if (!options.dryrun) {
1038
+ renameSync2(cwd, restoreTarget);
1039
+ process.chdir(restoreTarget);
1040
+ logTitle(`Moved to ${restoreTarget}`);
1041
+ } else {
1042
+ logTitle(`Would move ${cwd} \u2192 ${restoreTarget}`);
1043
+ }
1044
+ if (!options.dryrun) {
1045
+ logTitle(`
1046
+ Building "${configName}" \u2026`);
1047
+ await build({
1048
+ localCore: false,
1049
+ analyze: false,
1050
+ https: false,
1051
+ serve: false,
1052
+ deploy: false,
1053
+ nolint: false,
1054
+ tag: DEFAULT_TAG,
1055
+ profile: options.profile,
1056
+ ci: options.ci,
1057
+ dryrun: options.dryrun
1058
+ });
1059
+ logSuccess(`
1060
+ \u2705 Built "${configName}" \u2026`);
1061
+ logTitle(`
1062
+ Deploying "${configName}" to tag "${DEFAULT_TAG}" \u2026`);
1063
+ await deploy({
1064
+ tag: DEFAULT_TAG,
1065
+ profile: options.profile,
1066
+ ci: options.ci,
1067
+ dryrun: options.dryrun
1068
+ });
1069
+ logSuccess(`
1070
+ \u2705 Deployed "${configName}" to tag "${DEFAULT_TAG}" \u2026`);
1071
+ logTitle(`
1072
+ Deploying email configuration for "${configName}" \u2026`);
1073
+ await deployEmailConfig({
1074
+ profile: options.profile,
1075
+ ci: options.ci,
1076
+ dryrun: options.dryrun,
1077
+ bucket: AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD
1078
+ });
1079
+ logSuccess(`
1080
+ \u2705 Deployed email configuration for "${configName}" \u2026`);
1081
+ } else {
1082
+ logTitle('Would build, deploy to "latest", and deploy email configuration.');
1083
+ }
1084
+ logSuccess(`
1085
+ \u2705 "${configName}" has been unarchived and deployed.`);
1086
+ process.on("exit", () => {
1087
+ notify({
1088
+ title: "unarchive",
1089
+ message: `"${configName}" has been unarchived and deployed.`
1090
+ });
1091
+ });
1092
+ };
1093
+ var assertOptionsValid10 = (options) => {
1094
+ const { profile } = options;
1095
+ if (profile) {
1096
+ assertProfileValid(profile);
1097
+ }
1098
+ };
1099
+ var assertIsArchived = () => {
1100
+ const parentDirName = basename2(dirname2(process.cwd()));
1101
+ if (parentDirName !== "_archived") {
1102
+ halt(
1103
+ "This configuration is not archived. Only archived configurations can be unarchived."
1104
+ );
1105
+ }
1106
+ };
1107
+ var assertIsNotCore4 = (ifCore) => {
1108
+ if (ifCore()) {
1109
+ halt("You cannot unarchive Tamaro Core.");
1110
+ }
1111
+ };
1112
+ var assertRestoreTargetDoesNotExist = (restoreTarget) => {
1113
+ if (existsSync5(restoreTarget)) {
1114
+ halt(
1115
+ `Restore target already exists: ${restoreTarget}. Please remove it first.`
1116
+ );
1117
+ }
1118
+ };
1119
+
714
1120
  // package.json
715
1121
  var package_default = {
716
1122
  name: "@raisenow/tamaro-cli",
717
- version: "1.5.0",
1123
+ version: "1.6.0-beta.0",
718
1124
  author: {
719
1125
  name: "RaiseNow",
720
1126
  email: "development@raisenow.com"
721
1127
  },
722
1128
  type: "module",
723
1129
  bin: "dist/cli.js",
724
- packageManager: "pnpm@10.17.1",
1130
+ packageManager: "pnpm@10.28.2",
725
1131
  engines: {
726
- node: ">=22",
727
- pnpm: ">=10",
1132
+ node: ">=22.22.0",
1133
+ pnpm: ">=10.28.2",
728
1134
  npm: "please-use-pnpm",
729
1135
  yarn: "please-use-pnpm"
730
1136
  },
@@ -733,6 +1139,7 @@ var package_default = {
733
1139
  typecheck: "tsc --noEmit",
734
1140
  build: "tsup src/cli.ts src/webpack.config.ts --clean --shims --format esm",
735
1141
  "build:watch": "pnpm build --watch --ignore-watch .history --ignore-watch .idea",
1142
+ dev: "pnpm build:watch",
736
1143
  readme: "pnpx http-server . --cors -o /readme.html",
737
1144
  lint: "DEBUG=eslint:cli-engine eslint .",
738
1145
  "lint:fix": "DEBUG=eslint:cli-engine eslint --fix .",
@@ -742,62 +1149,62 @@ var package_default = {
742
1149
  "test:coverage": "vitest --coverage"
743
1150
  },
744
1151
  dependencies: {
745
- "@babel/cli": "^7.28.3",
746
- "@babel/core": "^7.28.4",
747
- "@babel/plugin-proposal-decorators": "^7.28.0",
1152
+ "@babel/cli": "^7.28.6",
1153
+ "@babel/core": "^7.29.0",
1154
+ "@babel/plugin-proposal-decorators": "^7.29.0",
748
1155
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
749
- "@babel/plugin-transform-runtime": "^7.28.3",
750
- "@babel/preset-env": "^7.28.3",
751
- "@babel/preset-react": "^7.27.1",
752
- "@babel/preset-typescript": "^7.27.1",
753
- "@babel/runtime-corejs3": "^7.28.4",
754
- "@pmmmwh/react-refresh-webpack-plugin": "^0.6.1",
1156
+ "@babel/plugin-transform-runtime": "^7.29.0",
1157
+ "@babel/preset-env": "^7.29.0",
1158
+ "@babel/preset-react": "^7.28.5",
1159
+ "@babel/preset-typescript": "^7.28.5",
1160
+ "@babel/runtime-corejs3": "^7.29.0",
1161
+ "@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
755
1162
  "@statoscope/webpack-plugin": "^5.29.0",
756
1163
  "@types/columnify": "^1.5.4",
757
1164
  "@types/handlebars-helpers": "^0.5.6",
758
- "@types/lodash": "^4.17.20",
759
- "@types/node": "^22.18.6",
1165
+ "@types/lodash": "^4.17.23",
1166
+ "@types/node": "^22.19.8",
760
1167
  "@types/node-notifier": "^8.0.5",
761
1168
  "@types/prompts": "^2.4.9",
762
1169
  "@types/resolve-bin": "^0.4.3",
763
1170
  "@types/webpack-config-utils": "^2.3.4",
764
1171
  "@typescript-eslint/eslint-plugin": "^7.18.0",
765
1172
  "@typescript-eslint/parser": "^7.18.0",
766
- autoprefixer: "^10.4.21",
1173
+ autoprefixer: "^10.4.24",
767
1174
  "babel-loader": "^10.0.0",
768
1175
  "babel-plugin-istanbul": "^7.0.1",
769
1176
  "babel-plugin-lodash": "^3.3.4",
770
1177
  chalk: "^5.6.2",
771
1178
  "clean-webpack-plugin": "^4.0.0",
772
1179
  columnify: "^1.6.0",
773
- commander: "^14.0.1",
1180
+ commander: "^14.0.3",
774
1181
  "copy-webpack-plugin": "^13.0.1",
775
- "core-js": "^3.45.1",
776
- "css-loader": "^7.1.2",
777
- "css-minimizer-webpack-plugin": "^7.0.2",
778
- dotenv: "^17.2.2",
1182
+ "core-js": "^3.48.0",
1183
+ "css-loader": "^7.1.3",
1184
+ "css-minimizer-webpack-plugin": "^7.0.4",
1185
+ dotenv: "^17.2.3",
779
1186
  "escape-string-regexp": "^5.0.0",
780
1187
  eslint: "^8.57.1",
781
1188
  "eslint-config-prettier": "^10.1.8",
782
- "eslint-import-resolver-typescript": "^3.6.3",
1189
+ "eslint-import-resolver-typescript": "^3.10.1",
783
1190
  "eslint-plugin-deprecation": "^3.0.0",
784
1191
  "eslint-plugin-import": "^2.32.0",
785
1192
  "eslint-plugin-markdownlint": "^0.9.0",
786
1193
  "eslint-plugin-promise": "^7.2.1",
787
1194
  "eslint-plugin-unused-imports": "^3.2.0",
788
- "eslint-plugin-yml": "^1.18.0",
1195
+ "eslint-plugin-yml": "^1.19.1",
789
1196
  "eslint-webpack-plugin": "^5.0.2",
790
- execa: "^9.6.0",
1197
+ execa: "^9.6.1",
791
1198
  "file-loader": "^6.2.0",
792
1199
  "fork-ts-checker-webpack-plugin": "^9.1.0",
793
- glob: "^11.0.3",
1200
+ glob: "^13.0.1",
794
1201
  handlebars: "^4.7.8",
795
1202
  "handlebars-helpers": "^0.10.0",
796
1203
  "html-loader": "^5.1.0",
797
- "html-webpack-plugin": "^5.6.4",
1204
+ "html-webpack-plugin": "^5.6.6",
798
1205
  "json-loader": "^0.5.7",
799
- lodash: "^4.17.21",
800
- "mini-css-extract-plugin": "^2.9.4",
1206
+ lodash: "^4.17.23",
1207
+ "mini-css-extract-plugin": "^2.10.0",
801
1208
  "node-notifier": "^10.0.1",
802
1209
  portfinder: "^1.0.38",
803
1210
  postcss: "^8.5.6",
@@ -805,25 +1212,25 @@ var package_default = {
805
1212
  "postcss-import": "^16.1.1",
806
1213
  "postcss-loader": "^8.2.0",
807
1214
  "postcss-nested": "^7.0.2",
808
- prettier: "^3.6.2",
1215
+ prettier: "^3.8.1",
809
1216
  prompts: "^2.4.2",
810
- "react-refresh": "^0.17.0",
1217
+ "react-refresh": "^0.18.0",
811
1218
  "resolve-url-loader": "^5.0.0",
812
- sass: "^1.93.1",
813
- "sass-loader": "^16.0.5",
814
- "strip-indent": "^4.1.0",
1219
+ sass: "^1.97.3",
1220
+ "sass-loader": "^16.0.6",
1221
+ "strip-indent": "^4.1.1",
815
1222
  "style-loader": "^4.0.0",
816
- "terser-webpack-plugin": "^5.3.14",
1223
+ "terser-webpack-plugin": "^5.3.16",
817
1224
  "tsconfig-paths-webpack-plugin": "^4.2.0",
818
- tsup: "^8.5.0",
819
- typescript: "^5.9.2",
1225
+ tsup: "^8.5.1",
1226
+ typescript: "^5.9.3",
820
1227
  "url-loader": "^4.1.1",
821
- vitest: "^3.2.4",
822
- webpack: "^5.101.3",
1228
+ vitest: "^4.0.18",
1229
+ webpack: "^5.105.0",
823
1230
  "webpack-cli": "^6.0.1",
824
1231
  "webpack-config-utils": "^2.3.1",
825
- "webpack-dev-server": "^5.2.2",
826
- "yaml-loader": "^0.8.1"
1232
+ "webpack-dev-server": "^5.2.3",
1233
+ "yaml-loader": "^0.9.0"
827
1234
  }
828
1235
  };
829
1236
 
@@ -910,6 +1317,44 @@ cli.command("deploy-email-config").description("Deploy a widget's email configur
910
1317
  ).action(async (options) => {
911
1318
  await deployEmailConfig(options);
912
1319
  });
1320
+ cli.command("undeploy").description(
1321
+ "Undeploy a Tamaro Core or customer configuration bundle from AWS S3"
1322
+ ).option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option(
1323
+ "--tag <tag>",
1324
+ "Tag which should be undeployed",
1325
+ DEFAULT_TAG
1326
+ ).option("--all", "Undeploy all tags", false).option(
1327
+ "--dryrun",
1328
+ "Displays the operations that would be performed without actually running them",
1329
+ false
1330
+ ).option("-y, --yes", "Skip confirmation prompt", false).action(async (options) => {
1331
+ await undeploy(options);
1332
+ });
1333
+ 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(
1334
+ "--dryrun",
1335
+ "Displays the operations that would be performed without actually running them",
1336
+ false
1337
+ ).option("-y, --yes", "Skip confirmation prompt", false).action(async (options) => {
1338
+ await undeployEmailConfig(options);
1339
+ });
1340
+ cli.command("archive").description(
1341
+ "Archive a customer configuration: moves to _archived folder and undeploys all tags and email configurations"
1342
+ ).option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option(
1343
+ "--dryrun",
1344
+ "Displays the operations that would be performed without actually running them",
1345
+ false
1346
+ ).option("-y, --yes", "Skip confirmation prompt", false).action(async (options) => {
1347
+ await archive(options);
1348
+ });
1349
+ cli.command("unarchive").description(
1350
+ "Unarchive a customer configuration: restores from _archived folder, builds and deploys"
1351
+ ).option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option(
1352
+ "--dryrun",
1353
+ "Displays the operations that would be performed without actually running them",
1354
+ false
1355
+ ).option("-y, --yes", "Skip confirmation prompt", false).action(async (options) => {
1356
+ await unarchive(options);
1357
+ });
913
1358
  cli.command("validate").description(
914
1359
  "Validate the current Tamaro configuration to avoid common errors"
915
1360
  ).action(async () => {