@eudiplo/cli 8.0.2 → 8.1.0-main.a6a67b3
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/README.md +6 -2
- package/dist/commands/deployment/action.d.ts +1 -1
- package/dist/commands/deployment/action.d.ts.map +1 -1
- package/dist/commands/deployment/action.js.map +1 -1
- package/dist/commands/deployment/index.d.ts.map +1 -1
- package/dist/commands/deployment/index.js +7 -4
- package/dist/commands/deployment/index.js.map +1 -1
- package/dist/commands/doctor/action.d.ts +2 -1
- package/dist/commands/doctor/action.d.ts.map +1 -1
- package/dist/commands/doctor/action.js +25 -7
- package/dist/commands/doctor/action.js.map +1 -1
- package/dist/commands/doctor/index.d.ts.map +1 -1
- package/dist/commands/doctor/index.js +2 -0
- package/dist/commands/doctor/index.js.map +1 -1
- package/dist/commands/open/action.d.ts +9 -0
- package/dist/commands/open/action.d.ts.map +1 -0
- package/dist/commands/open/action.js +41 -0
- package/dist/commands/open/action.js.map +1 -0
- package/dist/commands/open/index.d.ts +5 -0
- package/dist/commands/open/index.d.ts.map +1 -0
- package/dist/commands/open/index.js +15 -0
- package/dist/commands/open/index.js.map +1 -0
- package/dist/commands/upgrade/action.d.ts +3 -0
- package/dist/commands/upgrade/action.d.ts.map +1 -0
- package/dist/commands/upgrade/action.js +95 -0
- package/dist/commands/upgrade/action.js.map +1 -0
- package/dist/commands/upgrade/index.d.ts +5 -0
- package/dist/commands/upgrade/index.d.ts.map +1 -0
- package/dist/commands/upgrade/index.js +15 -0
- package/dist/commands/upgrade/index.js.map +1 -0
- package/dist/index.js +1397 -240
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +4 -0
- package/dist/runtime.js.map +1 -1
- package/dist/services/api-auth.d.ts +31 -0
- package/dist/services/api-auth.d.ts.map +1 -0
- package/dist/services/api-auth.js +78 -0
- package/dist/services/api-auth.js.map +1 -0
- package/dist/services/backend-checks.d.ts +24 -0
- package/dist/services/backend-checks.d.ts.map +1 -0
- package/dist/services/backend-checks.js +105 -0
- package/dist/services/backend-checks.js.map +1 -0
- package/dist/services/browser.d.ts +30 -0
- package/dist/services/browser.d.ts.map +1 -0
- package/dist/services/browser.js +102 -0
- package/dist/services/browser.js.map +1 -0
- package/dist/services/compose-args.d.ts +28 -0
- package/dist/services/compose-args.d.ts.map +1 -0
- package/dist/services/compose-args.js +117 -0
- package/dist/services/compose-args.js.map +1 -0
- package/dist/services/compose-project.js +6 -6
- package/dist/services/compose-project.js.map +1 -1
- package/dist/services/compose-upgrade.d.ts +40 -0
- package/dist/services/compose-upgrade.d.ts.map +1 -0
- package/dist/services/compose-upgrade.js +102 -0
- package/dist/services/compose-upgrade.js.map +1 -0
- package/dist/services/container-state.d.ts +14 -0
- package/dist/services/container-state.d.ts.map +1 -0
- package/dist/services/container-state.js +70 -0
- package/dist/services/container-state.js.map +1 -0
- package/dist/services/deployment-drivers.d.ts +8 -1
- package/dist/services/deployment-drivers.d.ts.map +1 -1
- package/dist/services/deployment-drivers.js +162 -19
- package/dist/services/deployment-drivers.js.map +1 -1
- package/dist/services/diagnostics.d.ts +19 -2
- package/dist/services/diagnostics.d.ts.map +1 -1
- package/dist/services/diagnostics.js +213 -49
- package/dist/services/diagnostics.js.map +1 -1
- package/dist/services/tls-expiry.d.ts +16 -0
- package/dist/services/tls-expiry.d.ts.map +1 -0
- package/dist/services/tls-expiry.js +80 -0
- package/dist/services/tls-expiry.js.map +1 -0
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/templates/docker-compose.yml +34 -27
- package/templates/schemas/KeyChainConfigFile.schema.json +7 -3
- package/templates/schemas/KeyChainImportDto.schema.json +7 -3
package/dist/index.js
CHANGED
|
@@ -2216,20 +2216,20 @@ var require_resolve = __commonJS({
|
|
|
2216
2216
|
return false;
|
|
2217
2217
|
}
|
|
2218
2218
|
function countKeys(schema) {
|
|
2219
|
-
let
|
|
2219
|
+
let count2 = 0;
|
|
2220
2220
|
for (const key in schema) {
|
|
2221
2221
|
if (key === "$ref")
|
|
2222
2222
|
return Infinity;
|
|
2223
|
-
|
|
2223
|
+
count2++;
|
|
2224
2224
|
if (SIMPLE_INLINED.has(key))
|
|
2225
2225
|
continue;
|
|
2226
2226
|
if (typeof schema[key] == "object") {
|
|
2227
|
-
(0, util_1.eachItem)(schema[key], (sch) =>
|
|
2227
|
+
(0, util_1.eachItem)(schema[key], (sch) => count2 += countKeys(sch));
|
|
2228
2228
|
}
|
|
2229
|
-
if (
|
|
2229
|
+
if (count2 === Infinity)
|
|
2230
2230
|
return Infinity;
|
|
2231
2231
|
}
|
|
2232
|
-
return
|
|
2232
|
+
return count2;
|
|
2233
2233
|
}
|
|
2234
2234
|
function getFullPath(resolver, id = "", normalize) {
|
|
2235
2235
|
if (normalize !== false)
|
|
@@ -2980,7 +2980,7 @@ var require_compile = __commonJS({
|
|
|
2980
2980
|
const schOrFunc = root.refs[ref];
|
|
2981
2981
|
if (schOrFunc)
|
|
2982
2982
|
return schOrFunc;
|
|
2983
|
-
let _sch =
|
|
2983
|
+
let _sch = resolve9.call(this, root, ref);
|
|
2984
2984
|
if (_sch === void 0) {
|
|
2985
2985
|
const schema = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
|
|
2986
2986
|
const { schemaId } = this.opts;
|
|
@@ -3007,7 +3007,7 @@ var require_compile = __commonJS({
|
|
|
3007
3007
|
function sameSchemaEnv(s1, s2) {
|
|
3008
3008
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3009
3009
|
}
|
|
3010
|
-
function
|
|
3010
|
+
function resolve9(root, ref) {
|
|
3011
3011
|
let sch;
|
|
3012
3012
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3013
3013
|
ref = sch;
|
|
@@ -3828,11 +3828,11 @@ var require_fast_uri = __commonJS({
|
|
|
3828
3828
|
normalizeString(uri, options);
|
|
3829
3829
|
} else if (typeof uri === "object") {
|
|
3830
3830
|
uri = /** @type {T} */
|
|
3831
|
-
|
|
3831
|
+
parse5(serialize(uri, options), options);
|
|
3832
3832
|
}
|
|
3833
3833
|
return uri;
|
|
3834
3834
|
}
|
|
3835
|
-
function
|
|
3835
|
+
function resolve9(baseURI, relativeURI, options) {
|
|
3836
3836
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3837
3837
|
const {
|
|
3838
3838
|
parsed: baseParsed,
|
|
@@ -3868,8 +3868,8 @@ var require_fast_uri = __commonJS({
|
|
|
3868
3868
|
function resolveComponent(base, relative3, options, skipNormalization) {
|
|
3869
3869
|
const target = {};
|
|
3870
3870
|
if (!skipNormalization) {
|
|
3871
|
-
base =
|
|
3872
|
-
relative3 =
|
|
3871
|
+
base = parse5(serialize(base, options), options);
|
|
3872
|
+
relative3 = parse5(serialize(relative3, options), options);
|
|
3873
3873
|
}
|
|
3874
3874
|
options = options || {};
|
|
3875
3875
|
if (!options.tolerant && relative3.scheme) {
|
|
@@ -4161,7 +4161,7 @@ var require_fast_uri = __commonJS({
|
|
|
4161
4161
|
}
|
|
4162
4162
|
return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
|
|
4163
4163
|
}
|
|
4164
|
-
function
|
|
4164
|
+
function parse5(uri, opts) {
|
|
4165
4165
|
return parseWithStatus(uri, opts).parsed;
|
|
4166
4166
|
}
|
|
4167
4167
|
function normalizeString(uri, opts) {
|
|
@@ -4194,11 +4194,11 @@ var require_fast_uri = __commonJS({
|
|
|
4194
4194
|
var fastUri = {
|
|
4195
4195
|
SCHEMES,
|
|
4196
4196
|
normalize,
|
|
4197
|
-
resolve:
|
|
4197
|
+
resolve: resolve9,
|
|
4198
4198
|
resolveComponent,
|
|
4199
4199
|
equal,
|
|
4200
4200
|
serialize,
|
|
4201
|
-
parse:
|
|
4201
|
+
parse: parse5
|
|
4202
4202
|
};
|
|
4203
4203
|
module.exports = fastUri;
|
|
4204
4204
|
module.exports.default = fastUri;
|
|
@@ -5699,8 +5699,8 @@ var require_contains = __commonJS({
|
|
|
5699
5699
|
cxt.result(valid, () => cxt.reset());
|
|
5700
5700
|
function validateItemsWithCount() {
|
|
5701
5701
|
const schValid = gen.name("_valid");
|
|
5702
|
-
const
|
|
5703
|
-
validateItems(schValid, () => gen.if(schValid, () => checkLimits(
|
|
5702
|
+
const count2 = gen.let("count", 0);
|
|
5703
|
+
validateItems(schValid, () => gen.if(schValid, () => checkLimits(count2)));
|
|
5704
5704
|
}
|
|
5705
5705
|
function validateItems(_valid, block) {
|
|
5706
5706
|
gen.forRange("i", 0, len, (i2) => {
|
|
@@ -5713,16 +5713,16 @@ var require_contains = __commonJS({
|
|
|
5713
5713
|
block();
|
|
5714
5714
|
});
|
|
5715
5715
|
}
|
|
5716
|
-
function checkLimits(
|
|
5717
|
-
gen.code((0, codegen_1._)`${
|
|
5716
|
+
function checkLimits(count2) {
|
|
5717
|
+
gen.code((0, codegen_1._)`${count2}++`);
|
|
5718
5718
|
if (max2 === void 0) {
|
|
5719
|
-
gen.if((0, codegen_1._)`${
|
|
5719
|
+
gen.if((0, codegen_1._)`${count2} >= ${min}`, () => gen.assign(valid, true).break());
|
|
5720
5720
|
} else {
|
|
5721
|
-
gen.if((0, codegen_1._)`${
|
|
5721
|
+
gen.if((0, codegen_1._)`${count2} > ${max2}`, () => gen.assign(valid, false).break());
|
|
5722
5722
|
if (min === 1)
|
|
5723
5723
|
gen.assign(valid, true);
|
|
5724
5724
|
else
|
|
5725
|
-
gen.if((0, codegen_1._)`${
|
|
5725
|
+
gen.if((0, codegen_1._)`${count2} >= ${min}`, () => gen.assign(valid, true));
|
|
5726
5726
|
}
|
|
5727
5727
|
}
|
|
5728
5728
|
}
|
|
@@ -12065,10 +12065,10 @@ import { isAbsolute as isAbsolute2, join as join2, relative, resolve as resolve2
|
|
|
12065
12065
|
function createScanner(text, ignoreTrivia = false) {
|
|
12066
12066
|
const len = text.length;
|
|
12067
12067
|
let pos = 0, value = "", tokenOffset = 0, token = 16, lineNumber = 0, lineStartOffset = 0, tokenLineStartOffset = 0, prevTokenLineStartOffset = 0, scanError = 0;
|
|
12068
|
-
function scanHexDigits(
|
|
12068
|
+
function scanHexDigits(count2, exact) {
|
|
12069
12069
|
let digits = 0;
|
|
12070
12070
|
let value2 = 0;
|
|
12071
|
-
while (digits <
|
|
12071
|
+
while (digits < count2 || !exact) {
|
|
12072
12072
|
let ch = text.charCodeAt(pos);
|
|
12073
12073
|
if (ch >= 48 && ch <= 57) {
|
|
12074
12074
|
value2 = value2 * 16 + ch - 48;
|
|
@@ -12082,7 +12082,7 @@ function createScanner(text, ignoreTrivia = false) {
|
|
|
12082
12082
|
pos++;
|
|
12083
12083
|
digits++;
|
|
12084
12084
|
}
|
|
12085
|
-
if (digits <
|
|
12085
|
+
if (digits < count2) {
|
|
12086
12086
|
value2 = -1;
|
|
12087
12087
|
}
|
|
12088
12088
|
return value2;
|
|
@@ -12707,9 +12707,9 @@ function format(documentText, range, options) {
|
|
|
12707
12707
|
}
|
|
12708
12708
|
return editOperations;
|
|
12709
12709
|
}
|
|
12710
|
-
function repeat(s,
|
|
12710
|
+
function repeat(s, count2) {
|
|
12711
12711
|
let result = "";
|
|
12712
|
-
for (let i2 = 0; i2 <
|
|
12712
|
+
for (let i2 = 0; i2 < count2; i2++) {
|
|
12713
12713
|
result += s;
|
|
12714
12714
|
}
|
|
12715
12715
|
return result;
|
|
@@ -15260,12 +15260,43 @@ function assertConfigBundle(input) {
|
|
|
15260
15260
|
}
|
|
15261
15261
|
|
|
15262
15262
|
// ../../packages/eudiplo-config-format/dist/config-values.js
|
|
15263
|
+
var isVariableChar = (char) => char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "_";
|
|
15264
|
+
function replacePlaceholders(input, replace) {
|
|
15265
|
+
let output = "";
|
|
15266
|
+
let copied = 0;
|
|
15267
|
+
let start = input.indexOf("${");
|
|
15268
|
+
while (start !== -1) {
|
|
15269
|
+
let cursor = start + 2;
|
|
15270
|
+
while (cursor < input.length && isVariableChar(input[cursor]))
|
|
15271
|
+
cursor++;
|
|
15272
|
+
const variable = input.slice(start + 2, cursor);
|
|
15273
|
+
let end = -1;
|
|
15274
|
+
let fallback;
|
|
15275
|
+
if (variable && input[cursor] === "}")
|
|
15276
|
+
end = cursor;
|
|
15277
|
+
else if (variable && input[cursor] === ":") {
|
|
15278
|
+
end = input.indexOf("}", cursor + 1);
|
|
15279
|
+
if (end === -1)
|
|
15280
|
+
break;
|
|
15281
|
+
fallback = input.slice(cursor + 1, end);
|
|
15282
|
+
}
|
|
15283
|
+
if (end === -1) {
|
|
15284
|
+
start = input.indexOf("${", start + 2);
|
|
15285
|
+
continue;
|
|
15286
|
+
}
|
|
15287
|
+
output += input.slice(copied, start);
|
|
15288
|
+
output += replace(input.slice(start, end + 1), variable, fallback);
|
|
15289
|
+
copied = end + 1;
|
|
15290
|
+
start = input.indexOf("${", copied);
|
|
15291
|
+
}
|
|
15292
|
+
return output + input.slice(copied);
|
|
15293
|
+
}
|
|
15263
15294
|
function resolveConfigVariables(input, env) {
|
|
15264
15295
|
const issues = [];
|
|
15265
15296
|
const seen = /* @__PURE__ */ new WeakMap();
|
|
15266
15297
|
function visit2(value, path2) {
|
|
15267
15298
|
if (typeof value === "string")
|
|
15268
|
-
return value
|
|
15299
|
+
return replacePlaceholders(value, (match, variable, fallback) => {
|
|
15269
15300
|
if (env[variable] !== void 0 && env[variable] !== "")
|
|
15270
15301
|
return env[variable];
|
|
15271
15302
|
if (fallback !== void 0)
|
|
@@ -20592,17 +20623,17 @@ function storageEnv(storage) {
|
|
|
20592
20623
|
if (storage === "local") {
|
|
20593
20624
|
return ["STORAGE_DRIVER=local", "LOCAL_STORAGE_DIR=/app/uploads"];
|
|
20594
20625
|
}
|
|
20595
|
-
const
|
|
20626
|
+
const rustfsPassword = randomSecret();
|
|
20596
20627
|
return [
|
|
20597
20628
|
"STORAGE_DRIVER=s3",
|
|
20598
|
-
"S3_ENDPOINT=http://
|
|
20629
|
+
"S3_ENDPOINT=http://rustfs:9000",
|
|
20599
20630
|
"S3_REGION=us-east-1",
|
|
20600
|
-
"S3_ACCESS_KEY_ID=
|
|
20601
|
-
`S3_SECRET_ACCESS_KEY=${
|
|
20631
|
+
"S3_ACCESS_KEY_ID=rustfsadmin",
|
|
20632
|
+
`S3_SECRET_ACCESS_KEY=${rustfsPassword}`,
|
|
20602
20633
|
"S3_BUCKET=uploads",
|
|
20603
20634
|
"S3_FORCE_PATH_STYLE=true",
|
|
20604
|
-
"
|
|
20605
|
-
`
|
|
20635
|
+
"RUSTFS_ACCESS_KEY=rustfsadmin",
|
|
20636
|
+
`RUSTFS_SECRET_KEY=${rustfsPassword}`
|
|
20606
20637
|
];
|
|
20607
20638
|
}
|
|
20608
20639
|
function kmsEnv(kms) {
|
|
@@ -20755,10 +20786,10 @@ function formatTextReport(scope, rootPath, results) {
|
|
|
20755
20786
|
function formatTenantBlock(result) {
|
|
20756
20787
|
const lines = [`${result.valid ? "PASS" : "FAIL"} ${result.id}`];
|
|
20757
20788
|
if (result.valid) {
|
|
20758
|
-
for (const [resourceType,
|
|
20789
|
+
for (const [resourceType, count2] of Object.entries(
|
|
20759
20790
|
result.resourceCounts
|
|
20760
20791
|
)) {
|
|
20761
|
-
lines.push(` ${
|
|
20792
|
+
lines.push(` ${count2} ${pluralize(resourceType, count2)}`);
|
|
20762
20793
|
}
|
|
20763
20794
|
} else {
|
|
20764
20795
|
lines.push(...formatTenantErrors(result.errors));
|
|
@@ -20804,8 +20835,8 @@ function groupErrorsByFile(errors) {
|
|
|
20804
20835
|
}
|
|
20805
20836
|
return Array.from(grouped.entries());
|
|
20806
20837
|
}
|
|
20807
|
-
function pluralize(label,
|
|
20808
|
-
return
|
|
20838
|
+
function pluralize(label, count2) {
|
|
20839
|
+
return count2 === 1 ? label : `${label}s`;
|
|
20809
20840
|
}
|
|
20810
20841
|
|
|
20811
20842
|
// src/commands/config/validate/validator.ts
|
|
@@ -21550,8 +21581,169 @@ function createConfigCommand(context, setExitCode) {
|
|
|
21550
21581
|
|
|
21551
21582
|
// src/services/deployment-drivers.ts
|
|
21552
21583
|
import { access as access2, mkdir as mkdir6, rm as rm4, stat as stat2, unlink as unlink2, writeFile as writeFile5 } from "node:fs/promises";
|
|
21553
|
-
import { delimiter, join as join10, resolve as
|
|
21584
|
+
import { delimiter, join as join10, resolve as resolve6 } from "node:path";
|
|
21554
21585
|
import { spawn } from "node:child_process";
|
|
21586
|
+
|
|
21587
|
+
// src/services/compose-args.ts
|
|
21588
|
+
import { resolve as resolve5 } from "node:path";
|
|
21589
|
+
var SERVICE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
|
|
21590
|
+
var DURATION = /^(\d+(\.\d+)?(ns|us|µs|ms|s|m|h))+$/;
|
|
21591
|
+
var TIMESTAMP = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})?)?$/;
|
|
21592
|
+
function assertServiceName(service) {
|
|
21593
|
+
if (!SERVICE_NAME.test(service)) {
|
|
21594
|
+
throw new Error(
|
|
21595
|
+
`Invalid service name ${JSON.stringify(service)}. Service names start with a letter or digit and contain only letters, digits, ".", "_" and "-".`
|
|
21596
|
+
);
|
|
21597
|
+
}
|
|
21598
|
+
}
|
|
21599
|
+
function assertSince(since) {
|
|
21600
|
+
const isDuration = DURATION.test(since);
|
|
21601
|
+
const isTimestamp = TIMESTAMP.test(since) && !Number.isNaN(Date.parse(since));
|
|
21602
|
+
if (!isDuration && !isTimestamp) {
|
|
21603
|
+
throw new Error(
|
|
21604
|
+
`Invalid --since value ${JSON.stringify(since)}. Use a duration such as 10m or 2h30m, or a timestamp such as 2026-09-01T12:00:00Z.`
|
|
21605
|
+
);
|
|
21606
|
+
}
|
|
21607
|
+
}
|
|
21608
|
+
function assertTail(tail) {
|
|
21609
|
+
if (!Number.isInteger(tail) || tail < 0) {
|
|
21610
|
+
throw new Error("--tail must be a whole number of lines, 0 or more.");
|
|
21611
|
+
}
|
|
21612
|
+
}
|
|
21613
|
+
function buildComposeBaseArgs(instance, projectDirectory, composeFiles) {
|
|
21614
|
+
const args = ["compose"];
|
|
21615
|
+
if (instance.envFile) {
|
|
21616
|
+
args.push("--env-file", resolve5(projectDirectory, instance.envFile));
|
|
21617
|
+
}
|
|
21618
|
+
for (const composeFile of composeFiles) {
|
|
21619
|
+
args.push("-f", resolve5(projectDirectory, composeFile));
|
|
21620
|
+
}
|
|
21621
|
+
for (const profile of instance.composeProfiles ?? []) {
|
|
21622
|
+
args.push("--profile", profile);
|
|
21623
|
+
}
|
|
21624
|
+
if (instance.projectName) {
|
|
21625
|
+
args.push("--project-name", instance.projectName);
|
|
21626
|
+
}
|
|
21627
|
+
return args;
|
|
21628
|
+
}
|
|
21629
|
+
function buildComposePsArgs() {
|
|
21630
|
+
return ["ps"];
|
|
21631
|
+
}
|
|
21632
|
+
function buildComposeListServicesArgs() {
|
|
21633
|
+
return ["config", "--services"];
|
|
21634
|
+
}
|
|
21635
|
+
function buildComposeLogsArgs(service, options = {}) {
|
|
21636
|
+
const args = ["logs"];
|
|
21637
|
+
if (options.follow === true) {
|
|
21638
|
+
args.push("--follow");
|
|
21639
|
+
}
|
|
21640
|
+
if (options.tail !== void 0) {
|
|
21641
|
+
assertTail(options.tail);
|
|
21642
|
+
args.push("--tail", String(options.tail));
|
|
21643
|
+
}
|
|
21644
|
+
if (options.since !== void 0) {
|
|
21645
|
+
assertSince(options.since);
|
|
21646
|
+
args.push("--since", options.since);
|
|
21647
|
+
}
|
|
21648
|
+
if (service !== void 0) {
|
|
21649
|
+
assertServiceName(service);
|
|
21650
|
+
args.push(service);
|
|
21651
|
+
}
|
|
21652
|
+
return args;
|
|
21653
|
+
}
|
|
21654
|
+
function buildComposePullArgs(service) {
|
|
21655
|
+
const args = ["pull"];
|
|
21656
|
+
if (service !== void 0) {
|
|
21657
|
+
assertServiceName(service);
|
|
21658
|
+
args.push(service);
|
|
21659
|
+
}
|
|
21660
|
+
return args;
|
|
21661
|
+
}
|
|
21662
|
+
function buildComposeUpArgs() {
|
|
21663
|
+
return ["up", "-d"];
|
|
21664
|
+
}
|
|
21665
|
+
function buildComposeRestartArgs(service) {
|
|
21666
|
+
const args = ["restart"];
|
|
21667
|
+
if (service !== void 0) {
|
|
21668
|
+
assertServiceName(service);
|
|
21669
|
+
args.push(service);
|
|
21670
|
+
}
|
|
21671
|
+
return args;
|
|
21672
|
+
}
|
|
21673
|
+
function parseServiceList(stdout) {
|
|
21674
|
+
return stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => SERVICE_NAME.test(line)).sort();
|
|
21675
|
+
}
|
|
21676
|
+
function assertKnownService(service, known) {
|
|
21677
|
+
if (!known.includes(service)) {
|
|
21678
|
+
throw new Error(
|
|
21679
|
+
`Unknown service ${service}. Services in this project: ${known.join(", ") || "none"}.`
|
|
21680
|
+
);
|
|
21681
|
+
}
|
|
21682
|
+
}
|
|
21683
|
+
|
|
21684
|
+
// src/services/container-state.ts
|
|
21685
|
+
function parseContainerStates(stdout) {
|
|
21686
|
+
const trimmed = stdout.trim();
|
|
21687
|
+
if (!trimmed) {
|
|
21688
|
+
return [];
|
|
21689
|
+
}
|
|
21690
|
+
const entries = trimmed.startsWith("[") ? asArray(JSON.parse(trimmed)) : trimmed.split("\n").map((line) => line.trim()).filter((line) => line.startsWith("{")).map((line) => JSON.parse(line));
|
|
21691
|
+
return entries.map((entry) => {
|
|
21692
|
+
const record = entry;
|
|
21693
|
+
const names = record.Names;
|
|
21694
|
+
return {
|
|
21695
|
+
name: Array.isArray(names) ? String(names[0] ?? "") : String(names ?? record.Name ?? ""),
|
|
21696
|
+
state: String(record.State ?? ""),
|
|
21697
|
+
status: String(record.Status ?? "")
|
|
21698
|
+
};
|
|
21699
|
+
});
|
|
21700
|
+
}
|
|
21701
|
+
function summarizeContainerStates(containers, projectName) {
|
|
21702
|
+
const name = "service containers";
|
|
21703
|
+
if (containers.length === 0) {
|
|
21704
|
+
return {
|
|
21705
|
+
name,
|
|
21706
|
+
status: "warn",
|
|
21707
|
+
message: `No containers found for project ${projectName}. Start it with eudiplo up.`
|
|
21708
|
+
};
|
|
21709
|
+
}
|
|
21710
|
+
const unhealthy = containers.filter(
|
|
21711
|
+
(container) => container.status.toLowerCase().includes("unhealthy")
|
|
21712
|
+
);
|
|
21713
|
+
if (unhealthy.length > 0) {
|
|
21714
|
+
return {
|
|
21715
|
+
name,
|
|
21716
|
+
status: "fail",
|
|
21717
|
+
message: `Unhealthy: ${describe(unhealthy)}`
|
|
21718
|
+
};
|
|
21719
|
+
}
|
|
21720
|
+
const notRunning = containers.filter(
|
|
21721
|
+
(container) => container.state.toLowerCase() !== "running"
|
|
21722
|
+
);
|
|
21723
|
+
if (notRunning.length > 0) {
|
|
21724
|
+
return {
|
|
21725
|
+
name,
|
|
21726
|
+
status: "warn",
|
|
21727
|
+
message: `${containers.length - notRunning.length}/${containers.length} running. Not running: ${describe(notRunning)}`
|
|
21728
|
+
};
|
|
21729
|
+
}
|
|
21730
|
+
const starting = containers.filter(
|
|
21731
|
+
(container) => container.status.toLowerCase().includes("starting")
|
|
21732
|
+
).length;
|
|
21733
|
+
return {
|
|
21734
|
+
name,
|
|
21735
|
+
status: "pass",
|
|
21736
|
+
message: `${containers.length} container(s) running${starting > 0 ? `, ${starting} still starting` : ""}.`
|
|
21737
|
+
};
|
|
21738
|
+
}
|
|
21739
|
+
function asArray(value) {
|
|
21740
|
+
return Array.isArray(value) ? value : [];
|
|
21741
|
+
}
|
|
21742
|
+
function describe(containers) {
|
|
21743
|
+
return containers.map((container) => `${container.name} (${container.status})`).join(", ");
|
|
21744
|
+
}
|
|
21745
|
+
|
|
21746
|
+
// src/services/deployment-drivers.ts
|
|
21555
21747
|
var drivers = {
|
|
21556
21748
|
compose: {
|
|
21557
21749
|
target: "compose",
|
|
@@ -21564,10 +21756,16 @@ var drivers = {
|
|
|
21564
21756
|
status: runtime ? "pass" : "warn",
|
|
21565
21757
|
message: runtime ? `${runtime.name} found at ${runtime.command}` : "Docker or Podman was not found in a supported install location."
|
|
21566
21758
|
});
|
|
21759
|
+
if (runtime) {
|
|
21760
|
+
checks.push(await composeProviderCheck(runtime, context));
|
|
21761
|
+
checks.push(
|
|
21762
|
+
await composeServicesCheck(runtime, instance, context)
|
|
21763
|
+
);
|
|
21764
|
+
}
|
|
21567
21765
|
for (const composeFile of getComposeFiles(instance)) {
|
|
21568
21766
|
let found = true;
|
|
21569
21767
|
try {
|
|
21570
|
-
await access2(
|
|
21768
|
+
await access2(resolve6(projectDirectory, composeFile));
|
|
21571
21769
|
} catch {
|
|
21572
21770
|
found = false;
|
|
21573
21771
|
}
|
|
@@ -21585,8 +21783,41 @@ var drivers = {
|
|
|
21585
21783
|
down(options) {
|
|
21586
21784
|
return runCompose(["down", ...options.args], options);
|
|
21587
21785
|
},
|
|
21588
|
-
logs(options) {
|
|
21589
|
-
|
|
21786
|
+
async logs(options) {
|
|
21787
|
+
const service = await resolveComposeService(options);
|
|
21788
|
+
const { follow, tail } = readLogOptions(options.flags);
|
|
21789
|
+
const since = typeof options.flags.since === "string" ? options.flags.since : void 0;
|
|
21790
|
+
return runCompose(
|
|
21791
|
+
[
|
|
21792
|
+
...buildComposeLogsArgs(service, { follow, tail, since }),
|
|
21793
|
+
...options.args
|
|
21794
|
+
],
|
|
21795
|
+
options
|
|
21796
|
+
);
|
|
21797
|
+
},
|
|
21798
|
+
async ps(options) {
|
|
21799
|
+
return runCompose(
|
|
21800
|
+
[...buildComposePsArgs(), ...options.args],
|
|
21801
|
+
options
|
|
21802
|
+
);
|
|
21803
|
+
},
|
|
21804
|
+
async pull(options) {
|
|
21805
|
+
const service = await resolveComposeService(options);
|
|
21806
|
+
return runCompose(
|
|
21807
|
+
[...buildComposePullArgs(service), ...options.args],
|
|
21808
|
+
options
|
|
21809
|
+
);
|
|
21810
|
+
},
|
|
21811
|
+
async restart(options) {
|
|
21812
|
+
assertWritable(options);
|
|
21813
|
+
const service = await resolveComposeService(options);
|
|
21814
|
+
const restartArgs = buildComposeRestartArgs(service);
|
|
21815
|
+
const runtime = await resolveComposeRuntime(options.context.env);
|
|
21816
|
+
options.context.stdout.write(
|
|
21817
|
+
`Restarting ${service ?? "all services"} of ${options.instanceName}${runtime ? ` with ${runtime.name} compose` : ""}
|
|
21818
|
+
`
|
|
21819
|
+
);
|
|
21820
|
+
return runCompose([...restartArgs, ...options.args], options);
|
|
21590
21821
|
}
|
|
21591
21822
|
},
|
|
21592
21823
|
external: {
|
|
@@ -21606,7 +21837,11 @@ var drivers = {
|
|
|
21606
21837
|
);
|
|
21607
21838
|
return runKubectl(
|
|
21608
21839
|
[
|
|
21609
|
-
...buildLogsArgs(
|
|
21840
|
+
...buildLogsArgs(
|
|
21841
|
+
scope,
|
|
21842
|
+
workload,
|
|
21843
|
+
readLogOptions(options.flags)
|
|
21844
|
+
),
|
|
21610
21845
|
...options.args
|
|
21611
21846
|
],
|
|
21612
21847
|
options
|
|
@@ -21634,14 +21869,17 @@ var drivers = {
|
|
|
21634
21869
|
[...buildRestartArgs(scope, workload), ...options.args],
|
|
21635
21870
|
options
|
|
21636
21871
|
);
|
|
21637
|
-
if (restarted !== 0 || options.flags
|
|
21872
|
+
if (restarted !== 0 || options.flags["no-wait"] === true) {
|
|
21638
21873
|
return restarted;
|
|
21639
21874
|
}
|
|
21640
21875
|
return runKubectl(buildRolloutStatusArgs(scope, workload), options);
|
|
21641
21876
|
}
|
|
21642
21877
|
}
|
|
21643
21878
|
};
|
|
21644
|
-
function assertWritable({
|
|
21879
|
+
function assertWritable({
|
|
21880
|
+
instance,
|
|
21881
|
+
instanceName
|
|
21882
|
+
}) {
|
|
21645
21883
|
if (instance.readOnly === true) {
|
|
21646
21884
|
throw new Error(
|
|
21647
21885
|
`Instance ${instanceName} is registered read-only. Re-register it without --read-only to allow changes.`
|
|
@@ -21847,7 +22085,10 @@ function unreadyEndpoints(stdout) {
|
|
|
21847
22085
|
(endpoint) => isRecord3(endpoint) && // An omitted ready condition means ready, per the API contract.
|
|
21848
22086
|
(!isRecord3(endpoint.conditions) || endpoint.conditions.ready !== false)
|
|
21849
22087
|
);
|
|
21850
|
-
readyByService.set(
|
|
22088
|
+
readyByService.set(
|
|
22089
|
+
service,
|
|
22090
|
+
(readyByService.get(service) ?? false) || ready
|
|
22091
|
+
);
|
|
21851
22092
|
}
|
|
21852
22093
|
return [...readyByService.entries()].filter(([, ready]) => !ready).map(([service]) => service).sort();
|
|
21853
22094
|
}
|
|
@@ -21975,7 +22216,7 @@ async function ensureComposeProject(cwd, options) {
|
|
|
21975
22216
|
composeProfiles: composeProfiles(options),
|
|
21976
22217
|
envFile: envFileName,
|
|
21977
22218
|
projectName: mode === "demo" ? "eudiplo-demo" : "eudiplo",
|
|
21978
|
-
projectDirectory:
|
|
22219
|
+
projectDirectory: resolve6(cwd)
|
|
21979
22220
|
};
|
|
21980
22221
|
}
|
|
21981
22222
|
async function ensureProjectDirectory(directory) {
|
|
@@ -22029,23 +22270,14 @@ function unsupportedCommand(command, target) {
|
|
|
22029
22270
|
}
|
|
22030
22271
|
async function runCompose(args, { instance, context }) {
|
|
22031
22272
|
const projectDirectory = instance.projectDirectory ?? context.cwd;
|
|
22032
|
-
const composeArgs = [
|
|
22033
|
-
|
|
22034
|
-
|
|
22035
|
-
|
|
22036
|
-
|
|
22037
|
-
)
|
|
22038
|
-
|
|
22039
|
-
|
|
22040
|
-
composeArgs.push("-f", resolve5(projectDirectory, composeFile));
|
|
22041
|
-
}
|
|
22042
|
-
for (const profile of instance.composeProfiles ?? []) {
|
|
22043
|
-
composeArgs.push("--profile", profile);
|
|
22044
|
-
}
|
|
22045
|
-
if (instance.projectName) {
|
|
22046
|
-
composeArgs.push("--project-name", instance.projectName);
|
|
22047
|
-
}
|
|
22048
|
-
composeArgs.push(...args);
|
|
22273
|
+
const composeArgs = [
|
|
22274
|
+
...buildComposeBaseArgs(
|
|
22275
|
+
instance,
|
|
22276
|
+
projectDirectory,
|
|
22277
|
+
getComposeFiles(instance)
|
|
22278
|
+
),
|
|
22279
|
+
...args
|
|
22280
|
+
];
|
|
22049
22281
|
const composeRuntime = await resolveComposeRuntime(context.env);
|
|
22050
22282
|
if (!composeRuntime) {
|
|
22051
22283
|
context.stderr.write(
|
|
@@ -22067,6 +22299,147 @@ async function runCompose(args, { instance, context }) {
|
|
|
22067
22299
|
child.on("close", (code) => resolveProcess(code ?? 1));
|
|
22068
22300
|
});
|
|
22069
22301
|
}
|
|
22302
|
+
async function captureCompose(args, { instance, context }) {
|
|
22303
|
+
const projectDirectory = instance.projectDirectory ?? context.cwd;
|
|
22304
|
+
const composeRuntime = await resolveComposeRuntime(context.env);
|
|
22305
|
+
if (!composeRuntime) {
|
|
22306
|
+
throw new Error(
|
|
22307
|
+
"Docker or Podman was not found in a supported install location."
|
|
22308
|
+
);
|
|
22309
|
+
}
|
|
22310
|
+
const composeArgs = [
|
|
22311
|
+
...buildComposeBaseArgs(
|
|
22312
|
+
instance,
|
|
22313
|
+
projectDirectory,
|
|
22314
|
+
getComposeFiles(instance)
|
|
22315
|
+
),
|
|
22316
|
+
...args
|
|
22317
|
+
];
|
|
22318
|
+
return new Promise((resolveProcess) => {
|
|
22319
|
+
const child = spawn(composeRuntime.command, composeArgs, {
|
|
22320
|
+
cwd: projectDirectory,
|
|
22321
|
+
env: context.env,
|
|
22322
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
22323
|
+
});
|
|
22324
|
+
let stdout = "";
|
|
22325
|
+
let stderr = "";
|
|
22326
|
+
child.stdout?.on("data", (chunk) => {
|
|
22327
|
+
stdout += String(chunk);
|
|
22328
|
+
});
|
|
22329
|
+
child.stderr?.on("data", (chunk) => {
|
|
22330
|
+
stderr += String(chunk);
|
|
22331
|
+
});
|
|
22332
|
+
child.on(
|
|
22333
|
+
"error",
|
|
22334
|
+
(error) => resolveProcess({ code: 1, stdout, stderr: error.message })
|
|
22335
|
+
);
|
|
22336
|
+
child.on(
|
|
22337
|
+
"close",
|
|
22338
|
+
(code) => resolveProcess({ code: code ?? 1, stdout, stderr })
|
|
22339
|
+
);
|
|
22340
|
+
});
|
|
22341
|
+
}
|
|
22342
|
+
async function resolveComposeService(options) {
|
|
22343
|
+
const service = readServiceFlag(options.flags);
|
|
22344
|
+
if (service === void 0) {
|
|
22345
|
+
return void 0;
|
|
22346
|
+
}
|
|
22347
|
+
assertServiceName(service);
|
|
22348
|
+
const listed = await captureCompose(
|
|
22349
|
+
buildComposeListServicesArgs(),
|
|
22350
|
+
options
|
|
22351
|
+
);
|
|
22352
|
+
if (listed.code !== 0) {
|
|
22353
|
+
throw new Error(
|
|
22354
|
+
`Could not list Compose services: ${listed.stderr.trim() || `exit code ${listed.code}`}`
|
|
22355
|
+
);
|
|
22356
|
+
}
|
|
22357
|
+
assertKnownService(service, parseServiceList(listed.stdout));
|
|
22358
|
+
return service;
|
|
22359
|
+
}
|
|
22360
|
+
async function composeProviderCheck(runtime, context) {
|
|
22361
|
+
const version = await captureRuntime(
|
|
22362
|
+
runtime.command,
|
|
22363
|
+
["compose", "version"],
|
|
22364
|
+
context
|
|
22365
|
+
);
|
|
22366
|
+
if (version.code !== 0) {
|
|
22367
|
+
return {
|
|
22368
|
+
name: "compose provider",
|
|
22369
|
+
status: "fail",
|
|
22370
|
+
message: `${runtime.name} compose is not usable: ${firstLine(version.stderr) || `exit code ${version.code}`}`
|
|
22371
|
+
};
|
|
22372
|
+
}
|
|
22373
|
+
return {
|
|
22374
|
+
name: "compose provider",
|
|
22375
|
+
status: "pass",
|
|
22376
|
+
message: firstLine(version.stdout) || `${runtime.name} compose works`
|
|
22377
|
+
};
|
|
22378
|
+
}
|
|
22379
|
+
async function composeServicesCheck(runtime, instance, context) {
|
|
22380
|
+
if (!instance.projectName) {
|
|
22381
|
+
return {
|
|
22382
|
+
name: "service containers",
|
|
22383
|
+
status: "skip",
|
|
22384
|
+
message: "This instance has no Compose project name, so its containers cannot be identified."
|
|
22385
|
+
};
|
|
22386
|
+
}
|
|
22387
|
+
const listed = await captureRuntime(
|
|
22388
|
+
runtime.command,
|
|
22389
|
+
[
|
|
22390
|
+
"ps",
|
|
22391
|
+
"--all",
|
|
22392
|
+
"--filter",
|
|
22393
|
+
`label=com.docker.compose.project=${instance.projectName}`,
|
|
22394
|
+
"--format",
|
|
22395
|
+
"json"
|
|
22396
|
+
],
|
|
22397
|
+
context
|
|
22398
|
+
);
|
|
22399
|
+
if (listed.code !== 0) {
|
|
22400
|
+
return {
|
|
22401
|
+
name: "service containers",
|
|
22402
|
+
status: "warn",
|
|
22403
|
+
message: `Containers could not be listed: ${firstLine(listed.stderr) || `exit code ${listed.code}`}`
|
|
22404
|
+
};
|
|
22405
|
+
}
|
|
22406
|
+
try {
|
|
22407
|
+
return summarizeContainerStates(
|
|
22408
|
+
parseContainerStates(listed.stdout),
|
|
22409
|
+
instance.projectName
|
|
22410
|
+
);
|
|
22411
|
+
} catch {
|
|
22412
|
+
return {
|
|
22413
|
+
name: "service containers",
|
|
22414
|
+
status: "warn",
|
|
22415
|
+
message: `The container list returned by ${runtime.name} could not be read.`
|
|
22416
|
+
};
|
|
22417
|
+
}
|
|
22418
|
+
}
|
|
22419
|
+
function captureRuntime(command, args, context) {
|
|
22420
|
+
return new Promise((resolveProcess) => {
|
|
22421
|
+
const child = spawn(command, args, {
|
|
22422
|
+
env: context.env,
|
|
22423
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
22424
|
+
});
|
|
22425
|
+
let stdout = "";
|
|
22426
|
+
let stderr = "";
|
|
22427
|
+
child.stdout?.on("data", (chunk) => {
|
|
22428
|
+
stdout += String(chunk);
|
|
22429
|
+
});
|
|
22430
|
+
child.stderr?.on("data", (chunk) => {
|
|
22431
|
+
stderr += String(chunk);
|
|
22432
|
+
});
|
|
22433
|
+
child.on(
|
|
22434
|
+
"error",
|
|
22435
|
+
(error) => resolveProcess({ code: 1, stdout, stderr: error.message })
|
|
22436
|
+
);
|
|
22437
|
+
child.on(
|
|
22438
|
+
"close",
|
|
22439
|
+
(code) => resolveProcess({ code: code ?? 1, stdout, stderr })
|
|
22440
|
+
);
|
|
22441
|
+
});
|
|
22442
|
+
}
|
|
22070
22443
|
async function resolveComposeRuntime(env) {
|
|
22071
22444
|
const preferredRuntime = parsePreferredRuntime(
|
|
22072
22445
|
env.EUDIPLO_CONTAINER_RUNTIME
|
|
@@ -22164,7 +22537,7 @@ async function removeIfExists(path2) {
|
|
|
22164
22537
|
}
|
|
22165
22538
|
|
|
22166
22539
|
// src/services/project-directory.ts
|
|
22167
|
-
import { resolve as
|
|
22540
|
+
import { resolve as resolve7 } from "node:path";
|
|
22168
22541
|
function resolveProjectDirectory(parsed, context, promptedDirectory) {
|
|
22169
22542
|
const positionalDirectory = parsed.subject;
|
|
22170
22543
|
const flagDirectory = readStringFlag(parsed.flags, "directory");
|
|
@@ -22176,7 +22549,7 @@ function resolveProjectDirectory(parsed, context, promptedDirectory) {
|
|
|
22176
22549
|
"Specify the project directory either positionally or with --directory."
|
|
22177
22550
|
);
|
|
22178
22551
|
}
|
|
22179
|
-
return
|
|
22552
|
+
return resolve7(
|
|
22180
22553
|
context.cwd,
|
|
22181
22554
|
positionalDirectory ?? flagDirectory ?? promptedDirectory ?? "."
|
|
22182
22555
|
);
|
|
@@ -22302,104 +22675,592 @@ var descriptions = {
|
|
|
22302
22675
|
down: "Stop the selected Compose deployment",
|
|
22303
22676
|
logs: "Stream logs for the selected deployment",
|
|
22304
22677
|
ps: "List the running containers or pods for the selected deployment",
|
|
22305
|
-
restart: "Restart the workloads of the selected deployment"
|
|
22678
|
+
restart: "Restart the workloads of the selected deployment",
|
|
22679
|
+
pull: "Download the configured images without changing configuration"
|
|
22306
22680
|
};
|
|
22307
22681
|
var serviceScoped = /* @__PURE__ */ new Set([
|
|
22308
22682
|
"logs",
|
|
22309
|
-
"restart"
|
|
22683
|
+
"restart",
|
|
22684
|
+
"pull"
|
|
22310
22685
|
]);
|
|
22311
22686
|
function createDeploymentCommands(context, setExitCode) {
|
|
22312
|
-
return ["up", "down", "logs", "ps", "restart"].map(
|
|
22313
|
-
|
|
22314
|
-
|
|
22315
|
-
|
|
22316
|
-
|
|
22317
|
-
|
|
22318
|
-
|
|
22319
|
-
|
|
22320
|
-
|
|
22321
|
-
|
|
22322
|
-
|
|
22323
|
-
|
|
22324
|
-
|
|
22325
|
-
|
|
22326
|
-
|
|
22327
|
-
|
|
22328
|
-
|
|
22329
|
-
|
|
22330
|
-
)
|
|
22687
|
+
return ["up", "down", "logs", "ps", "restart", "pull"].map(
|
|
22688
|
+
(name) => {
|
|
22689
|
+
const command = new Command(name).description(descriptions[name]).argument(
|
|
22690
|
+
"[args...]",
|
|
22691
|
+
"additional arguments passed to the deployment runtime"
|
|
22692
|
+
).option("--instance <name>", "select a configured instance");
|
|
22693
|
+
if (serviceScoped.has(name)) {
|
|
22694
|
+
command.option(
|
|
22695
|
+
"--service <name>",
|
|
22696
|
+
"select a single service (Compose) or configured workload (Kubernetes)"
|
|
22697
|
+
);
|
|
22698
|
+
}
|
|
22699
|
+
if (name === "logs") {
|
|
22700
|
+
command.option("--follow", "stream new output as it arrives").option("--tail <lines>", "number of recent lines to show").option(
|
|
22701
|
+
"--since <time>",
|
|
22702
|
+
"show logs since a duration (10m, 2h) or timestamp (Compose instances)"
|
|
22703
|
+
);
|
|
22704
|
+
}
|
|
22705
|
+
if (name === "restart") {
|
|
22706
|
+
command.option(
|
|
22707
|
+
"--no-wait",
|
|
22708
|
+
"return without waiting for the rollout to finish (Kubernetes instances)"
|
|
22709
|
+
);
|
|
22710
|
+
}
|
|
22711
|
+
return command.action(async (args, options) => {
|
|
22712
|
+
const { config } = await loadCliState(context);
|
|
22713
|
+
setExitCode(
|
|
22714
|
+
await runDriverCommand(
|
|
22715
|
+
config,
|
|
22716
|
+
name,
|
|
22717
|
+
parsedArgs(name, void 0, args, options),
|
|
22718
|
+
context
|
|
22719
|
+
)
|
|
22720
|
+
);
|
|
22721
|
+
});
|
|
22331
22722
|
}
|
|
22332
|
-
|
|
22333
|
-
const { config } = await loadCliState(context);
|
|
22334
|
-
setExitCode(
|
|
22335
|
-
await runDriverCommand(
|
|
22336
|
-
config,
|
|
22337
|
-
name,
|
|
22338
|
-
parsedArgs(name, void 0, args, options),
|
|
22339
|
-
context
|
|
22340
|
-
)
|
|
22341
|
-
);
|
|
22342
|
-
});
|
|
22343
|
-
});
|
|
22723
|
+
);
|
|
22344
22724
|
}
|
|
22345
22725
|
|
|
22346
|
-
//
|
|
22347
|
-
|
|
22348
|
-
|
|
22349
|
-
|
|
22350
|
-
|
|
22351
|
-
|
|
22352
|
-
|
|
22353
|
-
|
|
22354
|
-
|
|
22355
|
-
|
|
22356
|
-
|
|
22357
|
-
|
|
22358
|
-
|
|
22359
|
-
|
|
22360
|
-
|
|
22361
|
-
|
|
22362
|
-
|
|
22363
|
-
|
|
22364
|
-
|
|
22365
|
-
|
|
22366
|
-
|
|
22367
|
-
|
|
22368
|
-
|
|
22369
|
-
|
|
22370
|
-
|
|
22371
|
-
|
|
22372
|
-
|
|
22373
|
-
|
|
22374
|
-
|
|
22375
|
-
|
|
22376
|
-
|
|
22377
|
-
|
|
22378
|
-
|
|
22379
|
-
|
|
22380
|
-
|
|
22381
|
-
|
|
22382
|
-
|
|
22383
|
-
|
|
22384
|
-
|
|
22385
|
-
}
|
|
22386
|
-
|
|
22387
|
-
|
|
22388
|
-
|
|
22389
|
-
|
|
22726
|
+
// package.json
|
|
22727
|
+
var package_default = {
|
|
22728
|
+
name: "@eudiplo/cli",
|
|
22729
|
+
version: "8.1.0-main.a6a67b3",
|
|
22730
|
+
description: "Deployment-aware command line tools for EUDIPLO",
|
|
22731
|
+
private: false,
|
|
22732
|
+
type: "module",
|
|
22733
|
+
bin: {
|
|
22734
|
+
eudiplo: "dist/index.js"
|
|
22735
|
+
},
|
|
22736
|
+
files: [
|
|
22737
|
+
"dist",
|
|
22738
|
+
"templates",
|
|
22739
|
+
"README.md"
|
|
22740
|
+
],
|
|
22741
|
+
scripts: {
|
|
22742
|
+
"assets:sync": "node scripts/sync-cli-assets.cjs",
|
|
22743
|
+
"assets:check": "node scripts/check-cli-assets.cjs",
|
|
22744
|
+
build: "pnpm assets:sync && pnpm assets:check && tsc -p tsconfig.json",
|
|
22745
|
+
"build:publish": "pnpm build && esbuild src/index.ts --bundle --platform=node --format=esm --target=node22.12 --alias:jsonc-parser=jsonc-parser/lib/esm/main.js --outfile=dist/index.js",
|
|
22746
|
+
"build:sea:bundle": "pnpm assets:sync && pnpm assets:check && esbuild src/index.ts --bundle --platform=node --format=esm --target=node22.12 --alias:jsonc-parser=jsonc-parser/lib/esm/main.js --outfile=dist-sea/index.js",
|
|
22747
|
+
"build:sea": "node scripts/build-cli-sea.mjs",
|
|
22748
|
+
dev: "tsx src/index.ts",
|
|
22749
|
+
test: "vitest run --project cli",
|
|
22750
|
+
lint: "biome lint .",
|
|
22751
|
+
prepublishOnly: "pnpm build:publish"
|
|
22752
|
+
},
|
|
22753
|
+
keywords: [
|
|
22754
|
+
"eudiplo",
|
|
22755
|
+
"cli",
|
|
22756
|
+
"eudi",
|
|
22757
|
+
"wallet",
|
|
22758
|
+
"oid4vci",
|
|
22759
|
+
"oid4vp"
|
|
22760
|
+
],
|
|
22761
|
+
repository: {
|
|
22762
|
+
type: "git",
|
|
22763
|
+
url: "git+https://github.com/openwallet-foundation/eudiplo.git",
|
|
22764
|
+
directory: "apps/cli"
|
|
22765
|
+
},
|
|
22766
|
+
homepage: "https://github.com/openwallet-foundation/eudiplo/tree/main/apps/cli#readme",
|
|
22767
|
+
bugs: {
|
|
22768
|
+
url: "https://github.com/openwallet-foundation/eudiplo/issues"
|
|
22769
|
+
},
|
|
22770
|
+
author: "OpenWallet Foundation",
|
|
22771
|
+
license: "Apache-2.0",
|
|
22772
|
+
engines: {
|
|
22773
|
+
node: ">=22.12.0"
|
|
22774
|
+
},
|
|
22775
|
+
publishConfig: {
|
|
22776
|
+
access: "public"
|
|
22777
|
+
},
|
|
22778
|
+
dependencies: {
|
|
22779
|
+
"@eudiplo/config-format": "workspace:*",
|
|
22780
|
+
ajv: "^8.20.0",
|
|
22781
|
+
"ajv-formats": "^3.0.1",
|
|
22782
|
+
commander: "^15.0.0",
|
|
22783
|
+
fflate: "^0.8.3",
|
|
22784
|
+
"jsonc-parser": "^3.3.1"
|
|
22785
|
+
},
|
|
22786
|
+
devDependencies: {
|
|
22787
|
+
"@biomejs/biome": "2.5.14",
|
|
22788
|
+
"@types/node": "^26.4.0",
|
|
22789
|
+
esbuild: "^0.28.2",
|
|
22790
|
+
tsx: "^4.23.13",
|
|
22791
|
+
typescript: "^7.0.2",
|
|
22792
|
+
vitest: "4.1.11"
|
|
22793
|
+
}
|
|
22794
|
+
};
|
|
22795
|
+
|
|
22796
|
+
// src/services/api-auth.ts
|
|
22797
|
+
async function requestAccessToken(baseUrl, context) {
|
|
22798
|
+
const clientId = context.env.EUDIPLO_CLIENT_ID;
|
|
22799
|
+
const clientSecret = context.env.EUDIPLO_CLIENT_SECRET;
|
|
22800
|
+
if (!clientId || !clientSecret) {
|
|
22801
|
+
return {
|
|
22802
|
+
kind: "unavailable",
|
|
22803
|
+
reason: "EUDIPLO_CLIENT_ID and EUDIPLO_CLIENT_SECRET are not set"
|
|
22804
|
+
};
|
|
22805
|
+
}
|
|
22806
|
+
const tokenUrl = new URL("api/oauth2/token", withTrailingSlash(baseUrl));
|
|
22807
|
+
try {
|
|
22808
|
+
const response = await context.fetch(tokenUrl, {
|
|
22809
|
+
method: "POST",
|
|
22810
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
22811
|
+
body: new URLSearchParams({
|
|
22812
|
+
grant_type: "client_credentials",
|
|
22813
|
+
client_id: clientId,
|
|
22814
|
+
client_secret: clientSecret
|
|
22815
|
+
}).toString()
|
|
22390
22816
|
});
|
|
22817
|
+
if (!response.ok) {
|
|
22818
|
+
return {
|
|
22819
|
+
kind: "error",
|
|
22820
|
+
reason: `the token endpoint returned HTTP ${response.status}`
|
|
22821
|
+
};
|
|
22822
|
+
}
|
|
22823
|
+
const payload = await response.json();
|
|
22824
|
+
const accessToken = typeof payload === "object" && payload !== null && "access_token" in payload ? String(payload.access_token) : "";
|
|
22825
|
+
if (!accessToken) {
|
|
22826
|
+
return {
|
|
22827
|
+
kind: "error",
|
|
22828
|
+
reason: "the token response did not contain an access token"
|
|
22829
|
+
};
|
|
22830
|
+
}
|
|
22831
|
+
return { kind: "token", accessToken };
|
|
22832
|
+
} catch (error) {
|
|
22833
|
+
return {
|
|
22834
|
+
kind: "error",
|
|
22835
|
+
reason: `the token endpoint could not be reached: ${error instanceof Error ? error.message : String(error)}`
|
|
22836
|
+
};
|
|
22837
|
+
}
|
|
22838
|
+
}
|
|
22839
|
+
async function fetchAuthenticated(url, accessToken, context) {
|
|
22840
|
+
try {
|
|
22841
|
+
const response = await context.fetch(url, {
|
|
22842
|
+
method: "GET",
|
|
22843
|
+
headers: { authorization: `Bearer ${accessToken}` }
|
|
22844
|
+
});
|
|
22845
|
+
if (!response.ok) {
|
|
22846
|
+
return {
|
|
22847
|
+
ok: false,
|
|
22848
|
+
status: response.status,
|
|
22849
|
+
reason: `${url.pathname} returned HTTP ${response.status}`
|
|
22850
|
+
};
|
|
22851
|
+
}
|
|
22852
|
+
return { ok: true, body: await response.json() };
|
|
22853
|
+
} catch (error) {
|
|
22854
|
+
return {
|
|
22855
|
+
ok: false,
|
|
22856
|
+
reason: `${url.pathname} could not be reached: ${error instanceof Error ? error.message : String(error)}`
|
|
22857
|
+
};
|
|
22858
|
+
}
|
|
22859
|
+
}
|
|
22860
|
+
function withTrailingSlash(url) {
|
|
22861
|
+
return url.href.endsWith("/") ? url : new URL(`${url.href}/`);
|
|
22862
|
+
}
|
|
22863
|
+
|
|
22864
|
+
// src/services/backend-checks.ts
|
|
22865
|
+
function compareVersions(cliVersion, backendVersion) {
|
|
22866
|
+
const name = "version compatibility";
|
|
22867
|
+
const cli = parse4(cliVersion);
|
|
22868
|
+
const backend = parse4(backendVersion);
|
|
22869
|
+
if (!cli || !backend) {
|
|
22870
|
+
return {
|
|
22871
|
+
name,
|
|
22872
|
+
status: "warn",
|
|
22873
|
+
message: `CLI ${cliVersion} and backend ${backendVersion} cannot be compared as versions.`
|
|
22874
|
+
};
|
|
22875
|
+
}
|
|
22876
|
+
if (cli.major !== backend.major) {
|
|
22877
|
+
return {
|
|
22878
|
+
name,
|
|
22879
|
+
status: "fail",
|
|
22880
|
+
message: `CLI ${cliVersion} and backend ${backendVersion} are different major versions. Install a matching CLI with npm install -g @eudiplo/cli@${backend.major}.`
|
|
22881
|
+
};
|
|
22882
|
+
}
|
|
22883
|
+
if (cli.minor !== backend.minor) {
|
|
22884
|
+
return {
|
|
22885
|
+
name,
|
|
22886
|
+
status: "warn",
|
|
22887
|
+
message: `CLI ${cliVersion} and backend ${backendVersion} differ in minor version; some commands may not be supported.`
|
|
22888
|
+
};
|
|
22889
|
+
}
|
|
22890
|
+
return {
|
|
22891
|
+
name,
|
|
22892
|
+
status: "pass",
|
|
22893
|
+
message: `CLI ${cliVersion} matches backend ${backendVersion}.`
|
|
22894
|
+
};
|
|
22895
|
+
}
|
|
22896
|
+
function readBackendVersion(body) {
|
|
22897
|
+
if (typeof body === "object" && body !== null && "version" in body) {
|
|
22898
|
+
const version = body.version;
|
|
22899
|
+
return typeof version === "string" && version ? version : void 0;
|
|
22900
|
+
}
|
|
22901
|
+
return void 0;
|
|
22902
|
+
}
|
|
22903
|
+
function readProviderHealth(body) {
|
|
22904
|
+
if (!Array.isArray(body)) {
|
|
22905
|
+
throw new Error("the provider health response was not a list");
|
|
22906
|
+
}
|
|
22907
|
+
return body.map((entry) => {
|
|
22908
|
+
const record = entry;
|
|
22909
|
+
return {
|
|
22910
|
+
providerId: String(record.providerId ?? "unknown"),
|
|
22911
|
+
type: String(record.type ?? "unknown"),
|
|
22912
|
+
ok: record.ok === true,
|
|
22913
|
+
latencyMs: typeof record.latencyMs === "number" ? record.latencyMs : void 0,
|
|
22914
|
+
error: typeof record.error === "string" ? record.error : void 0
|
|
22915
|
+
};
|
|
22916
|
+
});
|
|
22917
|
+
}
|
|
22918
|
+
function summarizeProviderHealth(providers) {
|
|
22919
|
+
const name = "KMS providers";
|
|
22920
|
+
if (providers.length === 0) {
|
|
22921
|
+
return {
|
|
22922
|
+
name,
|
|
22923
|
+
status: "warn",
|
|
22924
|
+
message: "No KMS providers are registered for this tenant."
|
|
22925
|
+
};
|
|
22926
|
+
}
|
|
22927
|
+
const failing = providers.filter((provider) => !provider.ok);
|
|
22928
|
+
if (failing.length > 0) {
|
|
22929
|
+
return {
|
|
22930
|
+
name,
|
|
22931
|
+
status: "fail",
|
|
22932
|
+
message: `${failing.length}/${providers.length} provider(s) unhealthy: ${failing.map(
|
|
22933
|
+
(provider) => `${provider.providerId} (${provider.type})${provider.error ? ", health check reported an error" : ""}`
|
|
22934
|
+
).join(", ")}`
|
|
22935
|
+
};
|
|
22936
|
+
}
|
|
22937
|
+
return {
|
|
22938
|
+
name,
|
|
22939
|
+
status: "pass",
|
|
22940
|
+
message: `${providers.length} provider(s) healthy: ${providers.map(
|
|
22941
|
+
(provider) => `${provider.providerId} (${provider.type}${provider.latencyMs === void 0 ? "" : `, ${provider.latencyMs}ms`})`
|
|
22942
|
+
).join(", ")}`
|
|
22943
|
+
};
|
|
22944
|
+
}
|
|
22945
|
+
function parse4(version) {
|
|
22946
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
|
|
22947
|
+
if (!match) {
|
|
22948
|
+
return void 0;
|
|
22949
|
+
}
|
|
22950
|
+
return {
|
|
22951
|
+
major: Number(match[1]),
|
|
22952
|
+
minor: Number(match[2]),
|
|
22953
|
+
patch: Number(match[3])
|
|
22954
|
+
};
|
|
22955
|
+
}
|
|
22956
|
+
|
|
22957
|
+
// src/services/tls-expiry.ts
|
|
22958
|
+
import { connect } from "node:tls";
|
|
22959
|
+
var defaultExpiryWarningDays = 30;
|
|
22960
|
+
var dayInMs = 24 * 60 * 60 * 1e3;
|
|
22961
|
+
function evaluateCertificate(certificate, now, warningDays = defaultExpiryWarningDays) {
|
|
22962
|
+
const remainingDays = Math.floor(
|
|
22963
|
+
(certificate.validTo.getTime() - now.getTime()) / dayInMs
|
|
22964
|
+
);
|
|
22965
|
+
const expiresOn = certificate.validTo.toISOString().slice(0, 10);
|
|
22966
|
+
if (remainingDays < 0) {
|
|
22967
|
+
return {
|
|
22968
|
+
name: "TLS certificate",
|
|
22969
|
+
status: "fail",
|
|
22970
|
+
message: `The certificate issued by ${certificate.issuer} expired on ${expiresOn}.`
|
|
22971
|
+
};
|
|
22972
|
+
}
|
|
22973
|
+
if (remainingDays <= warningDays) {
|
|
22974
|
+
return {
|
|
22975
|
+
name: "TLS certificate",
|
|
22976
|
+
status: "warn",
|
|
22977
|
+
message: `The certificate expires on ${expiresOn}, in ${remainingDays} day(s). Renew it before then.`
|
|
22978
|
+
};
|
|
22979
|
+
}
|
|
22980
|
+
return {
|
|
22981
|
+
name: "TLS certificate",
|
|
22982
|
+
status: "pass",
|
|
22983
|
+
message: `Valid until ${expiresOn} (${remainingDays} day(s) left), issued by ${certificate.issuer}.`
|
|
22984
|
+
};
|
|
22985
|
+
}
|
|
22986
|
+
var readPeerCertificate = (url) => new Promise((resolvePeer, rejectPeer) => {
|
|
22987
|
+
const socket = connect(
|
|
22988
|
+
{
|
|
22989
|
+
host: url.hostname,
|
|
22990
|
+
port: Number(url.port || 443),
|
|
22991
|
+
servername: url.hostname,
|
|
22992
|
+
rejectUnauthorized: true
|
|
22993
|
+
},
|
|
22994
|
+
() => {
|
|
22995
|
+
const certificate = socket.getPeerCertificate();
|
|
22996
|
+
socket.end();
|
|
22997
|
+
if (!certificate || !certificate.valid_to) {
|
|
22998
|
+
rejectPeer(new Error("no peer certificate was presented"));
|
|
22999
|
+
return;
|
|
23000
|
+
}
|
|
23001
|
+
resolvePeer({
|
|
23002
|
+
subject: String(certificate.subject?.CN ?? url.hostname),
|
|
23003
|
+
issuer: String(certificate.issuer?.CN ?? "unknown issuer"),
|
|
23004
|
+
validTo: new Date(certificate.valid_to)
|
|
23005
|
+
});
|
|
23006
|
+
}
|
|
23007
|
+
);
|
|
23008
|
+
socket.setTimeout(1e4, () => {
|
|
23009
|
+
socket.destroy();
|
|
23010
|
+
rejectPeer(new Error("the TLS handshake timed out"));
|
|
23011
|
+
});
|
|
23012
|
+
socket.once("error", (error) => {
|
|
23013
|
+
socket.destroy();
|
|
23014
|
+
rejectPeer(error);
|
|
23015
|
+
});
|
|
23016
|
+
});
|
|
23017
|
+
async function checkTlsCertificate(baseUrl, now, read = readPeerCertificate, warningDays = defaultExpiryWarningDays) {
|
|
23018
|
+
if (baseUrl.protocol !== "https:") {
|
|
23019
|
+
return {
|
|
23020
|
+
name: "TLS certificate",
|
|
23021
|
+
status: "skip",
|
|
23022
|
+
message: `${baseUrl.origin} does not use HTTPS, so there is no certificate to check.`
|
|
23023
|
+
};
|
|
23024
|
+
}
|
|
23025
|
+
try {
|
|
23026
|
+
return evaluateCertificate(await read(baseUrl), now, warningDays);
|
|
23027
|
+
} catch (error) {
|
|
23028
|
+
return {
|
|
23029
|
+
name: "TLS certificate",
|
|
23030
|
+
status: "fail",
|
|
23031
|
+
message: `The certificate of ${baseUrl.origin} could not be verified: ${error instanceof Error ? error.message : String(error)}`
|
|
23032
|
+
};
|
|
23033
|
+
}
|
|
23034
|
+
}
|
|
23035
|
+
|
|
23036
|
+
// src/services/diagnostics.ts
|
|
23037
|
+
var publicUrlCheck = {
|
|
23038
|
+
id: "public-url",
|
|
23039
|
+
async run({ instance, baseUrl }) {
|
|
23040
|
+
if (!baseUrl) {
|
|
23041
|
+
return {
|
|
23042
|
+
name: "public URL",
|
|
23043
|
+
status: "fail",
|
|
23044
|
+
message: `${instance.url} is not a valid absolute URL`
|
|
23045
|
+
};
|
|
23046
|
+
}
|
|
23047
|
+
return {
|
|
23048
|
+
name: "public URL",
|
|
23049
|
+
status: baseUrl.protocol === "https:" ? "pass" : "warn",
|
|
23050
|
+
message: baseUrl.protocol === "https:" ? `${baseUrl.href} uses HTTPS` : `${baseUrl.href} does not use HTTPS`
|
|
23051
|
+
};
|
|
23052
|
+
}
|
|
23053
|
+
};
|
|
23054
|
+
var apiReachabilityCheck = {
|
|
23055
|
+
id: "api-reachability",
|
|
23056
|
+
run: ({ baseUrl, context }) => requireBaseUrl(
|
|
23057
|
+
"API reachability",
|
|
23058
|
+
baseUrl,
|
|
23059
|
+
(url) => checkEndpoint("API reachability", url, "/api/docs", context)
|
|
23060
|
+
)
|
|
23061
|
+
};
|
|
23062
|
+
var healthEndpointCheck = {
|
|
23063
|
+
id: "health-endpoint",
|
|
23064
|
+
run: ({ baseUrl, context }) => requireBaseUrl(
|
|
23065
|
+
"health endpoint",
|
|
23066
|
+
baseUrl,
|
|
23067
|
+
(url) => checkEndpoint("health endpoint", url, "/health", context)
|
|
23068
|
+
)
|
|
23069
|
+
};
|
|
23070
|
+
var tlsCertificateCheck = {
|
|
23071
|
+
id: "tls-certificate",
|
|
23072
|
+
run: ({ baseUrl, now, readCertificate }) => requireBaseUrl(
|
|
23073
|
+
"TLS certificate",
|
|
23074
|
+
baseUrl,
|
|
23075
|
+
(url) => checkTlsCertificate(url, now, readCertificate)
|
|
23076
|
+
)
|
|
23077
|
+
};
|
|
23078
|
+
var authenticationCheck = {
|
|
23079
|
+
id: "authentication",
|
|
23080
|
+
async run({ context }) {
|
|
23081
|
+
const configured = Boolean(context.env.EUDIPLO_CLIENT_ID) && Boolean(context.env.EUDIPLO_CLIENT_SECRET);
|
|
23082
|
+
return {
|
|
23083
|
+
name: "authentication configuration",
|
|
23084
|
+
status: configured ? "pass" : "warn",
|
|
23085
|
+
message: configured ? "Client credentials are available in environment variables." : "Set EUDIPLO_CLIENT_ID and EUDIPLO_CLIENT_SECRET when commands need authenticated API access."
|
|
23086
|
+
};
|
|
23087
|
+
}
|
|
23088
|
+
};
|
|
23089
|
+
var clientConnectivityCheck = {
|
|
23090
|
+
id: "client-connectivity",
|
|
23091
|
+
async run({ instance, context }) {
|
|
23092
|
+
if (!instance.clientUrl) {
|
|
23093
|
+
return {
|
|
23094
|
+
name: "client connectivity",
|
|
23095
|
+
status: "skip",
|
|
23096
|
+
message: "No client URL configured for this instance."
|
|
23097
|
+
};
|
|
23098
|
+
}
|
|
23099
|
+
const clientUrl = parseUrl(instance.clientUrl);
|
|
23100
|
+
if (!clientUrl) {
|
|
23101
|
+
return {
|
|
23102
|
+
name: "client connectivity",
|
|
23103
|
+
status: "fail",
|
|
23104
|
+
message: `${instance.clientUrl} is not a valid absolute URL`
|
|
23105
|
+
};
|
|
23106
|
+
}
|
|
23107
|
+
return checkEndpoint("client connectivity", clientUrl, "/", context);
|
|
23108
|
+
}
|
|
23109
|
+
};
|
|
23110
|
+
var versionCompatibilityCheck = {
|
|
23111
|
+
id: "version-compatibility",
|
|
23112
|
+
run: ({ baseUrl, context, accessToken }) => requireBaseUrl("version compatibility", baseUrl, async (url) => {
|
|
23113
|
+
const token = await accessToken();
|
|
23114
|
+
if (token.kind !== "token") {
|
|
23115
|
+
return authUnavailable("version compatibility", token);
|
|
23116
|
+
}
|
|
23117
|
+
const response = await fetchAuthenticated(
|
|
23118
|
+
new URL("version", withTrailingSlash(url)),
|
|
23119
|
+
token.accessToken,
|
|
23120
|
+
context
|
|
23121
|
+
);
|
|
23122
|
+
if (!response.ok) {
|
|
23123
|
+
if (response.status === 404) {
|
|
23124
|
+
return {
|
|
23125
|
+
name: "version compatibility",
|
|
23126
|
+
status: "skip",
|
|
23127
|
+
message: "This backend does not expose /version, so compatibility cannot be checked."
|
|
23128
|
+
};
|
|
23129
|
+
}
|
|
23130
|
+
return {
|
|
23131
|
+
name: "version compatibility",
|
|
23132
|
+
status: "fail",
|
|
23133
|
+
message: `The backend version could not be read: ${response.reason}`
|
|
23134
|
+
};
|
|
23135
|
+
}
|
|
23136
|
+
const backendVersion = readBackendVersion(response.body);
|
|
23137
|
+
if (!backendVersion) {
|
|
23138
|
+
return {
|
|
23139
|
+
name: "version compatibility",
|
|
23140
|
+
status: "warn",
|
|
23141
|
+
message: "The backend did not report a version."
|
|
23142
|
+
};
|
|
23143
|
+
}
|
|
23144
|
+
return compareVersions(package_default.version, backendVersion);
|
|
23145
|
+
})
|
|
23146
|
+
};
|
|
23147
|
+
var kmsHealthCheck = {
|
|
23148
|
+
id: "kms-health",
|
|
23149
|
+
run: ({ baseUrl, context, accessToken }) => requireBaseUrl("KMS providers", baseUrl, async (url) => {
|
|
23150
|
+
const token = await accessToken();
|
|
23151
|
+
if (token.kind !== "token") {
|
|
23152
|
+
return authUnavailable("KMS providers", token);
|
|
23153
|
+
}
|
|
23154
|
+
const response = await fetchAuthenticated(
|
|
23155
|
+
new URL("key-chain/providers/health", withTrailingSlash(url)),
|
|
23156
|
+
token.accessToken,
|
|
23157
|
+
context
|
|
23158
|
+
);
|
|
23159
|
+
if (!response.ok) {
|
|
23160
|
+
if (response.status === 404) {
|
|
23161
|
+
return {
|
|
23162
|
+
name: "KMS providers",
|
|
23163
|
+
status: "skip",
|
|
23164
|
+
message: "This backend does not expose the KMS provider health endpoint."
|
|
23165
|
+
};
|
|
23166
|
+
}
|
|
23167
|
+
return {
|
|
23168
|
+
name: "KMS providers",
|
|
23169
|
+
status: "fail",
|
|
23170
|
+
message: `Provider health could not be read: ${response.reason}`
|
|
23171
|
+
};
|
|
23172
|
+
}
|
|
23173
|
+
try {
|
|
23174
|
+
return summarizeProviderHealth(
|
|
23175
|
+
readProviderHealth(response.body)
|
|
23176
|
+
);
|
|
23177
|
+
} catch {
|
|
23178
|
+
return {
|
|
23179
|
+
name: "KMS providers",
|
|
23180
|
+
status: "warn",
|
|
23181
|
+
message: "The provider health response could not be read."
|
|
23182
|
+
};
|
|
23183
|
+
}
|
|
23184
|
+
})
|
|
23185
|
+
};
|
|
23186
|
+
function authUnavailable(name, token) {
|
|
23187
|
+
return {
|
|
23188
|
+
name,
|
|
23189
|
+
status: token.kind === "unavailable" ? "skip" : "fail",
|
|
23190
|
+
message: token.kind === "unavailable" ? `Needs authentication: ${token.reason}.` : `Could not authenticate: ${token.reason}.`
|
|
23191
|
+
};
|
|
23192
|
+
}
|
|
23193
|
+
var coreChecks = [
|
|
23194
|
+
publicUrlCheck,
|
|
23195
|
+
apiReachabilityCheck,
|
|
23196
|
+
healthEndpointCheck,
|
|
23197
|
+
tlsCertificateCheck,
|
|
23198
|
+
authenticationCheck,
|
|
23199
|
+
versionCompatibilityCheck,
|
|
23200
|
+
kmsHealthCheck,
|
|
23201
|
+
clientConnectivityCheck
|
|
23202
|
+
];
|
|
23203
|
+
async function runDoctor(instance, context, driverDiagnostics, options = {}) {
|
|
23204
|
+
const baseUrl = parseUrl(instance.url);
|
|
23205
|
+
let token;
|
|
23206
|
+
const checkContext = {
|
|
23207
|
+
instance,
|
|
23208
|
+
context,
|
|
23209
|
+
baseUrl,
|
|
23210
|
+
now: options.now ?? /* @__PURE__ */ new Date(),
|
|
23211
|
+
readCertificate: options.readCertificate ?? context.readCertificate ?? readPeerCertificate,
|
|
23212
|
+
accessToken: () => {
|
|
23213
|
+
if (!baseUrl) {
|
|
23214
|
+
return Promise.resolve({
|
|
23215
|
+
kind: "unavailable",
|
|
23216
|
+
reason: "the instance URL could not be parsed"
|
|
23217
|
+
});
|
|
23218
|
+
}
|
|
23219
|
+
token ??= requestAccessToken(baseUrl, context);
|
|
23220
|
+
return token;
|
|
23221
|
+
}
|
|
23222
|
+
};
|
|
23223
|
+
const checks = [];
|
|
23224
|
+
for (const check of coreChecks) {
|
|
23225
|
+
checks.push(await check.run(checkContext));
|
|
22391
23226
|
}
|
|
22392
23227
|
checks.push(...driverDiagnostics);
|
|
22393
23228
|
return checks;
|
|
22394
23229
|
}
|
|
22395
|
-
function
|
|
22396
|
-
return
|
|
23230
|
+
function summarizeChecks(checks) {
|
|
23231
|
+
return {
|
|
23232
|
+
pass: count(checks, "pass"),
|
|
23233
|
+
warn: count(checks, "warn"),
|
|
23234
|
+
fail: count(checks, "fail"),
|
|
23235
|
+
skip: count(checks, "skip")
|
|
23236
|
+
};
|
|
23237
|
+
}
|
|
23238
|
+
function hasFailedChecks(checks, strict = false) {
|
|
23239
|
+
return checks.some(
|
|
23240
|
+
(check) => check.status === "fail" || strict && check.status === "warn"
|
|
23241
|
+
);
|
|
22397
23242
|
}
|
|
22398
23243
|
function formatChecks(checks) {
|
|
22399
23244
|
return checks.map(
|
|
22400
23245
|
(check) => `${formatStatus(check.status)} ${check.name}: ${check.message}`
|
|
22401
23246
|
).join("\n");
|
|
22402
23247
|
}
|
|
23248
|
+
function formatSummary(summary) {
|
|
23249
|
+
return `${summary.pass} passed, ${summary.warn} warning(s), ${summary.fail} failure(s), ${summary.skip} skipped`;
|
|
23250
|
+
}
|
|
23251
|
+
function count(checks, status) {
|
|
23252
|
+
return checks.filter((check) => check.status === status).length;
|
|
23253
|
+
}
|
|
23254
|
+
async function requireBaseUrl(name, baseUrl, run) {
|
|
23255
|
+
if (!baseUrl) {
|
|
23256
|
+
return {
|
|
23257
|
+
name,
|
|
23258
|
+
status: "skip",
|
|
23259
|
+
message: "The instance URL could not be parsed."
|
|
23260
|
+
};
|
|
23261
|
+
}
|
|
23262
|
+
return run(baseUrl);
|
|
23263
|
+
}
|
|
22403
23264
|
async function checkEndpoint(name, baseUrl, path2, context) {
|
|
22404
23265
|
const url = new URL(path2, baseUrl);
|
|
22405
23266
|
try {
|
|
@@ -22424,22 +23285,6 @@ async function checkEndpoint(name, baseUrl, path2, context) {
|
|
|
22424
23285
|
};
|
|
22425
23286
|
}
|
|
22426
23287
|
}
|
|
22427
|
-
function checkAuthentication(context) {
|
|
22428
|
-
const hasClientId = Boolean(context.env.EUDIPLO_CLIENT_ID);
|
|
22429
|
-
const hasClientSecret = Boolean(context.env.EUDIPLO_CLIENT_SECRET);
|
|
22430
|
-
if (hasClientId && hasClientSecret) {
|
|
22431
|
-
return {
|
|
22432
|
-
name: "authentication configuration",
|
|
22433
|
-
status: "pass",
|
|
22434
|
-
message: "Client credentials are available in environment variables."
|
|
22435
|
-
};
|
|
22436
|
-
}
|
|
22437
|
-
return {
|
|
22438
|
-
name: "authentication configuration",
|
|
22439
|
-
status: "warn",
|
|
22440
|
-
message: "Set EUDIPLO_CLIENT_ID and EUDIPLO_CLIENT_SECRET when commands need authenticated API access."
|
|
22441
|
-
};
|
|
22442
|
-
}
|
|
22443
23288
|
function parseUrl(value) {
|
|
22444
23289
|
try {
|
|
22445
23290
|
const url = new URL(value);
|
|
@@ -22455,28 +23300,54 @@ function formatStatus(status) {
|
|
|
22455
23300
|
if (status === "warn") {
|
|
22456
23301
|
return "WARN";
|
|
22457
23302
|
}
|
|
23303
|
+
if (status === "skip") {
|
|
23304
|
+
return "SKIP";
|
|
23305
|
+
}
|
|
22458
23306
|
return "FAIL";
|
|
22459
23307
|
}
|
|
22460
23308
|
|
|
22461
23309
|
// src/commands/doctor/action.ts
|
|
22462
|
-
async function runDoctorCommand(config, parsed, context) {
|
|
22463
|
-
const
|
|
23310
|
+
async function runDoctorCommand(config, parsed, context, options = {}) {
|
|
23311
|
+
const strict = parsed.flags.strict === true;
|
|
23312
|
+
const instances = parsed.flags.all === true ? Object.keys(config.instances) : [resolveInstance(config, parsed)[0]];
|
|
23313
|
+
if (instances.length === 0) {
|
|
23314
|
+
throw new Error("No instances are configured.");
|
|
23315
|
+
}
|
|
23316
|
+
let failed = false;
|
|
23317
|
+
for (const [index, instanceName] of instances.entries()) {
|
|
23318
|
+
const instance = config.instances[instanceName];
|
|
23319
|
+
if (index > 0) {
|
|
23320
|
+
context.stdout.write("\n");
|
|
23321
|
+
}
|
|
23322
|
+
const checks = await runDiagnostics(instance, context, options);
|
|
23323
|
+
context.stdout.write(
|
|
23324
|
+
`Doctor for ${instanceName} (${instance.target})
|
|
23325
|
+
`
|
|
23326
|
+
);
|
|
23327
|
+
context.stdout.write(`${formatChecks(checks)}
|
|
23328
|
+
`);
|
|
23329
|
+
context.stdout.write(`${formatSummary(summarizeChecks(checks))}
|
|
23330
|
+
`);
|
|
23331
|
+
failed = hasFailedChecks(checks, strict) || failed;
|
|
23332
|
+
}
|
|
23333
|
+
return failed ? 1 : 0;
|
|
23334
|
+
}
|
|
23335
|
+
async function runDiagnostics(instance, context, options) {
|
|
22464
23336
|
const driver = drivers[instance.target];
|
|
22465
|
-
|
|
23337
|
+
return runDoctor(
|
|
22466
23338
|
instance,
|
|
22467
23339
|
context,
|
|
22468
|
-
await driver.diagnostics(instance, context)
|
|
23340
|
+
await driver.diagnostics(instance, context),
|
|
23341
|
+
options
|
|
22469
23342
|
);
|
|
22470
|
-
context.stdout.write(`Doctor for ${instanceName} (${instance.target})
|
|
22471
|
-
`);
|
|
22472
|
-
context.stdout.write(`${formatChecks(checks)}
|
|
22473
|
-
`);
|
|
22474
|
-
return hasFailedChecks(checks) ? 1 : 0;
|
|
22475
23343
|
}
|
|
22476
23344
|
|
|
22477
23345
|
// src/commands/doctor/index.ts
|
|
22478
23346
|
function createDoctorCommand(context, setExitCode) {
|
|
22479
|
-
return new Command("doctor").description("Run deployment diagnostics").option("--instance <name>", "select a configured instance").
|
|
23347
|
+
return new Command("doctor").description("Run deployment diagnostics").option("--instance <name>", "select a configured instance").option("--all", "run the checks for every configured instance").option(
|
|
23348
|
+
"--strict",
|
|
23349
|
+
"treat warnings as failures (skipped checks never fail)"
|
|
23350
|
+
).action(async (options) => {
|
|
22480
23351
|
const { config } = await loadCliState(context);
|
|
22481
23352
|
setExitCode(
|
|
22482
23353
|
await runDoctorCommand(
|
|
@@ -23020,6 +23891,146 @@ function createInstanceCommand(context, setExitCode) {
|
|
|
23020
23891
|
return instance;
|
|
23021
23892
|
}
|
|
23022
23893
|
|
|
23894
|
+
// src/services/browser.ts
|
|
23895
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
23896
|
+
import { access as access3 } from "node:fs/promises";
|
|
23897
|
+
import { delimiter as delimiter2, join as join11 } from "node:path";
|
|
23898
|
+
function assertWebUrl(value) {
|
|
23899
|
+
let url;
|
|
23900
|
+
try {
|
|
23901
|
+
url = new URL(value);
|
|
23902
|
+
} catch {
|
|
23903
|
+
throw new Error(`${value} is not a valid absolute URL.`);
|
|
23904
|
+
}
|
|
23905
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
23906
|
+
throw new Error(
|
|
23907
|
+
`Refusing to open ${value}: only http and https URLs are supported.`
|
|
23908
|
+
);
|
|
23909
|
+
}
|
|
23910
|
+
return url;
|
|
23911
|
+
}
|
|
23912
|
+
async function chooseLauncher(url, { platform, env, which }) {
|
|
23913
|
+
const browser = env.BROWSER?.trim();
|
|
23914
|
+
if (browser && !/\s|%s/.test(browser)) {
|
|
23915
|
+
const command = await which(browser);
|
|
23916
|
+
if (command) {
|
|
23917
|
+
return { command, args: [url] };
|
|
23918
|
+
}
|
|
23919
|
+
}
|
|
23920
|
+
if (platform === "darwin") {
|
|
23921
|
+
return { command: "open", args: [url] };
|
|
23922
|
+
}
|
|
23923
|
+
if (platform === "win32") {
|
|
23924
|
+
return {
|
|
23925
|
+
command: "rundll32.exe",
|
|
23926
|
+
args: ["url.dll,FileProtocolHandler", url]
|
|
23927
|
+
};
|
|
23928
|
+
}
|
|
23929
|
+
if (env.WSL_DISTRO_NAME) {
|
|
23930
|
+
const wslview = await which("wslview");
|
|
23931
|
+
if (wslview) {
|
|
23932
|
+
return { command: wslview, args: [url] };
|
|
23933
|
+
}
|
|
23934
|
+
}
|
|
23935
|
+
if (env.DISPLAY || env.WAYLAND_DISPLAY) {
|
|
23936
|
+
const xdgOpen = await which("xdg-open");
|
|
23937
|
+
if (xdgOpen) {
|
|
23938
|
+
return { command: xdgOpen, args: [url] };
|
|
23939
|
+
}
|
|
23940
|
+
}
|
|
23941
|
+
return void 0;
|
|
23942
|
+
}
|
|
23943
|
+
function launchBrowser(launcher) {
|
|
23944
|
+
return new Promise((resolveLaunch) => {
|
|
23945
|
+
const child = spawn2(launcher.command, launcher.args, {
|
|
23946
|
+
detached: true,
|
|
23947
|
+
stdio: "ignore"
|
|
23948
|
+
});
|
|
23949
|
+
child.once("error", () => resolveLaunch(false));
|
|
23950
|
+
child.once("spawn", () => {
|
|
23951
|
+
child.unref();
|
|
23952
|
+
resolveLaunch(true);
|
|
23953
|
+
});
|
|
23954
|
+
});
|
|
23955
|
+
}
|
|
23956
|
+
function pathLookup(env, platform = process.platform) {
|
|
23957
|
+
const extensions = platform === "win32" ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")] : [""];
|
|
23958
|
+
return async (name) => {
|
|
23959
|
+
const entries = (env.PATH ?? "").split(delimiter2).filter(Boolean);
|
|
23960
|
+
for (const entry of entries) {
|
|
23961
|
+
for (const extension of extensions) {
|
|
23962
|
+
const candidate = join11(entry, `${name}${extension}`);
|
|
23963
|
+
try {
|
|
23964
|
+
await access3(candidate);
|
|
23965
|
+
return candidate;
|
|
23966
|
+
} catch {
|
|
23967
|
+
}
|
|
23968
|
+
}
|
|
23969
|
+
}
|
|
23970
|
+
return void 0;
|
|
23971
|
+
};
|
|
23972
|
+
}
|
|
23973
|
+
|
|
23974
|
+
// src/commands/open/action.ts
|
|
23975
|
+
var defaultDependencies = {
|
|
23976
|
+
platform: process.platform,
|
|
23977
|
+
launch: launchBrowser
|
|
23978
|
+
};
|
|
23979
|
+
function resolveOpenTarget(instanceName, instance, docs) {
|
|
23980
|
+
if (docs) {
|
|
23981
|
+
const base = instance.url.endsWith("/") ? instance.url : `${instance.url}/`;
|
|
23982
|
+
return assertWebUrl(new URL("api/docs", base).href).href;
|
|
23983
|
+
}
|
|
23984
|
+
if (!instance.clientUrl) {
|
|
23985
|
+
throw new Error(
|
|
23986
|
+
`Instance ${instanceName} has no web client URL. Use --docs to open the API documentation instead.`
|
|
23987
|
+
);
|
|
23988
|
+
}
|
|
23989
|
+
return assertWebUrl(instance.clientUrl).href;
|
|
23990
|
+
}
|
|
23991
|
+
async function runOpenCommand(config, parsed, context, dependencies = defaultDependencies) {
|
|
23992
|
+
const [instanceName, instance] = resolveInstance(config, parsed);
|
|
23993
|
+
const url = resolveOpenTarget(
|
|
23994
|
+
instanceName,
|
|
23995
|
+
instance,
|
|
23996
|
+
parsed.flags.docs === true
|
|
23997
|
+
);
|
|
23998
|
+
if (parsed.flags.print === true || context.interactive !== true) {
|
|
23999
|
+
context.stdout.write(`${url}
|
|
24000
|
+
`);
|
|
24001
|
+
return 0;
|
|
24002
|
+
}
|
|
24003
|
+
const launcher = await chooseLauncher(url, {
|
|
24004
|
+
platform: dependencies.platform,
|
|
24005
|
+
env: context.env,
|
|
24006
|
+
which: pathLookup(context.env, dependencies.platform)
|
|
24007
|
+
});
|
|
24008
|
+
if (!launcher || !await dependencies.launch(launcher)) {
|
|
24009
|
+
context.stdout.write(`No browser is available here. Open: ${url}
|
|
24010
|
+
`);
|
|
24011
|
+
return 0;
|
|
24012
|
+
}
|
|
24013
|
+
context.stdout.write(`Opening ${url}
|
|
24014
|
+
`);
|
|
24015
|
+
return 0;
|
|
24016
|
+
}
|
|
24017
|
+
|
|
24018
|
+
// src/commands/open/index.ts
|
|
24019
|
+
function createOpenCommand(context, setExitCode) {
|
|
24020
|
+
return new Command("open").description(
|
|
24021
|
+
"Open the web client of the selected instance in a browser"
|
|
24022
|
+
).option("--instance <name>", "select a configured instance").option("--docs", "open the management API documentation instead").option("--print", "print the URL instead of opening a browser").action(async (options) => {
|
|
24023
|
+
const { config } = await loadCliState(context);
|
|
24024
|
+
setExitCode(
|
|
24025
|
+
await runOpenCommand(
|
|
24026
|
+
config,
|
|
24027
|
+
parsedArgs("open", void 0, [], options),
|
|
24028
|
+
context
|
|
24029
|
+
)
|
|
24030
|
+
);
|
|
24031
|
+
});
|
|
24032
|
+
}
|
|
24033
|
+
|
|
23023
24034
|
// src/commands/status/action.ts
|
|
23024
24035
|
async function runStatusCommand(config, parsed, context) {
|
|
23025
24036
|
const [instanceName, instance] = resolveInstance(config, parsed);
|
|
@@ -23052,80 +24063,224 @@ function createStatusCommand(context, setExitCode) {
|
|
|
23052
24063
|
});
|
|
23053
24064
|
}
|
|
23054
24065
|
|
|
23055
|
-
// src/commands/
|
|
23056
|
-
import {
|
|
24066
|
+
// src/commands/upgrade/action.ts
|
|
24067
|
+
import { readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
|
|
24068
|
+
import { resolve as resolve8 } from "node:path";
|
|
23057
24069
|
|
|
23058
|
-
//
|
|
23059
|
-
var
|
|
23060
|
-
|
|
23061
|
-
|
|
23062
|
-
description: "Deployment-aware command line tools for EUDIPLO",
|
|
23063
|
-
private: false,
|
|
23064
|
-
type: "module",
|
|
23065
|
-
bin: {
|
|
23066
|
-
eudiplo: "dist/index.js"
|
|
23067
|
-
},
|
|
23068
|
-
files: [
|
|
23069
|
-
"dist",
|
|
23070
|
-
"templates",
|
|
23071
|
-
"README.md"
|
|
23072
|
-
],
|
|
23073
|
-
scripts: {
|
|
23074
|
-
"assets:sync": "node scripts/sync-cli-assets.cjs",
|
|
23075
|
-
"assets:check": "node scripts/check-cli-assets.cjs",
|
|
23076
|
-
build: "pnpm assets:sync && pnpm assets:check && tsc -p tsconfig.json",
|
|
23077
|
-
"build:publish": "pnpm build && esbuild src/index.ts --bundle --platform=node --format=esm --target=node22.12 --alias:jsonc-parser=jsonc-parser/lib/esm/main.js --outfile=dist/index.js",
|
|
23078
|
-
"build:sea:bundle": "pnpm assets:sync && pnpm assets:check && esbuild src/index.ts --bundle --platform=node --format=esm --target=node22.12 --alias:jsonc-parser=jsonc-parser/lib/esm/main.js --outfile=dist-sea/index.js",
|
|
23079
|
-
"build:sea": "node scripts/build-cli-sea.mjs",
|
|
23080
|
-
dev: "tsx src/index.ts",
|
|
23081
|
-
test: "vitest run --project cli",
|
|
23082
|
-
lint: "biome lint .",
|
|
23083
|
-
prepublishOnly: "pnpm build:publish"
|
|
23084
|
-
},
|
|
23085
|
-
keywords: [
|
|
23086
|
-
"eudiplo",
|
|
23087
|
-
"cli",
|
|
23088
|
-
"eudi",
|
|
23089
|
-
"wallet",
|
|
23090
|
-
"oid4vci",
|
|
23091
|
-
"oid4vp"
|
|
23092
|
-
],
|
|
23093
|
-
repository: {
|
|
23094
|
-
type: "git",
|
|
23095
|
-
url: "git+https://github.com/openwallet-foundation/eudiplo.git",
|
|
23096
|
-
directory: "apps/cli"
|
|
23097
|
-
},
|
|
23098
|
-
homepage: "https://github.com/openwallet-foundation/eudiplo/tree/main/apps/cli#readme",
|
|
23099
|
-
bugs: {
|
|
23100
|
-
url: "https://github.com/openwallet-foundation/eudiplo/issues"
|
|
23101
|
-
},
|
|
23102
|
-
author: "OpenWallet Foundation",
|
|
23103
|
-
license: "Apache-2.0",
|
|
23104
|
-
engines: {
|
|
23105
|
-
node: ">=22.12.0"
|
|
23106
|
-
},
|
|
23107
|
-
publishConfig: {
|
|
23108
|
-
access: "public"
|
|
23109
|
-
},
|
|
23110
|
-
dependencies: {
|
|
23111
|
-
"@eudiplo/config-format": "workspace:*",
|
|
23112
|
-
ajv: "^8.20.0",
|
|
23113
|
-
"ajv-formats": "^3.0.1",
|
|
23114
|
-
commander: "^15.0.0",
|
|
23115
|
-
fflate: "^0.8.3",
|
|
23116
|
-
"jsonc-parser": "^3.3.1"
|
|
23117
|
-
},
|
|
23118
|
-
devDependencies: {
|
|
23119
|
-
"@biomejs/biome": "2.5.13",
|
|
23120
|
-
"@types/node": "^26.4.0",
|
|
23121
|
-
esbuild: "^0.28.2",
|
|
23122
|
-
tsx: "^4.23.13",
|
|
23123
|
-
typescript: "^7.0.2",
|
|
23124
|
-
vitest: "4.1.11"
|
|
23125
|
-
}
|
|
24070
|
+
// src/services/compose-upgrade.ts
|
|
24071
|
+
var managedImages = {
|
|
24072
|
+
EUDIPLO_IMAGE: "ghcr.io/openwallet-foundation/eudiplo",
|
|
24073
|
+
EUDIPLO_CLIENT_IMAGE: "ghcr.io/openwallet-foundation/eudiplo-client"
|
|
23126
24074
|
};
|
|
24075
|
+
var TAG = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/;
|
|
24076
|
+
function assertImageTag(tag) {
|
|
24077
|
+
if (!TAG.test(tag)) {
|
|
24078
|
+
throw new Error(
|
|
24079
|
+
`Invalid image tag ${JSON.stringify(tag)}. Tags contain letters, digits, "_", "." and "-", and cannot start with "." or "-".`
|
|
24080
|
+
);
|
|
24081
|
+
}
|
|
24082
|
+
}
|
|
24083
|
+
function planImageUpgrade(content, tag) {
|
|
24084
|
+
assertImageTag(tag);
|
|
24085
|
+
const found = /* @__PURE__ */ new Set();
|
|
24086
|
+
const changes = [];
|
|
24087
|
+
const lines = content.split("\n").map((line) => {
|
|
24088
|
+
const match = /^(EUDIPLO_IMAGE|EUDIPLO_CLIENT_IMAGE)=(.*?)(\r?)$/.exec(
|
|
24089
|
+
line
|
|
24090
|
+
);
|
|
24091
|
+
if (!match) {
|
|
24092
|
+
return line;
|
|
24093
|
+
}
|
|
24094
|
+
const key = match[1];
|
|
24095
|
+
const value = match[2];
|
|
24096
|
+
const lineEnding = match[3];
|
|
24097
|
+
const repository = managedImages[key];
|
|
24098
|
+
if (found.has(key)) {
|
|
24099
|
+
throw new Error(
|
|
24100
|
+
`${key} is defined more than once in the env file.`
|
|
24101
|
+
);
|
|
24102
|
+
}
|
|
24103
|
+
found.add(key);
|
|
24104
|
+
const prefix = `${repository}:`;
|
|
24105
|
+
const current = value.startsWith(prefix) ? value.slice(prefix.length) : void 0;
|
|
24106
|
+
if (current === void 0 || !TAG.test(current)) {
|
|
24107
|
+
throw new Error(
|
|
24108
|
+
`${key} is set to ${value}, which is not a CLI-managed image. Update it yourself, or restore ${prefix}<tag> to let the CLI manage it.`
|
|
24109
|
+
);
|
|
24110
|
+
}
|
|
24111
|
+
if (current !== tag) {
|
|
24112
|
+
changes.push({ key, repository, from: current, to: tag });
|
|
24113
|
+
}
|
|
24114
|
+
return `${key}=${prefix}${tag}${lineEnding}`;
|
|
24115
|
+
});
|
|
24116
|
+
if (!found.has("EUDIPLO_IMAGE")) {
|
|
24117
|
+
throw new Error(
|
|
24118
|
+
"The env file does not define EUDIPLO_IMAGE, so there is no CLI-managed image to upgrade."
|
|
24119
|
+
);
|
|
24120
|
+
}
|
|
24121
|
+
return { changes, content: lines.join("\n") };
|
|
24122
|
+
}
|
|
24123
|
+
function parseVersion(tag) {
|
|
24124
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(tag);
|
|
24125
|
+
if (!match) {
|
|
24126
|
+
return void 0;
|
|
24127
|
+
}
|
|
24128
|
+
return {
|
|
24129
|
+
major: Number(match[1]),
|
|
24130
|
+
minor: Number(match[2]),
|
|
24131
|
+
patch: Number(match[3])
|
|
24132
|
+
};
|
|
24133
|
+
}
|
|
24134
|
+
function compareVersions2(a, b) {
|
|
24135
|
+
return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
|
|
24136
|
+
}
|
|
24137
|
+
var migrationBaseUrl = "https://docs.eudiplo.dev/migration";
|
|
24138
|
+
function migrationNotes(from, to) {
|
|
24139
|
+
const fromVersion = parseVersion(from);
|
|
24140
|
+
const toVersion = parseVersion(to);
|
|
24141
|
+
if (!fromVersion || !toVersion) {
|
|
24142
|
+
return [
|
|
24143
|
+
`Cannot compare ${from} and ${to} as versions. Check the release notes and ${migrationBaseUrl} before continuing.`
|
|
24144
|
+
];
|
|
24145
|
+
}
|
|
24146
|
+
const order = compareVersions2(fromVersion, toVersion);
|
|
24147
|
+
if (order > 0) {
|
|
24148
|
+
return [
|
|
24149
|
+
`${to} is older than ${from}. Downgrades are not supported once database migrations have run; restore a backup instead if you need to go back.`
|
|
24150
|
+
];
|
|
24151
|
+
}
|
|
24152
|
+
const notes = [];
|
|
24153
|
+
for (let major = fromVersion.major + 1; major <= toVersion.major; major++) {
|
|
24154
|
+
notes.push(
|
|
24155
|
+
`Major version ${major}: read ${migrationBaseUrl}/${major - 1}.x-to-${major}.0 first.`
|
|
24156
|
+
);
|
|
24157
|
+
}
|
|
24158
|
+
return notes;
|
|
24159
|
+
}
|
|
24160
|
+
|
|
24161
|
+
// src/commands/upgrade/action.ts
|
|
24162
|
+
async function runUpgradeCommand(config, parsed, context) {
|
|
24163
|
+
const [instanceName, instance] = resolveInstance(config, parsed);
|
|
24164
|
+
if (instance.target !== "compose") {
|
|
24165
|
+
throw new Error(unsupportedCommand("upgrade", instance.target));
|
|
24166
|
+
}
|
|
24167
|
+
const options = {
|
|
24168
|
+
instanceName,
|
|
24169
|
+
instance,
|
|
24170
|
+
args: [],
|
|
24171
|
+
flags: parsed.flags,
|
|
24172
|
+
context
|
|
24173
|
+
};
|
|
24174
|
+
assertWritable(options);
|
|
24175
|
+
const tag = readStringFlag(parsed.flags, "image-tag");
|
|
24176
|
+
if (!tag) {
|
|
24177
|
+
throw new Error("upgrade requires --image-tag <tag>.");
|
|
24178
|
+
}
|
|
24179
|
+
if (!instance.envFile) {
|
|
24180
|
+
throw new Error(
|
|
24181
|
+
`Instance ${instanceName} has no env file, so its images are not managed by the CLI.`
|
|
24182
|
+
);
|
|
24183
|
+
}
|
|
24184
|
+
const envPath = resolve8(
|
|
24185
|
+
instance.projectDirectory ?? context.cwd,
|
|
24186
|
+
instance.envFile
|
|
24187
|
+
);
|
|
24188
|
+
const original = await readEnvFile(envPath);
|
|
24189
|
+
const plan = planImageUpgrade(original, tag);
|
|
24190
|
+
if (plan.changes.length === 0) {
|
|
24191
|
+
context.stdout.write(
|
|
24192
|
+
`${instanceName} already uses image tag ${tag}.
|
|
24193
|
+
`
|
|
24194
|
+
);
|
|
24195
|
+
return 0;
|
|
24196
|
+
}
|
|
24197
|
+
context.stdout.write(`Upgrade ${instanceName}:
|
|
24198
|
+
`);
|
|
24199
|
+
for (const change of plan.changes) {
|
|
24200
|
+
context.stdout.write(
|
|
24201
|
+
` ${change.repository}: ${change.from} -> ${change.to}
|
|
24202
|
+
`
|
|
24203
|
+
);
|
|
24204
|
+
}
|
|
24205
|
+
const notes = new Set(
|
|
24206
|
+
plan.changes.flatMap(
|
|
24207
|
+
(change) => migrationNotes(change.from, change.to)
|
|
24208
|
+
)
|
|
24209
|
+
);
|
|
24210
|
+
for (const note of notes) {
|
|
24211
|
+
context.stdout.write(`Note: ${note}
|
|
24212
|
+
`);
|
|
24213
|
+
}
|
|
24214
|
+
if (!await confirmUpgrade(parsed, context)) {
|
|
24215
|
+
context.stdout.write("Upgrade cancelled. Nothing was changed.\n");
|
|
24216
|
+
return 0;
|
|
24217
|
+
}
|
|
24218
|
+
await writeFile6(envPath, plan.content, "utf8");
|
|
24219
|
+
const pulled = await runCompose(buildComposePullArgs(void 0), options);
|
|
24220
|
+
if (pulled !== 0) {
|
|
24221
|
+
await writeFile6(envPath, original, "utf8");
|
|
24222
|
+
context.stderr.write(
|
|
24223
|
+
`Pulling the new images failed. Restored the previous image tags in ${envPath}.
|
|
24224
|
+
`
|
|
24225
|
+
);
|
|
24226
|
+
return pulled;
|
|
24227
|
+
}
|
|
24228
|
+
const recreated = await runCompose(buildComposeUpArgs(), options);
|
|
24229
|
+
if (recreated !== 0) {
|
|
24230
|
+
context.stderr.write(
|
|
24231
|
+
`The new images were pulled and ${envPath} now uses ${tag}, but recreating the services failed. Fix the error above and run eudiplo up.
|
|
24232
|
+
`
|
|
24233
|
+
);
|
|
24234
|
+
return recreated;
|
|
24235
|
+
}
|
|
24236
|
+
context.stdout.write(`Upgraded ${instanceName} to ${tag}.
|
|
24237
|
+
`);
|
|
24238
|
+
return 0;
|
|
24239
|
+
}
|
|
24240
|
+
async function readEnvFile(path2) {
|
|
24241
|
+
try {
|
|
24242
|
+
return await readFile9(path2, "utf8");
|
|
24243
|
+
} catch (error) {
|
|
24244
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
24245
|
+
throw new Error(`Env file not found: ${path2}`);
|
|
24246
|
+
}
|
|
24247
|
+
throw error;
|
|
24248
|
+
}
|
|
24249
|
+
}
|
|
24250
|
+
async function confirmUpgrade(parsed, context) {
|
|
24251
|
+
if (parsed.flags.yes === true) {
|
|
24252
|
+
return true;
|
|
24253
|
+
}
|
|
24254
|
+
if (context.interactive !== true) {
|
|
24255
|
+
throw new Error("upgrade requires --yes in non-interactive mode.");
|
|
24256
|
+
}
|
|
24257
|
+
const prompter = createPrompter(context);
|
|
24258
|
+
try {
|
|
24259
|
+
const answer = (await prompter.ask("Continue? [y/N]: ")).trim().toLowerCase();
|
|
24260
|
+
return answer === "y" || answer === "yes";
|
|
24261
|
+
} finally {
|
|
24262
|
+
prompter.close();
|
|
24263
|
+
}
|
|
24264
|
+
}
|
|
24265
|
+
|
|
24266
|
+
// src/commands/upgrade/index.ts
|
|
24267
|
+
function createUpgradeCommand(context, setExitCode) {
|
|
24268
|
+
return new Command("upgrade").description(
|
|
24269
|
+
"Upgrade the EUDIPLO application images of a Compose deployment"
|
|
24270
|
+
).requiredOption("--image-tag <tag>", "image tag to upgrade to").option("--instance <name>", "select a configured instance").option("--yes", "upgrade without asking for confirmation").action(async (options) => {
|
|
24271
|
+
const { config } = await loadCliState(context);
|
|
24272
|
+
setExitCode(
|
|
24273
|
+
await runUpgradeCommand(
|
|
24274
|
+
config,
|
|
24275
|
+
parsedArgs("upgrade", void 0, [], options),
|
|
24276
|
+
context
|
|
24277
|
+
)
|
|
24278
|
+
);
|
|
24279
|
+
});
|
|
24280
|
+
}
|
|
23127
24281
|
|
|
23128
24282
|
// src/commands/version/action.ts
|
|
24283
|
+
import { isSea as isSea2 } from "node:sea";
|
|
23129
24284
|
function versionText() {
|
|
23130
24285
|
return `${package_default.name} ${package_default.version}`;
|
|
23131
24286
|
}
|
|
@@ -23232,6 +24387,8 @@ function createProgram(context, setExitCode) {
|
|
|
23232
24387
|
for (const command of createDeploymentCommands(context, setExitCode)) {
|
|
23233
24388
|
program2.addCommand(command);
|
|
23234
24389
|
}
|
|
24390
|
+
program2.addCommand(createUpgradeCommand(context, setExitCode));
|
|
24391
|
+
program2.addCommand(createOpenCommand(context, setExitCode));
|
|
23235
24392
|
program2.addCommand(createInstanceCommand(context, setExitCode));
|
|
23236
24393
|
program2.addCommand(createConfigCommand(context, setExitCode));
|
|
23237
24394
|
program2.addCommand(createDoctorCommand(context, setExitCode));
|