@eudiplo/cli 8.1.0 → 9.0.0-main.455da95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +6 -6
  2. package/dist/commands/completion/action.js +1 -1
  3. package/dist/commands/completion/action.js.map +1 -1
  4. package/dist/commands/completion/index.d.ts.map +1 -1
  5. package/dist/commands/completion/index.js +6 -1
  6. package/dist/commands/completion/index.js.map +1 -1
  7. package/dist/commands/config/action.d.ts.map +1 -1
  8. package/dist/commands/config/action.js.map +1 -1
  9. package/dist/commands/config/portability/migrations.spec.js +5 -3
  10. package/dist/commands/config/portability/migrations.spec.js.map +1 -1
  11. package/dist/commands/config/validate/schemas.js.map +1 -1
  12. package/dist/commands/config/validate/validator.js +7 -4
  13. package/dist/commands/config/validate/validator.js.map +1 -1
  14. package/dist/commands/doctor/action.d.ts +2 -1
  15. package/dist/commands/doctor/action.d.ts.map +1 -1
  16. package/dist/commands/doctor/action.js +25 -7
  17. package/dist/commands/doctor/action.js.map +1 -1
  18. package/dist/commands/doctor/index.d.ts.map +1 -1
  19. package/dist/commands/doctor/index.js +2 -0
  20. package/dist/commands/doctor/index.js.map +1 -1
  21. package/dist/commands/instance/action.d.ts.map +1 -1
  22. package/dist/commands/instance/action.js.map +1 -1
  23. package/dist/commands/upgrade/action.d.ts.map +1 -1
  24. package/dist/commands/upgrade/action.js +3 -1
  25. package/dist/commands/upgrade/action.js.map +1 -1
  26. package/dist/index.js +3305 -484
  27. package/dist/runtime.js +1 -1
  28. package/dist/runtime.js.map +1 -1
  29. package/dist/services/api-auth.d.ts +31 -0
  30. package/dist/services/api-auth.d.ts.map +1 -0
  31. package/dist/services/api-auth.js +78 -0
  32. package/dist/services/api-auth.js.map +1 -0
  33. package/dist/services/backend-checks.d.ts +24 -0
  34. package/dist/services/backend-checks.d.ts.map +1 -0
  35. package/dist/services/backend-checks.js +105 -0
  36. package/dist/services/backend-checks.js.map +1 -0
  37. package/dist/services/cli-config.d.ts.map +1 -1
  38. package/dist/services/cli-config.js +5 -2
  39. package/dist/services/cli-config.js.map +1 -1
  40. package/dist/services/compose-project.d.ts.map +1 -1
  41. package/dist/services/compose-project.js +20 -15
  42. package/dist/services/compose-project.js.map +1 -1
  43. package/dist/services/compose-upgrade.d.ts +8 -5
  44. package/dist/services/compose-upgrade.d.ts.map +1 -1
  45. package/dist/services/compose-upgrade.js +37 -12
  46. package/dist/services/compose-upgrade.js.map +1 -1
  47. package/dist/services/container-state.d.ts +14 -0
  48. package/dist/services/container-state.d.ts.map +1 -0
  49. package/dist/services/container-state.js +70 -0
  50. package/dist/services/container-state.js.map +1 -0
  51. package/dist/services/deployment-drivers.d.ts.map +1 -1
  52. package/dist/services/deployment-drivers.js +77 -0
  53. package/dist/services/deployment-drivers.js.map +1 -1
  54. package/dist/services/diagnostics.d.ts +19 -2
  55. package/dist/services/diagnostics.d.ts.map +1 -1
  56. package/dist/services/diagnostics.js +219 -50
  57. package/dist/services/diagnostics.js.map +1 -1
  58. package/dist/services/kubectl.d.ts.map +1 -1
  59. package/dist/services/kubectl.js +1 -6
  60. package/dist/services/kubectl.js.map +1 -1
  61. package/dist/services/tls-expiry.d.ts +16 -0
  62. package/dist/services/tls-expiry.d.ts.map +1 -0
  63. package/dist/services/tls-expiry.js +80 -0
  64. package/dist/services/tls-expiry.js.map +1 -0
  65. package/dist/types.d.ts +4 -1
  66. package/dist/types.d.ts.map +1 -1
  67. package/package.json +9 -7
  68. package/templates/demo-config/trust-lists/trustlist-580831bc-ef11-43f4-a3be-a2b6bf1b29a3-config.json +1 -97
  69. package/templates/docker-compose.yml +37 -30
  70. package/templates/schemas/DCQL.schema.json +30 -4
  71. package/templates/schemas/IssuanceConfig.schema.json +13 -0
  72. package/templates/schemas/IssuanceConfigFile.schema.json +663 -1
  73. package/templates/schemas/PresentationConfigCreateDto.schema.json +33 -10
  74. package/templates/schemas/PresentationConfigFile.schema.json +1442 -1
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 count = 0;
2219
+ let count2 = 0;
2220
2220
  for (const key in schema) {
2221
2221
  if (key === "$ref")
2222
2222
  return Infinity;
2223
- count++;
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) => count += countKeys(sch));
2227
+ (0, util_1.eachItem)(schema[key], (sch) => count2 += countKeys(sch));
2228
2228
  }
2229
- if (count === Infinity)
2229
+ if (count2 === Infinity)
2230
2230
  return Infinity;
2231
2231
  }
2232
- return count;
2232
+ return count2;
2233
2233
  }
