c8y-nitro 0.10.3 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +2 -0
  2. package/dist/{apiClient-kxv5KHYJ.mjs → apiClient-GMQqcgQe.mjs} +2 -3
  3. package/dist/{autoBootstrap-DUjHuHe_.mjs → autoBootstrap-D4vEytWW.mjs} +6 -8
  4. package/dist/{bootstrap-DrMjty-k.mjs → bootstrap-DpYAhM7C.mjs} +6 -7
  5. package/dist/cli/index.mjs +6 -7
  6. package/dist/{config-BVQHqlgV.mjs → config-Dozrdw3A.mjs} +11 -8
  7. package/dist/{credentials-eKnJ7m9Y.d.mts → credentials-C_3m4LVX.d.mts} +0 -2
  8. package/dist/defaults-CrrCinRJ.mjs +20 -0
  9. package/dist/{c8y-api-DSs1xIIh.mjs → env-fUAhIWsI.mjs} +76 -3
  10. package/dist/{env-file-BrOTmVjA.mjs → env-file-DWbaFlIp.mjs} +1 -2
  11. package/dist/{index-BEFoDy-y.d.mts → index-CmIQfBpj.d.mts} +56 -22
  12. package/dist/index.d.mts +4 -6
  13. package/dist/index.mjs +20 -21
  14. package/dist/{logging-BUYT8Z2V.mjs → logging-B1PuWdiF.mjs} +1 -2
  15. package/dist/{manifest-DV3UW9Dl.mjs → manifest-BhrHDm8Y.mjs} +4 -6
  16. package/dist/{options-54uJmuUV.mjs → options-CgTptIpI.mjs} +5 -6
  17. package/dist/{package-Dqlz6AoZ.mjs → package-y3p_br3i.mjs} +2 -3
  18. package/dist/{roles-CNCObfa3.mjs → roles-DEIHe2Xt.mjs} +4 -5
  19. package/dist/runtime/handlers/invalidateTenantOptions.mjs +2 -408
  20. package/dist/runtime/handlers/liveness-readiness.mjs +1 -2
  21. package/dist/runtime/handlers/openapi.mjs +4 -5
  22. package/dist/runtime/handlers/rewriteRequestUrl.mjs +3 -4
  23. package/dist/runtime/middlewares/c8y-client.mjs +3 -4
  24. package/dist/runtime/middlewares/dev-user.dev.mjs +2 -3
  25. package/dist/runtime/plugins/c8y-variables.dev.mjs +2 -3
  26. package/dist/runtime/plugins/enrich-logs.mjs +2 -3
  27. package/dist/scope-CxEPZ74p.mjs +2763 -0
  28. package/dist/{client-BmSKIcuq.mjs → tenant-0MBgBY_o.mjs} +115 -139
  29. package/dist/testing/setup.d.mts +1 -0
  30. package/dist/testing/setup.mjs +62 -0
  31. package/dist/testing/vite.d.mts +26 -0
  32. package/dist/testing/vite.mjs +58 -0
  33. package/dist/testing-D5VjeD-F.mjs +14 -0
  34. package/dist/testing.d.mts +521 -0
  35. package/dist/testing.mjs +494 -0
  36. package/dist/{typegen-BO_AkyNc.mjs → typegen-Bw4VxFmA.mjs} +4 -5
  37. package/dist/types-D663gk80.d.mts +140 -0
  38. package/dist/types.d.mts +3 -3
  39. package/dist/utils.d.mts +48 -66
  40. package/dist/utils.mjs +16 -27
  41. package/package.json +36 -19
package/README.md CHANGED
@@ -13,6 +13,7 @@ Nitro-powered tooling for building Cumulocity IoT microservices without manually
13
13
  - 🔥 **File-based Routing** - Auto-discovered routes from your file structure
14
14
  - 🛠️ **TypeScript First** - Full type safety with excellent DX
15
15
  - 🔄 **Auto-Bootstrap** - Automatically registers and configures your microservice in development
16
+ - 🧪 **Testing** - Test your service in Vitest against a fake Cumulocity tenant you seed with devices, measurements, alarms, and more
16
17
 
17
18
  ## Quickstart
18
19
 
