@eudiplo/cli 7.0.1 → 7.1.0-main.56dc2f0

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 (46) hide show
  1. package/README.md +13 -35
  2. package/dist/config-validate/index.d.ts +4 -0
  3. package/dist/config-validate/index.d.ts.map +1 -0
  4. package/dist/config-validate/index.js +4 -0
  5. package/dist/config-validate/index.js.map +1 -0
  6. package/dist/config-validate/registry.d.ts +5 -0
  7. package/dist/config-validate/registry.d.ts.map +1 -0
  8. package/dist/config-validate/registry.js +8 -0
  9. package/dist/config-validate/registry.js.map +1 -0
  10. package/dist/config-validate/registry.json +138 -0
  11. package/dist/config-validate/report.d.ts +21 -0
  12. package/dist/config-validate/report.d.ts.map +1 -0
  13. package/dist/config-validate/report.js +78 -0
  14. package/dist/config-validate/report.js.map +1 -0
  15. package/dist/config-validate/schemas.d.ts +2 -0
  16. package/dist/config-validate/schemas.d.ts.map +1 -0
  17. package/dist/config-validate/schemas.js +24 -0
  18. package/dist/config-validate/schemas.js.map +1 -0
  19. package/dist/config-validate/types.d.ts +33 -0
  20. package/dist/config-validate/types.d.ts.map +1 -0
  21. package/dist/config-validate/types.js +2 -0
  22. package/dist/config-validate/types.js.map +1 -0
  23. package/dist/config-validate/validator.d.ts +4 -0
  24. package/dist/config-validate/validator.d.ts.map +1 -0
  25. package/dist/config-validate/validator.js +187 -0
  26. package/dist/config-validate/validator.js.map +1 -0
  27. package/dist/runtime.d.ts.map +1 -1
  28. package/dist/runtime.js +45 -4
  29. package/dist/runtime.js.map +1 -1
  30. package/dist/types.d.ts +3 -0
  31. package/dist/types.d.ts.map +1 -1
  32. package/package.json +6 -2
  33. package/templates/demo-config/issuance/status-lists/pid.json +1 -1
  34. package/templates/schemas/CreateAttributeProviderDto.schema.json +99 -0
  35. package/templates/schemas/CreateClientDto.schema.json +76 -0
  36. package/templates/schemas/CreateRegistrarConfigDto.schema.json +61 -0
  37. package/templates/schemas/CreateWebhookEndpointDto.schema.json +99 -0
  38. package/templates/schemas/CredentialConfigCreate.schema.json +673 -0
  39. package/templates/schemas/ImportTenantDto.schema.json +24 -0
  40. package/templates/schemas/IssuanceConfig.schema.json +497 -0
  41. package/templates/schemas/KeyChainImportDto.schema.json +111 -0
  42. package/templates/schemas/KmsConfigDto.schema.json +990 -0
  43. package/templates/schemas/PresentationConfigCreateDto.schema.json +627 -0
  44. package/templates/schemas/StatusListImportDto.schema.json +61 -0
  45. package/templates/schemas/TrustListCreateDto.schema.json +191 -0
  46. package/templates/schemas.manifest.json +14 -0
package/README.md CHANGED
@@ -3,11 +3,16 @@
3
3
  The EUDIPLO CLI provides deployment-neutral commands for inspecting an EUDIPLO
4
4
  instance and deployment-driver commands for local Docker Compose demos.
5
5
 
6
+ For installation, command reference, configuration validation, standalone
7
+ releases, and local development, see the [CLI documentation](../../docs/getting-started/cli.md).
8
+
6
9
  ## Quick Start
7
10
 
8
- `npx` is provided by npm, which is installed with Node.js. It is not bundled with
9
- operating systems by default, so install a current Node.js distribution first if
10
- `npx` is missing.
11
+ This package is the npm distribution of the **EUDIPLO CLI** and requires
12
+ Node.js 22+.
13
+
14
+ For users who do not want a local Node.js dependency, use the standalone CLI
15
+ installer instead: `curl -fsSL https://eudiplo.dev/install.sh | bash`
11
16
 
