@eudiplo/cli 7.0.1 → 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.
- package/README.md +34 -3
- package/dist/config-validate/index.d.ts +4 -0
- package/dist/config-validate/index.d.ts.map +1 -0
- package/dist/config-validate/index.js +4 -0
- package/dist/config-validate/index.js.map +1 -0
- package/dist/config-validate/registry.d.ts +5 -0
- package/dist/config-validate/registry.d.ts.map +1 -0
- package/dist/config-validate/registry.js +8 -0
- package/dist/config-validate/registry.js.map +1 -0
- package/dist/config-validate/registry.json +138 -0
- package/dist/config-validate/report.d.ts +21 -0
- package/dist/config-validate/report.d.ts.map +1 -0
- package/dist/config-validate/report.js +78 -0
- package/dist/config-validate/report.js.map +1 -0
- package/dist/config-validate/schemas.d.ts +2 -0
- package/dist/config-validate/schemas.d.ts.map +1 -0
- package/dist/config-validate/schemas.js +24 -0
- package/dist/config-validate/schemas.js.map +1 -0
- package/dist/config-validate/types.d.ts +33 -0
- package/dist/config-validate/types.d.ts.map +1 -0
- package/dist/config-validate/types.js +2 -0
- package/dist/config-validate/types.js.map +1 -0
- package/dist/config-validate/validator.d.ts +4 -0
- package/dist/config-validate/validator.d.ts.map +1 -0
- package/dist/config-validate/validator.js +185 -0
- package/dist/config-validate/validator.js.map +1 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +34 -1
- package/dist/runtime.js.map +1 -1
- package/package.json +5 -2
- package/templates/demo-config/issuance/status-lists/pid.json +1 -1
- package/templates/schemas/CreateAttributeProviderDto.schema.json +99 -0
- package/templates/schemas/CreateClientDto.schema.json +76 -0
- package/templates/schemas/CreateRegistrarConfigDto.schema.json +61 -0
- package/templates/schemas/CreateWebhookEndpointDto.schema.json +99 -0
- package/templates/schemas/CredentialConfigCreate.schema.json +673 -0
- package/templates/schemas/ImportTenantDto.schema.json +24 -0
- package/templates/schemas/IssuanceConfig.schema.json +492 -0
- package/templates/schemas/KeyChainImportDto.schema.json +111 -0
- package/templates/schemas/KmsConfigDto.schema.json +990 -0
- package/templates/schemas/PresentationConfigCreateDto.schema.json +627 -0
- package/templates/schemas/StatusListImportDto.schema.json +61 -0
- package/templates/schemas/TrustListCreateDto.schema.json +191 -0
- 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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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
|