@@ -28,6 +29,7 @@ Useful entry points:
28
29
  - [Configuration](https://schplitt.github.io/c8y-nitro/guide/configuration)
29
30
  - [Module Options](https://schplitt.github.io/c8y-nitro/reference/module-options)
30
31
  - [Utilities](https://schplitt.github.io/c8y-nitro/reference/utilities)
32
+ - [Testing](https://schplitt.github.io/c8y-nitro/guide/testing)
31
33
 
32
34
  ## Development
33
35
 
@@ -1,7 +1,7 @@
1
- import { n as createC8yManifestFromNitro } from "./manifest-DV3UW9Dl.mjs";
1
+ import { n as createC8yManifestFromNitro } from "./manifest-BhrHDm8Y.mjs";
2
2
  import { dirname, join, relative } from "node:path";
3
3
  import { mkdir, writeFile } from "node:fs/promises";
4
- //#region src/module/apiClient.ts
4
+ // src/module/apiClient.ts
5
5
  /**
6
6
  * Converts route path to PascalCase function name.
7
7
  * Examples:
@@ -202,5 +202,4 @@ async function writeAPIClient(nitro, options) {
202
202
  await writeFile(outputFile, code, "utf-8");
203
203
  nitro.logger.success(`Generated API client with ${routes.length} routes at: ${relative(rootDir, outputFile)}`);
204
204
  }
205
- //#endregion
206
205
  export { writeAPIClient as t };
@@ -1,11 +1,11 @@
1
- import { t as createC8yManifest } from "./manifest-DV3UW9Dl.mjs";
2
- import { a as findMicroserviceByName, d as updateMicroservice, l as subscribeToApplication, n as createBasicAuthHeader, o as getBootstrapCredentials, r as createMicroservice } from "./c8y-api-DSs1xIIh.mjs";
3
- import { t as writeBootstrapCredentials } from "./env-file-BrOTmVjA.mjs";
1
+ import { t as createC8yManifest } from "./manifest-BhrHDm8Y.mjs";
2
+ import { h as updateMicroservice, l as findMicroserviceByName, o as createBasicAuthHeader, p as subscribeToApplication, s as createMicroservice, u as getBootstrapCredentials } from "./env-fUAhIWsI.mjs";
3
+ import { t as writeBootstrapCredentials } from "./env-file-DWbaFlIp.mjs";
4
+ import process from "node:process";
4
5
  import { join } from "node:path";
5
6
  import { mkdir, readFile, writeFile } from "node:fs/promises";
6
7
  import { createHash } from "node:crypto";
7
- import process from "node:process";
8
- //#region src/module/manifestCache.ts
8
+ // src/module/manifestCache.ts
9
9
  /**
10
10
  * Directory (inside the project's `node_modules`) where c8y-nitro persists
11
11
  * local dev state such as the last-synced manifest hash. Lives in
@@ -64,8 +64,7 @@ async function writeManifestCache(rootDir, state) {
64
64
  };
65
65
  await writeFile(getCacheFile(rootDir), JSON.stringify(payload, null, 2), "utf-8");
66
66
  }
67
- //#endregion
68
- //#region src/module/autoBootstrap.ts
67
+ // src/module/autoBootstrap.ts
69
68
  const BOOTSTRAP_ENV_VARS = [
70
69
  "C8Y_BOOTSTRAP_TENANT",
71
70
  "C8Y_BOOTSTRAP_USER",
@@ -212,5 +211,4 @@ async function finalizeBootstrap(opts) {
212
211
  });
213
212
  return credentials;
214
213
  }
215
- //#endregion
216
214
  export { readBootstrapCredentialsFromEnv as n, syncBootstrappedMicroservice as r, autoBootstrap as t };
@@ -1,10 +1,10 @@
1
- import { t as createC8yManifest } from "./manifest-DV3UW9Dl.mjs";
2
- import { a as findMicroserviceByName, d as updateMicroservice, l as subscribeToApplication, n as createBasicAuthHeader, o as getBootstrapCredentials, r as createMicroservice } from "./c8y-api-DSs1xIIh.mjs";
3
- import { t as writeBootstrapCredentials } from "./env-file-BrOTmVjA.mjs";
4
- import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-BVQHqlgV.mjs";
1
+ import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-Dozrdw3A.mjs";
2
+ import { h as updateMicroservice, l as findMicroserviceByName, o as createBasicAuthHeader, p as subscribeToApplication, s as createMicroservice, u as getBootstrapCredentials } from "./env-fUAhIWsI.mjs";
3
+ import { t as writeBootstrapCredentials } from "./env-file-DWbaFlIp.mjs";
4
+ import { t as createC8yManifest } from "./manifest-BhrHDm8Y.mjs";
5
5
  import { defineCommand, runCommand } from "citty";
6
6
  import { consola } from "consola";
7
- //#region src/cli/commands/bootstrap.ts
7
+ // src/cli/commands/bootstrap.ts
8
8
  var bootstrap_default = defineCommand({
9
9
  meta: {
10
10
  name: "bootstrap",
@@ -54,12 +54,11 @@ var bootstrap_default = defineCommand({
54
54
  consola.success(`Bootstrap credentials written to ${envFileName}`);
55
55
  if (manifest.roles && manifest.roles.length > 0) {
56
56
  if (await consola.prompt("Do you want to manage microservice roles for your development user?", { type: "confirm" })) {
57
- const rolesCommand = await import("./roles-CNCObfa3.mjs").then((r) => r.default);
57
+ const rolesCommand = await import("./roles-DEIHe2Xt.mjs").then((r) => r.default);
58
58
  await runCommand(rolesCommand, { rawArgs: [] });
59
59
  }
60
60
  }
61
61
  consola.success("Bootstrap complete!");
62
62
  }
63
63
  });
64
- //#endregion
65
64
  export { bootstrap_default as default };
@@ -1,6 +1,6 @@
1
- import { n as name, r as version, t as description } from "../package-Dqlz6AoZ.mjs";
1
+ import { n as name, r as version, t as description } from "../package-y3p_br3i.mjs";
2
2
  import { defineCommand, runMain } from "citty";
3
- //#region src/cli/index.ts
3
+ // src/cli/index.ts
4
4
  const main = defineCommand({
5
5
  meta: {
6
6
  name,
@@ -8,12 +8,11 @@ const main = defineCommand({
8
8
  description
9
9
  },
10
10
  subCommands: {
11
- bootstrap: () => import("../bootstrap-DrMjty-k.mjs").then((r) => r.default),
12
- roles: () => import("../roles-CNCObfa3.mjs").then((r) => r.default),
13
- options: () => import("../options-54uJmuUV.mjs").then((r) => r.default),
14
- typegen: () => import("../typegen-BO_AkyNc.mjs").then((r) => r.default)
11
+ bootstrap: () => import("../bootstrap-DpYAhM7C.mjs").then((r) => r.default),
12
+ roles: () => import("../roles-DEIHe2Xt.mjs").then((r) => r.default),
13
+ options: () => import("../options-CgTptIpI.mjs").then((r) => r.default),
14
+ typegen: () => import("../typegen-Bw4VxFmA.mjs").then((r) => r.default)
15
15
  }
16
16
  });
17
17
  runMain(main);
18
- //#endregion
19
18
  export {};
@@ -1,7 +1,8 @@
1
+ import { i as toPrefixedEnvVar, n as resolveEnvFileNames, t as applyC8yNitroEnvVars } from "./env-fUAhIWsI.mjs";
1
2
  import { dirname } from "pathe";
2
3
  import { loadConfig, loadDotenv } from "c12";
3
4
  import process from "process";
4
- //#region src/cli/utils/config.ts
5
+ // src/cli/utils/config.ts
5
6
  function normalizeBaseUrl(baseUrl) {
6
7
  return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
7
8
  }
@@ -11,20 +12,23 @@ function normalizeBaseUrl(baseUrl) {
11
12
  */
12
13
  async function loadC8yConfig() {
13
14
  const cwd = process.cwd();
14
- const env = await loadDotenv({
15
- cwd,
16
- fileName: [".env", ".env.local"]
17
- });
18
15
  const { config, _configFile } = await loadConfig({
19
16
  configFile: "nitro.config",
20
17
  cwd
21
18
  });
22
19
  if (!_configFile) throw new Error("No nitro.config file found. Please ensure you have a valid nitro.config file.");
23
20
  const configDir = dirname(_configFile);
21
+ const c8yOptions = config.c8y;
22
+ const env = await loadDotenv({
23
+ cwd: configDir,
24
+ fileName: resolveEnvFileNames(configDir, c8yOptions?.envFile),
25
+ interpolate: true
26
+ });
27
+ applyC8yNitroEnvVars(env);
24
28
  return {
25
29
  env,
26
30
  nitroConfig: config,
27
- c8yOptions: config.c8y,
31
+ c8yOptions,
28
32
  configDir,
29
33
  configFile: _configFile
30
34
  };
@@ -46,7 +50,7 @@ const REQUIRED_BOOTSTRAP_ENV_VARS = [
46
50
  */
47
51
  function validateBootstrapEnv(env) {
48
52
  const missing = REQUIRED_BOOTSTRAP_ENV_VARS.filter((key) => !env[key]);
49
- if (missing.length > 0) throw new Error(`Missing required environment variables:\n${missing.map((k) => ` - ${k}`).join("\n")}\n\nPlease set these in your .env or .env.local file.`);
53
+ if (missing.length > 0) throw new Error(`Missing required environment variables:\n${missing.map((k) => ` - ${k} (or ${toPrefixedEnvVar(k)})`).join("\n")}\n\nPlease set these in your .env or .env.local file (or a file configured via \`c8y.envFile\`).`);
50
54
  return {
51
55
  C8Y_BASEURL: normalizeBaseUrl(env.C8Y_BASEURL),
52
56
  C8Y_DEVELOPMENT_TENANT: env.C8Y_DEVELOPMENT_TENANT,
@@ -54,5 +58,4 @@ function validateBootstrapEnv(env) {
54
58
  C8Y_DEVELOPMENT_PASSWORD: env.C8Y_DEVELOPMENT_PASSWORD
55
59
  };
56
60
  }
57
- //#endregion
58
61
  export { validateBootstrapEnv as n, loadC8yConfig as t };
@@ -1,5 +1,3 @@
1
1
  import { ICredentials } from "@c8y/client";
2
- //#region src/types/credentials.d.ts
3
2
  type TenantCredentials = Record<string, ICredentials>;
4
- //#endregion
5
3
  export { TenantCredentials as t };
@@ -0,0 +1,20 @@
1
+ // src/testing/defaults.ts
2
+ /**
3
+ * Values the fake tenant uses when the plugin config leaves them out.
4
+ */
5
+ const DEFAULTS = {
6
+ tenant: "t12345",
7
+ baseUrl: "https://t12345.cumulocity.test",
8
+ user: {
9
+ userName: "test-user",
10
+ roles: []
11
+ },
12
+ bootstrapUser: "servicebootstrap_test",
13
+ bootstrapPassword: "bootstrap-password",
14
+ developmentPassword: "test-password"
15
+ };
16
+ /**
17
+ * Key under which the plugin provides its options to the test workers.
18
+ */
19
+ const PROVIDE_KEY = "c8yNitroTest";
20
+ export { PROVIDE_KEY as n, DEFAULTS as t };
@@ -1,5 +1,9 @@
1
+ import process from "node:process";
2
+ import { resolve } from "pathe";
1
3
  import { Buffer } from "node:buffer";
2
- //#region src/cli/utils/c8y-api.ts
4
+ import { statSync } from "node:fs";
5
+ import { setupDotenv } from "c12";
6
+ // src/cli/utils/c8y-api.ts
3
7
  /**
4
8
  * Creates a Basic Auth header for Cumulocity API requests.
5
9
  * Format: base64(tenant/user:password)
@@ -307,5 +311,74 @@ async function deleteTenantOption(baseUrl, category, key, authHeader) {
307
311
  throw new Error(`Failed to delete tenant option ${category}/${key}: ${response.status} ${response.statusText}\n${errorText}`, { cause: response });
308
312
  }
309
313
  }
310
- //#endregion
311
- export { findMicroserviceByName as a, getTenantOptionsByCategory as c, updateMicroservice as d, upsertTenantOption as f, deleteTenantOption as i, subscribeToApplication as l, createBasicAuthHeader as n, getBootstrapCredentials as o, createMicroservice as r, getCurrentApplicationSettings as s, assignUserRole as t, unassignUserRole as u };
314
+ // src/cli/utils/env.ts
315
+ /**
316
+ * The project's own env files. Always loaded last so they override any
317
+ * additional (e.g. monorepo-root) env files configured via `c8y.envFile`.
318
+ */
319
+ const LOCAL_ENV_FILES = [".env", ".env.local"];
320
+ /**
321
+ * Env vars that also accept a `C8Y_NITRO_`-prefixed variant
322
+ * (e.g. `C8Y_NITRO_DEVELOPMENT_USER`). The prefixed variant takes precedence,
323
+ * so shared env files can carry c8y-nitro credentials without clashing with
324
+ * other tools that read the generic `C8Y_*` names.
325
+ */
326
+ const PREFIXABLE_ENV_VARS = [
327
+ "C8Y_BASEURL",
328
+ "C8Y_DEVELOPMENT_TENANT",
329
+ "C8Y_DEVELOPMENT_USER",
330
+ "C8Y_DEVELOPMENT_PASSWORD"
331
+ ];
332
+ /**
333
+ * Maps a `C8Y_*` variable name to its `C8Y_NITRO_*` variant.
334
+ * @param name - Unprefixed variable name (e.g. `C8Y_DEVELOPMENT_USER`)
335
+ */
336
+ function toPrefixedEnvVar(name) {
337
+ return name.replace(/^C8Y_/, "C8Y_NITRO_");
338
+ }
339
+ /**
340
+ * Builds the ordered list of env files to load. Later entries override earlier
341
+ * ones, so configured extra files (monorepo root) come first and the project's
342
+ * own `.env`/`.env.local` last.
343
+ *
344
+ * Extra entries may point at a file (`../../.env`) or a directory (`../..`,
345
+ * expanded to its `.env` and `.env.local`).
346
+ *
347
+ * @param cwd - Directory that relative entries are resolved against
348
+ * @param envFile - Extra env file(s) or directory(ies) from `c8y.envFile`
349
+ */
350
+ function resolveEnvFileNames(cwd, envFile) {
351
+ return [...(typeof envFile === "string" ? [envFile] : envFile ?? []).flatMap((entry) => {
352
+ const resolved = resolve(cwd, entry);
353
+ if (statSync(resolved, { throwIfNoEntry: false })?.isDirectory()) return LOCAL_ENV_FILES.map((fileName) => resolve(resolved, fileName));
354
+ return [resolved];
355
+ }), ...LOCAL_ENV_FILES];
356
+ }
357
+ /**
358
+ * Applies `C8Y_NITRO_*` variants onto their unprefixed `C8Y_*` counterparts
359
+ * (see {@link PREFIXABLE_ENV_VARS}). Mutates `env` in place; a set prefixed
360
+ * variant always wins over the unprefixed value.
361
+ * @param env - Environment variables object (e.g. `process.env` or a loaded `.env`)
362
+ */
363
+ function applyC8yNitroEnvVars(env) {
364
+ for (const name of PREFIXABLE_ENV_VARS) {
365
+ const prefixed = env[toPrefixedEnvVar(name)];
366
+ if (prefixed) env[name] = prefixed;
367
+ }
368
+ }
369
+ /**
370
+ * Loads the configured env files into `process.env` (existing real environment
371
+ * variables are never overridden) and resolves `C8Y_NITRO_*` variants.
372
+ * Used during Nitro dev mode, where only the project's own env files would be
373
+ * picked up otherwise.
374
+ * @param rootDir - Project root directory
375
+ * @param envFile - Extra env file(s)/directory(ies) from `c8y.envFile`
376
+ */
377
+ async function setupC8yDevEnv(rootDir, envFile) {
378
+ await setupDotenv({
379
+ cwd: rootDir,
380
+ fileName: resolveEnvFileNames(rootDir, envFile)
381
+ });
382
+ applyC8yNitroEnvVars(process.env);
383
+ }
384
+ export { assignUserRole as a, deleteTenantOption as c, getCurrentApplicationSettings as d, getTenantOptionsByCategory as f, upsertTenantOption as g, updateMicroservice as h, toPrefixedEnvVar as i, findMicroserviceByName as l, unassignUserRole as m, resolveEnvFileNames as n, createBasicAuthHeader as o, subscribeToApplication as p, setupC8yDevEnv as r, createMicroservice as s, applyC8yNitroEnvVars as t, getBootstrapCredentials as u };
@@ -1,7 +1,7 @@
1
1
  import { join } from "node:path";
2
2
  import { readFile, writeFile } from "node:fs/promises";
3
3
  import { existsSync } from "node:fs";
4
- //#region src/cli/utils/env-file.ts
4
+ // src/cli/utils/env-file.ts
5
5
  /**
6
6
  * Writes or updates environment variables in a file.
7
7
  * Existing variables are overwritten, new ones are appended.
@@ -55,5 +55,4 @@ async function writeBootstrapCredentials(configDir, variables) {
55
55
  await writeEnvVariables(targetPath, variables);
56
56
  return targetName;
57
57
  }
58
- //#endregion
59
58
  export { writeBootstrapCredentials as t };
@@ -1,7 +1,6 @@
1
- import "./credentials-eKnJ7m9Y.mjs";
1
+ import "./credentials-C_3m4LVX.mjs";
2
2
  import { ConsumerResilienceOptions, SubscriptionApi } from "c8y-realtime";
3
3
  import { C8YTenantOptionKeysCacheConfig } from "c8y-nitro/types";
4
- //#region src/types/apiClient.d.ts
5
4
  interface C8YAPIClientOptions {
6
5
  /**
7
6
  * Relative directory from nitro configuration file to generate the API client into.
@@ -15,8 +14,6 @@ interface C8YAPIClientOptions {
15
14
  */
16
15
  contextPath?: string;
17
16
  }
18
- //#endregion
19
- //#region src/types/roles.generated.d.ts
20
17
  /**
21
18
  * AUTO-GENERATED by scripts/generate-roles.ts — do not edit by hand.
22
19
  *
@@ -30,8 +27,6 @@ interface C8YAPIClientOptions {
30
27
  * Cumulocity platform roles ("required roles") known from the core OpenAPI spec.
31
28
  */
32
29
  type C8YKnownRole = 'ROLE_ALARM_ADMIN' | 'ROLE_ALARM_READ' | 'ROLE_APPLICATION_MANAGEMENT_ADMIN' | 'ROLE_APPLICATION_MANAGEMENT_READ' | 'ROLE_AUDIT_ADMIN' | 'ROLE_AUDIT_READ' | 'ROLE_BINARY_ADMIN' | 'ROLE_BINARY_CREATE' | 'ROLE_BINARY_READ' | 'ROLE_BULK_OPERATION_ADMIN' | 'ROLE_BULK_OPERATION_READ' | 'ROLE_DEVICE' | 'ROLE_DEVICE_BOOTSTRAP' | 'ROLE_DEVICE_CONTROL_ADMIN' | 'ROLE_DEVICE_CONTROL_READ' | 'ROLE_EVENT_ADMIN' | 'ROLE_EVENT_READ' | 'ROLE_IDENTITY_ADMIN' | 'ROLE_IDENTITY_READ' | 'ROLE_INVENTORY_ADMIN' | 'ROLE_INVENTORY_CREATE' | 'ROLE_INVENTORY_READ' | 'ROLE_MANAGED_OBJECT_ADMIN' | 'ROLE_MANAGED_OBJECT_CREATE' | 'ROLE_MANAGED_OBJECT_READ' | 'ROLE_MEASUREMENT_ADMIN' | 'ROLE_MEASUREMENT_READ' | 'ROLE_NOTIFICATION_2_ADMIN' | 'ROLE_OPTION_MANAGEMENT_ADMIN' | 'ROLE_OPTION_MANAGEMENT_READ' | 'ROLE_RETENTION_RULE_ADMIN' | 'ROLE_RETENTION_RULE_READ' | 'ROLE_SMS_READ' | 'ROLE_SYSTEM' | 'ROLE_TENANT_ADMIN' | 'ROLE_TENANT_ALARM_ADMIN' | 'ROLE_TENANT_MANAGEMENT_ADMIN' | 'ROLE_TENANT_MANAGEMENT_CREATE' | 'ROLE_TENANT_MANAGEMENT_READ' | 'ROLE_TENANT_MANAGEMENT_UPDATE' | 'ROLE_TENANT_STATISTICS_READ' | 'ROLE_USER_MANAGEMENT_ADMIN' | 'ROLE_USER_MANAGEMENT_CREATE' | 'ROLE_USER_MANAGEMENT_OWN_ADMIN' | 'ROLE_USER_MANAGEMENT_OWN_READ' | 'ROLE_USER_MANAGEMENT_READ';
33
- //#endregion
34
- //#region src/types/roles.d.ts
35
30
  /**
36
31
  * Roles your microservice provides. Populated at runtime from the manifest's
37
32
  * `roles` field (see the module's generated `c8y-nitro.d.ts`).
@@ -43,8 +38,6 @@ interface C8YRoles$1 {}
43
38
  * other string (custom roles, or roles not yet in the generated list).
44
39
  */
45
40
  type C8YRole$1 = C8YKnownRole | (string & {});
46
- //#endregion
47
- //#region src/types/manifest.d.ts
48
41
  type C8YManifestOptions = Omit<C8YManifest, 'name' | 'version' | 'apiVersion' | 'key' | 'type' | 'provider' | 'openApiSpec'>;
49
42
  interface C8YManifest {
50
43
  /**
@@ -165,6 +158,42 @@ interface C8YManifest {
165
158
  * @example "/_openapi.json"
166
159
  */
167
160
  openApiSpec?: string;
161
+ /**
162
+ * MCP (Model Context Protocol) servers exposed by this microservice.
163
+ * Once the application is subscribed, Cumulocity AI Agent Manager
164
+ * auto-registers these entries, and MCP-aware clients (e.g., mc8yp)
165
+ * discover them from the manifest.
166
+ * @example [{ name: "my-service", url: "/service/my-service/mcp", type: "http", sendAuthentication: true }]
167
+ */
168
+ exposeMcpServers?: ExposedMcpServer[];
169
+ }
170
+ /**
171
+ * MCP server entry declared via `exposeMcpServers`.
172
+ */
173
+ interface ExposedMcpServer {
174
+ /**
175
+ * Name of the MCP server as shown in AI Agent Manager.
176
+ * @example "my-service"
177
+ */
178
+ name: string;
179
+ /**
180
+ * Human-readable description of what the MCP server offers.
181
+ */
182
+ description?: string;
183
+ /**
184
+ * URL of the MCP endpoint, usually the full service path including the context path.
185
+ * @example "/service/my-service/mcp"
186
+ */
187
+ url: string;
188
+ /**
189
+ * MCP transport type. Streamable HTTP (`"http"`) is the supported transport.
190
+ * @example "http"
191
+ */
192
+ type: 'http' | (string & {});
193
+ /**
194
+ * Forward the calling user's authentication to the MCP server.
195
+ */
196
+ sendAuthentication?: boolean;
168
197
  }
169
198
  /**
170
199
  * Company/organization information.
@@ -367,8 +396,6 @@ interface Extension {
367
396
  */
368
397
  [key: string]: unknown;
369
398
  }
370
- //#endregion
371
- //#region src/types/zip.d.ts
372
399
  interface C8YZipOptions {
373
400
  /**
374
401
  * Name of the generated zip file
@@ -392,8 +419,6 @@ interface C8YZipOptions {
392
419
  */
393
420
  manifest?: C8YManifestOptions;
394
421
  }
395
- //#endregion
396
- //#region src/types/cache.d.ts
397
422
  interface C8yCacheOptions {
398
423
  /**
399
424
  * Cache TTL for subscribed tenant credentials in seconds.
@@ -417,8 +442,6 @@ interface C8yCacheOptions {
417
442
  */
418
443
  tenantOptions?: C8YTenantOptionKeysCacheConfig;
419
444
  }
420
- //#endregion
421
- //#region src/types/docker.d.ts
422
445
  interface C8yDockerOptions {
423
446
  /**
424
447
  * Path to a custom Dockerfile, relative to the Nitro config file location
@@ -453,8 +476,6 @@ interface C8yDockerOptions {
453
476
  */
454
477
  extraInstructions?: string[];
455
478
  }
456
- //#endregion
457
- //#region src/types/realtime.d.ts
458
479
  /**
459
480
  * Options for the Notification 2.0 realtime clients (backed by `c8y-realtime`).
460
481
  *
@@ -526,8 +547,6 @@ interface C8yRealtimeOptions {
526
547
  */
527
548
  resilience?: ConsumerResilienceOptions;
528
549
  }
529
- //#endregion
530
- //#region src/types/tenantOptions.d.ts
531
550
  /**
532
551
  * Per-key cache TTL configuration for tenant options.
533
552
  * Overwritten by generated types from manifest settings (manifest.settings[].key).
@@ -544,8 +563,6 @@ type C8YTenantOptionKey$1 = string;
544
563
  * Overwritten by generated types from the manifest.
545
564
  */
546
565
  type C8YSettingsCategory$1 = string;
547
- //#endregion
548
- //#region src/types/index.d.ts
549
566
  interface C8yDevOptions {
550
567
  /**
551
568
  * Automatically inject the configured development user into incoming requests
@@ -605,6 +622,24 @@ interface C8yNitroModuleOptions {
605
622
  * @default false
606
623
  */
607
624
  enableTenantOptionsInvalidationRoute?: boolean;
625
+ /**
626
+ * Additional `.env` file(s) to load during development and for CLI commands,
627
+ * for example a shared monorepo-root env file.
628
+ *
629
+ * Entries are resolved relative to the project root (absolute paths work too)
630
+ * and may point at a file (`'../../.env'`) or a directory (`'../..'`, which
631
+ * loads its `.env` and `.env.local`).
632
+ *
633
+ * Precedence (highest first): real environment variables, the project's own
634
+ * `.env.local` and `.env`, then the entries here (later entries override
635
+ * earlier ones). Bootstrap credentials are still written to the project's
636
+ * own env file, keeping them per-microservice in a monorepo.
637
+ *
638
+ * Only used in dev mode and by the CLI - deployed microservices get their
639
+ * environment from the platform.
640
+ * @default []
641
+ */
642
+ envFile?: string | string[];
608
643
  /**
609
644
  * Disable auto-bootstrap during development.
610
645
  * When true, the module will not automatically register the microservice
@@ -615,5 +650,4 @@ interface C8yNitroModuleOptions {
615
650
  */
616
651
  skipBootstrap?: boolean;
617
652
  }
618
- //#endregion
619
- export { C8YTenantOptionKey$1 as a, C8yDockerOptions as c, C8YManifest as d, C8YManifestOptions as f, C8YAPIClientOptions as g, C8YKnownRole as h, C8YSettingsCategory$1 as i, C8yCacheOptions as l, C8YRoles$1 as m, C8yNitroModuleOptions as n, C8YTenantOptionKeysCacheConfig$1 as o, C8YRole$1 as p, C8yOpenAPIOptions as r, C8yRealtimeOptions as s, C8yDevOptions as t, C8YZipOptions as u };
653
+ export { C8YAPIClientOptions as _, C8YTenantOptionKey$1 as a, C8yDockerOptions as c, C8YManifest as d, C8YManifestOptions as f, C8YKnownRole as g, C8YRoles$1 as h, C8YSettingsCategory$1 as i, C8yCacheOptions as l, C8YRole$1 as m, C8yNitroModuleOptions as n, C8YTenantOptionKeysCacheConfig$1 as o, ExposedMcpServer as p, C8yOpenAPIOptions as r, C8yRealtimeOptions as s, C8yDevOptions as t, C8YZipOptions as u };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,6 @@
1
- import { n as C8yNitroModuleOptions } from "./index-BEFoDy-y.mjs";
2
- import { t as TenantCredentials } from "./credentials-eKnJ7m9Y.mjs";
1
+ import { n as C8yNitroModuleOptions } from "./index-CmIQfBpj.mjs";
2
+ import { t as TenantCredentials } from "./credentials-C_3m4LVX.mjs";
3
3
  import { NitroModule } from "nitro/types";
4
- //#region src/index.d.ts
5
4
  declare module 'nitro/types' {
6
5
  interface NitroOptions {
7
6
  c8y?: C8yNitroModuleOptions;
@@ -12,6 +11,5 @@ declare module 'nitro/types' {
12
11
  'c8y:tenantCredentialsUpdated': (prev: TenantCredentials | null, next: TenantCredentials) => void;
13
12
  }
14
13
  }
15
- declare function c8y(): NitroModule;
16
- //#endregion
17
- export { c8y, c8y as default };
14
+ export declare function c8y(): NitroModule;
15
+ export { c8y as default };
package/dist/index.mjs CHANGED
@@ -1,7 +1,9 @@
1
- import { a as GENERATED_INVALIDATE_TENANT_OPTIONS_ROUTE, i as C8Y_NITRO_TYPES_FILE, n as createC8yManifestFromNitro, o as GENERATED_LIVENESS_ROUTE, r as C8Y_NITRO_TYPES_DIR, s as GENERATED_READINESS_ROUTE, t as createC8yManifest } from "./manifest-DV3UW9Dl.mjs";
2
- import { t as writeAPIClient } from "./apiClient-kxv5KHYJ.mjs";
3
- import { t as autoBootstrap } from "./autoBootstrap-DUjHuHe_.mjs";
4
- import { n as name } from "./package-Dqlz6AoZ.mjs";
1
+ import { t as writeAPIClient } from "./apiClient-GMQqcgQe.mjs";
2
+ import { a as GENERATED_INVALIDATE_TENANT_OPTIONS_ROUTE, i as C8Y_NITRO_TYPES_FILE, n as createC8yManifestFromNitro, o as GENERATED_LIVENESS_ROUTE, r as C8Y_NITRO_TYPES_DIR, s as GENERATED_READINESS_ROUTE, t as createC8yManifest } from "./manifest-BhrHDm8Y.mjs";
3
+ import { t as autoBootstrap } from "./autoBootstrap-D4vEytWW.mjs";
4
+ import { r as setupC8yDevEnv } from "./env-fUAhIWsI.mjs";
5
+ import { n as name } from "./package-y3p_br3i.mjs";
6
+ import process from "node:process";
5
7
  import { basename, join, resolve } from "node:path";
6
8
  import { copyFile, mkdir, writeFile } from "node:fs/promises";
7
9
  import { x } from "tinyexec";
@@ -15,7 +17,7 @@ import { isValidRealtimeName, toRealtimeName } from "c8y-realtime";
15
17
  import { createHash } from "node:crypto";
16
18
  import { fileURLToPath } from "node:url";
17
19
  import evlog from "evlog/nitro/v3";
18
- //#region src/module/docker.ts
20
+ // src/module/docker.ts
19
21
  /**
20
22
  * Generate the Dockerfile content for a Nitro build
21
23
  * @param outputDirName - Name of the output directory (e.g., '.output')
@@ -118,8 +120,7 @@ async function createDockerImage(nitro, options = {}) {
118
120
  nitro.logger.debug("Docker image creation complete.");
119
121
  return imageTarPath;
120
122
  }
121
- //#endregion
122
- //#region src/module/c8yzip.ts
123
+ // src/module/c8yzip.ts
123
124
  const spinnies = new Spinnies();
124
125
  function formatBytes(bytes) {
125
126
  if (bytes === 0) return "0 B";
@@ -178,8 +179,7 @@ async function createC8yZip(nitro, options = {}, dockerOptions = {}) {
178
179
  nitro.logger.log(colors.gray(` └─ ${outputFile} (${zipSize})`));
179
180
  nitro.logger.info("Zip file can be uploaded to Cumulocity IoT platform");
180
181
  }
181
- //#endregion
182
- //#region src/module/runtime.ts
182
+ // src/module/runtime.ts
183
183
  function setupRuntime(nitro, manifest) {
184
184
  nitro.logger.debug("Setting up C8Y nitro runtime");
185
185
  const roles = manifest.roles ?? [];
@@ -216,8 +216,7 @@ export const c8yTenantOptionKeys = [${settingKeys.map((key) => `'${key}'`).join(
216
216
  writeFileSync(typesFile, typesContent, { encoding: "utf-8" });
217
217
  nitro.logger.debug(`Written C8Y types to ${typesFile}`);
218
218
  }
219
- //#endregion
220
- //#region src/module/runtimeConfig.ts
219
+ // src/module/runtimeConfig.ts
221
220
  /**
222
221
  * Derive a stable, unique realtime client name from the microservice identity
223
222
  * (its `contextPath`, falling back to `name`).
@@ -266,8 +265,7 @@ async function setupRuntimeConfig(nitro, options) {
266
265
  const probePaths = [options.manifest?.livenessProbe?.httpGet?.path, options.manifest?.readinessProbe?.httpGet?.path].filter((path) => Boolean(path));
267
266
  nitro.options.runtimeConfig.c8yOpenApiExcludeRoutes = [...excludeInternalRoutes ? ["/_", ...probePaths] : [], ...options.openapi?.excludeRoutes ?? []];
268
267
  }
269
- //#endregion
270
- //#region src/module/registerRuntime.ts
268
+ // src/module/registerRuntime.ts
271
269
  /**
272
270
  * Links runtime middleware, handlers, and plugins to the nitro instance.
273
271
  * Works by having the handlers in a relative path to this file.
@@ -363,8 +361,7 @@ function registerRuntime(nitro, options = {}) {
363
361
  }
364
362
  nitro.options.handlers.push(...handlers);
365
363
  }
366
- //#endregion
367
- //#region src/module/probeCheck.ts
364
+ // src/module/probeCheck.ts
368
365
  function checkProbes(nitro, manifestOptions = {}) {
369
366
  if (manifestOptions.livenessProbe?.httpGet) {
370
367
  const probe = manifestOptions.livenessProbe.httpGet;
@@ -391,8 +388,7 @@ function checkProbeEndpoint(nitro, probe, probeType) {
391
388
  nitro.logger.warn(`${probeType} route "${path}" exists but does not accept ${method} requests. Available methods: ${availableMethods || "none specified"}. The probe will fail at runtime.`);
392
389
  }
393
390
  }
394
- //#endregion
395
- //#region src/index.ts
391
+ // src/index.ts
396
392
  function c8y() {
397
393
  return {
398
394
  name: "c8y-nitro",
@@ -424,20 +420,24 @@ function c8y() {
424
420
  exclude: [manifest.livenessProbe?.httpGet?.path, manifest.readinessProbe?.httpGet?.path].filter(Boolean)
425
421
  });
426
422
  await setupEvlog(nitro);
427
- if (nitro.options.preset === "nitro-dev" && !options.skipBootstrap) await autoBootstrap(nitro);
423
+ if (nitro.options.preset === "nitro-dev") {
424
+ await setupC8yDevEnv(nitro.options.rootDir, options.envFile);
425
+ if (!options.skipBootstrap && !process.env.VITEST) await autoBootstrap(nitro);
426
+ }
428
427
  await setupRuntimeConfig(nitro, options);
429
428
  setupRuntime(nitro, manifest);
429
+ registerRuntime(nitro, options);
430
430
  nitro.hooks.hook("dev:reload", async () => {
431
+ await setupC8yDevEnv(nitro.options.rootDir, options.envFile);
431
432
  manifest = await createC8yManifestFromNitro(nitro);
432
433
  setupRuntime(nitro, manifest);
433
- if (!options.skipBootstrap) await autoBootstrap(nitro);
434
+ if (!options.skipBootstrap && !process.env.VITEST) await autoBootstrap(nitro);
434
435
  if (options.apiClient) {
435
436
  nitro.logger.debug("Generating C8Y API client");
436
437
  await writeAPIClient(nitro, options);
437
438
  }
438
439
  });
439
440
  nitro.hooks.hook("build:before", async () => {
440
- registerRuntime(nitro, options);
441
441
  if (options.manifest) checkProbes(nitro, options.manifest);
442
442
  });
443
443
  nitro.hooks.hook("close", async () => {
@@ -446,5 +446,4 @@ function c8y() {
446
446
  }
447
447
  };
448
448
  }
449
- //#endregion
450
449
  export { c8y, c8y as default };
@@ -1,6 +1,6 @@
1
1
  import { createError } from "evlog";
2
2
  import "evlog/nitro/v3";
3
- //#region src/utils/logging.ts
3
+ // src/utils/logging.ts
4
4
  /**
5
5
  * Create a structured error for a Cumulocity microservice. Always prefer this
6
6
  * over Nitro/h3's built-in `createError`: the returned error is captured in the
@@ -53,5 +53,4 @@ import "evlog/nitro/v3";
53
53
  * @see https://www.evlog.dev
54
54
  */
55
55
  const createError$1 = createError;
56
- //#endregion
57
56
  export { createError$1 as t };
@@ -1,5 +1,5 @@
1
1
  import { readPackage } from "pkg-types";
2
- //#region src/module/constants.ts
2
+ // src/module/constants.ts
3
3
  const GENERATED_LIVENESS_ROUTE = "/_c8y_nitro/liveness";
4
4
  const GENERATED_READINESS_ROUTE = "/_c8y_nitro/readiness";
5
5
  const GENERATED_INVALIDATE_TENANT_OPTIONS_ROUTE = "/_c8y_nitro/invalidate-tenant-options";
@@ -13,8 +13,7 @@ const C8Y_NITRO_TYPES_DIR = "node_modules/.c8y-nitro";
13
13
  * File name of the generated declarations inside {@link C8Y_NITRO_TYPES_DIR}.
14
14
  */
15
15
  const C8Y_NITRO_TYPES_FILE = "types.d.ts";
16
- //#endregion
17
- //#region src/module/manifest.ts
16
+ // src/module/manifest.ts
18
17
  const DEFAULT_OPENAPI_ROUTE = "/_openapi.json";
19
18
  const ROLE_OPTION_MANAGEMENT_READ = "ROLE_OPTION_MANAGEMENT_READ";
20
19
  /**
@@ -85,7 +84,7 @@ async function readPackageJsonFieldsForManifest(rootDir, logger) {
85
84
  * Used by both the Nitro module and the CLI commands.
86
85
  * @param rootDir - Directory containing the project's `package.json`
87
86
  * @param nitroConfig - Nitro config (or options) for this project
88
- * @param logger - Optional logger for debug output
87
+ * @param logger - Optional Nitro logger (`nitro.logger`) for debug output
89
88
  */
90
89
  async function createC8yManifest(rootDir, nitroConfig = {}, logger) {
91
90
  const { options, openApiSpec } = extractManifestInputs(nitroConfig);
@@ -108,8 +107,8 @@ async function createC8yManifest(rootDir, nitroConfig = {}, logger) {
108
107
  }
109
108
  const manifest = {
110
109
  ...restManifestFields,
111
- ...probeFields,
112
110
  ...options,
111
+ ...probeFields,
113
112
  ...requiredRoles !== void 0 ? { requiredRoles } : {},
114
113
  ...openApiSpec ? { openApiSpec } : {},
115
114
  provider,
@@ -131,5 +130,4 @@ async function createC8yManifest(rootDir, nitroConfig = {}, logger) {
131
130
  async function createC8yManifestFromNitro(nitro) {
132
131
  return createC8yManifest(nitro.options.rootDir, nitro.options, nitro.logger);
133
132
  }
134
- //#endregion
135
133
  export { GENERATED_INVALIDATE_TENANT_OPTIONS_ROUTE as a, C8Y_NITRO_TYPES_FILE as i, createC8yManifestFromNitro as n, GENERATED_LIVENESS_ROUTE as o, C8Y_NITRO_TYPES_DIR as r, GENERATED_READINESS_ROUTE as s, createC8yManifest as t };