@eudiplo/cli 7.0.1-main.dc652b3 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +34 -3
  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 +185 -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 +34 -1
  29. package/dist/runtime.js.map +1 -1
  30. package/package.json +5 -2
  31. package/templates/demo-config/issuance/status-lists/pid.json +1 -1
  32. package/templates/schemas/CreateAttributeProviderDto.schema.json +99 -0
  33. package/templates/schemas/CreateClientDto.schema.json +76 -0
  34. package/templates/schemas/CreateRegistrarConfigDto.schema.json +61 -0
  35. package/templates/schemas/CreateWebhookEndpointDto.schema.json +99 -0
  36. package/templates/schemas/CredentialConfigCreate.schema.json +673 -0
  37. package/templates/schemas/ImportTenantDto.schema.json +24 -0
  38. package/templates/schemas/IssuanceConfig.schema.json +492 -0
  39. package/templates/schemas/KeyChainImportDto.schema.json +111 -0
  40. package/templates/schemas/KmsConfigDto.schema.json +990 -0
  41. package/templates/schemas/PresentationConfigCreateDto.schema.json +627 -0
  42. package/templates/schemas/StatusListImportDto.schema.json +61 -0
  43. package/templates/schemas/TrustListCreateDto.schema.json +191 -0
  44. package/templates/schemas.manifest.json +14 -0
package/README.md CHANGED
@@ -5,9 +5,11 @@ instance and deployment-driver commands for local Docker Compose demos.
5
5
 
6
6
  ## Quick Start
7
7
 
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.
8
+ This package is the npm distribution of the **EUDIPLO CLI** and requires
9
+ Node.js 22+.
10
+
11
+ For users who do not want a local Node.js dependency, use the standalone CLI
12
+ installer instead: `curl -fsSL https://eudiplo.dev/install.sh | bash`
11
13
 
12
14
  ```bash
13
15
  npx @eudiplo/cli demo
@@ -29,6 +31,10 @@ Tag selection defaults to the CLI version. For prerelease versions containing
29
31
  The generated demo config remains editable after creation. Existing files are
30
32
  preserved unless you pass `--force`.
31
33
 
34
+ The standalone CLI and this npm package are two distributions of the same
35
+ `eudiplo` command. The standalone CLI removes the Node.js requirement, but
36
+ Docker and Docker Compose are still required for the demo deployment.
37
+
32
38
  ## Package and Binary Names
33
39
 
34
40
  The npm package is scoped as `@eudiplo/cli`, while the installed binary is
@@ -53,6 +59,9 @@ eudiplo instance add production --url https://eudiplo.example.com
53
59
  eudiplo doctor --instance production
54
60
  eudiplo status --instance production
55
61
  eudiplo config validate
62
+ eudiplo config validate tenant ./assets/config/root
63
+ eudiplo config validate tenants ./assets/config
64
+ eudiplo config validate tenants ./assets/config --format json
56
65
  eudiplo --version
57
66
  eudiplo version
58
67
  ```
@@ -61,6 +70,26 @@ eudiplo version
61
70
  `version` also checks the npm registry and reports whether an update is
62
71
  available.
63
72
 
73
+ `config validate tenant <path>` validates a single tenant's config-import
74
+ files, and `config validate tenants <path>` validates every tenant directory
75
+ under a configuration root. Both commands parse and validate every supported
76
+ resource file (tenant metadata, clients, key chains, credential configs,
77
+ issuance configs, presentation configs, status lists, trust lists, attribute
78
+ providers, webhook endpoints, registrar config, and tenant-specific KMS
79
+ config) against the same JSON Schemas the backend uses for config-import,
80
+ without starting EUDIPLO, connecting to a database, or writing anything.
81
+ Unresolved `${VAR}` placeholders without a default are reported without ever
82
+ printing resolved secret values. Add `--format json` for a machine-readable
83
+ report suitable for CI. The command exits non-zero when any selected tenant
84
+ fails validation.
85
+
86
+ `src/config-validate/registry.json` is the single source of truth mapping
87
+ each tenant config-import file/folder to its resource type and schema. `pnpm
88
+ run gen:api` regenerates the schemas, the CLI-bundled subset in
89
+ `templates/schemas/`, and the corresponding `json.schemas` editor
90
+ associations in the repository's `.vscode/settings.json` from this file, so
91
+ none of them can drift from one another.
92
+
64
93
  Compose driver commands require a `compose` instance:
