@lesliechan721/aw 0.1.0-beta.10 → 0.1.0-beta.12
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/dist/authoring/assets/aw-config.d.mts +1 -50
- package/dist/authoring/assets/aw-config.mjs +58 -928
- package/dist/authoring/assets/aw-config.mjs.map +1 -1
- package/dist/authoring/scripts/init-plugin.mjs +28 -33
- package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
- package/dist/authoring/scripts/lib/plugin-version-check.d.mts +1 -0
- package/dist/authoring/scripts/lib/plugin-version-check.mjs +229 -8
- package/dist/authoring/scripts/lib/plugin-version-check.mjs.map +1 -1
- package/dist/authoring/scripts/validate-plugin.mjs +159 -48
- package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
- package/dist/scripts/bundle-plugins.js +18 -4
- package/dist/scripts/bundle-plugins.js.map +1 -1
- package/dist/scripts/test-plugin-scripts.d.ts +2 -0
- package/dist/scripts/test-plugin-scripts.js +21 -5
- package/dist/scripts/test-plugin-scripts.js.map +1 -1
- package/dist/src/commands/aw-capabilities.d.ts +1 -1
- package/dist/src/commands/aw-capabilities.js +1 -1
- package/dist/src/commands/aw-capabilities.js.map +1 -1
- package/dist/src/commands/catalog-dev-source.d.ts +6 -3
- package/dist/src/commands/catalog-dev-source.js +96 -35
- package/dist/src/commands/catalog-dev-source.js.map +1 -1
- package/dist/src/commands/catalog-publication-inspection.d.ts +1 -1
- package/dist/src/commands/catalog-publication-inspection.js +54 -8
- package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
- package/dist/src/commands/catalog-publication-journal.js +1 -1
- package/dist/src/commands/catalog-publication-journal.js.map +1 -1
- package/dist/src/commands/catalog-source.d.ts +19 -4
- package/dist/src/commands/catalog-source.js +121 -12
- package/dist/src/commands/catalog-source.js.map +1 -1
- package/dist/src/commands/catalog-store.d.ts +26 -7
- package/dist/src/commands/catalog-store.js +361 -42
- package/dist/src/commands/catalog-store.js.map +1 -1
- package/dist/src/commands/console.js +28 -17
- package/dist/src/commands/console.js.map +1 -1
- package/dist/src/commands/operation-plan.d.ts +2 -1
- package/dist/src/commands/operation-plan.js +24 -0
- package/dist/src/commands/operation-plan.js.map +1 -1
- package/dist/src/commands/operation-planner.d.ts +2 -2
- package/dist/src/commands/operation-planner.js +22 -20
- package/dist/src/commands/operation-planner.js.map +1 -1
- package/dist/src/commands/options.d.ts +2 -2
- package/dist/src/commands/options.js +18 -4
- package/dist/src/commands/options.js.map +1 -1
- package/dist/src/commands/plugin-capabilities.js +2 -2
- package/dist/src/commands/plugin-capabilities.js.map +1 -1
- package/dist/src/commands/rebuild-command.d.ts +19 -0
- package/dist/src/commands/rebuild-command.js +828 -283
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +25 -47
- package/dist/src/commands/rebuild-integration.js +1176 -1016
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/rebuild-operations.d.ts +50 -12
- package/dist/src/commands/rebuild-operations.js +207 -101
- package/dist/src/commands/rebuild-operations.js.map +1 -1
- package/dist/src/commands/status-v6.d.ts +21 -2
- package/dist/src/commands/status-v6.js +163 -31
- package/dist/src/commands/status-v6.js.map +1 -1
- package/dist/src/commands/sync-command.d.ts +17 -0
- package/dist/src/commands/sync-command.js +45 -37
- package/dist/src/commands/sync-command.js.map +1 -1
- package/dist/src/commands/write-commands.d.ts +5 -0
- package/dist/src/commands/write-commands.js +50 -3
- package/dist/src/commands/write-commands.js.map +1 -1
- package/dist/src/generators/local-plugin.js +14 -4
- package/dist/src/generators/local-plugin.js.map +1 -1
- package/dist/src/index.js +260 -91
- package/dist/src/index.js.map +1 -1
- package/dist/src/output/command-receipt.js +1 -3
- package/dist/src/output/command-receipt.js.map +1 -1
- package/dist/src/output/rebuild-progress.js +1 -3
- package/dist/src/output/rebuild-progress.js.map +1 -1
- package/dist/src/portable/materializer.js +10 -8
- package/dist/src/portable/materializer.js.map +1 -1
- package/dist/src/tui/catalog-generation.js +41 -1
- package/dist/src/tui/catalog-generation.js.map +1 -1
- package/dist/src/tui/catalog-sources/session.d.ts +1 -0
- package/dist/src/tui/catalog-sources/session.js +16 -3
- package/dist/src/tui/catalog-sources/session.js.map +1 -1
- package/dist/src/tui/catalog-sources/types.d.ts +3 -2
- package/dist/src/tui/components.d.ts +4 -0
- package/dist/src/tui/components.js +36 -24
- package/dist/src/tui/components.js.map +1 -1
- package/dist/src/tui/console-app-v4.d.ts +13 -4
- package/dist/src/tui/console-app-v4.js +2065 -857
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.js +731 -62
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +156 -10
- package/dist/src/tui/controller.js +1187 -163
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/diagnostic-projection.d.ts +22 -0
- package/dist/src/tui/diagnostic-projection.js +437 -0
- package/dist/src/tui/diagnostic-projection.js.map +1 -0
- package/dist/src/tui/diagnostics.d.ts +1 -1
- package/dist/src/tui/diagnostics.js +7 -2
- package/dist/src/tui/diagnostics.js.map +1 -1
- package/dist/src/tui/glossary.d.ts +23 -0
- package/dist/src/tui/glossary.js +42 -0
- package/dist/src/tui/glossary.js.map +1 -0
- package/dist/src/tui/index.d.ts +4 -1
- package/dist/src/tui/index.js +4 -1
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/keys.d.ts +44 -0
- package/dist/src/tui/keys.js +43 -15
- package/dist/src/tui/keys.js.map +1 -1
- package/dist/src/tui/lifecycle-action-view.d.ts +6 -0
- package/dist/src/tui/lifecycle-action-view.js +34 -0
- package/dist/src/tui/lifecycle-action-view.js.map +1 -0
- package/dist/src/tui/model.d.ts +12 -2
- package/dist/src/tui/model.js +128 -52
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/operation-diagnostic-screens.d.ts +21 -0
- package/dist/src/tui/operation-diagnostic-screens.js +101 -0
- package/dist/src/tui/operation-diagnostic-screens.js.map +1 -0
- package/dist/src/tui/operation-history.d.ts +80 -0
- package/dist/src/tui/operation-history.js +130 -0
- package/dist/src/tui/operation-history.js.map +1 -0
- package/dist/src/tui/operation-review-planner.js +366 -99
- package/dist/src/tui/operation-review-planner.js.map +1 -1
- package/dist/src/tui/router.d.ts +82 -17
- package/dist/src/tui/router.js +186 -38
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/screen-registry.d.ts +546 -0
- package/dist/src/tui/screen-registry.js +179 -0
- package/dist/src/tui/screen-registry.js.map +1 -0
- package/dist/src/tui/session-console-app.d.ts +6 -0
- package/dist/src/tui/session-console-app.js +13 -0
- package/dist/src/tui/session-console-app.js.map +1 -0
- package/dist/src/tui/session-host.d.ts +44 -0
- package/dist/src/tui/session-host.js +197 -0
- package/dist/src/tui/session-host.js.map +1 -0
- package/dist/src/types.d.ts +64 -3
- package/dist/src/types.js +1 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/agent-version.js +1 -1
- package/dist/src/utils/agent-version.js.map +1 -1
- package/dist/src/utils/artifact-plan.d.ts +2 -2
- package/dist/src/utils/artifact-plan.js +8 -6
- package/dist/src/utils/artifact-plan.js.map +1 -1
- package/dist/src/utils/artifact-renderer.d.ts +2 -1
- package/dist/src/utils/artifact-renderer.js +139 -120
- package/dist/src/utils/artifact-renderer.js.map +1 -1
- package/dist/src/utils/artifact-request.d.ts +5 -0
- package/dist/src/utils/artifact-request.js +40 -0
- package/dist/src/utils/artifact-request.js.map +1 -1
- package/dist/src/utils/bundle-layout.js +4 -4
- package/dist/src/utils/bundle-layout.js.map +1 -1
- package/dist/src/utils/catalog-capability-projection.d.ts +8 -0
- package/dist/src/utils/catalog-capability-projection.js +14 -0
- package/dist/src/utils/catalog-capability-projection.js.map +1 -0
- package/dist/src/utils/catalog-generation-resolver.d.ts +14 -2
- package/dist/src/utils/catalog-generation-resolver.js +49 -27
- package/dist/src/utils/catalog-generation-resolver.js.map +1 -1
- package/dist/src/utils/catalog-generation-service.d.ts +8 -1
- package/dist/src/utils/catalog-generation-service.js +81 -47
- package/dist/src/utils/catalog-generation-service.js.map +1 -1
- package/dist/src/utils/catalog-generation-store.d.ts +1 -0
- package/dist/src/utils/catalog-generation-store.js +60 -5
- package/dist/src/utils/catalog-generation-store.js.map +1 -1
- package/dist/src/utils/catalog-operation-context.js +1 -0
- package/dist/src/utils/catalog-operation-context.js.map +1 -1
- package/dist/src/utils/catalog-public-error.js +4 -0
- package/dist/src/utils/catalog-public-error.js.map +1 -1
- package/dist/src/utils/catalog-source-impact.d.ts +3 -2
- package/dist/src/utils/catalog-source-impact.js +142 -20
- package/dist/src/utils/catalog-source-impact.js.map +1 -1
- package/dist/src/utils/catalog-source-references.js +95 -9
- package/dist/src/utils/catalog-source-references.js.map +1 -1
- package/dist/src/utils/catalog-sources.d.ts +1 -1
- package/dist/src/utils/catalog-sources.js +19 -3
- package/dist/src/utils/catalog-sources.js.map +1 -1
- package/dist/src/utils/codex-manager.js +8 -0
- package/dist/src/utils/codex-manager.js.map +1 -1
- package/dist/src/utils/codex-native-observation.d.ts +34 -1
- package/dist/src/utils/codex-native-observation.js +132 -0
- package/dist/src/utils/codex-native-observation.js.map +1 -1
- package/dist/src/utils/codex-reconciler.d.ts +0 -2
- package/dist/src/utils/codex-reconciler.js.map +1 -1
- package/dist/src/utils/codex-shared-native.d.ts +2 -26
- package/dist/src/utils/codex-shared-native.js +8 -82
- package/dist/src/utils/codex-shared-native.js.map +1 -1
- package/dist/src/utils/git-catalog-provider.d.ts +3 -0
- package/dist/src/utils/git-catalog-provider.js +40 -1
- package/dist/src/utils/git-catalog-provider.js.map +1 -1
- package/dist/src/utils/local-marketplace.d.ts +1 -0
- package/dist/src/utils/local-marketplace.js +69 -73
- package/dist/src/utils/local-marketplace.js.map +1 -1
- package/dist/src/utils/multi-source-catalog.js +1 -0
- package/dist/src/utils/multi-source-catalog.js.map +1 -1
- package/dist/src/utils/native-cli.d.ts +9 -4
- package/dist/src/utils/native-cli.js +280 -34
- package/dist/src/utils/native-cli.js.map +1 -1
- package/dist/src/utils/path-catalog-provider.js +2 -0
- package/dist/src/utils/path-catalog-provider.js.map +1 -1
- package/dist/src/utils/plugin-config-impact.d.ts +33 -0
- package/dist/src/utils/plugin-config-impact.js +298 -0
- package/dist/src/utils/plugin-config-impact.js.map +1 -0
- package/dist/src/utils/plugin-config-write.d.ts +36 -14
- package/dist/src/utils/plugin-config-write.js +709 -225
- package/dist/src/utils/plugin-config-write.js.map +1 -1
- package/dist/src/utils/plugin-config.d.ts +58 -4
- package/dist/src/utils/plugin-config.js +328 -60
- package/dist/src/utils/plugin-config.js.map +1 -1
- package/dist/src/utils/plugin-scripts.d.ts +20 -12
- package/dist/src/utils/plugin-scripts.js +389 -114
- package/dist/src/utils/plugin-scripts.js.map +1 -1
- package/dist/src/utils/process-tree.d.ts +2 -2
- package/dist/src/utils/process-tree.js +1 -1
- package/dist/src/utils/process-tree.js.map +1 -1
- package/dist/src/utils/project-boundary.d.ts +11 -1
- package/dist/src/utils/project-boundary.js +33 -11
- package/dist/src/utils/project-boundary.js.map +1 -1
- package/dist/src/utils/safe-operation-diagnostic.d.ts +15 -0
- package/dist/src/utils/safe-operation-diagnostic.js +33 -0
- package/dist/src/utils/safe-operation-diagnostic.js.map +1 -0
- package/dist/src/utils/sensitive-content.d.ts +4 -0
- package/dist/src/utils/sensitive-content.js +36 -0
- package/dist/src/utils/sensitive-content.js.map +1 -0
- package/dist/src/utils/windows-claude-shim.d.ts +9 -0
- package/dist/src/utils/windows-claude-shim.js +141 -0
- package/dist/src/utils/windows-claude-shim.js.map +1 -0
- package/dist/vitest.config.js +91 -10
- package/dist/vitest.config.js.map +1 -1
- package/package.json +9 -9
- package/dist/authoring/templates/aw-config-runtime.d.mts +0 -2
- package/dist/authoring/templates/aw-config-runtime.mjs +0 -407
- package/dist/authoring/templates/aw-config-runtime.mjs.map +0 -1
|
@@ -1,953 +1,85 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
-
import os from 'node:os';
|
|
4
3
|
import path from 'node:path';
|
|
5
4
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
/* Generated validator contract from packages/plugin-core/src/plugin-config.ts. */
|
|
7
|
-
const CONFIG_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/u;
|
|
8
|
-
const IDENTIFIER_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/u;
|
|
9
|
-
const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
10
|
-
const SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|(?:[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*))(?:\.(?:0|[1-9]\d*|(?:[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/u;
|
|
11
|
-
const HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(?:\.(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?))*$/u;
|
|
12
|
-
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
|
|
13
|
-
const STRING_FORMATS = new Set([
|
|
14
|
-
'identifier',
|
|
15
|
-
'slug',
|
|
16
|
-
'semver',
|
|
17
|
-
'url',
|
|
18
|
-
'hostname',
|
|
19
|
-
'email',
|
|
20
|
-
]);
|
|
21
|
-
const MAX_OBJECT_PROPERTIES = 32;
|
|
22
|
-
const DANGEROUS_CONFIG_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
23
|
-
function createPropertyMap() {
|
|
24
|
-
return Object.create(null);
|
|
25
|
-
}
|
|
26
|
-
function ownProperty(value, property) {
|
|
27
|
-
return value !== undefined && Object.hasOwn(value, property) ? value[property] : undefined;
|
|
28
|
-
}
|
|
29
|
-
function isDangerousConfigKey(value) {
|
|
30
|
-
return DANGEROUS_CONFIG_KEYS.has(value);
|
|
31
|
-
}
|
|
32
|
-
function asObject(value, path) {
|
|
33
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
34
|
-
throw new Error(`Invalid ${path}: expected an object.`);
|
|
35
|
-
}
|
|
36
|
-
return value;
|
|
37
|
-
}
|
|
38
|
-
function assertAllowedKeys(value, path, allowed) {
|
|
39
|
-
for (const key of Object.keys(value)) {
|
|
40
|
-
if (!allowed.includes(key)) {
|
|
41
|
-
throw new Error(`Invalid ${path}: unknown field "${key}".`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
function nonEmptyString(value, path) {
|
|
46
|
-
if (typeof value !== 'string' || value.trim() === '') {
|
|
47
|
-
throw new Error(`Invalid ${path}: expected a non-empty string.`);
|
|
48
|
-
}
|
|
49
|
-
return value;
|
|
50
|
-
}
|
|
51
|
-
function optionalText(value, path) {
|
|
52
|
-
return value === undefined ? undefined : nonEmptyString(value, path);
|
|
53
|
-
}
|
|
54
|
-
function optionalBoolean(value, path) {
|
|
55
|
-
if (value === undefined)
|
|
56
|
-
return undefined;
|
|
57
|
-
if (typeof value !== 'boolean') {
|
|
58
|
-
throw new Error(`Invalid ${path}: expected a boolean.`);
|
|
59
|
-
}
|
|
60
|
-
return value;
|
|
61
|
-
}
|
|
62
|
-
function finiteNumber(value, path) {
|
|
63
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
64
|
-
throw new Error(`Invalid ${path}: expected a finite number.`);
|
|
65
|
-
}
|
|
66
|
-
return value;
|
|
67
|
-
}
|
|
68
|
-
function nonNegativeInteger(value, path) {
|
|
69
|
-
const result = finiteNumber(value, path);
|
|
70
|
-
if (!Number.isInteger(result) || result < 0) {
|
|
71
|
-
throw new Error(`Invalid ${path}: expected a non-negative integer.`);
|
|
72
|
-
}
|
|
73
|
-
return result;
|
|
74
|
-
}
|
|
75
|
-
function normalizeStringValidation(value, path) {
|
|
76
|
-
const input = asObject(value, path);
|
|
77
|
-
assertAllowedKeys(input, path, ['minLength', 'maxLength', 'pattern', 'format']);
|
|
78
|
-
const minLength = input.minLength === undefined
|
|
79
|
-
? undefined
|
|
80
|
-
: nonNegativeInteger(input.minLength, `${path}.minLength`);
|
|
81
|
-
const maxLength = input.maxLength === undefined
|
|
82
|
-
? undefined
|
|
83
|
-
: nonNegativeInteger(input.maxLength, `${path}.maxLength`);
|
|
84
|
-
if (minLength !== undefined && maxLength !== undefined && minLength > maxLength) {
|
|
85
|
-
throw new Error(`Invalid ${path}: minLength must be less than or equal to maxLength.`);
|
|
86
|
-
}
|
|
87
|
-
if (input.pattern !== undefined && typeof input.pattern !== 'string') {
|
|
88
|
-
throw new Error(`Invalid ${path}.pattern: expected a string.`);
|
|
89
|
-
}
|
|
90
|
-
const pattern = input.pattern;
|
|
91
|
-
if (pattern !== undefined) {
|
|
92
|
-
try {
|
|
93
|
-
new RegExp(pattern, 'u');
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
throw new Error(`Invalid ${path}.pattern: expected a valid regular expression.`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
const format = input.format === undefined
|
|
100
|
-
? undefined
|
|
101
|
-
: nonEmptyString(input.format, `${path}.format`);
|
|
102
|
-
if (format !== undefined && !STRING_FORMATS.has(format)) {
|
|
103
|
-
throw new Error(`Invalid ${path}.format: unsupported string format "${format}".`);
|
|
104
|
-
}
|
|
105
|
-
return { minLength, maxLength, pattern, format };
|
|
106
|
-
}
|
|
107
|
-
function normalizeNumberValidation(value, path) {
|
|
108
|
-
const input = asObject(value, path);
|
|
109
|
-
assertAllowedKeys(input, path, ['integer', 'minimum', 'maximum', 'multipleOf']);
|
|
110
|
-
const integer = optionalBoolean(input.integer, `${path}.integer`);
|
|
111
|
-
const minimum = input.minimum === undefined
|
|
112
|
-
? undefined
|
|
113
|
-
: finiteNumber(input.minimum, `${path}.minimum`);
|
|
114
|
-
const maximum = input.maximum === undefined
|
|
115
|
-
? undefined
|
|
116
|
-
: finiteNumber(input.maximum, `${path}.maximum`);
|
|
117
|
-
const multipleOf = input.multipleOf === undefined
|
|
118
|
-
? undefined
|
|
119
|
-
: finiteNumber(input.multipleOf, `${path}.multipleOf`);
|
|
120
|
-
if (minimum !== undefined && maximum !== undefined && minimum > maximum) {
|
|
121
|
-
throw new Error(`Invalid ${path}: minimum must be less than or equal to maximum.`);
|
|
122
|
-
}
|
|
123
|
-
if (multipleOf !== undefined && multipleOf <= 0) {
|
|
124
|
-
throw new Error(`Invalid ${path}.multipleOf: expected a number greater than zero.`);
|
|
125
|
-
}
|
|
126
|
-
return { integer, minimum, maximum, multipleOf };
|
|
127
|
-
}
|
|
128
|
-
function createIssue(code, field, message, property) {
|
|
129
|
-
return {
|
|
130
|
-
code,
|
|
131
|
-
path: property === undefined ? { field } : { field, property },
|
|
132
|
-
message,
|
|
133
|
-
recoverable: true,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
function stringFormatMatches(value, format) {
|
|
137
|
-
if (format === 'identifier')
|
|
138
|
-
return IDENTIFIER_PATTERN.test(value);
|
|
139
|
-
if (format === 'slug')
|
|
140
|
-
return SLUG_PATTERN.test(value);
|
|
141
|
-
if (format === 'semver')
|
|
142
|
-
return SEMVER_PATTERN.test(value);
|
|
143
|
-
if (format === 'hostname')
|
|
144
|
-
return HOSTNAME_PATTERN.test(value);
|
|
145
|
-
if (format === 'email')
|
|
146
|
-
return EMAIL_PATTERN.test(value);
|
|
147
|
-
try {
|
|
148
|
-
const url = new URL(value);
|
|
149
|
-
return url.protocol.length > 1;
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
function validateString(value, validation, field, property) {
|
|
156
|
-
if (typeof value !== 'string') {
|
|
157
|
-
return [createIssue('type', field, 'Expected a string.', property)];
|
|
158
|
-
}
|
|
159
|
-
const issues = [];
|
|
160
|
-
if (validation?.minLength !== undefined && [...value].length < validation.minLength) {
|
|
161
|
-
issues.push(createIssue('minLength', field, `Expected at least ${validation.minLength} characters.`, property));
|
|
162
|
-
}
|
|
163
|
-
if (validation?.maxLength !== undefined && [...value].length > validation.maxLength) {
|
|
164
|
-
issues.push(createIssue('maxLength', field, `Expected at most ${validation.maxLength} characters.`, property));
|
|
165
|
-
}
|
|
166
|
-
if (validation?.pattern !== undefined && !new RegExp(validation.pattern, 'u').test(value)) {
|
|
167
|
-
issues.push(createIssue('pattern', field, `Expected a value that matches ${validation.pattern}.`, property));
|
|
168
|
-
}
|
|
169
|
-
if (validation?.format !== undefined && !stringFormatMatches(value, validation.format)) {
|
|
170
|
-
issues.push(createIssue('format', field, `Expected ${validation.format} format.`, property));
|
|
171
|
-
}
|
|
172
|
-
return issues;
|
|
173
|
-
}
|
|
174
|
-
function validateNumber(value, validation, field, property) {
|
|
175
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
176
|
-
return [createIssue('type', field, 'Expected a finite number.', property)];
|
|
177
|
-
}
|
|
178
|
-
const issues = [];
|
|
179
|
-
if (validation?.integer === true && !Number.isInteger(value)) {
|
|
180
|
-
issues.push(createIssue('integer', field, 'Expected an integer.', property));
|
|
181
|
-
}
|
|
182
|
-
if (validation?.minimum !== undefined && value < validation.minimum) {
|
|
183
|
-
issues.push(createIssue('minimum', field, `Expected a number greater than or equal to ${validation.minimum}.`, property));
|
|
184
|
-
}
|
|
185
|
-
if (validation?.maximum !== undefined && value > validation.maximum) {
|
|
186
|
-
issues.push(createIssue('maximum', field, `Expected a number less than or equal to ${validation.maximum}.`, property));
|
|
187
|
-
}
|
|
188
|
-
if (validation?.multipleOf !== undefined
|
|
189
|
-
&& Math.abs(value / validation.multipleOf - Math.round(value / validation.multipleOf)) > 1e-9) {
|
|
190
|
-
issues.push(createIssue('multipleOf', field, `Expected a multiple of ${validation.multipleOf}.`, property));
|
|
191
|
-
}
|
|
192
|
-
return issues;
|
|
193
|
-
}
|
|
194
|
-
function validateScalarField(fieldDefinition, value, field, property, checkChoices = true) {
|
|
195
|
-
const issues = fieldDefinition.type === 'string'
|
|
196
|
-
? validateString(value, fieldDefinition.validation, field, property)
|
|
197
|
-
: fieldDefinition.type === 'number'
|
|
198
|
-
? validateNumber(value, fieldDefinition.validation, field, property)
|
|
199
|
-
: typeof value === 'boolean'
|
|
200
|
-
? []
|
|
201
|
-
: [createIssue('type', field, 'Expected a boolean.', property)];
|
|
202
|
-
if (checkChoices
|
|
203
|
-
&& issues.length === 0
|
|
204
|
-
&& fieldDefinition.choices?.allowCustom === false
|
|
205
|
-
&& matchPluginConfigPreset(fieldDefinition, value) === undefined) {
|
|
206
|
-
issues.push(createIssue('choice', field, 'Expected a declared preset value.', property));
|
|
207
|
-
}
|
|
208
|
-
return issues;
|
|
209
|
-
}
|
|
210
|
-
function validateObjectField(fieldDefinition, value, field, checkChoices = true) {
|
|
211
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
212
|
-
return [createIssue('type', field, 'Expected an object.')];
|
|
213
|
-
}
|
|
214
|
-
const input = value;
|
|
215
|
-
const keys = Object.keys(input);
|
|
216
|
-
const issues = [];
|
|
217
|
-
if (keys.length > MAX_OBJECT_PROPERTIES) {
|
|
218
|
-
issues.push(createIssue('maxProperties', field, `Expected at most ${MAX_OBJECT_PROPERTIES} properties.`));
|
|
219
|
-
}
|
|
220
|
-
if (fieldDefinition.validation?.minProperties !== undefined
|
|
221
|
-
&& keys.length < fieldDefinition.validation.minProperties) {
|
|
222
|
-
issues.push(createIssue('minProperties', field, `Expected at least ${fieldDefinition.validation.minProperties} properties.`));
|
|
223
|
-
}
|
|
224
|
-
if (fieldDefinition.validation?.maxProperties !== undefined
|
|
225
|
-
&& keys.length > fieldDefinition.validation.maxProperties) {
|
|
226
|
-
issues.push(createIssue('maxProperties', field, `Expected at most ${fieldDefinition.validation.maxProperties} properties.`));
|
|
227
|
-
}
|
|
228
|
-
for (const [property, child] of Object.entries(fieldDefinition.properties ?? {})) {
|
|
229
|
-
if (child.required === true && !Object.hasOwn(input, property)) {
|
|
230
|
-
issues.push(createIssue('required', field, 'Required property is missing.', property));
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
for (const [property, childValue] of Object.entries(input)) {
|
|
234
|
-
if (isDangerousConfigKey(property)) {
|
|
235
|
-
issues.push(createIssue('dangerousKey', field, 'Dangerous config key is not allowed.', property));
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
const child = ownProperty(fieldDefinition.properties, property);
|
|
239
|
-
if (child !== undefined) {
|
|
240
|
-
issues.push(...validateScalarField(child, childValue, field, property));
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
const dynamicChild = fieldDefinition.additionalProperties;
|
|
244
|
-
if (dynamicChild === undefined || dynamicChild === false) {
|
|
245
|
-
issues.push(createIssue('additionalProperty', field, 'Additional property is not allowed.', property));
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
if (fieldDefinition.validation?.key !== undefined) {
|
|
249
|
-
issues.push(...validateString(property, fieldDefinition.validation.key, field, property));
|
|
250
|
-
}
|
|
251
|
-
issues.push(...validateScalarField(dynamicChild, childValue, field, property));
|
|
252
|
-
}
|
|
253
|
-
if (checkChoices
|
|
254
|
-
&& issues.length === 0
|
|
255
|
-
&& fieldDefinition.choices?.allowCustom === false
|
|
256
|
-
&& matchPluginConfigPreset(fieldDefinition, input) === undefined) {
|
|
257
|
-
issues.push(createIssue('choice', field, 'Expected a declared preset value.'));
|
|
258
|
-
}
|
|
259
|
-
return issues;
|
|
260
|
-
}
|
|
261
|
-
function resolveValueArguments(first, second, third) {
|
|
262
|
-
if (typeof first === 'string') {
|
|
263
|
-
return {
|
|
264
|
-
field: first,
|
|
265
|
-
definition: second,
|
|
266
|
-
value: third,
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
return {
|
|
270
|
-
definition: first,
|
|
271
|
-
value: second,
|
|
272
|
-
field: typeof third === 'string' ? third : 'value',
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
export function validatePluginConfigValue(first, second, third) {
|
|
276
|
-
const resolved = resolveValueArguments(first, second, third);
|
|
277
|
-
return resolved.definition.type === 'object'
|
|
278
|
-
? validateObjectField(resolved.definition, resolved.value, resolved.field)
|
|
279
|
-
: validateScalarField(resolved.definition, resolved.value, resolved.field);
|
|
280
|
-
}
|
|
281
|
-
export function validatePluginConfigDocument(definition, value) {
|
|
282
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
283
|
-
return [createIssue('type', '$document', 'Expected a config object.')];
|
|
284
|
-
}
|
|
285
|
-
const input = value;
|
|
286
|
-
const issues = [];
|
|
287
|
-
for (const [field, fieldDefinition] of Object.entries(definition.fields)) {
|
|
288
|
-
if (!Object.hasOwn(input, field)) {
|
|
289
|
-
if (fieldDefinition.required === true) {
|
|
290
|
-
issues.push(createIssue('required', field, 'Required field is missing.'));
|
|
291
|
-
}
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
issues.push(...validatePluginConfigValue(fieldDefinition, input[field], field));
|
|
295
|
-
}
|
|
296
|
-
for (const field of Object.keys(input)) {
|
|
297
|
-
if (isDangerousConfigKey(field)) {
|
|
298
|
-
issues.push(createIssue('dangerousKey', field, 'Dangerous config key is not allowed.'));
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
if (!Object.hasOwn(definition.fields, field)) {
|
|
302
|
-
issues.push(createIssue('unknownField', field, 'Unknown config field.'));
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
return issues;
|
|
306
|
-
}
|
|
307
|
-
export function canonicalizePluginConfigValue(value) {
|
|
308
|
-
if (typeof value !== 'object')
|
|
309
|
-
return value;
|
|
310
|
-
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0));
|
|
311
|
-
}
|
|
312
|
-
export function canonicalizePluginConfigValues(values) {
|
|
313
|
-
return Object.fromEntries(Object.entries(values)
|
|
314
|
-
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
315
|
-
.map(([key, value]) => [key, canonicalizePluginConfigValue(value)]));
|
|
316
|
-
}
|
|
317
|
-
export function stablePluginConfigStringify(value) {
|
|
318
|
-
return JSON.stringify(canonicalizePluginConfigValue(value));
|
|
319
|
-
}
|
|
320
|
-
export function matchPluginConfigPreset(definition, value) {
|
|
321
|
-
return definition.choices?.presets.find((preset) => stablePluginConfigStringify(preset.value) === stablePluginConfigStringify(value));
|
|
322
|
-
}
|
|
323
|
-
export function validatePluginConfigPreset(definition, preset, field = 'value') {
|
|
324
|
-
return definition.type === 'object'
|
|
325
|
-
? validateObjectField(definition, preset.value, field, false)
|
|
326
|
-
: validateScalarField(definition, preset.value, field, undefined, false);
|
|
327
|
-
}
|
|
328
|
-
export function redactPluginConfigValue(definition, value) {
|
|
329
|
-
if (definition.sensitive === true)
|
|
330
|
-
return '[redacted]';
|
|
331
|
-
if (definition.type !== 'object' || typeof value !== 'object')
|
|
332
|
-
return value;
|
|
333
|
-
const redacted = createPropertyMap();
|
|
334
|
-
for (const [property, childValue] of Object.entries(value)) {
|
|
335
|
-
const child = ownProperty(definition.properties, property) ?? (definition.additionalProperties === false ? undefined : definition.additionalProperties);
|
|
336
|
-
redacted[property] = child?.sensitive === true ? '[redacted]' : childValue;
|
|
337
|
-
}
|
|
338
|
-
return redacted;
|
|
339
|
-
}
|
|
340
|
-
export function formatPluginConfigIssue(issue) {
|
|
341
|
-
const path = issue.path.property === undefined
|
|
342
|
-
? issue.path.field
|
|
343
|
-
: `${issue.path.field}.${issue.path.property}`;
|
|
344
|
-
return `${path}: ${issue.message}`;
|
|
345
|
-
}
|
|
346
|
-
function normalizePreset(value, path) {
|
|
347
|
-
const input = asObject(value, path);
|
|
348
|
-
assertAllowedKeys(input, path, ['id', 'title', 'description', 'value', 'recommended']);
|
|
349
|
-
if (!Object.hasOwn(input, 'value')) {
|
|
350
|
-
throw new Error(`Invalid ${path}.value: value is required.`);
|
|
351
|
-
}
|
|
352
|
-
if (input.recommended !== undefined && input.recommended !== true) {
|
|
353
|
-
throw new Error(`Invalid ${path}.recommended: expected true.`);
|
|
354
|
-
}
|
|
355
|
-
return {
|
|
356
|
-
id: nonEmptyString(input.id, `${path}.id`),
|
|
357
|
-
title: nonEmptyString(input.title, `${path}.title`),
|
|
358
|
-
description: optionalText(input.description, `${path}.description`),
|
|
359
|
-
value: input.value,
|
|
360
|
-
recommended: input.recommended,
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
function normalizeChoices(value, path) {
|
|
364
|
-
const input = asObject(value, path);
|
|
365
|
-
assertAllowedKeys(input, path, ['allowCustom', 'presets']);
|
|
366
|
-
if (typeof input.allowCustom !== 'boolean') {
|
|
367
|
-
throw new Error(`Invalid ${path}.allowCustom: expected a boolean.`);
|
|
368
|
-
}
|
|
369
|
-
if (!Array.isArray(input.presets)) {
|
|
370
|
-
throw new Error(`Invalid ${path}.presets: expected an array.`);
|
|
371
|
-
}
|
|
372
|
-
return {
|
|
373
|
-
allowCustom: input.allowCustom,
|
|
374
|
-
presets: input.presets.map((preset, index) => normalizePreset(preset, `${path}.presets[${index}]`)),
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
function normalizeScalarField(value, path, options) {
|
|
378
|
-
const input = asObject(value, path);
|
|
379
|
-
const type = nonEmptyString(input.type, `${path}.type`);
|
|
380
|
-
if (type !== 'string' && type !== 'number' && type !== 'boolean') {
|
|
381
|
-
throw new Error(`Invalid ${path}.type: expected string, number, or boolean.`);
|
|
382
|
-
}
|
|
383
|
-
const allowed = ['type', 'title', 'description', 'sensitive'];
|
|
384
|
-
if (options.allowDefault)
|
|
385
|
-
allowed.push('default');
|
|
386
|
-
if (options.allowRequired)
|
|
387
|
-
allowed.push('required');
|
|
388
|
-
allowed.push('choices');
|
|
389
|
-
if (type !== 'boolean')
|
|
390
|
-
allowed.push('validation');
|
|
391
|
-
assertAllowedKeys(input, path, allowed);
|
|
392
|
-
const common = {
|
|
393
|
-
title: optionalText(input.title, `${path}.title`),
|
|
394
|
-
description: optionalText(input.description, `${path}.description`),
|
|
395
|
-
required: options.allowRequired ? optionalBoolean(input.required, `${path}.required`) : undefined,
|
|
396
|
-
sensitive: optionalBoolean(input.sensitive, `${path}.sensitive`),
|
|
397
|
-
};
|
|
398
|
-
const defaultValue = options.allowDefault && input.default !== undefined
|
|
399
|
-
? input.default
|
|
400
|
-
: undefined;
|
|
401
|
-
if (type === 'string') {
|
|
402
|
-
return {
|
|
403
|
-
type,
|
|
404
|
-
...common,
|
|
405
|
-
default: defaultValue,
|
|
406
|
-
choices: input.choices === undefined
|
|
407
|
-
? undefined
|
|
408
|
-
: normalizeChoices(input.choices, `${path}.choices`),
|
|
409
|
-
validation: input.validation === undefined
|
|
410
|
-
? undefined
|
|
411
|
-
: normalizeStringValidation(input.validation, `${path}.validation`),
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
if (type === 'number') {
|
|
415
|
-
return {
|
|
416
|
-
type,
|
|
417
|
-
...common,
|
|
418
|
-
default: defaultValue,
|
|
419
|
-
choices: input.choices === undefined
|
|
420
|
-
? undefined
|
|
421
|
-
: normalizeChoices(input.choices, `${path}.choices`),
|
|
422
|
-
validation: input.validation === undefined
|
|
423
|
-
? undefined
|
|
424
|
-
: normalizeNumberValidation(input.validation, `${path}.validation`),
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
return {
|
|
428
|
-
type,
|
|
429
|
-
...common,
|
|
430
|
-
default: defaultValue,
|
|
431
|
-
choices: input.choices === undefined
|
|
432
|
-
? undefined
|
|
433
|
-
: normalizeChoices(input.choices, `${path}.choices`),
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
function assertChoices(definition, path) {
|
|
437
|
-
if (definition.choices === undefined)
|
|
438
|
-
return;
|
|
439
|
-
if (definition.choices.allowCustom === false && definition.choices.presets.length === 0) {
|
|
440
|
-
throw new Error(`Invalid ${path}.choices.presets: expected at least one preset when allowCustom is false.`);
|
|
441
|
-
}
|
|
442
|
-
const ids = new Set();
|
|
443
|
-
const values = new Set();
|
|
444
|
-
let recommendedCount = 0;
|
|
445
|
-
for (const [index, preset] of definition.choices.presets.entries()) {
|
|
446
|
-
if (ids.has(preset.id)) {
|
|
447
|
-
throw new Error(`Invalid ${path}.choices.presets[${index}].id: duplicate preset id "${preset.id}".`);
|
|
448
|
-
}
|
|
449
|
-
ids.add(preset.id);
|
|
450
|
-
const canonicalValue = stablePluginConfigStringify(preset.value);
|
|
451
|
-
if (values.has(canonicalValue)) {
|
|
452
|
-
throw new Error(`Invalid ${path}.choices.presets[${index}].value: duplicate preset value.`);
|
|
453
|
-
}
|
|
454
|
-
values.add(canonicalValue);
|
|
455
|
-
if (preset.recommended === true)
|
|
456
|
-
recommendedCount += 1;
|
|
457
|
-
const issues = validatePluginConfigPreset(definition, preset, path);
|
|
458
|
-
if (issues.length > 0) {
|
|
459
|
-
throw new Error(`Invalid ${path}.choices.presets[${index}].value: ${formatPluginConfigIssue(issues[0])}`);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
if (recommendedCount > 1) {
|
|
463
|
-
throw new Error(`Invalid ${path}.choices: expected at most one recommended preset.`);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
function normalizeObjectField(value, path) {
|
|
467
|
-
const input = asObject(value, path);
|
|
468
|
-
assertAllowedKeys(input, path, [
|
|
469
|
-
'type',
|
|
470
|
-
'title',
|
|
471
|
-
'description',
|
|
472
|
-
'default',
|
|
473
|
-
'required',
|
|
474
|
-
'sensitive',
|
|
475
|
-
'choices',
|
|
476
|
-
'properties',
|
|
477
|
-
'additionalProperties',
|
|
478
|
-
'validation',
|
|
479
|
-
]);
|
|
480
|
-
if (input.properties === undefined && (input.additionalProperties === undefined || input.additionalProperties === false)) {
|
|
481
|
-
throw new Error(`Invalid ${path}: key-value object config requires a scalar additionalProperties field.`);
|
|
482
|
-
}
|
|
483
|
-
if (input.properties !== undefined && input.additionalProperties !== undefined && input.additionalProperties !== false) {
|
|
484
|
-
throw new Error(`Invalid ${path}: fixed and key-value object config modes cannot be mixed.`);
|
|
485
|
-
}
|
|
486
|
-
let properties;
|
|
487
|
-
if (input.properties !== undefined) {
|
|
488
|
-
const propertiesInput = asObject(input.properties, `${path}.properties`);
|
|
489
|
-
if (Object.keys(propertiesInput).length > MAX_OBJECT_PROPERTIES) {
|
|
490
|
-
throw new Error(`Invalid ${path}.properties: expected at most ${MAX_OBJECT_PROPERTIES} properties.`);
|
|
491
|
-
}
|
|
492
|
-
properties = createPropertyMap();
|
|
493
|
-
for (const [property, child] of Object.entries(propertiesInput)) {
|
|
494
|
-
if (isDangerousConfigKey(property)) {
|
|
495
|
-
throw new Error(`Invalid ${path}.properties: dangerous config key "${property}" is not allowed.`);
|
|
496
|
-
}
|
|
497
|
-
properties[property] = normalizeScalarField(child, `${path}.properties.${property}`, {
|
|
498
|
-
allowDefault: false,
|
|
499
|
-
allowRequired: true,
|
|
500
|
-
});
|
|
501
|
-
assertChoices(properties[property], `${path}.properties.${property}`);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
let additionalProperties;
|
|
505
|
-
if (input.additionalProperties === undefined || input.additionalProperties === false) {
|
|
506
|
-
additionalProperties = false;
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
additionalProperties = normalizeScalarField(input.additionalProperties, `${path}.additionalProperties`, { allowDefault: false, allowRequired: false });
|
|
510
|
-
assertChoices(additionalProperties, `${path}.additionalProperties`);
|
|
511
|
-
}
|
|
512
|
-
let validation;
|
|
513
|
-
if (input.validation !== undefined) {
|
|
514
|
-
const validationInput = asObject(input.validation, `${path}.validation`);
|
|
515
|
-
assertAllowedKeys(validationInput, `${path}.validation`, ['minProperties', 'maxProperties', 'key']);
|
|
516
|
-
const minProperties = validationInput.minProperties === undefined
|
|
517
|
-
? undefined
|
|
518
|
-
: nonNegativeInteger(validationInput.minProperties, `${path}.validation.minProperties`);
|
|
519
|
-
const maxProperties = validationInput.maxProperties === undefined
|
|
520
|
-
? undefined
|
|
521
|
-
: nonNegativeInteger(validationInput.maxProperties, `${path}.validation.maxProperties`);
|
|
522
|
-
if (minProperties !== undefined && minProperties > MAX_OBJECT_PROPERTIES) {
|
|
523
|
-
throw new Error(`Invalid ${path}.validation.minProperties: expected at most ${MAX_OBJECT_PROPERTIES}.`);
|
|
524
|
-
}
|
|
525
|
-
if (maxProperties !== undefined && maxProperties > MAX_OBJECT_PROPERTIES) {
|
|
526
|
-
throw new Error(`Invalid ${path}.validation.maxProperties: expected at most ${MAX_OBJECT_PROPERTIES}.`);
|
|
527
|
-
}
|
|
528
|
-
if (minProperties !== undefined && maxProperties !== undefined && minProperties > maxProperties) {
|
|
529
|
-
throw new Error(`Invalid ${path}.validation: minProperties must be less than or equal to maxProperties.`);
|
|
530
|
-
}
|
|
531
|
-
validation = {
|
|
532
|
-
minProperties,
|
|
533
|
-
maxProperties,
|
|
534
|
-
key: validationInput.key === undefined
|
|
535
|
-
? undefined
|
|
536
|
-
: normalizeStringValidation(validationInput.key, `${path}.validation.key`),
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
return {
|
|
540
|
-
type: 'object',
|
|
541
|
-
title: optionalText(input.title, `${path}.title`),
|
|
542
|
-
description: optionalText(input.description, `${path}.description`),
|
|
543
|
-
default: input.default,
|
|
544
|
-
required: optionalBoolean(input.required, `${path}.required`),
|
|
545
|
-
sensitive: optionalBoolean(input.sensitive, `${path}.sensitive`),
|
|
546
|
-
choices: input.choices === undefined
|
|
547
|
-
? undefined
|
|
548
|
-
: normalizeChoices(input.choices, `${path}.choices`),
|
|
549
|
-
...(properties === undefined
|
|
550
|
-
? { additionalProperties: additionalProperties }
|
|
551
|
-
: { properties, additionalProperties: false }),
|
|
552
|
-
validation,
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
export function normalizePluginConfigDefinition(value, path = 'config') {
|
|
556
|
-
const input = asObject(value, path);
|
|
557
|
-
assertAllowedKeys(input, path, ['fields', 'presentation']);
|
|
558
|
-
const fieldsInput = asObject(input.fields, `${path}.fields`);
|
|
559
|
-
const fields = createPropertyMap();
|
|
560
|
-
for (const [field, rawDefinition] of Object.entries(fieldsInput)) {
|
|
561
|
-
if (!CONFIG_KEY_PATTERN.test(field) || isDangerousConfigKey(field)) {
|
|
562
|
-
throw new Error(`Invalid ${path}.fields: config key "${field}" is not valid.`);
|
|
563
|
-
}
|
|
564
|
-
const rawField = asObject(rawDefinition, `${path}.fields.${field}`);
|
|
565
|
-
const definition = rawField.type === 'object'
|
|
566
|
-
? normalizeObjectField(rawField, `${path}.fields.${field}`)
|
|
567
|
-
: normalizeScalarField(rawField, `${path}.fields.${field}`, {
|
|
568
|
-
allowDefault: true,
|
|
569
|
-
allowRequired: true,
|
|
570
|
-
});
|
|
571
|
-
assertChoices(definition, `${path}.fields.${field}`);
|
|
572
|
-
if (definition.default !== undefined) {
|
|
573
|
-
const issues = validatePluginConfigValue(definition, definition.default, field);
|
|
574
|
-
if (issues.length > 0) {
|
|
575
|
-
throw new Error(`Invalid ${path}.fields.${field}.default: ${formatPluginConfigIssue(issues[0])}`);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
fields[field] = definition;
|
|
579
|
-
}
|
|
580
|
-
let presentation;
|
|
581
|
-
if (input.presentation !== undefined) {
|
|
582
|
-
const presentationInput = asObject(input.presentation, `${path}.presentation`);
|
|
583
|
-
assertAllowedKeys(presentationInput, `${path}.presentation`, ['groups']);
|
|
584
|
-
if (presentationInput.groups !== undefined && !Array.isArray(presentationInput.groups)) {
|
|
585
|
-
throw new Error(`Invalid ${path}.presentation.groups: expected an array.`);
|
|
586
|
-
}
|
|
587
|
-
const groupIds = new Set();
|
|
588
|
-
const groupedFields = new Set();
|
|
589
|
-
presentation = {
|
|
590
|
-
groups: presentationInput.groups?.map((rawGroup, index) => {
|
|
591
|
-
const groupPath = `${path}.presentation.groups[${index}]`;
|
|
592
|
-
const group = asObject(rawGroup, groupPath);
|
|
593
|
-
assertAllowedKeys(group, groupPath, ['id', 'title', 'description', 'fields', 'advanced']);
|
|
594
|
-
const id = nonEmptyString(group.id, `${groupPath}.id`);
|
|
595
|
-
if (groupIds.has(id)) {
|
|
596
|
-
throw new Error(`Invalid ${groupPath}.id: duplicate group id "${id}".`);
|
|
597
|
-
}
|
|
598
|
-
groupIds.add(id);
|
|
599
|
-
if (!Array.isArray(group.fields)) {
|
|
600
|
-
throw new Error(`Invalid ${groupPath}.fields: expected an array.`);
|
|
601
|
-
}
|
|
602
|
-
const groupFields = group.fields.map((entry, fieldIndex) => {
|
|
603
|
-
const field = nonEmptyString(entry, `${groupPath}.fields[${fieldIndex}]`);
|
|
604
|
-
if (!Object.hasOwn(fields, field)) {
|
|
605
|
-
throw new Error(`Invalid ${groupPath}.fields[${fieldIndex}]: unknown config key "${field}".`);
|
|
606
|
-
}
|
|
607
|
-
if (groupedFields.has(field)) {
|
|
608
|
-
throw new Error(`Invalid ${groupPath}.fields[${fieldIndex}]: config key "${field}" is assigned to more than one group.`);
|
|
609
|
-
}
|
|
610
|
-
groupedFields.add(field);
|
|
611
|
-
return field;
|
|
612
|
-
});
|
|
613
|
-
return {
|
|
614
|
-
id,
|
|
615
|
-
title: nonEmptyString(group.title, `${groupPath}.title`),
|
|
616
|
-
description: optionalText(group.description, `${groupPath}.description`),
|
|
617
|
-
fields: groupFields,
|
|
618
|
-
advanced: optionalBoolean(group.advanced, `${groupPath}.advanced`),
|
|
619
|
-
};
|
|
620
|
-
}),
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
return { fields, presentation };
|
|
624
|
-
}
|
|
625
|
-
const CONFIG_DIR = 'plugin-config';
|
|
626
|
-
const CONFIG_FILE = 'config.json';
|
|
627
|
-
const SOURCE_IDENTITY_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
628
5
|
function isRecord(value) {
|
|
629
6
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
630
7
|
}
|
|
631
8
|
function fail(message) {
|
|
632
9
|
throw new Error(message);
|
|
633
10
|
}
|
|
634
|
-
function
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
catch {
|
|
640
|
-
fail(`Unable to read ${label}.`);
|
|
641
|
-
}
|
|
11
|
+
function readContext() {
|
|
12
|
+
const contextPath = process.env.AW_PLUGIN_CONTEXT;
|
|
13
|
+
if (!contextPath)
|
|
14
|
+
fail('AW_PLUGIN_CONTEXT is required.');
|
|
15
|
+
let context;
|
|
642
16
|
try {
|
|
643
|
-
|
|
17
|
+
context = JSON.parse(fs.readFileSync(contextPath, 'utf8'));
|
|
644
18
|
}
|
|
645
19
|
catch {
|
|
646
|
-
fail(
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
||
|
|
654
|
-
||
|
|
655
|
-
|
|
656
|
-
|| value.includes('\\')
|
|
657
|
-
|| path.posix.isAbsolute(value)
|
|
658
|
-
|| path.win32.isAbsolute(value)
|
|
659
|
-
|| /^[A-Za-z][A-Za-z0-9+.-]*:/.test(value))
|
|
660
|
-
fail('Invalid plugin config segment.');
|
|
661
|
-
return value;
|
|
662
|
-
}
|
|
663
|
-
function normalizeSchema(rawSchema) {
|
|
664
|
-
try {
|
|
665
|
-
return normalizePluginConfigDefinition({ fields: rawSchema }).fields;
|
|
666
|
-
}
|
|
667
|
-
catch (error) {
|
|
668
|
-
fail(error instanceof Error ? error.message : 'Invalid AW config schema.');
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
function validateManifest(rawManifest) {
|
|
672
|
-
if (!isRecord(rawManifest))
|
|
673
|
-
fail('Bundle manifest is not a JSON object.');
|
|
674
|
-
const pluginName = safeSegment(rawManifest.name);
|
|
675
|
-
if (!isRecord(rawManifest.config) || !isRecord(rawManifest.config.fields)) {
|
|
676
|
-
fail(`Plugin "${pluginName}" does not declare an AW config schema.`);
|
|
677
|
-
}
|
|
678
|
-
return { pluginName, schema: normalizeSchema(rawManifest.config.fields) };
|
|
679
|
-
}
|
|
680
|
-
function locateBundleManifest() {
|
|
681
|
-
let currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
682
|
-
while (true) {
|
|
683
|
-
const manifestPath = path.join(currentDir, '.agents-plugin', 'plugin.json');
|
|
684
|
-
if (fs.existsSync(manifestPath))
|
|
685
|
-
return { bundleRoot: currentDir, manifestPath };
|
|
686
|
-
const parentDir = path.dirname(currentDir);
|
|
687
|
-
if (parentDir === currentDir)
|
|
688
|
-
fail('Unable to locate bundle .agents-plugin/plugin.json.');
|
|
689
|
-
currentDir = parentDir;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
function resolveProjectRoot(cwd) {
|
|
693
|
-
let currentDir = path.resolve(cwd);
|
|
694
|
-
while (true) {
|
|
695
|
-
if (fs.existsSync(path.join(currentDir, '.git')) || fs.existsSync(path.join(currentDir, 'pnpm-workspace.yaml'))) {
|
|
696
|
-
return currentDir;
|
|
697
|
-
}
|
|
698
|
-
const parentDir = path.dirname(currentDir);
|
|
699
|
-
if (parentDir === currentDir)
|
|
700
|
-
return path.resolve(cwd);
|
|
701
|
-
currentDir = parentDir;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
function resolveAwHome() {
|
|
705
|
-
return process.env.AW_HOME || path.join(os.homedir(), '.aw');
|
|
706
|
-
}
|
|
707
|
-
function resolveStorePath(sourceId, pluginName, scope, cwd) {
|
|
708
|
-
const root = scope === 'user'
|
|
709
|
-
? resolveAwHome()
|
|
710
|
-
: path.join(resolveProjectRoot(cwd), '.agent-workflows');
|
|
711
|
-
return path.join(root, CONFIG_DIR, sourceId, pluginName, CONFIG_FILE);
|
|
712
|
-
}
|
|
713
|
-
function supportedValue(value) {
|
|
714
|
-
if (typeof value === 'string' || typeof value === 'boolean')
|
|
715
|
-
return true;
|
|
716
|
-
if (typeof value === 'number')
|
|
717
|
-
return Number.isFinite(value);
|
|
718
|
-
return isRecord(value) && Object.values(value).every((child) => typeof child === 'string'
|
|
719
|
-
|| typeof child === 'boolean'
|
|
720
|
-
|| (typeof child === 'number' && Number.isFinite(child)));
|
|
721
|
-
}
|
|
722
|
-
function validateValues(values, schema) {
|
|
723
|
-
if (!isRecord(values))
|
|
724
|
-
fail('invalidValues: Store values must be an object.');
|
|
725
|
-
for (const [key, value] of Object.entries(values)) {
|
|
726
|
-
if (isDangerousConfigKey(key)) {
|
|
727
|
-
fail(`dangerousKey: Config "${key}" is not allowed.`);
|
|
728
|
-
}
|
|
729
|
-
if (!supportedValue(value))
|
|
730
|
-
fail(`invalidValues: Config "${key}" has an unsupported value.`);
|
|
731
|
-
const field = Object.hasOwn(schema, key) ? schema[key] : undefined;
|
|
732
|
-
if (!field)
|
|
733
|
-
continue;
|
|
734
|
-
const issues = validatePluginConfigValue(field, value, key);
|
|
735
|
-
if (issues.length > 0)
|
|
736
|
-
fail(`invalidValues: ${formatPluginConfigIssue(issues[0])}`);
|
|
737
|
-
}
|
|
738
|
-
return values;
|
|
739
|
-
}
|
|
740
|
-
function missingStore(filePath) {
|
|
741
|
-
return { path: filePath, state: 'missing', exists: false, values: {} };
|
|
742
|
-
}
|
|
743
|
-
function readStore(filePath, identity, schema) {
|
|
744
|
-
if (!fs.existsSync(filePath))
|
|
745
|
-
return missingStore(filePath);
|
|
746
|
-
const envelope = readJsonFile(filePath, 'plugin config store');
|
|
747
|
-
if (!isRecord(envelope) || envelope.schemaVersion !== 3) {
|
|
748
|
-
fail('schemaVersionUnsupported: Only plugin config store schemaVersion 3 is supported.');
|
|
20
|
+
fail('Unable to read AW_PLUGIN_CONTEXT as JSON.');
|
|
21
|
+
}
|
|
22
|
+
if (!isRecord(context)
|
|
23
|
+
|| !isRecord(context.plugin)
|
|
24
|
+
|| typeof context.plugin.name !== 'string'
|
|
25
|
+
|| context.plugin.name.trim() === ''
|
|
26
|
+
|| !isRecord(context.config)
|
|
27
|
+
|| !isRecord(context.config.schema)
|
|
28
|
+
|| !isRecord(context.config.values)) {
|
|
29
|
+
fail('AW_PLUGIN_CONTEXT does not contain config data.');
|
|
749
30
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
31
|
+
return context;
|
|
32
|
+
}
|
|
33
|
+
function redactValues(schema, values) {
|
|
34
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [
|
|
35
|
+
key,
|
|
36
|
+
isRecord(schema[key]) && schema[key].sensitive === true ? '[redacted]' : value,
|
|
37
|
+
]));
|
|
38
|
+
}
|
|
39
|
+
function resolveConfig(scope) {
|
|
40
|
+
if (scope !== 'effective') {
|
|
41
|
+
fail('AW_PLUGIN_CONTEXT only supports effective config reads; use aw config for user or project scope.');
|
|
42
|
+
}
|
|
43
|
+
const context = readContext();
|
|
44
|
+
const schema = context.config.schema;
|
|
45
|
+
const values = Object.fromEntries(Object.entries(context.config.values).filter(([key]) => Object.hasOwn(schema, key)));
|
|
46
|
+
const sources = isRecord(context.config.sources)
|
|
47
|
+
? Object.fromEntries(Object.entries(context.config.sources).filter(([key]) => Object.hasOwn(schema, key)))
|
|
48
|
+
: {};
|
|
49
|
+
const identity = {
|
|
50
|
+
...(typeof context.plugin.sourceId === 'string' ? { sourceId: context.plugin.sourceId } : {}),
|
|
51
|
+
...(typeof context.plugin.sourceIdentity === 'string'
|
|
52
|
+
? { sourceIdentity: context.plugin.sourceIdentity }
|
|
53
|
+
: {}),
|
|
54
|
+
...(typeof context.plugin.logicalIdentity === 'string'
|
|
55
|
+
? { logicalIdentity: context.plugin.logicalIdentity }
|
|
56
|
+
: {}),
|
|
57
|
+
...(typeof context.plugin.pluginId === 'string' ? { pluginId: context.plugin.pluginId } : {}),
|
|
759
58
|
};
|
|
760
|
-
}
|
|
761
|
-
function createState(schema) {
|
|
762
59
|
return {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
sources: Object.fromEntries(Object.keys(schema).map((key) => [key, 'missing'])),
|
|
766
|
-
ignoredUnknownKeys: [],
|
|
767
|
-
issues: [],
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
function applyLayer(state, schema, entries, source) {
|
|
771
|
-
validateValues(entries, schema);
|
|
772
|
-
for (const [key, field] of Object.entries(schema)) {
|
|
773
|
-
if (!Object.hasOwn(entries, key))
|
|
774
|
-
continue;
|
|
775
|
-
const value = entries[key];
|
|
776
|
-
state.values[key] = value;
|
|
777
|
-
state.redactedValues[key] = redactPluginConfigValue(field, value);
|
|
778
|
-
state.sources[key] = source;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
function collectUnknownKeys(state, schema, store, scope) {
|
|
782
|
-
for (const key of Object.keys(store.values)) {
|
|
783
|
-
if (!Object.hasOwn(schema, key))
|
|
784
|
-
state.ignoredUnknownKeys.push({ scope, key });
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
function addRequiredIssues(state, schema) {
|
|
788
|
-
for (const [key, field] of Object.entries(schema)) {
|
|
789
|
-
if (field.required === true && !Object.hasOwn(state.values, key)) {
|
|
790
|
-
state.issues.push({
|
|
791
|
-
type: 'missing-required',
|
|
792
|
-
key,
|
|
793
|
-
source: state.sources[key],
|
|
794
|
-
message: `Config "${key}" is required.`,
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
function resolveFromStores({ identity, schema, scope, stores }) {
|
|
800
|
-
const state = createState(schema);
|
|
801
|
-
if (scope === 'effective') {
|
|
802
|
-
for (const [key, field] of Object.entries(schema)) {
|
|
803
|
-
if (field.default !== undefined)
|
|
804
|
-
applyLayer(state, { [key]: field }, { [key]: field.default }, 'default');
|
|
805
|
-
}
|
|
806
|
-
for (const [storeKey, source] of [['user', 'user'], ['project', 'project']]) {
|
|
807
|
-
collectUnknownKeys(state, schema, stores[storeKey], source);
|
|
808
|
-
applyLayer(state, schema, stores[storeKey].values, source);
|
|
809
|
-
}
|
|
810
|
-
addRequiredIssues(state, schema);
|
|
811
|
-
}
|
|
812
|
-
else {
|
|
813
|
-
collectUnknownKeys(state, schema, stores[scope], scope);
|
|
814
|
-
applyLayer(state, schema, stores[scope].values, scope);
|
|
815
|
-
}
|
|
816
|
-
return {
|
|
817
|
-
pluginName: identity.pluginName,
|
|
818
|
-
sourceId: identity.sourceId,
|
|
819
|
-
sourceIdentity: identity.sourceIdentity,
|
|
820
|
-
pluginId: `${identity.sourceId}/${identity.pluginName}`,
|
|
60
|
+
pluginName: context.plugin.name,
|
|
61
|
+
...identity,
|
|
821
62
|
scope,
|
|
822
|
-
values
|
|
823
|
-
redactedValues:
|
|
824
|
-
sources
|
|
63
|
+
values,
|
|
64
|
+
redactedValues: redactValues(schema, values),
|
|
65
|
+
sources,
|
|
825
66
|
schema,
|
|
826
|
-
diagnostics: {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
const sourceId = context.plugin?.sourceId;
|
|
832
|
-
const sourceIdentity = context.plugin?.sourceIdentity;
|
|
833
|
-
if ((sourceId === undefined) !== (sourceIdentity === undefined)) {
|
|
834
|
-
fail('AW_PLUGIN_CONTEXT contains an incomplete source identity.');
|
|
835
|
-
}
|
|
836
|
-
if (sourceId === undefined)
|
|
837
|
-
return null;
|
|
838
|
-
if (typeof sourceIdentity !== 'string' || !SOURCE_IDENTITY_PATTERN.test(sourceIdentity)) {
|
|
839
|
-
fail('AW_PLUGIN_CONTEXT contains an invalid source identity.');
|
|
840
|
-
}
|
|
841
|
-
return { pluginName, sourceId: safeSegment(sourceId), sourceIdentity };
|
|
842
|
-
}
|
|
843
|
-
function resolveFromContext(scope) {
|
|
844
|
-
const contextPath = process.env.AW_PLUGIN_CONTEXT;
|
|
845
|
-
if (!contextPath)
|
|
846
|
-
return null;
|
|
847
|
-
const context = readJsonFile(contextPath, 'AW_PLUGIN_CONTEXT');
|
|
848
|
-
if (!isRecord(context) || !isRecord(context.config))
|
|
849
|
-
fail('AW_PLUGIN_CONTEXT does not contain config data.');
|
|
850
|
-
const schema = normalizeSchema(context.config.schema);
|
|
851
|
-
const pluginName = safeSegment(context.plugin?.name || context.pluginName || context.config.pluginName || 'plugin');
|
|
852
|
-
const identity = contextIdentity(context, pluginName);
|
|
853
|
-
if (scope === 'effective') {
|
|
854
|
-
const state = createState(schema);
|
|
855
|
-
const values = validateValues(context.config.values, schema);
|
|
856
|
-
applyLayer(state, schema, values, 'missing');
|
|
857
|
-
for (const [key, source] of Object.entries(isRecord(context.config.sources) ? context.config.sources : {})) {
|
|
858
|
-
if (Object.hasOwn(schema, key)
|
|
859
|
-
&& ['default', 'user', 'project', 'missing'].includes(source))
|
|
860
|
-
state.sources[key] = source;
|
|
861
|
-
}
|
|
862
|
-
const contextStorePaths = isRecord(context.paths?.configStores)
|
|
863
|
-
? context.paths.configStores
|
|
864
|
-
: {};
|
|
865
|
-
return {
|
|
866
|
-
pluginName,
|
|
867
|
-
...(identity ? {
|
|
868
|
-
sourceId: identity.sourceId,
|
|
869
|
-
sourceIdentity: identity.sourceIdentity,
|
|
870
|
-
pluginId: `${identity.sourceId}/${pluginName}`,
|
|
871
|
-
} : {}),
|
|
872
|
-
scope,
|
|
873
|
-
values: state.values,
|
|
874
|
-
redactedValues: state.redactedValues,
|
|
875
|
-
sources: state.sources,
|
|
876
|
-
schema,
|
|
877
|
-
diagnostics: {
|
|
878
|
-
issues: Array.isArray(context.config.issues) ? context.config.issues : [],
|
|
879
|
-
ignoredUnknownKeys: Array.isArray(context.config.ignoredUnknownKeys)
|
|
880
|
-
? context.config.ignoredUnknownKeys : [],
|
|
881
|
-
},
|
|
882
|
-
paths: {
|
|
883
|
-
configStores: {
|
|
884
|
-
...(typeof contextStorePaths.user === 'string' ? { user: contextStorePaths.user } : {}),
|
|
885
|
-
...(typeof contextStorePaths.project === 'string' ? { project: contextStorePaths.project } : {}),
|
|
886
|
-
},
|
|
887
|
-
},
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
if (!identity)
|
|
891
|
-
fail('AW_PLUGIN_CONTEXT must provide a source identity for scoped config reads.');
|
|
892
|
-
const storePath = context.paths?.configStores?.[scope];
|
|
893
|
-
if (typeof storePath !== 'string' || storePath.trim() === '') {
|
|
894
|
-
fail(`AW_PLUGIN_CONTEXT does not provide a ${scope} config store path.`);
|
|
895
|
-
}
|
|
896
|
-
const store = readStore(storePath, identity, schema);
|
|
897
|
-
return resolveFromStores({
|
|
898
|
-
identity,
|
|
899
|
-
schema,
|
|
900
|
-
scope,
|
|
901
|
-
stores: {
|
|
902
|
-
user: scope === 'user' ? store : missingStore(''),
|
|
903
|
-
project: scope === 'project' ? store : missingStore(''),
|
|
67
|
+
diagnostics: {
|
|
68
|
+
issues: Array.isArray(context.config.issues) ? context.config.issues : [],
|
|
69
|
+
ignoredUnknownKeys: Array.isArray(context.config.ignoredUnknownKeys)
|
|
70
|
+
? context.config.ignoredUnknownKeys
|
|
71
|
+
: [],
|
|
904
72
|
},
|
|
905
|
-
|
|
906
|
-
}
|
|
907
|
-
function resolveFallback(scope) {
|
|
908
|
-
const { bundleRoot, manifestPath } = locateBundleManifest();
|
|
909
|
-
const { pluginName, schema } = validateManifest(readJsonFile(manifestPath, 'bundle manifest'));
|
|
910
|
-
const pluginsRoot = path.dirname(bundleRoot);
|
|
911
|
-
const marketplaceRoot = path.dirname(pluginsRoot);
|
|
912
|
-
if (path.basename(pluginsRoot) !== 'plugins' || path.basename(bundleRoot) !== pluginName) {
|
|
913
|
-
fail('Bundle manifest is not inside a namespaced local marketplace.');
|
|
914
|
-
}
|
|
915
|
-
const marketplace = readJsonFile(path.join(marketplaceRoot, 'config.json'), 'marketplace config');
|
|
916
|
-
const sourceId = safeSegment(marketplace.sourceId);
|
|
917
|
-
const sourceIdentity = marketplace.sourceIdentity;
|
|
918
|
-
if (marketplace.schemaVersion !== 2
|
|
919
|
-
|| sourceId !== path.basename(marketplaceRoot)
|
|
920
|
-
|| typeof sourceIdentity !== 'string'
|
|
921
|
-
|| !SOURCE_IDENTITY_PATTERN.test(sourceIdentity)
|
|
922
|
-
|| !['aw', `aw-${sourceId}`].includes(marketplace.marketplaceName))
|
|
923
|
-
fail('Marketplace config does not contain a canonical schema 2 source identity.');
|
|
924
|
-
const identity = { sourceId, sourceIdentity, pluginName };
|
|
925
|
-
const cwd = process.cwd();
|
|
926
|
-
const projectRoot = resolveProjectRoot(cwd);
|
|
927
|
-
for (const legacyPath of [
|
|
928
|
-
path.join(resolveAwHome(), CONFIG_DIR, pluginName, CONFIG_FILE),
|
|
929
|
-
path.join(projectRoot, '.agent-workflows', CONFIG_DIR, pluginName, CONFIG_FILE),
|
|
930
|
-
]) {
|
|
931
|
-
if (fs.existsSync(legacyPath))
|
|
932
|
-
fail('Legacy non-namespaced plugin config is not supported.');
|
|
933
|
-
}
|
|
934
|
-
const stores = {
|
|
935
|
-
user: scope === 'project'
|
|
936
|
-
? missingStore('')
|
|
937
|
-
: readStore(resolveStorePath(sourceId, pluginName, 'user', cwd), identity, schema),
|
|
938
|
-
project: scope === 'user'
|
|
939
|
-
? missingStore('')
|
|
940
|
-
: readStore(resolveStorePath(sourceId, pluginName, 'project', cwd), identity, schema),
|
|
73
|
+
paths: { configStores: {} },
|
|
941
74
|
};
|
|
942
|
-
return resolveFromStores({ identity, schema, scope, stores });
|
|
943
75
|
}
|
|
944
76
|
function printUsage() {
|
|
945
77
|
process.stdout.write(`Usage:
|
|
946
|
-
node aw-config.mjs get <key> [--scope effective
|
|
947
|
-
node aw-config.mjs json [--scope effective
|
|
78
|
+
node aw-config.mjs get <key> [--scope effective]
|
|
79
|
+
node aw-config.mjs json [--scope effective]
|
|
948
80
|
|
|
949
81
|
Options:
|
|
950
|
-
--scope <scope> Read effective
|
|
82
|
+
--scope <scope> Read effective config from AW_PLUGIN_CONTEXT. Default: effective.
|
|
951
83
|
-h, --help Show this help message.
|
|
952
84
|
`);
|
|
953
85
|
}
|
|
@@ -974,8 +106,6 @@ function parseArgs(argv) {
|
|
|
974
106
|
positional.push(arg);
|
|
975
107
|
}
|
|
976
108
|
}
|
|
977
|
-
if (!['effective', 'user', 'project'].includes(scope))
|
|
978
|
-
fail('--scope must be effective, user, or project.');
|
|
979
109
|
if (command !== 'get' && command !== 'json')
|
|
980
110
|
fail('Usage: node aw-config.mjs get <key> | json');
|
|
981
111
|
if (command === 'get' && positional.length !== 1)
|
|
@@ -990,7 +120,7 @@ function main() {
|
|
|
990
120
|
printUsage();
|
|
991
121
|
return;
|
|
992
122
|
}
|
|
993
|
-
const result =
|
|
123
|
+
const result = resolveConfig(args.scope);
|
|
994
124
|
if (args.command === 'get') {
|
|
995
125
|
if (!Object.hasOwn(result.schema, args.key))
|
|
996
126
|
fail(`Unknown config key "${args.key}".`);
|