c8y-nitro 0.3.0 → 0.4.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.
Files changed (51) hide show
  1. package/dist/{cli/commands/bootstrap.mjs → bootstrap-BqWPkH8q.mjs} +5 -8
  2. package/dist/{cli/utils/c8y-api.mjs → c8y-api-BBSKRwKs.mjs} +73 -3
  3. package/dist/cli/index.mjs +5 -7
  4. package/dist/{cli/utils/config.mjs → config-Dqi-ttQi.mjs} +1 -3
  5. package/dist/{cli/utils/env-file.mjs → env-file-B0BK-uZW.mjs} +1 -3
  6. package/dist/{types/manifest.d.mts → index-CzUqbp5C.d.mts} +94 -1
  7. package/dist/index.d.mts +1 -1
  8. package/dist/index.mjs +549 -14
  9. package/dist/{cli/commands/options.mjs → options-BDDJWdph.mjs} +3 -6
  10. package/dist/{package.mjs → package-CobwNpP9.mjs} +2 -3
  11. package/dist/{cli/commands/roles.mjs → roles-DJxp2d8p.mjs} +3 -6
  12. package/dist/runtime/handlers/liveness-readiness.mjs +7 -0
  13. package/dist/runtime/middlewares/dev-user.mjs +23 -0
  14. package/dist/runtime/plugins/c8y-variables.mjs +17 -0
  15. package/dist/runtime/plugins/enrich-logs.mjs +15 -0
  16. package/dist/types.d.mts +2 -25
  17. package/dist/types.mjs +1 -1
  18. package/dist/utils.d.mts +292 -6
  19. package/dist/utils.mjs +444 -8
  20. package/package.json +12 -11
  21. package/dist/module/apiClient.mjs +0 -207
  22. package/dist/module/autoBootstrap.mjs +0 -54
  23. package/dist/module/c8yzip.mjs +0 -66
  24. package/dist/module/constants.mjs +0 -6
  25. package/dist/module/docker.mjs +0 -101
  26. package/dist/module/manifest.mjs +0 -72
  27. package/dist/module/probeCheck.mjs +0 -30
  28. package/dist/module/register.mjs +0 -58
  29. package/dist/module/runtime/handlers/liveness-readiness.ts +0 -7
  30. package/dist/module/runtime/middlewares/dev-user.ts +0 -25
  31. package/dist/module/runtime/plugins/c8y-variables.ts +0 -24
  32. package/dist/module/runtime.mjs +0 -38
  33. package/dist/module/runtimeConfig.mjs +0 -20
  34. package/dist/types/apiClient.d.mts +0 -16
  35. package/dist/types/cache.d.mts +0 -28
  36. package/dist/types/roles.d.mts +0 -4
  37. package/dist/types/tenantOptions.d.mts +0 -13
  38. package/dist/types/zip.d.mts +0 -22
  39. package/dist/utils/client.d.mts +0 -52
  40. package/dist/utils/client.mjs +0 -90
  41. package/dist/utils/credentials.d.mts +0 -71
  42. package/dist/utils/credentials.mjs +0 -120
  43. package/dist/utils/internal/common.mjs +0 -26
  44. package/dist/utils/logging.d.mts +0 -3
  45. package/dist/utils/logging.mjs +0 -4
  46. package/dist/utils/middleware.d.mts +0 -89
  47. package/dist/utils/middleware.mjs +0 -62
  48. package/dist/utils/resources.d.mts +0 -30
  49. package/dist/utils/resources.mjs +0 -49
  50. package/dist/utils/tenantOptions.d.mts +0 -65
  51. package/dist/utils/tenantOptions.mjs +0 -127
package/dist/index.mjs CHANGED
@@ -1,14 +1,548 @@
1
- import { createC8yManifestFromNitro } from "./module/manifest.mjs";
2
- import { writeAPIClient } from "./module/apiClient.mjs";
3
- import { createC8yZip } from "./module/c8yzip.mjs";
4
- import { setupRuntime } from "./module/runtime.mjs";
5
- import { setupRuntimeConfig } from "./module/runtimeConfig.mjs";
6
- import { registerRuntime } from "./module/register.mjs";
7
- import { checkProbes } from "./module/probeCheck.mjs";
8
- import { autoBootstrap } from "./module/autoBootstrap.mjs";
9
- import { name } from "./package.mjs";
1
+ import { a as findMicroserviceByName, g as GENERATED_READINESS_ROUTE, h as GENERATED_LIVENESS_ROUTE, l as subscribeToApplication, m as createC8yManifestFromNitro, n as createBasicAuthHeader, o as getBootstrapCredentials, p as createC8yManifest, r as createMicroservice } from "./c8y-api-BBSKRwKs.mjs";
2
+ import { t as writeBootstrapCredentials } from "./env-file-B0BK-uZW.mjs";
3
+ import { n as name } from "./package-CobwNpP9.mjs";
4
+ import { basename, dirname, join, relative } from "node:path";
5
+ import { mkdir, writeFile } from "node:fs/promises";
6
+ import { x } from "tinyexec";
7
+ import { join as join$1 } from "pathe";
8
+ import { mkdir as mkdir$1, readFile as readFile$1, stat, writeFile as writeFile$1 } from "fs/promises";
9
+ import JSZip from "jszip";
10
+ import Spinnies from "spinnies";
11
+ import { colors } from "consola/utils";
12
+ import { mkdirSync, writeFileSync } from "fs";
13
+ import { fileURLToPath } from "node:url";
14
+ import process from "node:process";
10
15
  import evlog from "evlog/nitro/v3";
