@raisenow/tamaro-cli 1.9.0-dev.2 → 2.0.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/README.md +1 -1
- package/dist/cli.js +439 -399
- package/dist/{env-DBP70I1v.js → env-DZ3cWM6p.js} +9 -4
- package/dist/webpack.config.js +1 -1
- package/package.json +11 -9
- package/pnpm-workspace.yaml +7 -7
package/README.md
CHANGED
|
@@ -187,7 +187,7 @@ Deploy a pre-built _Tamaro Core_ or customer configuration bundle to AWS S3.
|
|
|
187
187
|
|
|
188
188
|
- `--dryrun` – Displays the operations that would be performed without actually running them
|
|
189
189
|
- `--tag <tag>` – Tag which should be used for the deployment of the bundle (default: "latest")
|
|
190
|
-
- `--skip-epms-sync
|
|
190
|
+
- `--force-skip-epms-sync` – Skip the EPMS sync entirely. Only use this in emergencies.
|
|
191
191
|
|
|
192
192
|
Make sure you have built the bundle before deploying it with this command, otherwise you may mistakenly deploy the
|
|
193
193
|
bundle from a previous build.
|
package/dist/cli.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as
|
|
2
|
+
import { A as EPMS_OAUTH_CLIENT_ID, B as logInfo, C as DEFAULT_HTTP_TIMEOUT, D as EPMS_API_BASE_URL_STAGE, E as EPMS_API_BASE_URL_PROD, F as runCommandSync, H as logTitle, I as createTerminalLink, L as logCommand, M as HTTPS_KEY_FILE, N as halt, O as EPMS_AUTH_BASE_URL_PROD, P as promptConfirmation, R as logDataTable, S as CORE_CONFIG_NAME, T as DEFAULT_TAG, V as logSuccess, _ as AWS_CLOUDFRONT_DISTRIBUTION_ID, a as getIfCoreFns, b as AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE, c as getWidgetUuid, d as resolveBin, f as resolveCoreVersion, g as AUTH_PORT, h as resolveOwn, j as HTTPS_CRT_FILE, k as EPMS_AUTH_BASE_URL_STAGE, l as resolveAccountUuidFromConfig, m as resolveManagedByFromConfig, n as assertEnvValid, o as getPaths, t as applyEnv, v as AWS_S3_BUCKET_TAMARO, w as DEFAULT_PORT, x as CACHE_DIR, y as AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD, z as logError } from "./env-DZ3cWM6p.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";
|
|
6
|
+
import stripIndent from "strip-indent";
|
|
6
7
|
import { S3 } from "@aws-sdk/client-s3";
|
|
7
8
|
import { execaCommandSync } from "execa";
|
|
8
9
|
import prompts from "prompts";
|
|
9
|
-
import stripIndent from "strip-indent";
|
|
10
10
|
import columnify from "columnify";
|
|
11
|
-
import
|
|
11
|
+
import * as yaml from "js-yaml";
|
|
12
12
|
import { z } from "zod";
|
|
13
|
+
import ky from "ky";
|
|
14
|
+
import { createServer } from "node:http";
|
|
15
|
+
import open from "open";
|
|
16
|
+
import notifier from "node-notifier";
|
|
13
17
|
import { globSync } from "glob";
|
|
14
18
|
import Handlebars from "handlebars";
|
|
15
19
|
import helpers from "handlebars-helpers";
|
|
16
20
|
import { config } from "dotenv";
|
|
17
|
-
import * as yaml from "js-yaml";
|
|
18
|
-
import ky from "ky";
|
|
19
|
-
import { createServer } from "node:http";
|
|
20
|
-
import open from "open";
|
|
21
21
|
import { getPortPromise } from "portfinder";
|
|
22
22
|
//#region src/lib/aws.ts
|
|
23
23
|
const awsAuthenticate = (options) => {
|
|
@@ -114,246 +114,6 @@ const getConfigDeployments = async (configName) => {
|
|
|
114
114
|
return deployments;
|
|
115
115
|
};
|
|
116
116
|
//#endregion
|
|
117
|
-
//#region src/lib/notifier.ts
|
|
118
|
-
const notify = (args) => {
|
|
119
|
-
const { message = "", title } = args;
|
|
120
|
-
notifier.notify({
|
|
121
|
-
contentImage: "https://assets.raisenow.io/favicon.png",
|
|
122
|
-
message: stripIndent(message).trim(),
|
|
123
|
-
sound: "Funk",
|
|
124
|
-
title
|
|
125
|
-
});
|
|
126
|
-
};
|
|
127
|
-
//#endregion
|
|
128
|
-
//#region src/commands/archive.ts
|
|
129
|
-
const archive = (options) => {
|
|
130
|
-
const { ifCore } = getIfCoreFns();
|
|
131
|
-
assertIsNotCore$4(ifCore);
|
|
132
|
-
assertIsNotArchived();
|
|
133
|
-
const configName = getWidgetUuid();
|
|
134
|
-
const cwd = process.cwd();
|
|
135
|
-
const parentDir = path.dirname(cwd);
|
|
136
|
-
const archiveDir = path.resolve(parentDir, "_archived");
|
|
137
|
-
const archiveTarget = path.resolve(archiveDir, configName);
|
|
138
|
-
assertArchiveTargetDoesNotExist(archiveTarget);
|
|
139
|
-
assertOptionsValid$9(options);
|
|
140
|
-
if (options.dryrun) logTitle("🧪 DRY RUN MODE - No actual changes will be made 🧪");
|
|
141
|
-
if (!options.dryrun) {
|
|
142
|
-
if (!existsSync(archiveDir)) mkdirSync(archiveDir, { recursive: true });
|
|
143
|
-
renameSync(cwd, archiveTarget);
|
|
144
|
-
} else logTitle(`Would move ${cwd} → ${archiveTarget}`);
|
|
145
|
-
logSuccess(`\n✅ "${configName}" has been archived and moved to ${archiveTarget}`);
|
|
146
|
-
process.on("exit", () => {
|
|
147
|
-
notify({
|
|
148
|
-
message: `"${configName}" has been archived.`,
|
|
149
|
-
title: "archive"
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
};
|
|
153
|
-
const assertOptionsValid$9 = (options) => {
|
|
154
|
-
const { profile } = options;
|
|
155
|
-
if (profile) assertProfileValid(profile);
|
|
156
|
-
};
|
|
157
|
-
const assertIsNotCore$4 = (ifCore) => {
|
|
158
|
-
if (ifCore()) halt("You cannot archive Tamaro Core.");
|
|
159
|
-
};
|
|
160
|
-
const assertIsNotArchived = () => {
|
|
161
|
-
if (path.basename(path.dirname(process.cwd())) === "_archived") halt("This configuration is already archived.");
|
|
162
|
-
};
|
|
163
|
-
const assertArchiveTargetDoesNotExist = (archiveTarget) => {
|
|
164
|
-
if (existsSync(archiveTarget)) halt(`Archive target already exists: ${archiveTarget}. Please remove it first.`);
|
|
165
|
-
};
|
|
166
|
-
//#endregion
|
|
167
|
-
//#region src/lib/validators/validateEmailTemplates.ts
|
|
168
|
-
/**
|
|
169
|
-
* Register custom Handlebars helpers - the same, which are used in the Email Service.
|
|
170
|
-
* Make sure to update it if new helpers are added in the Email Service.
|
|
171
|
-
*/
|
|
172
|
-
helpers.comparison({ handlebars: Handlebars });
|
|
173
|
-
/**
|
|
174
|
-
* Recurse through the expected email templates folder and attempt to parse
|
|
175
|
-
* handlebars template to validate their syntax.
|
|
176
|
-
*/
|
|
177
|
-
const validateEmailTemplates = (templatePaths) => {
|
|
178
|
-
const errors = [];
|
|
179
|
-
for (const templatePath of templatePaths) try {
|
|
180
|
-
const template = fs.readFileSync(templatePath, "utf8");
|
|
181
|
-
assureOnlySupportedHelpers(template);
|
|
182
|
-
compileEmailTemplate(template);
|
|
183
|
-
console.log(`${templatePath} is a valid email template`);
|
|
184
|
-
} catch (error) {
|
|
185
|
-
errors.push(`${templatePath} is invalid`);
|
|
186
|
-
errors.push(error);
|
|
187
|
-
}
|
|
188
|
-
return errors;
|
|
189
|
-
};
|
|
190
|
-
/**
|
|
191
|
-
* Validate that only supported Handlebars helpers are used in the template.
|
|
192
|
-
* This function extracts helpers from the template AST and checks them
|
|
193
|
-
* against a predefined set of supported helpers.
|
|
194
|
-
*/
|
|
195
|
-
const assureOnlySupportedHelpers = (template) => {
|
|
196
|
-
/**
|
|
197
|
-
* Recursively extract helpers from the AST
|
|
198
|
-
*/
|
|
199
|
-
const extractHelpers = (ast) => {
|
|
200
|
-
const usedHelpers = /* @__PURE__ */ new Set();
|
|
201
|
-
const extractHelperName = (node) => {
|
|
202
|
-
if (node.path?.type === "PathExpression" && (node.params?.length > 0 || node.hash?.pairs?.length > 0)) return node.path?.original;
|
|
203
|
-
};
|
|
204
|
-
const visit = (node) => {
|
|
205
|
-
if (!node) return;
|
|
206
|
-
switch (node.type) {
|
|
207
|
-
case "BlockStatement":
|
|
208
|
-
case "MustacheStatement": {
|
|
209
|
-
const name = extractHelperName(node);
|
|
210
|
-
if (name) usedHelpers.add(name);
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Recursively visit children of the AST node.
|
|
216
|
-
*/
|
|
217
|
-
for (const key in node) {
|
|
218
|
-
const value = node[key];
|
|
219
|
-
if (Array.isArray(value)) value.forEach((v) => visit(v));
|
|
220
|
-
else if (value && typeof value === "object" && value.type) visit(value);
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
visit(ast);
|
|
224
|
-
return usedHelpers;
|
|
225
|
-
};
|
|
226
|
-
const usedHelpers = extractHelpers(Handlebars.parse(template));
|
|
227
|
-
const supportedHelpers = new Set([
|
|
228
|
-
"if",
|
|
229
|
-
"unless",
|
|
230
|
-
"each",
|
|
231
|
-
"with",
|
|
232
|
-
"log",
|
|
233
|
-
"lookup",
|
|
234
|
-
"and",
|
|
235
|
-
"compare",
|
|
236
|
-
"contains",
|
|
237
|
-
"default",
|
|
238
|
-
"eq",
|
|
239
|
-
"gt",
|
|
240
|
-
"gte",
|
|
241
|
-
"has",
|
|
242
|
-
"isFalsey",
|
|
243
|
-
"isTruthy",
|
|
244
|
-
"ifEven",
|
|
245
|
-
"ifNth",
|
|
246
|
-
"ifOdd",
|
|
247
|
-
"is",
|
|
248
|
-
"isnt",
|
|
249
|
-
"lt",
|
|
250
|
-
"lte",
|
|
251
|
-
"neither",
|
|
252
|
-
"not",
|
|
253
|
-
"or",
|
|
254
|
-
"unlessEq",
|
|
255
|
-
"unlessGt",
|
|
256
|
-
"unlessLt",
|
|
257
|
-
"unlessGteq",
|
|
258
|
-
"unlessLteq"
|
|
259
|
-
]);
|
|
260
|
-
const unsupportedHelpers = [...usedHelpers].filter((h) => !supportedHelpers.has(h));
|
|
261
|
-
if (unsupportedHelpers.length > 0) throw new Error(`Email template uses unsupported Handlebars helpers: ${unsupportedHelpers.join(", ")}`);
|
|
262
|
-
};
|
|
263
|
-
/**
|
|
264
|
-
* Compile Email template and pass an empty context to validate syntax.
|
|
265
|
-
*/
|
|
266
|
-
const compileEmailTemplate = (template) => {
|
|
267
|
-
Handlebars.compile(template)({});
|
|
268
|
-
};
|
|
269
|
-
//#endregion
|
|
270
|
-
//#region src/commands/validate.ts
|
|
271
|
-
/**
|
|
272
|
-
* Validates the current Tamaro configuration to avoid common errors.
|
|
273
|
-
*
|
|
274
|
-
* The command description is kept generic on purpose. We check for a number
|
|
275
|
-
* of mistakes observed when tamaro configurations are manually edited and
|
|
276
|
-
* deployed.
|
|
277
|
-
*/
|
|
278
|
-
const validate = async () => {
|
|
279
|
-
const errors = validateEmailTemplates(globSync("email-config/templates/**/*.hbs").toSorted());
|
|
280
|
-
if (errors.length > 0) halt(errors.map((el) => el.toString()).join("\n"));
|
|
281
|
-
logTitle("Validated successfully");
|
|
282
|
-
};
|
|
283
|
-
//#endregion
|
|
284
|
-
//#region src/lib/https.ts
|
|
285
|
-
const assertCrtExists = () => {
|
|
286
|
-
const { ifCore } = getIfCoreFns();
|
|
287
|
-
const paths = getPaths(ifCore);
|
|
288
|
-
const certFile = path.resolve(paths.root, HTTPS_CRT_FILE);
|
|
289
|
-
const keyFile = path.resolve(paths.root, HTTPS_KEY_FILE);
|
|
290
|
-
if (existsSync(certFile) && existsSync(keyFile)) return;
|
|
291
|
-
halt(stripIndent(`
|
|
292
|
-
Flag "--https" is used, but "${HTTPS_CRT_FILE}" and/or "${HTTPS_KEY_FILE}" files are not found.
|
|
293
|
-
You need to generate certificate first.
|
|
294
|
-
Check docs: https://unpkg.com/@raisenow/tamaro-cli/readme.html#/?id=use-transport-level-security-https
|
|
295
|
-
`));
|
|
296
|
-
};
|
|
297
|
-
//#endregion
|
|
298
|
-
//#region src/commands/build.ts
|
|
299
|
-
const build = async (options) => {
|
|
300
|
-
const { env } = options;
|
|
301
|
-
applyEnv(env);
|
|
302
|
-
if (options.deploy && !options.profile && !options.ci) {
|
|
303
|
-
assertProfilesPresent();
|
|
304
|
-
options.profile = await promptProfile();
|
|
305
|
-
}
|
|
306
|
-
assertOptionsValid$8(options);
|
|
307
|
-
await validate();
|
|
308
|
-
const flags = prepareFlags$2(options);
|
|
309
|
-
const { ifCore } = getIfCoreFns();
|
|
310
|
-
const paths = getPaths(ifCore);
|
|
311
|
-
const configName = ifCore(CORE_CONFIG_NAME, getWidgetUuid());
|
|
312
|
-
const title = ifCore("Building optimised (minified) bundle of Tamaro Core …", `Building optimised (minified) bundle of "${configName}" customer configuration …`);
|
|
313
|
-
const cmd = `
|
|
314
|
-
${resolveBin("webpack")}
|
|
315
|
-
--config ${resolveOwn("dist/webpack.config.js")}
|
|
316
|
-
--env min
|
|
317
|
-
${flags}
|
|
318
|
-
`;
|
|
319
|
-
logTitle(title);
|
|
320
|
-
logCommand(cmd);
|
|
321
|
-
runCommandSync(cmd, { stdio: "inherit" });
|
|
322
|
-
logTitle(`\nBundle for "${configName}" customer configuration is done.`);
|
|
323
|
-
console.log(`Path: ${paths.appDist}\n`);
|
|
324
|
-
process.on("exit", () => {
|
|
325
|
-
notify({
|
|
326
|
-
message: `Bundle for "${configName}" customer configuration is done.`,
|
|
327
|
-
title: "build"
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
};
|
|
331
|
-
const assertOptionsValid$8 = (options) => {
|
|
332
|
-
const { deploy, env, https, localCore, profile } = options;
|
|
333
|
-
const { ifCore } = getIfCoreFns();
|
|
334
|
-
if (ifCore() && localCore) halt("Flag \"--local-core\" is redundant if running in Tamaro Core context.");
|
|
335
|
-
if (localCore && deploy) halt("Flags \"--local-core\" and \"--deploy\" must not be used together.");
|
|
336
|
-
if (https) assertCrtExists();
|
|
337
|
-
if (profile) assertProfileValid(profile);
|
|
338
|
-
assertEnvValid(env);
|
|
339
|
-
};
|
|
340
|
-
const prepareFlags$2 = (options) => {
|
|
341
|
-
const flags = [];
|
|
342
|
-
const { analyze, debug, https, localCore, nolint } = options;
|
|
343
|
-
if (localCore) flags.push("--env localCore");
|
|
344
|
-
if (analyze) flags.push("--env analyze");
|
|
345
|
-
if (https) flags.push("--env https");
|
|
346
|
-
if (nolint) flags.push("--env nolint");
|
|
347
|
-
if (debug) flags.push("--env debug");
|
|
348
|
-
return flags.join(" ");
|
|
349
|
-
};
|
|
350
|
-
//#endregion
|
|
351
|
-
//#region src/lib/validators/assertTagValid.ts
|
|
352
|
-
const assertTagValid = (tag) => {
|
|
353
|
-
const regex = /^[\w.-]+$/;
|
|
354
|
-
if (!regex.test(tag)) halt(`Flag "--tag" has forbidden format. Allowed format: ${regex.toString()}.`);
|
|
355
|
-
};
|
|
356
|
-
//#endregion
|
|
357
117
|
//#region src/lib/epms/auth/util.ts
|
|
358
118
|
const CachedTokenSchema = z.object({
|
|
359
119
|
expirationTime: z.number(),
|
|
@@ -538,121 +298,396 @@ const createBrowserAuthorizer = (authBaseUrl, clientId, epmsBaseUrl) => {
|
|
|
538
298
|
} });
|
|
539
299
|
};
|
|
540
300
|
//#endregion
|
|
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
|
|
301
|
+
//#region src/lib/epms/myselfSchema.ts
|
|
302
|
+
const myselfSchema = z.object({
|
|
303
|
+
email: z.string(),
|
|
304
|
+
identity_roles: z.array(z.object({
|
|
305
|
+
identity_uuid: z.string(),
|
|
306
|
+
role: z.object({
|
|
307
|
+
name: z.string(),
|
|
308
|
+
uuid: z.string()
|
|
309
|
+
})
|
|
310
|
+
})),
|
|
311
|
+
uuid: z.string()
|
|
312
|
+
});
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region src/lib/epms/epmsClient.ts
|
|
315
|
+
/**
|
|
316
|
+
* Manages authentication and API interactions with EPMS.
|
|
317
|
+
* It provides factory methods for creating instances based on different authentication flows (client credentials or browser-based) and handles token caching and renewal transparently.
|
|
318
|
+
*/
|
|
319
|
+
var EpmsClient = class EpmsClient {
|
|
320
|
+
#cacheKey;
|
|
321
|
+
#epmsClient;
|
|
322
|
+
#authType;
|
|
323
|
+
/**
|
|
324
|
+
* Creates an instance of EpmsClient.
|
|
325
|
+
*
|
|
326
|
+
* @param authType The type of authentication used by the client.
|
|
327
|
+
* @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.
|
|
328
|
+
* @param epmsClient The underlying KyInstance used for making API requests.
|
|
329
|
+
*/
|
|
330
|
+
constructor(authType, cacheKey, epmsClient) {
|
|
331
|
+
this.#authType = authType;
|
|
332
|
+
this.#cacheKey = cacheKey;
|
|
333
|
+
this.#epmsClient = epmsClient;
|
|
334
|
+
}
|
|
335
|
+
static fromClientCredentials(baseUrl, clientId, clientSecret) {
|
|
336
|
+
return new EpmsClient("client_credentials", getTokenCacheKey(baseUrl, clientId), createAuthorizer(baseUrl, clientId, clientSecret));
|
|
337
|
+
}
|
|
338
|
+
static fromBrowserAuth(epmsBaseUrl, authBaseUrl, clientId) {
|
|
339
|
+
return new EpmsClient("browser", getTokenCacheKey(authBaseUrl, clientId), createBrowserAuthorizer(authBaseUrl, clientId, epmsBaseUrl));
|
|
340
|
+
}
|
|
341
|
+
get authType() {
|
|
342
|
+
return this.#authType;
|
|
343
|
+
}
|
|
344
|
+
logout() {
|
|
345
|
+
removeCachedToken(this.#cacheKey);
|
|
346
|
+
}
|
|
347
|
+
async myself() {
|
|
348
|
+
return this.#epmsClient.get("users/myself").json().then((response) => myselfSchema.parse(response));
|
|
349
|
+
}
|
|
350
|
+
async getOrganisationIdByAccountUuid(accountUuid) {
|
|
351
|
+
try {
|
|
352
|
+
const organisationUuid = (await this.#epmsClient.get(`accounts/${accountUuid}`).json()).organisation.uuid;
|
|
353
|
+
if (!organisationUuid) throw new Error("Organisation UUID not found in response");
|
|
354
|
+
return organisationUuid;
|
|
355
|
+
} catch (error) {
|
|
356
|
+
throw new Error(`Organisation UUID not found for account UUID: ${accountUuid}`, { cause: error });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async updateTamaro(configName, tag, json) {
|
|
360
|
+
return this.#epmsClient.put(`products/tamaro/${configName}/tags/${tag}`, { json }).json();
|
|
361
|
+
}
|
|
362
|
+
async archiveTamaro(configName) {
|
|
363
|
+
return this.#epmsClient.post(`products/tamaro/${configName}/archive`).json();
|
|
364
|
+
}
|
|
365
|
+
async unarchiveTamaro(configName) {
|
|
366
|
+
return this.#epmsClient.post(`products/tamaro/${configName}/unarchive`).json();
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
//#endregion
|
|
370
|
+
//#region src/lib/epms/helpers.ts
|
|
371
|
+
const epmsAuthenticate = (env) => {
|
|
372
|
+
const { authBaseUrl, baseUrl, clientId, clientSecret } = {
|
|
373
|
+
prod: {
|
|
374
|
+
authBaseUrl: EPMS_AUTH_BASE_URL_PROD,
|
|
375
|
+
baseUrl: EPMS_API_BASE_URL_PROD,
|
|
376
|
+
clientId: process.env.EPMS_CLIENT_ID,
|
|
377
|
+
clientSecret: process.env.EPMS_CLIENT_SECRET
|
|
378
|
+
},
|
|
379
|
+
stage: {
|
|
380
|
+
authBaseUrl: EPMS_AUTH_BASE_URL_STAGE,
|
|
381
|
+
baseUrl: EPMS_API_BASE_URL_STAGE,
|
|
382
|
+
clientId: process.env.EPMS_CLIENT_ID_STAGE,
|
|
383
|
+
clientSecret: process.env.EPMS_CLIENT_SECRET_STAGE
|
|
384
|
+
}
|
|
385
|
+
}[env];
|
|
386
|
+
if (clientId && clientSecret) return EpmsClient.fromClientCredentials(baseUrl, clientId, clientSecret);
|
|
387
|
+
return EpmsClient.fromBrowserAuth(baseUrl, authBaseUrl, EPMS_OAUTH_CLIENT_ID);
|
|
388
|
+
};
|
|
389
|
+
const loadTamaroMetadataFromConfig = (paths) => {
|
|
390
|
+
if (!("configYml" in paths)) {
|
|
391
|
+
halt("Fatal error, this should never happen");
|
|
392
|
+
throw new Error("Fatal error");
|
|
393
|
+
}
|
|
394
|
+
const configContent = yaml.load(readFileSync(paths.configYml, "utf8"));
|
|
395
|
+
const accountUuidStage = resolveAccountUuidFromConfig(configContent, "epms_stage");
|
|
396
|
+
return {
|
|
397
|
+
accountUuidProd: resolveAccountUuidFromConfig(configContent, "epms"),
|
|
398
|
+
accountUuidStage,
|
|
399
|
+
managedBy: resolveManagedByFromConfig(configContent)
|
|
400
|
+
};
|
|
401
|
+
};
|
|
402
|
+
const assertUserIsSuperAdmin = async (epmsClient) => {
|
|
403
|
+
if (epmsClient.authType === "client_credentials") return;
|
|
404
|
+
const myself = await epmsClient.myself();
|
|
405
|
+
if (!myself.identity_roles.some(({ role }) => role.name === "super_admin")) {
|
|
406
|
+
epmsClient.logout();
|
|
407
|
+
logError("\nYou must be a super admin to perform this action.");
|
|
408
|
+
logTitle("\nYour EPMS user details:");
|
|
409
|
+
logDataTable({
|
|
410
|
+
email: myself.email,
|
|
411
|
+
roles: myself.identity_roles.map(({ role }) => role.name).join(", "),
|
|
412
|
+
uuid: myself.uuid
|
|
413
|
+
});
|
|
414
|
+
logError("If this keeps happening, go to Configurator / Hub and logout before trying again.");
|
|
415
|
+
halt();
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
//#endregion
|
|
419
|
+
//#region src/lib/notifier.ts
|
|
420
|
+
const notify = (args) => {
|
|
421
|
+
const { message = "", title } = args;
|
|
422
|
+
notifier.notify({
|
|
423
|
+
contentImage: "https://assets.raisenow.io/favicon.png",
|
|
424
|
+
message: stripIndent(message).trim(),
|
|
425
|
+
sound: "Funk",
|
|
426
|
+
title
|
|
427
|
+
});
|
|
428
|
+
};
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region src/commands/archive.ts
|
|
431
|
+
const archive = async (options) => {
|
|
432
|
+
const { ifCore } = getIfCoreFns();
|
|
433
|
+
assertIsNotCore$4(ifCore);
|
|
434
|
+
assertIsNotArchived();
|
|
435
|
+
const configName = getWidgetUuid();
|
|
436
|
+
const cwd = process.cwd();
|
|
437
|
+
const parentDir = path.dirname(cwd);
|
|
438
|
+
const archiveDir = path.resolve(parentDir, "_archived");
|
|
439
|
+
const archiveTarget = path.resolve(archiveDir, configName);
|
|
440
|
+
assertArchiveTargetDoesNotExist(archiveTarget);
|
|
441
|
+
assertOptionsValid$9(options);
|
|
442
|
+
if (options.dryrun) logTitle("🧪 DRY RUN MODE - No actual changes will be made 🧪");
|
|
443
|
+
if (!ifCore() && !options.forceSkipEpmsSync) await archiveEpms(configName, !!options.dryrun);
|
|
444
|
+
if (!options.dryrun) {
|
|
445
|
+
if (!existsSync(archiveDir)) mkdirSync(archiveDir, { recursive: true });
|
|
446
|
+
renameSync(cwd, archiveTarget);
|
|
447
|
+
} else logTitle(`Would move ${cwd} → ${archiveTarget}`);
|
|
448
|
+
logSuccess(`\n✅ "${configName}" has been archived and moved to ${archiveTarget}`);
|
|
449
|
+
process.on("exit", () => {
|
|
450
|
+
notify({
|
|
451
|
+
message: `"${configName}" has been archived.`,
|
|
452
|
+
title: "archive"
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
};
|
|
456
|
+
const archiveEpms = async (configName, dryrun) => {
|
|
457
|
+
const { ifCore } = getIfCoreFns();
|
|
458
|
+
const { accountUuidProd, accountUuidStage } = loadTamaroMetadataFromConfig(getPaths(ifCore));
|
|
459
|
+
if (!accountUuidProd && !accountUuidStage) halt(stripIndent(`
|
|
460
|
+
No EPMS account UUIDs found in config.yml.
|
|
461
|
+
Add them to your config.yml, for example:
|
|
462
|
+
epms:
|
|
463
|
+
account_uuid: <UUID>
|
|
464
|
+
epms_stage:
|
|
465
|
+
account_uuid: <UUID>
|
|
466
|
+
|
|
467
|
+
Or re-run with --force-skip-epms-sync to skip the EPMS sync entirely - ONLY use in emergencies / or if you know what you're doing.
|
|
468
|
+
`));
|
|
469
|
+
const environments = [{
|
|
470
|
+
accountUuid: accountUuidStage,
|
|
471
|
+
env: "stage",
|
|
472
|
+
label: "Stage"
|
|
473
|
+
}, {
|
|
474
|
+
accountUuid: accountUuidProd,
|
|
475
|
+
env: "prod",
|
|
476
|
+
label: "Prod"
|
|
477
|
+
}];
|
|
478
|
+
for (const { accountUuid, env, label } of environments) {
|
|
479
|
+
if (!accountUuid) continue;
|
|
480
|
+
const epmsClient = epmsAuthenticate(env);
|
|
481
|
+
await assertUserIsSuperAdmin(epmsClient);
|
|
482
|
+
logTitle(`\nArchiving EPMS ${label}: ${configName}`);
|
|
483
|
+
if (!dryrun) {
|
|
484
|
+
await epmsClient.archiveTamaro(configName);
|
|
485
|
+
logSuccess(`✅ EPMS ${label} entry archived.`);
|
|
486
|
+
} else logSuccess(`⏭️ EPMS ${label} entry would be archived (dry run).`);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
const assertOptionsValid$9 = (options) => {
|
|
490
|
+
const { profile } = options;
|
|
491
|
+
if (profile) assertProfileValid(profile);
|
|
492
|
+
};
|
|
493
|
+
const assertIsNotCore$4 = (ifCore) => {
|
|
494
|
+
if (ifCore()) halt("You cannot archive Tamaro Core.");
|
|
495
|
+
};
|
|
496
|
+
const assertIsNotArchived = () => {
|
|
497
|
+
if (path.basename(path.dirname(process.cwd())) === "_archived") halt("This configuration is already archived.");
|
|
498
|
+
};
|
|
499
|
+
const assertArchiveTargetDoesNotExist = (archiveTarget) => {
|
|
500
|
+
if (existsSync(archiveTarget)) halt(`Archive target already exists: ${archiveTarget}. Please remove it first.`);
|
|
501
|
+
};
|
|
502
|
+
//#endregion
|
|
503
|
+
//#region src/lib/validators/validateEmailTemplates.ts
|
|
504
|
+
/**
|
|
505
|
+
* Register custom Handlebars helpers - the same, which are used in the Email Service.
|
|
506
|
+
* Make sure to update it if new helpers are added in the Email Service.
|
|
507
|
+
*/
|
|
508
|
+
helpers.comparison({ handlebars: Handlebars });
|
|
509
|
+
/**
|
|
510
|
+
* Recurse through the expected email templates folder and attempt to parse
|
|
511
|
+
* handlebars template to validate their syntax.
|
|
512
|
+
*/
|
|
513
|
+
const validateEmailTemplates = (templatePaths) => {
|
|
514
|
+
const errors = [];
|
|
515
|
+
for (const templatePath of templatePaths) try {
|
|
516
|
+
const template = fs.readFileSync(templatePath, "utf8");
|
|
517
|
+
assureOnlySupportedHelpers(template);
|
|
518
|
+
compileEmailTemplate(template);
|
|
519
|
+
console.log(`${templatePath} is a valid email template`);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
errors.push(`${templatePath} is invalid`);
|
|
522
|
+
errors.push(error);
|
|
523
|
+
}
|
|
524
|
+
return errors;
|
|
525
|
+
};
|
|
526
|
+
/**
|
|
527
|
+
* Validate that only supported Handlebars helpers are used in the template.
|
|
528
|
+
* This function extracts helpers from the template AST and checks them
|
|
529
|
+
* against a predefined set of supported helpers.
|
|
530
|
+
*/
|
|
531
|
+
const assureOnlySupportedHelpers = (template) => {
|
|
532
|
+
/**
|
|
533
|
+
* Recursively extract helpers from the AST
|
|
534
|
+
*/
|
|
535
|
+
const extractHelpers = (ast) => {
|
|
536
|
+
const usedHelpers = /* @__PURE__ */ new Set();
|
|
537
|
+
const extractHelperName = (node) => {
|
|
538
|
+
if (node.path?.type === "PathExpression" && (node.params?.length > 0 || node.hash?.pairs?.length > 0)) return node.path?.original;
|
|
539
|
+
};
|
|
540
|
+
const visit = (node) => {
|
|
541
|
+
if (!node) return;
|
|
542
|
+
switch (node.type) {
|
|
543
|
+
case "BlockStatement":
|
|
544
|
+
case "MustacheStatement": {
|
|
545
|
+
const name = extractHelperName(node);
|
|
546
|
+
if (name) usedHelpers.add(name);
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Recursively visit children of the AST node.
|
|
552
|
+
*/
|
|
553
|
+
for (const key in node) {
|
|
554
|
+
const value = node[key];
|
|
555
|
+
if (Array.isArray(value)) value.forEach((v) => visit(v));
|
|
556
|
+
else if (value && typeof value === "object" && value.type) visit(value);
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
visit(ast);
|
|
560
|
+
return usedHelpers;
|
|
561
|
+
};
|
|
562
|
+
const usedHelpers = extractHelpers(Handlebars.parse(template));
|
|
563
|
+
const supportedHelpers = new Set([
|
|
564
|
+
"if",
|
|
565
|
+
"unless",
|
|
566
|
+
"each",
|
|
567
|
+
"with",
|
|
568
|
+
"log",
|
|
569
|
+
"lookup",
|
|
570
|
+
"and",
|
|
571
|
+
"compare",
|
|
572
|
+
"contains",
|
|
573
|
+
"default",
|
|
574
|
+
"eq",
|
|
575
|
+
"gt",
|
|
576
|
+
"gte",
|
|
577
|
+
"has",
|
|
578
|
+
"isFalsey",
|
|
579
|
+
"isTruthy",
|
|
580
|
+
"ifEven",
|
|
581
|
+
"ifNth",
|
|
582
|
+
"ifOdd",
|
|
583
|
+
"is",
|
|
584
|
+
"isnt",
|
|
585
|
+
"lt",
|
|
586
|
+
"lte",
|
|
587
|
+
"neither",
|
|
588
|
+
"not",
|
|
589
|
+
"or",
|
|
590
|
+
"unlessEq",
|
|
591
|
+
"unlessGt",
|
|
592
|
+
"unlessLt",
|
|
593
|
+
"unlessGteq",
|
|
594
|
+
"unlessLteq"
|
|
595
|
+
]);
|
|
596
|
+
const unsupportedHelpers = [...usedHelpers].filter((h) => !supportedHelpers.has(h));
|
|
597
|
+
if (unsupportedHelpers.length > 0) throw new Error(`Email template uses unsupported Handlebars helpers: ${unsupportedHelpers.join(", ")}`);
|
|
598
|
+
};
|
|
599
|
+
/**
|
|
600
|
+
* Compile Email template and pass an empty context to validate syntax.
|
|
601
|
+
*/
|
|
602
|
+
const compileEmailTemplate = (template) => {
|
|
603
|
+
Handlebars.compile(template)({});
|
|
604
|
+
};
|
|
605
|
+
//#endregion
|
|
606
|
+
//#region src/commands/validate.ts
|
|
555
607
|
/**
|
|
556
|
-
*
|
|
557
|
-
*
|
|
608
|
+
* Validates the current Tamaro configuration to avoid common errors.
|
|
609
|
+
*
|
|
610
|
+
* The command description is kept generic on purpose. We check for a number
|
|
611
|
+
* of mistakes observed when tamaro configurations are manually edited and
|
|
612
|
+
* deployed.
|
|
558
613
|
*/
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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;
|
|
573
|
-
this.#epmsClient = epmsClient;
|
|
574
|
-
}
|
|
575
|
-
static fromClientCredentials(baseUrl, clientId, clientSecret) {
|
|
576
|
-
return new EpmsClient("client_credentials", getTokenCacheKey(baseUrl, clientId), createAuthorizer(baseUrl, clientId, clientSecret));
|
|
577
|
-
}
|
|
578
|
-
static fromBrowserAuth(epmsBaseUrl, authBaseUrl, clientId) {
|
|
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));
|
|
589
|
-
}
|
|
590
|
-
async getOrganisationIdByAccountUuid(accountUuid) {
|
|
591
|
-
const query = {
|
|
592
|
-
from: 0,
|
|
593
|
-
query: { $and: [{ $term: { object_uuid: accountUuid } }, { $term: { object: "account" } }] },
|
|
594
|
-
size: 1
|
|
595
|
-
};
|
|
596
|
-
const response = await this.#epmsClient.post("search/events", { json: query }).json();
|
|
597
|
-
if (response.hits.length === 0) throw new Error(`No organisation found for account UUID: ${accountUuid}`);
|
|
598
|
-
const organisationId = response.hits[0].organisation_uuid;
|
|
599
|
-
if (!organisationId) throw new Error(`Organisation UUID not found for account UUID: ${accountUuid}`);
|
|
600
|
-
return organisationId;
|
|
601
|
-
}
|
|
602
|
-
async updateTamaro(configName, tag, json) {
|
|
603
|
-
return this.#epmsClient.put(`products/tamaro/${configName}/tags/${tag}`, { json }).json();
|
|
604
|
-
}
|
|
605
|
-
async archiveTamaro(configName, tag) {
|
|
606
|
-
return this.#epmsClient.post(`products/tamaro/${configName}/tags/${tag}/archive`).json();
|
|
607
|
-
}
|
|
614
|
+
const validate = async () => {
|
|
615
|
+
const errors = validateEmailTemplates(globSync("email-config/templates/**/*.hbs").toSorted());
|
|
616
|
+
if (errors.length > 0) halt(errors.map((el) => el.toString()).join("\n"));
|
|
617
|
+
logTitle("Validated successfully");
|
|
608
618
|
};
|
|
609
619
|
//#endregion
|
|
610
|
-
//#region src/lib/
|
|
611
|
-
const
|
|
612
|
-
const {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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);
|
|
620
|
+
//#region src/lib/https.ts
|
|
621
|
+
const assertCrtExists = () => {
|
|
622
|
+
const { ifCore } = getIfCoreFns();
|
|
623
|
+
const paths = getPaths(ifCore);
|
|
624
|
+
const certFile = path.resolve(paths.root, HTTPS_CRT_FILE);
|
|
625
|
+
const keyFile = path.resolve(paths.root, HTTPS_KEY_FILE);
|
|
626
|
+
if (existsSync(certFile) && existsSync(keyFile)) return;
|
|
627
|
+
halt(stripIndent(`
|
|
628
|
+
Flag "--https" is used, but "${HTTPS_CRT_FILE}" and/or "${HTTPS_KEY_FILE}" files are not found.
|
|
629
|
+
You need to generate certificate first.
|
|
630
|
+
Check docs: https://unpkg.com/@raisenow/tamaro-cli/readme.html#/?id=use-transport-level-security-https
|
|
631
|
+
`));
|
|
628
632
|
};
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
+
//#endregion
|
|
634
|
+
//#region src/commands/build.ts
|
|
635
|
+
const build = async (options) => {
|
|
636
|
+
const { env } = options;
|
|
637
|
+
applyEnv(env);
|
|
638
|
+
if (options.deploy && !options.profile && !options.ci) {
|
|
639
|
+
assertProfilesPresent();
|
|
640
|
+
options.profile = await promptProfile();
|
|
633
641
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
};
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
642
|
+
assertOptionsValid$8(options);
|
|
643
|
+
await validate();
|
|
644
|
+
const flags = prepareFlags$2(options);
|
|
645
|
+
const { ifCore } = getIfCoreFns();
|
|
646
|
+
const paths = getPaths(ifCore);
|
|
647
|
+
const configName = ifCore(CORE_CONFIG_NAME, getWidgetUuid());
|
|
648
|
+
const title = ifCore("Building optimised (minified) bundle of Tamaro Core …", `Building optimised (minified) bundle of "${configName}" customer configuration …`);
|
|
649
|
+
const cmd = `
|
|
650
|
+
${resolveBin("webpack")}
|
|
651
|
+
--config ${resolveOwn("dist/webpack.config.js")}
|
|
652
|
+
--env min
|
|
653
|
+
${flags}
|
|
654
|
+
`;
|
|
655
|
+
logTitle(title);
|
|
656
|
+
logCommand(cmd);
|
|
657
|
+
runCommandSync(cmd, { stdio: "inherit" });
|
|
658
|
+
logTitle(`\nBundle for "${configName}" customer configuration is done.`);
|
|
659
|
+
console.log(`Path: ${paths.appDist}\n`);
|
|
660
|
+
process.on("exit", () => {
|
|
661
|
+
notify({
|
|
662
|
+
message: `Bundle for "${configName}" customer configuration is done.`,
|
|
663
|
+
title: "build"
|
|
652
664
|
});
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
665
|
+
});
|
|
666
|
+
};
|
|
667
|
+
const assertOptionsValid$8 = (options) => {
|
|
668
|
+
const { deploy, env, https, localCore, profile } = options;
|
|
669
|
+
const { ifCore } = getIfCoreFns();
|
|
670
|
+
if (ifCore() && localCore) halt("Flag \"--local-core\" is redundant if running in Tamaro Core context.");
|
|
671
|
+
if (localCore && deploy) halt("Flags \"--local-core\" and \"--deploy\" must not be used together.");
|
|
672
|
+
if (https) assertCrtExists();
|
|
673
|
+
if (profile) assertProfileValid(profile);
|
|
674
|
+
assertEnvValid(env);
|
|
675
|
+
};
|
|
676
|
+
const prepareFlags$2 = (options) => {
|
|
677
|
+
const flags = [];
|
|
678
|
+
const { analyze, debug, https, localCore, nolint } = options;
|
|
679
|
+
if (localCore) flags.push("--env localCore");
|
|
680
|
+
if (analyze) flags.push("--env analyze");
|
|
681
|
+
if (https) flags.push("--env https");
|
|
682
|
+
if (nolint) flags.push("--env nolint");
|
|
683
|
+
if (debug) flags.push("--env debug");
|
|
684
|
+
return flags.join(" ");
|
|
685
|
+
};
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region src/lib/validators/assertTagValid.ts
|
|
688
|
+
const assertTagValid = (tag) => {
|
|
689
|
+
const regex = /^[\w.-]+$/;
|
|
690
|
+
if (!regex.test(tag)) halt(`Flag "--tag" has forbidden format. Allowed format: ${regex.toString()}.`);
|
|
656
691
|
};
|
|
657
692
|
//#endregion
|
|
658
693
|
//#region src/commands/update-epms.ts
|
|
@@ -672,7 +707,7 @@ const updateEpms = async (options) => {
|
|
|
672
707
|
const { dryrun, tag } = options;
|
|
673
708
|
const configName = getWidgetUuid();
|
|
674
709
|
loadEnv(paths);
|
|
675
|
-
const { accountUuidProd, accountUuidStage } =
|
|
710
|
+
const { accountUuidProd, accountUuidStage, managedBy } = loadTamaroMetadataFromConfig(paths);
|
|
676
711
|
if (!accountUuidProd && !accountUuidStage) {
|
|
677
712
|
logTitle("No EPMS account UUIDs found in config.yml. Skipping EPMS update.");
|
|
678
713
|
return;
|
|
@@ -686,6 +721,7 @@ const updateEpms = async (options) => {
|
|
|
686
721
|
const baseInfo = {
|
|
687
722
|
configured_version: configuredVersion ?? "unknown",
|
|
688
723
|
last_deployment: Math.trunc(Date.now() / 1e3),
|
|
724
|
+
managed_by: managedBy,
|
|
689
725
|
read_only: false
|
|
690
726
|
};
|
|
691
727
|
const environments = [{
|
|
@@ -721,8 +757,7 @@ const updateEpms = async (options) => {
|
|
|
721
757
|
};
|
|
722
758
|
const loadEnv = (paths) => {
|
|
723
759
|
const filePath = globSync(paths.appEnv).find((file) => path.basename(file) === ".env");
|
|
724
|
-
if (
|
|
725
|
-
config({
|
|
760
|
+
if (filePath) config({
|
|
726
761
|
path: filePath,
|
|
727
762
|
quiet: true
|
|
728
763
|
});
|
|
@@ -802,8 +837,8 @@ const deploy = async (options) => {
|
|
|
802
837
|
halt(error.stderr);
|
|
803
838
|
}
|
|
804
839
|
}
|
|
805
|
-
if (options.
|
|
806
|
-
else await updateEpms(options);
|
|
840
|
+
if (options.forceSkipEpmsSync) logTitle("⚠️ EPMS sync skipped. You better know what you are doing.");
|
|
841
|
+
else if (!ifCore()) await updateEpms(options);
|
|
807
842
|
const demoPage = `https://${AWS_S3_BUCKET_TAMARO}/${configName}/${tag}/index.html`;
|
|
808
843
|
const entryPoint = `https://${AWS_S3_BUCKET_TAMARO}/${configName}/${tag}/${entryFilename}`;
|
|
809
844
|
logTitle(`\nBundle for "${configName}" is deployed with tag "${tag}".`);
|
|
@@ -1026,7 +1061,7 @@ const prepareFlags = async (options) => {
|
|
|
1026
1061
|
};
|
|
1027
1062
|
//#endregion
|
|
1028
1063
|
//#region src/commands/unarchive.ts
|
|
1029
|
-
const unarchive = (options) => {
|
|
1064
|
+
const unarchive = async (options) => {
|
|
1030
1065
|
const { ifCore } = getIfCoreFns({ allowArchived: true });
|
|
1031
1066
|
assertIsNotCore$1(ifCore);
|
|
1032
1067
|
assertIsArchived();
|
|
@@ -1037,12 +1072,13 @@ const unarchive = (options) => {
|
|
|
1037
1072
|
const restoreTarget = path.resolve(configsDir, configName);
|
|
1038
1073
|
assertRestoreTargetDoesNotExist(restoreTarget);
|
|
1039
1074
|
assertOptionsValid$2(options);
|
|
1075
|
+
if (!ifCore() && !options.forceSkipEpmsSync) await unarchiveEpms(configName, !!options.dryrun);
|
|
1040
1076
|
if (options.dryrun) logTitle("🧪 DRY RUN MODE - No actual changes will be made 🧪");
|
|
1041
1077
|
if (!options.dryrun) {
|
|
1042
1078
|
renameSync(cwd, restoreTarget);
|
|
1043
1079
|
process.chdir(restoreTarget);
|
|
1044
1080
|
} else logTitle(`Would move ${cwd} → ${restoreTarget}`);
|
|
1045
|
-
|
|
1081
|
+
logSuccess(`\n✅ "${configName}" has been unarchived and moved to ${restoreTarget}`);
|
|
1046
1082
|
process.on("exit", () => {
|
|
1047
1083
|
notify({
|
|
1048
1084
|
message: `"${configName}" has been unarchived.`,
|
|
@@ -1050,6 +1086,39 @@ const unarchive = (options) => {
|
|
|
1050
1086
|
});
|
|
1051
1087
|
});
|
|
1052
1088
|
};
|
|
1089
|
+
const unarchiveEpms = async (configName, dryrun) => {
|
|
1090
|
+
const { ifCore } = getIfCoreFns({ allowArchived: true });
|
|
1091
|
+
const { accountUuidProd, accountUuidStage } = loadTamaroMetadataFromConfig(getPaths(ifCore));
|
|
1092
|
+
if (!accountUuidProd && !accountUuidStage) halt(stripIndent(`
|
|
1093
|
+
No EPMS account UUIDs found in config.yml.
|
|
1094
|
+
Add them to your config.yml, for example:
|
|
1095
|
+
epms:
|
|
1096
|
+
account_uuid: <UUID>
|
|
1097
|
+
epms_stage:
|
|
1098
|
+
account_uuid: <UUID>
|
|
1099
|
+
|
|
1100
|
+
Or re-run with --force-skip-epms-sync to skip the EPMS sync entirely - ONLY use in emergencies / or if you know what you're doing.
|
|
1101
|
+
`));
|
|
1102
|
+
const environments = [{
|
|
1103
|
+
accountUuid: accountUuidStage,
|
|
1104
|
+
env: "stage",
|
|
1105
|
+
label: "Stage"
|
|
1106
|
+
}, {
|
|
1107
|
+
accountUuid: accountUuidProd,
|
|
1108
|
+
env: "prod",
|
|
1109
|
+
label: "Prod"
|
|
1110
|
+
}];
|
|
1111
|
+
for (const { accountUuid, env, label } of environments) {
|
|
1112
|
+
if (!accountUuid) continue;
|
|
1113
|
+
const epmsClient = epmsAuthenticate(env);
|
|
1114
|
+
await assertUserIsSuperAdmin(epmsClient);
|
|
1115
|
+
logTitle(`\nUnarchiving EPMS ${label}: ${configName}`);
|
|
1116
|
+
if (!dryrun) {
|
|
1117
|
+
await epmsClient.unarchiveTamaro(configName);
|
|
1118
|
+
logSuccess(`✅ EPMS ${label} entry unarchived.`);
|
|
1119
|
+
} else logSuccess(`⏭️ EPMS ${label} entry would be unarchived (dry run).`);
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1053
1122
|
const assertOptionsValid$2 = (options) => {
|
|
1054
1123
|
const { profile } = options;
|
|
1055
1124
|
if (profile) assertProfileValid(profile);
|
|
@@ -1109,8 +1178,6 @@ const undeploy = async (options) => {
|
|
|
1109
1178
|
halt(error.stderr);
|
|
1110
1179
|
}
|
|
1111
1180
|
}
|
|
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.");
|
|
1114
1181
|
logTitle(`\n${description} has been undeployed.`);
|
|
1115
1182
|
logDataTable({ "Removed URL:": deployUrl });
|
|
1116
1183
|
process.on("exit", () => {
|
|
@@ -1120,33 +1187,6 @@ const undeploy = async (options) => {
|
|
|
1120
1187
|
});
|
|
1121
1188
|
});
|
|
1122
1189
|
};
|
|
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
|
-
};
|
|
1150
1190
|
const assertOptionsValid$1 = (options) => {
|
|
1151
1191
|
const { all, profile, tag } = options;
|
|
1152
1192
|
const { ifCore } = getIfCoreFns();
|
|
@@ -1229,7 +1269,7 @@ const promptBucketTamaroEmailConfig = async () => {
|
|
|
1229
1269
|
//#endregion
|
|
1230
1270
|
//#region package.json
|
|
1231
1271
|
var name = "@raisenow/tamaro-cli";
|
|
1232
|
-
var version = "
|
|
1272
|
+
var version = "2.0.0";
|
|
1233
1273
|
//#endregion
|
|
1234
1274
|
//#region src/cli.ts
|
|
1235
1275
|
/**
|
|
@@ -1247,7 +1287,7 @@ const cli = createCommand().name(name).version(version, "-v, --version");
|
|
|
1247
1287
|
cli.command("dev").description("Run the local development server for Tamaro Core or a particular customer configuration").option("--local-core", "Load Tamaro Core from localhost:1234 instead of the CDN", false).option("--https", "Let local web server serve Tamaro with SSL encryption", false).option("--port <port>", "Web server port", `${DEFAULT_PORT}`).option("--env <env>", "Environment (dev, stage, prod)").option("--nolint", "Disable ESLint", false).option("--debug", "Enable debug mode", false).action(async (options) => {
|
|
1248
1288
|
await dev(options);
|
|
1249
1289
|
});
|
|
1250
|
-
cli.command("build").description("Build an optimised (minified) bundle of Tamaro Core or a customer configuration").option("--local-core", "Load Tamaro Core from localhost:1234 instead of the CDN", false).option("--analyze", "Generate bundle statistics to \"reports\" folder", false).option("--serve", "Run the generated bundle with a local web server", false).option("--https", "Let local web server serve Tamaro with SSL encryption", false).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("--deploy", "Deploy Tamaro Core or a customer configuration bundle to AWS S3", false).option("--skip-epms-sync
|
|
1290
|
+
cli.command("build").description("Build an optimised (minified) bundle of Tamaro Core or a customer configuration").option("--local-core", "Load Tamaro Core from localhost:1234 instead of the CDN", false).option("--analyze", "Generate bundle statistics to \"reports\" folder", false).option("--serve", "Run the generated bundle with a local web server", false).option("--https", "Let local web server serve Tamaro with SSL encryption", false).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("--deploy", "Deploy Tamaro Core or a customer configuration bundle to AWS S3", false).option("--force-skip-epms-sync", "Skip the EPMS sync entirely. Only use this in emergencies.", false).option("--tag <tag>", "Tag which should be used for the deployment of the bundle", DEFAULT_TAG).option("--nolint", "Disable ESLint", false).option("--debug", "Enable debug mode", false).action(async (options) => {
|
|
1251
1291
|
await build(options);
|
|
1252
1292
|
if (options.serve) await serve(options);
|
|
1253
1293
|
if (options.deploy) await deploy(options);
|
|
@@ -1255,7 +1295,7 @@ cli.command("build").description("Build an optimised (minified) bundle of Tamaro
|
|
|
1255
1295
|
cli.command("serve").description("Run a local web server for pre-built bundle").option("--port <port>", "Web server port", `${DEFAULT_PORT}`).option("--https", "Let local web server serve Tamaro with SSL encryption", false).action(async (options) => {
|
|
1256
1296
|
await serve(options);
|
|
1257
1297
|
});
|
|
1258
|
-
cli.command("deploy").description("Deploy a pre-built Tamaro Core or customer configuration bundle to AWS S3").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--tag <tag>", "Tag which should be used for the deployment of the bundle", DEFAULT_TAG).option("--dryrun", "Displays the operations that would be performed without actually running them", false).option("--skip-epms-sync
|
|
1298
|
+
cli.command("deploy").description("Deploy a pre-built Tamaro Core or customer configuration bundle to AWS S3").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--tag <tag>", "Tag which should be used for the deployment of the bundle", DEFAULT_TAG).option("--dryrun", "Displays the operations that would be performed without actually running them", false).option("--force-skip-epms-sync", "Skip the EPMS sync entirely. Only use this in emergencies.", false).action(async (options) => {
|
|
1259
1299
|
await deploy(options);
|
|
1260
1300
|
});
|
|
1261
1301
|
cli.command("list-deployed").description("List deployments of Tamaro Core or a particular customer configuration").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--config <config>", "Configuration name (default: current customer configuration)").action(async (options) => {
|
|
@@ -1264,17 +1304,17 @@ cli.command("list-deployed").description("List deployments of Tamaro Core or a p
|
|
|
1264
1304
|
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) => {
|
|
1265
1305
|
await deployEmailConfig(options);
|
|
1266
1306
|
});
|
|
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).
|
|
1307
|
+
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) => {
|
|
1268
1308
|
await undeploy(options);
|
|
1269
1309
|
});
|
|
1270
1310
|
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) => {
|
|
1271
1311
|
await undeployEmailConfig(options);
|
|
1272
1312
|
});
|
|
1273
|
-
cli.command("archive").description("Archive a customer configuration by moving it to the _archived folder").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--dryrun", "Displays the operations that would be performed without actually running them", false).action((options) => {
|
|
1274
|
-
archive(options);
|
|
1313
|
+
cli.command("archive").description("Archive a customer configuration by moving it to the _archived folder").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--dryrun", "Displays the operations that would be performed without actually running them", false).option("--force-skip-epms-sync", "Skip EPMS archive sync (emergency use only)", false).action(async (options) => {
|
|
1314
|
+
await archive(options);
|
|
1275
1315
|
});
|
|
1276
|
-
cli.command("unarchive").description("Unarchive a customer configuration by restoring it from the _archived folder").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--dryrun", "Displays the operations that would be performed without actually running them", false).action((options) => {
|
|
1277
|
-
unarchive(options);
|
|
1316
|
+
cli.command("unarchive").description("Unarchive a customer configuration by restoring it from the _archived folder").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--dryrun", "Displays the operations that would be performed without actually running them", false).option("--force-skip-epms-sync", "Skip EPMS unarchive sync (emergency use only)", false).action(async (options) => {
|
|
1317
|
+
await unarchive(options);
|
|
1278
1318
|
});
|
|
1279
1319
|
cli.command("update-epms").description("Update EPMS with the currently deployed widget tags.").option("--profile <profile>", "AWS profile").option("--ci", "CI environment", false).option("--dryrun", "Displays the operations that would be performed without actually running them", false).option("--tag <tag>", "Tag which should be set for the deployment", DEFAULT_TAG).action(async (options) => {
|
|
1280
1320
|
await updateEpms(options);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { existsSync, realpathSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import stripIndent from "strip-indent";
|
|
4
5
|
import { execaCommandSync } from "execa";
|
|
5
6
|
import prompts from "prompts";
|
|
6
|
-
import stripIndent from "strip-indent";
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
import columnify from "columnify";
|
|
9
9
|
import envPaths from "env-paths";
|
|
@@ -76,8 +76,9 @@ const AWS_CLOUDFRONT_DISTRIBUTION_ID = "EHJ1OM458YQ0I";
|
|
|
76
76
|
const HTTPS_CRT_FILE = "localhost.crt";
|
|
77
77
|
const HTTPS_KEY_FILE = "localhost.key";
|
|
78
78
|
const CACHE_DIR = envPaths("tamaro-cli", { suffix: "" }).cache;
|
|
79
|
+
const TAMARO_VERSION_IN_URL_REGEX = /tamaro-core\/(.*)\/index.js/;
|
|
79
80
|
//#endregion
|
|
80
|
-
//#region node_modules/.pnpm/tsdown@0.
|
|
81
|
+
//#region node_modules/.pnpm/tsdown@0.22.0_tsx@4.19.1_typescript@6.0.3_unrun@0.2.37_synckit@0.11.11_/node_modules/tsdown/esm-shims.js
|
|
81
82
|
const getFilename = () => fileURLToPath(import.meta.url);
|
|
82
83
|
const getDirname = () => path.dirname(getFilename());
|
|
83
84
|
const __dirname = /* @__PURE__ */ getDirname();
|
|
@@ -215,7 +216,11 @@ const resolveAccountUuidFromConfig = (rawConfig, field) => {
|
|
|
215
216
|
`));
|
|
216
217
|
return accountUuidValidation.data;
|
|
217
218
|
};
|
|
218
|
-
const
|
|
219
|
+
const resolveManagedByFromConfig = (rawConfig) => {
|
|
220
|
+
const managedByConfig = z.object({ managed_by: z.string().trim().min(1).max(255).optional() }).safeParse(rawConfig);
|
|
221
|
+
if (!managedByConfig.success) halt("Could not extract \"managed_by\" from config.yml. Please ensure it is a non-empty string.");
|
|
222
|
+
return managedByConfig.data?.managed_by ?? "RaiseNow";
|
|
223
|
+
};
|
|
219
224
|
const extractTamaroVersionFromUrl = (url) => {
|
|
220
225
|
const match = url.match(TAMARO_VERSION_IN_URL_REGEX);
|
|
221
226
|
if (match) return match[1].toString();
|
|
@@ -339,4 +344,4 @@ const applyEnv = (env) => {
|
|
|
339
344
|
if (filePath) config({ path: filePath });
|
|
340
345
|
};
|
|
341
346
|
//#endregion
|
|
342
|
-
export {
|
|
347
|
+
export { EPMS_OAUTH_CLIENT_ID as A, logInfo as B, DEFAULT_HTTP_TIMEOUT as C, EPMS_API_BASE_URL_STAGE as D, EPMS_API_BASE_URL_PROD as E, runCommandSync as F, logTitle as H, createTerminalLink as I, logCommand as L, HTTPS_KEY_FILE as M, halt as N, EPMS_AUTH_BASE_URL_PROD as O, promptConfirmation as P, logDataTable as R, CORE_CONFIG_NAME as S, DEFAULT_TAG as T, logSuccess as V, AWS_CLOUDFRONT_DISTRIBUTION_ID as _, getIfCoreFns as a, AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_STAGE as b, getWidgetUuid as c, resolveBin as d, resolveCoreVersion as f, AUTH_PORT as g, resolveOwn as h, extensions as i, HTTPS_CRT_FILE as j, EPMS_AUTH_BASE_URL_STAGE as k, resolveAccountUuidFromConfig as l, resolveManagedByFromConfig 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 as v, DEFAULT_PORT as w, CACHE_DIR as x, AWS_S3_BUCKET_TAMARO_EMAIL_CONFIG_PROD as y, logError as z };
|
package/dist/webpack.config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { H as logTitle, M as HTTPS_KEY_FILE, R as logDataTable, 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-DZ3cWM6p.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
package/package.json
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raisenow/tamaro-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "RaiseNow",
|
|
6
6
|
"email": "development@raisenow.com"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
|
-
"bin": "dist/cli.js",
|
|
10
9
|
"engines": {
|
|
11
10
|
"node": ">=22.22.2",
|
|
12
|
-
"pnpm": ">=
|
|
11
|
+
"pnpm": ">=11.0.9",
|
|
13
12
|
"npm": "please-use-pnpm",
|
|
14
13
|
"yarn": "please-use-pnpm"
|
|
15
14
|
},
|
|
@@ -18,7 +17,7 @@
|
|
|
18
17
|
"@babel/cli": "^7.28.6",
|
|
19
18
|
"@babel/core": "^7.29.0",
|
|
20
19
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
|
21
|
-
"@babel/preset-env": "^7.29.
|
|
20
|
+
"@babel/preset-env": "^7.29.5",
|
|
22
21
|
"@babel/preset-react": "^7.28.5",
|
|
23
22
|
"@babel/preset-typescript": "^7.28.5",
|
|
24
23
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
|
|
@@ -26,7 +25,7 @@
|
|
|
26
25
|
"@types/columnify": "^1.5.4",
|
|
27
26
|
"@types/handlebars-helpers": "^0.5.6",
|
|
28
27
|
"@types/lodash": "^4.17.24",
|
|
29
|
-
"@types/node": "^22.19.
|
|
28
|
+
"@types/node": "^22.19.18",
|
|
30
29
|
"@types/node-notifier": "^8.0.5",
|
|
31
30
|
"@types/prompts": "^2.4.9",
|
|
32
31
|
"@types/resolve-bin": "^0.4.3",
|
|
@@ -63,7 +62,7 @@
|
|
|
63
62
|
"node-notifier": "^10.0.1",
|
|
64
63
|
"open": "^11.0.0",
|
|
65
64
|
"portfinder": "^1.0.38",
|
|
66
|
-
"postcss": "^8.5.
|
|
65
|
+
"postcss": "^8.5.14",
|
|
67
66
|
"postcss-import": "^16.1.1",
|
|
68
67
|
"postcss-loader": "^8.2.1",
|
|
69
68
|
"postcss-nested": "^7.0.2",
|
|
@@ -71,12 +70,12 @@
|
|
|
71
70
|
"react-refresh": "^0.18.0",
|
|
72
71
|
"resolve-url-loader": "^5.0.0",
|
|
73
72
|
"sass": "^1.99.0",
|
|
74
|
-
"sass-loader": "^16.0.
|
|
73
|
+
"sass-loader": "^16.0.8",
|
|
75
74
|
"strip-indent": "^4.1.1",
|
|
76
75
|
"style-loader": "^4.0.0",
|
|
77
|
-
"terser-webpack-plugin": "^5.
|
|
76
|
+
"terser-webpack-plugin": "^5.6.0",
|
|
78
77
|
"tsconfig-paths-webpack-plugin": "^4.2.0",
|
|
79
|
-
"tsdown": "^0.
|
|
78
|
+
"tsdown": "^0.22.0",
|
|
80
79
|
"typescript": "^6.0.3",
|
|
81
80
|
"url-loader": "^4.1.1",
|
|
82
81
|
"vitest": "^4.1.5",
|
|
@@ -111,5 +110,8 @@
|
|
|
111
110
|
"lint": "DEBUG=eslint:eslint-helpers eslint .",
|
|
112
111
|
"format": "prettier . --write --ignore-path ./.prettierignore",
|
|
113
112
|
"test": "vitest"
|
|
113
|
+
},
|
|
114
|
+
"bin": {
|
|
115
|
+
"tamaro-cli": "dist/cli.js"
|
|
114
116
|
}
|
|
115
117
|
}
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
minimumReleaseAge: 1440
|
|
2
|
-
|
|
3
2
|
minimumReleaseAgeExclude:
|
|
4
3
|
- '@raisenow/*'
|
|
5
4
|
- '@rnw-npm/*'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
engineStrict: true
|
|
6
|
+
allowBuilds:
|
|
7
|
+
core-js: true
|
|
8
|
+
core-js-pure: true
|
|
9
|
+
esbuild: true
|
|
10
|
+
highlight.js: true
|
|
11
|
+
unrs-resolver: true
|