@raisenow/tamaro-cli 1.9.0-dev.1 → 1.9.0-dev.2

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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { A as EPMS_AUTH_CLIENT_ID_STAGE, B as logSuccess, C as DEFAULT_PORT, D as EPMS_AUTH_BASE_URL_PROD, E as EPMS_API_BASE_URL_STAGE, F as runCommandSync, I as createTerminalLink, L as logCommand, M as HTTPS_KEY_FILE, N as halt, O as EPMS_AUTH_BASE_URL_STAGE, P as promptConfirmation, R as logDataTable, S as DEFAULT_HTTP_TIMEOUT, T as EPMS_API_BASE_URL_PROD, V as logTitle, _ as AWS_S3_BUCKET_TAMARO, a as getIfCoreFns, b as CACHE_DIR, c as getWidgetUuid, d as resolveBin, f as resolveCoreVersion, g as AWS_CLOUDFRONT_DISTRIBUTION_ID, h as AUTH_PORT, j as HTTPS_CRT_FILE, k as EPMS_AUTH_CLIENT_ID_PROD, l as resolveAccountUuidFromConfig, m as resolveOwn, n as assertEnvValid, o as getPaths, t as applyEnv, v as AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD, w as DEFAULT_TAG, x as CORE_CONFIG_NAME, y as AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE, z as logError } from "./env-JzPIlctP.js";
2
+ import { A as HTTPS_CRT_FILE, B as logSuccess, C as DEFAULT_PORT, D as EPMS_AUTH_BASE_URL_PROD, E as EPMS_API_BASE_URL_STAGE, F as createTerminalLink, I as logCommand, L as logDataTable, M as halt, N as promptConfirmation, O as EPMS_AUTH_BASE_URL_STAGE, P as runCommandSync, R as logError, S as DEFAULT_HTTP_TIMEOUT, T as EPMS_API_BASE_URL_PROD, V as logTitle, _ as AWS_S3_BUCKET_TAMARO, a as getIfCoreFns, b as CACHE_DIR, c as getWidgetUuid, d as resolveBin, f as resolveCoreVersion, g as AWS_CLOUDFRONT_DISTRIBUTION_ID, h as AUTH_PORT, j as HTTPS_KEY_FILE, k as EPMS_OAUTH_CLIENT_ID, l as resolveAccountUuidFromConfig, m as resolveOwn, n as assertEnvValid, o as getPaths, t as applyEnv, v as AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD, w as DEFAULT_TAG, x as CORE_CONFIG_NAME, y as AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE, z as logInfo } from "./env-DBP70I1v.js";
3
3
  import { createCommand } from "commander";
