@raisenow/tamaro-cli 3.0.0 → 3.1.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
@@ -14,7 +14,8 @@ import open from "open";
14
14
  import { globSync } from "glob";
15
15
  import Handlebars from "handlebars";
16
16
  import helpers from "handlebars-helpers";
17
- import { config } from "dotenv";
17
+ import { codeFrameColumns } from "@babel/code-frame";
18
+ import { config, parse } from "dotenv";
18
19
  import { getPortPromise } from "portfinder";
19
20
  import prompts from "prompts";
20
21
  import columnify from "columnify";
@@ -545,6 +546,98 @@ const compileEmailTemplate = (template) => {
545
546
  Handlebars.compile(template)({});
546
547
  };
547
548
  //#endregion
549
+ //#region src/lib/validators/validateEnv.ts
550
+ const envSchema = z.strictObject({
551
+ CORE_URL: z.url().optional(),
552
+ CORE_URL_PATTERN: z.string().includes("{{version}}", { message: "Must contain the \"{{version}}\" placeholder" }).optional(),
553
+ CORE_VERSION: z.string().refine((version) => version === version.trim(), { message: "There must be no leading or trailing whitespace in the version string" }).refine((version) => version === version.toLowerCase(), { message: "The version string must be lowercase" }).optional(),
554
+ DISABLE_URL_VERSION_OVERRIDE: z.enum([
555
+ "true",
556
+ "false",
557
+ "1",
558
+ "0"
559
+ ]).optional(),
560
+ LOCAL_CORE: z.enum(["true", "false"]).optional()
561
+ });
562
+ const TAMARO_CORE_REGISTRY_URL = "https://registry.npmjs.org/@raisenow/tamaro-core";
563
+ /**
564
+ * Check that the configured version exists for "@raisenow/tamaro-core" on npm:
565
+ * as a dist-tag (e.g. "latest", "lts_2026"), an exact version, or a partial
566
+ * version ("2", "2.15") matching at least one published version.
567
+ */
568
+ const coreVersionExists = async (version) => {
569
+ const metadata = await ky.get(TAMARO_CORE_REGISTRY_URL, {
570
+ headers: { accept: "application/vnd.npm.install-v1+json" },
571
+ retry: 0,
572
+ timeout: DEFAULT_HTTP_TIMEOUT
573
+ }).json();
574
+ const distTags = Object.keys(metadata["dist-tags"] ?? {});
575
+ const versions = Object.keys(metadata.versions ?? {});
576
+ return distTags.includes(version) || versions.includes(version) || versions.some((existing) => existing.startsWith(`${version}.`));
577
+ };
578
+ /**
579
+ * Locate the line of a `KEY=` assignment in the .env source, so the error can
580
+ * be rendered as a code frame pointing at the value.
581
+ */
582
+ const findKeyLocation = (source, key) => {
583
+ const lines = source.split("\n");
584
+ const assignment = new RegExp(String.raw`^\s*${key}\s*=`);
585
+ const index = lines.findIndex((line) => assignment.test(line));
586
+ if (index === -1) return;
587
+ const line = index + 1;
588
+ const valueStart = lines[index].indexOf("=") + 1;
589
+ return {
590
+ end: {
591
+ column: lines[index].length,
592
+ line
593
+ },
594
+ start: {
595
+ column: valueStart,
596
+ line
597
+ }
598
+ };
599
+ };
600
+ /**
601
+ * Format an error for a `KEY=` assignment as a code frame pointing at the
602
+ * value, so it looks fancy like this:
603
+ *
604
+ * .env:2
605
+ * 1 | CORE_VERSION=LTS_2026
606
+ * 2 | CORE_URL_PATTERN=adsf
607
+ * | ^^^^ Must contain the "{{version}}" placeholder
608
+ */
609
+ const formatError = (dotEnvPath, source, key, message) => {
610
+ const location = findKeyLocation(source, key);
611
+ if (!location) return `${dotEnvPath}: ${key}: ${message}`;
612
+ const frame = codeFrameColumns(source, location, {
613
+ forceColor: true,
614
+ highlightCode: true,
615
+ message
616
+ });
617
+ return `\n${dotEnvPath}:${location.start.line}\n${frame}`;
618
+ };
619
+ /**
620
+ * Validate the environment variables in the config's .env file.
621
+ */
622
+ const validateEnv = async (dotEnvPath) => {
623
+ if (!fs.existsSync(dotEnvPath)) return [];
624
+ const source = fs.readFileSync(dotEnvPath, "utf8");
625
+ const parsed = parse(source);
626
+ const result = envSchema.safeParse(parsed);
627
+ if (!result.success) {
628
+ const errors = [];
629
+ for (const issue of result.error.issues) {
630
+ const keys = issue.code === "unrecognized_keys" ? issue.keys : [String(issue.path[0] ?? "")];
631
+ for (const key of keys) errors.push(formatError(dotEnvPath, source, key, issue.message));
632
+ }
633
+ return errors;
634
+ }
635
+ if (parsed.CORE_VERSION) {
636
+ if (await coreVersionExists(parsed.CORE_VERSION) === false) return [formatError(dotEnvPath, source, "CORE_VERSION", `Version "${parsed.CORE_VERSION}" does not exist. See all versions at https://www.npmjs.com/package/@raisenow/tamaro-core?activeTab=versions`)];
637
+ }
638
+ return [];
639
+ };
640
+ //#endregion
548
641
  //#region src/commands/validate.ts
