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/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-BbRS1-Ls.mjs";
2
- import { t as writeBootstrapCredentials } from "./env-file-B0BK-uZW.mjs";
3
- import { n as name } from "./package-DsLC9Mo3.mjs";
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
- ${routes.map((route) => generateMethod(route)).join("\n\n")}
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.manifest, nitro.logger);
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
- * Automatically bootstraps the microservice to the development tenant if needed.
521
- * Runs silently - only logs if bootstrap was performed or if errors occur.
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
- if ([
527
- "C8Y_BOOTSTRAP_TENANT",
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 authHeader = createBasicAuthHeader(process.env.C8Y_DEVELOPMENT_TENANT, process.env.C8Y_DEVELOPMENT_USER, process.env.C8Y_DEVELOPMENT_PASSWORD);
539
- const manifest = await createC8yManifest(nitro.options.rootDir, nitro.options.c8y?.manifest, nitro.logger);
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
- let appId;
542
- if (existingApp) {
543
- appId = existingApp.id;
544
- nitro.logger.debug(`Microservice "${manifest.name}" already exists (ID: ${appId}), retrieving bootstrap credentials...`);
545
- } else {
546
- appId = (await createMicroservice(baseUrl, manifest, authHeader)).id;
547
- nitro.logger.debug(`Microservice "${manifest.name}" created (ID: ${appId})`);
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
- await subscribeToApplication(baseUrl, process.env.C8Y_DEVELOPMENT_TENANT, appId, authHeader);
550
- const credentials = await getBootstrapCredentials(baseUrl, appId, authHeader);
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.success(`Auto-bootstrap complete! Bootstrap credentials written to ${envFileName}`);
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-BbRS1-Ls.mjs";
2
- import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-BRnvtthI.mjs";
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, c8yOptions, configDir } = await loadC8yConfig();
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, c8yOptions?.manifest);
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,6 +1,6 @@
1
1
  //#region package.json
2
2
  var name = "c8y-nitro";
3
- var version = "0.6.0";
3
+ var version = "0.7.1";
4
4
  var description = "Lightning fast Cumulocity IoT microservice development powered by Nitro";
5
5
  //#endregion
6
6
  export { name as n, version as r, description as t };
@@ -1,5 +1,5 @@
1
- import { n as createBasicAuthHeader, p as createC8yManifest, t as assignUserRole, u as unassignUserRole } from "./c8y-api-BbRS1-Ls.mjs";
2
- import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-BRnvtthI.mjs";
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, c8yOptions, configDir } = await loadC8yConfig();
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, c8yOptions?.manifest);
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):", {