c8y-nitro 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/autoBootstrap-Drk7ltXK.mjs +212 -0
- package/dist/{bootstrap-ObaEF7rN.mjs → bootstrap-C1Qly0J_.mjs} +2 -2
- package/dist/{c8y-api-BFjK6AYb.mjs → c8y-api-DscUs-Kd.mjs} +22 -18
- package/dist/cli/index.mjs +4 -4
- package/dist/{client-DxmbyZju.mjs → client-D0Tdq7H1.mjs} +45 -1
- package/dist/index.mjs +20 -173
- package/dist/options-CmLpZ3Qf.mjs +179 -0
- package/dist/{package-D5xRshsx.mjs → package-CNWz76PV.mjs} +1 -1
- package/dist/{roles-B58Fguop.mjs → roles-B-DNuScZ.mjs} +1 -1
- package/dist/runtime/handlers/invalidateTenantOptions.mjs +1 -1
- package/dist/runtime/handlers/openapi.mjs +7 -6
- package/dist/runtime/handlers/rewriteRequestUrl.mjs +38 -0
- package/dist/utils.d.mts +229 -75
- package/dist/utils.mjs +153 -155
- package/package.json +2 -2
- package/dist/options-B7_yb_wr.mjs +0 -147
package/dist/utils.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
import { createHash, randomBytes
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { useLogger as useLogger$1 } from "evlog/nitro/v3";
|
|
5
5
|
import { BasicAuth, Client, MicroserviceClientRequestAuth } from "@c8y/client";
|
|
@@ -9,8 +9,7 @@ import { useRuntimeConfig } from "nitro/runtime-config";
|
|
|
9
9
|
import { useNitroHooks } from "nitro/app";
|
|
10
10
|
import { defineHandler } from "nitro/h3";
|
|
11
11
|
import { c8yManifest } from "c8y-nitro/runtime";
|
|
12
|
-
import {
|
|
13
|
-
import { tasks } from "#nitro/virtual/tasks";
|
|
12
|
+
import { Cron } from "croner";
|
|
14
13
|
//#region src/utils/internal/common.ts
|
|
15
14
|
/**
|
|
16
15
|
* Converts undici Request headers to the format expected by MicroserviceClientRequestAuth.\
|
|
@@ -884,168 +883,167 @@ const useTenantOptions = Object.assign(useTenantOptionsBase, {
|
|
|
884
883
|
refreshAll: () => refreshFetchers(() => true)
|
|
885
884
|
});
|
|
886
885
|
//#endregion
|
|
887
|
-
//#region src/utils/
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
const scheduledTaskTimers = /* @__PURE__ */ new Map();
|
|
893
|
-
const schedulerReady = Promise.resolve();
|
|
894
|
-
let schedulerInterval;
|
|
895
|
-
function createScheduledTaskId() {
|
|
896
|
-
return randomUUID();
|
|
897
|
-
}
|
|
898
|
-
function resolveScheduleTime(schedule) {
|
|
899
|
-
if (schedule instanceof Date) {
|
|
900
|
-
const timestamp = schedule.getTime();
|
|
901
|
-
if (Number.isNaN(timestamp)) throw new TypeError("schedule date must be valid");
|
|
902
|
-
return timestamp;
|
|
903
|
-
}
|
|
904
|
-
if (typeof schedule === "number") {
|
|
905
|
-
if (!Number.isFinite(schedule) || schedule < 0) throw new TypeError("schedule number must be a non-negative number of seconds");
|
|
906
|
-
return Date.now() + schedule * 1e3;
|
|
886
|
+
//#region src/utils/tasks.ts
|
|
887
|
+
function resolveOnceDate(schedule) {
|
|
888
|
+
if ("in" in schedule) {
|
|
889
|
+
if (!Number.isFinite(schedule.in) || schedule.in < 0) throw new TypeError("schedule.in must be a non-negative number of seconds");
|
|
890
|
+
return new Date(Date.now() + schedule.in * 1e3);
|
|
907
891
|
}
|
|
908
|
-
const
|
|
909
|
-
if (
|
|
910
|
-
return
|
|
911
|
-
}
|
|
912
|
-
async function executeScheduledTask(id) {
|
|
913
|
-
const record = scheduledTasks.get(id);
|
|
914
|
-
if (!record) return;
|
|
915
|
-
if (record.timeoutId !== void 0) scheduledTaskTimers.delete(record.timeoutId);
|
|
916
|
-
scheduledTasks.delete(id);
|
|
917
|
-
stopSchedulerIntervalIfIdle();
|
|
918
|
-
const taskDef = tasks[record.taskName];
|
|
919
|
-
if (!taskDef) throw new Error(`Task "${record.taskName}" is not available!`);
|
|
920
|
-
if (!taskDef.resolve) throw new Error(`Task "${record.taskName}" is not implemented!`);
|
|
921
|
-
await (await taskDef.resolve()).run({
|
|
922
|
-
name: record.taskName,
|
|
923
|
-
payload: record.payload,
|
|
924
|
-
context: record.context
|
|
925
|
-
});
|
|
926
|
-
}
|
|
927
|
-
function armScheduledTaskTimeout(id) {
|
|
928
|
-
const record = scheduledTasks.get(id);
|
|
929
|
-
if (!record || record.timeoutId !== void 0) return;
|
|
930
|
-
const remainingMs = Math.max(record.runAt - Date.now(), 0);
|
|
931
|
-
if (remainingMs > SCHEDULE_LOOKAHEAD_MS) return;
|
|
932
|
-
const timeout = setTimeout(() => {
|
|
933
|
-
executeScheduledTask(id);
|
|
934
|
-
}, Math.min(remainingMs, MAX_TIMEOUT_MS));
|
|
935
|
-
record.timeoutId = Number(timeout);
|
|
936
|
-
scheduledTaskTimers.set(record.timeoutId, timeout);
|
|
937
|
-
}
|
|
938
|
-
function runSchedulerTick() {
|
|
939
|
-
for (const record of scheduledTasks.values()) armScheduledTaskTimeout(record.id);
|
|
940
|
-
}
|
|
941
|
-
function ensureSchedulerInterval() {
|
|
942
|
-
if (schedulerInterval) return;
|
|
943
|
-
schedulerInterval = setInterval(runSchedulerTick, SCHEDULER_TICK_MS);
|
|
944
|
-
}
|
|
945
|
-
function stopSchedulerIntervalIfIdle() {
|
|
946
|
-
if (!schedulerInterval || scheduledTasks.size > 0) return;
|
|
947
|
-
clearInterval(schedulerInterval);
|
|
948
|
-
schedulerInterval = void 0;
|
|
949
|
-
}
|
|
950
|
-
/**
|
|
951
|
-
* Schedules a Nitro task to run once in the future.\
|
|
952
|
-
* Resolves and calls the task handler directly from Nitro's virtual task registry\
|
|
953
|
-
* Numbers are treated as seconds, strings are parsed as human-readable durations, and dates are used as exact run times.
|
|
954
|
-
*
|
|
955
|
-
* @param taskName - The Nitro task name to run (from `tasks/*.ts`)
|
|
956
|
-
* @param options - Task payload, context, and the schedule time
|
|
957
|
-
* @returns Information about the scheduled task
|
|
958
|
-
*
|
|
959
|
-
* @example
|
|
960
|
-
* // Run a task in 30 seconds:
|
|
961
|
-
* const scheduled = await scheduleTask('emails:send', {
|
|
962
|
-
* payload: { messageId: 'abc123' },
|
|
963
|
-
* schedule: 30,
|
|
964
|
-
* })
|
|
965
|
-
*
|
|
966
|
-
* @example
|
|
967
|
-
* // Run a task using a human-readable duration:
|
|
968
|
-
* await scheduleTask('reports:generate', {
|
|
969
|
-
* payload: { reportId: 'report-1' },
|
|
970
|
-
* schedule: '1 hour',
|
|
971
|
-
* })
|
|
972
|
-
*
|
|
973
|
-
* @example
|
|
974
|
-
* // Run a task at an exact time:
|
|
975
|
-
* await scheduleTask('cleanup:tenant', {
|
|
976
|
-
* payload: { tenant: 't12345' },
|
|
977
|
-
* schedule: new Date('2026-05-01T12:00:00Z'),
|
|
978
|
-
* })
|
|
979
|
-
*/
|
|
980
|
-
async function scheduleTask(taskName, options) {
|
|
981
|
-
if (!import.meta._tasks) throw new Error("scheduleTask() requires tasks to be enabled. Set `experimental: { tasks: true }` in your nitro.config.ts.");
|
|
982
|
-
if (!taskName) throw new TypeError("taskName is required");
|
|
983
|
-
const runAt = resolveScheduleTime(options.schedule);
|
|
984
|
-
const record = {
|
|
985
|
-
id: createScheduledTaskId(),
|
|
986
|
-
taskName,
|
|
987
|
-
payload: options.payload ?? {},
|
|
988
|
-
context: options.context ?? {},
|
|
989
|
-
runAt
|
|
990
|
-
};
|
|
991
|
-
scheduledTasks.set(record.id, record);
|
|
992
|
-
armScheduledTaskTimeout(record.id);
|
|
993
|
-
ensureSchedulerInterval();
|
|
994
|
-
return {
|
|
995
|
-
id: record.id,
|
|
996
|
-
task: record.taskName,
|
|
997
|
-
runAt: new Date(record.runAt).toISOString()
|
|
998
|
-
};
|
|
892
|
+
const date = schedule.at instanceof Date ? schedule.at : new Date(schedule.at);
|
|
893
|
+
if (Number.isNaN(date.getTime())) throw new TypeError("schedule.at must be a valid Date or ISO 8601 string");
|
|
894
|
+
return date;
|
|
999
895
|
}
|
|
1000
896
|
/**
|
|
1001
|
-
*
|
|
1002
|
-
* The returned object is keyed by the scheduled task UUID for easy lookup and cancellation.
|
|
1003
|
-
*
|
|
1004
|
-
* @returns Object mapping scheduled task IDs to their public task information
|
|
897
|
+
* Creates a new, empty task registry.
|
|
1005
898
|
*
|
|
1006
899
|
* @example
|
|
1007
|
-
*
|
|
1008
|
-
* for (const [id, task] of Object.entries(tasks)) {
|
|
1009
|
-
* console.log(id, task.task, task.runAt)
|
|
1010
|
-
* }
|
|
1011
|
-
*/
|
|
1012
|
-
async function listScheduledTasks() {
|
|
1013
|
-
await schedulerReady;
|
|
1014
|
-
return Object.fromEntries([...scheduledTasks.values()].map((record) => [record.id, {
|
|
1015
|
-
id: record.id,
|
|
1016
|
-
task: record.taskName,
|
|
1017
|
-
runAt: new Date(record.runAt).toISOString()
|
|
1018
|
-
}]));
|
|
1019
|
-
}
|
|
1020
|
-
/**
|
|
1021
|
-
* Cancels a scheduled task before it starts running.\
|
|
1022
|
-
* Once the underlying Nitro task has started, it cannot be cancelled with this utility.
|
|
900
|
+
* import { c8yTasks } from 'c8y-nitro/utils'
|
|
1023
901
|
*
|
|
1024
|
-
*
|
|
1025
|
-
*
|
|
902
|
+
* export const tasks = c8yTasks()
|
|
903
|
+
* .createTask('sync', async ({ payload }) => {
|
|
904
|
+
* const config = await loadConfig(payload.configId) // resolve live state here
|
|
905
|
+
* await sync(config)
|
|
906
|
+
* })
|
|
1026
907
|
*
|
|
1027
|
-
*
|
|
1028
|
-
*
|
|
1029
|
-
*
|
|
1030
|
-
* schedule: '10 minutes',
|
|
1031
|
-
* })
|
|
908
|
+
* // one per config, each an independent job
|
|
909
|
+
* tasks.scheduleJob({ name: 'sync-abc', task: 'sync', payload: { configId: 'abc' }, schedule: { cron: '*\/5 * * * *' } })
|
|
910
|
+
* tasks.scheduleJob({ name: 'sync-def', task: 'sync', payload: { configId: 'def' }, schedule: { cron: '*\/5 * * * *' }, concurrency: 'parallel' })
|
|
1032
911
|
*
|
|
1033
|
-
*
|
|
912
|
+
* @remarks
|
|
913
|
+
* The registry is a plain runtime singleton — export it from one module and import
|
|
914
|
+
* it wherever you schedule or cancel jobs. Recurring jobs only arm when the module
|
|
915
|
+
* that schedules them executes, so register them from a Nitro plugin (or another
|
|
916
|
+
* startup path) if they must run from boot.
|
|
1034
917
|
*/
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
if (
|
|
1042
|
-
|
|
1043
|
-
|
|
918
|
+
function c8yTasks() {
|
|
919
|
+
const tasks = /* @__PURE__ */ new Map();
|
|
920
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
921
|
+
function execute(record, override) {
|
|
922
|
+
const definition = tasks.get(record.task);
|
|
923
|
+
if (!definition) return Promise.reject(/* @__PURE__ */ new Error(`Task "${record.task}" is not registered`));
|
|
924
|
+
if (record.concurrency === "single" && record.inFlight) return record.inFlight;
|
|
925
|
+
const event = {
|
|
926
|
+
task: record.task,
|
|
927
|
+
job: record.name,
|
|
928
|
+
payload: override?.payload ?? record.payload,
|
|
929
|
+
context: override?.context ?? record.context
|
|
930
|
+
};
|
|
931
|
+
const promise = (async () => definition.handler(event))();
|
|
932
|
+
if (record.concurrency === "single") {
|
|
933
|
+
record.inFlight = promise;
|
|
934
|
+
promise.finally(() => {
|
|
935
|
+
if (record.inFlight === promise) record.inFlight = void 0;
|
|
936
|
+
});
|
|
1044
937
|
}
|
|
938
|
+
return promise;
|
|
1045
939
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
940
|
+
function toJobInfo(record) {
|
|
941
|
+
const next = record.cron.nextRun();
|
|
942
|
+
const base = {
|
|
943
|
+
name: record.name,
|
|
944
|
+
task: record.task,
|
|
945
|
+
concurrency: record.concurrency,
|
|
946
|
+
nextRun: next ? next.toISOString() : null,
|
|
947
|
+
running: record.inFlight !== void 0
|
|
948
|
+
};
|
|
949
|
+
if (record.kind === "recurring") return {
|
|
950
|
+
...base,
|
|
951
|
+
kind: "recurring",
|
|
952
|
+
cron: record.cronExpr,
|
|
953
|
+
timezone: record.timezone,
|
|
954
|
+
nextRuns: record.cron.nextRuns(3).map((date) => date.toISOString())
|
|
955
|
+
};
|
|
956
|
+
return {
|
|
957
|
+
...base,
|
|
958
|
+
kind: "once",
|
|
959
|
+
cron: null
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
function cancelJob(name) {
|
|
963
|
+
const record = jobs.get(name);
|
|
964
|
+
if (!record) return false;
|
|
965
|
+
record.cron.stop();
|
|
966
|
+
return jobs.delete(name);
|
|
967
|
+
}
|
|
968
|
+
const api = {
|
|
969
|
+
createTask(name, handler) {
|
|
970
|
+
if (typeof name !== "string" || !name) throw new TypeError("task name is required");
|
|
971
|
+
if (tasks.has(name)) throw new Error(`Task "${name}" is already registered`);
|
|
972
|
+
tasks.set(name, {
|
|
973
|
+
name,
|
|
974
|
+
handler
|
|
975
|
+
});
|
|
976
|
+
return api;
|
|
977
|
+
},
|
|
978
|
+
async run(task, options) {
|
|
979
|
+
const definition = tasks.get(task);
|
|
980
|
+
if (!definition) throw new Error(`Task "${task}" is not registered`);
|
|
981
|
+
return definition.handler({
|
|
982
|
+
task,
|
|
983
|
+
payload: options?.payload ?? {},
|
|
984
|
+
context: options?.context ?? {}
|
|
985
|
+
});
|
|
986
|
+
},
|
|
987
|
+
scheduleJob(options) {
|
|
988
|
+
const { name, task, schedule, concurrency = "single", replace = false } = options;
|
|
989
|
+
if (!name) throw new TypeError("job name is required");
|
|
990
|
+
if (!tasks.has(task)) throw new Error(`Task "${task}" is not registered. Register it with createTask() first.`);
|
|
991
|
+
if (jobs.has(name)) {
|
|
992
|
+
if (!replace) throw new Error(`Job "${name}" already exists. Pass { replace: true } to overwrite it.`);
|
|
993
|
+
cancelJob(name);
|
|
994
|
+
}
|
|
995
|
+
const isRecurring = "cron" in schedule;
|
|
996
|
+
const timezone = options.timezone ?? "UTC";
|
|
997
|
+
const record = {
|
|
998
|
+
name,
|
|
999
|
+
task,
|
|
1000
|
+
kind: isRecurring ? "recurring" : "once",
|
|
1001
|
+
concurrency,
|
|
1002
|
+
cronExpr: isRecurring ? schedule.cron : null,
|
|
1003
|
+
timezone,
|
|
1004
|
+
payload: options.payload ?? {},
|
|
1005
|
+
context: options.context ?? {},
|
|
1006
|
+
cron: void 0
|
|
1007
|
+
};
|
|
1008
|
+
const callback = () => {
|
|
1009
|
+
Promise.resolve(execute(record)).catch((error) => {
|
|
1010
|
+
console.error(`c8y-nitro: job "${name}" (task "${task}") failed`, error);
|
|
1011
|
+
}).finally(() => {
|
|
1012
|
+
if (!record.cron.nextRun()) jobs.delete(name);
|
|
1013
|
+
});
|
|
1014
|
+
};
|
|
1015
|
+
if (isRecurring) {
|
|
1016
|
+
const cronOptions = { timezone };
|
|
1017
|
+
if (typeof options.maxRuns === "number") cronOptions.maxRuns = options.maxRuns;
|
|
1018
|
+
record.cron = new Cron(schedule.cron, cronOptions, callback);
|
|
1019
|
+
} else {
|
|
1020
|
+
const fireAt = Math.max(resolveOnceDate(schedule).getTime(), Date.now() + 50);
|
|
1021
|
+
record.cron = new Cron(new Date(fireAt), { maxRuns: 1 }, callback);
|
|
1022
|
+
}
|
|
1023
|
+
jobs.set(name, record);
|
|
1024
|
+
if (isRecurring && options.immediate) Promise.resolve(execute(record)).catch((error) => {
|
|
1025
|
+
console.error(`c8y-nitro: job "${name}" immediate run failed`, error);
|
|
1026
|
+
});
|
|
1027
|
+
return toJobInfo(record);
|
|
1028
|
+
},
|
|
1029
|
+
triggerJob(name, options) {
|
|
1030
|
+
const record = jobs.get(name);
|
|
1031
|
+
if (!record) throw new Error(`Job "${name}" is not registered`);
|
|
1032
|
+
return execute(record, options);
|
|
1033
|
+
},
|
|
1034
|
+
cancelJob,
|
|
1035
|
+
listJobs() {
|
|
1036
|
+
return [...jobs.values()].map(toJobInfo);
|
|
1037
|
+
},
|
|
1038
|
+
getJob(name) {
|
|
1039
|
+
const record = jobs.get(name);
|
|
1040
|
+
return record ? toJobInfo(record) : void 0;
|
|
1041
|
+
},
|
|
1042
|
+
hasTask(name) {
|
|
1043
|
+
return tasks.has(name);
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
return api;
|
|
1049
1047
|
}
|
|
1050
1048
|
//#endregion
|
|
1051
|
-
export {
|
|
1049
|
+
export { c8yTasks, createError, createLogger, hasUserRequiredRole, isUserFromAllowedTenant, isUserFromDeployedTenant, useDeployedTenantClient, useDeployedTenantCredentials, useLogger, useSubscribedTenantClients, useSubscribedTenantCredentials, useTenantOption, useTenantOptions, useUser, useUserClient, useUserRoles, useUserTenantClient, useUserTenantCredentials };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8y-nitro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lightning fast Cumulocity IoT microservice development powered by Nitro",
|
|
6
6
|
"keywords": [
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"c12": "^4.0.0-beta.5",
|
|
42
42
|
"citty": "^0.2.2",
|
|
43
43
|
"consola": "^3.4.2",
|
|
44
|
+
"croner": "^10.0.1",
|
|
44
45
|
"evlog": "^2.21.0",
|
|
45
|
-
"itty-time": "^2.0.2",
|
|
46
46
|
"jszip": "^3.10.1",
|
|
47
47
|
"pathe": "^2.0.3",
|
|
48
48
|
"pkg-types": "^2.3.1",
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { c as getTenantOptionsByCategory, f as upsertTenantOption, i as deleteTenantOption, n as createBasicAuthHeader, p as createC8yManifest, s as getTenantOption } from "./c8y-api-BFjK6AYb.mjs";
|
|
2
|
-
import { n as validateBootstrapEnv, t as loadC8yConfig } from "./config-BVQHqlgV.mjs";
|
|
3
|
-
import { defineCommand } from "citty";
|
|
4
|
-
import { consola } from "consola";
|
|
5
|
-
//#region src/cli/commands/options.ts
|
|
6
|
-
var options_default = defineCommand({
|
|
7
|
-
meta: {
|
|
8
|
-
name: "options",
|
|
9
|
-
description: "Manage tenant options on the development tenant"
|
|
10
|
-
},
|
|
11
|
-
args: {},
|
|
12
|
-
async run() {
|
|
13
|
-
consola.info("Loading configuration...");
|
|
14
|
-
const { env, nitroConfig, configDir } = await loadC8yConfig();
|
|
15
|
-
consola.info("Validating environment variables...");
|
|
16
|
-
const envVars = validateBootstrapEnv(env);
|
|
17
|
-
consola.info("Loading manifest...");
|
|
18
|
-
const manifest = await createC8yManifest(configDir, nitroConfig);
|
|
19
|
-
const category = manifest.settingsCategory || manifest.contextPath || manifest.name;
|
|
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.success(`Using category: ${category}`);
|
|
22
|
-
const authHeader = createBasicAuthHeader(envVars.C8Y_DEVELOPMENT_TENANT, envVars.C8Y_DEVELOPMENT_USER, envVars.C8Y_DEVELOPMENT_PASSWORD);
|
|
23
|
-
consola.info("Fetching current tenant options...");
|
|
24
|
-
const currentOptions = await getTenantOptionsByCategory(envVars.C8Y_BASEURL, category, authHeader);
|
|
25
|
-
const availableKeys = manifest.settings.map((s) => s.key);
|
|
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
|
-
type: "select",
|
|
29
|
-
options: [
|
|
30
|
-
{
|
|
31
|
-
label: "Read option value",
|
|
32
|
-
value: "read"
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
label: "Update/Create option",
|
|
36
|
-
value: "update"
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
label: "Delete option(s)",
|
|
40
|
-
value: "delete"
|
|
41
|
-
}
|
|
42
|
-
]
|
|
43
|
-
});
|
|
44
|
-
if (action === "read") await handleRead(envVars.C8Y_BASEURL, category, authHeader, availableKeys, currentOptions);
|
|
45
|
-
else if (action === "update") await handleUpdate(envVars.C8Y_BASEURL, category, authHeader, availableKeys, currentOptions);
|
|
46
|
-
else if (action === "delete") await handleDelete(envVars.C8Y_BASEURL, category, authHeader, availableKeys, currentOptions);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
/**
|
|
50
|
-
* Handle reading a single option
|
|
51
|
-
* @param baseUrl - The Cumulocity base URL
|
|
52
|
-
* @param category - The tenant option category
|
|
53
|
-
* @param authHeader - The Basic Auth header
|
|
54
|
-
* @param availableKeys - List of available option keys
|
|
55
|
-
* @param currentOptions - Current option values from tenant
|
|
56
|
-
*/
|
|
57
|
-
async function handleRead(baseUrl, category, authHeader, availableKeys, currentOptions) {
|
|
58
|
-
const setKeys = availableKeys.filter((k) => !k.startsWith("credentials.") && currentOptions[k] !== void 0);
|
|
59
|
-
const credentialsKeys = availableKeys.filter((k) => k.startsWith("credentials."));
|
|
60
|
-
const allKeys = [...setKeys, ...credentialsKeys];
|
|
61
|
-
if (allKeys.length === 0) {
|
|
62
|
-
consola.warn("No options are currently set");
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (credentialsKeys.length > 0) consola.warn("Encrypted credentials.* options cannot be read in decrypted form with the development user in this CLI.");
|
|
66
|
-
const key = await consola.prompt("Select option to read:", {
|
|
67
|
-
type: "select",
|
|
68
|
-
options: allKeys.map((k) => ({
|
|
69
|
-
label: k.startsWith("credentials.") ? `${k} (unknown)` : k,
|
|
70
|
-
value: k
|
|
71
|
-
})),
|
|
72
|
-
cancel: "reject"
|
|
73
|
-
});
|
|
74
|
-
consola.info(`Reading option: ${key}`);
|
|
75
|
-
const value = await getTenantOption(baseUrl, category, key, authHeader);
|
|
76
|
-
if (value === void 0) consola.warn(`Option '${key}' is not set`);
|
|
77
|
-
else consola.success(`Value: ${value}`);
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Handle updating options (with loop for multiple updates)
|
|
81
|
-
* @param baseUrl - The Cumulocity base URL
|
|
82
|
-
* @param category - The tenant option category
|
|
83
|
-
* @param authHeader - The Basic Auth header
|
|
84
|
-
* @param availableKeys - List of available option keys
|
|
85
|
-
* @param currentOptions - Current option values from tenant
|
|
86
|
-
*/
|
|
87
|
-
async function handleUpdate(baseUrl, category, authHeader, availableKeys, currentOptions) {
|
|
88
|
-
let continueUpdating = true;
|
|
89
|
-
while (continueUpdating) {
|
|
90
|
-
const key = await consola.prompt("Select option to update:", {
|
|
91
|
-
type: "select",
|
|
92
|
-
options: availableKeys.map((k) => ({
|
|
93
|
-
label: currentOptions[k] !== void 0 ? `${k} (current: ${currentOptions[k]})` : k.startsWith("credentials.") ? `${k} (current: unknown)` : `${k} (not set)`,
|
|
94
|
-
value: k
|
|
95
|
-
})),
|
|
96
|
-
cancel: "reject"
|
|
97
|
-
});
|
|
98
|
-
const currentValue = currentOptions[key];
|
|
99
|
-
const newValue = await consola.prompt("Enter new value:", {
|
|
100
|
-
type: "text",
|
|
101
|
-
default: currentValue,
|
|
102
|
-
cancel: "reject"
|
|
103
|
-
});
|
|
104
|
-
consola.info(`Updating option: ${key}`);
|
|
105
|
-
await upsertTenantOption(baseUrl, category, key, newValue, authHeader);
|
|
106
|
-
currentOptions[key] = newValue;
|
|
107
|
-
consola.success(`Option '${key}' updated successfully`);
|
|
108
|
-
if (!await consola.prompt("Update another option?", {
|
|
109
|
-
type: "confirm",
|
|
110
|
-
initial: false,
|
|
111
|
-
cancel: "reject"
|
|
112
|
-
})) continueUpdating = false;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Handle deleting multiple options
|
|
117
|
-
* @param baseUrl - The Cumulocity base URL
|
|
118
|
-
* @param category - The tenant option category
|
|
119
|
-
* @param authHeader - The Basic Auth header
|
|
120
|
-
* @param availableKeys - List of available option keys
|
|
121
|
-
* @param currentOptions - Current option values from tenant
|
|
122
|
-
*/
|
|
123
|
-
async function handleDelete(baseUrl, category, authHeader, availableKeys, currentOptions) {
|
|
124
|
-
const setKeys = availableKeys.filter((k) => !k.startsWith("credentials.") && currentOptions[k] !== void 0);
|
|
125
|
-
const credentialsKeys = availableKeys.filter((k) => k.startsWith("credentials."));
|
|
126
|
-
const allKeys = [...setKeys, ...credentialsKeys];
|
|
127
|
-
if (allKeys.length === 0) {
|
|
128
|
-
consola.warn("No options are currently set");
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
const keysToDelete = await consola.prompt("Select option(s) to delete:", {
|
|
132
|
-
type: "multiselect",
|
|
133
|
-
options: allKeys,
|
|
134
|
-
required: true,
|
|
135
|
-
cancel: "reject"
|
|
136
|
-
});
|
|
137
|
-
consola.info(`Deleting ${keysToDelete.length} option(s)...`);
|
|
138
|
-
for (const key of keysToDelete) {
|
|
139
|
-
consola.info(`Deleting option: ${key}`);
|
|
140
|
-
await deleteTenantOption(baseUrl, category, key, authHeader);
|
|
141
|
-
delete currentOptions[key];
|
|
142
|
-
consola.success(`✓ Deleted: ${key}`);
|
|
143
|
-
}
|
|
144
|
-
consola.success("Delete operation completed");
|
|
145
|
-
}
|
|
146
|
-
//#endregion
|
|
147
|
-
export { options_default as default };
|