2234
2234
  function getFullPath(resolver, id = "", normalize) {
2235
2235
  if (normalize !== false)
@@ -3828,7 +3828,7 @@ var require_fast_uri = __commonJS({
3828
3828
  normalizeString(uri, options);
3829
3829
  } else if (typeof uri === "object") {
3830
3830
  uri = /** @type {T} */
3831
- parse4(serialize(uri, options), options);
3831
+ parse5(serialize(uri, options), options);
3832
3832
  }
3833
3833
  return uri;
3834
3834
  }
@@ -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 = parse4(serialize(base, options), options);
3872
- relative3 = parse4(serialize(relative3, options), options);
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 parse4(uri, opts) {
4164
+ function parse5(uri, opts) {
4165
4165
  return parseWithStatus(uri, opts).parsed;
4166
4166
  }
4167
4167
  function normalizeString(uri, opts) {
@@ -4198,7 +4198,7 @@ var require_fast_uri = __commonJS({
4198
4198
  resolveComponent,
4199
4199
  equal,
4200
4200
  serialize,
4201
- parse: parse4
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 count = gen.let("count", 0);
5703
- validateItems(schValid, () => gen.if(schValid, () => checkLimits(count)));
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(count) {
5717
- gen.code((0, codegen_1._)`${count}++`);
5716
+ function checkLimits(count2) {
5717
+ gen.code((0, codegen_1._)`${count2}++`);
5718
5718
  if (max2 === void 0) {
5719
- gen.if((0, codegen_1._)`${count} >= ${min}`, () => gen.assign(valid, true).break());
5719
+ gen.if((0, codegen_1._)`${count2} >= ${min}`, () => gen.assign(valid, true).break());
5720
5720
  } else {
5721
- gen.if((0, codegen_1._)`${count} > ${max2}`, () => gen.assign(valid, false).break());
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._)`${count} >= ${min}`, () => gen.assign(valid, true));
5725
+ gen.if((0, codegen_1._)`${count2} >= ${min}`, () => gen.assign(valid, true));
5726
5726
  }
5727
5727
  }
5728
5728
  }
@@ -11388,12 +11388,7 @@ import { homedir } from "node:os";
11388
11388
 
11389
11389
  // src/services/kubectl.ts
11390
11390
  function scopeArgs(scope) {
11391
- return [
11392
- "--context",
11393
- scope.context,
11394
- "--namespace",
11395
- scope.namespace
11396
- ];
11391
+ return ["--context", scope.context, "--namespace", scope.namespace];
11397
11392
  }
11398
11393
  function resolveScope(instance, instanceName) {
11399
11394
  if (!instance.context) {
@@ -11922,7 +11917,12 @@ function powershellCompletion() {
11922
11917
  }
11923
11918
 
11924
11919
  // src/commands/completion/index.ts
11925
- var supportedShells = ["bash", "zsh", "fish", "powershell"];
11920
+ var supportedShells = [
11921
+ "bash",
11922
+ "zsh",
11923
+ "fish",
11924
+ "powershell"
11925
+ ];
11926
11926
  function createCompletionCommand(context, setExitCode) {
11927
11927
  return new Command("completion").description("Generate shell completion scripts").addArgument(
11928
11928
  new Argument("<shell>", "shell to generate completion for").choices(
@@ -11933,14 +11933,22 @@ function createCompletionCommand(context, setExitCode) {
11933
11933
  });
11934
11934
  }
11935
11935
  function createCompletionCandidatesCommand(context, setExitCode) {
11936
- return new Command("_complete").argument("[words...]").allowUnknownOption(true).action(async (words, _options, command) => {
11937
- if (!command.parent) {
11938
- throw new Error("Completion command is not attached to the CLI root.");
11936
+ return new Command("_complete").argument("[words...]").allowUnknownOption(true).action(
11937
+ async (words, _options, command) => {
11938
+ if (!command.parent) {
11939
+ throw new Error(
11940
+ "Completion command is not attached to the CLI root."
11941
+ );
11942
+ }
11943
+ setExitCode(
11944
+ await runCompletionCandidates(
11945
+ command.parent,
11946
+ words,
11947
+ context
11948
+ )
11949
+ );
11939
11950
  }
11940
- setExitCode(
11941
- await runCompletionCandidates(command.parent, words, context)
11942
- );
11943
- });
11951
+ );
11944
11952
  }
11945
11953
 
11946
11954
  // src/commands/commands/render.ts
@@ -12028,8 +12036,10 @@ function runConfigShow(configPath, config, json, context) {
12028
12036
  }
12029
12037
  context.stdout.write(`Config: ${configPath}
12030
12038
  `);
12031
- context.stdout.write(`Default instance: ${config.defaultInstance ?? "none"}
12032
- `);
12039
+ context.stdout.write(
12040
+ `Default instance: ${config.defaultInstance ?? "none"}
12041
+ `
12042
+ );
12033
12043
  const instances = Object.entries(config.instances).sort(
12034
12044
  ([left], [right]) => left.localeCompare(right)
12035
12045
  );
@@ -12065,10 +12075,10 @@ import { isAbsolute as isAbsolute2, join as join2, relative, resolve as resolve2
12065
12075
  function createScanner(text, ignoreTrivia = false) {
12066
12076
  const len = text.length;
12067
12077
  let pos = 0, value = "", tokenOffset = 0, token = 16, lineNumber = 0, lineStartOffset = 0, tokenLineStartOffset = 0, prevTokenLineStartOffset = 0, scanError = 0;
12068
- function scanHexDigits(count, exact) {
12078
+ function scanHexDigits(count2, exact) {
12069
12079
  let digits = 0;
12070
12080
  let value2 = 0;
12071
- while (digits < count || !exact) {
12081
+ while (digits < count2 || !exact) {
12072
12082
  let ch = text.charCodeAt(pos);
12073
12083
  if (ch >= 48 && ch <= 57) {
12074
12084
  value2 = value2 * 16 + ch - 48;
@@ -12082,7 +12092,7 @@ function createScanner(text, ignoreTrivia = false) {
12082
12092
  pos++;
12083
12093
  digits++;
12084
12094
  }
12085
- if (digits < count) {
12095
+ if (digits < count2) {
12086
12096
  value2 = -1;
12087
12097
  }
12088
12098
  return value2;
@@ -12707,9 +12717,9 @@ function format(documentText, range, options) {
12707
12717
  }
12708
12718
  return editOperations;
12709
12719
  }
12710
- function repeat(s, count) {
12720
+ function repeat(s, count2) {
12711
12721
  let result = "";
12712
- for (let i2 = 0; i2 < count; i2++) {
12722
+ for (let i2 = 0; i2 < count2; i2++) {
12713
12723
  result += s;
12714
12724
  }
12715
12725
  return result;
@@ -14887,7 +14897,7 @@ var CONFIG_FORMATS = {
14887
14897
  IssuanceConfig: {
14888
14898
  slug: "issuance-config",
14889
14899
  file: "IssuanceConfigFile",
14890
- version: 1
14900
+ version: 2
14891
14901
  },
14892
14902
  CredentialConfig: {
14893
14903
  slug: "credential-config",
@@ -14897,7 +14907,7 @@ var CONFIG_FORMATS = {
14897
14907
  PresentationConfig: {
14898
14908
  slug: "presentation-config",
14899
14909
  file: "PresentationConfigFile",
14900
- version: 1
14910
+ version: 3
14901
14911
  },
14902
14912
  AttributeProvider: {
14903
14913
  slug: "attribute-provider",
@@ -14936,7 +14946,53 @@ function resourceId(input) {
14936
14946
  throw new Error(`Configuration spec.${field} is required`);
14937
14947
  return id;
14938
14948
  }
14939
- var CONFIG_MIGRATIONS = [];
14949
+ var presentationConfigV1ToV2 = {
14950
+ id: "presentation-config-v2-provides-attestations",
14951
+ kind: "PresentationConfig",
14952
+ from: 1,
14953
+ to: 2,
14954
+ migrate(spec) {
14955
+ const registrationCert = spec.registration_cert;
14956
+ if (!object(registrationCert) || !object(registrationCert.body) || !("provided_attestations" in registrationCert.body)) {
14957
+ return { spec };
14958
+ }
14959
+ const { provided_attestations: _, ...body } = registrationCert.body;
14960
+ return {
14961
+ spec: { ...spec, registration_cert: { ...registrationCert, body } },
14962
+ issues: [
14963
+ {
14964
+ severity: "warning",
14965
+ code: "PROVIDED_ATTESTATIONS_REMOVED",
14966
+ path: "/spec/registration_cert/body/provided_attestations",
14967
+ message: "provided_attestations is not part of the registrar API and was removed. Set provides_attestations to the credential type identifiers (SD-JWT VC vct or mdoc doctype) the issuer provides."
14968
+ }
14969
+ ]
14970
+ };
14971
+ }
14972
+ };
14973
+ var CONFIG_MIGRATIONS = [
14974
+ {
14975
+ id: "issuance-config-v2-offer-lifetime",
14976
+ kind: "IssuanceConfig",
14977
+ from: 1,
14978
+ to: 2,
14979
+ // v2 only adds the optional offerLifetimeSeconds: a v1 spec is a
14980
+ // valid v2 spec whose offers keep having no lifetime.
14981
+ migrate: (spec) => ({ spec })
14982
+ },
14983
+ presentationConfigV1ToV2,
14984
+ {
14985
+ id: "presentation-config-v3-typed-claim-values",
14986
+ kind: "PresentationConfig",
14987
+ from: 2,
14988
+ to: 3,
14989
+ // v3 accepts integer and boolean DCQL claim `values` besides strings
14990
+ // and requires them to be non-empty. A v2 spec with non-empty string
14991
+ // values is a valid v3 spec; an empty `values` array fails v3
14992
+ // validation, since it could never match a disclosed claim.
14993
+ migrate: (spec) => ({ spec })
14994
+ }
14995
+ ];
14940
14996
  function schemaUrl(kind, version = CONFIG_FORMATS[kind].version) {
14941
14997
  if (!Number.isSafeInteger(version) || version < 1)
14942
14998
  throw new Error("Invalid config version");
@@ -15260,12 +15316,43 @@ function assertConfigBundle(input) {
15260
15316
  }
15261
15317
 
15262
15318
  // ../../packages/eudiplo-config-format/dist/config-values.js
15319
+ var isVariableChar = (char) => char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "_";
15320
+ function replacePlaceholders(input, replace) {
15321
+ let output = "";
15322
+ let copied = 0;
15323
+ let start = input.indexOf("${");
15324
+ while (start !== -1) {
15325
+ let cursor = start + 2;
15326
+ while (cursor < input.length && isVariableChar(input[cursor]))
15327
+ cursor++;
15328
+ const variable = input.slice(start + 2, cursor);
15329
+ let end = -1;
15330
+ let fallback;
15331
+ if (variable && input[cursor] === "}")
15332
+ end = cursor;
15333
+ else if (variable && input[cursor] === ":") {
15334
+ end = input.indexOf("}", cursor + 1);
15335
+ if (end === -1)
15336
+ break;
15337
+ fallback = input.slice(cursor + 1, end);
15338
+ }
15339
+ if (end === -1) {
15340
+ start = input.indexOf("${", start + 2);
15341
+ continue;
15342
+ }
15343
+ output += input.slice(copied, start);
15344
+ output += replace(input.slice(start, end + 1), variable, fallback);
15345
+ copied = end + 1;
15346
+ start = input.indexOf("${", copied);
15347
+ }
15348
+ return output + input.slice(copied);
15349
+ }
15263
15350
  function resolveConfigVariables(input, env) {
15264
15351
  const issues = [];
15265
15352
  const seen = /* @__PURE__ */ new WeakMap();
15266
15353
  function visit2(value, path2) {
15267
15354
  if (typeof value === "string")
15268
- return value.replace(/\$\{([A-Z0-9_]+)(?::([^}]*))?\}/g, (match, variable, fallback) => {
15355
+ return replacePlaceholders(value, (match, variable, fallback) => {
15269
15356
  if (env[variable] !== void 0 && env[variable] !== "")
15270
15357
  return env[variable];
15271
15358
  if (fallback !== void 0)
@@ -19970,225 +20057,2364 @@ var CONFIG_SCHEMAS = [
19970
20057
  "title": "CreateWebhookEndpointDto"
19971
20058
  }
19972
20059
  }
19973
- }
19974
- ];
19975
-
19976
- // ../../packages/eudiplo-config-format/dist/config-validator.js
19977
- var ajv = new import__.Ajv2020({
19978
- allErrors: true,
19979
- strict: false,
19980
- validateFormats: false
19981
- });
19982
- for (const schema of CONFIG_SCHEMAS)
19983
- ajv.addSchema(schema);
19984
- function validateConfigDocument(document) {
19985
- const file = serializeDocument(document);
19986
- const validate = ajv.getSchema(file.$schema);
19987
- if (!validate)
19988
- throw new Error(`No bundled validator for ${file.$schema}`);
19989
- const issues = [];
19990
- if (!validate(file)) {
19991
- for (const issue of validate.errors ?? [])
19992
- issues.push({
19993
- severity: "error",
19994
- code: "CONFIG_SCHEMA_VALIDATION_FAILED",
19995
- path: issue.instancePath || "/",
19996
- message: issue.keyword === "additionalProperties" ? `Unknown property: ${String(issue.params.additionalProperty)}` : issue.message ?? "Invalid configuration",
19997
- resource: { kind: document.kind, id: resourceId(document) }
19998
- });
19999
- }
20000
- const id = document.kind === "Client" ? document.spec.clientId : document.spec.id;
20001
- if (id !== void 0 && id !== resourceId(document))
20002
- issues.push({
20003
- severity: "error",
20004
- code: "RESOURCE_ID_MISMATCH",
20005
- path: document.kind === "Client" ? "/spec/clientId" : "/spec/id",
20006
- message: "Resource identifier does not match metadata.id"
20007
- });
20008
- return issues;
20009
- }
20010
-
20011
- // src/commands/config/portability/migrations.ts
20012
- function upgradeDocument(input) {
20013
- const result = migrateDocument(input, validateConfigDocument);
20014
- return { ...result, document: serializeDocument(result.document) };
20015
- }
20016
-
20017
- // src/commands/config/portability/upgrade.ts
20018
- var codec = new ConfigBundleCodec();
20019
- function parse3(text, path2) {
20020
- try {
20021
- return JSON.parse(text);
20022
- } catch {
20023
- throw new Error(`Invalid JSON in ${path2}`);
20024
- }
20025
- }
20026
- function upgradeJson(input, file, context) {
20027
- const bundle = Array.isArray(input?.documents);
20028
- if (bundle) assertConfigBundle(input);
20029
- const results = (bundle ? input.documents : [input]).map(
20030
- (document) => upgradeDocument(document)
20031
- );
20032
- let blocked = false;
20033
- for (const result of results) {
20034
- for (const issue of result.issues) {
20035
- context.stderr.write(
20036
- `${file}${issue.path}: ${issue.severity} ${issue.code}: ${issue.message}
20037
- `
20038
- );
20039
- if (issue.severity !== "warning") blocked = true;
20040
- }
20041
- for (const migration of result.migrations)
20042
- context.stdout.write(`${file}: migration ${migration}
20043
- `);
20044
- }
20045
- if (blocked) throw new Error(`${file}: upgrade blocked; no output written`);
20046
- if (!bundle) return results[0].document;
20047
- const documents = results.map(
20048
- (result) => result.document
20049
- );
20050
- const resources = input.manifest.resources.map(
20051
- (resource) => {
20052
- const index = input.documents.findIndex(
20053
- (candidate) => normalizeDocument(candidate).kind === resource.kind && resourceId(candidate) === resource.id
20054
- );
20055
- const document = documents[index];
20056
- return {
20057
- ...resource,
20058
- id: resourceId(document),
20059
- $schema: document.$schema,
20060
- sha256: createHash2("sha256").update(JSON.stringify(document)).digest("hex")
20061
- };
20062
- }
20063
- );
20064
- const output = {
20065
- ...input,
20066
- manifest: { ...input.manifest, formatVersion: 2, resources },
20067
- documents
20068
- };
20069
- assertConfigBundle(output);
20070
- return output;
20071
- }
20072
- function report(input, output, file, parsed, context) {
20073
- const changed = stableConfigJson(input) !== stableConfigJson(output);
20074
- context.stdout.write(
20075
- `${file}: ${changed ? "upgrade required" : "current"}
20076
- `
20077
- );
20078
- if (parsed.flags.diff === true)
20079
- for (const change of configChanges(input, output)) {
20080
- context.stdout.write(
20081
- ` ${change.path}: ${JSON.stringify(change.before) ?? "(absent)"} -> ${JSON.stringify(change.after) ?? "(absent)"}
20082
- `
20083
- );
20084
- }
20085
- return changed;
20086
- }
20087
- function inferredKind(path2) {
20088
- const normalized = path2.split(sep2).join("/");
20089
- const match = TENANT_RESOURCE_REGISTRY.find(
20090
- (entry) => entry.kind === "file" ? normalized === entry.file || normalized.endsWith(`/${entry.file}`) : normalized.endsWith(`/${entry.subfolder}/${basename2(path2)}`) || normalized === `${entry.subfolder}/${basename2(path2)}`
20091
- );
20092
- return CONFIG_RESOURCE_KINDS.find(
20093
- (kind) => `${CONFIG_FORMATS[kind].file}.schema.json` === match?.schemaFile
20094
- );
20095
- }
20096
- function wrapBare(kind, input, path2) {
20097
- if (!input || typeof input !== "object" || Array.isArray(input))
20098
- throw new Error(`${path2}: configuration must be an object`);
20099
- const singleton = {
20100
- Tenant: "tenant",
20101
- KmsConfig: "kms",
20102
- RegistrarConfig: "registrar",
20103
- IssuanceConfig: "issuance"
20104
- };
20105
- const spec = structuredClone(input);
20106
- if (kind === "KeyChain" && spec.key && !spec.keySource) {
20107
- spec.keySource = { type: "private-jwk", jwk: spec.key };
20108
- delete spec.key;
20109
- }
20110
- const id = String(
20111
- singleton[kind] ?? spec.id ?? spec.clientId ?? basename2(path2, ".json")
20112
- );
20113
- if (!singleton[kind]) spec[kind === "Client" ? "clientId" : "id"] = id;
20114
- return { $schema: schemaUrl(kind, 1), metadata: { generation: 1 }, spec };
20115
- }
20116
- async function folderFiles(path2) {
20117
- const files = [];
20118
- for (const entry of await readdir(path2, { withFileTypes: true })) {
20119
- if (entry.name.startsWith(".")) continue;
20120
- const file = join4(path2, entry.name);
20121
- if (entry.isSymbolicLink())
20122
- throw new Error(`Config folder contains a symbolic link: ${file}`);
20123
- if (entry.isDirectory()) files.push(...await folderFiles(file));
20124
- else if (entry.isFile()) files.push(file);
20125
- }
20126
- return files.sort();
20127
- }
20128
- async function upgradeFolder(source, parsed, context) {
20129
- const outputs = /* @__PURE__ */ new Map();
20130
- let changed = 0;
20131
- for (const file of await folderFiles(source)) {
20132
- if (extname(file) !== ".json" || relative2(source, file).split(sep2).includes("images"))
20133
- continue;
20134
- const input = parse3(await readFile4(file, "utf8"), file);
20135
- const kind = inferredKind(relative2(source, file));
20136
- if (!kind && !isConfigDocument(input)) continue;
20137
- const candidate = isConfigDocument(input) ? input : wrapBare(kind, input, file);
20138
- const output = upgradeJson(candidate, file, context);
20139
- if (kind && normalizeDocument(output).kind !== kind)
20140
- throw new Error(`${file}: expected ${kind} configuration`);
20141
- if (report(input, output, relative2(source, file), parsed, context))
20142
- changed++;
20143
- outputs.set(
20144
- relative2(source, file),
20145
- `${JSON.stringify(output, null, 2)}
20146
- `
20147
- );
20148
- }
20149
- if (!outputs.size)
20150
- throw new Error(`No configuration documents found in ${source}`);
20151
- context.stdout.write(
20152
- `${outputs.size - changed} current, ${changed} require upgrading
20153
- `
20154
- );
20155
- if (parsed.flags.check === true) return changed ? 1 : 0;
20156
- if (parsed.flags["dry-run"] === true) return 0;
20157
- const destination = resolve3(
20158
- context.cwd,
20159
- readStringFlag(parsed.flags, "output") ?? `${source}.upgraded`
20160
- );
20161
- const overlap = (parent, child) => {
20162
- const path2 = relative2(parent, child);
20163
- return path2 === "" || !path2.startsWith(`..${sep2}`) && path2 !== "..";
20164
- };
20165
- if (overlap(source, destination) || overlap(destination, source))
20166
- throw new Error(
20167
- "Folder output must be separate from the source directory"
20168
- );
20169
- await lstat(destination).then(
20170
- () => {
20171
- throw new Error(`Output already exists: ${destination}`);
20172
- },
20173
- (error) => {
20174
- if (error.code !== "ENOENT") throw error;
20175
- }
20176
- );
20177
- await mkdir3(dirname3(destination), { recursive: true });
20178
- const staging = await mkdtemp(
20179
- join4(dirname3(destination), ".eudiplo-upgrade-")
20180
- );
20181
- try {
20182
- await cp(source, staging, {
20183
- recursive: true,
20184
- filter: (path2) => path2 === source || !relative2(source, path2).split(sep2).some((part) => part.startsWith("."))
20185
- });
20186
- for (const [path2, contents] of outputs)
20187
- await atomicWriteFile(join4(staging, path2), contents);
20188
- await rename2(staging, destination);
20189
- context.stdout.write(
20190
- `Wrote upgraded configuration folder to ${destination}
20191
- `
20060
+ },
20061
+ {
20062
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
20063
+ "$id": "https://eudiplo.dev/schemas/v2/IssuanceConfigFile.schema.json",
20064
+ "title": "IssuanceConfigFile",
20065
+ "type": "object",
20066
+ "properties": {
20067
+ "$schema": {
20068
+ "type": "string",
20069
+ "const": "https://eudiplo.dev/schemas/v2/IssuanceConfigFile.schema.json"
20070
+ },
20071
+ "metadata": {
20072
+ "type": "object",
20073
+ "properties": {
20074
+ "generation": {
20075
+ "type": "integer",
20076
+ "minimum": 1,
20077
+ "description": "Monotonically increasing configuration generation."
20078
+ },
20079
+ "ownership": {
20080
+ "type": "string",
20081
+ "enum": [
20082
+ "unmanaged",
20083
+ "file-managed"
20084
+ ],
20085
+ "description": "Requested ownership recorded when the document is applied."
20086
+ }
20087
+ },
20088
+ "additionalProperties": false
20089
+ },
20090
+ "spec": {
20091
+ "$ref": "#/$defs/IssuanceConfig"
20092
+ }
20093
+ },
20094
+ "required": [
20095
+ "$schema",
20096
+ "spec"
20097
+ ],
20098
+ "additionalProperties": false,
20099
+ "$defs": {
20100
+ "IssuanceConfig": {
20101
+ "type": "object",
20102
+ "properties": {
20103
+ "batchSize": {
20104
+ "description": "Optional issuance batch size.",
20105
+ "type": "integer",
20106
+ "minimum": 1,
20107
+ "maximum": 9007199254740991
20108
+ },
20109
+ "dPopRequired": {
20110
+ "description": "Require DPoP proofs for issuance endpoints.",
20111
+ "type": "boolean"
20112
+ },
20113
+ "walletAttestationRequired": {
20114
+ "description": "Default wallet attestation requirement for managed authorization servers.",
20115
+ "type": "boolean"
20116
+ },
20117
+ "walletProviderTrustLists": {
20118
+ "description": "Shared wallet provider trust lists for key attestations and default authorization server wallet authentication.",
20119
+ "type": "array",
20120
+ "items": {
20121
+ "type": "object",
20122
+ "properties": {
20123
+ "trustListId": {
20124
+ "type": "string",
20125
+ "minLength": 1
20126
+ },
20127
+ "url": {
20128
+ "description": "URL of the wallet provider trust list.",
20129
+ "type": "string",
20130
+ "format": "uri"
20131
+ },
20132
+ "verifierKey": {
20133
+ "description": "Optional verifier key material used for trust list verification.",
20134
+ "type": "object",
20135
+ "propertyNames": {
20136
+ "type": "string"
20137
+ },
20138
+ "additionalProperties": {}
20139
+ },
20140
+ "verifierX509Der": {
20141
+ "description": "Optional verifier certificate in DER/base64 form.",
20142
+ "type": "string"
20143
+ }
20144
+ },
20145
+ "additionalProperties": false
20146
+ }
20147
+ },
20148
+ "signingKeyId": {
20149
+ "description": "Default signing key chain id for credential issuance.",
20150
+ "type": "string",
20151
+ "minLength": 1
20152
+ },
20153
+ "authorizationServers": {
20154
+ "minItems": 1,
20155
+ "type": "array",
20156
+ "items": {
20157
+ "oneOf": [
20158
+ {
20159
+ "type": "object",
20160
+ "properties": {
20161
+ "type": {
20162
+ "type": "string",
20163
+ "const": "external",
20164
+ "description": "Use an externally managed authorization server."
20165
+ },
20166
+ "id": {
20167
+ "type": "string",
20168
+ "minLength": 1,
20169
+ "description": "Authorization server identifier."
20170
+ },
20171
+ "issuer": {
20172
+ "type": "string",
20173
+ "format": "uri",
20174
+ "description": "Issuer URL for the external authorization server."
20175
+ },
20176
+ "sessionBinding": {
20177
+ "description": "Explicit mapping from an external access-token claim to an existing issuance session.",
20178
+ "type": "object",
20179
+ "properties": {
20180
+ "method": {
20181
+ "type": "string",
20182
+ "const": "access_token_claim",
20183
+ "description": "Read the correlation value from a configured access-token claim."
20184
+ },
20185
+ "claim": {
20186
+ "type": "string",
20187
+ "minLength": 1,
20188
+ "description": "Claim name that contains the issuance-session correlation value."
20189
+ }
20190
+ },
20191
+ "required": [
20192
+ "method",
20193
+ "claim"
20194
+ ],
20195
+ "additionalProperties": false
20196
+ },
20197
+ "label": {
20198
+ "description": "Optional display label for UI selection.",
20199
+ "type": "string"
20200
+ },
20201
+ "enabled": {
20202
+ "description": "Whether this authorization server entry is enabled.",
20203
+ "type": "boolean"
20204
+ }
20205
+ },
20206
+ "required": [
20207
+ "type",
20208
+ "id",
20209
+ "issuer"
20210
+ ],
20211
+ "additionalProperties": false
20212
+ },
20213
+ {
20214
+ "type": "object",
20215
+ "properties": {
20216
+ "type": {
20217
+ "type": "string",
20218
+ "const": "oid4vp",
20219
+ "description": "Use OID4VP-based authorization server chaining."
20220
+ },
20221
+ "id": {
20222
+ "type": "string",
20223
+ "minLength": 1,
20224
+ "description": "Authorization server identifier."
20225
+ },
20226
+ "presentationConfigId": {
20227
+ "type": "string",
20228
+ "minLength": 1,
20229
+ "description": "Presentation configuration id used during authorization."
20230
+ },
20231
+ "immediateWalletRedirect": {
20232
+ "description": "Redirect wallets immediately after authorization response creation.",
20233
+ "type": "boolean"
20234
+ },
20235
+ "token": {
20236
+ "description": "Optional token issuance settings.",
20237
+ "type": "object",
20238
+ "properties": {
20239
+ "lifetimeSeconds": {
20240
+ "description": "Access token lifetime in seconds.",
20241
+ "type": "number",
20242
+ "minimum": 60
20243
+ },
20244
+ "signingKeyId": {
20245
+ "description": "Optional key chain id used to sign issued tokens.",
20246
+ "type": "string",
20247
+ "minLength": 1
20248
+ },
20249
+ "refreshTokenEnabled": {
20250
+ "description": "Enable issuing refresh tokens.",
20251
+ "type": "boolean"
20252
+ },
20253
+ "refreshTokenExpiresInSeconds": {
20254
+ "description": "Refresh token lifetime in seconds.",
20255
+ "type": "number",
20256
+ "minimum": 60
20257
+ }
20258
+ },
20259
+ "additionalProperties": false
20260
+ },
20261
+ "requireDPoP": {
20262
+ "description": "Require DPoP proofs for token/credential requests.",
20263
+ "type": "boolean"
20264
+ },
20265
+ "walletAttestationRequired": {
20266
+ "description": "Require wallet attestation for this authorization server.",
20267
+ "type": "boolean"
20268
+ },
20269
+ "walletProviderTrustLists": {
20270
+ "description": "Optional wallet provider trust list references for this authorization server.",
20271
+ "type": "array",
20272
+ "items": {
20273
+ "type": "object",
20274
+ "properties": {
20275
+ "trustListId": {
20276
+ "type": "string",
20277
+ "minLength": 1
20278
+ },
20279
+ "url": {
20280
+ "description": "URL of the wallet provider trust list.",
20281
+ "type": "string",
20282
+ "format": "uri"
20283
+ },
20284
+ "verifierKey": {
20285
+ "description": "Optional verifier key material used for trust list verification.",
20286
+ "type": "object",
20287
+ "propertyNames": {
20288
+ "type": "string"
20289
+ },
20290
+ "additionalProperties": {}
20291
+ },
20292
+ "verifierX509Der": {
20293
+ "description": "Optional verifier certificate in DER/base64 form.",
20294
+ "type": "string"
20295
+ }
20296
+ },
20297
+ "additionalProperties": false
20298
+ }
20299
+ },
20300
+ "label": {
20301
+ "description": "Optional display label for UI selection.",
20302
+ "type": "string"
20303
+ },
20304
+ "enabled": {
20305
+ "description": "Whether this authorization server entry is enabled.",
20306
+ "type": "boolean"
20307
+ }
20308
+ },
20309
+ "required": [
20310
+ "type",
20311
+ "id",
20312
+ "presentationConfigId"
20313
+ ],
20314
+ "additionalProperties": false
20315
+ },
20316
+ {
20317
+ "type": "object",
20318
+ "properties": {
20319
+ "type": {
20320
+ "type": "string",
20321
+ "const": "chained",
20322
+ "description": "Use upstream OIDC as authorization source."
20323
+ },
20324
+ "id": {
20325
+ "type": "string",
20326
+ "minLength": 1,
20327
+ "description": "Authorization server identifier."
20328
+ },
20329
+ "upstream": {
20330
+ "type": "object",
20331
+ "properties": {
20332
+ "issuer": {
20333
+ "type": "string",
20334
+ "format": "uri",
20335
+ "description": "Upstream OIDC issuer URL."
20336
+ },
20337
+ "clientId": {
20338
+ "type": "string",
20339
+ "minLength": 1,
20340
+ "description": "Client id for upstream OIDC authentication."
20341
+ },
20342
+ "clientSecret": {
20343
+ "description": "Optional client secret for upstream OIDC authentication.",
20344
+ "type": "string",
20345
+ "minLength": 1
20346
+ },
20347
+ "scopes": {
20348
+ "description": "Optional scopes requested from the upstream issuer.",
20349
+ "type": "array",
20350
+ "items": {
20351
+ "type": "string",
20352
+ "minLength": 1
20353
+ }
20354
+ }
20355
+ },
20356
+ "required": [
20357
+ "issuer",
20358
+ "clientId"
20359
+ ],
20360
+ "additionalProperties": false,
20361
+ "description": "Upstream OIDC connection settings."
20362
+ },
20363
+ "token": {
20364
+ "description": "Optional token issuance settings.",
20365
+ "type": "object",
20366
+ "properties": {
20367
+ "lifetimeSeconds": {
20368
+ "description": "Access token lifetime in seconds.",
20369
+ "type": "number",
20370
+ "minimum": 60
20371
+ },
20372
+ "signingKeyId": {
20373
+ "description": "Optional key chain id used to sign issued tokens.",
20374
+ "type": "string",
20375
+ "minLength": 1
20376
+ },
20377
+ "refreshTokenEnabled": {
20378
+ "description": "Enable issuing refresh tokens.",
20379
+ "type": "boolean"
20380
+ },
20381
+ "refreshTokenExpiresInSeconds": {
20382
+ "description": "Refresh token lifetime in seconds.",
20383
+ "type": "number",
20384
+ "minimum": 60
20385
+ }
20386
+ },
20387
+ "additionalProperties": false
20388
+ },
20389
+ "requireDPoP": {
20390
+ "description": "Require DPoP proofs for token/credential requests.",
20391
+ "type": "boolean"
20392
+ },
20393
+ "walletAttestationRequired": {
20394
+ "description": "Require wallet attestation for this authorization server.",
20395
+ "type": "boolean"
20396
+ },
20397
+ "walletProviderTrustLists": {
20398
+ "description": "Optional wallet provider trust list references for this authorization server.",
20399
+ "type": "array",
20400
+ "items": {
20401
+ "type": "object",
20402
+ "properties": {
20403
+ "trustListId": {
20404
+ "type": "string",
20405
+ "minLength": 1
20406
+ },
20407
+ "url": {
20408
+ "description": "URL of the wallet provider trust list.",
20409
+ "type": "string",
20410
+ "format": "uri"
20411
+ },
20412
+ "verifierKey": {
20413
+ "description": "Optional verifier key material used for trust list verification.",
20414
+ "type": "object",
20415
+ "propertyNames": {
20416
+ "type": "string"
20417
+ },
20418
+ "additionalProperties": {}
20419
+ },
20420
+ "verifierX509Der": {
20421
+ "description": "Optional verifier certificate in DER/base64 form.",
20422
+ "type": "string"
20423
+ }
20424
+ },
20425
+ "additionalProperties": false
20426
+ }
20427
+ },
20428
+ "label": {
20429
+ "description": "Optional display label for UI selection.",
20430
+ "type": "string"
20431
+ },
20432
+ "enabled": {
20433
+ "description": "Whether this authorization server entry is enabled.",
20434
+ "type": "boolean"
20435
+ }
20436
+ },
20437
+ "required": [
20438
+ "type",
20439
+ "id",
20440
+ "upstream"
20441
+ ],
20442
+ "additionalProperties": false
20443
+ },
20444
+ {
20445
+ "type": "object",
20446
+ "properties": {
20447
+ "type": {
20448
+ "type": "string",
20449
+ "const": "built-in",
20450
+ "description": "Use EUDIPLO built-in authorization server."
20451
+ },
20452
+ "id": {
20453
+ "type": "string",
20454
+ "minLength": 1,
20455
+ "description": "Authorization server identifier."
20456
+ },
20457
+ "token": {
20458
+ "description": "Optional token issuance settings.",
20459
+ "type": "object",
20460
+ "properties": {
20461
+ "lifetimeSeconds": {
20462
+ "description": "Access token lifetime in seconds.",
20463
+ "type": "number",
20464
+ "minimum": 60
20465
+ },
20466
+ "signingKeyId": {
20467
+ "description": "Optional key chain id used to sign issued tokens.",
20468
+ "type": "string",
20469
+ "minLength": 1
20470
+ },
20471
+ "refreshTokenEnabled": {
20472
+ "description": "Enable issuing refresh tokens.",
20473
+ "type": "boolean"
20474
+ },
20475
+ "refreshTokenExpiresInSeconds": {
20476
+ "description": "Refresh token lifetime in seconds.",
20477
+ "type": "number",
20478
+ "minimum": 60
20479
+ }
20480
+ },
20481
+ "additionalProperties": false
20482
+ },
20483
+ "requireDPoP": {
20484
+ "description": "Require DPoP proofs for token/credential requests.",
20485
+ "type": "boolean"
20486
+ },
20487
+ "walletAttestationRequired": {
20488
+ "description": "Require wallet attestation for this authorization server.",
20489
+ "type": "boolean"
20490
+ },
20491
+ "walletProviderTrustLists": {
20492
+ "description": "Optional wallet provider trust list references for this authorization server.",
20493
+ "type": "array",
20494
+ "items": {
20495
+ "type": "object",
20496
+ "properties": {
20497
+ "trustListId": {
20498
+ "type": "string",
20499
+ "minLength": 1
20500
+ },
20501
+ "url": {
20502
+ "description": "URL of the wallet provider trust list.",
20503
+ "type": "string",
20504
+ "format": "uri"
20505
+ },
20506
+ "verifierKey": {
20507
+ "description": "Optional verifier key material used for trust list verification.",
20508
+ "type": "object",
20509
+ "propertyNames": {
20510
+ "type": "string"
20511
+ },
20512
+ "additionalProperties": {}
20513
+ },
20514
+ "verifierX509Der": {
20515
+ "description": "Optional verifier certificate in DER/base64 form.",
20516
+ "type": "string"
20517
+ }
20518
+ },
20519
+ "additionalProperties": false
20520
+ }
20521
+ },
20522
+ "label": {
20523
+ "description": "Optional display label for UI selection.",
20524
+ "type": "string"
20525
+ },
20526
+ "enabled": {
20527
+ "description": "Whether this authorization server entry is enabled.",
20528
+ "type": "boolean"
20529
+ }
20530
+ },
20531
+ "required": [
20532
+ "type",
20533
+ "id"
20534
+ ],
20535
+ "additionalProperties": false
20536
+ }
20537
+ ],
20538
+ "description": "Supported authorization server configurations."
20539
+ },
20540
+ "description": "Configured authorization server entries."
20541
+ },
20542
+ "federation": {
20543
+ "description": "Optional OpenID Federation settings.",
20544
+ "anyOf": [
20545
+ {
20546
+ "type": "object",
20547
+ "properties": {
20548
+ "role": {
20549
+ "description": "Federation role for this issuer.",
20550
+ "type": "string",
20551
+ "enum": [
20552
+ "trust_anchor",
20553
+ "intermediate",
20554
+ "leaf"
20555
+ ]
20556
+ },
20557
+ "mode": {
20558
+ "description": "Federation operation mode.",
20559
+ "type": "string",
20560
+ "enum": [
20561
+ "federation-only",
20562
+ "hybrid"
20563
+ ]
20564
+ },
20565
+ "entityId": {
20566
+ "description": "Optional local federation entity id.",
20567
+ "type": "string"
20568
+ },
20569
+ "enforceSigningPolicy": {
20570
+ "description": "Enforce strict signing policy checks.",
20571
+ "type": "boolean"
20572
+ },
20573
+ "cacheTtlSeconds": {
20574
+ "description": "Cache time-to-live for federation metadata in seconds.",
20575
+ "type": "integer",
20576
+ "minimum": 1,
20577
+ "maximum": 9007199254740991
20578
+ },
20579
+ "trustAnchors": {
20580
+ "type": "array",
20581
+ "items": {
20582
+ "type": "object",
20583
+ "properties": {
20584
+ "entityId": {
20585
+ "type": "string",
20586
+ "minLength": 1,
20587
+ "description": "Federation trust anchor entity id."
20588
+ },
20589
+ "entityConfigurationUri": {
20590
+ "type": "string",
20591
+ "format": "uri",
20592
+ "description": "Entity configuration URI for the trust anchor."
20593
+ }
20594
+ },
20595
+ "required": [
20596
+ "entityId",
20597
+ "entityConfigurationUri"
20598
+ ],
20599
+ "additionalProperties": false
20600
+ },
20601
+ "description": "Federation trust anchors."
20602
+ }
20603
+ },
20604
+ "required": [
20605
+ "trustAnchors"
20606
+ ],
20607
+ "additionalProperties": false
20608
+ },
20609
+ {
20610
+ "type": "null"
20611
+ }
20612
+ ]
20613
+ },
20614
+ "registrationCertificate": {
20615
+ "description": "Optional registration certificate settings.",
20616
+ "anyOf": [
20617
+ {
20618
+ "type": "object",
20619
+ "properties": {
20620
+ "enabled": {
20621
+ "description": "Enable issuer registration certificate support.",
20622
+ "type": "boolean"
20623
+ },
20624
+ "mode": {
20625
+ "description": "How registration certificate data is provided.",
20626
+ "type": "string",
20627
+ "enum": [
20628
+ "import",
20629
+ "generate"
20630
+ ]
20631
+ },
20632
+ "jwt": {
20633
+ "description": "Optional registration certificate JWT when using import mode.",
20634
+ "type": "string"
20635
+ },
20636
+ "privacyPolicy": {
20637
+ "description": "Optional privacy policy URI.",
20638
+ "type": "string"
20639
+ },
20640
+ "supportUri": {
20641
+ "description": "Optional support URI.",
20642
+ "type": "string"
20643
+ }
20644
+ },
20645
+ "additionalProperties": false
20646
+ },
20647
+ {
20648
+ "type": "null"
20649
+ }
20650
+ ]
20651
+ },
20652
+ "display": {
20653
+ "description": "Localized issuer metadata shown to wallets.",
20654
+ "type": "array",
20655
+ "items": {
20656
+ "type": "object",
20657
+ "properties": {
20658
+ "name": {
20659
+ "description": "Issuer display name.",
20660
+ "type": "string"
20661
+ },
20662
+ "locale": {
20663
+ "description": "Locale tag for this display entry.",
20664
+ "type": "string"
20665
+ },
20666
+ "logo": {
20667
+ "description": "Optional issuer logo metadata.",
20668
+ "type": "object",
20669
+ "properties": {
20670
+ "uri": {
20671
+ "type": "string",
20672
+ "minLength": 1,
20673
+ "description": "Logo URI."
20674
+ },
20675
+ "alt_text": {
20676
+ "description": "Optional localized alternative text.",
20677
+ "type": "string"
20678
+ }
20679
+ },
20680
+ "required": [
20681
+ "uri"
20682
+ ],
20683
+ "additionalProperties": {}
20684
+ }
20685
+ },
20686
+ "additionalProperties": {}
20687
+ }
20688
+ },
20689
+ "notificationEndpointEnabled": {
20690
+ "description": "Whether the OID4VCI notification endpoint is exposed for this issuance configuration.",
20691
+ "type": "boolean"
20692
+ },
20693
+ "credentialResponseEncryption": {
20694
+ "description": "Enable encrypted credential responses.",
20695
+ "type": "boolean"
20696
+ },
20697
+ "credentialRequestEncryption": {
20698
+ "description": "Require encrypted credential requests.",
20699
+ "type": "boolean"
20700
+ },
20701
+ "txCodeMaxAttempts": {
20702
+ "description": "Maximum verification attempts for transaction codes. Null resets to defaults.",
20703
+ "anyOf": [
20704
+ {
20705
+ "type": "integer",
20706
+ "minimum": 1,
20707
+ "maximum": 9007199254740991
20708
+ },
20709
+ {
20710
+ "type": "null"
20711
+ }
20712
+ ]
20713
+ },
20714
+ "offerLifetimeSeconds": {
20715
+ "description": "Default lifetime of credential offers in seconds. Can be overridden per offer request. Null or unset: offers do not expire.",
20716
+ "anyOf": [
20717
+ {
20718
+ "type": "integer",
20719
+ "minimum": 1,
20720
+ "maximum": 9007199254740991
20721
+ },
20722
+ {
20723
+ "type": "null"
20724
+ }
20725
+ ]
20726
+ }
20727
+ },
20728
+ "required": [
20729
+ "authorizationServers"
20730
+ ],
20731
+ "additionalProperties": false,
20732
+ "title": "IssuanceConfig"
20733
+ }
20734
+ }
20735
+ },
20736
+ {
20737
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
20738
+ "$id": "https://eudiplo.dev/schemas/v2/PresentationConfigFile.schema.json",
20739
+ "title": "PresentationConfigFile",
20740
+ "type": "object",
20741
+ "properties": {
20742
+ "$schema": {
20743
+ "type": "string",
20744
+ "const": "https://eudiplo.dev/schemas/v2/PresentationConfigFile.schema.json"
20745
+ },
20746
+ "metadata": {
20747
+ "type": "object",
20748
+ "properties": {
20749
+ "generation": {
20750
+ "type": "integer",
20751
+ "minimum": 1,
20752
+ "description": "Monotonically increasing configuration generation."
20753
+ },
20754
+ "ownership": {
20755
+ "type": "string",
20756
+ "enum": [
20757
+ "unmanaged",
20758
+ "file-managed"
20759
+ ],
20760
+ "description": "Requested ownership recorded when the document is applied."
20761
+ }
20762
+ },
20763
+ "additionalProperties": false
20764
+ },
20765
+ "spec": {
20766
+ "allOf": [
20767
+ {
20768
+ "$ref": "#/$defs/PresentationConfigCreateDto"
20769
+ },
20770
+ {
20771
+ "type": "object",
20772
+ "required": [
20773
+ "id"
20774
+ ],
20775
+ "properties": {
20776
+ "id": {
20777
+ "type": "string",
20778
+ "minLength": 1
20779
+ }
20780
+ }
20781
+ }
20782
+ ]
20783
+ }
20784
+ },
20785
+ "required": [
20786
+ "$schema",
20787
+ "spec"
20788
+ ],
20789
+ "additionalProperties": false,
20790
+ "$defs": {
20791
+ "PresentationConfigCreateDto": {
20792
+ "type": "object",
20793
+ "properties": {
20794
+ "id": {
20795
+ "type": "string",
20796
+ "minLength": 1,
20797
+ "description": "Presentation configuration identifier."
20798
+ },
20799
+ "description": {
20800
+ "description": "Optional presentation configuration description.",
20801
+ "type": [
20802
+ "string",
20803
+ "null"
20804
+ ]
20805
+ },
20806
+ "lifeTime": {
20807
+ "description": "Presentation request lifetime in seconds.",
20808
+ "type": "integer",
20809
+ "minimum": 1,
20810
+ "maximum": 9007199254740991
20811
+ },
20812
+ "skewSeconds": {
20813
+ "description": "Clock skew tolerance in seconds.",
20814
+ "type": "integer",
20815
+ "minimum": 0,
20816
+ "maximum": 9007199254740991
20817
+ },
20818
+ "statusCheckMode": {
20819
+ "description": "Revocation/status check mode.",
20820
+ "type": "string",
20821
+ "enum": [
20822
+ "strict",
20823
+ "best_effort",
20824
+ "disabled"
20825
+ ]
20826
+ },
20827
+ "dcql_query": {
20828
+ "type": "object",
20829
+ "properties": {
20830
+ "credentials": {
20831
+ "minItems": 1,
20832
+ "type": "array",
20833
+ "items": {
20834
+ "oneOf": [
20835
+ {
20836
+ "type": "object",
20837
+ "properties": {
20838
+ "id": {
20839
+ "type": "string",
20840
+ "minLength": 1,
20841
+ "pattern": "^[A-Za-z0-9_-]+$",
20842
+ "description": "Credential query identifier."
20843
+ },
20844
+ "multiple": {
20845
+ "description": "Allow multiple matching credentials.",
20846
+ "type": "boolean"
20847
+ },
20848
+ "claim_sets": {
20849
+ "description": "Optional claim set constraints.",
20850
+ "type": "array",
20851
+ "items": {
20852
+ "type": "array",
20853
+ "items": {
20854
+ "type": "string"
20855
+ }
20856
+ }
20857
+ },
20858
+ "trusted_authorities": {
20859
+ "description": "Optional trusted authority constraints.",
20860
+ "type": "array",
20861
+ "items": {
20862
+ "oneOf": [
20863
+ {
20864
+ "type": "object",
20865
+ "properties": {
20866
+ "type": {
20867
+ "type": "string",
20868
+ "const": "etsi_tl",
20869
+ "description": "Trusted authority type discriminator for ETSI trust lists."
20870
+ },
20871
+ "values": {
20872
+ "type": "array",
20873
+ "items": {
20874
+ "type": "object",
20875
+ "properties": {
20876
+ "trustListId": {
20877
+ "description": "Optional trust list id reference.",
20878
+ "type": "string"
20879
+ },
20880
+ "url": {
20881
+ "description": "Optional trust list URL reference.",
20882
+ "anyOf": [
20883
+ {
20884
+ "type": "string",
20885
+ "format": "uri"
20886
+ },
20887
+ {
20888
+ "type": "string",
20889
+ "pattern": "^<TENANT_URL>(?:\\/.*)?$"
20890
+ }
20891
+ ]
20892
+ },
20893
+ "verifierKey": {
20894
+ "description": "Optional verifier key material.",
20895
+ "type": "object",
20896
+ "propertyNames": {
20897
+ "type": "string"
20898
+ },
20899
+ "additionalProperties": {}
20900
+ },
20901
+ "verifierX509Der": {
20902
+ "description": "Optional verifier certificate in DER/base64 form.",
20903
+ "type": "string"
20904
+ }
20905
+ },
20906
+ "additionalProperties": false
20907
+ },
20908
+ "description": "Trust list references for ETSI TL verification."
20909
+ }
20910
+ },
20911
+ "required": [
20912
+ "type",
20913
+ "values"
20914
+ ],
20915
+ "additionalProperties": false
20916
+ },
20917
+ {
20918
+ "type": "object",
20919
+ "properties": {
20920
+ "type": {
20921
+ "type": "string",
20922
+ "const": "openid_federation",
20923
+ "description": "Trusted authority type discriminator for OpenID Federation."
20924
+ },
20925
+ "values": {
20926
+ "type": "array",
20927
+ "items": {
20928
+ "type": "string"
20929
+ },
20930
+ "description": "OpenID Federation authority identifiers."
20931
+ }
20932
+ },
20933
+ "required": [
20934
+ "type",
20935
+ "values"
20936
+ ],
20937
+ "additionalProperties": false
20938
+ }
20939
+ ]
20940
+ }
20941
+ },
20942
+ "format": {
20943
+ "type": "string",
20944
+ "const": "dc+sd-jwt",
20945
+ "description": "Credential format discriminator."
20946
+ },
20947
+ "meta": {
20948
+ "type": "object",
20949
+ "properties": {
20950
+ "vct_values": {
20951
+ "minItems": 1,
20952
+ "type": "array",
20953
+ "items": {
20954
+ "type": "string"
20955
+ },
20956
+ "description": "Accepted VCT values."
20957
+ }
20958
+ },
20959
+ "required": [
20960
+ "vct_values"
20961
+ ],
20962
+ "additionalProperties": false
20963
+ },
20964
+ "claims": {
20965
+ "description": "Optional claim-level constraints.",
20966
+ "type": "array",
20967
+ "items": {
20968
+ "type": "object",
20969
+ "properties": {
20970
+ "id": {
20971
+ "description": "Optional claim query id.",
20972
+ "type": "string"
20973
+ },
20974
+ "path": {
20975
+ "type": "array",
20976
+ "items": {
20977
+ "type": [
20978
+ "string",
20979
+ "number"
20980
+ ]
20981
+ },
20982
+ "description": "Path to the claim value in presented credentials."
20983
+ },
20984
+ "values": {
20985
+ "description": "Optional allowed values for the claim.",
20986
+ "type": "array",
20987
+ "items": {
20988
+ "type": "string"
20989
+ }
20990
+ }
20991
+ },
20992
+ "required": [
20993
+ "path"
20994
+ ],
20995
+ "additionalProperties": false
20996
+ }
20997
+ }
20998
+ },
20999
+ "required": [
21000
+ "id",
21001
+ "format",
21002
+ "meta"
21003
+ ],
21004
+ "additionalProperties": false
21005
+ },
21006
+ {
21007
+ "type": "object",
21008
+ "properties": {
21009
+ "id": {
21010
+ "type": "string",
21011
+ "minLength": 1,
21012
+ "pattern": "^[A-Za-z0-9_-]+$",
21013
+ "description": "Credential query identifier."
21014
+ },
21015
+ "multiple": {
21016
+ "description": "Allow multiple matching credentials.",
21017
+ "type": "boolean"
21018
+ },
21019
+ "claim_sets": {
21020
+ "description": "Optional claim set constraints.",
21021
+ "type": "array",
21022
+ "items": {
21023
+ "type": "array",
21024
+ "items": {
21025
+ "type": "string"
21026
+ }
21027
+ }
21028
+ },
21029
+ "trusted_authorities": {
21030
+ "description": "Optional trusted authority constraints.",
21031
+ "type": "array",
21032
+ "items": {
21033
+ "oneOf": [
21034
+ {
21035
+ "type": "object",
21036
+ "properties": {
21037
+ "type": {
21038
+ "type": "string",
21039
+ "const": "etsi_tl",
21040
+ "description": "Trusted authority type discriminator for ETSI trust lists."
21041
+ },
21042
+ "values": {
21043
+ "type": "array",
21044
+ "items": {
21045
+ "type": "object",
21046
+ "properties": {
21047
+ "trustListId": {
21048
+ "description": "Optional trust list id reference.",
21049
+ "type": "string"
21050
+ },
21051
+ "url": {
21052
+ "description": "Optional trust list URL reference.",
21053
+ "anyOf": [
21054
+ {
21055
+ "type": "string",
21056
+ "format": "uri"
21057
+ },
21058
+ {
21059
+ "type": "string",
21060
+ "pattern": "^<TENANT_URL>(?:\\/.*)?$"
21061
+ }
21062
+ ]
21063
+ },
21064
+ "verifierKey": {
21065
+ "description": "Optional verifier key material.",
21066
+ "type": "object",
21067
+ "propertyNames": {
21068
+ "type": "string"
21069
+ },
21070
+ "additionalProperties": {}
21071
+ },
21072
+ "verifierX509Der": {
21073
+ "description": "Optional verifier certificate in DER/base64 form.",
21074
+ "type": "string"
21075
+ }
21076
+ },
21077
+ "additionalProperties": false
21078
+ },
21079
+ "description": "Trust list references for ETSI TL verification."
21080
+ }
21081
+ },
21082
+ "required": [
21083
+ "type",
21084
+ "values"
21085
+ ],
21086
+ "additionalProperties": false
21087
+ },
21088
+ {
21089
+ "type": "object",
21090
+ "properties": {
21091
+ "type": {
21092
+ "type": "string",
21093
+ "const": "openid_federation",
21094
+ "description": "Trusted authority type discriminator for OpenID Federation."
21095
+ },
21096
+ "values": {
21097
+ "type": "array",
21098
+ "items": {
21099
+ "type": "string"
21100
+ },
21101
+ "description": "OpenID Federation authority identifiers."
21102
+ }
21103
+ },
21104
+ "required": [
21105
+ "type",
21106
+ "values"
21107
+ ],
21108
+ "additionalProperties": false
21109
+ }
21110
+ ]
21111
+ }
21112
+ },
21113
+ "format": {
21114
+ "type": "string",
21115
+ "const": "mso_mdoc",
21116
+ "description": "Credential format discriminator."
21117
+ },
21118
+ "meta": {
21119
+ "type": "object",
21120
+ "properties": {
21121
+ "doctype_value": {
21122
+ "type": "string",
21123
+ "minLength": 1,
21124
+ "description": "Expected mDoc doctype value."
21125
+ }
21126
+ },
21127
+ "required": [
21128
+ "doctype_value"
21129
+ ],
21130
+ "additionalProperties": false
21131
+ },
21132
+ "claims": {
21133
+ "description": "Optional mDoc claim-level constraints.",
21134
+ "type": "array",
21135
+ "items": {
21136
+ "type": "object",
21137
+ "properties": {
21138
+ "id": {
21139
+ "description": "Optional claim query id.",
21140
+ "type": "string"
21141
+ },
21142
+ "path": {
21143
+ "type": "array",
21144
+ "items": {
21145
+ "type": [
21146
+ "string",
21147
+ "number"
21148
+ ]
21149
+ },
21150
+ "description": "Path to the claim value in presented credentials."
21151
+ },
21152
+ "values": {
21153
+ "description": "Optional allowed values for the claim.",
21154
+ "type": "array",
21155
+ "items": {
21156
+ "type": "string"
21157
+ }
21158
+ },
21159
+ "intent_to_retain": {
21160
+ "description": "Whether relying party intends to retain the claim.",
21161
+ "type": "boolean"
21162
+ }
21163
+ },
21164
+ "required": [
21165
+ "path"
21166
+ ],
21167
+ "additionalProperties": false
21168
+ }
21169
+ }
21170
+ },
21171
+ "required": [
21172
+ "id",
21173
+ "format",
21174
+ "meta"
21175
+ ],
21176
+ "additionalProperties": false
21177
+ }
21178
+ ]
21179
+ },
21180
+ "description": "Credential queries requested by the verifier."
21181
+ },
21182
+ "credential_sets": {
21183
+ "description": "Optional higher-level credential set requirements.",
21184
+ "type": "array",
21185
+ "items": {
21186
+ "type": "object",
21187
+ "properties": {
21188
+ "options": {
21189
+ "minItems": 1,
21190
+ "type": "array",
21191
+ "items": {
21192
+ "minItems": 1,
21193
+ "type": "array",
21194
+ "items": {
21195
+ "type": "string"
21196
+ }
21197
+ },
21198
+ "description": "Alternative credential query id combinations."
21199
+ },
21200
+ "required": {
21201
+ "description": "Whether this credential set is mandatory.",
21202
+ "type": "boolean"
21203
+ }
21204
+ },
21205
+ "required": [
21206
+ "options"
21207
+ ],
21208
+ "additionalProperties": false
21209
+ }
21210
+ }
21211
+ },
21212
+ "required": [
21213
+ "credentials"
21214
+ ],
21215
+ "additionalProperties": false,
21216
+ "description": "DCQL query defining requested credentials and claims."
21217
+ },
21218
+ "transaction_data": {
21219
+ "description": "Optional transaction data descriptors.",
21220
+ "anyOf": [
21221
+ {
21222
+ "type": "array",
21223
+ "items": {
21224
+ "type": "object",
21225
+ "properties": {
21226
+ "type": {
21227
+ "type": "string",
21228
+ "description": "Transaction data type identifier."
21229
+ },
21230
+ "credential_ids": {
21231
+ "type": "array",
21232
+ "items": {
21233
+ "type": "string"
21234
+ },
21235
+ "description": "Credential query ids this transaction data applies to."
21236
+ },
21237
+ "payload": {
21238
+ "description": "Transaction details. Required for TS12 SCA transaction data."
21239
+ }
21240
+ },
21241
+ "required": [
21242
+ "type",
21243
+ "credential_ids"
21244
+ ],
21245
+ "additionalProperties": {}
21246
+ }
21247
+ },
21248
+ {
21249
+ "type": "null"
21250
+ }
21251
+ ]
21252
+ },
21253
+ "registration_cert": {
21254
+ "description": "Optional registration certificate request settings.",
21255
+ "anyOf": [
21256
+ {
21257
+ "type": "object",
21258
+ "properties": {
21259
+ "id": {
21260
+ "type": "string"
21261
+ },
21262
+ "body": {
21263
+ "type": "object",
21264
+ "properties": {
21265
+ "privacy_policy": {
21266
+ "type": "string"
21267
+ },
21268
+ "support_uri": {
21269
+ "type": "string"
21270
+ },
21271
+ "intermediary": {
21272
+ "type": "string"
21273
+ },
21274
+ "purpose": {
21275
+ "type": "array",
21276
+ "items": {
21277
+ "type": "object",
21278
+ "properties": {
21279
+ "lang": {
21280
+ "type": "string"
21281
+ },
21282
+ "content": {
21283
+ "type": "string"
21284
+ }
21285
+ },
21286
+ "required": [
21287
+ "lang",
21288
+ "content"
21289
+ ],
21290
+ "additionalProperties": false
21291
+ }
21292
+ },
21293
+ "credentials": {
21294
+ "type": "array",
21295
+ "items": {
21296
+ "type": "object",
21297
+ "propertyNames": {
21298
+ "type": "string"
21299
+ },
21300
+ "additionalProperties": {}
21301
+ }
21302
+ },
21303
+ "provides_attestations": {
21304
+ "type": "array",
21305
+ "items": {
21306
+ "type": "string",
21307
+ "minLength": 1
21308
+ }
21309
+ }
21310
+ },
21311
+ "additionalProperties": false
21312
+ },
21313
+ "jwt": {
21314
+ "type": "string"
21315
+ }
21316
+ },
21317
+ "additionalProperties": false
21318
+ },
21319
+ {
21320
+ "type": "null"
21321
+ }
21322
+ ]
21323
+ },
21324
+ "registrationCertImportJwt": {
21325
+ "description": "Optional imported registration certificate JWT.",
21326
+ "type": [
21327
+ "string",
21328
+ "null"
21329
+ ]
21330
+ },
21331
+ "registrationCertImportId": {
21332
+ "description": "Optional registrar-side registration certificate id.",
21333
+ "type": [
21334
+ "string",
21335
+ "null"
21336
+ ]
21337
+ },
21338
+ "registrationCertBodyPrivacyPolicy": {
21339
+ "description": "Optional registration certificate privacy policy URI.",
21340
+ "type": [
21341
+ "string",
21342
+ "null"
21343
+ ]
21344
+ },
21345
+ "registrationCertBodySupportUri": {
21346
+ "description": "Optional registration certificate support URI.",
21347
+ "type": [
21348
+ "string",
21349
+ "null"
21350
+ ]
21351
+ },
21352
+ "registrationCertBodyIntermediary": {
21353
+ "description": "Optional registration certificate intermediary.",
21354
+ "type": [
21355
+ "string",
21356
+ "null"
21357
+ ]
21358
+ },
21359
+ "registrationCertBodyPurpose": {
21360
+ "description": "Optional registration certificate purpose entries.",
21361
+ "anyOf": [
21362
+ {
21363
+ "type": "array",
21364
+ "items": {
21365
+ "type": "object",
21366
+ "properties": {
21367
+ "lang": {
21368
+ "type": "string"
21369
+ },
21370
+ "content": {
21371
+ "type": "string"
21372
+ }
21373
+ },
21374
+ "additionalProperties": false
21375
+ }
21376
+ },
21377
+ {
21378
+ "type": "null"
21379
+ }
21380
+ ]
21381
+ },
21382
+ "webhookEndpointId": {
21383
+ "description": "Optional webhook endpoint id for presentation callbacks.",
21384
+ "type": [
21385
+ "string",
21386
+ "null"
21387
+ ]
21388
+ },
21389
+ "attached": {
21390
+ "description": "Optional attachments included with presentation requests.",
21391
+ "anyOf": [
21392
+ {
21393
+ "type": "array",
21394
+ "items": {
21395
+ "type": "object",
21396
+ "properties": {
21397
+ "format": {
21398
+ "type": "string",
21399
+ "description": "Attachment format identifier."
21400
+ },
21401
+ "data": {
21402
+ "description": "Attachment payload."
21403
+ },
21404
+ "credential_ids": {
21405
+ "description": "Optional credential query ids bound to this attachment.",
21406
+ "type": "array",
21407
+ "items": {
21408
+ "type": "string"
21409
+ }
21410
+ }
21411
+ },
21412
+ "required": [
21413
+ "format",
21414
+ "data"
21415
+ ],
21416
+ "additionalProperties": false
21417
+ }
21418
+ },
21419
+ {
21420
+ "type": "null"
21421
+ }
21422
+ ]
21423
+ },
21424
+ "redirectUri": {
21425
+ "description": "Optional redirect URI after presentation completion.",
21426
+ "type": [
21427
+ "string",
21428
+ "null"
21429
+ ]
21430
+ },
21431
+ "accessKeyChainId": {
21432
+ "description": "Optional key chain id for access token/auth operations.",
21433
+ "type": [
21434
+ "string",
21435
+ "null"
21436
+ ]
21437
+ },
21438
+ "readerAuth": {
21439
+ "description": "Whether reader authentication is required for mDoc requests.",
21440
+ "type": [
21441
+ "boolean",
21442
+ "null"
21443
+ ]
21444
+ }
21445
+ },
21446
+ "required": [
21447
+ "id",
21448
+ "dcql_query"
21449
+ ],
21450
+ "additionalProperties": false,
21451
+ "title": "PresentationConfigCreateDto"
21452
+ }
21453
+ }
21454
+ },
21455
+ {
21456
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
21457
+ "$id": "https://eudiplo.dev/schemas/v3/PresentationConfigFile.schema.json",
21458
+ "title": "PresentationConfigFile",
21459
+ "type": "object",
21460
+ "properties": {
21461
+ "$schema": {
21462
+ "type": "string",
21463
+ "const": "https://eudiplo.dev/schemas/v3/PresentationConfigFile.schema.json"
21464
+ },
21465
+ "metadata": {
21466
+ "type": "object",
21467
+ "properties": {
21468
+ "generation": {
21469
+ "type": "integer",
21470
+ "minimum": 1,
21471
+ "description": "Monotonically increasing configuration generation."
21472
+ },
21473
+ "ownership": {
21474
+ "type": "string",
21475
+ "enum": [
21476
+ "unmanaged",
21477
+ "file-managed"
21478
+ ],
21479
+ "description": "Requested ownership recorded when the document is applied."
21480
+ }
21481
+ },
21482
+ "additionalProperties": false
21483
+ },
21484
+ "spec": {
21485
+ "allOf": [
21486
+ {
21487
+ "$ref": "#/$defs/PresentationConfigCreateDto"
21488
+ },
21489
+ {
21490
+ "type": "object",
21491
+ "required": [
21492
+ "id"
21493
+ ],
21494
+ "properties": {
21495
+ "id": {
21496
+ "type": "string",
21497
+ "minLength": 1
21498
+ }
21499
+ }
21500
+ }
21501
+ ]
21502
+ }
21503
+ },
21504
+ "required": [
21505
+ "$schema",
21506
+ "spec"
21507
+ ],
21508
+ "additionalProperties": false,
21509
+ "$defs": {
21510
+ "PresentationConfigCreateDto": {
21511
+ "type": "object",
21512
+ "properties": {
21513
+ "id": {
21514
+ "type": "string",
21515
+ "minLength": 1,
21516
+ "description": "Presentation configuration identifier."
21517
+ },
21518
+ "description": {
21519
+ "description": "Optional presentation configuration description.",
21520
+ "type": [
21521
+ "string",
21522
+ "null"
21523
+ ]
21524
+ },
21525
+ "lifeTime": {
21526
+ "description": "Presentation request lifetime in seconds.",
21527
+ "type": "integer",
21528
+ "minimum": 1,
21529
+ "maximum": 9007199254740991
21530
+ },
21531
+ "skewSeconds": {
21532
+ "description": "Clock skew tolerance in seconds.",
21533
+ "type": "integer",
21534
+ "minimum": 0,
21535
+ "maximum": 9007199254740991
21536
+ },
21537
+ "statusCheckMode": {
21538
+ "description": "Revocation/status check mode.",
21539
+ "type": "string",
21540
+ "enum": [
21541
+ "strict",
21542
+ "best_effort",
21543
+ "disabled"
21544
+ ]
21545
+ },
21546
+ "dcql_query": {
21547
+ "type": "object",
21548
+ "properties": {
21549
+ "credentials": {
21550
+ "minItems": 1,
21551
+ "type": "array",
21552
+ "items": {
21553
+ "oneOf": [
21554
+ {
21555
+ "type": "object",
21556
+ "properties": {
21557
+ "id": {
21558
+ "type": "string",
21559
+ "minLength": 1,
21560
+ "pattern": "^[A-Za-z0-9_-]+$",
21561
+ "description": "Credential query identifier."
21562
+ },
21563
+ "multiple": {
21564
+ "description": "Allow multiple matching credentials.",
21565
+ "type": "boolean"
21566
+ },
21567
+ "claim_sets": {
21568
+ "description": "Optional claim set constraints.",
21569
+ "type": "array",
21570
+ "items": {
21571
+ "type": "array",
21572
+ "items": {
21573
+ "type": "string"
21574
+ }
21575
+ }
21576
+ },
21577
+ "trusted_authorities": {
21578
+ "description": "Optional trusted authority constraints.",
21579
+ "type": "array",
21580
+ "items": {
21581
+ "oneOf": [
21582
+ {
21583
+ "type": "object",
21584
+ "properties": {
21585
+ "type": {
21586
+ "type": "string",
21587
+ "const": "etsi_tl",
21588
+ "description": "Trusted authority type discriminator for ETSI trust lists."
21589
+ },
21590
+ "values": {
21591
+ "type": "array",
21592
+ "items": {
21593
+ "type": "object",
21594
+ "properties": {
21595
+ "trustListId": {
21596
+ "description": "Optional trust list id reference.",
21597
+ "type": "string"
21598
+ },
21599
+ "url": {
21600
+ "description": "Optional trust list URL reference.",
21601
+ "anyOf": [
21602
+ {
21603
+ "type": "string",
21604
+ "format": "uri"
21605
+ },
21606
+ {
21607
+ "type": "string",
21608
+ "pattern": "^<TENANT_URL>(?:\\/.*)?$"
21609
+ }
21610
+ ]
21611
+ },
21612
+ "verifierKey": {
21613
+ "description": "Optional verifier key material.",
21614
+ "type": "object",
21615
+ "propertyNames": {
21616
+ "type": "string"
21617
+ },
21618
+ "additionalProperties": {}
21619
+ },
21620
+ "verifierX509Der": {
21621
+ "description": "Optional verifier certificate in DER/base64 form.",
21622
+ "type": "string"
21623
+ }
21624
+ },
21625
+ "additionalProperties": false
21626
+ },
21627
+ "description": "Trust list references for ETSI TL verification."
21628
+ }
21629
+ },
21630
+ "required": [
21631
+ "type",
21632
+ "values"
21633
+ ],
21634
+ "additionalProperties": false
21635
+ },
21636
+ {
21637
+ "type": "object",
21638
+ "properties": {
21639
+ "type": {
21640
+ "type": "string",
21641
+ "const": "openid_federation",
21642
+ "description": "Trusted authority type discriminator for OpenID Federation."
21643
+ },
21644
+ "values": {
21645
+ "type": "array",
21646
+ "items": {
21647
+ "type": "string"
21648
+ },
21649
+ "description": "OpenID Federation authority identifiers."
21650
+ }
21651
+ },
21652
+ "required": [
21653
+ "type",
21654
+ "values"
21655
+ ],
21656
+ "additionalProperties": false
21657
+ }
21658
+ ]
21659
+ }
21660
+ },
21661
+ "format": {
21662
+ "type": "string",
21663
+ "const": "dc+sd-jwt",
21664
+ "description": "Credential format discriminator."
21665
+ },
21666
+ "meta": {
21667
+ "type": "object",
21668
+ "properties": {
21669
+ "vct_values": {
21670
+ "minItems": 1,
21671
+ "type": "array",
21672
+ "items": {
21673
+ "type": "string"
21674
+ },
21675
+ "description": "Accepted VCT values."
21676
+ }
21677
+ },
21678
+ "required": [
21679
+ "vct_values"
21680
+ ],
21681
+ "additionalProperties": false
21682
+ },
21683
+ "claims": {
21684
+ "description": "Optional claim-level constraints.",
21685
+ "type": "array",
21686
+ "items": {
21687
+ "type": "object",
21688
+ "properties": {
21689
+ "id": {
21690
+ "description": "Optional claim query id.",
21691
+ "type": "string"
21692
+ },
21693
+ "path": {
21694
+ "type": "array",
21695
+ "items": {
21696
+ "type": [
21697
+ "string",
21698
+ "number"
21699
+ ]
21700
+ },
21701
+ "description": "Path to the claim value in presented credentials."
21702
+ },
21703
+ "values": {
21704
+ "description": "Optional allowed values for the claim. A disclosed value must equal one of them in type and value.",
21705
+ "minItems": 1,
21706
+ "type": "array",
21707
+ "items": {
21708
+ "anyOf": [
21709
+ {
21710
+ "type": "string"
21711
+ },
21712
+ {
21713
+ "type": "integer",
21714
+ "minimum": -9007199254740991,
21715
+ "maximum": 9007199254740991
21716
+ },
21717
+ {
21718
+ "type": "boolean"
21719
+ }
21720
+ ]
21721
+ }
21722
+ }
21723
+ },
21724
+ "required": [
21725
+ "path"
21726
+ ],
21727
+ "additionalProperties": false
21728
+ }
21729
+ }
21730
+ },
21731
+ "required": [
21732
+ "id",
21733
+ "format",
21734
+ "meta"
21735
+ ],
21736
+ "additionalProperties": false
21737
+ },
21738
+ {
21739
+ "type": "object",
21740
+ "properties": {
21741
+ "id": {
21742
+ "type": "string",
21743
+ "minLength": 1,
21744
+ "pattern": "^[A-Za-z0-9_-]+$",
21745
+ "description": "Credential query identifier."
21746
+ },
21747
+ "multiple": {
21748
+ "description": "Allow multiple matching credentials.",
21749
+ "type": "boolean"
21750
+ },
21751
+ "claim_sets": {
21752
+ "description": "Optional claim set constraints.",
21753
+ "type": "array",
21754
+ "items": {
21755
+ "type": "array",
21756
+ "items": {
21757
+ "type": "string"
21758
+ }
21759
+ }
21760
+ },
21761
+ "trusted_authorities": {
21762
+ "description": "Optional trusted authority constraints.",
21763
+ "type": "array",
21764
+ "items": {
21765
+ "oneOf": [
21766
+ {
21767
+ "type": "object",
21768
+ "properties": {
21769
+ "type": {
21770
+ "type": "string",
21771
+ "const": "etsi_tl",
21772
+ "description": "Trusted authority type discriminator for ETSI trust lists."
21773
+ },
21774
+ "values": {
21775
+ "type": "array",
21776
+ "items": {
21777
+ "type": "object",
21778
+ "properties": {
21779
+ "trustListId": {
21780
+ "description": "Optional trust list id reference.",
21781
+ "type": "string"
21782
+ },
21783
+ "url": {
21784
+ "description": "Optional trust list URL reference.",
21785
+ "anyOf": [
21786
+ {
21787
+ "type": "string",
21788
+ "format": "uri"
21789
+ },
21790
+ {
21791
+ "type": "string",
21792
+ "pattern": "^<TENANT_URL>(?:\\/.*)?$"
21793
+ }
21794
+ ]
21795
+ },
21796
+ "verifierKey": {
21797
+ "description": "Optional verifier key material.",
21798
+ "type": "object",
21799
+ "propertyNames": {
21800
+ "type": "string"
21801
+ },
21802
+ "additionalProperties": {}
21803
+ },
21804
+ "verifierX509Der": {
21805
+ "description": "Optional verifier certificate in DER/base64 form.",
21806
+ "type": "string"
21807
+ }
21808
+ },
21809
+ "additionalProperties": false
21810
+ },
21811
+ "description": "Trust list references for ETSI TL verification."
21812
+ }
21813
+ },
21814
+ "required": [
21815
+ "type",
21816
+ "values"
21817
+ ],
21818
+ "additionalProperties": false
21819
+ },
21820
+ {
21821
+ "type": "object",
21822
+ "properties": {
21823
+ "type": {
21824
+ "type": "string",
21825
+ "const": "openid_federation",
21826
+ "description": "Trusted authority type discriminator for OpenID Federation."
21827
+ },
21828
+ "values": {
21829
+ "type": "array",
21830
+ "items": {
21831
+ "type": "string"
21832
+ },
21833
+ "description": "OpenID Federation authority identifiers."
21834
+ }
21835
+ },
21836
+ "required": [
21837
+ "type",
21838
+ "values"
21839
+ ],
21840
+ "additionalProperties": false
21841
+ }
21842
+ ]
21843
+ }
21844
+ },
21845
+ "format": {
21846
+ "type": "string",
21847
+ "const": "mso_mdoc",
21848
+ "description": "Credential format discriminator."
21849
+ },
21850
+ "meta": {
21851
+ "type": "object",
21852
+ "properties": {
21853
+ "doctype_value": {
21854
+ "type": "string",
21855
+ "minLength": 1,
21856
+ "description": "Expected mDoc doctype value."
21857
+ }
21858
+ },
21859
+ "required": [
21860
+ "doctype_value"
21861
+ ],
21862
+ "additionalProperties": false
21863
+ },
21864
+ "claims": {
21865
+ "description": "Optional mDoc claim-level constraints.",
21866
+ "type": "array",
21867
+ "items": {
21868
+ "type": "object",
21869
+ "properties": {
21870
+ "id": {
21871
+ "description": "Optional claim query id.",
21872
+ "type": "string"
21873
+ },
21874
+ "path": {
21875
+ "type": "array",
21876
+ "items": {
21877
+ "type": [
21878
+ "string",
21879
+ "number"
21880
+ ]
21881
+ },
21882
+ "description": "Path to the claim value in presented credentials."
21883
+ },
21884
+ "values": {
21885
+ "description": "Optional allowed values for the claim. A disclosed value must equal one of them in type and value.",
21886
+ "minItems": 1,
21887
+ "type": "array",
21888
+ "items": {
21889
+ "anyOf": [
21890
+ {
21891
+ "type": "string"
21892
+ },
21893
+ {
21894
+ "type": "integer",
21895
+ "minimum": -9007199254740991,
21896
+ "maximum": 9007199254740991
21897
+ },
21898
+ {
21899
+ "type": "boolean"
21900
+ }
21901
+ ]
21902
+ }
21903
+ },
21904
+ "intent_to_retain": {
21905
+ "description": "Whether relying party intends to retain the claim.",
21906
+ "type": "boolean"
21907
+ }
21908
+ },
21909
+ "required": [
21910
+ "path"
21911
+ ],
21912
+ "additionalProperties": false
21913
+ }
21914
+ }
21915
+ },
21916
+ "required": [
21917
+ "id",
21918
+ "format",
21919
+ "meta"
21920
+ ],
21921
+ "additionalProperties": false
21922
+ }
21923
+ ]
21924
+ },
21925
+ "description": "Credential queries requested by the verifier."
21926
+ },
21927
+ "credential_sets": {
21928
+ "description": "Optional higher-level credential set requirements.",
21929
+ "type": "array",
21930
+ "items": {
21931
+ "type": "object",
21932
+ "properties": {
21933
+ "options": {
21934
+ "minItems": 1,
21935
+ "type": "array",
21936
+ "items": {
21937
+ "minItems": 1,
21938
+ "type": "array",
21939
+ "items": {
21940
+ "type": "string"
21941
+ }
21942
+ },
21943
+ "description": "Alternative credential query id combinations."
21944
+ },
21945
+ "required": {
21946
+ "description": "Whether this credential set is mandatory.",
21947
+ "type": "boolean"
21948
+ }
21949
+ },
21950
+ "required": [
21951
+ "options"
21952
+ ],
21953
+ "additionalProperties": false
21954
+ }
21955
+ }
21956
+ },
21957
+ "required": [
21958
+ "credentials"
21959
+ ],
21960
+ "additionalProperties": false,
21961
+ "description": "DCQL query defining requested credentials and claims."
21962
+ },
21963
+ "transaction_data": {
21964
+ "description": "Optional transaction data descriptors.",
21965
+ "anyOf": [
21966
+ {
21967
+ "type": "array",
21968
+ "items": {
21969
+ "type": "object",
21970
+ "properties": {
21971
+ "type": {
21972
+ "type": "string",
21973
+ "description": "Transaction data type identifier."
21974
+ },
21975
+ "credential_ids": {
21976
+ "type": "array",
21977
+ "items": {
21978
+ "type": "string"
21979
+ },
21980
+ "description": "Credential query ids this transaction data applies to."
21981
+ },
21982
+ "payload": {
21983
+ "description": "Transaction details. Required for TS12 SCA transaction data."
21984
+ }
21985
+ },
21986
+ "required": [
21987
+ "type",
21988
+ "credential_ids"
21989
+ ],
21990
+ "additionalProperties": {}
21991
+ }
21992
+ },
21993
+ {
21994
+ "type": "null"
21995
+ }
21996
+ ]
21997
+ },
21998
+ "registration_cert": {
21999
+ "description": "Optional registration certificate request settings.",
22000
+ "anyOf": [
22001
+ {
22002
+ "type": "object",
22003
+ "properties": {
22004
+ "id": {
22005
+ "type": "string"
22006
+ },
22007
+ "body": {
22008
+ "type": "object",
22009
+ "properties": {
22010
+ "privacy_policy": {
22011
+ "type": "string"
22012
+ },
22013
+ "support_uri": {
22014
+ "type": "string"
22015
+ },
22016
+ "intermediary": {
22017
+ "type": "string"
22018
+ },
22019
+ "purpose": {
22020
+ "type": "array",
22021
+ "items": {
22022
+ "type": "object",
22023
+ "properties": {
22024
+ "lang": {
22025
+ "type": "string"
22026
+ },
22027
+ "content": {
22028
+ "type": "string"
22029
+ }
22030
+ },
22031
+ "required": [
22032
+ "lang",
22033
+ "content"
22034
+ ],
22035
+ "additionalProperties": false
22036
+ }
22037
+ },
22038
+ "credentials": {
22039
+ "type": "array",
22040
+ "items": {
22041
+ "type": "object",
22042
+ "propertyNames": {
22043
+ "type": "string"
22044
+ },
22045
+ "additionalProperties": {}
22046
+ }
22047
+ },
22048
+ "provides_attestations": {
22049
+ "type": "array",
22050
+ "items": {
22051
+ "type": "string",
22052
+ "minLength": 1
22053
+ }
22054
+ }
22055
+ },
22056
+ "additionalProperties": false
22057
+ },
22058
+ "jwt": {
22059
+ "type": "string"
22060
+ }
22061
+ },
22062
+ "additionalProperties": false
22063
+ },
22064
+ {
22065
+ "type": "null"
22066
+ }
22067
+ ]
22068
+ },
22069
+ "registrationCertImportJwt": {
22070
+ "description": "Optional imported registration certificate JWT.",
22071
+ "type": [
22072
+ "string",
22073
+ "null"
22074
+ ]
22075
+ },
22076
+ "registrationCertImportId": {
22077
+ "description": "Optional registrar-side registration certificate id.",
22078
+ "type": [
22079
+ "string",
22080
+ "null"
22081
+ ]
22082
+ },
22083
+ "registrationCertBodyPrivacyPolicy": {
22084
+ "description": "Optional registration certificate privacy policy URI.",
22085
+ "type": [
22086
+ "string",
22087
+ "null"
22088
+ ]
22089
+ },
22090
+ "registrationCertBodySupportUri": {
22091
+ "description": "Optional registration certificate support URI.",
22092
+ "type": [
22093
+ "string",
22094
+ "null"
22095
+ ]
22096
+ },
22097
+ "registrationCertBodyIntermediary": {
22098
+ "description": "Optional registration certificate intermediary.",
22099
+ "type": [
22100
+ "string",
22101
+ "null"
22102
+ ]
22103
+ },
22104
+ "registrationCertBodyPurpose": {
22105
+ "description": "Optional registration certificate purpose entries.",
22106
+ "anyOf": [
22107
+ {
22108
+ "type": "array",
22109
+ "items": {
22110
+ "type": "object",
22111
+ "properties": {
22112
+ "lang": {
22113
+ "type": "string"
22114
+ },
22115
+ "content": {
22116
+ "type": "string"
22117
+ }
22118
+ },
22119
+ "additionalProperties": false
22120
+ }
22121
+ },
22122
+ {
22123
+ "type": "null"
22124
+ }
22125
+ ]
22126
+ },
22127
+ "webhookEndpointId": {
22128
+ "description": "Optional webhook endpoint id for presentation callbacks.",
22129
+ "type": [
22130
+ "string",
22131
+ "null"
22132
+ ]
22133
+ },
22134
+ "attached": {
22135
+ "description": "Optional attachments included with presentation requests.",
22136
+ "anyOf": [
22137
+ {
22138
+ "type": "array",
22139
+ "items": {
22140
+ "type": "object",
22141
+ "properties": {
22142
+ "format": {
22143
+ "type": "string",
22144
+ "description": "Attachment format identifier."
22145
+ },
22146
+ "data": {
22147
+ "description": "Attachment payload."
22148
+ },
22149
+ "credential_ids": {
22150
+ "description": "Optional credential query ids bound to this attachment.",
22151
+ "type": "array",
22152
+ "items": {
22153
+ "type": "string"
22154
+ }
22155
+ }
22156
+ },
22157
+ "required": [
22158
+ "format",
22159
+ "data"
22160
+ ],
22161
+ "additionalProperties": false
22162
+ }
22163
+ },
22164
+ {
22165
+ "type": "null"
22166
+ }
22167
+ ]
22168
+ },
22169
+ "redirectUri": {
22170
+ "description": "Optional redirect URI after presentation completion.",
22171
+ "type": [
22172
+ "string",
22173
+ "null"
22174
+ ]
22175
+ },
22176
+ "accessKeyChainId": {
22177
+ "description": "Optional key chain id for access token/auth operations.",
22178
+ "type": [
22179
+ "string",
22180
+ "null"
22181
+ ]
22182
+ },
22183
+ "readerAuth": {
22184
+ "description": "Whether reader authentication is required for mDoc requests.",
22185
+ "type": [
22186
+ "boolean",
22187
+ "null"
22188
+ ]
22189
+ }
22190
+ },
22191
+ "required": [
22192
+ "id",
22193
+ "dcql_query"
22194
+ ],
22195
+ "additionalProperties": false,
22196
+ "title": "PresentationConfigCreateDto"
22197
+ }
22198
+ }
22199
+ }
22200
+ ];
22201
+
22202
+ // ../../packages/eudiplo-config-format/dist/config-validator.js
22203
+ var ajv = new import__.Ajv2020({
22204
+ allErrors: true,
22205
+ strict: false,
22206
+ validateFormats: false
22207
+ });
22208
+ for (const schema of CONFIG_SCHEMAS)
22209
+ ajv.addSchema(schema);
22210
+ function validateConfigDocument(document) {
22211
+ const file = serializeDocument(document);
22212
+ const validate = ajv.getSchema(file.$schema);
22213
+ if (!validate)
22214
+ throw new Error(`No bundled validator for ${file.$schema}`);
22215
+ const issues = [];
22216
+ if (!validate(file)) {
22217
+ for (const issue of validate.errors ?? [])
22218
+ issues.push({
22219
+ severity: "error",
22220
+ code: "CONFIG_SCHEMA_VALIDATION_FAILED",
22221
+ path: issue.instancePath || "/",
22222
+ message: issue.keyword === "additionalProperties" ? `Unknown property: ${String(issue.params.additionalProperty)}` : issue.message ?? "Invalid configuration",
22223
+ resource: { kind: document.kind, id: resourceId(document) }
22224
+ });
22225
+ }
22226
+ const id = document.kind === "Client" ? document.spec.clientId : document.spec.id;
22227
+ if (id !== void 0 && id !== resourceId(document))
22228
+ issues.push({
22229
+ severity: "error",
22230
+ code: "RESOURCE_ID_MISMATCH",
22231
+ path: document.kind === "Client" ? "/spec/clientId" : "/spec/id",
22232
+ message: "Resource identifier does not match metadata.id"
22233
+ });
22234
+ return issues;
22235
+ }
22236
+
22237
+ // src/commands/config/portability/migrations.ts
22238
+ function upgradeDocument(input) {
22239
+ const result = migrateDocument(input, validateConfigDocument);
22240
+ return { ...result, document: serializeDocument(result.document) };
22241
+ }
22242
+
22243
+ // src/commands/config/portability/upgrade.ts
22244
+ var codec = new ConfigBundleCodec();
22245
+ function parse3(text, path2) {
22246
+ try {
22247
+ return JSON.parse(text);
22248
+ } catch {
22249
+ throw new Error(`Invalid JSON in ${path2}`);
22250
+ }
22251
+ }
22252
+ function upgradeJson(input, file, context) {
22253
+ const bundle = Array.isArray(input?.documents);
22254
+ if (bundle) assertConfigBundle(input);
22255
+ const results = (bundle ? input.documents : [input]).map(
22256
+ (document) => upgradeDocument(document)
22257
+ );
22258
+ let blocked = false;
22259
+ for (const result of results) {
22260
+ for (const issue of result.issues) {
22261
+ context.stderr.write(
22262
+ `${file}${issue.path}: ${issue.severity} ${issue.code}: ${issue.message}
22263
+ `
22264
+ );
22265
+ if (issue.severity !== "warning") blocked = true;
22266
+ }
22267
+ for (const migration of result.migrations)
22268
+ context.stdout.write(`${file}: migration ${migration}
22269
+ `);
22270
+ }
22271
+ if (blocked) throw new Error(`${file}: upgrade blocked; no output written`);
22272
+ if (!bundle) return results[0].document;
22273
+ const documents = results.map(
22274
+ (result) => result.document
22275
+ );
22276
+ const resources = input.manifest.resources.map(
22277
+ (resource) => {
22278
+ const index = input.documents.findIndex(
22279
+ (candidate) => normalizeDocument(candidate).kind === resource.kind && resourceId(candidate) === resource.id
22280
+ );
22281
+ const document = documents[index];
22282
+ return {
22283
+ ...resource,
22284
+ id: resourceId(document),
22285
+ $schema: document.$schema,
22286
+ sha256: createHash2("sha256").update(JSON.stringify(document)).digest("hex")
22287
+ };
22288
+ }
22289
+ );
22290
+ const output = {
22291
+ ...input,
22292
+ manifest: { ...input.manifest, formatVersion: 2, resources },
22293
+ documents
22294
+ };
22295
+ assertConfigBundle(output);
22296
+ return output;
22297
+ }
22298
+ function report(input, output, file, parsed, context) {
22299
+ const changed = stableConfigJson(input) !== stableConfigJson(output);
22300
+ context.stdout.write(
22301
+ `${file}: ${changed ? "upgrade required" : "current"}
22302
+ `
22303
+ );
22304
+ if (parsed.flags.diff === true)
22305
+ for (const change of configChanges(input, output)) {
22306
+ context.stdout.write(
22307
+ ` ${change.path}: ${JSON.stringify(change.before) ?? "(absent)"} -> ${JSON.stringify(change.after) ?? "(absent)"}
22308
+ `
22309
+ );
22310
+ }
22311
+ return changed;
22312
+ }
22313
+ function inferredKind(path2) {
22314
+ const normalized = path2.split(sep2).join("/");
22315
+ const match = TENANT_RESOURCE_REGISTRY.find(
22316
+ (entry) => entry.kind === "file" ? normalized === entry.file || normalized.endsWith(`/${entry.file}`) : normalized.endsWith(`/${entry.subfolder}/${basename2(path2)}`) || normalized === `${entry.subfolder}/${basename2(path2)}`
22317
+ );
22318
+ return CONFIG_RESOURCE_KINDS.find(
22319
+ (kind) => `${CONFIG_FORMATS[kind].file}.schema.json` === match?.schemaFile
22320
+ );
22321
+ }
22322
+ function wrapBare(kind, input, path2) {
22323
+ if (!input || typeof input !== "object" || Array.isArray(input))
22324
+ throw new Error(`${path2}: configuration must be an object`);
22325
+ const singleton = {
22326
+ Tenant: "tenant",
22327
+ KmsConfig: "kms",
22328
+ RegistrarConfig: "registrar",
22329
+ IssuanceConfig: "issuance"
22330
+ };
22331
+ const spec = structuredClone(input);
22332
+ if (kind === "KeyChain" && spec.key && !spec.keySource) {
22333
+ spec.keySource = { type: "private-jwk", jwk: spec.key };
22334
+ delete spec.key;
22335
+ }
22336
+ const id = String(
22337
+ singleton[kind] ?? spec.id ?? spec.clientId ?? basename2(path2, ".json")
22338
+ );
22339
+ if (!singleton[kind]) spec[kind === "Client" ? "clientId" : "id"] = id;
22340
+ return { $schema: schemaUrl(kind, 1), metadata: { generation: 1 }, spec };
22341
+ }
22342
+ async function folderFiles(path2) {
22343
+ const files = [];
22344
+ for (const entry of await readdir(path2, { withFileTypes: true })) {
22345
+ if (entry.name.startsWith(".")) continue;
22346
+ const file = join4(path2, entry.name);
22347
+ if (entry.isSymbolicLink())
22348
+ throw new Error(`Config folder contains a symbolic link: ${file}`);
22349
+ if (entry.isDirectory()) files.push(...await folderFiles(file));
22350
+ else if (entry.isFile()) files.push(file);
22351
+ }
22352
+ return files.sort();
22353
+ }
22354
+ async function upgradeFolder(source, parsed, context) {
22355
+ const outputs = /* @__PURE__ */ new Map();
22356
+ let changed = 0;
22357
+ for (const file of await folderFiles(source)) {
22358
+ if (extname(file) !== ".json" || relative2(source, file).split(sep2).includes("images"))
22359
+ continue;
22360
+ const input = parse3(await readFile4(file, "utf8"), file);
22361
+ const kind = inferredKind(relative2(source, file));
22362
+ if (!kind && !isConfigDocument(input)) continue;
22363
+ const candidate = isConfigDocument(input) ? input : wrapBare(kind, input, file);
22364
+ const output = upgradeJson(candidate, file, context);
22365
+ if (kind && normalizeDocument(output).kind !== kind)
22366
+ throw new Error(`${file}: expected ${kind} configuration`);
22367
+ if (report(input, output, relative2(source, file), parsed, context))
22368
+ changed++;
22369
+ outputs.set(
22370
+ relative2(source, file),
22371
+ `${JSON.stringify(output, null, 2)}
22372
+ `
22373
+ );
22374
+ }
22375
+ if (!outputs.size)
22376
+ throw new Error(`No configuration documents found in ${source}`);
22377
+ context.stdout.write(
22378
+ `${outputs.size - changed} current, ${changed} require upgrading
22379
+ `
22380
+ );
22381
+ if (parsed.flags.check === true) return changed ? 1 : 0;
22382
+ if (parsed.flags["dry-run"] === true) return 0;
22383
+ const destination = resolve3(
22384
+ context.cwd,
22385
+ readStringFlag(parsed.flags, "output") ?? `${source}.upgraded`
22386
+ );
22387
+ const overlap = (parent, child) => {
22388
+ const path2 = relative2(parent, child);
22389
+ return path2 === "" || !path2.startsWith(`..${sep2}`) && path2 !== "..";
22390
+ };
22391
+ if (overlap(source, destination) || overlap(destination, source))
22392
+ throw new Error(
22393
+ "Folder output must be separate from the source directory"
22394
+ );
22395
+ await lstat(destination).then(
22396
+ () => {
22397
+ throw new Error(`Output already exists: ${destination}`);
22398
+ },
22399
+ (error) => {
22400
+ if (error.code !== "ENOENT") throw error;
22401
+ }
22402
+ );
22403
+ await mkdir3(dirname3(destination), { recursive: true });
22404
+ const staging = await mkdtemp(
22405
+ join4(dirname3(destination), ".eudiplo-upgrade-")
22406
+ );
22407
+ try {
22408
+ await cp(source, staging, {
22409
+ recursive: true,
22410
+ filter: (path2) => path2 === source || !relative2(source, path2).split(sep2).some((part) => part.startsWith("."))
22411
+ });
22412
+ for (const [path2, contents] of outputs)
22413
+ await atomicWriteFile(join4(staging, path2), contents);
22414
+ await rename2(staging, destination);
22415
+ context.stdout.write(
22416
+ `Wrote upgraded configuration folder to ${destination}
22417
+ `
20192
22418
  );
20193
22419
  } finally {
20194
22420
  await rm(staging, { recursive: true, force: true });
@@ -20526,8 +22752,8 @@ function createComposeEnv(options) {
20526
22752
  const authClientSecret = options.authClientSecret ?? "root";
20527
22753
  const lines = [
20528
22754
  `EUDIPLO_ENV_FILE=${envFileName}`,
20529
- `EUDIPLO_IMAGE=ghcr.io/openwallet-foundation/eudiplo:${imageTag}`,
20530
- `EUDIPLO_CLIENT_IMAGE=ghcr.io/openwallet-foundation/eudiplo-client:${imageTag}`,
22755
+ `EUDIPLO_IMAGE=ghcr.io/eudiplo/eudiplo:${imageTag}`,
22756
+ `EUDIPLO_CLIENT_IMAGE=ghcr.io/eudiplo/eudiplo-client:${imageTag}`,
20531
22757
  ...options.mode === "demo" ? ["EUDIPLO_BIND_ADDRESS=127.0.0.1"] : [],
20532
22758
  `EUDIPLO_CONFIG_MOUNT=./${configDirectoryName}:/app/config`,
20533
22759
  "CONFIG_FOLDER=/app/config",
@@ -20537,14 +22763,11 @@ function createComposeEnv(options) {
20537
22763
  `AUTH_CLIENT_ID=${escapeEnvValue(authClientId)}`,
20538
22764
  `AUTH_CLIENT_SECRET=${escapeEnvValue(authClientSecret)}`
20539
22765
  ];
20540
- lines.push(
20541
- "",
20542
- ...databaseEnv(database),
20543
- "",
20544
- ...storageEnv(storage),
20545
- "",
20546
- ...kmsEnv(kms)
20547
- );
22766
+ lines.push("", ...databaseEnv(database), "", ...storageEnv(storage));
22767
+ const kmsLines = kmsEnv(kms);
22768
+ if (kmsLines.length > 0) {
22769
+ lines.push("", ...kmsLines);
22770
+ }
20548
22771
  return `${lines.join("\n")}
20549
22772
  `;
20550
22773
  }
@@ -20592,28 +22815,24 @@ function storageEnv(storage) {
20592
22815
  if (storage === "local") {
20593
22816
  return ["STORAGE_DRIVER=local", "LOCAL_STORAGE_DIR=/app/uploads"];
20594
22817
  }
20595
- const minioPassword = randomSecret();
22818
+ const rustfsPassword = randomSecret();
20596
22819
  return [
20597
22820
  "STORAGE_DRIVER=s3",
20598
- "S3_ENDPOINT=http://minio:9000",
22821
+ "S3_ENDPOINT=http://rustfs:9000",
20599
22822
  "S3_REGION=us-east-1",
20600
- "S3_ACCESS_KEY_ID=minioadmin",
20601
- `S3_SECRET_ACCESS_KEY=${minioPassword}`,
22823
+ "S3_ACCESS_KEY_ID=rustfsadmin",
22824
+ `S3_SECRET_ACCESS_KEY=${rustfsPassword}`,
20602
22825
  "S3_BUCKET=uploads",
20603
22826
  "S3_FORCE_PATH_STYLE=true",
20604
- "MINIO_ROOT_USER=minioadmin",
20605
- `MINIO_ROOT_PASSWORD=${minioPassword}`
22827
+ "RUSTFS_ACCESS_KEY=rustfsadmin",
22828
+ `RUSTFS_SECRET_KEY=${rustfsPassword}`
20606
22829
  ];
20607
22830
  }
20608
22831
  function kmsEnv(kms) {
20609
22832
  if (kms === "db") {
20610
- return ["KM_TYPE=db"];
22833
+ return [];
20611
22834
  }
20612
- return [
20613
- "KM_TYPE=vault",
20614
- "VAULT_ADDR=http://vault:8200",
20615
- "VAULT_TOKEN=root"
20616
- ];
22835
+ return ["VAULT_ADDR=http://vault:8200", "VAULT_TOKEN=root"];
20617
22836
  }
20618
22837
  function randomSecret() {
20619
22838
  return randomBytes(24).toString("base64url");
@@ -20755,10 +22974,10 @@ function formatTextReport(scope, rootPath, results) {
20755
22974
  function formatTenantBlock(result) {
20756
22975
  const lines = [`${result.valid ? "PASS" : "FAIL"} ${result.id}`];
20757
22976
  if (result.valid) {
20758
- for (const [resourceType, count] of Object.entries(
22977
+ for (const [resourceType, count2] of Object.entries(
20759
22978
  result.resourceCounts
20760
22979
  )) {
20761
- lines.push(` ${count} ${pluralize(resourceType, count)}`);
22980
+ lines.push(` ${count2} ${pluralize(resourceType, count2)}`);
20762
22981
  }
20763
22982
  } else {
20764
22983
  lines.push(...formatTenantErrors(result.errors));
@@ -20804,8 +23023,8 @@ function groupErrorsByFile(errors) {
20804
23023
  }
20805
23024
  return Array.from(grouped.entries());
20806
23025
  }
20807
- function pluralize(label, count) {
20808
- return count === 1 ? label : `${label}s`;
23026
+ function pluralize(label, count2) {
23027
+ return count2 === 1 ? label : `${label}s`;
20809
23028
  }
20810
23029
 
20811
23030
  // src/commands/config/validate/validator.ts
@@ -21000,7 +23219,10 @@ async function validateResourceFile(filePath, relativeFile, schemaFile, getValid
21000
23219
  delete spec.key;
21001
23220
  }
21002
23221
  if (!CONFIG_SINGLETON_IDS[kind])
21003
- spec[kind === "Client" ? "clientId" : "id"] ??= basename4(relativeFile, ".json");
23222
+ spec[kind === "Client" ? "clientId" : "id"] ??= basename4(
23223
+ relativeFile,
23224
+ ".json"
23225
+ );
21004
23226
  resolved = {
21005
23227
  $schema: schemaUrl(kind),
21006
23228
  metadata: { generation: 1 },
@@ -21650,6 +23872,68 @@ function assertKnownService(service, known) {
21650
23872
  }
21651
23873
  }
21652
23874
 
23875
+ // src/services/container-state.ts
23876
+ function parseContainerStates(stdout) {
23877
+ const trimmed = stdout.trim();
23878
+ if (!trimmed) {
23879
+ return [];
23880
+ }
23881
+ const entries = trimmed.startsWith("[") ? asArray(JSON.parse(trimmed)) : trimmed.split("\n").map((line) => line.trim()).filter((line) => line.startsWith("{")).map((line) => JSON.parse(line));
23882
+ return entries.map((entry) => {
23883
+ const record = entry;
23884
+ const names = record.Names;
23885
+ return {
23886
+ name: Array.isArray(names) ? String(names[0] ?? "") : String(names ?? record.Name ?? ""),
23887
+ state: String(record.State ?? ""),
23888
+ status: String(record.Status ?? "")
23889
+ };
23890
+ });
23891
+ }
23892
+ function summarizeContainerStates(containers, projectName) {
23893
+ const name = "service containers";
23894
+ if (containers.length === 0) {
23895
+ return {
23896
+ name,
23897
+ status: "warn",
23898
+ message: `No containers found for project ${projectName}. Start it with eudiplo up.`
23899
+ };
23900
+ }
23901
+ const unhealthy = containers.filter(
23902
+ (container) => container.status.toLowerCase().includes("unhealthy")
23903
+ );
23904
+ if (unhealthy.length > 0) {
23905
+ return {
23906
+ name,
23907
+ status: "fail",
23908
+ message: `Unhealthy: ${describe(unhealthy)}`
23909
+ };
23910
+ }
23911
+ const notRunning = containers.filter(
23912
+ (container) => container.state.toLowerCase() !== "running"
23913
+ );
23914
+ if (notRunning.length > 0) {
23915
+ return {
23916
+ name,
23917
+ status: "warn",
23918
+ message: `${containers.length - notRunning.length}/${containers.length} running. Not running: ${describe(notRunning)}`
23919
+ };
23920
+ }
23921
+ const starting = containers.filter(
23922
+ (container) => container.status.toLowerCase().includes("starting")
23923
+ ).length;
23924
+ return {
23925
+ name,
23926
+ status: "pass",
23927
+ message: `${containers.length} container(s) running${starting > 0 ? `, ${starting} still starting` : ""}.`
23928
+ };
23929
+ }
23930
+ function asArray(value) {
23931
+ return Array.isArray(value) ? value : [];
23932
+ }
23933
+ function describe(containers) {
23934
+ return containers.map((container) => `${container.name} (${container.status})`).join(", ");
23935
+ }
23936
+
21653
23937
  // src/services/deployment-drivers.ts
21654
23938
  var drivers = {
21655
23939
  compose: {
@@ -21663,6 +23947,12 @@ var drivers = {
21663
23947
  status: runtime ? "pass" : "warn",
21664
23948
  message: runtime ? `${runtime.name} found at ${runtime.command}` : "Docker or Podman was not found in a supported install location."
21665
23949
  });
23950
+ if (runtime) {
23951
+ checks.push(await composeProviderCheck(runtime, context));
23952
+ checks.push(
23953
+ await composeServicesCheck(runtime, instance, context)
23954
+ );
23955
+ }
21666
23956
  for (const composeFile of getComposeFiles(instance)) {
21667
23957
  let found = true;
21668
23958
  try {
@@ -22258,6 +24548,89 @@ async function resolveComposeService(options) {
22258
24548
  assertKnownService(service, parseServiceList(listed.stdout));
22259
24549
  return service;
22260
24550
  }
24551
+ async function composeProviderCheck(runtime, context) {
24552
+ const version = await captureRuntime(
24553
+ runtime.command,
24554
+ ["compose", "version"],
24555
+ context
24556
+ );
24557
+ if (version.code !== 0) {
24558
+ return {
24559
+ name: "compose provider",
24560
+ status: "fail",
24561
+ message: `${runtime.name} compose is not usable: ${firstLine(version.stderr) || `exit code ${version.code}`}`
24562
+ };
24563
+ }
24564
+ return {
24565
+ name: "compose provider",
24566
+ status: "pass",
24567
+ message: firstLine(version.stdout) || `${runtime.name} compose works`
24568
+ };
24569
+ }
24570
+ async function composeServicesCheck(runtime, instance, context) {
24571
+ if (!instance.projectName) {
24572
+ return {
24573
+ name: "service containers",
24574
+ status: "skip",
24575
+ message: "This instance has no Compose project name, so its containers cannot be identified."
24576
+ };
24577
+ }
24578
+ const listed = await captureRuntime(
24579
+ runtime.command,
24580
+ [
24581
+ "ps",
24582
+ "--all",
24583
+ "--filter",
24584
+ `label=com.docker.compose.project=${instance.projectName}`,
24585
+ "--format",
24586
+ "json"
24587
+ ],
24588
+ context
24589
+ );
24590
+ if (listed.code !== 0) {
24591
+ return {
24592
+ name: "service containers",
24593
+ status: "warn",
24594
+ message: `Containers could not be listed: ${firstLine(listed.stderr) || `exit code ${listed.code}`}`
24595
+ };
24596
+ }
24597
+ try {
24598
+ return summarizeContainerStates(
24599
+ parseContainerStates(listed.stdout),
24600
+ instance.projectName
24601
+ );
24602
+ } catch {
24603
+ return {
24604
+ name: "service containers",
24605
+ status: "warn",
24606
+ message: `The container list returned by ${runtime.name} could not be read.`
24607
+ };
24608
+ }
24609
+ }
24610
+ function captureRuntime(command, args, context) {
24611
+ return new Promise((resolveProcess) => {
24612
+ const child = spawn(command, args, {
24613
+ env: context.env,
24614
+ stdio: ["ignore", "pipe", "pipe"]
24615
+ });
24616
+ let stdout = "";
24617
+ let stderr = "";
24618
+ child.stdout?.on("data", (chunk) => {
24619
+ stdout += String(chunk);
24620
+ });
24621
+ child.stderr?.on("data", (chunk) => {
24622
+ stderr += String(chunk);
24623
+ });
24624
+ child.on(
24625
+ "error",
24626
+ (error) => resolveProcess({ code: 1, stdout, stderr: error.message })
24627
+ );
24628
+ child.on(
24629
+ "close",
24630
+ (code) => resolveProcess({ code: code ?? 1, stdout, stderr })
24631
+ );
24632
+ });
24633
+ }
22261
24634
  async function resolveComposeRuntime(env) {
22262
24635
  const preferredRuntime = parsePreferredRuntime(
22263
24636
  env.EUDIPLO_CONTAINER_RUNTIME
@@ -22486,120 +24859,606 @@ async function runDriverCommand(config, command, parsed, context) {
22486
24859
  context
22487
24860
  });
22488
24861
  }
22489
-
22490
- // src/commands/deployment/index.ts
22491
- var descriptions = {
22492
- up: "Start the selected Compose deployment",
22493
- down: "Stop the selected Compose deployment",
22494
- logs: "Stream logs for the selected deployment",
22495
- ps: "List the running containers or pods for the selected deployment",
22496
- restart: "Restart the workloads of the selected deployment",
22497
- pull: "Download the configured images without changing configuration"
22498
- };
22499
- var serviceScoped = /* @__PURE__ */ new Set([
22500
- "logs",
22501
- "restart",
22502
- "pull"
22503
- ]);
22504
- function createDeploymentCommands(context, setExitCode) {
22505
- return ["up", "down", "logs", "ps", "restart", "pull"].map(
22506
- (name) => {
22507
- const command = new Command(name).description(descriptions[name]).argument(
22508
- "[args...]",
22509
- "additional arguments passed to the deployment runtime"
22510
- ).option("--instance <name>", "select a configured instance");
22511
- if (serviceScoped.has(name)) {
22512
- command.option(
22513
- "--service <name>",
22514
- "select a single service (Compose) or configured workload (Kubernetes)"
22515
- );
22516
- }
22517
- if (name === "logs") {
22518
- command.option("--follow", "stream new output as it arrives").option("--tail <lines>", "number of recent lines to show").option(
22519
- "--since <time>",
22520
- "show logs since a duration (10m, 2h) or timestamp (Compose instances)"
22521
- );
22522
- }
22523
- if (name === "restart") {
22524
- command.option(
22525
- "--no-wait",
22526
- "return without waiting for the rollout to finish (Kubernetes instances)"
22527
- );
24862
+
24863
+ // src/commands/deployment/index.ts
24864
+ var descriptions = {
24865
+ up: "Start the selected Compose deployment",
24866
+ down: "Stop the selected Compose deployment",
24867
+ logs: "Stream logs for the selected deployment",
24868
+ ps: "List the running containers or pods for the selected deployment",
24869
+ restart: "Restart the workloads of the selected deployment",
24870
+ pull: "Download the configured images without changing configuration"
24871
+ };
24872
+ var serviceScoped = /* @__PURE__ */ new Set([
24873
+ "logs",
24874
+ "restart",
24875
+ "pull"
24876
+ ]);
24877
+ function createDeploymentCommands(context, setExitCode) {
24878
+ return ["up", "down", "logs", "ps", "restart", "pull"].map(
24879
+ (name) => {
24880
+ const command = new Command(name).description(descriptions[name]).argument(
24881
+ "[args...]",
24882
+ "additional arguments passed to the deployment runtime"
24883
+ ).option("--instance <name>", "select a configured instance");
24884
+ if (serviceScoped.has(name)) {
24885
+ command.option(
24886
+ "--service <name>",
24887
+ "select a single service (Compose) or configured workload (Kubernetes)"
24888
+ );
24889
+ }
24890
+ if (name === "logs") {
24891
+ command.option("--follow", "stream new output as it arrives").option("--tail <lines>", "number of recent lines to show").option(
24892
+ "--since <time>",
24893
+ "show logs since a duration (10m, 2h) or timestamp (Compose instances)"
24894
+ );
24895
+ }
24896
+ if (name === "restart") {
24897
+ command.option(
24898
+ "--no-wait",
24899
+ "return without waiting for the rollout to finish (Kubernetes instances)"
24900
+ );
24901
+ }
24902
+ return command.action(async (args, options) => {
24903
+ const { config } = await loadCliState(context);
24904
+ setExitCode(
24905
+ await runDriverCommand(
24906
+ config,
24907
+ name,
24908
+ parsedArgs(name, void 0, args, options),
24909
+ context
24910
+ )
24911
+ );
24912
+ });
24913
+ }
24914
+ );
24915
+ }
24916
+
24917
+ // package.json
24918
+ var package_default = {
24919
+ name: "@eudiplo/cli",
24920
+ version: "9.0.0-main.455da95",
24921
+ description: "Deployment-aware command line tools for EUDIPLO",
24922
+ private: false,
24923
+ type: "module",
24924
+ bin: {
24925
+ eudiplo: "dist/index.js"
24926
+ },
24927
+ files: [
24928
+ "dist",
24929
+ "templates",
24930
+ "README.md"
24931
+ ],
24932
+ scripts: {
24933
+ "assets:sync": "node scripts/sync-cli-assets.cjs",
24934
+ "assets:check": "node scripts/check-cli-assets.cjs",
24935
+ build: "pnpm assets:sync && pnpm assets:check && tsc -p tsconfig.json",
24936
+ "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",
24937
+ "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",
24938
+ "build:sea": "node scripts/build-cli-sea.mjs",
24939
+ dev: "tsx src/index.ts",
24940
+ test: "vitest run --root ../.. --project cli",
24941
+ lint: "biome lint .",
24942
+ format: "biome format --write .",
24943
+ "format:check": "biome format .",
24944
+ prepublishOnly: "pnpm build:publish"
24945
+ },
24946
+ keywords: [
24947
+ "eudiplo",
24948
+ "cli",
24949
+ "eudi",
24950
+ "wallet",
24951
+ "oid4vci",
24952
+ "oid4vp"
24953
+ ],
24954
+ repository: {
24955
+ type: "git",
24956
+ url: "git+https://github.com/EUDIPLO/eudiplo.git",
24957
+ directory: "apps/cli"
24958
+ },
24959
+ homepage: "https://github.com/EUDIPLO/eudiplo/tree/main/apps/cli#readme",
24960
+ bugs: {
24961
+ url: "https://github.com/EUDIPLO/eudiplo/issues"
24962
+ },
24963
+ author: "LF Decentralized Trust",
24964
+ license: "Apache-2.0",
24965
+ engines: {
24966
+ node: ">=22.12.0"
24967
+ },
24968
+ publishConfig: {
24969
+ access: "public"
24970
+ },
24971
+ dependencies: {
24972
+ "@eudiplo/config-format": "workspace:*",
24973
+ ajv: "^8.20.0",
24974
+ "ajv-formats": "^3.0.1",
24975
+ commander: "^15.0.0",
24976
+ fflate: "^0.8.3",
24977
+ "jsonc-parser": "^3.3.1"
24978
+ },
24979
+ devDependencies: {
24980
+ "@biomejs/biome": "2.5.14",
24981
+ "@types/node": "^26.4.0",
24982
+ esbuild: "^0.28.2",
24983
+ tsx: "^4.23.13",
24984
+ typescript: "^7.0.2",
24985
+ vitest: "5.0.2"
24986
+ }
24987
+ };
24988
+
24989
+ // src/services/api-auth.ts
24990
+ async function requestAccessToken(baseUrl, context) {
24991
+ const clientId = context.env.EUDIPLO_CLIENT_ID;
24992
+ const clientSecret = context.env.EUDIPLO_CLIENT_SECRET;
24993
+ if (!clientId || !clientSecret) {
24994
+ return {
24995
+ kind: "unavailable",
24996
+ reason: "EUDIPLO_CLIENT_ID and EUDIPLO_CLIENT_SECRET are not set"
24997
+ };
24998
+ }
24999
+ const tokenUrl = new URL("api/oauth2/token", withTrailingSlash(baseUrl));
25000
+ try {
25001
+ const response = await context.fetch(tokenUrl, {
25002
+ method: "POST",
25003
+ headers: { "content-type": "application/x-www-form-urlencoded" },
25004
+ body: new URLSearchParams({
25005
+ grant_type: "client_credentials",
25006
+ client_id: clientId,
25007
+ client_secret: clientSecret
25008
+ }).toString()
25009
+ });
25010
+ if (!response.ok) {
25011
+ return {
25012
+ kind: "error",
25013
+ reason: `the token endpoint returned HTTP ${response.status}`
25014
+ };
25015
+ }
25016
+ const payload = await response.json();
25017
+ const accessToken = typeof payload === "object" && payload !== null && "access_token" in payload ? String(payload.access_token) : "";
25018
+ if (!accessToken) {
25019
+ return {
25020
+ kind: "error",
25021
+ reason: "the token response did not contain an access token"
25022
+ };
25023
+ }
25024
+ return { kind: "token", accessToken };
25025
+ } catch (error) {
25026
+ return {
25027
+ kind: "error",
25028
+ reason: `the token endpoint could not be reached: ${error instanceof Error ? error.message : String(error)}`
25029
+ };
25030
+ }
25031
+ }
25032
+ async function fetchAuthenticated(url, accessToken, context) {
25033
+ try {
25034
+ const response = await context.fetch(url, {
25035
+ method: "GET",
25036
+ headers: { authorization: `Bearer ${accessToken}` }
25037
+ });
25038
+ if (!response.ok) {
25039
+ return {
25040
+ ok: false,
25041
+ status: response.status,
25042
+ reason: `${url.pathname} returned HTTP ${response.status}`
25043
+ };
25044
+ }
25045
+ return { ok: true, body: await response.json() };
25046
+ } catch (error) {
25047
+ return {
25048
+ ok: false,
25049
+ reason: `${url.pathname} could not be reached: ${error instanceof Error ? error.message : String(error)}`
25050
+ };
25051
+ }
25052
+ }
25053
+ function withTrailingSlash(url) {
25054
+ return url.href.endsWith("/") ? url : new URL(`${url.href}/`);
25055
+ }
25056
+
25057
+ // src/services/backend-checks.ts
25058
+ function compareVersions(cliVersion, backendVersion) {
25059
+ const name = "version compatibility";
25060
+ const cli = parse4(cliVersion);
25061
+ const backend = parse4(backendVersion);
25062
+ if (!cli || !backend) {
25063
+ return {
25064
+ name,
25065
+ status: "warn",
25066
+ message: `CLI ${cliVersion} and backend ${backendVersion} cannot be compared as versions.`
25067
+ };
25068
+ }
25069
+ if (cli.major !== backend.major) {
25070
+ return {
25071
+ name,
25072
+ status: "fail",
25073
+ message: `CLI ${cliVersion} and backend ${backendVersion} are different major versions. Install a matching CLI with npm install -g @eudiplo/cli@${backend.major}.`
25074
+ };
25075
+ }
25076
+ if (cli.minor !== backend.minor) {
25077
+ return {
25078
+ name,
25079
+ status: "warn",
25080
+ message: `CLI ${cliVersion} and backend ${backendVersion} differ in minor version; some commands may not be supported.`
25081
+ };
25082
+ }
25083
+ return {
25084
+ name,
25085
+ status: "pass",
25086
+ message: `CLI ${cliVersion} matches backend ${backendVersion}.`
25087
+ };
25088
+ }
25089
+ function readBackendVersion(body) {
25090
+ if (typeof body === "object" && body !== null && "version" in body) {
25091
+ const version = body.version;
25092
+ return typeof version === "string" && version ? version : void 0;
25093
+ }
25094
+ return void 0;
25095
+ }
25096
+ function readProviderHealth(body) {
25097
+ if (!Array.isArray(body)) {
25098
+ throw new Error("the provider health response was not a list");
25099
+ }
25100
+ return body.map((entry) => {
25101
+ const record = entry;
25102
+ return {
25103
+ providerId: String(record.providerId ?? "unknown"),
25104
+ type: String(record.type ?? "unknown"),
25105
+ ok: record.ok === true,
25106
+ latencyMs: typeof record.latencyMs === "number" ? record.latencyMs : void 0,
25107
+ error: typeof record.error === "string" ? record.error : void 0
25108
+ };
25109
+ });
25110
+ }
25111
+ function summarizeProviderHealth(providers) {
25112
+ const name = "KMS providers";
25113
+ if (providers.length === 0) {
25114
+ return {
25115
+ name,
25116
+ status: "warn",
25117
+ message: "No KMS providers are registered for this tenant."
25118
+ };
25119
+ }
25120
+ const failing = providers.filter((provider) => !provider.ok);
25121
+ if (failing.length > 0) {
25122
+ return {
25123
+ name,
25124
+ status: "fail",
25125
+ message: `${failing.length}/${providers.length} provider(s) unhealthy: ${failing.map(
25126
+ (provider) => `${provider.providerId} (${provider.type})${provider.error ? ", health check reported an error" : ""}`
25127
+ ).join(", ")}`
25128
+ };
25129
+ }
25130
+ return {
25131
+ name,
25132
+ status: "pass",
25133
+ message: `${providers.length} provider(s) healthy: ${providers.map(
25134
+ (provider) => `${provider.providerId} (${provider.type}${provider.latencyMs === void 0 ? "" : `, ${provider.latencyMs}ms`})`
25135
+ ).join(", ")}`
25136
+ };
25137
+ }
25138
+ function parse4(version) {
25139
+ const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
25140
+ if (!match) {
25141
+ return void 0;
25142
+ }
25143
+ return {
25144
+ major: Number(match[1]),
25145
+ minor: Number(match[2]),
25146
+ patch: Number(match[3])
25147
+ };
25148
+ }
25149
+
25150
+ // src/services/tls-expiry.ts
25151
+ import { connect } from "node:tls";
25152
+ var defaultExpiryWarningDays = 30;
25153
+ var dayInMs = 24 * 60 * 60 * 1e3;
25154
+ function evaluateCertificate(certificate, now, warningDays = defaultExpiryWarningDays) {
25155
+ const remainingDays = Math.floor(
25156
+ (certificate.validTo.getTime() - now.getTime()) / dayInMs
25157
+ );
25158
+ const expiresOn = certificate.validTo.toISOString().slice(0, 10);
25159
+ if (remainingDays < 0) {
25160
+ return {
25161
+ name: "TLS certificate",
25162
+ status: "fail",
25163
+ message: `The certificate issued by ${certificate.issuer} expired on ${expiresOn}.`
25164
+ };
25165
+ }
25166
+ if (remainingDays <= warningDays) {
25167
+ return {
25168
+ name: "TLS certificate",
25169
+ status: "warn",
25170
+ message: `The certificate expires on ${expiresOn}, in ${remainingDays} day(s). Renew it before then.`
25171
+ };
25172
+ }
25173
+ return {
25174
+ name: "TLS certificate",
25175
+ status: "pass",
25176
+ message: `Valid until ${expiresOn} (${remainingDays} day(s) left), issued by ${certificate.issuer}.`
25177
+ };
25178
+ }
25179
+ var readPeerCertificate = (url) => new Promise((resolvePeer, rejectPeer) => {
25180
+ const socket = connect(
25181
+ {
25182
+ host: url.hostname,
25183
+ port: Number(url.port || 443),
25184
+ servername: url.hostname,
25185
+ rejectUnauthorized: true
25186
+ },
25187
+ () => {
25188
+ const certificate = socket.getPeerCertificate();
25189
+ socket.end();
25190
+ if (!certificate || !certificate.valid_to) {
25191
+ rejectPeer(new Error("no peer certificate was presented"));
25192
+ return;
22528
25193
  }
22529
- return command.action(async (args, options) => {
22530
- const { config } = await loadCliState(context);
22531
- setExitCode(
22532
- await runDriverCommand(
22533
- config,
22534
- name,
22535
- parsedArgs(name, void 0, args, options),
22536
- context
22537
- )
22538
- );
25194
+ resolvePeer({
25195
+ subject: String(certificate.subject?.CN ?? url.hostname),
25196
+ issuer: String(certificate.issuer?.CN ?? "unknown issuer"),
25197
+ validTo: new Date(certificate.valid_to)
22539
25198
  });
22540
25199
  }
22541
25200
  );
25201
+ socket.setTimeout(1e4, () => {
25202
+ socket.destroy();
25203
+ rejectPeer(new Error("the TLS handshake timed out"));
25204
+ });
25205
+ socket.once("error", (error) => {
25206
+ socket.destroy();
25207
+ rejectPeer(error);
25208
+ });
25209
+ });
25210
+ async function checkTlsCertificate(baseUrl, now, read = readPeerCertificate, warningDays = defaultExpiryWarningDays) {
25211
+ if (baseUrl.protocol !== "https:") {
25212
+ return {
25213
+ name: "TLS certificate",
25214
+ status: "skip",
25215
+ message: `${baseUrl.origin} does not use HTTPS, so there is no certificate to check.`
25216
+ };
25217
+ }
25218
+ try {
25219
+ return evaluateCertificate(await read(baseUrl), now, warningDays);
25220
+ } catch (error) {
25221
+ return {
25222
+ name: "TLS certificate",
25223
+ status: "fail",
25224
+ message: `The certificate of ${baseUrl.origin} could not be verified: ${error instanceof Error ? error.message : String(error)}`
25225
+ };
25226
+ }
22542
25227
  }
22543
25228
 
22544
25229
  // src/services/diagnostics.ts
22545
- async function runDoctor(instance, context, driverDiagnostics) {
22546
- const checks = [];
22547
- const baseUrl = parseUrl(instance.url);
22548
- if (!baseUrl) {
22549
- return [
22550
- {
25230
+ var publicUrlCheck = {
25231
+ id: "public-url",
25232
+ async run({ instance, baseUrl }) {
25233
+ if (!baseUrl) {
25234
+ return {
22551
25235
  name: "public URL",
22552
25236
  status: "fail",
22553
25237
  message: `${instance.url} is not a valid absolute URL`
22554
- }
22555
- ];
25238
+ };
25239
+ }
25240
+ return {
25241
+ name: "public URL",
25242
+ status: baseUrl.protocol === "https:" ? "pass" : "warn",
25243
+ message: baseUrl.protocol === "https:" ? `${baseUrl.href} uses HTTPS` : `${baseUrl.href} does not use HTTPS`
25244
+ };
22556
25245
  }
22557
- checks.push({
22558
- name: "public URL",
22559
- status: baseUrl.protocol === "https:" ? "pass" : "warn",
22560
- message: baseUrl.protocol === "https:" ? `${baseUrl.href} uses HTTPS` : `${baseUrl.href} does not use HTTPS`
22561
- });
22562
- checks.push(
22563
- await checkEndpoint("API reachability", baseUrl, "/api/docs", context)
22564
- );
22565
- checks.push(
22566
- await checkEndpoint("health endpoint", baseUrl, "/health", context)
22567
- );
22568
- checks.push(checkAuthentication(context));
22569
- if (instance.clientUrl) {
25246
+ };
25247
+ var apiReachabilityCheck = {
25248
+ id: "api-reachability",
25249
+ run: ({ baseUrl, context }) => requireBaseUrl(
25250
+ "API reachability",
25251
+ baseUrl,
25252
+ (url) => checkEndpoint("API reachability", url, "api/docs", context)
25253
+ )
25254
+ };
25255
+ var healthEndpointCheck = {
25256
+ id: "health-endpoint",
25257
+ run: ({ baseUrl, context }) => requireBaseUrl(
25258
+ "health endpoint",
25259
+ baseUrl,
25260
+ (url) => checkEndpoint("health endpoint", url, "health", context)
25261
+ )
25262
+ };
25263
+ var tlsCertificateCheck = {
25264
+ id: "tls-certificate",
25265
+ run: ({ baseUrl, now, readCertificate }) => requireBaseUrl(
25266
+ "TLS certificate",
25267
+ baseUrl,
25268
+ (url) => checkTlsCertificate(url, now, readCertificate)
25269
+ )
25270
+ };
25271
+ var authenticationCheck = {
25272
+ id: "authentication",
25273
+ async run({ context }) {
25274
+ const configured = Boolean(context.env.EUDIPLO_CLIENT_ID) && Boolean(context.env.EUDIPLO_CLIENT_SECRET);
25275
+ return {
25276
+ name: "authentication configuration",
25277
+ status: configured ? "pass" : "warn",
25278
+ message: configured ? "Client credentials are available in environment variables." : "Set EUDIPLO_CLIENT_ID and EUDIPLO_CLIENT_SECRET when commands need authenticated API access."
25279
+ };
25280
+ }
25281
+ };
25282
+ var clientConnectivityCheck = {
25283
+ id: "client-connectivity",
25284
+ async run({ instance, context }) {
25285
+ if (!instance.clientUrl) {
25286
+ return {
25287
+ name: "client connectivity",
25288
+ status: "skip",
25289
+ message: "No client URL configured for this instance."
25290
+ };
25291
+ }
22570
25292
  const clientUrl = parseUrl(instance.clientUrl);
22571
- checks.push(
22572
- clientUrl ? await checkEndpoint(
22573
- "client connectivity",
22574
- clientUrl,
22575
- "/",
22576
- context
22577
- ) : {
25293
+ if (!clientUrl) {
25294
+ return {
22578
25295
  name: "client connectivity",
22579
25296
  status: "fail",
22580
25297
  message: `${instance.clientUrl} is not a valid absolute URL`
25298
+ };
25299
+ }
25300
+ return checkEndpoint("client connectivity", clientUrl, "", context);
25301
+ }
25302
+ };
25303
+ var versionCompatibilityCheck = {
25304
+ id: "version-compatibility",
25305
+ run: ({ baseUrl, context, accessToken }) => requireBaseUrl("version compatibility", baseUrl, async (url) => {
25306
+ const token = await accessToken();
25307
+ if (token.kind !== "token") {
25308
+ return authUnavailable("version compatibility", token);
25309
+ }
25310
+ const response = await fetchAuthenticated(
25311
+ new URL("api/version", withTrailingSlash(url)),
25312
+ token.accessToken,
25313
+ context
25314
+ );
25315
+ if (!response.ok) {
25316
+ if (response.status === 404) {
25317
+ return {
25318
+ name: "version compatibility",
25319
+ status: "skip",
25320
+ message: "This backend does not expose /api/version, so compatibility cannot be checked."
25321
+ };
22581
25322
  }
25323
+ return {
25324
+ name: "version compatibility",
25325
+ status: "fail",
25326
+ message: `The backend version could not be read: ${response.reason}`
25327
+ };
25328
+ }
25329
+ const backendVersion = readBackendVersion(response.body);
25330
+ if (!backendVersion) {
25331
+ return {
25332
+ name: "version compatibility",
25333
+ status: "warn",
25334
+ message: "The backend did not report a version."
25335
+ };
25336
+ }
25337
+ return compareVersions(package_default.version, backendVersion);
25338
+ })
25339
+ };
25340
+ var kmsHealthCheck = {
25341
+ id: "kms-health",
25342
+ run: ({ baseUrl, context, accessToken }) => requireBaseUrl("KMS providers", baseUrl, async (url) => {
25343
+ const token = await accessToken();
25344
+ if (token.kind !== "token") {
25345
+ return authUnavailable("KMS providers", token);
25346
+ }
25347
+ const response = await fetchAuthenticated(
25348
+ new URL(
25349
+ "api/key-chain/providers/health",
25350
+ withTrailingSlash(url)
25351
+ ),
25352
+ token.accessToken,
25353
+ context
22582
25354
  );
22583
- } else {
22584
- checks.push({
22585
- name: "client connectivity",
22586
- status: "warn",
22587
- message: "No client URL configured for this instance."
22588
- });
25355
+ if (!response.ok) {
25356
+ if (response.status === 404) {
25357
+ return {
25358
+ name: "KMS providers",
25359
+ status: "skip",
25360
+ message: "This backend does not expose the KMS provider health endpoint."
25361
+ };
25362
+ }
25363
+ return {
25364
+ name: "KMS providers",
25365
+ status: "fail",
25366
+ message: `Provider health could not be read: ${response.reason}`
25367
+ };
25368
+ }
25369
+ try {
25370
+ return summarizeProviderHealth(
25371
+ readProviderHealth(response.body)
25372
+ );
25373
+ } catch {
25374
+ return {
25375
+ name: "KMS providers",
25376
+ status: "warn",
25377
+ message: "The provider health response could not be read."
25378
+ };
25379
+ }
25380
+ })
25381
+ };
25382
+ function authUnavailable(name, token) {
25383
+ return {
25384
+ name,
25385
+ status: token.kind === "unavailable" ? "skip" : "fail",
25386
+ message: token.kind === "unavailable" ? `Needs authentication: ${token.reason}.` : `Could not authenticate: ${token.reason}.`
25387
+ };
25388
+ }
25389
+ var coreChecks = [
25390
+ publicUrlCheck,
25391
+ apiReachabilityCheck,
25392
+ healthEndpointCheck,
25393
+ tlsCertificateCheck,
25394
+ authenticationCheck,
25395
+ versionCompatibilityCheck,
25396
+ kmsHealthCheck,
25397
+ clientConnectivityCheck
25398
+ ];
25399
+ async function runDoctor(instance, context, driverDiagnostics, options = {}) {
25400
+ const baseUrl = parseUrl(instance.url);
25401
+ let token;
25402
+ const checkContext = {
25403
+ instance,
25404
+ context,
25405
+ baseUrl,
25406
+ now: options.now ?? /* @__PURE__ */ new Date(),
25407
+ readCertificate: options.readCertificate ?? context.readCertificate ?? readPeerCertificate,
25408
+ accessToken: () => {
25409
+ if (!baseUrl) {
25410
+ return Promise.resolve({
25411
+ kind: "unavailable",
25412
+ reason: "the instance URL could not be parsed"
25413
+ });
25414
+ }
25415
+ token ??= requestAccessToken(baseUrl, context);
25416
+ return token;
25417
+ }
25418
+ };
25419
+ const checks = [];
25420
+ for (const check of coreChecks) {
25421
+ checks.push(await check.run(checkContext));
22589
25422
  }
22590
25423
  checks.push(...driverDiagnostics);
22591
25424
  return checks;
22592
25425
  }
22593
- function hasFailedChecks(checks) {
22594
- return checks.some((check) => check.status === "fail");
25426
+ function summarizeChecks(checks) {
25427
+ return {
25428
+ pass: count(checks, "pass"),
25429
+ warn: count(checks, "warn"),
25430
+ fail: count(checks, "fail"),
25431
+ skip: count(checks, "skip")
25432
+ };
25433
+ }
25434
+ function hasFailedChecks(checks, strict = false) {
25435
+ return checks.some(
25436
+ (check) => check.status === "fail" || strict && check.status === "warn"
25437
+ );
22595
25438
  }
22596
25439
  function formatChecks(checks) {
22597
25440
  return checks.map(
22598
25441
  (check) => `${formatStatus(check.status)} ${check.name}: ${check.message}`
22599
25442
  ).join("\n");
22600
25443
  }
25444
+ function formatSummary(summary) {
25445
+ return `${summary.pass} passed, ${summary.warn} warning(s), ${summary.fail} failure(s), ${summary.skip} skipped`;
25446
+ }
25447
+ function count(checks, status) {
25448
+ return checks.filter((check) => check.status === status).length;
25449
+ }
25450
+ async function requireBaseUrl(name, baseUrl, run) {
25451
+ if (!baseUrl) {
25452
+ return {
25453
+ name,
25454
+ status: "skip",
25455
+ message: "The instance URL could not be parsed."
25456
+ };
25457
+ }
25458
+ return run(baseUrl);
25459
+ }
22601
25460
  async function checkEndpoint(name, baseUrl, path2, context) {
22602
- const url = new URL(path2, baseUrl);
25461
+ const url = new URL(path2, withTrailingSlash(baseUrl));
22603
25462
  try {
22604
25463
  const response = await context.fetch(url, { method: "GET" });
22605
25464
  if (response.ok) {
@@ -22622,22 +25481,6 @@ async function checkEndpoint(name, baseUrl, path2, context) {
22622
25481
  };
22623
25482
  }
22624
25483
  }
22625
- function checkAuthentication(context) {
22626
- const hasClientId = Boolean(context.env.EUDIPLO_CLIENT_ID);
22627
- const hasClientSecret = Boolean(context.env.EUDIPLO_CLIENT_SECRET);
22628
- if (hasClientId && hasClientSecret) {
22629
- return {
22630
- name: "authentication configuration",
22631
- status: "pass",
22632
- message: "Client credentials are available in environment variables."
22633
- };
22634
- }
22635
- return {
22636
- name: "authentication configuration",
22637
- status: "warn",
22638
- message: "Set EUDIPLO_CLIENT_ID and EUDIPLO_CLIENT_SECRET when commands need authenticated API access."
22639
- };
22640
- }
22641
25484
  function parseUrl(value) {
22642
25485
  try {
22643
25486
  const url = new URL(value);
@@ -22653,28 +25496,54 @@ function formatStatus(status) {
22653
25496
  if (status === "warn") {
22654
25497
  return "WARN";
22655
25498
  }
25499
+ if (status === "skip") {
25500
+ return "SKIP";
25501
+ }
22656
25502
  return "FAIL";
22657
25503
  }
22658
25504
 
22659
25505
  // src/commands/doctor/action.ts
22660
- async function runDoctorCommand(config, parsed, context) {
22661
- const [instanceName, instance] = resolveInstance(config, parsed);
25506
+ async function runDoctorCommand(config, parsed, context, options = {}) {
25507
+ const strict = parsed.flags.strict === true;
25508
+ const instances = parsed.flags.all === true ? Object.keys(config.instances) : [resolveInstance(config, parsed)[0]];
25509
+ if (instances.length === 0) {
25510
+ throw new Error("No instances are configured.");
25511
+ }
25512
+ let failed = false;
25513
+ for (const [index, instanceName] of instances.entries()) {
25514
+ const instance = config.instances[instanceName];
25515
+ if (index > 0) {
25516
+ context.stdout.write("\n");
25517
+ }
25518
+ const checks = await runDiagnostics(instance, context, options);
25519
+ context.stdout.write(
25520
+ `Doctor for ${instanceName} (${instance.target})
25521
+ `
25522
+ );
25523
+ context.stdout.write(`${formatChecks(checks)}
25524
+ `);
25525
+ context.stdout.write(`${formatSummary(summarizeChecks(checks))}
25526
+ `);
25527
+ failed = hasFailedChecks(checks, strict) || failed;
25528
+ }
25529
+ return failed ? 1 : 0;
25530
+ }
25531
+ async function runDiagnostics(instance, context, options) {
22662
25532
  const driver = drivers[instance.target];
22663
- const checks = await runDoctor(
25533
+ return runDoctor(
22664
25534
  instance,
22665
25535
  context,
22666
- await driver.diagnostics(instance, context)
25536
+ await driver.diagnostics(instance, context),
25537
+ options
22667
25538
  );
22668
- context.stdout.write(`Doctor for ${instanceName} (${instance.target})
22669
- `);
22670
- context.stdout.write(`${formatChecks(checks)}
22671
- `);
22672
- return hasFailedChecks(checks) ? 1 : 0;
22673
25539
  }
22674
25540
 
22675
25541
  // src/commands/doctor/index.ts
22676
25542
  function createDoctorCommand(context, setExitCode) {
22677
- return new Command("doctor").description("Run deployment diagnostics").option("--instance <name>", "select a configured instance").action(async (options) => {
25543
+ 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(
25544
+ "--strict",
25545
+ "treat warnings as failures (skipped checks never fail)"
25546
+ ).action(async (options) => {
22678
25547
  const { config } = await loadCliState(context);
22679
25548
  setExitCode(
22680
25549
  await runDoctorCommand(
@@ -23070,7 +25939,9 @@ function runInstanceList(config, context) {
23070
25939
  function runInstanceShow(config, parsed, context) {
23071
25940
  const name = parsed.positionals[0] ?? config.defaultInstance;
23072
25941
  if (!name) {
23073
- throw new Error("No instance selected. Specify a name or add an instance first.");
25942
+ throw new Error(
25943
+ "No instance selected. Specify a name or add an instance first."
25944
+ );
23074
25945
  }
23075
25946
  const instance = config.instances[name];
23076
25947
  if (!instance) {
@@ -23396,6 +26267,10 @@ import { resolve as resolve8 } from "node:path";
23396
26267
 
23397
26268
  // src/services/compose-upgrade.ts
23398
26269
  var managedImages = {
26270
+ EUDIPLO_IMAGE: "ghcr.io/eudiplo/eudiplo",
26271
+ EUDIPLO_CLIENT_IMAGE: "ghcr.io/eudiplo/eudiplo-client"
26272
+ };
26273
+ var legacyImages = {
23399
26274
  EUDIPLO_IMAGE: "ghcr.io/openwallet-foundation/eudiplo",
23400
26275
  EUDIPLO_CLIENT_IMAGE: "ghcr.io/openwallet-foundation/eudiplo-client"
23401
26276
  };
@@ -23407,6 +26282,14 @@ function assertImageTag(tag) {
23407
26282
  );
23408
26283
  }
23409
26284
  }
26285
+ function tagOf(value, repository) {
26286
+ const prefix = `${repository}:`;
26287
+ if (!value.startsWith(prefix)) {
26288
+ return void 0;
26289
+ }
26290
+ const tag = value.slice(prefix.length);
26291
+ return TAG.test(tag) ? tag : void 0;
26292
+ }
23410
26293
  function planImageUpgrade(content, tag) {
23411
26294
  assertImageTag(tag);
23412
26295
  const found = /* @__PURE__ */ new Set();
@@ -23428,17 +26311,26 @@ function planImageUpgrade(content, tag) {
23428
26311
  );
23429
26312
  }
23430
26313
  found.add(key);
23431
- const prefix = `${repository}:`;
23432
- const current = value.startsWith(prefix) ? value.slice(prefix.length) : void 0;
23433
- if (current === void 0 || !TAG.test(current)) {
26314
+ const currentTag = tagOf(value, repository);
26315
+ const legacyTag = tagOf(value, legacyImages[key]);
26316
+ const current = currentTag ?? legacyTag;
26317
+ if (current === void 0) {
23434
26318
  throw new Error(
23435
- `${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.`
26319
+ `${key} is set to ${value}, which is not a CLI-managed image. Update it yourself, or restore ${repository}:<tag> to let the CLI manage it.`
23436
26320
  );
23437
26321
  }
23438
- if (current !== tag) {
26322
+ if (currentTag === void 0) {
26323
+ changes.push({
26324
+ key,
26325
+ repository,
26326
+ movedFrom: legacyImages[key],
26327
+ from: current,
26328
+ to: tag
26329
+ });
26330
+ } else if (current !== tag) {
23439
26331
  changes.push({ key, repository, from: current, to: tag });
23440
26332
  }
23441
- return `${key}=${prefix}${tag}${lineEnding}`;
26333
+ return `${key}=${repository}:${tag}${lineEnding}`;
23442
26334
  });
23443
26335
  if (!found.has("EUDIPLO_IMAGE")) {
23444
26336
  throw new Error(
@@ -23458,10 +26350,10 @@ function parseVersion(tag) {
23458
26350
  patch: Number(match[3])
23459
26351
  };
23460
26352
  }
23461
- function compareVersions(a, b) {
26353
+ function compareVersions2(a, b) {
23462
26354
  return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
23463
26355
  }
23464
- var migrationBaseUrl = "https://docs.eudiplo.dev/migration";
26356
+ var migrationBaseUrl = "https://docs.eudiplo.dev/upgrade";
23465
26357
  function migrationNotes(from, to) {
23466
26358
  const fromVersion = parseVersion(from);
23467
26359
  const toVersion = parseVersion(to);
@@ -23470,7 +26362,7 @@ function migrationNotes(from, to) {
23470
26362
  `Cannot compare ${from} and ${to} as versions. Check the release notes and ${migrationBaseUrl} before continuing.`
23471
26363
  ];
23472
26364
  }
23473
- const order = compareVersions(fromVersion, toVersion);
26365
+ const order = compareVersions2(fromVersion, toVersion);
23474
26366
  if (order > 0) {
23475
26367
  return [
23476
26368
  `${to} is older than ${from}. Downgrades are not supported once database migrations have run; restore a backup instead if you need to go back.`
@@ -23525,7 +26417,8 @@ async function runUpgradeCommand(config, parsed, context) {
23525
26417
  `);
23526
26418
  for (const change of plan.changes) {
23527
26419
  context.stdout.write(
23528
- ` ${change.repository}: ${change.from} -> ${change.to}
26420
+ change.movedFrom ? ` ${change.movedFrom}:${change.from} -> ${change.repository}:${change.to}
26421
+ ` : ` ${change.repository}: ${change.from} -> ${change.to}
23529
26422
  `
23530
26423
  );
23531
26424
  }
@@ -23608,78 +26501,6 @@ function createUpgradeCommand(context, setExitCode) {
23608
26501
 
23609
26502
  // src/commands/version/action.ts
23610
26503
  import { isSea as isSea2 } from "node:sea";
23611
-
23612
- // package.json
23613
- var package_default = {
23614
- name: "@eudiplo/cli",
23615
- version: "8.1.0",
23616
- description: "Deployment-aware command line tools for EUDIPLO",
23617
- private: false,
23618
- type: "module",
23619
- bin: {
23620
- eudiplo: "dist/index.js"
23621
- },
23622
- files: [
23623
- "dist",
23624
- "templates",
23625
- "README.md"
23626
- ],
23627
- scripts: {
23628
- "assets:sync": "node scripts/sync-cli-assets.cjs",
23629
- "assets:check": "node scripts/check-cli-assets.cjs",
23630
- build: "pnpm assets:sync && pnpm assets:check && tsc -p tsconfig.json",
23631
- "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",
23632
- "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",
23633
- "build:sea": "node scripts/build-cli-sea.mjs",
23634
- dev: "tsx src/index.ts",
23635
- test: "vitest run --project cli",
23636
- lint: "biome lint .",
23637
- prepublishOnly: "pnpm build:publish"
23638
- },
23639
- keywords: [
23640
- "eudiplo",
23641
- "cli",
23642
- "eudi",
23643
- "wallet",
23644
- "oid4vci",
23645
- "oid4vp"
23646
- ],
23647
- repository: {
23648
- type: "git",
23649
- url: "git+https://github.com/openwallet-foundation/eudiplo.git",
23650
- directory: "apps/cli"
23651
- },
23652
- homepage: "https://github.com/openwallet-foundation/eudiplo/tree/main/apps/cli#readme",
23653
- bugs: {
23654
- url: "https://github.com/openwallet-foundation/eudiplo/issues"
23655
- },
23656
- author: "OpenWallet Foundation",
23657
- license: "Apache-2.0",
23658
- engines: {
23659
- node: ">=22.12.0"
23660
- },
23661
- publishConfig: {
23662
- access: "public"
23663
- },
23664
- dependencies: {
23665
- "@eudiplo/config-format": "workspace:*",
23666
- ajv: "^8.20.0",
23667
- "ajv-formats": "^3.0.1",
23668
- commander: "^15.0.0",
23669
- fflate: "^0.8.3",
23670
- "jsonc-parser": "^3.3.1"
23671
- },
23672
- devDependencies: {
23673
- "@biomejs/biome": "2.5.14",
23674
- "@types/node": "^26.4.0",
23675
- esbuild: "^0.28.2",
23676
- tsx: "^4.23.13",
23677
- typescript: "^7.0.2",
23678
- vitest: "4.1.11"
23679
- }
23680
- };
23681
-
23682
- // src/commands/version/action.ts
23683
26504
  function versionText() {
23684
26505
  return `${package_default.name} ${package_default.version}`;
23685
26506
  }
@@ -23779,7 +26600,7 @@ function createProgram(context, setExitCode) {
23779
26600
  "Print the installed CLI version"
23780
26601
  ).helpCommand(true).addHelpText(
23781
26602
  "afterAll",
23782
- "\nFor more information, see https://docs.eudiplo.dev/getting-started/quick-start"
26603
+ "\nFor more information, see https://docs.eudiplo.dev/cookbooks/foundation"
23783
26604
  );
23784
26605
  program2.addCommand(createDemoCommand(context, setExitCode));
23785
26606
  program2.addCommand(createInitCommand(context, setExitCode));