65
94
 
66
95
  ```bash
@@ -117,6 +146,8 @@ The build synchronizes and validates bundled CLI assets from canonical sources:
117
146
 
118
147
  - `deployment/docker-compose/docker-compose.yml`
119
148
  - `assets/config/demo/**`
149
+ - `schemas/*.schema.json` referenced by `src/config-validate/registry.json`
150
+ (regenerate with `pnpm run gen:api` after changing backend import schemas)
120
151
 
121
152
  ## Single Executable Application
122
153
 
@@ -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":"AAMA,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,185 @@
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 { CLI_VALIDATED_REGISTRY } from "./registry.js";
6
+ const PLACEHOLDER_PATTERN = /\$\{([A-Z0-9_]+)(?::([^}]*))?\}/g;
7
+ async function discoverTenantDirectories(rootPath) {
8
+ const entries = await readdir(rootPath, { withFileTypes: true });
9
+ return entries
10
+ .filter((entry) => entry.isDirectory())
11
+ .map((entry) => entry.name)
12
+ .sort((left, right) => left.localeCompare(right));
13
+ }
14
+ export async function validateTenantsRoot(rootPath, schemas, env) {
15
+ if (!existsSync(rootPath)) {
16
+ throw new Error(`Configuration root not found: ${rootPath}`);
17
+ }
18
+ const tenantIds = await discoverTenantDirectories(rootPath);
19
+ const results = [];
20
+ for (const tenantId of tenantIds) {
21
+ results.push(await validateTenantDirectory(join(rootPath, tenantId), tenantId, schemas, env));
22
+ }
23
+ return results;
24
+ }
25
+ export async function validateTenantDirectory(tenantPath, tenantId, schemas, env) {
26
+ if (!existsSync(tenantPath)) {
27
+ throw new Error(`Tenant directory not found: ${tenantPath}`);
28
+ }
29
+ const state = {
30
+ getValidator: createValidatorFactory(schemas),
31
+ env,
32
+ errors: [],
33
+ resourceCounts: {},
34
+ files: 0,
35
+ };
36
+ for (const entry of CLI_VALIDATED_REGISTRY) {
37
+ if (entry.kind === "file") {
38
+ await validateFixedFileEntry(tenantPath, entry, state);
39
+ }
40
+ else {
41
+ await validateDirectoryEntry(tenantPath, entry, state);
42
+ }
43
+ }
44
+ return {
45
+ id: tenantId,
46
+ valid: state.errors.length === 0,
47
+ files: state.files,
48
+ errors: state.errors,
49
+ resourceCounts: state.resourceCounts,
50
+ };
51
+ }
52
+ function createValidatorFactory(schemas) {
53
+ const ajv = new Ajv2020({ allErrors: true, strict: false });
54
+ const validators = new Map();
55
+ return (schemaFile) => {
56
+ const cached = validators.get(schemaFile);
57
+ if (cached) {
58
+ return cached;
59
+ }
60
+ const schema = schemas.get(schemaFile);
61
+ if (!schema) {
62
+ throw new Error(`No bundled schema found for "${schemaFile}".`);
63
+ }
64
+ const validator = ajv.compile(schema);
65
+ validators.set(schemaFile, validator);
66
+ return validator;
67
+ };
68
+ }
69
+ async function validateFixedFileEntry(tenantPath, entry, state) {
70
+ const filePath = join(tenantPath, entry.file);
71
+ if (!existsSync(filePath)) {
72
+ if (entry.required) {
73
+ state.errors.push({
74
+ file: entry.file,
75
+ message: `Missing required ${entry.resourceType} file`,
76
+ });
77
+ }
78
+ return;
79
+ }
80
+ state.files += 1;
81
+ await recordValidationOutcome(filePath, entry.file, entry.resourceType, entry.schemaFile, state);
82
+ }
83
+ async function validateDirectoryEntry(tenantPath, entry, state) {
84
+ const directoryPath = join(tenantPath, entry.subfolder);
85
+ if (!existsSync(directoryPath)) {
86
+ return;
87
+ }
88
+ const dirEntries = (await readdir(directoryPath, { withFileTypes: true }))
89
+ .filter((dirEntry) => dirEntry.isFile() && dirEntry.name.endsWith(".json"))
90
+ .sort((left, right) => left.name.localeCompare(right.name));
91
+ for (const dirEntry of dirEntries) {
92
+ const relativeFile = `${entry.subfolder}/${dirEntry.name}`;
93
+ const filePath = join(directoryPath, dirEntry.name);
94
+ state.files += 1;
95
+ await recordValidationOutcome(filePath, relativeFile, entry.resourceType, entry.schemaFile, state);
96
+ }
97
+ }
98
+ async function recordValidationOutcome(filePath, relativeFile, resourceType, schemaFile, state) {
99
+ const isValid = await validateResourceFile(filePath, relativeFile, schemaFile, state.getValidator, state.env, state.errors);
100
+ if (isValid) {
101
+ state.resourceCounts[resourceType] = (state.resourceCounts[resourceType] ?? 0) + 1;
102
+ }
103
+ }
104
+ async function validateResourceFile(filePath, relativeFile, schemaFile, getValidator, env, errors) {
105
+ let raw;
106
+ try {
107
+ raw = await readFile(filePath, "utf8");
108
+ }
109
+ catch (error) {
110
+ errors.push({
111
+ file: relativeFile,
112
+ message: `Failed to read file: ${error.message}`,
113
+ });
114
+ return false;
115
+ }
116
+ let payload;
117
+ try {
118
+ payload = JSON.parse(raw);
119
+ }
120
+ catch (error) {
121
+ errors.push({ file: relativeFile, message: `Invalid JSON: ${error.message}` });
122
+ return false;
123
+ }
124
+ const errorsBefore = errors.length;
125
+ const resolved = resolvePlaceholders(payload, env, relativeFile, errors);
126
+ if (errors.length > errorsBefore) {
127
+ return false;
128
+ }
129
+ const validate = getValidator(schemaFile);
130
+ if (!validate(resolved)) {
131
+ for (const issue of validate.errors ?? []) {
132
+ errors.push({
133
+ file: relativeFile,
134
+ path: formatErrorPath(issue),
135
+ message: issue.message ?? "Invalid value",
136
+ });
137
+ }
138
+ return false;
139
+ }
140
+ return true;
141
+ }
142
+ function formatErrorPath(issue) {
143
+ const path = issue.instancePath.replace(/^\//, "").replaceAll("/", ".");
144
+ if (path) {
145
+ return path;
146
+ }
147
+ const missingProperty = issue.params
148
+ ?.missingProperty;
149
+ return missingProperty;
150
+ }
151
+ /**
152
+ * Resolves ${VAR} / ${VAR:default} placeholders for schema validation without ever
153
+ * emitting resolved secret values in error output; unresolved required placeholders
154
+ * are reported by reference to their variable name only.
155
+ */
156
+ function resolvePlaceholders(value, env, file, errors) {
157
+ if (typeof value === "string") {
158
+ return value.replace(PLACEHOLDER_PATTERN, (match, varName, defaultValue) => {
159
+ const envValue = env[varName];
160
+ if (envValue !== undefined && envValue !== "") {
161
+ return envValue;
162
+ }
163
+ if (defaultValue !== undefined) {
164
+ return defaultValue;
165
+ }
166
+ errors.push({
167
+ file,
168
+ message: `Unresolved placeholder \${${varName}}: no environment value or default is available`,
169
+ });
170
+ return match;
171
+ });
172
+ }
173
+ if (Array.isArray(value)) {
174
+ return value.map((item) => resolvePlaceholders(item, env, file, errors));
175
+ }
176
+ if (value && typeof value === "object") {
177
+ const result = {};
178
+ for (const [key, val] of Object.entries(value)) {
179
+ result[key] = resolvePlaceholders(val, env, file, errors);
180
+ }
181
+ return result;
182
+ }
183
+ return value;
184
+ }
185
+ //# sourceMappingURL=validator.js.map