@raisenow/tamaro-cli 1.9.0-dev.1 → 1.9.0-dev.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +414 -273
- package/dist/{env-JzPIlctP.js → env-DZ3cWM6p.js} +19 -18
- package/dist/webpack.config.js +17 -1
- package/package.json +17 -15
- package/pnpm-workspace.yaml +7 -7
- package/eslint.config.ts +0 -30
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,6 +114,308 @@ const getConfigDeployments = async (configName) => {
|
|
|
114
114
|
return deployments;
|
|
115
115
|
};
|
|
116
116
|
//#endregion
|
|
117
|
+
//#region src/lib/epms/auth/util.ts
|
|
118
|
+
const CachedTokenSchema = z.object({
|
|
119
|
+
expirationTime: z.number(),
|
|
120
|
+
token: z.string()
|
|
121
|
+
});
|
|
122
|
+
const AuthResponseSchema = z.object({
|
|
123
|
+
access_token: z.string(),
|
|
124
|
+
expires_in: z.number(),
|
|
125
|
+
token_type: z.string()
|
|
126
|
+
});
|
|
127
|
+
const getCacheFilePath = (key) => {
|
|
128
|
+
const cacheKey = Buffer.from(key).toString("base64url");
|
|
129
|
+
return path.join(CACHE_DIR, `token-${cacheKey}.json`);
|
|
130
|
+
};
|
|
131
|
+
const getTokenCacheKey = (baseUrl, clientId) => {
|
|
132
|
+
return `${baseUrl}|${clientId}`;
|
|
133
|
+
};
|
|
134
|
+
const readCachedToken = (key) => {
|
|
135
|
+
try {
|
|
136
|
+
const filePath = getCacheFilePath(key);
|
|
137
|
+
if (!existsSync(filePath)) return;
|
|
138
|
+
const data = JSON.parse(readFileSync(filePath, "utf8"));
|
|
139
|
+
const parsed = CachedTokenSchema.safeParse(data);
|
|
140
|
+
if (parsed.success) return parsed.data;
|
|
141
|
+
} catch {}
|
|
142
|
+
};
|
|
143
|
+
const writeCachedToken = (key, cached) => {
|
|
144
|
+
try {
|
|
145
|
+
const filePath = getCacheFilePath(key);
|
|
146
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
147
|
+
writeFileSync(filePath, JSON.stringify(cached), { mode: 384 });
|
|
148
|
+
} catch {}
|
|
149
|
+
};
|
|
150
|
+
const removeCachedToken = (key) => {
|
|
151
|
+
try {
|
|
152
|
+
const filePath = getCacheFilePath(key);
|
|
153
|
+
if (existsSync(filePath)) writeFileSync(filePath, "", { flag: "w" });
|
|
154
|
+
} catch {}
|
|
155
|
+
};
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/lib/epms/auth/apiClient.ts
|
|
158
|
+
const createAuthorizer = (baseUrl, clientId, clientSecret) => {
|
|
159
|
+
const apiUnauthorized = ky.extend({
|
|
160
|
+
headers: { "Access-Control-Allow-Origin": "*" },
|
|
161
|
+
prefixUrl: baseUrl,
|
|
162
|
+
retry: { limit: 3 },
|
|
163
|
+
timeout: DEFAULT_HTTP_TIMEOUT
|
|
164
|
+
});
|
|
165
|
+
let expirationTime = void 0;
|
|
166
|
+
let token = void 0;
|
|
167
|
+
const cacheKey = getTokenCacheKey(baseUrl, clientId);
|
|
168
|
+
const cached = readCachedToken(cacheKey);
|
|
169
|
+
if (cached) {
|
|
170
|
+
token = cached.token;
|
|
171
|
+
expirationTime = cached.expirationTime;
|
|
172
|
+
}
|
|
173
|
+
const authorize = async () => {
|
|
174
|
+
const isExpired = (expirationTime ?? 0) - Date.now() < 300 * 1e3;
|
|
175
|
+
if (token && !isExpired) return token;
|
|
176
|
+
const data = await apiUnauthorized.post("oauth2/token", { json: {
|
|
177
|
+
client_id: clientId,
|
|
178
|
+
client_secret: clientSecret,
|
|
179
|
+
grant_type: "client_credentials"
|
|
180
|
+
} }).json();
|
|
181
|
+
token = `${data.token_type} ${data.access_token}`;
|
|
182
|
+
expirationTime = Date.now() + data.expires_in * 1e3;
|
|
183
|
+
writeCachedToken(cacheKey, {
|
|
184
|
+
expirationTime,
|
|
185
|
+
token
|
|
186
|
+
});
|
|
187
|
+
return token;
|
|
188
|
+
};
|
|
189
|
+
return apiUnauthorized.extend({ hooks: { beforeRequest: [async (request) => {
|
|
190
|
+
const authToken = await authorize();
|
|
191
|
+
request.headers.set("Authorization", authToken);
|
|
192
|
+
}] } });
|
|
193
|
+
};
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/lib/epms/auth/authCallback.html
|
|
196
|
+
var authCallback_default = "<!doctype html>\n<html>\n <head\n ><title>Authenticating</title></head\n >\n <body>\n <p>Authenticating</p>\n <script>\n const hash = window.location.hash.substring(1)\n const params = new URLSearchParams(hash)\n const data = {\n access_token: params.get('access_token'),\n token_type: params.get('token_type'),\n expires_in: parseInt(params.get('expires_in') || '0', 10),\n }\n fetch('/token', {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify(data),\n })\n .then(() => {\n document.querySelector('p').textContent =\n 'Authentication successful! You can close this tab.'\n })\n .catch(() => {\n document.querySelector('p').textContent =\n 'Authentication failed. Please try again.'\n })\n <\/script>\n </body>\n</html>\n";
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/lib/epms/auth/browser.ts
|
|
199
|
+
/**
|
|
200
|
+
* The following authentication method is a browser-based OAuth flow.
|
|
201
|
+
* A very similar flow is used by tools like AWS CLI when running `aws sso login` or Claude Code.
|
|
202
|
+
*
|
|
203
|
+
* The flow works as follows:
|
|
204
|
+
* 1. The CLI starts a temporary local HTTP server that listens for the OAuth callback.
|
|
205
|
+
* 2. The CLI opens the user's default web browser and navigates to the EPMS authorization URL, passing the local server's callback URL as the redirect_uri.
|
|
206
|
+
* 3. The user authenticates in the browser, and the OAuth server redirects back to the local server with the authorization token.
|
|
207
|
+
* 4. The local server captures the token and resolves the promise, completing the authentication flow.
|
|
208
|
+
*
|
|
209
|
+
*
|
|
210
|
+
* We also implement a simple caching mechanism that stores the token in a file in the user's home directory.
|
|
211
|
+
* This allows us to reuse the token for subsequent requests until it expires, at which point we automatically trigger a new authentication flow.
|
|
212
|
+
*/
|
|
213
|
+
const doBrowserOAuthFlow = (authBaseUrl, clientId, port) => {
|
|
214
|
+
return new Promise((resolve, reject) => {
|
|
215
|
+
const server = createServer((req, res) => {
|
|
216
|
+
if (req.method === "POST" && req.url === "/token") {
|
|
217
|
+
let body = "";
|
|
218
|
+
req.on("data", (chunk) => {
|
|
219
|
+
body += chunk.toString();
|
|
220
|
+
});
|
|
221
|
+
req.on("end", () => {
|
|
222
|
+
try {
|
|
223
|
+
const data = AuthResponseSchema.parse(JSON.parse(body));
|
|
224
|
+
res.writeHead(200);
|
|
225
|
+
res.end();
|
|
226
|
+
server.close();
|
|
227
|
+
resolve(data);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
res.writeHead(400);
|
|
230
|
+
res.end();
|
|
231
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
} else {
|
|
235
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
236
|
+
res.end(authCallback_default);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
server.on("error", reject);
|
|
240
|
+
server.listen(port, () => {
|
|
241
|
+
const redirectUri = `http://localhost:${port}`;
|
|
242
|
+
const authUrl = `${authBaseUrl}/oauth2/authorize?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=token`;
|
|
243
|
+
logInfo("\nℹ️ Log in with your super admin account in the browser to continue.");
|
|
244
|
+
console.log(`Opening browser for authentication...`);
|
|
245
|
+
console.log(createTerminalLink(authUrl, authUrl));
|
|
246
|
+
open(authUrl).catch((error) => {
|
|
247
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
const createBrowserAuthorizer = (authBaseUrl, clientId, epmsBaseUrl) => {
|
|
253
|
+
const apiBase = ky.extend({
|
|
254
|
+
prefixUrl: epmsBaseUrl,
|
|
255
|
+
retry: { limit: 3 },
|
|
256
|
+
timeout: DEFAULT_HTTP_TIMEOUT
|
|
257
|
+
});
|
|
258
|
+
let expirationTime = void 0;
|
|
259
|
+
let token = void 0;
|
|
260
|
+
const cacheKey = getTokenCacheKey(authBaseUrl, clientId);
|
|
261
|
+
const cached = readCachedToken(cacheKey);
|
|
262
|
+
if (cached) {
|
|
263
|
+
token = cached.token;
|
|
264
|
+
expirationTime = cached.expirationTime;
|
|
265
|
+
}
|
|
266
|
+
const resetAuth = () => {
|
|
267
|
+
token = void 0;
|
|
268
|
+
expirationTime = void 0;
|
|
269
|
+
removeCachedToken(cacheKey);
|
|
270
|
+
};
|
|
271
|
+
const authorize = async () => {
|
|
272
|
+
const isExpired = (expirationTime ?? 0) - Date.now() < 300 * 1e3;
|
|
273
|
+
if (token && !isExpired) return token;
|
|
274
|
+
const data = await doBrowserOAuthFlow(authBaseUrl, clientId, AUTH_PORT);
|
|
275
|
+
token = `${data.token_type} ${data.access_token}`;
|
|
276
|
+
expirationTime = Date.now() + data.expires_in * 1e3;
|
|
277
|
+
writeCachedToken(cacheKey, {
|
|
278
|
+
expirationTime,
|
|
279
|
+
token
|
|
280
|
+
});
|
|
281
|
+
return token;
|
|
282
|
+
};
|
|
283
|
+
return apiBase.extend({ hooks: {
|
|
284
|
+
afterResponse: [async (request, options, response) => {
|
|
285
|
+
if (response.status === 401) {
|
|
286
|
+
resetAuth();
|
|
287
|
+
const newToken = await authorize();
|
|
288
|
+
const newRequest = new Request(request, { headers: new Headers(request.headers) });
|
|
289
|
+
newRequest.headers.set("Authorization", newToken);
|
|
290
|
+
return ky(newRequest, options);
|
|
291
|
+
}
|
|
292
|
+
return response;
|
|
293
|
+
}],
|
|
294
|
+
beforeRequest: [async (request) => {
|
|
295
|
+
const authToken = await authorize();
|
|
296
|
+
request.headers.set("Authorization", authToken);
|
|
297
|
+
}]
|
|
298
|
+
} });
|
|
299
|
+
};
|
|
300
|
+
//#endregion
|
|
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
|
|
117
419
|
//#region src/lib/notifier.ts
|
|
118
420
|
const notify = (args) => {
|
|
119
421
|
const { message = "", title } = args;
|
|
@@ -126,7 +428,7 @@ const notify = (args) => {
|
|
|
126
428
|
};
|
|
127
429
|
//#endregion
|
|
128
430
|
//#region src/commands/archive.ts
|
|
129
|
-
const archive = (options) => {
|
|
431
|
+
const archive = async (options) => {
|
|
130
432
|
const { ifCore } = getIfCoreFns();
|
|
131
433
|
assertIsNotCore$4(ifCore);
|
|
132
434
|
assertIsNotArchived();
|
|
@@ -138,6 +440,7 @@ const archive = (options) => {
|
|
|
138
440
|
assertArchiveTargetDoesNotExist(archiveTarget);
|
|
139
441
|
assertOptionsValid$9(options);
|
|
140
442
|
if (options.dryrun) logTitle("🧪 DRY RUN MODE - No actual changes will be made 🧪");
|
|
443
|
+
if (!ifCore() && !options.forceSkipEpmsSync) await archiveEpms(configName, !!options.dryrun);
|
|
141
444
|
if (!options.dryrun) {
|
|
142
445
|
if (!existsSync(archiveDir)) mkdirSync(archiveDir, { recursive: true });
|
|
143
446
|
renameSync(cwd, archiveTarget);
|
|
@@ -150,6 +453,39 @@ const archive = (options) => {
|
|
|
150
453
|
});
|
|
151
454
|
});
|
|
152
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
|
+
};
|
|
153
489
|
const assertOptionsValid$9 = (options) => {
|
|
154
490
|
const { profile } = options;
|
|
155
491
|
if (profile) assertProfileValid(profile);
|
|
@@ -354,211 +690,6 @@ const assertTagValid = (tag) => {
|
|
|
354
690
|
if (!regex.test(tag)) halt(`Flag "--tag" has forbidden format. Allowed format: ${regex.toString()}.`);
|
|
355
691
|
};
|
|
356
692
|
//#endregion
|
|
357
|
-
//#region src/lib/epms/auth/util.ts
|
|
358
|
-
const CachedTokenSchema = z.object({
|
|
359
|
-
expirationTime: z.number(),
|
|
360
|
-
token: z.string()
|
|
361
|
-
});
|
|
362
|
-
const AuthResponseSchema = z.object({
|
|
363
|
-
access_token: z.string(),
|
|
364
|
-
expires_in: z.number(),
|
|
365
|
-
token_type: z.string()
|
|
366
|
-
});
|
|
367
|
-
const getCacheFilePath = (baseUrl, clientId) => {
|
|
368
|
-
const cacheKey = Buffer.from(`${baseUrl}|${clientId}`).toString("base64url");
|
|
369
|
-
return path.join(CACHE_DIR, `token-${cacheKey}.json`);
|
|
370
|
-
};
|
|
371
|
-
const readCachedToken = (baseUrl, clientId) => {
|
|
372
|
-
try {
|
|
373
|
-
const filePath = getCacheFilePath(baseUrl, clientId);
|
|
374
|
-
if (!existsSync(filePath)) return;
|
|
375
|
-
const data = JSON.parse(readFileSync(filePath, "utf8"));
|
|
376
|
-
const parsed = CachedTokenSchema.safeParse(data);
|
|
377
|
-
if (parsed.success) return parsed.data;
|
|
378
|
-
} catch {}
|
|
379
|
-
};
|
|
380
|
-
const writeCachedToken = (baseUrl, clientId, cached) => {
|
|
381
|
-
try {
|
|
382
|
-
const filePath = getCacheFilePath(baseUrl, clientId);
|
|
383
|
-
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
384
|
-
writeFileSync(filePath, JSON.stringify(cached), { mode: 384 });
|
|
385
|
-
} catch {}
|
|
386
|
-
};
|
|
387
|
-
const removeCachedToken = (baseUrl, clientId) => {
|
|
388
|
-
try {
|
|
389
|
-
const filePath = getCacheFilePath(baseUrl, clientId);
|
|
390
|
-
if (existsSync(filePath)) writeFileSync(filePath, "", { flag: "w" });
|
|
391
|
-
} catch {}
|
|
392
|
-
};
|
|
393
|
-
//#endregion
|
|
394
|
-
//#region src/lib/epms/auth/apiClient.ts
|
|
395
|
-
const createAuthorizer = (baseUrl, clientId, clientSecret) => {
|
|
396
|
-
const apiUnauthorized = ky.extend({
|
|
397
|
-
headers: { "Access-Control-Allow-Origin": "*" },
|
|
398
|
-
prefixUrl: baseUrl,
|
|
399
|
-
timeout: DEFAULT_HTTP_TIMEOUT
|
|
400
|
-
});
|
|
401
|
-
let expirationTime = void 0;
|
|
402
|
-
let token = void 0;
|
|
403
|
-
const cached = readCachedToken(baseUrl, clientId);
|
|
404
|
-
if (cached) {
|
|
405
|
-
token = cached.token;
|
|
406
|
-
expirationTime = cached.expirationTime;
|
|
407
|
-
}
|
|
408
|
-
const authorize = async () => {
|
|
409
|
-
const isExpired = (expirationTime ?? 0) - Date.now() < 300 * 1e3;
|
|
410
|
-
if (token && !isExpired) return token;
|
|
411
|
-
const data = await apiUnauthorized.post("oauth2/token", { json: {
|
|
412
|
-
client_id: clientId,
|
|
413
|
-
client_secret: clientSecret,
|
|
414
|
-
grant_type: "client_credentials"
|
|
415
|
-
} }).json();
|
|
416
|
-
token = `${data.token_type} ${data.access_token}`;
|
|
417
|
-
expirationTime = Date.now() + data.expires_in * 1e3;
|
|
418
|
-
writeCachedToken(baseUrl, clientId, {
|
|
419
|
-
expirationTime,
|
|
420
|
-
token
|
|
421
|
-
});
|
|
422
|
-
return token;
|
|
423
|
-
};
|
|
424
|
-
return apiUnauthorized.extend({ hooks: { beforeRequest: [async (request) => {
|
|
425
|
-
const authToken = await authorize();
|
|
426
|
-
request.headers.set("Authorization", authToken);
|
|
427
|
-
}] } });
|
|
428
|
-
};
|
|
429
|
-
//#endregion
|
|
430
|
-
//#region src/lib/epms/auth/authCallback.html
|
|
431
|
-
var authCallback_default = "<!doctype html>\n<html>\n <head\n ><title>Authenticating</title></head\n >\n <body>\n <p>Authenticating</p>\n <script>\n const hash = window.location.hash.substring(1)\n const params = new URLSearchParams(hash)\n const data = {\n access_token: params.get('access_token'),\n token_type: params.get('token_type'),\n expires_in: parseInt(params.get('expires_in') || '0', 10),\n }\n fetch('/token', {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify(data),\n })\n .then(() => {\n document.querySelector('p').textContent =\n 'Authentication successful! You can close this tab.'\n })\n .catch(() => {\n document.querySelector('p').textContent =\n 'Authentication failed. Please try again.'\n })\n <\/script>\n </body>\n</html>\n";
|
|
432
|
-
//#endregion
|
|
433
|
-
//#region src/lib/epms/auth/browser.ts
|
|
434
|
-
/**
|
|
435
|
-
* The following authentication method is a browser-based OAuth flow.
|
|
436
|
-
* A very similar flow is used by tools like AWS CLI when running `aws sso login` or Claude Code.
|
|
437
|
-
*
|
|
438
|
-
* The flow works as follows:
|
|
439
|
-
* 1. The CLI starts a temporary local HTTP server that listens for the OAuth callback.
|
|
440
|
-
* 2. The CLI opens the user's default web browser and navigates to the EPMS authorization URL, passing the local server's callback URL as the redirect_uri.
|
|
441
|
-
* 3. The user authenticates in the browser, and the OAuth server redirects back to the local server with the authorization token.
|
|
442
|
-
* 4. The local server captures the token and resolves the promise, completing the authentication flow.
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
* We also implement a simple caching mechanism that stores the token in a file in the user's home directory.
|
|
446
|
-
* This allows us to reuse the token for subsequent requests until it expires, at which point we automatically trigger a new authentication flow.
|
|
447
|
-
*/
|
|
448
|
-
const doBrowserOAuthFlow = (authBaseUrl, clientId, port) => {
|
|
449
|
-
return new Promise((resolve, reject) => {
|
|
450
|
-
const server = createServer((req, res) => {
|
|
451
|
-
if (req.method === "POST" && req.url === "/token") {
|
|
452
|
-
let body = "";
|
|
453
|
-
req.on("data", (chunk) => {
|
|
454
|
-
body += chunk.toString();
|
|
455
|
-
});
|
|
456
|
-
req.on("end", () => {
|
|
457
|
-
try {
|
|
458
|
-
const data = AuthResponseSchema.parse(JSON.parse(body));
|
|
459
|
-
res.writeHead(200);
|
|
460
|
-
res.end();
|
|
461
|
-
server.close();
|
|
462
|
-
resolve(data);
|
|
463
|
-
} catch (error) {
|
|
464
|
-
res.writeHead(400);
|
|
465
|
-
res.end();
|
|
466
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
467
|
-
}
|
|
468
|
-
});
|
|
469
|
-
} else {
|
|
470
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
471
|
-
res.end(authCallback_default);
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
server.on("error", reject);
|
|
475
|
-
server.listen(port, () => {
|
|
476
|
-
const redirectUri = `http://localhost:${port}`;
|
|
477
|
-
const authUrl = `${authBaseUrl}/oauth2/authorize?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=token`;
|
|
478
|
-
console.log(`Opening browser for authentication: ${authUrl}`);
|
|
479
|
-
open(authUrl).catch((error) => {
|
|
480
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
481
|
-
});
|
|
482
|
-
});
|
|
483
|
-
});
|
|
484
|
-
};
|
|
485
|
-
const createBrowserAuthorizer = (authBaseUrl, clientId, epmsBaseUrl) => {
|
|
486
|
-
const apiBase = ky.extend({
|
|
487
|
-
prefixUrl: epmsBaseUrl,
|
|
488
|
-
timeout: DEFAULT_HTTP_TIMEOUT
|
|
489
|
-
});
|
|
490
|
-
let expirationTime = void 0;
|
|
491
|
-
let token = void 0;
|
|
492
|
-
const cacheBaseUrl = `browser|${authBaseUrl}`;
|
|
493
|
-
const cached = readCachedToken(cacheBaseUrl, clientId);
|
|
494
|
-
if (cached) {
|
|
495
|
-
token = cached.token;
|
|
496
|
-
expirationTime = cached.expirationTime;
|
|
497
|
-
}
|
|
498
|
-
const resetAuth = () => {
|
|
499
|
-
token = void 0;
|
|
500
|
-
expirationTime = void 0;
|
|
501
|
-
removeCachedToken(cacheBaseUrl, clientId);
|
|
502
|
-
};
|
|
503
|
-
const authorize = async () => {
|
|
504
|
-
const isExpired = (expirationTime ?? 0) - Date.now() < 300 * 1e3;
|
|
505
|
-
if (token && !isExpired) return token;
|
|
506
|
-
const data = await doBrowserOAuthFlow(authBaseUrl, clientId, AUTH_PORT);
|
|
507
|
-
token = `${data.token_type} ${data.access_token}`;
|
|
508
|
-
expirationTime = Date.now() + data.expires_in * 1e3;
|
|
509
|
-
writeCachedToken(cacheBaseUrl, clientId, {
|
|
510
|
-
expirationTime,
|
|
511
|
-
token
|
|
512
|
-
});
|
|
513
|
-
return token;
|
|
514
|
-
};
|
|
515
|
-
return apiBase.extend({ hooks: {
|
|
516
|
-
afterResponse: [async (request, options, response) => {
|
|
517
|
-
if (response.status === 401) {
|
|
518
|
-
resetAuth();
|
|
519
|
-
const newToken = await authorize();
|
|
520
|
-
const newRequest = new Request(request, { headers: new Headers(request.headers) });
|
|
521
|
-
newRequest.headers.set("Authorization", newToken);
|
|
522
|
-
return ky(newRequest, options);
|
|
523
|
-
}
|
|
524
|
-
return response;
|
|
525
|
-
}],
|
|
526
|
-
beforeRequest: [async (request) => {
|
|
527
|
-
const authToken = await authorize();
|
|
528
|
-
request.headers.set("Authorization", authToken);
|
|
529
|
-
}]
|
|
530
|
-
} });
|
|
531
|
-
};
|
|
532
|
-
//#endregion
|
|
533
|
-
//#region src/lib/epms/client.ts
|
|
534
|
-
var EpmsClient = class EpmsClient {
|
|
535
|
-
#epmsClient;
|
|
536
|
-
constructor(epmsClient) {
|
|
537
|
-
this.#epmsClient = epmsClient;
|
|
538
|
-
}
|
|
539
|
-
static fromClientCredentials(baseUrl, clientId, clientSecret) {
|
|
540
|
-
return new EpmsClient(createAuthorizer(baseUrl, clientId, clientSecret));
|
|
541
|
-
}
|
|
542
|
-
static fromBrowserAuth(epmsBaseUrl, authBaseUrl, clientId) {
|
|
543
|
-
return new EpmsClient(createBrowserAuthorizer(authBaseUrl, clientId, epmsBaseUrl));
|
|
544
|
-
}
|
|
545
|
-
async getOrganisationIdByAccountUuid(accountUuid) {
|
|
546
|
-
const query = {
|
|
547
|
-
from: 0,
|
|
548
|
-
query: { $and: [{ $term: { object_uuid: accountUuid } }, { $term: { object: "account" } }] },
|
|
549
|
-
size: 1
|
|
550
|
-
};
|
|
551
|
-
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}`);
|
|
553
|
-
const organisationId = response.hits[0].organisation_uuid;
|
|
554
|
-
if (!organisationId) throw new Error(`Organisation UUID not found for account UUID: ${accountUuid}`);
|
|
555
|
-
return organisationId;
|
|
556
|
-
}
|
|
557
|
-
async updateTamaro(configName, tag, json) {
|
|
558
|
-
return this.#epmsClient.put(`products/tamaro/${configName}/tags/${tag}`, { json }).json();
|
|
559
|
-
}
|
|
560
|
-
};
|
|
561
|
-
//#endregion
|
|
562
693
|
//#region src/commands/update-epms.ts
|
|
563
694
|
const updateEpms = async (options) => {
|
|
564
695
|
const { ifCore } = getIfCoreFns();
|
|
@@ -576,7 +707,11 @@ const updateEpms = async (options) => {
|
|
|
576
707
|
const { dryrun, tag } = options;
|
|
577
708
|
const configName = getWidgetUuid();
|
|
578
709
|
loadEnv(paths);
|
|
579
|
-
const {
|
|
710
|
+
const { accountUuidProd, accountUuidStage, managedBy } = loadTamaroMetadataFromConfig(paths);
|
|
711
|
+
if (!accountUuidProd && !accountUuidStage) {
|
|
712
|
+
logTitle("No EPMS account UUIDs found in config.yml. Skipping EPMS update.");
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
580
715
|
if (dryrun) logTitle("🧪 DRY RUN MODE - No actual changes will be made 🧪");
|
|
581
716
|
const configuredVersion = resolveCoreVersion();
|
|
582
717
|
if (!configuredVersion) {
|
|
@@ -586,36 +721,29 @@ const updateEpms = async (options) => {
|
|
|
586
721
|
const baseInfo = {
|
|
587
722
|
configured_version: configuredVersion ?? "unknown",
|
|
588
723
|
last_deployment: Math.trunc(Date.now() / 1e3),
|
|
724
|
+
managed_by: managedBy,
|
|
589
725
|
read_only: false
|
|
590
726
|
};
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
if (!dryrun) {
|
|
606
|
-
await epmsClient.updateTamaro(configName, tag, body);
|
|
607
|
-
logSuccess("✅ EPMS Stage is updated.");
|
|
608
|
-
} else logSuccess("⏭️ EPMS Stage would be updated (dry run).");
|
|
609
|
-
}
|
|
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}":`);
|
|
727
|
+
const environments = [{
|
|
728
|
+
accountUuid: accountUuidStage,
|
|
729
|
+
env: "stage",
|
|
730
|
+
label: "Stage"
|
|
731
|
+
}, {
|
|
732
|
+
accountUuid: accountUuidProd,
|
|
733
|
+
env: "prod",
|
|
734
|
+
label: "Prod"
|
|
735
|
+
}];
|
|
736
|
+
for (const { accountUuid, env, label } of environments) {
|
|
737
|
+
if (!accountUuid) continue;
|
|
738
|
+
const epmsClient = epmsAuthenticate(env);
|
|
739
|
+
await assertUserIsSuperAdmin(epmsClient);
|
|
740
|
+
const organisationUuid = await epmsClient.getOrganisationIdByAccountUuid(accountUuid);
|
|
614
741
|
const body = {
|
|
615
742
|
...baseInfo,
|
|
616
|
-
account_uuid:
|
|
743
|
+
account_uuid: accountUuid,
|
|
617
744
|
organisation_uuid: organisationUuid
|
|
618
745
|
};
|
|
746
|
+
logTitle(`\nUpdating EPMS ${label}:`);
|
|
619
747
|
logDataTable({
|
|
620
748
|
name: configName,
|
|
621
749
|
tag,
|
|
@@ -623,34 +751,13 @@ const updateEpms = async (options) => {
|
|
|
623
751
|
});
|
|
624
752
|
if (!dryrun) {
|
|
625
753
|
await epmsClient.updateTamaro(configName, tag, body);
|
|
626
|
-
logSuccess(
|
|
627
|
-
} else logSuccess(
|
|
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);
|
|
754
|
+
logSuccess(`✅ EPMS ${label} is updated.`);
|
|
755
|
+
} else logSuccess(`⏭️ EPMS ${label} would be updated (dry run).`);
|
|
634
756
|
}
|
|
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
757
|
};
|
|
650
758
|
const loadEnv = (paths) => {
|
|
651
759
|
const filePath = globSync(paths.appEnv).find((file) => path.basename(file) === ".env");
|
|
652
|
-
if (
|
|
653
|
-
config({
|
|
760
|
+
if (filePath) config({
|
|
654
761
|
path: filePath,
|
|
655
762
|
quiet: true
|
|
656
763
|
});
|
|
@@ -730,8 +837,8 @@ const deploy = async (options) => {
|
|
|
730
837
|
halt(error.stderr);
|
|
731
838
|
}
|
|
732
839
|
}
|
|
733
|
-
if (options.
|
|
734
|
-
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);
|
|
735
842
|
const demoPage = `https://${AWS_S3_BUCKET_TAMARO}/${configName}/${tag}/index.html`;
|
|
736
843
|
const entryPoint = `https://${AWS_S3_BUCKET_TAMARO}/${configName}/${tag}/${entryFilename}`;
|
|
737
844
|
logTitle(`\nBundle for "${configName}" is deployed with tag "${tag}".`);
|
|
@@ -954,7 +1061,7 @@ const prepareFlags = async (options) => {
|
|
|
954
1061
|
};
|
|
955
1062
|
//#endregion
|
|
956
1063
|
//#region src/commands/unarchive.ts
|
|
957
|
-
const unarchive = (options) => {
|
|
1064
|
+
const unarchive = async (options) => {
|
|
958
1065
|
const { ifCore } = getIfCoreFns({ allowArchived: true });
|
|
959
1066
|
assertIsNotCore$1(ifCore);
|
|
960
1067
|
assertIsArchived();
|
|
@@ -965,12 +1072,13 @@ const unarchive = (options) => {
|
|
|
965
1072
|
const restoreTarget = path.resolve(configsDir, configName);
|
|
966
1073
|
assertRestoreTargetDoesNotExist(restoreTarget);
|
|
967
1074
|
assertOptionsValid$2(options);
|
|
1075
|
+
if (!ifCore() && !options.forceSkipEpmsSync) await unarchiveEpms(configName, !!options.dryrun);
|
|
968
1076
|
if (options.dryrun) logTitle("🧪 DRY RUN MODE - No actual changes will be made 🧪");
|
|
969
1077
|
if (!options.dryrun) {
|
|
970
1078
|
renameSync(cwd, restoreTarget);
|
|
971
1079
|
process.chdir(restoreTarget);
|
|
972
1080
|
} else logTitle(`Would move ${cwd} → ${restoreTarget}`);
|
|
973
|
-
|
|
1081
|
+
logSuccess(`\n✅ "${configName}" has been unarchived and moved to ${restoreTarget}`);
|
|
974
1082
|
process.on("exit", () => {
|
|
975
1083
|
notify({
|
|
976
1084
|
message: `"${configName}" has been unarchived.`,
|
|
@@ -978,6 +1086,39 @@ const unarchive = (options) => {
|
|
|
978
1086
|
});
|
|
979
1087
|
});
|
|
980
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
|
+
};
|
|
981
1122
|
const assertOptionsValid$2 = (options) => {
|
|
982
1123
|
const { profile } = options;
|
|
983
1124
|
if (profile) assertProfileValid(profile);
|
|
@@ -1128,7 +1269,7 @@ const promptBucketTamaroEmailConfig = async () => {
|
|
|
1128
1269
|
//#endregion
|
|
1129
1270
|
//#region package.json
|
|
1130
1271
|
var name = "@raisenow/tamaro-cli";
|
|
1131
|
-
var version = "1.9.0-dev.
|
|
1272
|
+
var version = "1.9.0-dev.3";
|
|
1132
1273
|
//#endregion
|
|
1133
1274
|
//#region src/cli.ts
|
|
1134
1275
|
/**
|
|
@@ -1146,7 +1287,7 @@ const cli = createCommand().name(name).version(version, "-v, --version");
|
|
|
1146
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) => {
|
|
1147
1288
|
await dev(options);
|
|
1148
1289
|
});
|
|
1149
|
-
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) => {
|
|
1150
1291
|
await build(options);
|
|
1151
1292
|
if (options.serve) await serve(options);
|
|
1152
1293
|
if (options.deploy) await deploy(options);
|
|
@@ -1154,7 +1295,7 @@ cli.command("build").description("Build an optimised (minified) bundle of Tamaro
|
|
|
1154
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) => {
|
|
1155
1296
|
await serve(options);
|
|
1156
1297
|
});
|
|
1157
|
-
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) => {
|
|
1158
1299
|
await deploy(options);
|
|
1159
1300
|
});
|
|
1160
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) => {
|
|
@@ -1169,11 +1310,11 @@ cli.command("undeploy").description("Undeploy a Tamaro Core or customer configur
|
|
|
1169
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) => {
|
|
1170
1311
|
await undeployEmailConfig(options);
|
|
1171
1312
|
});
|
|
1172
|
-
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) => {
|
|
1173
|
-
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);
|
|
1174
1315
|
});
|
|
1175
|
-
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) => {
|
|
1176
|
-
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);
|
|
1177
1318
|
});
|
|
1178
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) => {
|
|
1179
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";
|
|
@@ -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,16 @@ 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
|
|
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 =
|
|
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;
|
|
79
|
+
const TAMARO_VERSION_IN_URL_REGEX = /tamaro-core\/(.*)\/index.js/;
|
|
77
80
|
//#endregion
|
|
78
|
-
//#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
|
|
79
82
|
const getFilename = () => fileURLToPath(import.meta.url);
|
|
80
83
|
const getDirname = () => path.dirname(getFilename());
|
|
81
84
|
const __dirname = /* @__PURE__ */ getDirname();
|
|
@@ -199,27 +202,25 @@ const getIfCoreFns = ({ allowArchived = false } = {}) => {
|
|
|
199
202
|
};
|
|
200
203
|
const resolveAccountUuidFromConfig = (rawConfig, field) => {
|
|
201
204
|
const epmsConfig = z.object({ [field]: z.record(z.string(), z.unknown()) }).safeParse(rawConfig);
|
|
202
|
-
if (!epmsConfig.success) return
|
|
205
|
+
if (!epmsConfig.success) return;
|
|
203
206
|
const configContent = epmsConfig.data[field];
|
|
204
207
|
const accountUuid = configContent.account_mapping ?? configContent.account_uuid;
|
|
205
208
|
const accountUuidValidation = z.uuid().safeParse(accountUuid);
|
|
206
|
-
|
|
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:
|
|
209
|
+
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
210
|
1. ${field}:
|
|
209
211
|
account_uuid: <UUID>
|
|
210
|
-
|
|
212
|
+
|
|
211
213
|
Alternatively, if the account_uuid is conditional using "if" / "then" / "else" statements, it must be provided using the "account_mapping" format:
|
|
212
214
|
2. ${field}:
|
|
213
|
-
account_mapping:
|
|
214
|
-
- <UUID_1>
|
|
215
|
-
- <UUID_2>
|
|
216
|
-
- <UUID_N>
|
|
215
|
+
account_mapping: <UUID>
|
|
217
216
|
`));
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
217
|
+
return accountUuidValidation.data;
|
|
218
|
+
};
|
|
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";
|
|
221
223
|
};
|
|
222
|
-
const TAMARO_VERSION_IN_URL_REGEX = /tamaro-core\/(.*)\/index.js/;
|
|
223
224
|
const extractTamaroVersionFromUrl = (url) => {
|
|
224
225
|
const match = url.match(TAMARO_VERSION_IN_URL_REGEX);
|
|
225
226
|
if (match) return match[1].toString();
|
|
@@ -343,4 +344,4 @@ const applyEnv = (env) => {
|
|
|
343
344
|
if (filePath) config({ path: filePath });
|
|
344
345
|
};
|
|
345
346
|
//#endregion
|
|
346
|
-
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";
|
|
@@ -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,15 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raisenow/tamaro-cli",
|
|
3
|
-
"version": "1.9.0-dev.
|
|
3
|
+
"version": "1.9.0-dev.3",
|
|
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",
|
|
@@ -54,7 +53,7 @@
|
|
|
54
53
|
"handlebars": "^4.7.9",
|
|
55
54
|
"handlebars-helpers": "^0.10.0",
|
|
56
55
|
"html-loader": "^5.1.0",
|
|
57
|
-
"html-webpack-plugin": "^5.6.
|
|
56
|
+
"html-webpack-plugin": "^5.6.7",
|
|
58
57
|
"js-yaml": "^4.1.1",
|
|
59
58
|
"json-loader": "^0.5.7",
|
|
60
59
|
"ky": "^1.14.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,16 +70,16 @@
|
|
|
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.
|
|
80
|
-
"typescript": "^6.0.
|
|
78
|
+
"tsdown": "^0.22.0",
|
|
79
|
+
"typescript": "^6.0.3",
|
|
81
80
|
"url-loader": "^4.1.1",
|
|
82
|
-
"vitest": "^4.1.
|
|
83
|
-
"webpack": "^5.106.
|
|
81
|
+
"vitest": "^4.1.5",
|
|
82
|
+
"webpack": "^5.106.2",
|
|
84
83
|
"webpack-cli": "^7.0.2",
|
|
85
84
|
"webpack-config-utils": "^2.3.1",
|
|
86
85
|
"webpack-dev-server": "^5.2.3",
|
|
@@ -90,11 +89,11 @@
|
|
|
90
89
|
"devDependencies": {
|
|
91
90
|
"@rnw-npm/eslint-config": "^2.0.0",
|
|
92
91
|
"@rnw-npm/prettier-config": "^1.0.0",
|
|
92
|
+
"eslint": "^10.3.0",
|
|
93
93
|
"@types/js-yaml": "^4.0.9",
|
|
94
|
-
"eslint": "^10.2.0",
|
|
95
94
|
"husky": "^9.1.7",
|
|
96
95
|
"lint-staged": "^16.4.0",
|
|
97
|
-
"prettier": "^3.8.
|
|
96
|
+
"prettier": "^3.8.3"
|
|
98
97
|
},
|
|
99
98
|
"prettier": "@rnw-npm/prettier-config",
|
|
100
99
|
"lint-staged": {
|
|
@@ -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
|
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
|
-
])
|