c8y-nitro 0.4.0 → 0.4.2

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.
@@ -2,7 +2,7 @@ import { a as findMicroserviceByName, d as updateMicroservice, l as subscribeToA
2
2
  import { t as writeBootstrapCredentials } from "./env-file-B0BK-uZW.mjs";
3
3
  import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-Dqi-ttQi.mjs";
4
4
  import { defineCommand, runCommand } from "citty";
5
- import { consola as consola$1 } from "consola";
5
+ import { consola } from "consola";
6
6
  //#region src/cli/commands/bootstrap.ts
7
7
  var bootstrap_default = defineCommand({
8
8
  meta: {
@@ -11,50 +11,50 @@ var bootstrap_default = defineCommand({
11
11
  },
12
12
  args: {},
13
13
  async run() {
14
- consola$1.info("Loading configuration...");
14
+ consola.info("Loading configuration...");
15
15
  const { env, c8yOptions, configDir } = await loadC8yConfig();
16
- consola$1.info("Validating environment variables...");
16
+ consola.info("Validating environment variables...");
17
17
  const envVars = validateBootstrapEnv(env);
18
- consola$1.info("Building manifest...");
18
+ consola.info("Building manifest...");
19
19
  const manifest = await createC8yManifest(configDir, c8yOptions?.manifest);
20
- consola$1.success(`Manifest created for: ${manifest.name} v${manifest.version}`);
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
- consola$1.info(`Checking if microservice "${manifest.name}" exists...`);
22
+ consola.info(`Checking if microservice "${manifest.name}" exists...`);
23
23
  const existingApp = await findMicroserviceByName(envVars.C8Y_BASEURL, manifest.name, authHeader);
24
24
  let appId;
25
25
  if (existingApp) {
26
- consola$1.warn(`Microservice "${manifest.name}" already exists on development tenant (ID: ${existingApp.id})`);
27
- if (!await consola$1.prompt("Do you want to update the existing microservice?", {
26
+ consola.warn(`Microservice "${manifest.name}" already exists on development tenant (ID: ${existingApp.id})`);
27
+ if (!await consola.prompt("Do you want to update the existing microservice?", {
28
28
  type: "confirm",
29
29
  cancel: "reject"
30
30
  })) {
31
- consola$1.info("Bootstrap cancelled.");
31
+ consola.info("Bootstrap cancelled.");
32
32
  return;
33
33
  }
34
- consola$1.info("Updating microservice...");
34
+ consola.info("Updating microservice...");
35
35
  appId = (await updateMicroservice(envVars.C8Y_BASEURL, existingApp.id, manifest, authHeader)).id;
36
- consola$1.success(`Microservice updated successfully (ID: ${appId})`);
36
+ consola.success(`Microservice updated successfully (ID: ${appId})`);
37
37
  } else {
38
- consola$1.info("Creating microservice...");
38
+ consola.info("Creating microservice...");
39
39
  appId = (await createMicroservice(envVars.C8Y_BASEURL, manifest, authHeader)).id;
40
- consola$1.success(`Microservice created successfully (ID: ${appId})`);
40
+ consola.success(`Microservice created successfully (ID: ${appId})`);
41
41
  }
42
- consola$1.info("Subscribing tenant to application...");
42
+ consola.info("Subscribing tenant to application...");
43
43
  await subscribeToApplication(envVars.C8Y_BASEURL, envVars.C8Y_DEVELOPMENT_TENANT, appId, authHeader);
44
- consola$1.success("Tenant subscribed to application");
45
- consola$1.info("Fetching bootstrap credentials...");
44
+ consola.success("Tenant subscribed to application");
45
+ consola.info("Fetching bootstrap credentials...");
46
46
  const credentials = await getBootstrapCredentials(envVars.C8Y_BASEURL, appId, authHeader);
47
- consola$1.info("Writing bootstrap credentials...");
47
+ consola.info("Writing bootstrap credentials...");
48
48
  const envFileName = await writeBootstrapCredentials(configDir, {
49
49
  C8Y_BOOTSTRAP_TENANT: credentials.tenant,
50
50
  C8Y_BOOTSTRAP_USER: credentials.name,
51
51
  C8Y_BOOTSTRAP_PASSWORD: credentials.password
52
52
  });
53
- consola$1.success(`Bootstrap credentials written to ${envFileName}`);
53
+ consola.success(`Bootstrap credentials written to ${envFileName}`);
54
54
  if (manifest.roles && manifest.roles.length > 0) {
55
- if (await consola$1.prompt("Do you want to manage microservice roles for your development user?", { type: "confirm" })) await runCommand(await import("./roles-DrJsxUG-.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-DJxp2d8p.mjs").then((r) => r.default), { rawArgs: [] });
56
56
  }
57
- consola$1.success("Bootstrap complete!");
57
+ consola.success("Bootstrap complete!");
58
58
  }
59
59
  });
60
60
  //#endregion
@@ -1,4 +1,4 @@
1
- import { n as name, r as version, t as description } from "../package-BAjMvZYS.mjs";
1
+ import { n as name, r as version, t as description } from "../package-C4HtuVu_.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-CGOe2HxK.mjs").then((r) => r.default),
12
- roles: () => import("../roles-DrJsxUG-.mjs").then((r) => r.default),
13
- options: () => import("../options-CuGdGP4l.mjs").then((r) => r.default)
11
+ bootstrap: () => import("../bootstrap-BqWPkH8q.mjs").then((r) => r.default),
12
+ roles: () => import("../roles-DJxp2d8p.mjs").then((r) => r.default),
13
+ options: () => import("../options-BDDJWdph.mjs").then((r) => r.default)
14
14
  }
15
15
  }));
16
16
  //#endregion
@@ -405,4 +405,4 @@ interface C8yNitroModuleOptions {
405
405
  skipBootstrap?: boolean;
406
406
  }
407
407
  //#endregion
408
- export { C8yCacheOptions as a, C8YAPIClientOptions as c, C8YRoles$1 as i, C8YTenantOptionKey$1 as n, C8YZipOptions as o, C8YTenantOptionKeysCacheConfig$1 as r, C8YManifestOptions as s, C8yNitroModuleOptions as t };
408
+ export { C8yCacheOptions as a, C8YManifestOptions as c, C8YRoles$1 as i, C8YAPIClientOptions as l, C8YTenantOptionKey$1 as n, C8YZipOptions as o, C8YTenantOptionKeysCacheConfig$1 as r, C8YManifest as s, C8yNitroModuleOptions as t };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as C8yNitroModuleOptions } from "./index-B6HtYHU0.mjs";
1
+ import { t as C8yNitroModuleOptions } from "./index-CzUqbp5C.mjs";
2
2
  import { NitroModule } from "nitro/types";