12
17
  ```bash
13
18
  npx @eudiplo/cli demo
@@ -29,6 +34,10 @@ Tag selection defaults to the CLI version. For prerelease versions containing
29
34
  The generated demo config remains editable after creation. Existing files are
30
35
  preserved unless you pass `--force`.
31
36
 
37
+ The standalone CLI and this npm package are two distributions of the same
38
+ `eudiplo` command. The standalone CLI removes the Node.js requirement, but
39
+ Docker and Docker Compose are still required for the demo deployment.
40
+
32
41
  ## Package and Binary Names
33
42
 
34
43
  The npm package is scoped as `@eudiplo/cli`, while the installed binary is
@@ -43,23 +52,7 @@ pnpm add -D @eudiplo/cli
43
52
  pnpm eudiplo status
44
53
  ```
45
54
 
46
- ## Commands
47
-
48
- Deployment-neutral commands work with any registered EUDIPLO instance and do not
49
- require Docker:
50
-
51
- ```bash
52
- eudiplo instance add production --url https://eudiplo.example.com
53
- eudiplo doctor --instance production
54
- eudiplo status --instance production
55
- eudiplo config validate
56
- eudiplo --version
57
- eudiplo version
58
- ```
59
-
60
- `--version` and `-v` print the installed CLI version without network access.
61
- `version` also checks the npm registry and reports whether an update is
62
- available.
55
+ ## Compose Driver Commands
63
56
 
64
57
  Compose driver commands require a `compose` instance:
65
58
 
@@ -103,21 +96,6 @@ Do not store secrets in the CLI config. Commands that need authenticated API
103
96
  access read credentials from environment variables such as `EUDIPLO_CLIENT_ID`
104
97
  and `EUDIPLO_CLIENT_SECRET`.
105
98
 
106
- ## Local Development
107
-
108
- From the repository root:
109
-
110
- ```bash
111
- pnpm --filter @eudiplo/cli test
112
- pnpm --filter @eudiplo/cli build
113
- pnpm --filter @eudiplo/cli lint
114
- ```
115
-
116
- The build synchronizes and validates bundled CLI assets from canonical sources:
117
-
118
- - `deployment/docker-compose/docker-compose.yml`
119
- - `assets/config/demo/**`
120
-
121
99
  ## Single Executable Application
122
100
 
123
101
  To produce a standalone CLI binary with Node.js SEA support, run:
