c8y-nitro 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{bootstrap-DUDpmXcU.mjs → bootstrap-ObaEF7rN.mjs} +6 -6
- package/dist/{c8y-api-BbRS1-Ls.mjs → c8y-api-BFjK6AYb.mjs} +36 -8
- package/dist/cli/index.mjs +4 -4
- package/dist/client-DxmbyZju.mjs +236 -0
- package/dist/{credentials-9FO7rTIR.d.mts → credentials-eKnJ7m9Y.d.mts} +0 -1
- package/dist/{index-uOwpI6rD.d.mts → index-vu4xaFkz.d.mts} +70 -4
- package/dist/index.d.mts +2 -3
- package/dist/index.mjs +187 -45
- package/dist/logging-BUYT8Z2V.mjs +57 -0
- package/dist/{options-Web5UyIU.mjs → options-B7_yb_wr.mjs} +4 -4
- package/dist/{package-DsLC9Mo3.mjs → package-D5xRshsx.mjs} +1 -1
- package/dist/{roles-D7rpbxPp.mjs → roles-B58Fguop.mjs} +4 -4
- package/dist/runtime/handlers/invalidateTenantOptions.mjs +403 -5
- package/dist/runtime/handlers/openapi.mjs +56 -0
- package/dist/runtime/middlewares/c8y-client.mjs +2 -2
- package/dist/runtime/middlewares/dev-user.dev.mjs +2 -2
- package/dist/runtime/plugins/c8y-variables.dev.mjs +1 -1
- package/dist/types.d.mts +3 -3
- package/dist/utils.d.mts +280 -42
- package/dist/utils.mjs +470 -110
- package/package.json +11 -9
- package/dist/logging-CVlaRS4O.mjs +0 -3
- /package/dist/{config-BRnvtthI.mjs → config-BVQHqlgV.mjs} +0 -0
- /package/dist/{env-file-B0BK-uZW.mjs → env-file-BrOTmVjA.mjs} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { _ as GENERATED_READINESS_ROUTE, a as findMicroserviceByName, g as GENERATED_LIVENESS_ROUTE, h as GENERATED_INVALIDATE_TENANT_OPTIONS_ROUTE, l as subscribeToApplication, m as createC8yManifestFromNitro, n as createBasicAuthHeader, o as getBootstrapCredentials, p as createC8yManifest, r as createMicroservice } from "./c8y-api-
|
|
2
|
-
import { t as writeBootstrapCredentials } from "./env-file-
|
|
3
|
-
import { n as name } from "./package-
|
|
1
|
+
import { _ as GENERATED_READINESS_ROUTE, a as findMicroserviceByName, d as updateMicroservice, g as GENERATED_LIVENESS_ROUTE, h as GENERATED_INVALIDATE_TENANT_OPTIONS_ROUTE, l as subscribeToApplication, m as createC8yManifestFromNitro, n as createBasicAuthHeader, o as getBootstrapCredentials, p as createC8yManifest, r as createMicroservice } from "./c8y-api-BFjK6AYb.mjs";
|
|
2
|
+
import { t as writeBootstrapCredentials } from "./env-file-BrOTmVjA.mjs";
|
|
3
|
+
import { n as name } from "./package-D5xRshsx.mjs";
|
|
4
4
|
import { basename, dirname, join, relative } from "node:path";
|
|
5
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
6
|
import { x } from "tinyexec";
|
|
7
7
|
import { join as join$1 } from "pathe";
|
|
8
8
|
import { mkdir as mkdir$1, readFile as readFile$1, stat, writeFile as writeFile$1 } from "fs/promises";
|
|
@@ -11,6 +11,7 @@ import Spinnies from "spinnies";
|
|
|
11
11
|
import { colors } from "consola/utils";
|
|
12
12
|
import { mkdirSync, writeFileSync } from "fs";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
14
15
|
import process from "node:process";
|
|
15
16
|
import evlog from "evlog/nitro/v3";
|
|
16
17
|
//#region src/module/apiClient.ts
|
|
@@ -113,6 +114,7 @@ type C8ySerialize<T> = T extends void | undefined | null ? undefined : T extends
|
|
|
113
114
|
* @param className - The class name for the generated service (e.g., "PlaygroundAPIClient")
|
|
114
115
|
*/
|
|
115
116
|
function generateAPIClient(routes, contextPath, className) {
|
|
117
|
+
const methods = routes.map((route) => generateMethod(route)).join("\n\n");
|
|
116
118
|
return `/* eslint-disable eslint-comments/no-unlimited-disable */
|
|
117
119
|
/* eslint-disable */
|
|
118
120
|
/**
|
|
@@ -157,7 +159,7 @@ export class ${className} {
|
|
|
157
159
|
return JSON.parse(text);
|
|
158
160
|
}
|
|
159
161
|
|
|
160
|
-
${
|
|
162
|
+
${methods}
|
|
161
163
|
}`;
|
|
162
164
|
}
|
|
163
165
|
/**
|
|
@@ -218,14 +220,15 @@ async function writeAPIClient(nitro, options) {
|
|
|
218
220
|
/**
|
|
219
221
|
* Generate the Dockerfile content for a Nitro build
|
|
220
222
|
* @param outputDirName - Name of the output directory (e.g., '.output')
|
|
223
|
+
* @param options - Dockerfile customization (base image, extra instructions)
|
|
221
224
|
* @returns Dockerfile content as a string
|
|
222
225
|
*/
|
|
223
|
-
function getDockerfileContent(outputDirName) {
|
|
224
|
-
return `FROM node:24-slim AS runtime
|
|
226
|
+
function getDockerfileContent(outputDirName, options = {}) {
|
|
227
|
+
return `FROM ${options.baseImage ?? "node:24-slim"} AS runtime
|
|
225
228
|
|
|
226
229
|
WORKDIR /app
|
|
227
230
|
|
|
228
|
-
# Copy the Nitro build output
|
|
231
|
+
${options.extraInstructions?.length ? `${options.extraInstructions.join("\n")}\n\n` : ""}# Copy the Nitro build output
|
|
229
232
|
COPY ${outputDirName}/ ${outputDirName}/
|
|
230
233
|
|
|
231
234
|
ENV NODE_ENV=production
|
|
@@ -246,12 +249,12 @@ async function checkDockerInstalled() {
|
|
|
246
249
|
return false;
|
|
247
250
|
}
|
|
248
251
|
}
|
|
249
|
-
async function writeDockerfile(outputDir) {
|
|
252
|
+
async function writeDockerfile(outputDir, options) {
|
|
250
253
|
const outputDirName = basename(outputDir);
|
|
251
254
|
const c8yDir = join(outputDir, "../.c8y");
|
|
252
255
|
const dockerfilePath = join(c8yDir, "Dockerfile");
|
|
253
256
|
await mkdir(c8yDir, { recursive: true });
|
|
254
|
-
await writeFile(dockerfilePath, getDockerfileContent(outputDirName), "utf-8");
|
|
257
|
+
await writeFile(dockerfilePath, getDockerfileContent(outputDirName, options), "utf-8");
|
|
255
258
|
return c8yDir;
|
|
256
259
|
}
|
|
257
260
|
async function buildDockerImage(nitro, c8yDir) {
|
|
@@ -262,6 +265,8 @@ async function buildDockerImage(nitro, c8yDir) {
|
|
|
262
265
|
try {
|
|
263
266
|
const result = await x("docker", [
|
|
264
267
|
"build",
|
|
268
|
+
"--platform",
|
|
269
|
+
"linux/amd64",
|
|
265
270
|
"-t",
|
|
266
271
|
imageName,
|
|
267
272
|
"-f",
|
|
@@ -298,12 +303,13 @@ async function saveDockerImageToTar(nitro, c8yDir, imageName) {
|
|
|
298
303
|
/**
|
|
299
304
|
* Create a Docker image from the Nitro build output
|
|
300
305
|
* @param nitro Nitro instance
|
|
306
|
+
* @param options Dockerfile customization (base image, extra instructions)
|
|
301
307
|
* @returns Path to the saved Docker image tar file
|
|
302
308
|
*/
|
|
303
|
-
async function createDockerImage(nitro) {
|
|
309
|
+
async function createDockerImage(nitro, options = {}) {
|
|
304
310
|
if (!await checkDockerInstalled()) throw new Error("Docker is not installed or not available in PATH. Please install Docker to build images.");
|
|
305
311
|
nitro.logger.debug("Creating Docker image...");
|
|
306
|
-
const c8yDir = await writeDockerfile(nitro.options.output.dir);
|
|
312
|
+
const c8yDir = await writeDockerfile(nitro.options.output.dir, options);
|
|
307
313
|
const imageTarPath = await saveDockerImageToTar(nitro, c8yDir, await buildDockerImage(nitro, c8yDir));
|
|
308
314
|
nitro.logger.debug("Docker image creation complete.");
|
|
309
315
|
return imageTarPath;
|
|
@@ -333,12 +339,12 @@ function formatBytes(bytes) {
|
|
|
333
339
|
function resolveZipOutputPath(rootDir, options, manifest) {
|
|
334
340
|
return join$1(join$1(rootDir, options.outputDir ?? "./"), typeof options.name === "function" ? options.name(manifest.name, manifest.version) : options.name ?? `${manifest.name}-${manifest.version}.zip`);
|
|
335
341
|
}
|
|
336
|
-
async function createC8yZip(nitro, options = {}) {
|
|
342
|
+
async function createC8yZip(nitro, options = {}, dockerOptions = {}) {
|
|
337
343
|
const startTime = Date.now();
|
|
338
344
|
const spinnerName = "c8y-zip";
|
|
339
345
|
spinnies.add(spinnerName, { text: "Creating Dockerfile..." });
|
|
340
346
|
spinnies.update(spinnerName, { text: "Building Docker image..." });
|
|
341
|
-
const imageTarPath = await createDockerImage(nitro);
|
|
347
|
+
const imageTarPath = await createDockerImage(nitro, dockerOptions);
|
|
342
348
|
spinnies.update(spinnerName, { text: "Creating manifest..." });
|
|
343
349
|
const manifest = await createC8yManifestFromNitro(nitro);
|
|
344
350
|
spinnies.update(spinnerName, { text: "Reading image.tar..." });
|
|
@@ -370,6 +376,7 @@ function setupRuntime(nitro, manifest) {
|
|
|
370
376
|
nitro.logger.debug("Setting up C8Y nitro runtime");
|
|
371
377
|
const roles = manifest.roles ?? [];
|
|
372
378
|
const settingKeys = (manifest.settings ?? []).map((s) => s.key);
|
|
379
|
+
const settingsCategory = manifest.settingsCategory ?? manifest.contextPath ?? manifest.name;
|
|
373
380
|
const completeTypesDir = join$1(nitro.options.rootDir, nitro.options.typescript.generatedTypesDir ?? "node_modules/.nitro/types");
|
|
374
381
|
const typesFile = join$1(completeTypesDir, "c8y-nitro.d.ts");
|
|
375
382
|
const typesContent = `// generated by c8y-nitro
|
|
@@ -379,6 +386,7 @@ ${roles.map((role) => ` '${role}': '${role}';`).join("\n")}
|
|
|
379
386
|
}
|
|
380
387
|
type C8YTenantOptionKey = ${settingKeys.length > 0 ? `${settingKeys.map((k) => `'${k}'`).join(" | ")}` : "never"}
|
|
381
388
|
type C8YTenantOptionKeysCacheConfig = Partial<Record<C8YTenantOptionKey, number>>;
|
|
389
|
+
type C8YSettingsCategory = ${settingsCategory ? `'${settingsCategory}'` : "string"};
|
|
382
390
|
}
|
|
383
391
|
|
|
384
392
|
declare module 'c8y-nitro/runtime' {
|
|
@@ -413,8 +421,11 @@ async function setupRuntimeConfig(nitro, options) {
|
|
|
413
421
|
nitro.options.runtimeConfig.c8yCredentialsCacheTTL = options.cache?.credentialsTTL ?? 600;
|
|
414
422
|
nitro.options.runtimeConfig.c8yDefaultTenantOptionsTTL = options.cache?.defaultTenantOptionsTTL ?? 600;
|
|
415
423
|
nitro.options.runtimeConfig.c8yTenantOptionsPerKeyTTL = options.cache?.tenantOptions ?? {};
|
|
416
|
-
const manifest = await createC8yManifest(nitro.options.rootDir, options
|
|
424
|
+
const manifest = await createC8yManifest(nitro.options.rootDir, nitro.options, nitro.logger);
|
|
417
425
|
nitro.options.runtimeConfig.c8ySettingsCategory = options.manifest?.settingsCategory ?? manifest.contextPath ?? manifest.name;
|
|
426
|
+
const excludeInternalRoutes = options.openapi?.excludeInternalRoutes ?? true;
|
|
427
|
+
const probePaths = [options.manifest?.livenessProbe?.httpGet?.path, options.manifest?.readinessProbe?.httpGet?.path].filter((path) => Boolean(path));
|
|
428
|
+
nitro.options.runtimeConfig.c8yOpenApiExcludeRoutes = [...excludeInternalRoutes ? ["/_", ...probePaths] : [], ...options.openapi?.excludeRoutes ?? []];
|
|
418
429
|
}
|
|
419
430
|
//#endregion
|
|
420
431
|
//#region src/module/registerRuntime.ts
|
|
@@ -454,6 +465,15 @@ function registerRuntime(nitro, options = {}) {
|
|
|
454
465
|
handler,
|
|
455
466
|
middleware: true
|
|
456
467
|
})));
|
|
468
|
+
if (Boolean(nitro.options.experimental?.openAPI && (isNitroDev || nitro.options.openAPI?.production))) if (!isNitroDev && nitro.options.openAPI?.production === "prerender") nitro.logger.warn("c8y-nitro cannot transform a prerendered OpenAPI document (internal routes stay listed and the server URL is baked in at build time). Use `openAPI: { production: true }` to serve the document at runtime instead.");
|
|
469
|
+
else {
|
|
470
|
+
nitro.options.handlers.push({
|
|
471
|
+
route: "/**",
|
|
472
|
+
handler: join$1(thisFilePath, "./runtime/handlers/openapi"),
|
|
473
|
+
middleware: true
|
|
474
|
+
});
|
|
475
|
+
nitro.logger.debug(`Registered OpenAPI transform middleware for ${nitro.options.openAPI?.route || "/_openapi.json"}`);
|
|
476
|
+
}
|
|
457
477
|
/**
|
|
458
478
|
* Handlers (can't be auto scanned as they need methods etc)
|
|
459
479
|
*/
|
|
@@ -515,39 +535,156 @@ function checkProbeEndpoint(nitro, probe, probeType) {
|
|
|
515
535
|
}
|
|
516
536
|
}
|
|
517
537
|
//#endregion
|
|
538
|
+
//#region src/module/manifestCache.ts
|
|
539
|
+
/**
|
|
540
|
+
* Directory (inside the project's `node_modules`) where c8y-nitro persists
|
|
541
|
+
* local dev state such as the last-synced manifest hash. Lives in
|
|
542
|
+
* `node_modules` so it is naturally gitignored and scoped per-checkout.
|
|
543
|
+
*/
|
|
544
|
+
const CACHE_DIR_SEGMENTS = ["node_modules", ".c8y-nitro"];
|
|
545
|
+
const CACHE_FILE_NAME = "bootstrap-state.json";
|
|
546
|
+
const CACHE_SCHEMA_VERSION = 1;
|
|
547
|
+
function getCacheDir(rootDir) {
|
|
548
|
+
return join(rootDir, ...CACHE_DIR_SEGMENTS);
|
|
549
|
+
}
|
|
550
|
+
function getCacheFile(rootDir) {
|
|
551
|
+
return join(getCacheDir(rootDir), CACHE_FILE_NAME);
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Compute a hash over the manifest and the deployment target it will be synced
|
|
555
|
+
* to. Folding the target in means switching tenant/baseURL invalidates the
|
|
556
|
+
* cached hash even if the manifest itself is unchanged.
|
|
557
|
+
*
|
|
558
|
+
* @param manifest - The generated Cumulocity manifest
|
|
559
|
+
* @param target - The deployment target identity
|
|
560
|
+
*/
|
|
561
|
+
function hashManifest(manifest, target) {
|
|
562
|
+
const payload = JSON.stringify({
|
|
563
|
+
manifest,
|
|
564
|
+
target
|
|
565
|
+
});
|
|
566
|
+
return createHash("sha256").update(payload).digest("hex");
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Read the persisted bootstrap state, or `null` when absent, unreadable, or
|
|
570
|
+
* from an incompatible schema version.
|
|
571
|
+
* @param rootDir - Project root directory (containing `node_modules`)
|
|
572
|
+
*/
|
|
573
|
+
async function readManifestCache(rootDir) {
|
|
574
|
+
try {
|
|
575
|
+
const content = await readFile(getCacheFile(rootDir), "utf-8");
|
|
576
|
+
const state = JSON.parse(content);
|
|
577
|
+
if (state.schemaVersion !== CACHE_SCHEMA_VERSION) return null;
|
|
578
|
+
return state;
|
|
579
|
+
} catch {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Persist the bootstrap state for future dev restarts.
|
|
585
|
+
* @param rootDir - Project root directory (containing `node_modules`)
|
|
586
|
+
* @param state - The state to persist (schema version is filled in automatically)
|
|
587
|
+
*/
|
|
588
|
+
async function writeManifestCache(rootDir, state) {
|
|
589
|
+
await mkdir(getCacheDir(rootDir), { recursive: true });
|
|
590
|
+
const payload = {
|
|
591
|
+
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
592
|
+
...state
|
|
593
|
+
};
|
|
594
|
+
await writeFile(getCacheFile(rootDir), JSON.stringify(payload, null, 2), "utf-8");
|
|
595
|
+
}
|
|
596
|
+
//#endregion
|
|
518
597
|
//#region src/module/autoBootstrap.ts
|
|
598
|
+
const BOOTSTRAP_ENV_VARS = [
|
|
599
|
+
"C8Y_BOOTSTRAP_TENANT",
|
|
600
|
+
"C8Y_BOOTSTRAP_USER",
|
|
601
|
+
"C8Y_BOOTSTRAP_PASSWORD"
|
|
602
|
+
];
|
|
603
|
+
const DEV_ENV_VARS = [
|
|
604
|
+
"C8Y_BASEURL",
|
|
605
|
+
"C8Y_DEVELOPMENT_TENANT",
|
|
606
|
+
"C8Y_DEVELOPMENT_USER",
|
|
607
|
+
"C8Y_DEVELOPMENT_PASSWORD"
|
|
608
|
+
];
|
|
519
609
|
/**
|
|
520
|
-
*
|
|
521
|
-
*
|
|
610
|
+
* Verifies the microservice on the development tenant and keeps it in sync with
|
|
611
|
+
* the local manifest, bootstrapping (or re-bootstrapping) when needed.
|
|
612
|
+
*
|
|
613
|
+
* Unlike a purely env-var-based check, this always verifies against the tenant
|
|
614
|
+
* when development credentials are available, so it also handles:
|
|
615
|
+
* - the manifest changing between dev restarts (auto-updates a placeholder), and
|
|
616
|
+
* - the application being deleted on the tenant while stale bootstrap env vars
|
|
617
|
+
* still linger locally (re-creates it).
|
|
618
|
+
*
|
|
619
|
+
* A previously *deployed* microservice (one with an active version) is never
|
|
620
|
+
* overwritten automatically - we only warn about the drift.
|
|
621
|
+
*
|
|
622
|
+
* Runs silently unless a bootstrap/sync was performed or an error occurs.
|
|
522
623
|
* @param nitro - Nitro instance
|
|
523
624
|
*/
|
|
524
625
|
async function autoBootstrap(nitro) {
|
|
525
626
|
try {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
"C8Y_BOOTSTRAP_USER",
|
|
529
|
-
"C8Y_BOOTSTRAP_PASSWORD"
|
|
530
|
-
].every((v) => process.env[v])) return;
|
|
531
|
-
if ([
|
|
532
|
-
"C8Y_BASEURL",
|
|
533
|
-
"C8Y_DEVELOPMENT_TENANT",
|
|
534
|
-
"C8Y_DEVELOPMENT_USER",
|
|
535
|
-
"C8Y_DEVELOPMENT_PASSWORD"
|
|
536
|
-
].filter((v) => !process.env[v]).length > 0) return;
|
|
627
|
+
const rootDir = nitro.options.rootDir;
|
|
628
|
+
if (DEV_ENV_VARS.filter((v) => !process.env[v]).length > 0) return;
|
|
537
629
|
const baseUrl = process.env.C8Y_BASEURL.endsWith("/") ? process.env.C8Y_BASEURL.slice(0, -1) : process.env.C8Y_BASEURL;
|
|
538
|
-
const
|
|
539
|
-
const
|
|
630
|
+
const developmentTenant = process.env.C8Y_DEVELOPMENT_TENANT;
|
|
631
|
+
const authHeader = createBasicAuthHeader(developmentTenant, process.env.C8Y_DEVELOPMENT_USER, process.env.C8Y_DEVELOPMENT_PASSWORD);
|
|
632
|
+
const manifest = await createC8yManifest(rootDir, nitro.options, nitro.logger);
|
|
633
|
+
const target = {
|
|
634
|
+
baseUrl,
|
|
635
|
+
developmentTenant,
|
|
636
|
+
name: manifest.name
|
|
637
|
+
};
|
|
638
|
+
const currentHash = hashManifest(manifest, target);
|
|
540
639
|
const existingApp = await findMicroserviceByName(baseUrl, manifest.name, authHeader);
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
640
|
+
if (!existingApp) {
|
|
641
|
+
const createdApp = await createMicroservice(baseUrl, manifest, authHeader);
|
|
642
|
+
nitro.logger.debug(`Microservice "${manifest.name}" created (ID: ${createdApp.id})`);
|
|
643
|
+
await finalizeBootstrap(nitro, {
|
|
644
|
+
baseUrl,
|
|
645
|
+
developmentTenant,
|
|
646
|
+
appId: createdApp.id,
|
|
647
|
+
authHeader,
|
|
648
|
+
target,
|
|
649
|
+
manifestHash: currentHash,
|
|
650
|
+
forceCredentials: true
|
|
651
|
+
});
|
|
652
|
+
nitro.logger.success(`Auto-bootstrap complete! Microservice "${manifest.name}" created on tenant.`);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const appId = existingApp.id;
|
|
656
|
+
const isRealService = Boolean(existingApp.activeVersionId);
|
|
657
|
+
const cache = await readManifestCache(rootDir);
|
|
658
|
+
if (!cache || cache.manifestHash !== currentHash || cache.appId !== appId) if (isRealService) nitro.logger.warn(`Local manifest for "${manifest.name}" differs from the deployed microservice on the development tenant. The running service was left untouched. Redeploy or run \`npx c8y-nitro bootstrap\` to update it explicitly.`);
|
|
659
|
+
else {
|
|
660
|
+
await updateMicroservice(baseUrl, appId, manifest, authHeader);
|
|
661
|
+
nitro.logger.success(`Auto-bootstrap: updated microservice "${manifest.name}" with changed manifest.`);
|
|
548
662
|
}
|
|
549
|
-
|
|
550
|
-
const
|
|
663
|
+
const hasBootstrapCreds = BOOTSTRAP_ENV_VARS.every((v) => process.env[v]);
|
|
664
|
+
const appChanged = cache?.appId !== appId;
|
|
665
|
+
await finalizeBootstrap(nitro, {
|
|
666
|
+
baseUrl,
|
|
667
|
+
developmentTenant,
|
|
668
|
+
appId,
|
|
669
|
+
authHeader,
|
|
670
|
+
target,
|
|
671
|
+
manifestHash: currentHash,
|
|
672
|
+
forceCredentials: !hasBootstrapCreds || appChanged
|
|
673
|
+
});
|
|
674
|
+
} catch (error) {
|
|
675
|
+
nitro.logger.warn("Auto-bootstrap failed:", error instanceof Error ? error.message : String(error));
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Subscribe the tenant, optionally (re)write bootstrap credentials, and persist
|
|
680
|
+
* the synced state to the local cache.
|
|
681
|
+
* @param nitro
|
|
682
|
+
* @param opts
|
|
683
|
+
*/
|
|
684
|
+
async function finalizeBootstrap(nitro, opts) {
|
|
685
|
+
await subscribeToApplication(opts.baseUrl, opts.developmentTenant, opts.appId, opts.authHeader);
|
|
686
|
+
if (opts.forceCredentials) {
|
|
687
|
+
const credentials = await getBootstrapCredentials(opts.baseUrl, opts.appId, opts.authHeader);
|
|
551
688
|
const envFileName = await writeBootstrapCredentials(nitro.options.rootDir, {
|
|
552
689
|
C8Y_BOOTSTRAP_TENANT: credentials.tenant,
|
|
553
690
|
C8Y_BOOTSTRAP_USER: credentials.name,
|
|
@@ -556,10 +693,13 @@ async function autoBootstrap(nitro) {
|
|
|
556
693
|
process.env.C8Y_BOOTSTRAP_TENANT = credentials.tenant;
|
|
557
694
|
process.env.C8Y_BOOTSTRAP_USER = credentials.name;
|
|
558
695
|
process.env.C8Y_BOOTSTRAP_PASSWORD = credentials.password;
|
|
559
|
-
nitro.logger.
|
|
560
|
-
} catch (error) {
|
|
561
|
-
nitro.logger.warn("Auto-bootstrap failed:", error instanceof Error ? error.message : String(error));
|
|
696
|
+
nitro.logger.debug(`Bootstrap credentials written to ${envFileName}`);
|
|
562
697
|
}
|
|
698
|
+
await writeManifestCache(nitro.options.rootDir, {
|
|
699
|
+
manifestHash: opts.manifestHash,
|
|
700
|
+
target: opts.target,
|
|
701
|
+
appId: opts.appId
|
|
702
|
+
});
|
|
563
703
|
}
|
|
564
704
|
//#endregion
|
|
565
705
|
//#region src/index.ts
|
|
@@ -575,7 +715,8 @@ function c8y() {
|
|
|
575
715
|
nitro.options.noExternals = nitro.options.noExternals === true ? true : [
|
|
576
716
|
...Array.isArray(nitro.options.noExternals) ? nitro.options.noExternals : [],
|
|
577
717
|
name,
|
|
578
|
-
"@c8y/client"
|
|
718
|
+
"@c8y/client",
|
|
719
|
+
"tslib"
|
|
579
720
|
];
|
|
580
721
|
nitro.options.rolldownConfig = {
|
|
581
722
|
...nitro.options.rolldownConfig,
|
|
@@ -597,12 +738,13 @@ function c8y() {
|
|
|
597
738
|
exclude: [manifest.livenessProbe?.httpGet?.path, manifest.readinessProbe?.httpGet?.path].filter(Boolean)
|
|
598
739
|
});
|
|
599
740
|
await setupEvlog(nitro);
|
|
600
|
-
if (!options.skipBootstrap) await autoBootstrap(nitro);
|
|
741
|
+
if (nitro.options.preset === "nitro-dev" && !options.skipBootstrap) await autoBootstrap(nitro);
|
|
601
742
|
await setupRuntimeConfig(nitro, options);
|
|
602
743
|
setupRuntime(nitro, manifest);
|
|
603
744
|
nitro.hooks.hook("dev:reload", async () => {
|
|
604
745
|
manifest = await createC8yManifestFromNitro(nitro);
|
|
605
746
|
setupRuntime(nitro, manifest);
|
|
747
|
+
if (!options.skipBootstrap) await autoBootstrap(nitro);
|
|
606
748
|
if (options.apiClient) {
|
|
607
749
|
nitro.logger.debug("Generating C8Y API client");
|
|
608
750
|
await writeAPIClient(nitro, options);
|
|
@@ -619,7 +761,7 @@ function c8y() {
|
|
|
619
761
|
}
|
|
620
762
|
});
|
|
621
763
|
nitro.hooks.hook("close", async () => {
|
|
622
|
-
if (nitro.options.preset !== "nitro-dev") await createC8yZip(nitro, options.zip);
|
|
764
|
+
if (nitro.options.preset !== "nitro-dev") await createC8yZip(nitro, options.zip, options.docker);
|
|
623
765
|
});
|
|
624
766
|
}
|
|
625
767
|
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createError } from "evlog";
|
|
2
|
+
import "evlog/nitro/v3";
|
|
3
|
+
//#region src/utils/logging.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create a structured error for a Cumulocity microservice. Always prefer this
|
|
6
|
+
* over Nitro/h3's built-in `createError`: the returned error is captured in the
|
|
7
|
+
* wide log event **and** serialized into the JSON response under a `data` key.
|
|
8
|
+
*
|
|
9
|
+
* ## What the caller sees vs what stays on the server
|
|
10
|
+
*
|
|
11
|
+
* Every field **except `internal`** is sent to the client in the HTTP response.
|
|
12
|
+
* `internal` is stripped from the response and only ever appears in your logs.
|
|
13
|
+
*
|
|
14
|
+
* | Field | Sent to client? | Purpose |
|
|
15
|
+
* | ---------- | :-------------: | ------------------------------------------------------------- |
|
|
16
|
+
* | `message` | yes | Short, safe summary of what went wrong. |
|
|
17
|
+
* | `status` | yes | HTTP status code (default `500`). |
|
|
18
|
+
* | `code` | yes | Stable machine-readable id (e.g. `'PAYMENT_DECLINED'`). |
|
|
19
|
+
* | `why` | yes | Human-readable cause — safe to expose, no secrets. |
|
|
20
|
+
* | `fix` | yes | What the caller can do about it. |
|
|
21
|
+
* | `link` | yes | Docs URL with more detail. |
|
|
22
|
+
* | `cause` | no (logged) | The original `Error` you caught. |
|
|
23
|
+
* | `internal` | no (logged) | Backend-only diagnostics — never reaches the client. |
|
|
24
|
+
*
|
|
25
|
+
* ## Do NOT leak internal system errors
|
|
26
|
+
*
|
|
27
|
+
* Put anything sensitive or diagnostic (raw Cumulocity core responses, DB
|
|
28
|
+
* errors, tokens, tenant internals) in `internal`, **not** in `message`/`why`.
|
|
29
|
+
* `internal` is logged for debugging but never serialized to the response.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // User-facing error — every field here is visible to the caller:
|
|
33
|
+
* throw createError({
|
|
34
|
+
* code: 'PAYMENT_DECLINED',
|
|
35
|
+
* message: 'Payment failed',
|
|
36
|
+
* status: 402,
|
|
37
|
+
* why: 'Card declined by issuer',
|
|
38
|
+
* fix: 'Try a different payment method',
|
|
39
|
+
* link: 'https://docs.example.com/payments/declined',
|
|
40
|
+
* })
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // Wrapping an upstream failure — keep the raw payload server-side only:
|
|
44
|
+
* catch (cause) {
|
|
45
|
+
* throw createError({
|
|
46
|
+
* status: 502,
|
|
47
|
+
* message: 'Upstream request failed', // safe, generic — what the caller sees
|
|
48
|
+
* cause: cause as Error,
|
|
49
|
+
* internal: { upstreamStatus: cause?.res?.status, upstreamBody: cause?.data },
|
|
50
|
+
* })
|
|
51
|
+
* }
|
|
52
|
+
*
|
|
53
|
+
* @see https://www.evlog.dev
|
|
54
|
+
*/
|
|
55
|
+
const createError$1 = createError;
|
|
56
|
+
//#endregion
|
|
57
|
+
export { createError$1 as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as getTenantOptionsByCategory, f as upsertTenantOption, i as deleteTenantOption, n as createBasicAuthHeader, p as createC8yManifest, s as getTenantOption } from "./c8y-api-
|
|
2
|
-
import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-
|
|
1
|
+
import { c as getTenantOptionsByCategory, f as upsertTenantOption, i as deleteTenantOption, n as createBasicAuthHeader, p as createC8yManifest, s as getTenantOption } from "./c8y-api-BFjK6AYb.mjs";
|
|
2
|
+
import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-BVQHqlgV.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { consola } from "consola";
|
|
5
5
|
//#region src/cli/commands/options.ts
|
|
@@ -11,11 +11,11 @@ var options_default = defineCommand({
|
|
|
11
11
|
args: {},
|
|
12
12
|
async run() {
|
|
13
13
|
consola.info("Loading configuration...");
|
|
14
|
-
const { env,
|
|
14
|
+
const { env, nitroConfig, configDir } = await loadC8yConfig();
|
|
15
15
|
consola.info("Validating environment variables...");
|
|
16
16
|
const envVars = validateBootstrapEnv(env);
|
|
17
17
|
consola.info("Loading manifest...");
|
|
18
|
-
const manifest = await createC8yManifest(configDir,
|
|
18
|
+
const manifest = await createC8yManifest(configDir, nitroConfig);
|
|
19
19
|
const category = manifest.settingsCategory || manifest.contextPath || manifest.name;
|
|
20
20
|
if (!manifest.settings || manifest.settings.length === 0) throw new Error("No settings defined in manifest. Add settings to your c8y.manifest configuration.");
|
|
21
21
|
consola.success(`Using category: ${category}`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as createBasicAuthHeader, p as createC8yManifest, t as assignUserRole, u as unassignUserRole } from "./c8y-api-
|
|
2
|
-
import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-
|
|
1
|
+
import { n as createBasicAuthHeader, p as createC8yManifest, t as assignUserRole, u as unassignUserRole } from "./c8y-api-BFjK6AYb.mjs";
|
|
2
|
+
import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-BVQHqlgV.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { consola } from "consola";
|
|
5
5
|
//#region src/cli/commands/roles.ts
|
|
@@ -11,11 +11,11 @@ var roles_default = defineCommand({
|
|
|
11
11
|
args: {},
|
|
12
12
|
async run() {
|
|
13
13
|
consola.info("Loading configuration...");
|
|
14
|
-
const { env,
|
|
14
|
+
const { env, nitroConfig, configDir } = await loadC8yConfig();
|
|
15
15
|
consola.info("Validating environment variables...");
|
|
16
16
|
const envVars = validateBootstrapEnv(env);
|
|
17
17
|
consola.info("Building manifest...");
|
|
18
|
-
const manifest = await createC8yManifest(configDir,
|
|
18
|
+
const manifest = await createC8yManifest(configDir, nitroConfig);
|
|
19
19
|
const authHeader = createBasicAuthHeader(envVars.C8Y_DEVELOPMENT_TENANT, envVars.C8Y_DEVELOPMENT_USER, envVars.C8Y_DEVELOPMENT_PASSWORD);
|
|
20
20
|
if (manifest.roles && manifest.roles.length > 0) {
|
|
21
21
|
const rolesToAssign = await consola.prompt("Select roles to assign to your user (unselected roles will be removed):", {
|