4
4
  import fs, { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
5
5
  import path from "node:path";
@@ -364,29 +364,32 @@ const AuthResponseSchema = z.object({
364
364
  expires_in: z.number(),
365
365
  token_type: z.string()
366
366
  });
367
- const getCacheFilePath = (baseUrl, clientId) => {
368
- const cacheKey = Buffer.from(`${baseUrl}|${clientId}`).toString("base64url");
367
+ const getCacheFilePath = (key) => {
368
+ const cacheKey = Buffer.from(key).toString("base64url");
369
369
  return path.join(CACHE_DIR, `token-${cacheKey}.json`);
370
370
  };
371
- const readCachedToken = (baseUrl, clientId) => {
371
+ const getTokenCacheKey = (baseUrl, clientId) => {
372
+ return `${baseUrl}|${clientId}`;
373
+ };
374
+ const readCachedToken = (key) => {
372
375
  try {
373
- const filePath = getCacheFilePath(baseUrl, clientId);
376
+ const filePath = getCacheFilePath(key);
374
377
  if (!existsSync(filePath)) return;
375
378
  const data = JSON.parse(readFileSync(filePath, "utf8"));
376
379
  const parsed = CachedTokenSchema.safeParse(data);
377
380
  if (parsed.success) return parsed.data;
378
381
  } catch {}
379
382
  };
380
- const writeCachedToken = (baseUrl, clientId, cached) => {
383
+ const writeCachedToken = (key, cached) => {
381
384
  try {
382
- const filePath = getCacheFilePath(baseUrl, clientId);
385
+ const filePath = getCacheFilePath(key);
383
386
  mkdirSync(path.dirname(filePath), { recursive: true });
384
387
  writeFileSync(filePath, JSON.stringify(cached), { mode: 384 });
385
388
  } catch {}
386
389
  };
387
- const removeCachedToken = (baseUrl, clientId) => {
390
+ const removeCachedToken = (key) => {
388
391
  try {
389
- const filePath = getCacheFilePath(baseUrl, clientId);
392
+ const filePath = getCacheFilePath(key);
390
393
  if (existsSync(filePath)) writeFileSync(filePath, "", { flag: "w" });
391
394
  } catch {}
392
395
  };
@@ -396,11 +399,13 @@ const createAuthorizer = (baseUrl, clientId, clientSecret) => {
396
399
  const apiUnauthorized = ky.extend({
397
400
  headers: { "Access-Control-Allow-Origin": "*" },
398
401
  prefixUrl: baseUrl,
402
+ retry: { limit: 3 },
399
403
  timeout: DEFAULT_HTTP_TIMEOUT
400
404
  });
401
405
  let expirationTime = void 0;
402
406
  let token = void 0;
403
- const cached = readCachedToken(baseUrl, clientId);
407
+ const cacheKey = getTokenCacheKey(baseUrl, clientId);
408
+ const cached = readCachedToken(cacheKey);
404
409
  if (cached) {
405
410
  token = cached.token;
406
411
  expirationTime = cached.expirationTime;
@@ -415,7 +420,7 @@ const createAuthorizer = (baseUrl, clientId, clientSecret) => {
415
420
  } }).json();
416
421
  token = `${data.token_type} ${data.access_token}`;
417
422
  expirationTime = Date.now() + data.expires_in * 1e3;
418
- writeCachedToken(baseUrl, clientId, {
423
+ writeCachedToken(cacheKey, {
419
424
  expirationTime,
420
425
  token
421
426
  });
@@ -475,7 +480,9 @@ const doBrowserOAuthFlow = (authBaseUrl, clientId, port) => {
475
480
  server.listen(port, () => {
476
481
  const redirectUri = `http://localhost:${port}`;
477
482
  const authUrl = `${authBaseUrl}/oauth2/authorize?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=token`;
478
- console.log(`Opening browser for authentication: ${authUrl}`);
483
+ logInfo("\nℹ️ Log in with your super admin account in the browser to continue.");
484
+ console.log(`Opening browser for authentication...`);
485
+ console.log(createTerminalLink(authUrl, authUrl));
479
486
  open(authUrl).catch((error) => {
480
487
  reject(error instanceof Error ? error : new Error(String(error)));
481
488
  });
@@ -485,12 +492,13 @@ const doBrowserOAuthFlow = (authBaseUrl, clientId, port) => {
485
492
  const createBrowserAuthorizer = (authBaseUrl, clientId, epmsBaseUrl) => {
486
493
  const apiBase = ky.extend({
487
494
  prefixUrl: epmsBaseUrl,
495
+ retry: { limit: 3 },
488
496
  timeout: DEFAULT_HTTP_TIMEOUT
489
497
  });
490
498
  let expirationTime = void 0;
491
499
  let token = void 0;
492
- const cacheBaseUrl = `browser|${authBaseUrl}`;
493
- const cached = readCachedToken(cacheBaseUrl, clientId);
500
+ const cacheKey = getTokenCacheKey(authBaseUrl, clientId);
501
+ const cached = readCachedToken(cacheKey);
494
502
  if (cached) {
495
503
  token = cached.token;
496
504
  expirationTime = cached.expirationTime;
@@ -498,7 +506,7 @@ const createBrowserAuthorizer = (authBaseUrl, clientId, epmsBaseUrl) => {
498
506
  const resetAuth = () => {
499
507
  token = void 0;
500
508
  expirationTime = void 0;
501
- removeCachedToken(cacheBaseUrl, clientId);
509
+ removeCachedToken(cacheKey);
502
510
  };
503
511
  const authorize = async () => {
504
512
  const isExpired = (expirationTime ?? 0) - Date.now() < 300 * 1e3;
@@ -506,7 +514,7 @@ const createBrowserAuthorizer = (authBaseUrl, clientId, epmsBaseUrl) => {
506
514
  const data = await doBrowserOAuthFlow(authBaseUrl, clientId, AUTH_PORT);
507
515
  token = `${data.token_type} ${data.access_token}`;
508
516
  expirationTime = Date.now() + data.expires_in * 1e3;
509
- writeCachedToken(cacheBaseUrl, clientId, {
517
+ writeCachedToken(cacheKey, {
510
518
  expirationTime,
511
519
  token
512
520
  });
@@ -530,17 +538,54 @@ const createBrowserAuthorizer = (authBaseUrl, clientId, epmsBaseUrl) => {
530
538
  } });
531
539
  };
532
540
  //#endregion
533
- //#region src/lib/epms/client.ts
541
+ //#region src/lib/epms/myselfSchema.ts
542
+ const myselfSchema = z.object({
543
+ email: z.string(),
544
+ identity_roles: z.array(z.object({
545
+ identity_uuid: z.string(),
546
+ role: z.object({
547
+ name: z.string(),
548
+ uuid: z.string()
549
+ })
550
+ })),
551
+ uuid: z.string()
552
+ });
553
+ //#endregion
554
+ //#region src/lib/epms/epmsClient.ts
555
+ /**
556
+ * Manages authentication and API interactions with EPMS.
557
+ * It provides factory methods for creating instances based on different authentication flows (client credentials or browser-based) and handles token caching and renewal transparently.
558
+ */
534
559
  var EpmsClient = class EpmsClient {
560
+ #cacheKey;
535
561
  #epmsClient;
536
- constructor(epmsClient) {
562
+ #authType;
563
+ /**
564
+ * Creates an instance of EpmsClient.
565
+ *
566
+ * @param authType The type of authentication used by the client.
567
+ * @param cacheKey They key used for caching the token so that the client can manage the cache (e.g. clear it on logout). It should be unique per EPMS environment and client ID.
568
+ * @param epmsClient The underlying KyInstance used for making API requests.
569
+ */
570
+ constructor(authType, cacheKey, epmsClient) {
571
+ this.#authType = authType;
572
+ this.#cacheKey = cacheKey;
537
573
  this.#epmsClient = epmsClient;
538
574
  }
539
575
  static fromClientCredentials(baseUrl, clientId, clientSecret) {
540
- return new EpmsClient(createAuthorizer(baseUrl, clientId, clientSecret));
576
+ return new EpmsClient("client_credentials", getTokenCacheKey(baseUrl, clientId), createAuthorizer(baseUrl, clientId, clientSecret));
541
577
  }
542
578
  static fromBrowserAuth(epmsBaseUrl, authBaseUrl, clientId) {
543
- return new EpmsClient(createBrowserAuthorizer(authBaseUrl, clientId, epmsBaseUrl));
579
+ return new EpmsClient("browser", getTokenCacheKey(authBaseUrl, clientId), createBrowserAuthorizer(authBaseUrl, clientId, epmsBaseUrl));
580
+ }
581
+ get authType() {
582
+ return this.#authType;
583
+ }
584
+ logout() {
585
+ removeCachedToken(this.#cacheKey);
586
+ }
587
+ async myself() {
588
+ return this.#epmsClient.get("users/myself").json().then((response) => myselfSchema.parse(response));
544
589
  }
545
590
  async getOrganisationIdByAccountUuid(accountUuid) {
546
591
  const query = {
@@ -549,7 +594,7 @@ var EpmsClient = class EpmsClient {
549
594
  size: 1
550
595
  };
551
596
  const response = await this.#epmsClient.post("search/events", { json: query }).json();
552
- if (response.hits.length === 0) throw new Error(`No events found for account UUID: ${accountUuid}`);
597
+ if (response.hits.length === 0) throw new Error(`No organisation found for account UUID: ${accountUuid}`);
553
598
  const organisationId = response.hits[0].organisation_uuid;
554
599
  if (!organisationId) throw new Error(`Organisation UUID not found for account UUID: ${accountUuid}`);
555
600
  return organisationId;
@@ -557,6 +602,57 @@ var EpmsClient = class EpmsClient {
557
602
  async updateTamaro(configName, tag, json) {
558
603
  return this.#epmsClient.put(`products/tamaro/${configName}/tags/${tag}`, { json }).json();
559
604
  }
605
+ async archiveTamaro(configName, tag) {
606
+ return this.#epmsClient.post(`products/tamaro/${configName}/tags/${tag}/archive`).json();
607
+ }
608
+ };
609
+ //#endregion
610
+ //#region src/lib/epms/helpers.ts
611
+ const epmsAuthenticate = (env) => {
612
+ const { authBaseUrl, baseUrl, clientId, clientSecret } = {
613
+ prod: {
614
+ authBaseUrl: EPMS_AUTH_BASE_URL_PROD,
615
+ baseUrl: EPMS_API_BASE_URL_PROD,
616
+ clientId: process.env.EPMS_CLIENT_ID,
617
+ clientSecret: process.env.EPMS_CLIENT_SECRET
618
+ },
619
+ stage: {
620
+ authBaseUrl: EPMS_AUTH_BASE_URL_STAGE,
621
+ baseUrl: EPMS_API_BASE_URL_STAGE,
622
+ clientId: process.env.EPMS_CLIENT_ID_STAGE,
623
+ clientSecret: process.env.EPMS_CLIENT_SECRET_STAGE
624
+ }
625
+ }[env];
626
+ if (clientId && clientSecret) return EpmsClient.fromClientCredentials(baseUrl, clientId, clientSecret);
627
+ return EpmsClient.fromBrowserAuth(baseUrl, authBaseUrl, EPMS_OAUTH_CLIENT_ID);
628
+ };
629
+ const loadAccountUuidsFromConfig = (paths) => {
630
+ if (!("configYml" in paths)) {
631
+ halt("Fatal error, this should never happen");
632
+ throw new Error("Fatal error");
633
+ }
634
+ const configContent = yaml.load(readFileSync(paths.configYml, "utf8"));
635
+ const accountUuidStage = resolveAccountUuidFromConfig(configContent, "epms_stage");
636
+ return {
637
+ accountUuidProd: resolveAccountUuidFromConfig(configContent, "epms"),
638
+ accountUuidStage
639
+ };
640
+ };
641
+ const assertUserIsSuperAdmin = async (epmsClient) => {
642
+ if (epmsClient.authType === "client_credentials") return;
643
+ const myself = await epmsClient.myself();
644
+ if (!myself.identity_roles.some(({ role }) => role.name === "super_admin")) {
645
+ epmsClient.logout();
646
+ logError("\nYou must be a super admin to perform this action.");
647
+ logTitle("\nYour EPMS user details:");
648
+ logDataTable({
649
+ email: myself.email,
650
+ roles: myself.identity_roles.map(({ role }) => role.name).join(", "),
651
+ uuid: myself.uuid
652
+ });
653
+ logError("If this keeps happening, go to Configurator / Hub and logout before trying again.");
654
+ halt();
655
+ }
560
656
  };
561
657
  //#endregion
562
658
  //#region src/commands/update-epms.ts
@@ -576,7 +672,11 @@ const updateEpms = async (options) => {
576
672
  const { dryrun, tag } = options;
577
673
  const configName = getWidgetUuid();
578
674
  loadEnv(paths);
579
- const { accountUuidsProd, accountUuidsStage } = loadAccountUuidsFromConfig(paths);
675
+ const { accountUuidProd, accountUuidStage } = loadAccountUuidsFromConfig(paths);
676
+ if (!accountUuidProd && !accountUuidStage) {
677
+ logTitle("No EPMS account UUIDs found in config.yml. Skipping EPMS update.");
678
+ return;
679
+ }
580
680
  if (dryrun) logTitle("🧪 DRY RUN MODE - No actual changes will be made 🧪");
581
681
  const configuredVersion = resolveCoreVersion();
582
682
  if (!configuredVersion) {
@@ -588,15 +688,26 @@ const updateEpms = async (options) => {
588
688
  last_deployment: Math.trunc(Date.now() / 1e3),
589
689
  read_only: false
590
690
  };
591
- for (const accountUuidStage of accountUuidsStage) if (accountUuidStage) {
592
- const epmsClient = epmsAuthenticate("stage");
593
- const organisationUuid = await epmsClient.getOrganisationIdByAccountUuid(accountUuidStage);
691
+ const environments = [{
692
+ accountUuid: accountUuidStage,
693
+ env: "stage",
694
+ label: "Stage"
695
+ }, {
696
+ accountUuid: accountUuidProd,
697
+ env: "prod",
698
+ label: "Prod"
699
+ }];
700
+ for (const { accountUuid, env, label } of environments) {
701
+ if (!accountUuid) continue;
702
+ const epmsClient = epmsAuthenticate(env);
703
+ await assertUserIsSuperAdmin(epmsClient);
704
+ const organisationUuid = await epmsClient.getOrganisationIdByAccountUuid(accountUuid);
594
705
  const body = {
595
706
  ...baseInfo,
596
- account_uuid: accountUuidStage,
707
+ account_uuid: accountUuid,
597
708
  organisation_uuid: organisationUuid
598
709
  };
599
- logTitle(`Updating EPMS Stage for tag "${tag}":`);
710
+ logTitle(`\nUpdating EPMS ${label}:`);
600
711
  logDataTable({
601
712
  name: configName,
602
713
  tag,
@@ -604,48 +715,9 @@ const updateEpms = async (options) => {
604
715
  });
605
716
  if (!dryrun) {
606
717
  await epmsClient.updateTamaro(configName, tag, body);
607
- logSuccess("✅ EPMS Stage is updated.");
608
- } else logSuccess("⏭️ EPMS Stage would be updated (dry run).");
718
+ logSuccess(`✅ EPMS ${label} is updated.`);
719
+ } else logSuccess(`⏭️ EPMS ${label} would be updated (dry run).`);
609
720
  }
610
- for (const accountUuidProd of accountUuidsProd) if (accountUuidProd) {
611
- const epmsClient = epmsAuthenticate("prod");
612
- const organisationUuid = await epmsClient.getOrganisationIdByAccountUuid(accountUuidProd);
613
- logTitle(`Updating EPMS Prod for tag "${tag}":`);
614
- const body = {
615
- ...baseInfo,
616
- account_uuid: accountUuidProd,
617
- organisation_uuid: organisationUuid
618
- };
619
- logDataTable({
620
- name: configName,
621
- tag,
622
- ...body
623
- });
624
- if (!dryrun) {
625
- await epmsClient.updateTamaro(configName, tag, body);
626
- logSuccess("✅ EPMS Prod is updated.");
627
- } else logSuccess("⏭️ EPMS Prod would be updated (dry run).");
628
- }
629
- };
630
- const epmsAuthenticate = (env) => {
631
- if (env === "stage") {
632
- if (process.env.EPMS_CLIENT_ID_STAGE && process.env.EPMS_CLIENT_SECRET_STAGE) return EpmsClient.fromClientCredentials(EPMS_API_BASE_URL_STAGE, process.env.EPMS_CLIENT_ID_STAGE, process.env.EPMS_CLIENT_SECRET_STAGE);
633
- return EpmsClient.fromBrowserAuth(EPMS_API_BASE_URL_STAGE, EPMS_AUTH_BASE_URL_STAGE, EPMS_AUTH_CLIENT_ID_STAGE);
634
- }
635
- if (process.env.EPMS_CLIENT_ID && process.env.EPMS_CLIENT_SECRET) return EpmsClient.fromClientCredentials(EPMS_API_BASE_URL_PROD, process.env.EPMS_CLIENT_ID, process.env.EPMS_CLIENT_SECRET);
636
- return EpmsClient.fromBrowserAuth(EPMS_API_BASE_URL_PROD, EPMS_AUTH_BASE_URL_PROD, EPMS_AUTH_CLIENT_ID_PROD);
637
- };
638
- const loadAccountUuidsFromConfig = (paths) => {
639
- if (!("configYml" in paths)) {
640
- halt("Fatal error, this should never happen");
641
- throw new Error("Fatal error");
642
- }
643
- const configContent = yaml.load(readFileSync(paths.configYml, "utf8"));
644
- const accountUuidsStage = resolveAccountUuidFromConfig(configContent, "epms_stage");
645
- return {
646
- accountUuidsProd: resolveAccountUuidFromConfig(configContent, "epms"),
647
- accountUuidsStage
648
- };
649
721
  };
650
722
  const loadEnv = (paths) => {
651
723
  const filePath = globSync(paths.appEnv).find((file) => path.basename(file) === ".env");
@@ -1037,6 +1109,8 @@ const undeploy = async (options) => {
1037
1109
  halt(error.stderr);
1038
1110
  }
1039
1111
  }
1112
+ if (!dryRunFlag && !all && !ifCore() && !options.skipEpmsSyncOnlyUseInEmergenciesOrYouWillBeFired) await archiveEpms(configName, tag, false);
1113
+ else if (all && !ifCore()) logTitle("⚠️ EPMS archive skipped: --all flag used. Archive entries in EPMS manually if needed.");
1040
1114
  logTitle(`\n${description} has been undeployed.`);
1041
1115
  logDataTable({ "Removed URL:": deployUrl });
1042
1116
  process.on("exit", () => {
@@ -1046,6 +1120,33 @@ const undeploy = async (options) => {
1046
1120
  });
1047
1121
  });
1048
1122
  };
1123
+ const archiveEpms = async (configName, tag, dryrun) => {
1124
+ const { ifCore } = getIfCoreFns();
1125
+ const { accountUuidProd, accountUuidStage } = loadAccountUuidsFromConfig(getPaths(ifCore));
1126
+ if (!accountUuidProd && !accountUuidStage) {
1127
+ logTitle("No EPMS account UUIDs found in config.yml. Skipping EPMS archive.");
1128
+ return;
1129
+ }
1130
+ const environments = [{
1131
+ accountUuid: accountUuidStage,
1132
+ env: "stage",
1133
+ label: "Stage"
1134
+ }, {
1135
+ accountUuid: accountUuidProd,
1136
+ env: "prod",
1137
+ label: "Prod"
1138
+ }];
1139
+ for (const { accountUuid, env, label } of environments) {
1140
+ if (!accountUuid) continue;
1141
+ const epmsClient = epmsAuthenticate(env);
1142
+ await assertUserIsSuperAdmin(epmsClient);
1143
+ logTitle(`\nArchiving EPMS ${label}: ${configName}/${tag}`);
1144
+ if (!dryrun) {
1145
+ await epmsClient.archiveTamaro(configName, tag);
1146
+ logSuccess(`✅ EPMS ${label} entry archived.`);
1147
+ } else logSuccess(`⏭️ EPMS ${label} entry would be archived (dry run).`);
1148
+ }
1149
+ };
1049
1150
  const assertOptionsValid$1 = (options) => {
1050
1151
  const { all, profile, tag } = options;
1051
1152
  const { ifCore } = getIfCoreFns();
@@ -1128,7 +1229,7 @@ const promptBucketTamaroEmailConfig = async () => {
1128
1229
  //#endregion
1129
1230
  //#region package.json
1130
1231
  var name = "@raisenow/tamaro-cli";
1131
- var version = "1.9.0-dev.1";
1232
+ var version = "1.9.0-dev.2";
1132
1233
  //#endregion
1133
1234
  //#region src/cli.ts
1134
1235
  /**
@@ -1163,7 +1264,7 @@ cli.command("list-deployed").description("List deployments of Tamaro Core or a p
1163
1264
  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("--dryrun", "Displays the operations that would be performed without actually running them", false).action(async (options) => {
1164
1265
  await deployEmailConfig(options);
1165
1266
  });
1166
- cli.command("undeploy").description("Undeploy a Tamaro Core or customer configuration bundle from AWS S3").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--tag <tag>", "Tag which should be undeployed").option("--all", "Undeploy all tags", false).option("--dryrun", "Displays the operations that would be performed without actually running them", false).action(async (options) => {
1267
+ cli.command("undeploy").description("Undeploy a Tamaro Core or customer configuration bundle from AWS S3").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--tag <tag>", "Tag which should be undeployed").option("--all", "Undeploy all tags", false).option("--dryrun", "Displays the operations that would be performed without actually running them", false).option("--skip-epms-sync-only-use-in-emergencies-or-you-will-be-fired", "Skip EPMS archive sync (emergency use only)", false).action(async (options) => {
1167
1268
  await undeploy(options);
1168
1269
  });
1169
1270
  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("--stage", "Undeploy from stage environment (alternative to --bucket)").option("--prod", "Undeploy from prod environment (alternative to --bucket)").option("--dryrun", "Displays the operations that would be performed without actually running them", false).action(async (options) => {
@@ -22,6 +22,9 @@ const logError = (message) => {
22
22
  const logSuccess = (message) => {
23
23
  if (message) console.log(chalk.green(message));
24
24
  };
25
+ const logInfo = (message) => {
26
+ if (message) console.log(chalk.blue(message));
27
+ };
25
28
  const logCommand = (cmd) => {
26
29
  console.log(`\n${chalk.dim(stripIndent(cmd).trim())}\n`);
27
30
  };
@@ -66,16 +69,15 @@ const EPMS_API_BASE_URL_STAGE = "https://api.stage.mesos.raisenow.net";
66
69
  const EPMS_API_BASE_URL_PROD = "https://api.raisenow.io";
67
70
  const EPMS_AUTH_BASE_URL_STAGE = "https://login.stage.mesos.raisenow.net";
68
71
  const EPMS_AUTH_BASE_URL_PROD = "https://login.raisenow.com";
69
- const EPMS_AUTH_CLIENT_ID_STAGE = "luna-stage-local";
70
- const EPMS_AUTH_CLIENT_ID_PROD = "luna-local";
72
+ const EPMS_OAUTH_CLIENT_ID = "tamaro-cli";
71
73
  const CORE_CONFIG_NAME = "tamaro-core";
72
- const AUTH_PORT = 8080;
74
+ const AUTH_PORT = 4571;
73
75
  const AWS_CLOUDFRONT_DISTRIBUTION_ID = "EHJ1OM458YQ0I";
74
76
  const HTTPS_CRT_FILE = "localhost.crt";
75
77
  const HTTPS_KEY_FILE = "localhost.key";
76
78
  const CACHE_DIR = envPaths("tamaro-cli", { suffix: "" }).cache;
77
79
  //#endregion
78
- //#region node_modules/.pnpm/tsdown@0.21.8_synckit@0.11.11_typescript@6.0.2/node_modules/tsdown/esm-shims.js
80
+ //#region node_modules/.pnpm/tsdown@0.21.10_synckit@0.11.11_typescript@6.0.3/node_modules/tsdown/esm-shims.js
79
81
  const getFilename = () => fileURLToPath(import.meta.url);
80
82
  const getDirname = () => path.dirname(getFilename());
81
83
  const __dirname = /* @__PURE__ */ getDirname();
@@ -199,25 +201,19 @@ const getIfCoreFns = ({ allowArchived = false } = {}) => {
199
201
  };
200
202
  const resolveAccountUuidFromConfig = (rawConfig, field) => {
201
203
  const epmsConfig = z.object({ [field]: z.record(z.string(), z.unknown()) }).safeParse(rawConfig);
202
- if (!epmsConfig.success) return [];
204
+ if (!epmsConfig.success) return;
203
205
  const configContent = epmsConfig.data[field];
204
206
  const accountUuid = configContent.account_mapping ?? configContent.account_uuid;
205
207
  const accountUuidValidation = z.uuid().safeParse(accountUuid);
206
- const accontUuidArrayValidation = z.array(z.uuid()).safeParse(accountUuid);
207
- if (!accountUuidValidation.success && !accontUuidArrayValidation.success) halt(stripIndent(`Could not extract EPMS account UUID(s) from config.yml. Please ensure that the "${field}" field is correctly formatted. Expected formats:
208
+ if (!accountUuidValidation.success) halt(stripIndent(`Could not extract EPMS account UUID from config.yml. Please ensure that the "${field}" field is correctly formatted. Expected formats:
208
209
  1. ${field}:
209
210
  account_uuid: <UUID>
210
-
211
+
211
212
  Alternatively, if the account_uuid is conditional using "if" / "then" / "else" statements, it must be provided using the "account_mapping" format:
212
213
  2. ${field}:
213
- account_mapping:
214
- - <UUID_1>
215
- - <UUID_2>
216
- - <UUID_N>
214
+ account_mapping: <UUID>
217
215
  `));
218
- if (accontUuidArrayValidation.success) return accontUuidArrayValidation.data;
219
- if (accountUuidValidation.success) return [accountUuidValidation.data];
220
- throw new Error("This should never happen");
216
+ return accountUuidValidation.data;
221
217
  };
222
218
  const TAMARO_VERSION_IN_URL_REGEX = /tamaro-core\/(.*)\/index.js/;
223
219
  const extractTamaroVersionFromUrl = (url) => {
@@ -343,4 +339,4 @@ const applyEnv = (env) => {
343
339
  if (filePath) config({ path: filePath });
344
340
  };
345
341
  //#endregion
346
- export { EPMS_AUTH_CLIENT_ID_STAGE as A, logSuccess as B, DEFAULT_PORT as C, EPMS_AUTH_BASE_URL_PROD as D, EPMS_API_BASE_URL_STAGE as E, runCommandSync as F, createTerminalLink as I, logCommand as L, HTTPS_KEY_FILE as M, halt as N, EPMS_AUTH_BASE_URL_STAGE as O, promptConfirmation as P, logDataTable as R, DEFAULT_HTTP_TIMEOUT as S, EPMS_API_BASE_URL_PROD as T, logTitle as V, AWS_S3_BUCKET_TAMARO as _, getIfCoreFns as a, CACHE_DIR as b, getWidgetUuid as c, resolveBin as d, resolveCoreVersion as f, AWS_CLOUDFRONT_DISTRIBUTION_ID as g, AUTH_PORT as h, extensions as i, HTTPS_CRT_FILE as j, EPMS_AUTH_CLIENT_ID_PROD as k, resolveAccountUuidFromConfig as l, resolveOwn as m, assertEnvValid as n, getPaths as o, resolveEslintPluginConfig as p, getEnvVars as r, getRelativePaths as s, applyEnv as t, resolveApp as u, AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD as v, DEFAULT_TAG as w, CORE_CONFIG_NAME as x, AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE as y, logError as z };
342
+ export { HTTPS_CRT_FILE as A, logSuccess as B, DEFAULT_PORT as C, EPMS_AUTH_BASE_URL_PROD as D, EPMS_API_BASE_URL_STAGE as E, createTerminalLink as F, logCommand as I, logDataTable as L, halt as M, promptConfirmation as N, EPMS_AUTH_BASE_URL_STAGE as O, runCommandSync as P, logError as R, DEFAULT_HTTP_TIMEOUT as S, EPMS_API_BASE_URL_PROD as T, logTitle as V, AWS_S3_BUCKET_TAMARO as _, getIfCoreFns as a, CACHE_DIR as b, getWidgetUuid as c, resolveBin as d, resolveCoreVersion as f, AWS_CLOUDFRONT_DISTRIBUTION_ID as g, AUTH_PORT as h, extensions as i, HTTPS_KEY_FILE as j, EPMS_OAUTH_CLIENT_ID as k, resolveAccountUuidFromConfig as l, resolveOwn as m, assertEnvValid as n, getPaths as o, resolveEslintPluginConfig as p, getEnvVars as r, getRelativePaths as s, applyEnv as t, resolveApp as u, AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD as v, DEFAULT_TAG as w, CORE_CONFIG_NAME as x, AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE as y, logInfo as z };
@@ -1,4 +1,4 @@
1
- import { M as HTTPS_KEY_FILE, R as logDataTable, V as logTitle, a as getIfCoreFns, i as extensions, j as HTTPS_CRT_FILE, o as getPaths, p as resolveEslintPluginConfig, r as getEnvVars, s as getRelativePaths, u as resolveApp } from "./env-JzPIlctP.js";
1
+ import { A as HTTPS_CRT_FILE, L as logDataTable, V as logTitle, a as getIfCoreFns, i as extensions, j as HTTPS_KEY_FILE, o as getPaths, p as resolveEslintPluginConfig, r as getEnvVars, s as getRelativePaths, u as resolveApp } from "./env-DBP70I1v.js";
2
2
  import { createRequire } from "node:module";
3
3
  import { existsSync } from "node:fs";
4
4
  import path from "node:path";
@@ -164,6 +164,22 @@ const getWebpackConfig = (env) => {
164
164
  debug: ifDebug(),
165
165
  modules: false,
166
166
  useBuiltIns: "usage",
167
+ /**
168
+ * Exclude plugins that interfere with MobX flow inference via makeAutoObservable.
169
+ *
170
+ * MobX detects generator methods on the prototype and wraps them as `flow` automatically.
171
+ * Several Babel transforms break this detection:
172
+ *
173
+ * - transform-parameters: rewrites generator methods with default params (e.g. `*update(data, skip = false)`)
174
+ * into plain functions returning an IIFE generator. makeAutoObservable then sees a regular function
175
+ * and wraps it as `action` instead of `flow`.
176
+ *
177
+ * - async-to-generator / regenerator / async-generator-functions: downcompile async/generator syntax
178
+ * to state-machine helpers, again making the methods invisible to MobX flow inference.
179
+ *
180
+ * All of these transforms are unnecessary — our browser targets support default parameters,
181
+ * generators, and async functions natively.
182
+ */
167
183
  exclude: [
168
184
  "@babel/plugin-transform-async-to-generator",
169
185
  "@babel/plugin-transform-regenerator",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raisenow/tamaro-cli",
3
- "version": "1.9.0-dev.1",
3
+ "version": "1.9.0-dev.2",
4
4
  "author": {
5
5
  "name": "RaiseNow",
6
6
  "email": "development@raisenow.com"
@@ -9,7 +9,7 @@
9
9
  "bin": "dist/cli.js",
10
10
  "engines": {
11
11
  "node": ">=22.22.2",
12
- "pnpm": ">=10.33.0",
12
+ "pnpm": ">=10.33.2",
13
13
  "npm": "please-use-pnpm",
14
14
  "yarn": "please-use-pnpm"
15
15
  },
@@ -18,7 +18,7 @@
18
18
  "@babel/cli": "^7.28.6",
19
19
  "@babel/core": "^7.29.0",
20
20
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
21
- "@babel/preset-env": "^7.29.2",
21
+ "@babel/preset-env": "^7.29.3",
22
22
  "@babel/preset-react": "^7.28.5",
23
23
  "@babel/preset-typescript": "^7.28.5",
24
24
  "@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
@@ -54,7 +54,7 @@
54
54
  "handlebars": "^4.7.9",
55
55
  "handlebars-helpers": "^0.10.0",
56
56
  "html-loader": "^5.1.0",
57
- "html-webpack-plugin": "^5.6.6",
57
+ "html-webpack-plugin": "^5.6.7",
58
58
  "js-yaml": "^4.1.1",
59
59
  "json-loader": "^0.5.7",
60
60
  "ky": "^1.14.3",
@@ -63,7 +63,7 @@
63
63
  "node-notifier": "^10.0.1",
64
64
  "open": "^11.0.0",
65
65
  "portfinder": "^1.0.38",
66
- "postcss": "^8.5.9",
66
+ "postcss": "^8.5.13",
67
67
  "postcss-import": "^16.1.1",
68
68
  "postcss-loader": "^8.2.1",
69
69
  "postcss-nested": "^7.0.2",
@@ -74,13 +74,13 @@
74
74
  "sass-loader": "^16.0.7",
75
75
  "strip-indent": "^4.1.1",
76
76
  "style-loader": "^4.0.0",
77
- "terser-webpack-plugin": "^5.4.0",
77
+ "terser-webpack-plugin": "^5.5.0",
78
78
  "tsconfig-paths-webpack-plugin": "^4.2.0",
79
- "tsdown": "^0.21.8",
80
- "typescript": "^6.0.2",
79
+ "tsdown": "^0.21.10",
80
+ "typescript": "^6.0.3",
81
81
  "url-loader": "^4.1.1",
82
- "vitest": "^4.1.4",
83
- "webpack": "^5.106.1",
82
+ "vitest": "^4.1.5",
83
+ "webpack": "^5.106.2",
84
84
  "webpack-cli": "^7.0.2",
85
85
  "webpack-config-utils": "^2.3.1",
86
86
  "webpack-dev-server": "^5.2.3",
@@ -90,11 +90,11 @@
90
90
  "devDependencies": {
91
91
  "@rnw-npm/eslint-config": "^2.0.0",
92
92
  "@rnw-npm/prettier-config": "^1.0.0",
93
+ "eslint": "^10.3.0",
93
94
  "@types/js-yaml": "^4.0.9",
94
- "eslint": "^10.2.0",
95
95
  "husky": "^9.1.7",
96
96
  "lint-staged": "^16.4.0",
97
- "prettier": "^3.8.2"
97
+ "prettier": "^3.8.3"
98
98
  },
99
99
  "prettier": "@rnw-npm/prettier-config",
100
100
  "lint-staged": {
package/eslint.config.ts DELETED
@@ -1,30 +0,0 @@
1
- import {
2
- baseConfig,
3
- defineConfig,
4
- GLOB_MARKDOWN,
5
- GLOB_SRC,
6
- OFF,
7
- } from '@rnw-npm/eslint-config'
8
-
9
- export default defineConfig([
10
- ...baseConfig,
11
-
12
- /**
13
- * "tamaro-cli" specific overrides.
14
- */
15
- {
16
- files: [GLOB_SRC],
17
- name: 'tamaro-cli/overrides/ts',
18
- rules: {
19
- 'jsdoc/convert-to-jsdoc-comments': OFF,
20
- 'no-useless-assignment': OFF,
21
- },
22
- },
23
- {
24
- files: [GLOB_MARKDOWN],
25
- name: 'tamaro-cli/overrides/md',
26
- rules: {
27
- 'markdown/no-multiple-h1': OFF,
28
- },
29
- },
30
- ])