@subbly/plugin-lint 0.0.4 → 0.0.7
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/CHANGELOG.md +18 -0
- package/package.json +1 -1
- package/src/report.mjs +4 -2
- package/src/rules.mjs +29 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @subbly/plugin-lint
|
|
2
2
|
|
|
3
|
+
## 0.0.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- A config field may carry `description` beside `label`: a string of 1-300 characters after trimming, shown under the input in the install dialog. Before, the linter rejected it as an unknown key (rule `manifest/namespace-unknown-key`), though the builder accepts it. A blank or over-long description fails rule `field/description`.
|
|
8
|
+
|
|
9
|
+
## 0.0.6
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 937b7e6: A connector marker may carry `liveTools` beside `auth` and `tools`: the read tools that a live page may call. It is a non-empty array of unique tool names, 1-100 characters each, as `tools` is. When `tools` is there too, each live tool must also be in `tools` (rule `connector/live-tools`). A marker with none of `auth`, `tools` and `liveTools` is rejected, as the builder refresh rejects it.
|
|
14
|
+
|
|
15
|
+
## 0.0.5
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 1efe820: The plugin slug `custom` is an error, as the builder refresh reserves it for user-created connectors and credentials. No other rule changes.
|
|
20
|
+
|
|
3
21
|
## 0.0.4
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@subbly/plugin-lint",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The Subbly marketplace release gate, offline. Lints marketplace.json, plugin manifests, mcp.json, skills, agents and automations.",
|
package/src/report.mjs
CHANGED
|
@@ -41,6 +41,7 @@ export const RULES = {
|
|
|
41
41
|
'field/key-format': 'field keys must match /^[A-Z][A-Z0-9_]*$/',
|
|
42
42
|
'field/key-duplicate': 'a duplicate field key silently overrides the first',
|
|
43
43
|
'field/label': 'label is required, 1-100 characters',
|
|
44
|
+
'field/description': 'description is optional, 1-300 characters after trimming',
|
|
44
45
|
'field/type': 'type must be "text" or "select"',
|
|
45
46
|
'field/options': 'options belong to select fields and select fields require them',
|
|
46
47
|
'field/public-secret': 'public and secret are mutually exclusive',
|
|
@@ -64,9 +65,10 @@ export const RULES = {
|
|
|
64
65
|
'setup/missing-install-skill': 'setup: true requires a skills/install skill',
|
|
65
66
|
|
|
66
67
|
// connectors and mcp.json
|
|
67
|
-
'connector/marker': 'a connector marker holds auth, tools
|
|
68
|
+
'connector/marker': 'a connector marker holds one or more of auth, tools and liveTools, and nothing else',
|
|
68
69
|
'connector/auth': 'auth, when present, is exactly "oauth"',
|
|
69
|
-
'connector/tools': 'tools
|
|
70
|
+
'connector/tools': 'tools and liveTools are each a non-empty array of unique tool names, 1-100 characters each',
|
|
71
|
+
'connector/live-tools': 'each live tool is also in tools, when tools is present',
|
|
70
72
|
'connector/name-length': 'connector names are 1-100 characters',
|
|
71
73
|
'connector/undefined-server': 'a declared connector needs a matching mcp.json server',
|
|
72
74
|
'connector/no-mcp': 'declaring a connector requires an mcp.json',
|
package/src/rules.mjs
CHANGED
|
@@ -35,7 +35,7 @@ const CONTENT_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]*$/i
|
|
|
35
35
|
|
|
36
36
|
const MANIFEST_KEYS = ['$schema', 'name', 'version', 'description', 'author', 'homepage', 'repository', 'license', 'keywords', 'extensions']
|
|
37
37
|
const NAMESPACE_KEYS = ['displayName', 'image', 'default', 'fields', 'automations', 'setup', 'connectors']
|
|
38
|
-
const FIELD_KEYS = ['key', 'label', 'type', 'required', 'secret', 'public', 'pattern', 'options']
|
|
38
|
+
const FIELD_KEYS = ['key', 'label', 'description', 'type', 'required', 'secret', 'public', 'pattern', 'options']
|
|
39
39
|
const AUTOMATION_KEYS = ['name', 'schedule', 'model']
|
|
40
40
|
const AUTOMATION_MODELS = ['normal', 'intelligent-high']
|
|
41
41
|
const SERVER_KEYS = ['type', 'url', 'headers']
|
|
@@ -51,6 +51,8 @@ export const DEFAULT_ENV_KEYS = [
|
|
|
51
51
|
'NEXT_PUBLIC_TERMS_CONDITIONS_URL',
|
|
52
52
|
]
|
|
53
53
|
|
|
54
|
+
const RESERVED_SLUG = 'custom'
|
|
55
|
+
|
|
54
56
|
const MISPLACED = ['agents', 'automations', 'scripts', 'instructions.md']
|
|
55
57
|
|
|
56
58
|
// The builder-maintained symlink to the installed version, the only stable
|
|
@@ -224,8 +226,13 @@ function lintMarketplaceManifest(root, report) {
|
|
|
224
226
|
|
|
225
227
|
let slug = null
|
|
226
228
|
if (check.string('marketplace/entry-slug', `${base}.slug`, entry.slug, { min: 1, max: 64, required: true })) {
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
+
if (entry.slug === RESERVED_SLUG) {
|
|
230
|
+
report.error('marketplace/entry-slug', file, `${base}.slug: "${RESERVED_SLUG}" is reserved for user-created connectors and credentials`, at(`${base}.slug`))
|
|
231
|
+
} else if (SLUG_PATTERN.test(entry.slug)) {
|
|
232
|
+
slug = entry.slug
|
|
233
|
+
} else {
|
|
234
|
+
report.error('marketplace/entry-slug', file, `${base}.slug: ${SLUG_MESSAGE}`, at(`${base}.slug`))
|
|
235
|
+
}
|
|
229
236
|
}
|
|
230
237
|
|
|
231
238
|
if (slug) {
|
|
@@ -451,6 +458,9 @@ function lintFields(report, file, at, check, ns, ext, slug) {
|
|
|
451
458
|
}
|
|
452
459
|
|
|
453
460
|
check.string('field/label', `${p}.label`, field.label, { min: 1, max: 100, required: true, label: `${label}.label` })
|
|
461
|
+
if (field.description !== undefined) {
|
|
462
|
+
check.string('field/description', `${p}.description`, field.description, { min: 1, max: 300, trim: true, label: `${label}.description` })
|
|
463
|
+
}
|
|
454
464
|
|
|
455
465
|
if (field.type !== 'text' && field.type !== 'select') {
|
|
456
466
|
report.error('field/type', file, `${label}.type: expected "text" or "select", received ${JSON.stringify(field.type)}`, at(`${p}.type`))
|
|
@@ -580,9 +590,9 @@ function lintConnectors(report, file, at, check, ns, ext) {
|
|
|
580
590
|
report.error('connector/marker', file, `${label}: expected object, received ${typeOf(marker)}`, at(p))
|
|
581
591
|
continue
|
|
582
592
|
}
|
|
583
|
-
check.strictKeys('connector/marker', p, marker, ['auth', 'tools'], label)
|
|
584
|
-
if (marker.auth === undefined && marker.tools === undefined) {
|
|
585
|
-
report.error('connector/marker', file, `${label}: declare auth, tools or
|
|
593
|
+
check.strictKeys('connector/marker', p, marker, ['auth', 'tools', 'liveTools'], label)
|
|
594
|
+
if (marker.auth === undefined && marker.tools === undefined && marker.liveTools === undefined) {
|
|
595
|
+
report.error('connector/marker', file, `${label}: declare auth, tools or liveTools`, at(p))
|
|
586
596
|
}
|
|
587
597
|
if (marker.auth !== undefined && marker.auth !== 'oauth') {
|
|
588
598
|
report.error('connector/auth', file, `${label}.auth: expected "oauth", received ${JSON.stringify(marker.auth)}`, at(`${p}.auth`))
|
|
@@ -590,6 +600,10 @@ function lintConnectors(report, file, at, check, ns, ext) {
|
|
|
590
600
|
if (marker.tools !== undefined) {
|
|
591
601
|
lintToolAllowlist(report, file, at, check, `${p}.tools`, `${label}.tools`, marker.tools)
|
|
592
602
|
}
|
|
603
|
+
if (marker.liveTools !== undefined) {
|
|
604
|
+
lintToolAllowlist(report, file, at, check, `${p}.liveTools`, `${label}.liveTools`, marker.liveTools)
|
|
605
|
+
lintLiveToolsInTools(report, file, at, p, label, marker)
|
|
606
|
+
}
|
|
593
607
|
names.push(name)
|
|
594
608
|
}
|
|
595
609
|
return names
|
|
@@ -617,6 +631,15 @@ function lintToolAllowlist(report, file, at, check, p, label, tools) {
|
|
|
617
631
|
}
|
|
618
632
|
}
|
|
619
633
|
|
|
634
|
+
function lintLiveToolsInTools(report, file, at, p, label, marker) {
|
|
635
|
+
if (!Array.isArray(marker.tools) || !Array.isArray(marker.liveTools)) return
|
|
636
|
+
for (const [index, tool] of marker.liveTools.entries()) {
|
|
637
|
+
if (typeof tool === 'string' && !marker.tools.includes(tool)) {
|
|
638
|
+
report.error('connector/live-tools', file, `${label}.liveTools[${index}]: live tool "${tool}" must also be in tools`, at(`${p}.liveTools.${index}`))
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
620
643
|
// --- mcp.json --------------------------------------------------------------
|
|
621
644
|
|
|
622
645
|
function lintMcp(root, entry, manifest, report) {
|