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.
@@ -1,6 +1,6 @@
1
- import { a as findMicroserviceByName, d as updateMicroservice, l as subscribeToApplication, 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 validateBootstrapEnv, t as loadC8yConfig } from "./config-BRnvtthI.mjs";
1
+ import { a as findMicroserviceByName, d as updateMicroservice, l as subscribeToApplication, 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 validateBootstrapEnv, t as loadC8yConfig } from "./config-BVQHqlgV.mjs";
4
4
  import { defineCommand, runCommand } from "citty";
5
5
  import { consola } from "consola";
6
6
  //#region src/cli/commands/bootstrap.ts
@@ -12,11 +12,11 @@ var bootstrap_default = defineCommand({
12
12
  args: {},
13
13
  async run() {
14
14
  consola.info("Loading configuration...");
15
- const { env, c8yOptions, configDir } = await loadC8yConfig();
15
+ const { env, nitroConfig, configDir } = await loadC8yConfig();
16
16
  consola.info("Validating environment variables...");
17
17
  const envVars = validateBootstrapEnv(env);
18
18
  consola.info("Building manifest...");
19
- const manifest = await createC8yManifest(configDir, c8yOptions?.manifest);
19
+ const manifest = await createC8yManifest(configDir, nitroConfig);
20
20
  consola.success(`Manifest created for: ${manifest.name} v${manifest.version}`);
21
21
  const authHeader = createBasicAuthHeader(envVars.C8Y_DEVELOPMENT_TENANT, envVars.C8Y_DEVELOPMENT_USER, envVars.C8Y_DEVELOPMENT_PASSWORD);
22
22
  consola.info(`Checking if microservice "${manifest.name}" exists...`);
@@ -52,7 +52,7 @@ var bootstrap_default = defineCommand({
52
52
  });
53
53
  consola.success(`Bootstrap credentials written to ${envFileName}`);
54
54
  if (manifest.roles && manifest.roles.length > 0) {
55
- if (await consola.prompt("Do you want to manage microservice roles for your development user?", { type: "confirm" })) await runCommand(await import("./roles-D7rpbxPp.mjs").then((r) => r.default), { rawArgs: [] });
55
+ if (await consola.prompt("Do you want to manage microservice roles for your development user?", { type: "confirm" })) await runCommand(await import("./roles-B58Fguop.mjs").then((r) => r.default), { rawArgs: [] });
56
56
  }
57
57
  consola.success("Bootstrap complete!");
58
58
  }
@@ -6,8 +6,29 @@ const GENERATED_READINESS_ROUTE = "/_c8y_nitro/readiness";
6
6
  const GENERATED_INVALIDATE_TENANT_OPTIONS_ROUTE = "/_c8y_nitro/invalidate-tenant-options";
7
7
  //#endregion
8
8
  //#region src/module/manifest.ts
9
+ const DEFAULT_OPENAPI_ROUTE = "/_openapi.json";
9
10
  const ROLE_OPTION_MANAGEMENT_READ = "ROLE_OPTION_MANAGEMENT_READ";
10
- const ROLE_OPTION_MANAGEMENT_ADMIN = "ROLE_OPTION_MANAGEMENT_ADMIN";
11
+ /**
12
+ * Decide whether Nitro will expose an OpenAPI spec in the deployed build,
13
+ * and if so return the configured route (with leading slash).
14
+ * Returns null when OpenAPI is not enabled for production.
15
+ * @param config - Nitro config (or options) to inspect
16
+ */
17
+ function resolveNitroOpenApiRoute(config) {
18
+ if (!config.experimental?.openAPI) return null;
19
+ if (!config.openAPI?.production) return null;
20
+ return config.openAPI?.route || DEFAULT_OPENAPI_ROUTE;
21
+ }
22
+ /**
23
+ * Pull the parts of a Nitro config that feed manifest generation.
24
+ * @param config - Nitro config (or options) loaded from disk or from a Nitro instance
25
+ */
26
+ function extractManifestInputs(config) {
27
+ return {
28
+ options: config.c8y?.manifest ?? {},
29
+ openApiSpec: resolveNitroOpenApiRoute(config) ?? void 0
30
+ };
31
+ }
11
32
  function validateManifestSettings(options) {
12
33
  const invalidSettings = options.settings?.filter((setting) => typeof setting.defaultValue !== "string" || setting.defaultValue.length === 0) ?? [];
13
34
  if (invalidSettings.length === 0) return;
@@ -39,13 +60,18 @@ async function readPackageJsonFieldsForManifest(rootDir, logger) {
39
60
  };
40
61
  }
41
62
  /**
42
- * Creates a Cumulocity manifest from rootDir and options.
43
- * Standalone function that can be used by CLI or module.
44
- * @param rootDir - Directory containing package.json
45
- * @param options - Manifest options
63
+ * Create a Cumulocity manifest from a project root and its Nitro config.
64
+ * Reads `package.json` for identity fields, takes user manifest options from
65
+ * `nitroConfig.c8y.manifest`, and auto-injects `openApiSpec` when Nitro
66
+ * OpenAPI is enabled for production.
67
+ *
68
+ * Used by both the Nitro module and the CLI commands.
69
+ * @param rootDir - Directory containing the project's `package.json`
70
+ * @param nitroConfig - Nitro config (or options) for this project
46
71
  * @param logger - Optional logger for debug output
47
72
  */
48
- async function createC8yManifest(rootDir, options = {}, logger) {
73
+ async function createC8yManifest(rootDir, nitroConfig = {}, logger) {
74
+ const { options, openApiSpec } = extractManifestInputs(nitroConfig);
49
75
  validateManifestSettings(options);
50
76
  const { name, version, provider, ...restManifestFields } = await readPackageJsonFieldsForManifest(rootDir, logger);
51
77
  const probeFields = {};
@@ -59,7 +85,7 @@ async function createC8yManifest(rootDir, options = {}, logger) {
59
85
  };
60
86
  const key = `${name}-key`;
61
87
  let requiredRoles = options.requiredRoles ? [...options.requiredRoles] : void 0;
62
- if (options.settings && options.settings.length > 0 && !requiredRoles?.includes(ROLE_OPTION_MANAGEMENT_READ) && !requiredRoles?.includes(ROLE_OPTION_MANAGEMENT_ADMIN)) {
88
+ if (options.settings && options.settings.length > 0 && !requiredRoles?.includes(ROLE_OPTION_MANAGEMENT_READ)) {
63
89
  requiredRoles = [...requiredRoles ?? [], ROLE_OPTION_MANAGEMENT_READ];
64
90
  logger?.debug(`Auto-added ${ROLE_OPTION_MANAGEMENT_READ} to requiredRoles because manifest.settings are defined`);
65
91
  }
@@ -68,6 +94,7 @@ async function createC8yManifest(rootDir, options = {}, logger) {
68
94
  ...probeFields,
69
95
  ...options,
70
96
  ...requiredRoles !== void 0 ? { requiredRoles } : {},
97
+ ...openApiSpec ? { openApiSpec } : {},
71
98
  provider,
72
99
  name,
73
100
  version,
@@ -75,6 +102,7 @@ async function createC8yManifest(rootDir, options = {}, logger) {
75
102
  key,
76
103
  type: "MICROSERVICE"
77
104
  };
105
+ if (openApiSpec) logger?.debug(`Auto-added openApiSpec="${openApiSpec}" to manifest (Nitro OpenAPI is enabled)`);
78
106
  logger?.debug(`Created Cumulocity manifest: ${JSON.stringify(manifest, null, 2)}`);
79
107
  return manifest;
80
108
  }
@@ -84,7 +112,7 @@ async function createC8yManifest(rootDir, options = {}, logger) {
84
112
  * @param nitro - The Nitro instance
85
113
  */
86
114
  async function createC8yManifestFromNitro(nitro) {
87
- return createC8yManifest(nitro.options.rootDir, nitro.options.c8y?.manifest, nitro.logger);
115
+ return createC8yManifest(nitro.options.rootDir, nitro.options, nitro.logger);
88
116
  }
89
117
  //#endregion
90
118
  //#region src/cli/utils/c8y-api.ts
@@ -1,4 +1,4 @@
1
- import { n as name, r as version, t as description } from "../package-DsLC9Mo3.mjs";
1
+ import { n as name, r as version, t as description } from "../package-D5xRshsx.mjs";
2
2
  import { defineCommand, runMain } from "citty";
3
3
  //#region src/cli/index.ts
4
4
  runMain(defineCommand({
@@ -8,9 +8,9 @@ runMain(defineCommand({
8
8
  description
9
9
  },
10
10
  subCommands: {
11
- bootstrap: () => import("../bootstrap-DUDpmXcU.mjs").then((r) => r.default),
12
- roles: () => import("../roles-D7rpbxPp.mjs").then((r) => r.default),
13
- options: () => import("../options-Web5UyIU.mjs").then((r) => r.default)
11
+ bootstrap: () => import("../bootstrap-ObaEF7rN.mjs").then((r) => r.default),
12
+ roles: () => import("../roles-B58Fguop.mjs").then((r) => r.default),
13
+ options: () => import("../options-B7_yb_wr.mjs").then((r) => r.default)
14
14
  }
15
15
  }));
16
16
  //#endregion
@@ -0,0 +1,236 @@
1
+ import { t as createError } from "./logging-BUYT8Z2V.mjs";
2
+ import { BasicAuth, Client, MicroserviceClientRequestAuth } from "@c8y/client";
3
+ import { defineCachedFunction } from "nitro/cache";
4
+ import { Buffer } from "node:buffer";
5
+ import { createHash, randomBytes } from "node:crypto";
6
+ import process from "node:process";
7
+ import { useRuntimeConfig } from "nitro/runtime-config";
8
+ import { useNitroHooks } from "nitro/app";
9
+ //#region src/utils/internal/common.ts
10
+ /**
11
+ * Converts undici Request headers to the format expected by MicroserviceClientRequestAuth.\
12
+ * Extracts the following headers from the request:
13
+ * - `authorization`: Used for Basic Auth or Bearer token authentication
14
+ * - `cookie`: Used to extract XSRF-TOKEN and authorization token from cookies
15
+ *
16
+ * The MicroserviceClientRequestAuth class will automatically:
17
+ * - Extract XSRF-TOKEN from cookies for CSRF protection
18
+ * - Extract authorization token from cookies (prioritized over header auth)
19
+ * - Fall back to Authorization header if no cookie-based auth is present
20
+ *
21
+ * @param request - The HTTP request containing headers
22
+ * @returns Headers object compatible with \@c8y/client's MicroserviceClientRequestAuth
23
+ */
24
+ function convertRequestHeadersToC8yFormat(request) {
25
+ const headers = {};
26
+ const authorization = request.headers.get("authorization");
27
+ if (authorization) headers.authorization = authorization;
28
+ const cookie = request.headers.get("cookie");
29
+ if (cookie) headers.cookie = cookie;
30
+ return headers;
31
+ }
32
+ //#endregion
33
+ //#region src/utils/internal/tenant.ts
34
+ const USER_TENANT_CACHE_SALT = randomBytes(32).toString("hex");
35
+ function getCookieValue(cookieHeader, name) {
36
+ try {
37
+ const value = cookieHeader?.match(`(^|;)\\s*${name}\\s*=\\s*([^;]+)`);
38
+ return value ? value.pop() : void 0;
39
+ } catch {
40
+ return;
41
+ }
42
+ }
43
+ function getCurrentUserTenantCacheKeyMaterial(requestOrEvent) {
44
+ const request = "req" in requestOrEvent ? requestOrEvent.req : requestOrEvent;
45
+ const cookieAuth = getCookieValue(request.headers.get("cookie"), "authorization");
46
+ if (cookieAuth) return `cookie:${cookieAuth}`;
47
+ const authorization = request.headers.get("authorization");
48
+ if (authorization) return `header:${authorization}`;
49
+ }
50
+ function createCurrentUserTenantCacheKey(requestOrEvent) {
51
+ const material = getCurrentUserTenantCacheKeyMaterial(requestOrEvent);
52
+ if (!material) throw new Error("Cannot create current user tenant cache key without auth material");
53
+ return createHash("sha256").update(USER_TENANT_CACHE_SALT).update(":").update(material).digest("hex");
54
+ }
55
+ function tryGetTenantFromBasicAuth(requestOrEvent) {
56
+ const authorization = ("req" in requestOrEvent ? requestOrEvent.req : requestOrEvent).headers.get("authorization");
57
+ if (!authorization?.startsWith("Basic ")) return;
58
+ try {
59
+ const decoded = Buffer.from(authorization.slice(6), "base64").toString("utf8");
60
+ const separatorIndex = decoded.indexOf(":");
61
+ const userPart = separatorIndex === -1 ? decoded : decoded.slice(0, separatorIndex);
62
+ const slashIndex = userPart.indexOf("/");
63
+ if (slashIndex <= 0) return;
64
+ return userPart.slice(0, slashIndex);
65
+ } catch {
66
+ return;
67
+ }
68
+ }
69
+ const getCurrentUserTenantId = defineCachedFunction(async (requestOrEvent) => {
70
+ const basicTenant = tryGetTenantFromBasicAuth(requestOrEvent);
71
+ if (basicTenant) return basicTenant;
72
+ return (await useUserClient(requestOrEvent).tenant.current()).data.name;
73
+ }, {
74
+ maxAge: 60,
75
+ name: "_c8y_nitro_get_current_user_tenant_id",
76
+ group: "c8y_nitro",
77
+ swr: false,
78
+ getKey: (requestOrEvent) => createCurrentUserTenantCacheKey(requestOrEvent),
79
+ shouldBypassCache: (requestOrEvent) => !getCurrentUserTenantCacheKeyMaterial(requestOrEvent)
80
+ });
81
+ const cachedTenantDomain = defineCachedFunction(async (requestOrEvent, _tenantId) => {
82
+ return (await useUserClient(requestOrEvent).tenant.current()).data.domainName;
83
+ }, {
84
+ maxAge: 86400,
85
+ name: "_c8y_nitro_get_user_tenant_domain",
86
+ group: "c8y_nitro",
87
+ swr: false,
88
+ getKey: (_requestOrEvent, tenantId) => tenantId
89
+ });
90
+ /**
91
+ * Resolves the public domain of the current user's tenant (e.g. `tenant.cumulocity.com`)
92
+ * via `/tenant/currentTenant` with the user's credentials from the current request.
93
+ * @param requestOrEvent - The H3Event or ServerRequest from the current request
94
+ */
95
+ async function getUserTenantDomain(requestOrEvent) {
96
+ const tenantId = await getCurrentUserTenantId(requestOrEvent);
97
+ return cachedTenantDomain(requestOrEvent, tenantId);
98
+ }
99
+ //#endregion
100
+ //#region src/utils/credentials.ts
101
+ let prevCredentials = null;
102
+ function shouldEmitTenantCredentialsUpdated(prev, next) {
103
+ if (!prev) return true;
104
+ const prevTenantIds = Object.keys(prev);
105
+ const nextTenantIds = Object.keys(next);
106
+ if (prevTenantIds.length !== nextTenantIds.length) return true;
107
+ return new Set(nextTenantIds).symmetricDifference(new Set(prevTenantIds)).size > 0;
108
+ }
109
+ /**
110
+ * Fetches credentials for all tenants subscribed to this microservice.\
111
+ * Uses bootstrap credentials from runtime config to query the microservice subscriptions API.\
112
+ * Results are cached based on the configured TTL (default: 10 minutes).\
113
+ * @returns Object mapping tenant IDs to their respective credentials
114
+ * @config Cache TTL can be configured via:
115
+ * - `c8y.cache.credentialsTTL` in the Nitro config (value in seconds)
116
+ * - `NITRO_C8Y_CREDENTIALS_CACHE_TTL` environment variable
117
+ * @example
118
+ * // Get all subscribed tenant credentials:
119
+ * const credentials = await useSubscribedTenantCredentials()
120
+ * console.log(Object.keys(credentials)) // ['t12345', 't67890']
121
+ *
122
+ * // Access specific tenant:
123
+ * const tenant1Creds = credentials['t12345']
124
+ *
125
+ * // Invalidate cache:
126
+ * await useSubscribedTenantCredentials.invalidate()
127
+ *
128
+ * // Force refresh:
129
+ * const freshCreds = await useSubscribedTenantCredentials.refresh()
130
+ */
131
+ const cachedSubscribedTenantCredentials = defineCachedFunction(async () => {
132
+ const newCredentials = (await Client.getMicroserviceSubscriptions({
133
+ tenant: process.env.C8Y_BOOTSTRAP_TENANT,
134
+ user: process.env.C8Y_BOOTSTRAP_USER,
135
+ password: process.env.C8Y_BOOTSTRAP_PASSWORD
136
+ }, process.env.C8Y_BASEURL)).reduce((acc, cred) => {
137
+ if (cred.tenant) acc[cred.tenant] = cred;
138
+ return acc;
139
+ }, {});
140
+ if (shouldEmitTenantCredentialsUpdated(prevCredentials, newCredentials)) useNitroHooks().callHook("c8y:tenantCredentialsUpdated", prevCredentials, newCredentials);
141
+ prevCredentials = newCredentials;
142
+ return newCredentials;
143
+ }, {
144
+ maxAge: useRuntimeConfig().c8yCredentialsCacheTTL ?? 600,
145
+ name: "_c8y_nitro_get_subscribed_tenant_credentials",
146
+ group: "c8y_nitro",
147
+ swr: false
148
+ });
149
+ const useSubscribedTenantCredentials = Object.assign(async () => {
150
+ const credentials = await cachedSubscribedTenantCredentials();
151
+ prevCredentials = credentials;
152
+ return credentials;
153
+ }, {
154
+ invalidate: cachedSubscribedTenantCredentials.invalidate,
155
+ refresh: async () => {
156
+ await cachedSubscribedTenantCredentials.invalidate();
157
+ const credentials = await cachedSubscribedTenantCredentials();
158
+ prevCredentials = credentials;
159
+ return credentials;
160
+ }
161
+ });
162
+ /**
163
+ * Fetches credentials for the tenant where this microservice is deployed.\
164
+ * Uses the C8Y_BOOTSTRAP_TENANT environment variable to identify the deployed tenant.\
165
+ * Returns credentials from the subscribed tenant credentials cache (cached based on configured TTL, default: 10 minutes).
166
+ * @returns Credentials for the deployed tenant
167
+ * @throws {EvlogError} If no credentials found for the deployed tenant
168
+ * @example
169
+ * // Get deployed tenant credentials:
170
+ * const creds = await useDeployedTenantCredentials()
171
+ * console.log(creds.tenant, creds.user)
172
+ *
173
+ * // Invalidate cache:
174
+ * await useDeployedTenantCredentials.invalidate()
175
+ *
176
+ * // Force refresh:
177
+ * const freshCreds = await useDeployedTenantCredentials.refresh()
178
+ * @note This function is not cached separately. It uses the cache of `useSubscribedTenantCredentials()`. Invalidating or refreshing one will refresh `useDeployedTenantCredentials()`s cache.
179
+ */
180
+ const useDeployedTenantCredentials = Object.assign(async () => {
181
+ const tenant = process.env.C8Y_BOOTSTRAP_TENANT;
182
+ const allCredsPromise = await useSubscribedTenantCredentials();
183
+ if (!allCredsPromise[tenant]) throw createError({
184
+ message: `No credentials found for tenant deployed tenant '${tenant}'`,
185
+ status: 500
186
+ });
187
+ return allCredsPromise[tenant];
188
+ }, {
189
+ invalidate: useSubscribedTenantCredentials.invalidate,
190
+ refresh: async () => {
191
+ await useSubscribedTenantCredentials.refresh();
192
+ return await useDeployedTenantCredentials();
193
+ }
194
+ });
195
+ //#endregion
196
+ //#region src/utils/client.ts
197
+ /**
198
+ * Creates a Cumulocity client authenticated with the current user's credentials.\
199
+ * Extracts credentials from the Authorization header of the current request.
200
+ * @param requestOrEvent - The H3Event or ServerRequest from the current request
201
+ * @returns A configured Cumulocity Client instance
202
+ * @example
203
+ * // In a request handler:
204
+ * const client = useUserClient(event)
205
+ * const { data } = await client.inventory.list()
206
+ */
207
+ function useUserClient(requestOrEvent) {
208
+ const request = "req" in requestOrEvent ? requestOrEvent.req : requestOrEvent;
209
+ if (request.context?.["c8y_user_client"]) return request.context["c8y_user_client"];
210
+ const client = new Client(new MicroserviceClientRequestAuth(convertRequestHeadersToC8yFormat(request)), process.env.C8Y_BASEURL);
211
+ request.context ??= {};
212
+ request.context["c8y_user_client"] = client;
213
+ return client;
214
+ }
215
+ /**
216
+ * Creates a Cumulocity client for the tenant where this microservice is deployed.\
217
+ * Uses the bootstrap tenant ID from runtime config to identify the deployed tenant.\
218
+ * @returns A configured Cumulocity Client instance for the deployed tenant
219
+ * @example
220
+ * // Get client for the tenant hosting this microservice:
221
+ * const client = await useDeployedTenantClient()
222
+ * const { data } = await client.application.list()
223
+ */
224
+ async function useDeployedTenantClient() {
225
+ const creds = await useSubscribedTenantCredentials();
226
+ const tenant = process.env.C8Y_BOOTSTRAP_TENANT;
227
+ if (!creds[tenant]) throw createError({
228
+ message: `No subscribed tenant credentials found for tenant '${tenant}'`,
229
+ status: 500
230
+ });
231
+ const client = new Client(new BasicAuth(creds[tenant]), process.env.C8Y_BASEURL);
232
+ client.core.tenant = tenant;
233
+ return client;
234
+ }
235
+ //#endregion
236
+ export { getUserTenantDomain as n, useDeployedTenantClient as t };
@@ -1,5 +1,4 @@
1
1
  import { ICredentials } from "@c8y/client";
2
-
3
2
  //#region src/types/credentials.d.ts
4
3
  type TenantCredentials = Record<string, ICredentials>;
5
4
  //#endregion
@@ -1,5 +1,5 @@
1
+ import "./credentials-eKnJ7m9Y.mjs";
1
2
  import { C8YTenantOptionKeysCacheConfig } from "c8y-nitro/types";
2
-
3
3
  //#region src/types/apiClient.d.ts
4
4
  interface C8YAPIClientOptions {
5
5
  /**
@@ -16,7 +16,7 @@ interface C8YAPIClientOptions {
16
16
  }
17
17
  //#endregion
18
18
  //#region src/types/manifest.d.ts
19
- type C8YManifestOptions = Omit<C8YManifest, 'name' | 'version' | 'apiVersion' | 'key' | 'type' | 'provider'>;
19
+ type C8YManifestOptions = Omit<C8YManifest, 'name' | 'version' | 'apiVersion' | 'key' | 'type' | 'provider' | 'openApiSpec'>;
20
20
  interface C8YManifest {
21
21
  /**
22
22
  * API version (e.g., "v2", "2"). Version 2 required for enhanced container security.
@@ -126,6 +126,16 @@ interface C8YManifest {
126
126
  * Platform extensions (e.g., UI plugins).
127
127
  */
128
128
  extensions?: Extension[];
129
+ /**
130
+ * OpenAPI specification path exposed by this microservice. Auto-populated
131
+ * by c8y-nitro when Nitro's `experimental.openAPI` is enabled and
132
+ * `openAPI.production` is configured. Used by clients (e.g., mc8yp) to
133
+ * auto-discover the API.
134
+ *
135
+ * Path is relative to the microservice context path.
136
+ * @example "/_openapi.json"
137
+ */
138
+ openApiSpec?: string;
129
139
  }
130
140
  /**
131
141
  * Company/organization information.
@@ -373,6 +383,30 @@ interface C8yCacheOptions {
373
383
  tenantOptions?: C8YTenantOptionKeysCacheConfig;
374
384
  }
375
385
  //#endregion
386
+ //#region src/types/docker.d.ts
387
+ interface C8yDockerOptions {
388
+ /**
389
+ * Base image for the generated Dockerfile.
390
+ *
391
+ * Use this when the default slim image is missing something the service
392
+ * needs at runtime (e.g. CA certificates, native runtime libraries).
393
+ * @default 'node:24-slim'
394
+ */
395
+ baseImage?: string;
396
+ /**
397
+ * Raw Dockerfile instructions inserted after `WORKDIR` and before the build
398
+ * output is copied, one instruction per entry.
399
+ *
400
+ * Placing them before the `COPY` keeps their layers cached across rebuilds.
401
+ * `ENV`, `EXPOSE 80` and the `CMD` entrypoint stay under module control and
402
+ * cannot be overridden — they are the Cumulocity microservice contract.
403
+ *
404
+ * @example ['RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*']
405
+ * @default []
406
+ */
407
+ extraInstructions?: string[];
408
+ }
409
+ //#endregion
376
410
  //#region src/types/roles.d.ts
377
411
  interface C8YRoles$1 {}
378
412
  //#endregion
@@ -383,10 +417,16 @@ interface C8YRoles$1 {}
383
417
  */
384
418
  type C8YTenantOptionKeysCacheConfig$1 = Partial<Record<C8YTenantOptionKey$1, number>>;
385
419
  /**
386
- * Type for tenant option keys.
420
+ * Union of tenant option keys declared in `manifest.settings`.
387
421
  * Overwritten by generated types from manifest settings (manifest.settings[].key).
388
422
  */
389
423
  type C8YTenantOptionKey$1 = string;
424
+ /**
425
+ * The microservice's own tenant option settings category, resolved from
426
+ * `manifest.settingsCategory` (falling back to `contextPath`, then `name`).
427
+ * Overwritten by generated types from the manifest.
428
+ */
429
+ type C8YSettingsCategory$1 = string;
390
430
  //#endregion
391
431
  //#region src/types/index.d.ts
392
432
  interface C8yDevOptions {
@@ -399,12 +439,38 @@ interface C8yDevOptions {
399
439
  */
400
440
  injectUser?: boolean;
401
441
  }
442
+ interface C8yOpenAPIOptions {
443
+ /**
444
+ * Strip internal routes from the served OpenAPI document.
445
+ * Removes every route starting with `/_` (generated probes, `/_c8y_nitro/**`
446
+ * and the OpenAPI/Scalar/Swagger routes themselves) as well as user-defined
447
+ * probe `httpGet` paths.
448
+ * @default true
449
+ */
450
+ excludeInternalRoutes?: boolean;
451
+ /**
452
+ * Additional route prefixes to strip from the served OpenAPI document.
453
+ * Entries are matched as path prefixes (e.g. `/internal` also removes `/internal/status`).
454
+ * @default []
455
+ */
456
+ excludeRoutes?: string[];
457
+ }
402
458
  interface C8yNitroModuleOptions {
403
459
  dev?: C8yDevOptions;
404
460
  manifest?: C8YManifestOptions;
405
461
  apiClient?: C8YAPIClientOptions;
406
462
  zip?: C8YZipOptions;
463
+ /**
464
+ * Controls the Dockerfile generated for the microservice image
465
+ * (base image, extra instructions).
466
+ */
467
+ docker?: C8yDockerOptions;
407
468
  cache?: C8yCacheOptions;
469
+ /**
470
+ * Controls how the OpenAPI document served by Nitro (`experimental.openAPI`)
471
+ * is transformed for Cumulocity microservices.
472
+ */
473
+ openapi?: C8yOpenAPIOptions;
408
474
  /**
409
475
  * Adds a debug route for invalidating already-created tenant option caches.
410
476
  * Exposes `GET /_c8y_nitro/invalidate-tenant-options`.
@@ -428,4 +494,4 @@ interface C8yNitroModuleOptions {
428
494
  skipBootstrap?: boolean;
429
495
  }
430
496
  //#endregion
431
- export { C8YRoles$1 as a, C8YManifest as c, C8YTenantOptionKeysCacheConfig$1 as i, C8YManifestOptions as l, C8yNitroModuleOptions as n, C8yCacheOptions as o, C8YTenantOptionKey$1 as r, C8YZipOptions as s, C8yDevOptions as t, C8YAPIClientOptions as u };
497
+ export { C8YTenantOptionKey$1 as a, C8yDockerOptions as c, C8YManifest as d, C8YManifestOptions as f, C8YSettingsCategory$1 as i, C8yCacheOptions as l, C8yNitroModuleOptions as n, C8YTenantOptionKeysCacheConfig$1 as o, C8YAPIClientOptions as p, C8yOpenAPIOptions as r, C8YRoles$1 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-uOwpI6rD.mjs";
2
- import { t as TenantCredentials } from "./credentials-9FO7rTIR.mjs";
1
+ import { n as C8yNitroModuleOptions } from "./index-vu4xaFkz.mjs";
2
+ import { t as TenantCredentials } from "./credentials-eKnJ7m9Y.mjs";
3
3
  import { NitroModule } from "nitro/types";
4
-
5
4
  //#region src/index.d.ts
6
5
  declare module 'nitro/types' {
7
6
  interface NitroOptions {