3
3
 
4
4
  //#region src/index.d.ts
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
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
2
  import { t as writeBootstrapCredentials } from "./env-file-B0BK-uZW.mjs";
3
- import { n as name } from "./package-BAjMvZYS.mjs";
3
+ import { n as name } from "./package-C4HtuVu_.mjs";
4
4
  import { basename, dirname, join, relative } from "node:path";
5
5
  import { mkdir, writeFile } from "node:fs/promises";
6
6
  import { x } from "tinyexec";
@@ -366,10 +366,10 @@ async function createC8yZip(nitro, options = {}) {
366
366
  }
367
367
  //#endregion
368
368
  //#region src/module/runtime.ts
369
- function setupRuntime(nitro, manifestOptions = {}) {
369
+ function setupRuntime(nitro, manifest) {
370
370
  nitro.logger.debug("Setting up C8Y nitro runtime");
371
- const roles = manifestOptions.roles ?? [];
372
- const settingKeys = (manifestOptions.settings ?? []).map((s) => s.key);
371
+ const roles = manifest.roles ?? [];
372
+ const settingKeys = (manifest.settings ?? []).map((s) => s.key);
373
373
  const completeTypesDir = join$1(nitro.options.rootDir, nitro.options.typescript.generatedTypesDir ?? "node_modules/.nitro/types");
374
374
  const typesFile = join$1(completeTypesDir, "c8y-nitro.d.ts");
375
375
  const typesContent = `// generated by c8y-nitro
@@ -382,15 +382,18 @@ ${roles.map((role) => ` '${role}': '${role}';`).join("\n")}
382
382
  }
383
383
 
384
384
  declare module 'c8y-nitro/runtime' {
385
- import type { C8YRoles } from 'c8y-nitro/types';
385
+ import type { C8YRoles, C8yManifest } from 'c8y-nitro/types';
386
386
  export const c8yRoles: C8YRoles;
387
387
  export const c8yTenantOptionKeys: readonly [${settingKeys.map((key) => `'${key}'`).join(", ")}];
388
+ export const c8yManifest: C8yManifest;
388
389
  }`;
389
390
  nitro.options.virtual["c8y-nitro/runtime"] = `
390
391
  export const c8yRoles = {
391
392
  ${roles.map((role) => ` '${role}': '${role}',`).join("\n")}
392
393
  }
393
394
 
395
+ export const c8yManifest = ${JSON.stringify(manifest, null, 2)}
396
+
394
397
  export const c8yTenantOptionKeys = [${settingKeys.map((key) => `'${key}'`).join(", ")}]
395
398
  `;
396
399
  mkdirSync(completeTypesDir, { recursive: true });
@@ -414,7 +417,7 @@ async function setupRuntimeConfig(nitro, options) {
414
417
  nitro.options.runtimeConfig.c8ySettingsCategory = options.manifest?.settingsCategory ?? manifest.contextPath ?? manifest.name;
415
418
  }
416
419
  //#endregion
417
- //#region src/module/register.ts
420
+ //#region src/module/registerRuntime.ts
418
421
  /**
419
422
  * Links runtime middleware, handlers, and plugins to the nitro instance.
420
423
  * Works by having the handlers in a relative path to this file.
@@ -424,26 +427,25 @@ async function setupRuntimeConfig(nitro, options) {
424
427
  */
425
428
  function registerRuntime(nitro, options = {}) {
426
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", "")));
427
435
  /**
428
- * Plugins
436
+ * Plugins (auto scanned)
429
437
  */
430
- const plugins = [];
431
- const c8yVariablesPluginPath = join$1(thisFilePath, "./runtime/plugins/c8y-variables");
432
- plugins.push(c8yVariablesPluginPath);
433
- nitro.options.plugins.push(...plugins);
438
+ nitro.options.plugins.push(...allPlugins);
434
439
  /**
435
- * Middlewares (global)
440
+ * Middlewares (auto scanned)
436
441
  */
437
- const middlewares = [];
438
- const devUserMiddlewarePath = join$1(thisFilePath, "./runtime/middlewares/dev-user");
439
- middlewares.push(devUserMiddlewarePath);
440
- nitro.options.handlers.push(...middlewares.map((handler) => ({
442
+ nitro.options.handlers.push(...allMiddlewares.map((handler) => ({
441
443
  route: "/**",
442
444
  handler,
443
445
  middleware: true
444
446
  })));
445
447
  /**
446
- * Handlers
448
+ * Handlers (can't be auto scanned as they need methods etc)
447
449
  */
448
450
  const handlers = [];
449
451
  const probeHandlerPath = join$1(thisFilePath, "./runtime/handlers/liveness-readiness");
@@ -560,13 +562,15 @@ function c8y() {
560
562
  nitro.logger.error(`Unsupported preset "${nitro.options.preset}" for c8y-nitro module, only node presets are supported.`);
561
563
  throw new Error("Unsupported preset for c8y-nitro module");
562
564
  }
563
- 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 } });
564
567
  await setupEvlog(nitro);
565
568
  if (!options.skipBootstrap) await autoBootstrap(nitro);
566
569
  await setupRuntimeConfig(nitro, options);
567
- setupRuntime(nitro, options.manifest);
570
+ setupRuntime(nitro, manifest);
568
571
  nitro.hooks.hook("dev:reload", async () => {
569
- setupRuntime(nitro, options.manifest);
572
+ manifest = await createC8yManifestFromNitro(nitro);
573
+ setupRuntime(nitro, manifest);
570
574
  if (options.apiClient) {
571
575
  nitro.logger.debug("Generating C8Y API client");
572
576
  await writeAPIClient(nitro, options);
@@ -1,7 +1,7 @@
1
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
2
  import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-Dqi-ttQi.mjs";
3
3
  import { defineCommand } from "citty";
4
- import { consola as consola$1 } from "consola";
4
+ import { consola } from "consola";
5
5
  //#region src/cli/commands/options.ts
6
6
  var options_default = defineCommand({
7
7
  meta: {
@@ -10,21 +10,21 @@ var options_default = defineCommand({
10
10
  },
11
11
  args: {},
12
12
  async run() {
13
- consola$1.info("Loading configuration...");
13
+ consola.info("Loading configuration...");
14
14
  const { env, c8yOptions, configDir } = await loadC8yConfig();
15
- consola$1.info("Validating environment variables...");
15
+ consola.info("Validating environment variables...");
16
16
  const envVars = validateBootstrapEnv(env);
17
- consola$1.info("Loading manifest...");
17
+ consola.info("Loading manifest...");
18
18
  const manifest = await createC8yManifest(configDir, c8yOptions?.manifest);
19
19
  const category = manifest.settingsCategory || manifest.contextPath || manifest.name;
20
20
  if (!manifest.settings || manifest.settings.length === 0) throw new Error("No settings defined in manifest. Add settings to your c8y.manifest configuration.");
21
- consola$1.success(`Using category: ${category}`);
21
+ consola.success(`Using category: ${category}`);
22
22
  const authHeader = createBasicAuthHeader(envVars.C8Y_DEVELOPMENT_TENANT, envVars.C8Y_DEVELOPMENT_USER, envVars.C8Y_DEVELOPMENT_PASSWORD);
23
- consola$1.info("Fetching current tenant options...");
23
+ consola.info("Fetching current tenant options...");
24
24
  const currentOptions = await getTenantOptionsByCategory(envVars.C8Y_BASEURL, category, authHeader);
25
25
  const availableKeys = manifest.settings.map((s) => s.key);
26
- consola$1.success(`Found ${Object.keys(currentOptions).length} options set on tenant`);
27
- const action = await consola$1.prompt("What do you want to do?", {
26
+ consola.success(`Found ${Object.keys(currentOptions).length} options set on tenant`);
27
+ const action = await consola.prompt("What do you want to do?", {
28
28
  type: "select",
29
29
  options: [
30
30
  {
@@ -59,10 +59,10 @@ async function handleRead(baseUrl, category, authHeader, availableKeys, currentO
59
59
  const credentialsKeys = availableKeys.filter((k) => k.startsWith("credentials."));
60
60
  const allKeys = [...setKeys, ...credentialsKeys];
61
61
  if (allKeys.length === 0) {
62
- consola$1.warn("No options are currently set");
62
+ consola.warn("No options are currently set");
63
63
  return;
64
64
  }
65
- const key = await consola$1.prompt("Select option to read:", {
65
+ const key = await consola.prompt("Select option to read:", {
66
66
  type: "select",
67
67
  options: allKeys.map((k) => ({
68
68
  label: k.startsWith("credentials.") ? `${k} (unknown)` : k,
@@ -70,10 +70,10 @@ async function handleRead(baseUrl, category, authHeader, availableKeys, currentO
70
70
  })),
71
71
  cancel: "reject"
72
72
  });
73
- consola$1.info(`Reading option: ${key}`);
73
+ consola.info(`Reading option: ${key}`);
74
74
  const value = await getTenantOption(baseUrl, category, key.startsWith("credentials.") ? key.replace(/^credentials\./, "") : key, authHeader);
75
- if (value === void 0) consola$1.warn(`Option '${key}' is not set`);
76
- else consola$1.success(`Value: ${value}`);
75
+ if (value === void 0) consola.warn(`Option '${key}' is not set`);
76
+ else consola.success(`Value: ${value}`);
77
77
  }
78
78
  /**
79
79
  * Handle updating options (with loop for multiple updates)
@@ -86,7 +86,7 @@ async function handleRead(baseUrl, category, authHeader, availableKeys, currentO
86
86
  async function handleUpdate(baseUrl, category, authHeader, availableKeys, currentOptions) {
87
87
  let continueUpdating = true;
88
88
  while (continueUpdating) {
89
- const key = await consola$1.prompt("Select option to update:", {
89
+ const key = await consola.prompt("Select option to update:", {
90
90
  type: "select",
91
91
  options: availableKeys.map((k) => ({
92
92
  label: currentOptions[k] !== void 0 ? `${k} (current: ${currentOptions[k]})` : k.startsWith("credentials.") ? `${k} (current: unknown)` : `${k} (not set)`,
@@ -95,16 +95,16 @@ async function handleUpdate(baseUrl, category, authHeader, availableKeys, curren
95
95
  cancel: "reject"
96
96
  });
97
97
  const currentValue = currentOptions[key];
98
- const newValue = await consola$1.prompt("Enter new value:", {
98
+ const newValue = await consola.prompt("Enter new value:", {
99
99
  type: "text",
100
100
  default: currentValue,
101
101
  cancel: "reject"
102
102
  });
103
- consola$1.info(`Updating option: ${key}`);
103
+ consola.info(`Updating option: ${key}`);
104
104
  await updateTenantOption(baseUrl, category, key.replace(/^credentials\./, ""), newValue, authHeader);
105
105
  currentOptions[key] = newValue;
106
- consola$1.success(`Option '${key}' updated successfully`);
107
- if (!await consola$1.prompt("Update another option?", {
106
+ consola.success(`Option '${key}' updated successfully`);
107
+ if (!await consola.prompt("Update another option?", {
108
108
  type: "confirm",
109
109
  initial: false,
110
110
  cancel: "reject"
@@ -124,22 +124,22 @@ async function handleDelete(baseUrl, category, authHeader, availableKeys, curren
124
124
  const credentialsKeys = availableKeys.filter((k) => k.startsWith("credentials."));
125
125
  const allKeys = [...setKeys, ...credentialsKeys];
126
126
  if (allKeys.length === 0) {
127
- consola$1.warn("No options are currently set");
127
+ consola.warn("No options are currently set");
128
128
  return;
129
129
  }
130
- const keysToDelete = await consola$1.prompt("Select option(s) to delete:", {
130
+ const keysToDelete = await consola.prompt("Select option(s) to delete:", {
131
131
  type: "multiselect",
132
132
  options: allKeys,
133
133
  required: true,
134
134
  cancel: "reject"
135
135
  });
136
- consola$1.info(`Deleting ${keysToDelete.length} option(s)...`);
136
+ consola.info(`Deleting ${keysToDelete.length} option(s)...`);
137
137
  for (const key of keysToDelete) {
138
- consola$1.info(`Deleting option: ${key}`);
138
+ consola.info(`Deleting option: ${key}`);
139
139
  await deleteTenantOption(baseUrl, category, key.startsWith("credentials.") ? key.replace(/^credentials\./, "") : key, authHeader);
140
- consola$1.success(`✓ Deleted: ${key}`);
140
+ consola.success(`✓ Deleted: ${key}`);
141
141
  }
142
- consola$1.success("Delete operation completed");
142
+ consola.success("Delete operation completed");
143
143
  }
144
144
  //#endregion
145
145
  export { options_default as default };
@@ -1,6 +1,6 @@
1
1
  //#region package.json
2
2
  var name = "c8y-nitro";
3
- var version = "0.4.0";
3
+ var version = "0.4.2";
4
4
  var description = "Lightning fast Cumulocity IoT microservice development powered by Nitro";
5
5
  //#endregion
6
6
  export { name as n, version as r, description as t };
@@ -1,7 +1,7 @@
1
1
  import { n as createBasicAuthHeader, p as createC8yManifest, t as assignUserRole, u as unassignUserRole } from "./c8y-api-BBSKRwKs.mjs";
2
2
  import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-Dqi-ttQi.mjs";
3
3
  import { defineCommand } from "citty";
4
- import { consola as consola$1 } from "consola";
4
+ import { consola } from "consola";
5
5
  //#region src/cli/commands/roles.ts
6
6
  var roles_default = defineCommand({
7
7
  meta: {
@@ -10,28 +10,28 @@ var roles_default = defineCommand({
10
10
  },
11
11
  args: {},
12
12
  async run() {
13
- consola$1.info("Loading configuration...");
13
+ consola.info("Loading configuration...");
14
14
  const { env, c8yOptions, configDir } = await loadC8yConfig();
15
- consola$1.info("Validating environment variables...");
15
+ consola.info("Validating environment variables...");
16
16
  const envVars = validateBootstrapEnv(env);
17
- consola$1.info("Building manifest...");
17
+ consola.info("Building manifest...");
18
18
  const manifest = await createC8yManifest(configDir, c8yOptions?.manifest);
19
19
  const authHeader = createBasicAuthHeader(envVars.C8Y_DEVELOPMENT_TENANT, envVars.C8Y_DEVELOPMENT_USER, envVars.C8Y_DEVELOPMENT_PASSWORD);
20
20
  if (manifest.roles && manifest.roles.length > 0) {
21
- const rolesToAssign = await consola$1.prompt("Select roles to assign to your user (unselected roles will be removed):", {
21
+ const rolesToAssign = await consola.prompt("Select roles to assign to your user (unselected roles will be removed):", {
22
22
  type: "multiselect",
23
23
  options: manifest.roles,
24
24
  cancel: "reject",
25
25
  required: false
26
26
  });
27
- consola$1.info("Managing user roles...");
27
+ consola.info("Managing user roles...");
28
28
  const rolePromises = manifest.roles.map(async (role) => {
29
29
  if (rolesToAssign.includes(role)) return await assignUserRole(envVars.C8Y_BASEURL, envVars.C8Y_DEVELOPMENT_TENANT, envVars.C8Y_DEVELOPMENT_USER, role, authHeader);
30
30
  else return await unassignUserRole(envVars.C8Y_BASEURL, envVars.C8Y_DEVELOPMENT_TENANT, envVars.C8Y_DEVELOPMENT_USER, role, authHeader);
31
31
  });
32
32
  await Promise.all(rolePromises);
33
- consola$1.success("Role management complete");
34
- } else consola$1.warn("No roles defined in manifest. Nothing to manage.");
33
+ consola.success("Role management complete");
34
+ } else consola.warn("No roles defined in manifest. Nothing to manage.");
35
35
  }
36
36
  });
37
37
  //#endregion
@@ -1,6 +1,6 @@
1
- import { Buffer } from "node:buffer";
2
- import process from "node:process";
3
1
  import { defineHandler } from "nitro/h3";
2
+ import process from "node:process";
3
+ import { Buffer } from "node:buffer";
4
4
  import consola from "consola";
5
5
  //#region src/module/runtime/middlewares/dev-user.ts
6
6
  var dev_user_default = defineHandler((event) => {
@@ -0,0 +1,15 @@
1
+ import { definePlugin } from "nitro";
2
+ import { c8yManifest } from "c8y-nitro/runtime";
3
+ //#region src/module/runtime/plugins/enrich-logs.ts
4
+ var enrich_logs_default = definePlugin(async (nitroApp) => {
5
+ nitroApp.hooks.hook("evlog:enrich", (enrichContext) => {
6
+ enrichContext.event.microservice = {
7
+ name: c8yManifest.name,
8
+ version: c8yManifest.version,
9
+ provider: c8yManifest.provider,
10
+ contextPath: c8yManifest.contextPath
11
+ };
12
+ });
13
+ });
14
+ //#endregion
15
+ export { enrich_logs_default as default };
package/dist/types.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as C8yCacheOptions, c as C8YAPIClientOptions, i as C8YRoles, n as C8YTenantOptionKey, o as C8YZipOptions, r as C8YTenantOptionKeysCacheConfig, s as C8YManifestOptions, t as C8yNitroModuleOptions } from "./index-B6HtYHU0.mjs";
2
- export { C8YAPIClientOptions, C8YManifestOptions, C8YRoles, C8YTenantOptionKey, C8YTenantOptionKeysCacheConfig, C8YZipOptions, C8yCacheOptions, C8yNitroModuleOptions };
1
+ import { a as C8yCacheOptions, c as C8YManifestOptions, i as C8YRoles, l as C8YAPIClientOptions, n as C8YTenantOptionKey, o as C8YZipOptions, r as C8YTenantOptionKeysCacheConfig, s as C8YManifest, t as C8yNitroModuleOptions } from "./index-CzUqbp5C.mjs";
2
+ export { C8YAPIClientOptions, C8YManifest, C8YManifestOptions, C8YRoles, C8YTenantOptionKey, C8YTenantOptionKeysCacheConfig, C8YZipOptions, C8yCacheOptions, C8yNitroModuleOptions };
package/dist/utils.mjs CHANGED
@@ -1,7 +1,9 @@
1
+ import { Buffer } from "node:buffer";
1
2
  import process from "node:process";
2
3
  import { useLogger } from "evlog/nitro/v3";
3
4
  import { BasicAuth, Client, MicroserviceClientRequestAuth } from "@c8y/client";
4
5
  import { defineCachedFunction } from "nitro/cache";
6
+ import { createHash, randomBytes } from "node:crypto";
5
7
  import { HTTPError, defineHandler } from "nitro/h3";
6
8
  import { useStorage } from "nitro/storage";
7
9
  import { useRuntimeConfig } from "nitro/runtime-config";
@@ -30,6 +32,55 @@ function convertRequestHeadersToC8yFormat(request) {
30
32
  return headers;
31
33
  }
32
34
  //#endregion
35
+ //#region src/utils/internal/tenant.ts
36
+ const USER_TENANT_CACHE_SALT = randomBytes(32).toString("hex");
37
+ function getCookieValue(cookieHeader, name) {
38
+ try {
39
+ const value = cookieHeader?.match(`(^|;)\\s*${name}\\s*=\\s*([^;]+)`);
40
+ return value ? value.pop() : void 0;
41
+ } catch {
42
+ return;
43
+ }
44
+ }
45
+ function getCurrentUserTenantCacheKeyMaterial(requestOrEvent) {
46
+ const request = "req" in requestOrEvent ? requestOrEvent.req : requestOrEvent;
47
+ const cookieAuth = getCookieValue(request.headers.get("cookie"), "authorization");
48
+ if (cookieAuth) return `cookie:${cookieAuth}`;
49
+ const authorization = request.headers.get("authorization");
50
+ if (authorization) return `header:${authorization}`;
51
+ }
52
+ function createCurrentUserTenantCacheKey(requestOrEvent) {
53
+ const material = getCurrentUserTenantCacheKeyMaterial(requestOrEvent);
54
+ if (!material) throw new Error("Cannot create current user tenant cache key without auth material");
55
+ return createHash("sha256").update(USER_TENANT_CACHE_SALT).update(":").update(material).digest("hex");
56
+ }
57
+ function tryGetTenantFromBasicAuth(requestOrEvent) {
58
+ const authorization = ("req" in requestOrEvent ? requestOrEvent.req : requestOrEvent).headers.get("authorization");
59
+ if (!authorization?.startsWith("Basic ")) return;
60
+ try {
61
+ const decoded = Buffer.from(authorization.slice(6), "base64").toString("utf8");
62
+ const separatorIndex = decoded.indexOf(":");
63
+ const userPart = separatorIndex === -1 ? decoded : decoded.slice(0, separatorIndex);
64
+ const slashIndex = userPart.indexOf("/");
65
+ if (slashIndex <= 0) return;
66
+ return userPart.slice(0, slashIndex);
67
+ } catch {
68
+ return;
69
+ }
70
+ }
71
+ const getCurrentUserTenantId = defineCachedFunction(async (requestOrEvent) => {
72
+ const basicTenant = tryGetTenantFromBasicAuth(requestOrEvent);
73
+ if (basicTenant) return basicTenant;
74
+ return (await useUserClient(requestOrEvent).tenant.current()).data.name;
75
+ }, {
76
+ maxAge: 60,
77
+ name: "_c8y_nitro_get_current_user_tenant_id",
78
+ group: "c8y_nitro",
79
+ swr: false,
80
+ getKey: (requestOrEvent) => createCurrentUserTenantCacheKey(requestOrEvent),
81
+ shouldBypassCache: (requestOrEvent) => !getCurrentUserTenantCacheKeyMaterial(requestOrEvent)
82
+ });
83
+ //#endregion
33
84
  //#region src/utils/credentials.ts
34
85
  /**
35
86
  * Fetches credentials for all tenants subscribed to this microservice.\
@@ -128,7 +179,7 @@ const useDeployedTenantCredentials = Object.assign(async () => {
128
179
  async function useUserTenantCredentials(requestOrEvent) {
129
180
  const request = "req" in requestOrEvent ? requestOrEvent.req : requestOrEvent;
130
181
  if (request.context?.["c8y_user_tenant_credentials"]) return request.context["c8y_user_tenant_credentials"];
131
- const tenantId = useUserClient(requestOrEvent).core.tenant;
182
+ const tenantId = await getCurrentUserTenantId(requestOrEvent);
132
183
  const userTenantCreds = (await useSubscribedTenantCredentials())[tenantId];
133
184
  if (!userTenantCreds) throw new HTTPError({
134
185
  message: `No subscribed tenant credentials found for user tenant '${tenantId}'`,
@@ -172,7 +223,7 @@ function useUserClient(requestOrEvent) {
172
223
  async function useUserTenantClient(requestOrEvent) {
173
224
  const request = "req" in requestOrEvent ? requestOrEvent.req : requestOrEvent;
174
225
  if (request.context?.["c8y_user_tenant_client"]) return request.context["c8y_user_tenant_client"];
175
- const tenantId = useUserClient(requestOrEvent).core.tenant;
226
+ const tenantId = await getCurrentUserTenantId(requestOrEvent);
176
227
  const creds = await useSubscribedTenantCredentials();
177
228
  if (!creds[tenantId]) throw new HTTPError({
178
229
  message: `No subscribed tenant credentials found for user tenant '${tenantId}'`,
@@ -281,7 +332,7 @@ function hasUserRequiredRole(roleOrRoles) {
281
332
  function isUserFromAllowedTenant(tenantIdOrIds) {
282
333
  return defineHandler(async (event) => {
283
334
  const allowedTenants = Array.isArray(tenantIdOrIds) ? tenantIdOrIds : [tenantIdOrIds];
284
- const userTenantId = useUserClient(event).core.tenant;
335
+ const userTenantId = await getCurrentUserTenantId(event);
285
336
  if (!allowedTenants.includes(userTenantId)) throw new HTTPError({
286
337
  status: 403,
287
338
  statusText: "Forbidden",
@@ -306,7 +357,7 @@ function isUserFromAllowedTenant(tenantIdOrIds) {
306
357
  */
307
358
  function isUserFromDeployedTenant() {
308
359
  return defineHandler(async (event) => {
309
- const userTenantId = useUserClient(event).core.tenant;
360
+ const userTenantId = await getCurrentUserTenantId(event);
310
361
  const deployedTenantId = process.env.C8Y_BOOTSTRAP_TENANT;
311
362
  if (!deployedTenantId) throw new HTTPError({
312
363
  status: 500,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c8y-nitro",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "description": "Lightning fast Cumulocity IoT microservice development powered by Nitro",
6
6
  "keywords": [
@@ -48,10 +48,10 @@
48
48
  "dist"
49
49
  ],
50
50
  "dependencies": {
51
- "c12": "^4.0.0-beta.3",
51
+ "c12": "^4.0.0-beta.4",
52
52
  "citty": "^0.2.1",
53
53
  "consola": "^3.4.2",
54
- "evlog": "^2.6.0",
54
+ "evlog": "^2.9.0",
55
55
  "jszip": "^3.10.1",
56
56
  "pathe": "^2.0.3",
57
57
  "pkg-types": "^2.3.0",
@@ -61,10 +61,11 @@
61
61
  "devDependencies": {
62
62
  "@schplitt/eslint-config": "^1.3.1",
63
63
  "@types/spinnies": "^0.5.3",
64
- "bumpp": "^10.4.1",
65
- "eslint": "^10.0.3",
66
- "memfs": "^4.56.11",
67
- "tsdown": "^0.21.2",
64
+ "bumpp": "^11.0.1",
65
+ "changelogithub": "^14.0.0",
66
+ "eslint": "^10.1.0",
67
+ "memfs": "^4.57.1",
68
+ "tsdown": "^0.21.4",
68
69
  "typescript": "^5.9.3",
69
70
  "vitest": "^4.1.0"
70
71
  },
@@ -1,8 +0,0 @@
1
- import * as nitro_h30 from "nitro/h3";
2
-
3
- //#region src/module/runtime/handlers/liveness-readiness.d.ts
4
- declare const _default: nitro_h30.EventHandlerWithFetch<nitro_h30.EventHandlerRequest, Promise<{
5
- status: string;
6
- }>>;
7
- //#endregion
8
- export { _default as default };
@@ -1,6 +0,0 @@
1
- import * as nitro_h30 from "nitro/h3";
2
-
3
- //#region src/module/runtime/middlewares/dev-user.d.ts
4
- declare const _default: nitro_h30.EventHandlerWithFetch<nitro_h30.EventHandlerRequest, void>;
5
- //#endregion
6
- export { _default as default };
@@ -1,6 +0,0 @@
1
- import * as nitro_types0 from "nitro/types";
2
-
3
- //#region src/module/runtime/plugins/c8y-variables.d.ts
4
- declare const _default: nitro_types0.NitroAppPlugin;
5
- //#endregion
6
- export { _default as default };