16
+ //#region src/module/apiClient.ts
17
+ /**
18
+ * Converts route path to PascalCase function name.
19
+ * Examples:
20
+ * - /health + get -> GetHealth
21
+ * - /health + post -> PostHealth
22
+ * - /someRoute -> SomeRoute (default)
23
+ * - /[id] or /:id + get -> GetById
24
+ * - /api/[multiple]/[params] -> GetApiByMultipleByParams
25
+ * @param path - The route path (e.g., "/api/[id]" or "/api/:id")
26
+ * @param method - HTTP method (get, post, put, delete, etc.)
27
+ */
28
+ function generateFunctionName(path, method) {
29
+ return `${method}${path.replace(/^\//, "").replaceAll(".", "_").replaceAll("*", "").split("/").map((seg) => {
30
+ if (seg.startsWith("[") && seg.endsWith("]")) return `By${capitalize(seg.slice(1, -1))}`;
31
+ if (seg.startsWith(":")) return `By${capitalize(seg.slice(1))}`;
32
+ return capitalize(seg);
33
+ }).join("") || "Index"}`;
34
+ }
35
+ /**
36
+ * Extracts route parameters from path.
37
+ * Example: "/api/[id]/items/[itemId]" or "/api/:id/items/:itemId" -> [{name: "id", type: "string"}, {name: "itemId", type: "string"}]
38
+ * @param path - The route path to extract parameters from
39
+ */
40
+ function extractParams(path) {
41
+ const params = [];
42
+ const segments = path.split("/");
43
+ for (const seg of segments) if (seg.startsWith("[") && seg.endsWith("]")) {
44
+ const name = seg.slice(1, -1);
45
+ params.push(name);
46
+ } else if (seg.startsWith(":")) {
47
+ const name = seg.slice(1);
48
+ params.push(name);
49
+ }
50
+ return params;
51
+ }
52
+ /**
53
+ * Capitalizes first letter of a string.
54
+ * @param str - The string to capitalize
55
+ */
56
+ function capitalize(str) {
57
+ return str.charAt(0).toUpperCase() + str.slice(1);
58
+ }
59
+ /**
60
+ * Converts a file name to a valid JavaScript class name.
61
+ * Only allows letters, numbers (not at start), $ and _.
62
+ * Removes or replaces invalid characters and ensures PascalCase.
63
+ * Always prefixed with "Generated" for clarity.
64
+ * @param fileName - The file name to convert
65
+ * @example "my-service-api" -> "GeneratedMyServiceApi"
66
+ * @example "playgroundAPIClient" -> "GeneratedPlaygroundAPIClient"
67
+ * @example "special@chars!" -> "GeneratedSpecialchars"
68
+ */
69
+ function toValidClassName(fileName) {
70
+ let cleaned = fileName.replace(/\.(ts|js|mjs|cjs|tsx|jsx)$/, "").replace(/[-_.]/g, " ");
71
+ cleaned = cleaned.replace(/[^a-zA-Z0-9\s$]/g, "");
72
+ return `Generated${cleaned.split(/\s+/).filter((word) => word.length > 0).map((word) => capitalize(word)).join("")}`;
73
+ }
74
+ /**
75
+ * Generates TypeScript method code for a route in an Angular service.
76
+ * @param route - Parsed route information including path, method, params, and return type
77
+ */
78
+ function generateMethod(route) {
79
+ const hasParams = route.params.length > 0;
80
+ let inlineParamsType = "";
81
+ if (hasParams) inlineParamsType = `{ ${route.params.map((name) => `${name}: string | number`).join("; ")} }`;
82
+ const methodParam = hasParams ? `params: ${inlineParamsType}` : "";
83
+ const returnTypeAnnotation = `Promise<${route.returnType}>`;
84
+ let pathExpression = `\`\${this.BASE_PATH}${route.path}\``;
85
+ if (hasParams) pathExpression = `\`\${this.BASE_PATH}${route.path.replace(/\[([^\]]+)\]/g, (_, paramName) => `\${params.${paramName}}`)}\``;
86
+ const fetchOptions = `{ method: '${route.method}', headers: { 'Content-Type': 'application/json' } }`;
87
+ return ` async ${route.functionName}(${methodParam}): ${returnTypeAnnotation} {
88
+ const response = await this.fetchClient.fetch(${pathExpression}, ${fetchOptions});
89
+ return this.serialize(response);
90
+ }`;
91
+ }
92
+ /**
93
+ * Helper types for proper type serialization from Nitro.
94
+ * https://github.com/nitrojs/nitro/blob/67b43f2692a41728a2759462b6982c6872ed3a81/src/types/fetch/_serialize.ts
95
+ */
96
+ const serializationTypes = `type JsonPrimitive = string | number | boolean | string | number | boolean | null;
97
+ type NonJsonPrimitive = undefined | Function | symbol;
98
+ type IsAny<T> = 0 extends 1 & T ? true : false;
99
+ type FilterKeys<TObj extends object, TFilter> = { [TKey in keyof TObj]: TObj[TKey] extends TFilter ? TKey : never }[keyof TObj];
100
+ type Serialize<T> = IsAny<T> extends true ? any : T extends JsonPrimitive | undefined ? T : T extends Map<any, any> | Set<any> ? Record<string, never> : T extends NonJsonPrimitive ? never : T extends {
101
+ toJSON: () => infer U;
102
+ } ? U : T extends [] ? [] : T extends [unknown, ...unknown[]] ? SerializeTuple<T> : T extends ReadonlyArray<infer U> ? (U extends NonJsonPrimitive ? null : Serialize<U>)[] : T extends object ? SerializeObject<T> : never;
103
+ type SerializeTuple<T extends [unknown, ...unknown[]]> = { [k in keyof T]: T[k] extends NonJsonPrimitive ? null : Serialize<T[k]> };
104
+ type SerializeObject<T extends object> = { [k in keyof Omit<T, FilterKeys<T, NonJsonPrimitive>>]: Serialize<T[k]> };
105
+ type Simplify<TType> = TType extends any[] | Date ? TType : { [K in keyof TType]: Simplify<TType[K]> };
106
+ type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;
107
+ type C8ySerialize<T> = T extends void | undefined | null ? undefined : T extends string ? string : Simplify<Serialize<T>>;
108
+ `;
109
+ /**
110
+ * Generates complete Angular API client service.
111
+ * @param routes - Array of parsed routes to generate methods for
112
+ * @param contextPath - The microservice context path (e.g., "my-service")
113
+ * @param className - The class name for the generated service (e.g., "PlaygroundAPIClient")
114
+ */
115
+ function generateAPIClient(routes, contextPath, className) {
116
+ return `/* eslint-disable eslint-comments/no-unlimited-disable */
117
+ /* eslint-disable */
118
+ /**
119
+ * Auto-generated Cumulocity API Client
120
+ * Generated by c8y-nitro
121
+ *
122
+ * This Angular service provides typed methods for all Nitro routes.
123
+ * Each method corresponds to a route handler and returns properly typed responses.
124
+ */
125
+ import { Injectable, inject } from '@angular/core'
126
+ import { FetchClient } from '@c8y/client'
127
+
128
+ // Type helpers for proper serialization
129
+ ${serializationTypes}
130
+
131
+ @Injectable({ providedIn: 'root' })
132
+ export class ${className} {
133
+ private readonly BASE_PATH = '/service/${contextPath}';
134
+ private readonly fetchClient: FetchClient = inject(FetchClient)
135
+
136
+ /**
137
+ * Serializes the response based on content type and body.
138
+ * - Empty body (null, undefined, void) -> undefined
139
+ * - text/plain (string) -> raw text
140
+ * - application/json (number, boolean, object, array) -> JSON parsed
141
+ */
142
+ private async serialize(response: Response): Promise<any> {
143
+ const contentType = response.headers.get('content-type');
144
+ const text = await response.text();
145
+
146
+ // Handle empty responses (null, undefined, empty return)
147
+ if (!text || text.length === 0) {
148
+ return undefined;
149
+ }
150
+
151
+ // Handle plain text responses (strings)
152
+ if (contentType?.includes('text/plain')) {
153
+ return text;
154
+ }
155
+
156
+ // Handle JSON responses (numbers, booleans, objects, arrays)
157
+ return JSON.parse(text);
158
+ }
159
+
160
+ ${routes.map((route) => generateMethod(route)).join("\n\n")}
161
+ }`;
162
+ }
163
+ /**
164
+ * Generates the TypeScript return type for a route handler.
165
+ * @param handlerPath - Absolute path to the route handler file
166
+ * @param outputFile - Absolute path to the output API client file
167
+ * @returns TypeScript return type string with proper serialization wrappers
168
+ */
169
+ function getReturnType(handlerPath, outputFile) {
170
+ const relativeHandlerPath = relative(dirname(outputFile), handlerPath);
171
+ return `C8ySerialize<Awaited<ReturnType<typeof import('${(relativeHandlerPath.startsWith(".") ? relativeHandlerPath : `./${relativeHandlerPath}`).replace(/\.ts$/, "")}').default>>>`;
172
+ }
173
+ /**
174
+ * Writes the generated API client to disk.
175
+ * @param nitro - Nitro instance
176
+ * @param options - Complete module options including apiClient and manifest
177
+ */
178
+ async function writeAPIClient(nitro, options) {
179
+ const { apiClient: apiClientOptions } = options;
180
+ if (!apiClientOptions) {
181
+ nitro.logger.debug("API client generation skipped: no apiClient options provided");
182
+ return;
183
+ }
184
+ const manifest = await createC8yManifestFromNitro(nitro);
185
+ const serviceName = manifest.name;
186
+ const serviceContextPath = apiClientOptions.contextPath ?? manifest.contextPath ?? serviceName;
187
+ const name = `${serviceName}APIClient`;
188
+ const rootDir = nitro.options.rootDir;
189
+ const outputDir = join(rootDir, apiClientOptions.dir);
190
+ const outputFile = join(outputDir, `${name}.ts`);
191
+ const routes = (nitro.routing.routes._routes ?? []).filter((route) => {
192
+ return !route.handler.includes("nitro/dist/runtime/internal");
193
+ }).map((route) => {
194
+ const path = route.route;
195
+ const method = (!route.method ? "GET" : route.method).toUpperCase();
196
+ const params = extractParams(path);
197
+ const functionName = generateFunctionName(path, method);
198
+ const returnType = getReturnType(route.handler, outputFile);
199
+ return {
200
+ path: path.replace(/:([^/]+)/g, "[$1]"),
201
+ method,
202
+ functionName,
203
+ params,
204
+ returnType
205
+ };
206
+ });
207
+ if (routes.length === 0) {
208
+ nitro.logger.warn("No routes found to generate API client");
209
+ return;
210
+ }
211
+ const code = generateAPIClient(routes, serviceContextPath, toValidClassName(name));
212
+ await mkdir(outputDir, { recursive: true });
213
+ await writeFile(outputFile, code, "utf-8");
214
+ nitro.logger.success(`Generated API client with ${routes.length} routes at: ${relative(rootDir, outputFile)}`);
215
+ }
216
+ //#endregion
217
+ //#region src/module/docker.ts
218
+ /**
219
+ * Generate the Dockerfile content for a Nitro build
220
+ * @param outputDirName - Name of the output directory (e.g., '.output')
221
+ * @returns Dockerfile content as a string
222
+ */
223
+ function getDockerfileContent(outputDirName) {
224
+ return `FROM node:24-slim AS runtime
225
+
226
+ WORKDIR /app
227
+
228
+ # Copy the Nitro build output
229
+ COPY ${outputDirName}/ ${outputDirName}/
230
+
231
+ ENV NODE_ENV=production
232
+ ENV PORT=80
233
+
234
+ EXPOSE 80
235
+
236
+ # Run the Nitro server entrypoint. Use source maps to aid debugging if present.
237
+ CMD ["node", "--enable-source-maps", "${outputDirName}/server/index.mjs"]`;
238
+ }
239
+ async function checkDockerInstalled() {
240
+ try {
241
+ const result = await x("docker", ["--version"]);
242
+ if (result.exitCode !== 0) return false;
243
+ if (result.stderr) return false;
244
+ return true;
245
+ } catch {
246
+ return false;
247
+ }
248
+ }
249
+ async function writeDockerfile(outputDir) {
250
+ const outputDirName = basename(outputDir);
251
+ const c8yDir = join(outputDir, "../.c8y");
252
+ const dockerfilePath = join(c8yDir, "Dockerfile");
253
+ await mkdir(c8yDir, { recursive: true });
254
+ await writeFile(dockerfilePath, getDockerfileContent(outputDirName), "utf-8");
255
+ return c8yDir;
256
+ }
257
+ async function buildDockerImage(nitro, c8yDir) {
258
+ const imageName = `${nitro.options.rootDir.split("/").pop() || "c8y-app"}:latest`;
259
+ const buildContext = join(c8yDir, "..");
260
+ nitro.logger.debug(`Building Docker image: ${imageName}`);
261
+ nitro.logger.debug(`Build context: ${buildContext}`);
262
+ try {
263
+ const result = await x("docker", [
264
+ "build",
265
+ "-t",
266
+ imageName,
267
+ "-f",
268
+ join(c8yDir, "Dockerfile"),
269
+ buildContext
270
+ ]);
271
+ if (result.stdout) nitro.logger.debug(result.stdout);
272
+ if (result.stderr) nitro.logger.debug(result.stderr);
273
+ if (result.exitCode !== 0) throw new Error(`Docker build failed with exit code ${result.exitCode}`, { cause: result.stderr });
274
+ nitro.logger.debug(`Docker image built successfully: ${imageName}`);
275
+ return imageName;
276
+ } catch (error) {
277
+ throw new Error("Failed to build Docker image", { cause: error });
278
+ }
279
+ }
280
+ async function saveDockerImageToTar(nitro, c8yDir, imageName) {
281
+ const imageTarPath = join(c8yDir, "image.tar");
282
+ nitro.logger.debug(`Saving Docker image to ${imageTarPath}`);
283
+ try {
284
+ const result = await x("docker", [
285
+ "save",
286
+ "-o",
287
+ imageTarPath,
288
+ imageName
289
+ ]);
290
+ if (result.stderr) nitro.logger.debug(result.stderr);
291
+ if (result.exitCode !== 0) throw new Error(`Docker save failed with exit code ${result.exitCode}`, { cause: result.stderr });
292
+ nitro.logger.debug(`Docker image saved to ${imageTarPath}`);
293
+ return imageTarPath;
294
+ } catch (error) {
295
+ throw new Error("Failed to save Docker image to tar file", { cause: error });
296
+ }
297
+ }
298
+ /**
299
+ * Create a Docker image from the Nitro build output
300
+ * @param nitro Nitro instance
301
+ * @returns Path to the saved Docker image tar file
302
+ */
303
+ async function createDockerImage(nitro) {
304
+ if (!await checkDockerInstalled()) throw new Error("Docker is not installed or not available in PATH. Please install Docker to build images.");
305
+ nitro.logger.debug("Creating Docker image...");
306
+ const c8yDir = await writeDockerfile(nitro.options.output.dir);
307
+ const imageTarPath = await saveDockerImageToTar(nitro, c8yDir, await buildDockerImage(nitro, c8yDir));
308
+ nitro.logger.debug("Docker image creation complete.");
309
+ return imageTarPath;
310
+ }
311
+ //#endregion
312
+ //#region src/module/c8yzip.ts
313
+ const spinnies = new Spinnies();
314
+ function formatBytes(bytes) {
315
+ if (bytes === 0) return "0 B";
316
+ const k = 1024;
317
+ const sizes = [
318
+ "B",
319
+ "kB",
320
+ "MB",
321
+ "GB"
322
+ ];
323
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
324
+ return `${(bytes / k ** i).toFixed(1)} ${sizes[i]}`;
325
+ }
326
+ /**
327
+ * Resolve the output path for the zip file based on options and manifest
328
+ * @param rootDir - Root directory of the project
329
+ * @param options - Zip options with optional name and outputDir
330
+ * @param manifest - Cumulocity manifest containing name and version
331
+ * @returns Absolute path to the output zip file
332
+ */
333
+ function resolveZipOutputPath(rootDir, options, manifest) {
334
+ 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
+ }
336
+ async function createC8yZip(nitro, options = {}) {
337
+ const startTime = Date.now();
338
+ const spinnerName = "c8y-zip";
339
+ spinnies.add(spinnerName, { text: "Creating Dockerfile..." });
340
+ spinnies.update(spinnerName, { text: "Building Docker image..." });
341
+ const imageTarPath = await createDockerImage(nitro);
342
+ spinnies.update(spinnerName, { text: "Creating manifest..." });
343
+ const manifest = await createC8yManifestFromNitro(nitro);
344
+ spinnies.update(spinnerName, { text: "Reading image.tar..." });
345
+ const imageTarBuffer = await readFile$1(imageTarPath);
346
+ spinnies.update(spinnerName, { text: "Building zip file..." });
347
+ const zip = new JSZip();
348
+ zip.file("image.tar", imageTarBuffer);
349
+ zip.file("cumulocity.json", JSON.stringify(manifest, null, 2));
350
+ const zipBuffer = await zip.generateAsync({
351
+ type: "nodebuffer",
352
+ compression: "STORE"
353
+ });
354
+ const outputFile = resolveZipOutputPath(nitro.options.rootDir, options, manifest);
355
+ const outputDir = join$1(outputFile, "..");
356
+ spinnies.update(spinnerName, { text: "Writing zip file..." });
357
+ await mkdir$1(outputDir, { recursive: true });
358
+ await writeFile$1(outputFile, zipBuffer);
359
+ const zipSize = formatBytes((await stat(outputFile)).size);
360
+ const duration = Date.now() - startTime;
361
+ spinnies.remove(spinnerName);
362
+ spinnies.stopAll();
363
+ nitro.logger.success(`Cumulocity zip built in ${duration}ms`);
364
+ nitro.logger.log(colors.gray(` └─ ${outputFile} (${zipSize})`));
365
+ nitro.logger.info("Zip file can be uploaded to Cumulocity IoT platform");
366
+ }
367
+ //#endregion
368
+ //#region src/module/runtime.ts
369
+ function setupRuntime(nitro, manifest) {
370
+ nitro.logger.debug("Setting up C8Y nitro runtime");
371
+ const roles = manifest.roles ?? [];
372
+ const settingKeys = (manifest.settings ?? []).map((s) => s.key);
373
+ const completeTypesDir = join$1(nitro.options.rootDir, nitro.options.typescript.generatedTypesDir ?? "node_modules/.nitro/types");
374
+ const typesFile = join$1(completeTypesDir, "c8y-nitro.d.ts");
375
+ const typesContent = `// generated by c8y-nitro
376
+ declare module 'c8y-nitro/types' {
377
+ interface C8YRoles {
378
+ ${roles.map((role) => ` '${role}': '${role}';`).join("\n")}
379
+ }
380
+ type C8YTenantOptionKey = ${settingKeys.length > 0 ? `${settingKeys.map((k) => `'${k}'`).join(" | ")}` : "never"}
381
+ type C8YTenantOptionKeysCacheConfig = Partial<Record<C8YTenantOptionKey, number>>;
382
+ }
383
+
384
+ declare module 'c8y-nitro/runtime' {
385
+ import type { C8YRoles, C8yManifest } from 'c8y-nitro/types';
386
+ export const c8yRoles: C8YRoles;
387
+ export const c8yTenantOptionKeys: readonly [${settingKeys.map((key) => `'${key}'`).join(", ")}];
388
+ export const c8yManifest: C8yManifest;
389
+ }`;
390
+ nitro.options.virtual["c8y-nitro/runtime"] = `
391
+ export const c8yRoles = {
392
+ ${roles.map((role) => ` '${role}': '${role}',`).join("\n")}
393
+ }
11
394
 
395
+ export const c8yManifest = ${JSON.stringify(manifest, null, 2)}
396
+
397
+ export const c8yTenantOptionKeys = [${settingKeys.map((key) => `'${key}'`).join(", ")}]
398
+ `;
399
+ mkdirSync(completeTypesDir, { recursive: true });
400
+ writeFileSync(typesFile, typesContent, { encoding: "utf-8" });
401
+ nitro.logger.debug(`Written C8Y types to ${typesFile}`);
402
+ }
403
+ //#endregion
404
+ //#region src/module/runtimeConfig.ts
405
+ /**
406
+ * Sets up runtime configuration values from module options.
407
+ * These can be overridden by environment variables.
408
+ * @param nitro - The Nitro instance
409
+ * @param options - The c8y-nitro module options
410
+ */
411
+ async function setupRuntimeConfig(nitro, options) {
412
+ nitro.logger.debug("Setting up C8Y runtime config");
413
+ nitro.options.runtimeConfig.c8yCredentialsCacheTTL = options.cache?.credentialsTTL ?? 600;
414
+ nitro.options.runtimeConfig.c8yDefaultTenantOptionsTTL = options.cache?.defaultTenantOptionsTTL ?? 600;
415
+ nitro.options.runtimeConfig.c8yTenantOptionsPerKeyTTL = options.cache?.tenantOptions ?? {};
416
+ const manifest = await createC8yManifest(nitro.options.rootDir, options.manifest, nitro.logger);
417
+ nitro.options.runtimeConfig.c8ySettingsCategory = options.manifest?.settingsCategory ?? manifest.contextPath ?? manifest.name;
418
+ }
419
+ //#endregion
420
+ //#region src/module/registerRuntime.ts
421
+ /**
422
+ * Links runtime middleware, handlers, and plugins to the nitro instance.
423
+ * Works by having the handlers in a relative path to this file.
424
+ * Needs to be the same when built.
425
+ * @param nitro - Nitro instance
426
+ * @param options - C8yNitroModuleOptions
427
+ */
428
+ function registerRuntime(nitro, options = {}) {
429
+ const thisFilePath = fileURLToPath(new URL(".", import.meta.url));
430
+ const allPlugins = Object.keys({
431
+ "./runtime/plugins/c8y-variables.ts": 0,
432
+ "./runtime/plugins/enrich-logs.ts": 0
433
+ }).map((p) => join$1(thisFilePath, p.replace(".ts", "")));
434
+ const allMiddlewares = Object.keys({ "./runtime/middlewares/dev-user.ts": 0 }).map((p) => join$1(thisFilePath, p.replace(".ts", "")));
435
+ /**
436
+ * Plugins (auto scanned)
437
+ */
438
+ nitro.options.plugins.push(...allPlugins);
439
+ /**
440
+ * Middlewares (auto scanned)
441
+ */
442
+ nitro.options.handlers.push(...allMiddlewares.map((handler) => ({
443
+ route: "/**",
444
+ handler,
445
+ middleware: true
446
+ })));
447
+ /**
448
+ * Handlers (can't be auto scanned as they need methods etc)
449
+ */
450
+ const handlers = [];
451
+ const probeHandlerPath = join$1(thisFilePath, "./runtime/handlers/liveness-readiness");
452
+ if (!options.manifest?.livenessProbe?.httpGet) {
453
+ handlers.push({
454
+ route: GENERATED_LIVENESS_ROUTE,
455
+ handler: probeHandlerPath,
456
+ method: "GET"
457
+ });
458
+ nitro.logger.debug(`Generated liveness probe at ${GENERATED_LIVENESS_ROUTE}`);
459
+ } else nitro.logger.debug("Liveness probe httpGet defined by user; skipping generation");
460
+ if (!options.manifest?.readinessProbe?.httpGet) {
461
+ handlers.push({
462
+ route: GENERATED_READINESS_ROUTE,
463
+ handler: probeHandlerPath,
464
+ method: "GET"
465
+ });
466
+ nitro.logger.debug(`Generated readiness probe at ${GENERATED_READINESS_ROUTE}`);
467
+ } else nitro.logger.debug("Readiness probe httpGet defined by user; skipping generation");
468
+ nitro.options.handlers.push(...handlers);
469
+ }
470
+ //#endregion
471
+ //#region src/module/probeCheck.ts
472
+ function checkProbes(nitro, manifestOptions = {}) {
473
+ if (manifestOptions.livenessProbe?.httpGet) {
474
+ const probe = manifestOptions.livenessProbe.httpGet;
475
+ checkProbeEndpoint(nitro, probe, "livenessProbe");
476
+ }
477
+ if (manifestOptions.readinessProbe?.httpGet) {
478
+ const probe = manifestOptions.readinessProbe.httpGet;
479
+ checkProbeEndpoint(nitro, probe, "readinessProbe");
480
+ }
481
+ }
482
+ function checkProbeEndpoint(nitro, probe, probeType) {
483
+ const path = probe.path;
484
+ const method = "GET";
485
+ const matchingHandlers = nitro.scannedHandlers.filter((h) => h.route === path);
486
+ if (matchingHandlers.length === 0) {
487
+ nitro.logger.warn(`${probeType} route "${path}" not found in scanned handlers. The probe will fail at runtime.`);
488
+ return;
489
+ }
490
+ if (!matchingHandlers.some((h) => {
491
+ if (!h.method) return true;
492
+ return h.method.toUpperCase() === method.toUpperCase();
493
+ })) {
494
+ const availableMethods = matchingHandlers.filter((h) => h.method).map((h) => h.method?.toUpperCase()).join(", ");
495
+ nitro.logger.warn(`${probeType} route "${path}" exists but does not accept ${method} requests. Available methods: ${availableMethods || "none specified"}. The probe will fail at runtime.`);
496
+ }
497
+ }
498
+ //#endregion
499
+ //#region src/module/autoBootstrap.ts
500
+ /**
501
+ * Automatically bootstraps the microservice to the development tenant if needed.
502
+ * Runs silently - only logs if bootstrap was performed or if errors occur.
503
+ * @param nitro - Nitro instance
504
+ */
505
+ async function autoBootstrap(nitro) {
506
+ try {
507
+ if ([
508
+ "C8Y_BOOTSTRAP_TENANT",
509
+ "C8Y_BOOTSTRAP_USER",
510
+ "C8Y_BOOTSTRAP_PASSWORD"
511
+ ].every((v) => process.env[v])) return;
512
+ if ([
513
+ "C8Y_BASEURL",
514
+ "C8Y_DEVELOPMENT_TENANT",
515
+ "C8Y_DEVELOPMENT_USER",
516
+ "C8Y_DEVELOPMENT_PASSWORD"
517
+ ].filter((v) => !process.env[v]).length > 0) return;
518
+ const baseUrl = process.env.C8Y_BASEURL.endsWith("/") ? process.env.C8Y_BASEURL.slice(0, -1) : process.env.C8Y_BASEURL;
519
+ const authHeader = createBasicAuthHeader(process.env.C8Y_DEVELOPMENT_TENANT, process.env.C8Y_DEVELOPMENT_USER, process.env.C8Y_DEVELOPMENT_PASSWORD);
520
+ const manifest = await createC8yManifest(nitro.options.rootDir, nitro.options.c8y?.manifest, nitro.logger);
521
+ const existingApp = await findMicroserviceByName(baseUrl, manifest.name, authHeader);
522
+ let appId;
523
+ if (existingApp) {
524
+ appId = existingApp.id;
525
+ nitro.logger.debug(`Microservice "${manifest.name}" already exists (ID: ${appId}), retrieving bootstrap credentials...`);
526
+ } else {
527
+ appId = (await createMicroservice(baseUrl, manifest, authHeader)).id;
528
+ nitro.logger.debug(`Microservice "${manifest.name}" created (ID: ${appId})`);
529
+ }
530
+ await subscribeToApplication(baseUrl, process.env.C8Y_DEVELOPMENT_TENANT, appId, authHeader);
531
+ const credentials = await getBootstrapCredentials(baseUrl, appId, authHeader);
532
+ const envFileName = await writeBootstrapCredentials(nitro.options.rootDir, {
533
+ C8Y_BOOTSTRAP_TENANT: credentials.tenant,
534
+ C8Y_BOOTSTRAP_USER: credentials.name,
535
+ C8Y_BOOTSTRAP_PASSWORD: credentials.password
536
+ });
537
+ process.env.C8Y_BOOTSTRAP_TENANT = credentials.tenant;
538
+ process.env.C8Y_BOOTSTRAP_USER = credentials.name;
539
+ process.env.C8Y_BOOTSTRAP_PASSWORD = credentials.password;
540
+ nitro.logger.success(`Auto-bootstrap complete! Bootstrap credentials written to ${envFileName}`);
541
+ } catch (error) {
542
+ nitro.logger.warn("Auto-bootstrap failed:", error instanceof Error ? error.message : String(error));
543
+ }
544
+ }
545
+ //#endregion
12
546
  //#region src/index.ts