@@ -0,0 +1,4 @@
1
+ export { loadTenantConfigSchemas } from "./schemas.js";
2
+ export { validateTenantDirectory, validateTenantsRoot } from "./validator.js";
3
+ export { buildJsonReport, formatTextReport } from "./report.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config-validate/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { loadTenantConfigSchemas } from "./schemas.js";
2
+ export { validateTenantDirectory, validateTenantsRoot } from "./validator.js";
3
+ export { buildJsonReport, formatTextReport } from "./report.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config-validate/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { TenantResourceDefinition } from "./types.js";
2
+ /** Entries actually scanned by the CLI validator (excludes editor-authoring-only schemas). */
3
+ export declare const CLI_VALIDATED_REGISTRY: readonly TenantResourceDefinition[];
4
+ export declare function requiredSchemaFiles(): string[];
5
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/config-validate/registry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAK3D,8FAA8F;AAC9F,eAAO,MAAM,sBAAsB,EAAE,SAAS,wBAAwB,EACM,CAAC;AAE7E,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C"}
@@ -0,0 +1,8 @@
1
+ import registryData from "./registry.json" with { type: "json" };
2
+ const TENANT_RESOURCE_REGISTRY = registryData;
3
+ /** Entries actually scanned by the CLI validator (excludes editor-authoring-only schemas). */
4
+ export const CLI_VALIDATED_REGISTRY = TENANT_RESOURCE_REGISTRY.filter((entry) => entry.cliValidated !== false);
5
+ export function requiredSchemaFiles() {
6
+ return Array.from(new Set(CLI_VALIDATED_REGISTRY.map((entry) => entry.schemaFile)));
7
+ }
8
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/config-validate/registry.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAGjE,MAAM,wBAAwB,GAC1B,YAA0C,CAAC;AAE/C,8FAA8F;AAC9F,MAAM,CAAC,MAAM,sBAAsB,GAC/B,wBAAwB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,KAAK,KAAK,CAAC,CAAC;AAE7E,MAAM,UAAU,mBAAmB;IAC/B,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACxF,CAAC"}
@@ -0,0 +1,138 @@
1
+ [
2
+ {
3
+ "kind": "file",
4
+ "file": "info.json",
5
+ "resourceType": "tenant metadata",
6
+ "schemaFile": "ImportTenantDto.schema.json",
7
+ "required": true,
8
+ "fileMatch": [
9
+ "/assets/config/*/info.json",
10
+ "/apps/backend/test/fixtures/*/info.json"
11
+ ]
12
+ },
13
+ {
14
+ "kind": "file",
15
+ "file": "registrar.json",
16
+ "resourceType": "registrar config",
17
+ "schemaFile": "CreateRegistrarConfigDto.schema.json",
18
+ "required": false,
19
+ "fileMatch": [
20
+ "/assets/config/*/registrar*.json",
21
+ "/apps/backend/test/fixtures/*/registrar*.json"
22
+ ]
23
+ },
24
+ {
25
+ "kind": "file",
26
+ "file": "kms.json",
27
+ "resourceType": "tenant KMS config",
28
+ "schemaFile": "KmsConfigDto.schema.json",
29
+ "required": false,
30
+ "fileMatch": [
31
+ "/assets/config/kms.json",
32
+ "/assets/config/*/kms.json",
33
+ "/apps/backend/test/fixtures/kms.json",
34
+ "/apps/backend/test/fixtures/*/kms.json"
35
+ ]
36
+ },
37
+ {
38
+ "kind": "directory",
39
+ "subfolder": "clients",
40
+ "resourceType": "client config",
41
+ "schemaFile": "CreateClientDto.schema.json",
42
+ "fileMatch": [
43
+ "/assets/config/*/clients/*.json",
44
+ "/apps/backend/test/fixtures/*/clients/*.json"
45
+ ]
46
+ },
47
+ {
48
+ "kind": "directory",
49
+ "subfolder": "key-chains",
50
+ "resourceType": "key-chain",
51
+ "schemaFile": "KeyChainImportDto.schema.json",
52
+ "fileMatch": [
53
+ "/assets/config/*/key-chains/*.json",
54
+ "/apps/backend/test/fixtures/*/key-chains/*.json"
55
+ ]
56
+ },
57
+ {
58
+ "kind": "directory",
59
+ "subfolder": "certs",
60
+ "resourceType": "certificate",
61
+ "schemaFile": "CertImportDto.schema.json",
62
+ "cliValidated": false,
63
+ "fileMatch": [
64
+ "/assets/config/*/certs/*.json",
65
+ "/apps/backend/test/fixtures/*/certs/*.json"
66
+ ]
67
+ },
68
+ {
69
+ "kind": "directory",
70
+ "subfolder": "attribute-providers",
71
+ "resourceType": "attribute provider",
72
+ "schemaFile": "CreateAttributeProviderDto.schema.json",
73
+ "fileMatch": [
74
+ "/assets/config/*/attribute-providers/*.json",
75
+ "/apps/backend/test/fixtures/*/attribute-providers/*.json"
76
+ ]
77
+ },
78
+ {
79
+ "kind": "directory",
80
+ "subfolder": "webhook-endpoints",
81
+ "resourceType": "webhook endpoint",
82
+ "schemaFile": "CreateWebhookEndpointDto.schema.json",
83
+ "fileMatch": [
84
+ "/assets/config/*/webhook-endpoints/*.json",
85
+ "/apps/backend/test/fixtures/*/webhook-endpoints/*.json"
86
+ ]
87
+ },
88
+ {
89
+ "kind": "directory",
90
+ "subfolder": "issuance",
91
+ "resourceType": "issuance config",
92
+ "schemaFile": "IssuanceConfig.schema.json",
93
+ "fileMatch": [
94
+ "/assets/config/*/issuance/issuance.json",
95
+ "/apps/backend/test/fixtures/*/issuance/issuance.json"
96
+ ]
97
+ },
98
+ {
99
+ "kind": "directory",
100
+ "subfolder": "issuance/credentials",
101
+ "resourceType": "credential config",
102
+ "schemaFile": "CredentialConfigCreate.schema.json",
103
+ "fileMatch": [
104
+ "/assets/config/*/issuance/credentials/*.json",
105
+ "/apps/backend/test/fixtures/*/issuance/credentials/*.json"
106
+ ]
107
+ },
108
+ {
109
+ "kind": "directory",
110
+ "subfolder": "issuance/status-lists",
111
+ "resourceType": "status list",
112
+ "schemaFile": "StatusListImportDto.schema.json",
113
+ "fileMatch": [
114
+ "/assets/config/*/issuance/status-lists/*.json",
115
+ "/apps/backend/test/fixtures/*/issuance/status-lists/*.json"
116
+ ]
117
+ },
118
+ {
119
+ "kind": "directory",
120
+ "subfolder": "trust-lists",
121
+ "resourceType": "trustlist",
122
+ "schemaFile": "TrustListCreateDto.schema.json",
123
+ "fileMatch": [
124
+ "/assets/config/*/trust-lists/*.json",
125
+ "/apps/backend/test/fixtures/*/trust-lists/*.json"
126
+ ]
127
+ },
128
+ {
129
+ "kind": "directory",
130
+ "subfolder": "presentation",
131
+ "resourceType": "presentation config",
132
+ "schemaFile": "PresentationConfigCreateDto.schema.json",
133
+ "fileMatch": [
134
+ "/assets/config/*/presentation/*.json",
135
+ "/apps/backend/test/fixtures/*/presentation/*.json"
136
+ ]
137
+ }
138
+ ]
@@ -0,0 +1,21 @@
1
+ import type { TenantValidationResult } from "./types.js";
2
+ export declare function buildJsonReport(results: TenantValidationResult[]): {
3
+ valid: boolean;
4
+ tenants: {
5
+ id: string;
6
+ valid: boolean;
7
+ files: number;
8
+ errors: {
9
+ file: string;
10
+ path: string | undefined;
11
+ message: string;
12
+ }[];
13
+ }[];
14
+ summary: {
15
+ tenants: number;
16
+ files: number;
17
+ errors: number;
18
+ };
19
+ };
20
+ export declare function formatTextReport(scope: "tenant" | "tenants", rootPath: string, results: TenantValidationResult[]): string;
21
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/config-validate/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAmB,MAAM,YAAY,CAAC;AAE1E,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,EAAE;IAEzD,KAAK;IACL,OAAO;;;;;;;;;;IAUP,OAAO;QACH,OAAO;QACP,KAAK;QACL,MAAM;;EAGjB;AAED,wBAAgB,gBAAgB,CAC5B,KAAK,EAAE,QAAQ,GAAG,SAAS,EAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,sBAAsB,EAAE,GAClC,MAAM,CASR"}
@@ -0,0 +1,78 @@
1
+ export function buildJsonReport(results) {
2
+ return {
3
+ valid: results.every((result) => result.valid),
4
+ tenants: results.map((result) => ({
5
+ id: result.id,
6
+ valid: result.valid,
7
+ files: result.files,
8
+ errors: result.errors.map((error) => ({
9
+ file: error.file,
10
+ path: error.path,
11
+ message: error.message,
12
+ })),
13
+ })),
14
+ summary: {
15
+ tenants: results.length,
16
+ files: results.reduce((sum, result) => sum + result.files, 0),
17
+ errors: results.reduce((sum, result) => sum + result.errors.length, 0),
18
+ },
19
+ };
20
+ }
21
+ export function formatTextReport(scope, rootPath, results) {
22
+ const header = scope === "tenants"
23
+ ? `Validating tenant configuration root: ${rootPath}`
24
+ : `Validating tenant configuration: ${rootPath}`;
25
+ const lines = [header, "", ...results.flatMap(formatTenantBlock), formatSummaryLine(results)];
26
+ return `${lines.join("\n")}\n`;
27
+ }
28
+ function formatTenantBlock(result) {
29
+ const lines = [`${result.valid ? "PASS" : "FAIL"} ${result.id}`];
30
+ if (result.valid) {
31
+ for (const [resourceType, count] of Object.entries(result.resourceCounts)) {
32
+ lines.push(` ${count} ${pluralize(resourceType, count)}`);
33
+ }
34
+ }
35
+ else {
36
+ lines.push(...formatTenantErrors(result.errors));
37
+ }
38
+ lines.push("");
39
+ return lines;
40
+ }
41
+ function formatTenantErrors(errors) {
42
+ const lines = [];
43
+ for (const [file, issues] of groupErrorsByFile(errors)) {
44
+ lines.push(` ${file}`);
45
+ for (const issue of issues) {
46
+ lines.push(` ${formatIssueMessage(issue)}`);
47
+ }
48
+ }
49
+ return lines;
50
+ }
51
+ function formatIssueMessage(issue) {
52
+ return issue.path ? `${issue.path}: ${issue.message}` : issue.message;
53
+ }
54
+ function formatSummaryLine(results) {
55
+ const totalFiles = results.reduce((sum, result) => sum + result.files, 0);
56
+ const totalErrors = results.reduce((sum, result) => sum + result.errors.length, 0);
57
+ if (totalErrors === 0) {
58
+ return `Validated ${results.length} tenant(s) and ${totalFiles} configuration file(s).\nNo errors found.`;
59
+ }
60
+ const failedTenants = results.filter((result) => !result.valid).length;
61
+ return `Validation failed: ${totalErrors} error(s) in ${failedTenants} tenant(s).`;
62
+ }
63
+ function groupErrorsByFile(errors) {
64
+ const grouped = new Map();
65
+ for (const error of errors) {
66
+ const existing = grouped.get(error.file);
67
+ if (existing) {
68
+ existing.push(error);
69
+ continue;
70
+ }
71
+ grouped.set(error.file, [error]);
72
+ }
73
+ return Array.from(grouped.entries());
74
+ }
75
+ function pluralize(label, count) {
76
+ return count === 1 ? label : `${label}s`;
77
+ }
78
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/config-validate/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function loadTenantConfigSchemas(): Promise<Map<string, Record<string, unknown>>>;
2
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/config-validate/schemas.ts"],"names":[],"mappings":"AAKA,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAmB7F"}
@@ -0,0 +1,24 @@
1
+ import { readCliTextAsset } from "../sea-assets.js";
2
+ import { requiredSchemaFiles } from "./registry.js";
3
+ const manifestAssetKey = "templates/schemas.manifest.json";
4
+ export async function loadTenantConfigSchemas() {
5
+ const manifest = await readSchemaManifest();
6
+ const schemas = new Map();
7
+ for (const schemaFile of requiredSchemaFiles()) {
8
+ if (!manifest.includes(schemaFile)) {
9
+ throw new Error(`Bundled schema manifest is missing "${schemaFile}". Run "pnpm --filter @eudiplo/cli assets:sync".`);
10
+ }
11
+ const text = await readCliTextAsset(`templates/schemas/${schemaFile}`, new URL(`../../templates/schemas/${schemaFile}`, import.meta.url));
12
+ schemas.set(schemaFile, JSON.parse(text));
13
+ }
14
+ return schemas;
15
+ }
16
+ async function readSchemaManifest() {
17
+ const text = await readCliTextAsset(manifestAssetKey, new URL("../../templates/schemas.manifest.json", import.meta.url));
18
+ const parsed = JSON.parse(text);
19
+ if (!Array.isArray(parsed) || !parsed.every((item) => typeof item === "string")) {
20
+ throw new Error("Bundled schema manifest is malformed.");
21
+ }
22
+ return parsed;
23
+ }
24
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/config-validate/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEpD,MAAM,gBAAgB,GAAG,iCAAiC,CAAC;AAE3D,MAAM,CAAC,KAAK,UAAU,uBAAuB;IACzC,MAAM,QAAQ,GAAG,MAAM,kBAAkB,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAmC,CAAC;IAE3D,KAAK,MAAM,UAAU,IAAI,mBAAmB,EAAE,EAAE,CAAC;QAC7C,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CACX,uCAAuC,UAAU,kDAAkD,CACtG,CAAC;QACN,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAC/B,qBAAqB,UAAU,EAAE,EACjC,IAAI,GAAG,CAAC,2BAA2B,UAAU,EAAE,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CACpE,CAAC;QACF,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,kBAAkB;IAC7B,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAC/B,gBAAgB,EAChB,IAAI,GAAG,CAAC,uCAAuC,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CACpE,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,EAAE,CAAC;QAC9E,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC"}
@@ -0,0 +1,33 @@
1
+ export type FixedFileResourceDefinition = {
2
+ kind: "file";
3
+ file: string;
4
+ resourceType: string;
5
+ schemaFile: string;
6
+ required: boolean;
7
+ /** Editor (`.vscode/settings.json`) `json.schemas` fileMatch globs generated for this entry. */
8
+ fileMatch: string[];
9
+ /** Set to false for schemas that aren't scanned by the CLI validator (editor authoring only). */
10
+ cliValidated?: boolean;
11
+ };
12
+ export type DirectoryResourceDefinition = {
13
+ kind: "directory";
14
+ subfolder: string;
15
+ resourceType: string;
16
+ schemaFile: string;
17
+ fileMatch: string[];
18
+ cliValidated?: boolean;
19
+ };
20
+ export type TenantResourceDefinition = FixedFileResourceDefinition | DirectoryResourceDefinition;
21
+ export interface ValidationIssue {
22
+ file: string;
23
+ path?: string;
24
+ message: string;
25
+ }
26
+ export interface TenantValidationResult {
27
+ id: string;
28
+ valid: boolean;
29
+ files: number;
30
+ errors: ValidationIssue[];
31
+ resourceCounts: Record<string, number>;
32
+ }
33
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config-validate/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,2BAA2B,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,gGAAgG;IAChG,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,iGAAiG;IACjG,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,2BAA2B,GAAG,2BAA2B,CAAC;AAEjG,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/config-validate/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ import type { TenantValidationResult } from "./types.js";
2
+ export declare function validateTenantsRoot(rootPath: string, schemas: Map<string, Record<string, unknown>>, env: NodeJS.ProcessEnv): Promise<TenantValidationResult[]>;
3
+ export declare function validateTenantDirectory(tenantPath: string, tenantId: string, schemas: Map<string, Record<string, unknown>>, env: NodeJS.ProcessEnv): Promise<TenantValidationResult>;
4
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/config-validate/validator.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAGR,sBAAsB,EAEzB,MAAM,YAAY,CAAC;AAYpB,wBAAsB,mBAAmB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC7C,GAAG,EAAE,MAAM,CAAC,UAAU,GACvB,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAanC;AAED,wBAAsB,uBAAuB,CACzC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC7C,GAAG,EAAE,MAAM,CAAC,UAAU,GACvB,OAAO,CAAC,sBAAsB,CAAC,CA4BjC"}
@@ -0,0 +1,187 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import Ajv2020 from "ajv/dist/2020";
5
+ import addFormatsModule from "ajv-formats";
6
+ import { CLI_VALIDATED_REGISTRY } from "./registry.js";
7
+ const PLACEHOLDER_PATTERN = /\$\{([A-Z0-9_]+)(?::([^}]*))?\}/g;
8
+ async function discoverTenantDirectories(rootPath) {
9
+ const entries = await readdir(rootPath, { withFileTypes: true });
10
+ return entries
11
+ .filter((entry) => entry.isDirectory())
12
+ .map((entry) => entry.name)
13
+ .sort((left, right) => left.localeCompare(right));
14
+ }
15
+ export async function validateTenantsRoot(rootPath, schemas, env) {
16
+ if (!existsSync(rootPath)) {
17
+ throw new Error(`Configuration root not found: ${rootPath}`);
18
+ }
19
+ const tenantIds = await discoverTenantDirectories(rootPath);
20
+ const results = [];
21
+ for (const tenantId of tenantIds) {
22
+ results.push(await validateTenantDirectory(join(rootPath, tenantId), tenantId, schemas, env));
23
+ }
24
+ return results;
25
+ }
26
+ export async function validateTenantDirectory(tenantPath, tenantId, schemas, env) {
27
+ if (!existsSync(tenantPath)) {
28
+ throw new Error(`Tenant directory not found: ${tenantPath}`);
29
+ }
30
+ const state = {
31
+ getValidator: createValidatorFactory(schemas),
32
+ env,
33
+ errors: [],
34
+ resourceCounts: {},
35
+ files: 0,
36
+ };
37
+ for (const entry of CLI_VALIDATED_REGISTRY) {
38
+ if (entry.kind === "file") {
39
+ await validateFixedFileEntry(tenantPath, entry, state);
40
+ }
41
+ else {
42
+ await validateDirectoryEntry(tenantPath, entry, state);
43
+ }
44
+ }
45
+ return {
46
+ id: tenantId,
47
+ valid: state.errors.length === 0,
48
+ files: state.files,
49
+ errors: state.errors,
50
+ resourceCounts: state.resourceCounts,
51
+ };
52
+ }
53
+ function createValidatorFactory(schemas) {
54
+ const ajv = new Ajv2020({ allErrors: true, strict: false });
55
+ addFormatsModule.default(ajv);
56
+ const validators = new Map();
57
+ return (schemaFile) => {
58
+ const cached = validators.get(schemaFile);
59
+ if (cached) {
60
+ return cached;
61
+ }
62
+ const schema = schemas.get(schemaFile);
63
+ if (!schema) {
64
+ throw new Error(`No bundled schema found for "${schemaFile}".`);
65
+ }
66
+ const validator = ajv.compile(schema);
67
+ validators.set(schemaFile, validator);
68
+ return validator;
69
+ };
70
+ }
71
+ async function validateFixedFileEntry(tenantPath, entry, state) {
72
+ const filePath = join(tenantPath, entry.file);
73
+ if (!existsSync(filePath)) {
74
+ if (entry.required) {
75
+ state.errors.push({
76
+ file: entry.file,
77
+ message: `Missing required ${entry.resourceType} file`,
78
+ });
79
+ }
80
+ return;
81
+ }
82
+ state.files += 1;
83
+ await recordValidationOutcome(filePath, entry.file, entry.resourceType, entry.schemaFile, state);
84
+ }
85
+ async function validateDirectoryEntry(tenantPath, entry, state) {
86
+ const directoryPath = join(tenantPath, entry.subfolder);
87
+ if (!existsSync(directoryPath)) {
88
+ return;
89
+ }
90
+ const dirEntries = (await readdir(directoryPath, { withFileTypes: true }))
91
+ .filter((dirEntry) => dirEntry.isFile() && dirEntry.name.endsWith(".json"))
92
+ .sort((left, right) => left.name.localeCompare(right.name));
93
+ for (const dirEntry of dirEntries) {
94
+ const relativeFile = `${entry.subfolder}/${dirEntry.name}`;
95
+ const filePath = join(directoryPath, dirEntry.name);
96
+ state.files += 1;
97
+ await recordValidationOutcome(filePath, relativeFile, entry.resourceType, entry.schemaFile, state);
98
+ }
99
+ }
100
+ async function recordValidationOutcome(filePath, relativeFile, resourceType, schemaFile, state) {
101
+ const isValid = await validateResourceFile(filePath, relativeFile, schemaFile, state.getValidator, state.env, state.errors);
102
+ if (isValid) {
103
+ state.resourceCounts[resourceType] = (state.resourceCounts[resourceType] ?? 0) + 1;
104
+ }
105
+ }
106
+ async function validateResourceFile(filePath, relativeFile, schemaFile, getValidator, env, errors) {
107
+ let raw;
108
+ try {
109
+ raw = await readFile(filePath, "utf8");
110
+ }
111
+ catch (error) {
112
+ errors.push({
113
+ file: relativeFile,
114
+ message: `Failed to read file: ${error.message}`,
115
+ });
116
+ return false;
117
+ }
118
+ let payload;
119
+ try {
120
+ payload = JSON.parse(raw);
121
+ }
122
+ catch (error) {
123
+ errors.push({ file: relativeFile, message: `Invalid JSON: ${error.message}` });
124
+ return false;
125
+ }
126
+ const errorsBefore = errors.length;
127
+ const resolved = resolvePlaceholders(payload, env, relativeFile, errors);
128
+ if (errors.length > errorsBefore) {
129
+ return false;
130
+ }
131
+ const validate = getValidator(schemaFile);
132
+ if (!validate(resolved)) {
133
+ for (const issue of validate.errors ?? []) {
134
+ errors.push({
135
+ file: relativeFile,
136
+ path: formatErrorPath(issue),
137
+ message: issue.message ?? "Invalid value",
138
+ });
139
+ }
140
+ return false;
141
+ }
142
+ return true;
143
+ }
144
+ function formatErrorPath(issue) {
145
+ const path = issue.instancePath.replace(/^\//, "").replaceAll("/", ".");
146
+ if (path) {
147
+ return path;
148
+ }
149
+ const missingProperty = issue.params
150
+ ?.missingProperty;
151
+ return missingProperty;
152
+ }
153
+ /**
154
+ * Resolves ${VAR} / ${VAR:default} placeholders for schema validation without ever
155
+ * emitting resolved secret values in error output; unresolved required placeholders
156
+ * are reported by reference to their variable name only.
157
+ */
158
+ function resolvePlaceholders(value, env, file, errors) {
159
+ if (typeof value === "string") {
160
+ return value.replace(PLACEHOLDER_PATTERN, (match, varName, defaultValue) => {
161
+ const envValue = env[varName];
162
+ if (envValue !== undefined && envValue !== "") {
163
+ return envValue;
164
+ }
165
+ if (defaultValue !== undefined) {
166
+ return defaultValue;
167
+ }
168
+ errors.push({
169
+ file,
170
+ message: `Unresolved placeholder \${${varName}}: no environment value or default is available`,
171
+ });
172
+ return match;
173
+ });
174
+ }
175
+ if (Array.isArray(value)) {
176
+ return value.map((item) => resolvePlaceholders(item, env, file, errors));
177
+ }
178
+ if (value && typeof value === "object") {
179
+ const result = {};
180
+ for (const [key, val] of Object.entries(value)) {
181
+ result[key] = resolvePlaceholders(val, env, file, errors);
182
+ }
183
+ return result;
184
+ }
185
+ return value;
186
+ }
187
+ //# sourceMappingURL=validator.js.map