@gxp-dev/tools 2.0.71 → 2.0.72
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 +108 -81
- package/bin/lib/cli.js +18 -0
- package/bin/lib/commands/index.js +2 -0
- package/bin/lib/commands/init.js +23 -0
- package/bin/lib/commands/lint.js +77 -0
- package/bin/lib/constants.js +12 -0
- package/bin/lib/lint/formatter.js +91 -0
- package/bin/lib/lint/index.js +284 -0
- package/bin/lib/lint/schemas/app-manifest.schema.json +124 -0
- package/bin/lib/lint/schemas/card.schema.json +165 -0
- package/bin/lib/lint/schemas/common.schema.json +62 -0
- package/bin/lib/lint/schemas/configuration.schema.json +19 -0
- package/bin/lib/lint/schemas/field.schema.json +230 -0
- package/mcp/gxp-api-server.js +56 -127
- package/mcp/lib/api-tools.js +456 -0
- package/mcp/lib/config-ops.js +234 -0
- package/mcp/lib/config-tools.js +549 -0
- package/mcp/lib/docs-tools.js +142 -0
- package/mcp/lib/docs.js +263 -0
- package/mcp/lib/specs.js +135 -0
- package/mcp/lib/test-tools.js +358 -0
- package/package.json +3 -1
- package/runtime/vite.config.js +5 -3
- package/template/.prettierrc +10 -0
- package/template/README.md +205 -240
- package/template/app-instructions.md +91 -0
- package/template/eslint.config.js +32 -0
- package/template/githooks/pre-commit +37 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GxP JSON Config Linter
|
|
3
|
+
*
|
|
4
|
+
* Validates `configuration.json` and `app-manifest.json` against JSON Schemas
|
|
5
|
+
* derived from the GxP templating system documentation. Schemas live in
|
|
6
|
+
* ./schemas/ and are composed via $ref.
|
|
7
|
+
*
|
|
8
|
+
* Public API:
|
|
9
|
+
* - detectSchema(filePath): pick the correct schema for a file, or null.
|
|
10
|
+
* - lintFile(filePath): return { file, ok, errors[] }.
|
|
11
|
+
* - lintFiles(files): return aggregated results.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require("fs")
|
|
15
|
+
const path = require("path")
|
|
16
|
+
const Ajv = require("ajv/dist/2020")
|
|
17
|
+
const addFormats = require("ajv-formats")
|
|
18
|
+
|
|
19
|
+
const SCHEMA_DIR = path.join(__dirname, "schemas")
|
|
20
|
+
|
|
21
|
+
const SCHEMA_FILES = [
|
|
22
|
+
"common.schema.json",
|
|
23
|
+
"field.schema.json",
|
|
24
|
+
"card.schema.json",
|
|
25
|
+
"configuration.schema.json",
|
|
26
|
+
"app-manifest.schema.json",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
let cachedAjv = null
|
|
30
|
+
|
|
31
|
+
function getAjv() {
|
|
32
|
+
if (cachedAjv) {
|
|
33
|
+
return cachedAjv
|
|
34
|
+
}
|
|
35
|
+
const ajv = new Ajv({
|
|
36
|
+
allErrors: true,
|
|
37
|
+
strict: false,
|
|
38
|
+
allowUnionTypes: true,
|
|
39
|
+
verbose: true,
|
|
40
|
+
})
|
|
41
|
+
addFormats(ajv)
|
|
42
|
+
|
|
43
|
+
for (const fileName of SCHEMA_FILES) {
|
|
44
|
+
const schemaPath = path.join(SCHEMA_DIR, fileName)
|
|
45
|
+
const schema = JSON.parse(fs.readFileSync(schemaPath, "utf-8"))
|
|
46
|
+
// Reference schemas by their bare filename (how siblings $ref each other).
|
|
47
|
+
ajv.addSchema(schema, fileName)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
cachedAjv = ajv
|
|
51
|
+
return ajv
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Given a file path, decide which root schema should validate it.
|
|
56
|
+
* Matches exact filenames `configuration.json` / `app-manifest.json` and also
|
|
57
|
+
* suffixed variants like `broken-configuration.json` so users can keep multiple
|
|
58
|
+
* samples around during development. Returns the schema filename or null.
|
|
59
|
+
*/
|
|
60
|
+
function detectSchema(filePath) {
|
|
61
|
+
const base = path.basename(filePath)
|
|
62
|
+
if (base === "configuration.json" || /configuration\.json$/i.test(base)) {
|
|
63
|
+
return "configuration.schema.json"
|
|
64
|
+
}
|
|
65
|
+
if (base === "app-manifest.json" || /app-manifest\.json$/i.test(base)) {
|
|
66
|
+
return "app-manifest.schema.json"
|
|
67
|
+
}
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Walk a JSON document by dotted/bracketed pointer to locate a value.
|
|
73
|
+
* Falls back to the document start if the path is unresolvable — good enough
|
|
74
|
+
* for error location heuristics; the AJV path is always printed too.
|
|
75
|
+
*/
|
|
76
|
+
function locateInSource(source, instancePath) {
|
|
77
|
+
if (!instancePath || instancePath === "") {
|
|
78
|
+
return { line: 1, column: 1 }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Split AJV 2020-12 JSON pointer: "/additionalTabs/0/cards/1/fieldsList/0/type"
|
|
82
|
+
const segments = instancePath.split("/").filter(Boolean)
|
|
83
|
+
let cursor = 0
|
|
84
|
+
let line = 1
|
|
85
|
+
let column = 1
|
|
86
|
+
|
|
87
|
+
// Simple forward scanner: find each segment's appearance after current cursor.
|
|
88
|
+
// Good enough for error pinpointing; not a full JSON parser.
|
|
89
|
+
for (const rawSegment of segments) {
|
|
90
|
+
const segment = decodeURIComponent(
|
|
91
|
+
rawSegment.replace(/~1/g, "/").replace(/~0/g, "~"),
|
|
92
|
+
)
|
|
93
|
+
let needle
|
|
94
|
+
if (/^\d+$/.test(segment)) {
|
|
95
|
+
// Array index — scan forward to the Nth top-level "," or "[" after cursor.
|
|
96
|
+
// Cheap approximation: skip.
|
|
97
|
+
needle = null
|
|
98
|
+
} else {
|
|
99
|
+
needle = `"${segment}"`
|
|
100
|
+
}
|
|
101
|
+
if (needle) {
|
|
102
|
+
const next = source.indexOf(needle, cursor)
|
|
103
|
+
if (next >= 0) {
|
|
104
|
+
cursor = next + needle.length
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (let i = 0; i < cursor && i < source.length; i++) {
|
|
110
|
+
if (source[i] === "\n") {
|
|
111
|
+
line++
|
|
112
|
+
column = 1
|
|
113
|
+
} else {
|
|
114
|
+
column++
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { line, column }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Lint a single file. Returns a result object; never throws for validation or
|
|
122
|
+
* JSON-parse errors (those become errors in the result).
|
|
123
|
+
*/
|
|
124
|
+
function lintFile(filePath) {
|
|
125
|
+
const absPath = path.resolve(filePath)
|
|
126
|
+
const result = {
|
|
127
|
+
file: absPath,
|
|
128
|
+
ok: true,
|
|
129
|
+
skipped: false,
|
|
130
|
+
reason: null,
|
|
131
|
+
errors: [],
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!fs.existsSync(absPath)) {
|
|
135
|
+
result.ok = false
|
|
136
|
+
result.errors.push({
|
|
137
|
+
code: "file-not-found",
|
|
138
|
+
message: `File not found: ${absPath}`,
|
|
139
|
+
line: 1,
|
|
140
|
+
column: 1,
|
|
141
|
+
instancePath: "",
|
|
142
|
+
})
|
|
143
|
+
return result
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const schemaKey = detectSchema(absPath)
|
|
147
|
+
if (!schemaKey) {
|
|
148
|
+
result.skipped = true
|
|
149
|
+
result.reason = "no-schema-for-filename"
|
|
150
|
+
return result
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const source = fs.readFileSync(absPath, "utf-8")
|
|
154
|
+
let data
|
|
155
|
+
try {
|
|
156
|
+
data = JSON.parse(source)
|
|
157
|
+
} catch (e) {
|
|
158
|
+
result.ok = false
|
|
159
|
+
result.errors.push({
|
|
160
|
+
code: "json-parse-error",
|
|
161
|
+
message: `Invalid JSON: ${e.message}`,
|
|
162
|
+
line: 1,
|
|
163
|
+
column: 1,
|
|
164
|
+
instancePath: "",
|
|
165
|
+
})
|
|
166
|
+
return result
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const ajv = getAjv()
|
|
170
|
+
const validate = ajv.getSchema(schemaKey)
|
|
171
|
+
const valid = validate(data)
|
|
172
|
+
if (!valid) {
|
|
173
|
+
result.ok = false
|
|
174
|
+
const seen = new Set()
|
|
175
|
+
for (const err of validate.errors || []) {
|
|
176
|
+
// Drop AJV's meta-errors that just restate "a nested if/then failed" —
|
|
177
|
+
// the real cause is the nested error itself, which we also have.
|
|
178
|
+
if (err.keyword === "if" || err.keyword === "allOf") {
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
// Collapse duplicates that AJV emits when an inner schema is reached
|
|
182
|
+
// via multiple schemaPaths (e.g. through if/then and directly).
|
|
183
|
+
const dedupeKey = `${err.keyword}|${err.instancePath}|${err.params?.missingProperty || ""}|${err.params?.additionalProperty || ""}|${err.params?.allowedValue || ""}`
|
|
184
|
+
if (seen.has(dedupeKey)) {
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
seen.add(dedupeKey)
|
|
188
|
+
|
|
189
|
+
const { line, column } = locateInSource(source, err.instancePath)
|
|
190
|
+
result.errors.push({
|
|
191
|
+
code: err.keyword,
|
|
192
|
+
message: formatAjvMessage(err),
|
|
193
|
+
line,
|
|
194
|
+
column,
|
|
195
|
+
instancePath: err.instancePath || "/",
|
|
196
|
+
schemaPath: err.schemaPath,
|
|
197
|
+
params: err.params,
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return result
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function formatAjvMessage(err) {
|
|
205
|
+
const at = err.instancePath || "(root)"
|
|
206
|
+
const core = err.message || "failed validation"
|
|
207
|
+
const hints = []
|
|
208
|
+
if (err.keyword === "enum" && err.params?.allowedValues) {
|
|
209
|
+
hints.push(`allowed: ${err.params.allowedValues.join(", ")}`)
|
|
210
|
+
}
|
|
211
|
+
if (err.keyword === "required" && err.params?.missingProperty) {
|
|
212
|
+
return `${at} missing required property "${err.params.missingProperty}"`
|
|
213
|
+
}
|
|
214
|
+
if (err.keyword === "type" && err.params?.type) {
|
|
215
|
+
return `${at} must be ${err.params.type}`
|
|
216
|
+
}
|
|
217
|
+
if (
|
|
218
|
+
err.keyword === "additionalProperties" &&
|
|
219
|
+
err.params?.additionalProperty
|
|
220
|
+
) {
|
|
221
|
+
return `${at} has unexpected property "${err.params.additionalProperty}"`
|
|
222
|
+
}
|
|
223
|
+
const tail = hints.length ? ` (${hints.join("; ")})` : ""
|
|
224
|
+
return `${at} ${core}${tail}`
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function lintFiles(files) {
|
|
228
|
+
const results = files.map((f) => lintFile(f))
|
|
229
|
+
const summary = {
|
|
230
|
+
totalFiles: results.length,
|
|
231
|
+
filesWithErrors: results.filter((r) => !r.ok).length,
|
|
232
|
+
skipped: results.filter((r) => r.skipped).length,
|
|
233
|
+
totalErrors: results.reduce((n, r) => n + r.errors.length, 0),
|
|
234
|
+
}
|
|
235
|
+
return { results, summary }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Validate an already-parsed JSON value. `pathHint` is only used to pick the
|
|
240
|
+
* right root schema (via detectSchema). Useful for MCP tools that want to
|
|
241
|
+
* validate a prospective edit before touching disk.
|
|
242
|
+
*/
|
|
243
|
+
function lintData(data, pathHint) {
|
|
244
|
+
const result = { ok: true, skipped: false, reason: null, errors: [] }
|
|
245
|
+
const schemaKey = detectSchema(pathHint)
|
|
246
|
+
if (!schemaKey) {
|
|
247
|
+
result.skipped = true
|
|
248
|
+
result.reason = "no-schema-for-filename"
|
|
249
|
+
return result
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const ajv = getAjv()
|
|
253
|
+
const validate = ajv.getSchema(schemaKey)
|
|
254
|
+
const valid = validate(data)
|
|
255
|
+
if (!valid) {
|
|
256
|
+
result.ok = false
|
|
257
|
+
const seen = new Set()
|
|
258
|
+
for (const err of validate.errors || []) {
|
|
259
|
+
if (err.keyword === "if" || err.keyword === "allOf") continue
|
|
260
|
+
const dedupeKey = `${err.keyword}|${err.instancePath}|${err.params?.missingProperty || ""}|${err.params?.additionalProperty || ""}`
|
|
261
|
+
if (seen.has(dedupeKey)) continue
|
|
262
|
+
seen.add(dedupeKey)
|
|
263
|
+
|
|
264
|
+
result.errors.push({
|
|
265
|
+
code: err.keyword,
|
|
266
|
+
message: formatAjvMessage(err),
|
|
267
|
+
line: 1,
|
|
268
|
+
column: 1,
|
|
269
|
+
instancePath: err.instancePath || "/",
|
|
270
|
+
schemaPath: err.schemaPath,
|
|
271
|
+
params: err.params,
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return result
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
module.exports = {
|
|
279
|
+
detectSchema,
|
|
280
|
+
lintFile,
|
|
281
|
+
lintFiles,
|
|
282
|
+
lintData,
|
|
283
|
+
SCHEMA_DIR,
|
|
284
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "https://gxp.dev/schemas/app-manifest.schema.json",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"title": "GxP App Manifest",
|
|
5
|
+
"description": "Plugin metadata + default datastore values. Read by the platform at install time and by the toolkit dev server.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["name", "version"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"name": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"minLength": 1,
|
|
12
|
+
"description": "Plugin display name."
|
|
13
|
+
},
|
|
14
|
+
"version": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"pattern": "^[0-9]+(\\.[0-9]+){0,2}([-+][A-Za-z0-9.-]+)?$",
|
|
17
|
+
"description": "Semver-like plugin version."
|
|
18
|
+
},
|
|
19
|
+
"description": { "type": "string" },
|
|
20
|
+
"manifest_version": {
|
|
21
|
+
"type": "integer",
|
|
22
|
+
"enum": [1, 2, 3]
|
|
23
|
+
},
|
|
24
|
+
"asset_dir": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Relative path to the asset directory, e.g. /src/public.",
|
|
27
|
+
"pattern": "^/?[A-Za-z0-9._-][A-Za-z0-9/._-]*$"
|
|
28
|
+
},
|
|
29
|
+
"configurationFile": { "type": "string" },
|
|
30
|
+
"appInstructionsFile": { "type": "string" },
|
|
31
|
+
"defaultStylingFile": { "type": "string" },
|
|
32
|
+
"appInstructions": { "type": "string" },
|
|
33
|
+
"defaultStyling": { "type": "string" },
|
|
34
|
+
"configuration": {
|
|
35
|
+
"type": ["object", "string"]
|
|
36
|
+
},
|
|
37
|
+
"settings": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"description": "Default key/value settings (pluginVars). Keys should match identifiers used in gxp-settings directives.",
|
|
40
|
+
"patternProperties": {
|
|
41
|
+
"^[A-Za-z_][A-Za-z0-9_]*$": {
|
|
42
|
+
"type": ["string", "number", "boolean", "null", "array", "object"]
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"additionalProperties": false
|
|
46
|
+
},
|
|
47
|
+
"strings": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"description": "i18n strings. Must contain a `default` locale map; other keys are locale codes.",
|
|
50
|
+
"properties": {
|
|
51
|
+
"default": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"patternProperties": {
|
|
54
|
+
"^[A-Za-z_][A-Za-z0-9_]*$": { "type": "string" }
|
|
55
|
+
},
|
|
56
|
+
"additionalProperties": false
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"patternProperties": {
|
|
60
|
+
"^[a-z]{2}(-[A-Z]{2})?$": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"additionalProperties": { "type": "string" }
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"required": ["default"],
|
|
66
|
+
"additionalProperties": true
|
|
67
|
+
},
|
|
68
|
+
"assets": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"description": "Default asset URLs/paths keyed by gxp-src identifier.",
|
|
71
|
+
"patternProperties": {
|
|
72
|
+
"^[A-Za-z_][A-Za-z0-9_]*$": {
|
|
73
|
+
"$ref": "common.schema.json#/$defs/urlOrPath"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"additionalProperties": false
|
|
77
|
+
},
|
|
78
|
+
"triggerState": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"description": "Default values for dynamic state updated by sockets/CLI.",
|
|
81
|
+
"patternProperties": {
|
|
82
|
+
"^[A-Za-z_][A-Za-z0-9_]*$": {
|
|
83
|
+
"type": ["string", "number", "boolean", "null"]
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"additionalProperties": false
|
|
87
|
+
},
|
|
88
|
+
"dependencies": {
|
|
89
|
+
"type": "array",
|
|
90
|
+
"items": {
|
|
91
|
+
"type": "object",
|
|
92
|
+
"required": ["identifier"],
|
|
93
|
+
"properties": {
|
|
94
|
+
"identifier": {
|
|
95
|
+
"$ref": "common.schema.json#/$defs/identifier"
|
|
96
|
+
},
|
|
97
|
+
"model": { "type": "string" },
|
|
98
|
+
"events": { "type": "object" }
|
|
99
|
+
},
|
|
100
|
+
"additionalProperties": true
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"permissions": {
|
|
104
|
+
"type": "array",
|
|
105
|
+
"items": {
|
|
106
|
+
"oneOf": [
|
|
107
|
+
{ "$ref": "common.schema.json#/$defs/identifier" },
|
|
108
|
+
{
|
|
109
|
+
"type": "object",
|
|
110
|
+
"required": ["identifier"],
|
|
111
|
+
"properties": {
|
|
112
|
+
"identifier": {
|
|
113
|
+
"$ref": "common.schema.json#/$defs/identifier"
|
|
114
|
+
},
|
|
115
|
+
"description": { "type": "string" }
|
|
116
|
+
},
|
|
117
|
+
"additionalProperties": true
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"additionalProperties": true
|
|
124
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "https://gxp.dev/schemas/card.schema.json",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"title": "GxP Form Card",
|
|
5
|
+
"description": "A card inside additionalTabs.cards or a nested card_list/tabs_list.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["type"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"type": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"enum": [
|
|
12
|
+
"fields_list",
|
|
13
|
+
"data_table",
|
|
14
|
+
"card_list",
|
|
15
|
+
"tabs_list",
|
|
16
|
+
"info_card",
|
|
17
|
+
"render_list",
|
|
18
|
+
"carousel",
|
|
19
|
+
"grid_view",
|
|
20
|
+
"component",
|
|
21
|
+
"data_display",
|
|
22
|
+
"button_group",
|
|
23
|
+
"form_builder"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"title": { "type": ["string", "null"] },
|
|
27
|
+
"tabId": { "type": ["string", "null"] },
|
|
28
|
+
"cols": { "$ref": "common.schema.json#/$defs/gridCols" },
|
|
29
|
+
"colsMd": { "$ref": "common.schema.json#/$defs/gridCols" },
|
|
30
|
+
"colsLg": { "$ref": "common.schema.json#/$defs/gridCols" },
|
|
31
|
+
"marginBottom": { "type": "integer", "minimum": 0 },
|
|
32
|
+
"showCard": { "type": "boolean" },
|
|
33
|
+
"border": { "type": "boolean" },
|
|
34
|
+
"listKey": { "type": ["string", "null"] },
|
|
35
|
+
"condition": { "type": ["string", "null"] },
|
|
36
|
+
"conditionParams": {
|
|
37
|
+
"$ref": "common.schema.json#/$defs/conditionParams"
|
|
38
|
+
},
|
|
39
|
+
"fieldsList": {
|
|
40
|
+
"type": "array",
|
|
41
|
+
"items": { "$ref": "field.schema.json" }
|
|
42
|
+
},
|
|
43
|
+
"cards": {
|
|
44
|
+
"type": "array",
|
|
45
|
+
"items": { "$ref": "card.schema.json" }
|
|
46
|
+
},
|
|
47
|
+
"tabsList": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"required": ["title"],
|
|
52
|
+
"properties": {
|
|
53
|
+
"title": { "type": "string" },
|
|
54
|
+
"tabId": { "type": ["string", "null"] },
|
|
55
|
+
"cards": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": { "$ref": "card.schema.json" }
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"additionalProperties": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"buttons": {
|
|
64
|
+
"type": "array",
|
|
65
|
+
"items": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"required": ["text"],
|
|
68
|
+
"properties": {
|
|
69
|
+
"text": { "type": "string" },
|
|
70
|
+
"type": { "type": "string" },
|
|
71
|
+
"icon": { "type": ["string", "null"] },
|
|
72
|
+
"href": { "type": ["string", "null"] },
|
|
73
|
+
"action": { "type": ["string", "null"] },
|
|
74
|
+
"condition": { "type": ["string", "null"] },
|
|
75
|
+
"conditionParams": {
|
|
76
|
+
"$ref": "common.schema.json#/$defs/conditionParams"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"additionalProperties": true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"component": { "type": ["string", "null"] },
|
|
83
|
+
"componentProps": { "type": "object" },
|
|
84
|
+
"headers": {
|
|
85
|
+
"type": "array",
|
|
86
|
+
"items": { "type": ["string", "object"] }
|
|
87
|
+
},
|
|
88
|
+
"rows": { "type": "array" },
|
|
89
|
+
"options": { "type": "object" },
|
|
90
|
+
"tableTitle": { "type": ["string", "null"] }
|
|
91
|
+
},
|
|
92
|
+
"allOf": [
|
|
93
|
+
{
|
|
94
|
+
"if": {
|
|
95
|
+
"properties": { "type": { "const": "fields_list" } },
|
|
96
|
+
"required": ["type"]
|
|
97
|
+
},
|
|
98
|
+
"then": {
|
|
99
|
+
"required": ["fieldsList"],
|
|
100
|
+
"properties": {
|
|
101
|
+
"fieldsList": {
|
|
102
|
+
"type": "array",
|
|
103
|
+
"items": { "$ref": "field.schema.json" }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"if": {
|
|
110
|
+
"properties": { "type": { "const": "card_list" } },
|
|
111
|
+
"required": ["type"]
|
|
112
|
+
},
|
|
113
|
+
"then": {
|
|
114
|
+
"required": ["cards"],
|
|
115
|
+
"properties": {
|
|
116
|
+
"cards": {
|
|
117
|
+
"type": "array",
|
|
118
|
+
"minItems": 1,
|
|
119
|
+
"items": { "$ref": "card.schema.json" }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"if": {
|
|
126
|
+
"properties": { "type": { "const": "tabs_list" } },
|
|
127
|
+
"required": ["type"]
|
|
128
|
+
},
|
|
129
|
+
"then": {
|
|
130
|
+
"required": ["tabsList"]
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"if": {
|
|
135
|
+
"properties": { "type": { "const": "button_group" } },
|
|
136
|
+
"required": ["type"]
|
|
137
|
+
},
|
|
138
|
+
"then": {
|
|
139
|
+
"required": ["buttons"]
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"if": {
|
|
144
|
+
"properties": { "type": { "const": "component" } },
|
|
145
|
+
"required": ["type"]
|
|
146
|
+
},
|
|
147
|
+
"then": {
|
|
148
|
+
"required": ["component"],
|
|
149
|
+
"properties": {
|
|
150
|
+
"component": { "type": "string", "minLength": 1 }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"if": {
|
|
156
|
+
"properties": { "type": { "const": "data_table" } },
|
|
157
|
+
"required": ["type"]
|
|
158
|
+
},
|
|
159
|
+
"then": {
|
|
160
|
+
"anyOf": [{ "required": ["headers"] }, { "required": ["rows"] }]
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
"additionalProperties": true
|
|
165
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "https://gxp.dev/schemas/common.schema.json",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"title": "GxP Common Definitions",
|
|
5
|
+
"description": "Shared sub-schemas referenced by card and field schemas.",
|
|
6
|
+
"$defs": {
|
|
7
|
+
"conditionParam": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"name": { "type": "string" },
|
|
11
|
+
"value": {
|
|
12
|
+
"type": ["string", "number", "boolean", "null"]
|
|
13
|
+
},
|
|
14
|
+
"logic": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"enum": ["==", "!=", ">", ">=", "<", "<=", "in", "not_in", "includes"]
|
|
17
|
+
},
|
|
18
|
+
"column": { "type": ["string", "null"] }
|
|
19
|
+
},
|
|
20
|
+
"required": ["name", "logic"],
|
|
21
|
+
"additionalProperties": true
|
|
22
|
+
},
|
|
23
|
+
"conditionParams": {
|
|
24
|
+
"type": "array",
|
|
25
|
+
"items": { "$ref": "#/$defs/conditionParam" }
|
|
26
|
+
},
|
|
27
|
+
"gridCols": {
|
|
28
|
+
"type": "integer",
|
|
29
|
+
"minimum": 1,
|
|
30
|
+
"maximum": 12
|
|
31
|
+
},
|
|
32
|
+
"colorHex": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"pattern": "^#(?:[0-9a-fA-F]{3}){1,2}$"
|
|
35
|
+
},
|
|
36
|
+
"urlOrPath": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "Absolute URL or site-relative path",
|
|
39
|
+
"pattern": "^(https?:)?(//)?/?[^\\s]*$"
|
|
40
|
+
},
|
|
41
|
+
"identifier": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_-]*$"
|
|
44
|
+
},
|
|
45
|
+
"option": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"properties": {
|
|
48
|
+
"label": { "type": "string" },
|
|
49
|
+
"value": {
|
|
50
|
+
"type": ["string", "number", "boolean", "null"]
|
|
51
|
+
},
|
|
52
|
+
"disabled": { "type": "boolean" }
|
|
53
|
+
},
|
|
54
|
+
"required": ["label", "value"],
|
|
55
|
+
"additionalProperties": true
|
|
56
|
+
},
|
|
57
|
+
"options": {
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": { "$ref": "#/$defs/option" }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "https://gxp.dev/schemas/configuration.schema.json",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"title": "GxP Plugin Configuration Form",
|
|
5
|
+
"description": "Defines the configuration form shown to admins in the GxP admin panel when they install or configure a plugin. Follows the GxP templating system (ShowPage > tabs > cards > fields).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["additionalTabs"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"additionalTabs": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"description": "Top-level array of tab definitions. Each item is a card (usually card_list or fields_list) rendered under the plugin's configuration panel.",
|
|
12
|
+
"items": { "$ref": "card.schema.json" }
|
|
13
|
+
},
|
|
14
|
+
"title": { "type": ["string", "null"] },
|
|
15
|
+
"description": { "type": ["string", "null"] },
|
|
16
|
+
"version": { "type": ["string", "integer", "null"] }
|
|
17
|
+
},
|
|
18
|
+
"additionalProperties": true
|
|
19
|
+
}
|