13
547
  function c8y() {
14
548
  return {
@@ -28,13 +562,15 @@ function c8y() {
28
562
  nitro.logger.error(`Unsupported preset "${nitro.options.preset}" for c8y-nitro module, only node presets are supported.`);
29
563
  throw new Error("Unsupported preset for c8y-nitro module");
30
564
  }
31
- const { setup: setupEvlog } = evlog({ env: { service: (await createC8yManifestFromNitro(nitro)).name } });
565
+ let manifest = await createC8yManifestFromNitro(nitro);
566
+ const { setup: setupEvlog } = evlog({ env: { service: manifest.name } });
32
567
  await setupEvlog(nitro);
33
568
  if (!options.skipBootstrap) await autoBootstrap(nitro);
34
569
  await setupRuntimeConfig(nitro, options);
35
- setupRuntime(nitro, options.manifest);
570
+ setupRuntime(nitro, manifest);
36
571
  nitro.hooks.hook("dev:reload", async () => {
37
- setupRuntime(nitro, options.manifest);
572
+ manifest = await createC8yManifestFromNitro(nitro);
573
+ setupRuntime(nitro, manifest);
38
574
  if (options.apiClient) {
39
575
  nitro.logger.debug("Generating C8Y API client");
40
576
  await writeAPIClient(nitro, options);
@@ -56,6 +592,5 @@ function c8y() {
56
592
  }
57
593
  };
58
594
  }
59
-
60
595
  //#endregion
61
- export { c8y, c8y as default };
596
+ export { c8y, c8y as default };
@@ -1,9 +1,7 @@
1
- import { createC8yManifest } from "../../module/manifest.mjs";
2
- import { createBasicAuthHeader, deleteTenantOption, getTenantOption, getTenantOptionsByCategory, updateTenantOption } from "../utils/c8y-api.mjs";
3
- import { loadC8yConfig, validateBootstrapEnv } from "../utils/config.mjs";
1
+ import { c as getTenantOptionsByCategory, f as updateTenantOption, i as deleteTenantOption, n as createBasicAuthHeader, p as createC8yManifest, s as getTenantOption } from "./c8y-api-BBSKRwKs.mjs";
2
+ import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-Dqi-ttQi.mjs";
4
3
  import { defineCommand } from "citty";
5
4
  import { consola } from "consola";
6
-
7
5
  //#region src/cli/commands/options.ts
8
6
  var options_default = defineCommand({
9
7
  meta: {
@@ -143,6 +141,5 @@ async function handleDelete(baseUrl, category, authHeader, availableKeys, curren
143
141
  }
144
142
  consola.success("Delete operation completed");
145
143
  }
146
-
147
144
  //#endregion
148
- export { options_default as default };
145
+ export { options_default as default };
@@ -1,7 +1,6 @@
1
1
  //#region package.json
2
2
  var name = "c8y-nitro";
3
- var version = "0.3.0";
3
+ var version = "0.4.1";
4
4
  var description = "Lightning fast Cumulocity IoT microservice development powered by Nitro";
5
-
6
5
  //#endregion
7
- export { description, name, version };
6
+ export { name as n, version as r, description as t };
@@ -1,9 +1,7 @@
1
- import { createC8yManifest } from "../../module/manifest.mjs";
2
- import { assignUserRole, createBasicAuthHeader, unassignUserRole } from "../utils/c8y-api.mjs";
3
- import { loadC8yConfig, validateBootstrapEnv } from "../utils/config.mjs";
1
+ import { n as createBasicAuthHeader, p as createC8yManifest, t as assignUserRole, u as unassignUserRole } from "./c8y-api-BBSKRwKs.mjs";
2
+ import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-Dqi-ttQi.mjs";
4
3
  import { defineCommand } from "citty";
5
4
  import { consola } from "consola";
6
-
7
5
  //#region src/cli/commands/roles.ts
8
6
  var roles_default = defineCommand({
9
7
  meta: {
@@ -36,6 +34,5 @@ var roles_default = defineCommand({
36
34
  } else consola.warn("No roles defined in manifest. Nothing to manage.");
37
35
  }
38
36
  });
39
-
40
37
  //#endregion
41
- export { roles_default as default };
38
+ export { roles_default as default };
@@ -0,0 +1,7 @@
1
+ import { defineEventHandler } from "nitro/h3";
2
+ //#region src/module/runtime/handlers/liveness-readiness.ts
3
+ var liveness_readiness_default = defineEventHandler(async () => {
4
+ return { status: "OK" };
5
+ });
6
+ //#endregion
7
+ export { liveness_readiness_default as default };
@@ -0,0 +1,23 @@
1
+ import { defineHandler } from "nitro/h3";
2
+ import process from "node:process";
3
+ import { Buffer } from "node:buffer";
4
+ import consola from "consola";
5
+ //#region src/module/runtime/middlewares/dev-user.ts
6
+ var dev_user_default = defineHandler((event) => {
7
+ if (import.meta.dev) {
8
+ const missingDevVars = [
9
+ "C8Y_DEVELOPMENT_TENANT",
10
+ "C8Y_DEVELOPMENT_USER",
11
+ "C8Y_DEVELOPMENT_PASSWORD"
12
+ ].filter((varName) => !process.env[varName]);
13
+ if (missingDevVars.length > 0) consola.warn(`Missing development environment variables: ${missingDevVars.join(", ")}. Dev user injection will be skipped. Routes requiring authentication or roles will fail.`);
14
+ else {
15
+ const authString = `${process.env.C8Y_DEVELOPMENT_TENANT}/${process.env.C8Y_DEVELOPMENT_USER}:${process.env.C8Y_DEVELOPMENT_PASSWORD}`;
16
+ const authHeader = `Basic ${Buffer.from(authString).toString("base64")}`;
17
+ event.req.headers.set("authorization", authHeader);
18
+ }
19
+ if (event.req.headers.has("cookie")) event.req.headers.delete("cookie");
20
+ }
21
+ });
22
+ //#endregion
23
+ export { dev_user_default as default };