549
642
  /**
550
643
  * Validates the current Tamaro configuration to avoid common errors.
@@ -554,8 +647,12 @@ const compileEmailTemplate = (template) => {
554
647
  * deployed.
555
648
  */
556
649
  const validate = async () => {
557
- const errors = validateEmailTemplates(globSync("email-config/templates/**/*.hbs").toSorted());
558
- if (errors.length > 0) halt(errors.map((el) => el.toString()).join("\n"));
650
+ const errors = [...await validateEnv(".env"), ...validateEmailTemplates(globSync("email-config/templates/**/*.hbs").toSorted())];
651
+ if (errors.length > 0) {
652
+ logError("❌ Validation failed with the following errors:");
653
+ console.log(errors.map((el) => el.toString()).join("\n"));
654
+ halt();
655
+ }
559
656
  logTitle("Validated successfully");
560
657
  };
561
658
  //#endregion
@@ -631,6 +728,7 @@ const getOutDir = (configName) => {
631
728
  //#region src/commands/update-epms.ts
632
729
  const updateEpms = async (options) => {
633
730
  const configName = getConfigName();
731
+ await validate();
634
732
  if (!options.profile && !options.ci) {
635
733
  assertAwsProfilesPresent();
636
734
  options.profile = await promptAwsProfile();
@@ -912,6 +1010,7 @@ const dev = async (options) => {
912
1010
  const configName = getConfigName();
913
1011
  const configsPackageRoot = getConfigsPackageRoot();
914
1012
  assertCrtExists(configsPackageRoot);
1013
+ await validate();
915
1014
  const port = await getPortPromise({ port: Number(DEFAULT_PORT) });
916
1015
  logTitle(`\nStarting Vite dev server for "${configName}" configuration...`);
917
1016
  runCommandSync(`pnpm vite dev --port ${port}`, {
@@ -1225,7 +1324,7 @@ const promptBucketTamaroEmailConfig = async () => {
1225
1324
  //#endregion
1226
1325
  //#region package.json
1227
1326
  var name = "@raisenow/tamaro-cli";
1228
- var version = "3.0.0";
1327
+ var version = "3.1.0";
1229
1328
  //#endregion
1230
1329
  //#region src/cli.ts
1231
1330
  const cli = createCommand().name(name).description("CLI for Tamaro Customer Configurations development").version(version, "-v, --version");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raisenow/tamaro-cli",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "author": {
5
5
  "name": "RaiseNow",
6
6
  "email": "development@raisenow.com"
@@ -13,7 +13,8 @@
13
13
  "yarn": "please-use-pnpm"
14
14
  },
15
15
  "dependencies": {
16
- "@aws-sdk/client-s3": "^3.1080.0",
16
+ "@aws-sdk/client-s3": "^3.1081.0",
17
+ "@babel/code-frame": "^8.0.0",
17
18
  "@rnw-npm/cli-helpers": "^2.0.1",
18
19
  "columnify": "^1.6.0",
19
20
  "commander": "^15.0.0",
@@ -1,6 +0,0 @@
1
- <component name="InspectionProjectProfileManager">
2
- <profile version="1.0">
3
- <option name="myName" value="Project Default" />
4
- <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
5
- </profile>
6
- </component>
package/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/tamaro-cli.iml" filepath="$PROJECT_DIR$/.idea/tamaro-cli.iml" />
6
- </modules>
7
- </component>
8
- </project>
@@ -1,12 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <module type="WEB_MODULE" version="4">
3
- <component name="NewModuleRootManager">
4
- <content url="file://$MODULE_DIR$">
5
- <excludeFolder url="file://$MODULE_DIR$/temp" />
6
- <excludeFolder url="file://$MODULE_DIR$/.tmp" />
7
- <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
- </content>
9
- <orderEntry type="inheritedJdk" />
10
- <orderEntry type="sourceFolder" forTests="false" />
11
- </component>
12
- </module>
package/.idea/vcs.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
- </component>
6
- </project>
@@ -1,62 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ChangeListManager">
4
- <list default="true" id="03cc3f97-55ca-4737-9fe2-779ac8457ab2" name="Changes" comment="" />
5
- <option name="SHOW_DIALOG" value="false" />
6
- <option name="HIGHLIGHT_CONFLICTS" value="true" />
7
- <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
8
- <option name="LAST_RESOLUTION" value="IGNORE" />
9
- </component>
10
- <component name="Git.Settings">
11
- <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
12
- </component>
13
- <component name="MarkdownSettingsMigration">
14
- <option name="stateVersion" value="1" />
15
- </component>
16
- <component name="ProjectId" id="3BwvHZOW2N4A0f0JXo7q8qDzYwY" />
17
- <component name="ProjectViewState">
18
- <option name="autoscrollFromSource" value="true" />
19
- <option name="autoscrollToSource" value="true" />
20
- <option name="hideEmptyMiddlePackages" value="true" />
21
- <option name="showLibraryContents" value="true" />
22
- <option name="showMembers" value="true" />
23
- </component>
24
- <component name="PropertiesComponent">{
25
- &quot;keyToString&quot;: {
26
- &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
27
- &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
28
- &quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
29
- &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
30
- &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
31
- &quot;nodejs_package_manager_path&quot;: &quot;pnpm&quot;,
32
- &quot;prettierjs.PrettierConfiguration.Package&quot;: &quot;/Users/sigerello/dev/_raisenow/tools/tamaro-cli/node_modules/prettier&quot;,
33
- &quot;ts.external.directory.path&quot;: &quot;/Users/sigerello/dev/_raisenow/tools/tamaro-cli/node_modules/typescript/lib&quot;,
34
- &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
35
- }
36
- }</component>
37
- <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
38
- <component name="TaskManager">
39
- <task active="true" id="Default" summary="Default task">
40
- <changelist id="03cc3f97-55ca-4737-9fe2-779ac8457ab2" name="Changes" comment="" />
41
- <created>1775412859839</created>
42
- <option name="number" value="Default" />
43
- <option name="presentableId" value="Default" />
44
- <updated>1775412859839</updated>
45
- <workItem from="1775412861312" duration="2060000" />
46
- <workItem from="1776174639969" duration="1525000" />
47
- <workItem from="1776736351878" duration="598000" />
48
- <workItem from="1777934504965" duration="82000" />
49
- <workItem from="1777936008103" duration="88000" />
50
- <workItem from="1778477038620" duration="2530000" />
51
- <workItem from="1780398242165" duration="1962000" />
52
- <workItem from="1781661280180" duration="2828000" />
53
- <workItem from="1782699840335" duration="599000" />
54
- <workItem from="1782869357470" duration="598000" />
55
- <workItem from="1783270034892" duration="1197000" />
56
- </task>
57
- <servers />
58
- </component>
59
- <component name="TypeScriptGeneratedFilesManager">
60
- <option name="version" value="3" />
61
- </component>
62
- </project>