@pikku/inspector 0.12.101 → 0.12.103
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 +25 -0
- package/dist/add/add-define-screens.d.ts +8 -0
- package/dist/add/add-define-screens.js +140 -0
- package/dist/add/add-scope.d.ts +7 -0
- package/dist/add/add-scope.js +58 -0
- package/dist/add/add-wire-addon.js +39 -2
- package/dist/error-codes.d.ts +5 -0
- package/dist/error-codes.js +5 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/inspector.js +5 -1
- package/dist/types.d.ts +44 -0
- package/dist/utils/load-addon-functions-meta.d.ts +0 -5
- package/dist/utils/load-addon-functions-meta.js +62 -1
- package/dist/utils/post-process.d.ts +2 -0
- package/dist/utils/post-process.js +33 -0
- package/dist/utils/read-jsx-literal-text.d.ts +5 -0
- package/dist/utils/read-jsx-literal-text.js +126 -0
- package/dist/utils/serialize-inspector-state.d.ts +5 -0
- package/dist/utils/serialize-inspector-state.js +4 -0
- package/dist/visit.js +2 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
## 0.12.103
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 53ac40d: An addon can ship screens: `wireAddon({ ui: true })` mounts what the package declares with `defineScreens`, the inspector reads them, `pikku all` emits the screens manifest and the installed-addons registry, and `x-pikku-addon` narrows a session to the addon's role. `pikku new addon --ui` scaffolds one; its tables must be prefixed `<name>_`.
|
|
6
|
+
- Updated dependencies [a196a21]
|
|
7
|
+
- Updated dependencies [53ac40d]
|
|
8
|
+
- Updated dependencies [443fd40]
|
|
9
|
+
- Updated dependencies [56c8047]
|
|
10
|
+
- @pikku/core@0.12.139
|
|
11
|
+
|
|
12
|
+
## 0.12.102
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 3c0b151: `pikku validate` checks block libraries. The project's `pikku.config.json` lists them by package name under `blocks`; each listed package's `src/blocks` is checked. A block that renders a literal string is an error, because its words have to come from messages, and a block with no `.stories.tsx` is a warning. A block with no errors is reported as productized. A listed name that matches no package, or a package with no `src/blocks`, is an error.
|
|
17
|
+
- 0963b73: Every scope tree now records where it came from: `origin` on `ScopeDefinitionMeta` is `{ kind: 'app' }` for the app's own `defineScope`, `{ kind: 'generated' }` for a tree the CLI wrote into the project (a `.gen.ts`, or the `app` root derived from personas), and `{ kind: 'addon', package, displayName }` for one an installed addon declared. An addon's build stamps its own package and display name on its trees, and the host keeps that name while recording the package it actually installed. `MetaService.getScopesMeta()` reads the result, and the console's all-meta payload carries it as `scopes`.
|
|
18
|
+
|
|
19
|
+
The console's Scopes page becomes **Permissions** and is grouped by that origin: the app's own permissions first, then what Pikku generated into the project, then one card per addon (Pikku's own before third-party, each alphabetical), each card folding to its header — the app's own starts open, every addon's starts folded, and any search or filter opens them all. Above the cards, two filters narrow the page to what one role is given (or to what no role is given yet) and to one source. A permission group no longer says "Not part of any role yet" above lines that are each given to a role.
|
|
20
|
+
|
|
21
|
+
- Updated dependencies [0c865d9]
|
|
22
|
+
- Updated dependencies [c36b066]
|
|
23
|
+
- Updated dependencies [0963b73]
|
|
24
|
+
- @pikku/core@0.12.137
|
|
25
|
+
|
|
1
26
|
## 0.12.101
|
|
2
27
|
|
|
3
28
|
### Patch Changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import type { InspectorState, InspectorLogger } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Detect `defineScreens({ title, icon, screens })` and record the manifest.
|
|
5
|
+
* A screen with a field that is not a literal is reported and left out: a
|
|
6
|
+
* navigation entry the host cannot read would otherwise vanish with no sign.
|
|
7
|
+
*/
|
|
8
|
+
export declare function addDefineScreens(node: ts.Node, state: InspectorState, logger: InspectorLogger): void;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { ErrorCode } from '../error-codes.js';
|
|
3
|
+
const propName = (prop) => {
|
|
4
|
+
if (!ts.isPropertyAssignment(prop))
|
|
5
|
+
return undefined;
|
|
6
|
+
return ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)
|
|
7
|
+
? prop.name.text
|
|
8
|
+
: undefined;
|
|
9
|
+
};
|
|
10
|
+
const stringArray = (node) => {
|
|
11
|
+
if (!ts.isArrayLiteralExpression(node))
|
|
12
|
+
return undefined;
|
|
13
|
+
const values = [];
|
|
14
|
+
for (const element of node.elements) {
|
|
15
|
+
if (!ts.isStringLiteral(element))
|
|
16
|
+
return undefined;
|
|
17
|
+
values.push(element.text);
|
|
18
|
+
}
|
|
19
|
+
return values;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The module specifier of `() => import('./screens/x')`. Anything else cannot
|
|
23
|
+
* be handed to the host's bundler as a lazy import, so it is not read.
|
|
24
|
+
*/
|
|
25
|
+
const lazyImportSpecifier = (node) => {
|
|
26
|
+
if (!ts.isArrowFunction(node))
|
|
27
|
+
return undefined;
|
|
28
|
+
const body = node.body;
|
|
29
|
+
if (!ts.isCallExpression(body) ||
|
|
30
|
+
body.expression.kind !== ts.SyntaxKind.ImportKeyword)
|
|
31
|
+
return undefined;
|
|
32
|
+
const [specifier] = body.arguments;
|
|
33
|
+
return specifier && ts.isStringLiteral(specifier) ? specifier.text : undefined;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Detect `defineScreens({ title, icon, screens })` and record the manifest.
|
|
37
|
+
* A screen with a field that is not a literal is reported and left out: a
|
|
38
|
+
* navigation entry the host cannot read would otherwise vanish with no sign.
|
|
39
|
+
*/
|
|
40
|
+
export function addDefineScreens(node, state, logger) {
|
|
41
|
+
if (!ts.isCallExpression(node))
|
|
42
|
+
return;
|
|
43
|
+
const { expression, arguments: args } = node;
|
|
44
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'defineScreens')
|
|
45
|
+
return;
|
|
46
|
+
const [firstArg] = args;
|
|
47
|
+
if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
|
|
48
|
+
return;
|
|
49
|
+
const fail = (message) => logger.critical(ErrorCode.ADDON_SCREENS_NOT_STATIC, message);
|
|
50
|
+
let title;
|
|
51
|
+
let icon;
|
|
52
|
+
const screens = [];
|
|
53
|
+
for (const prop of firstArg.properties) {
|
|
54
|
+
const key = propName(prop);
|
|
55
|
+
if (!key || !ts.isPropertyAssignment(prop))
|
|
56
|
+
continue;
|
|
57
|
+
if (key === 'title' && ts.isStringLiteral(prop.initializer)) {
|
|
58
|
+
title = prop.initializer.text;
|
|
59
|
+
}
|
|
60
|
+
else if (key === 'icon' && ts.isStringLiteral(prop.initializer)) {
|
|
61
|
+
icon = prop.initializer.text;
|
|
62
|
+
}
|
|
63
|
+
else if (key === 'screens') {
|
|
64
|
+
if (!ts.isArrayLiteralExpression(prop.initializer)) {
|
|
65
|
+
fail(`defineScreens's screens must be an array literal, got: ${prop.initializer.getText()}`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
for (const element of prop.initializer.elements) {
|
|
69
|
+
if (!ts.isObjectLiteralExpression(element)) {
|
|
70
|
+
fail(`A defineScreens screen must be an object literal, got: ${element.getText()}`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
let path;
|
|
74
|
+
let screenTitle;
|
|
75
|
+
let nav;
|
|
76
|
+
let scopes;
|
|
77
|
+
let component;
|
|
78
|
+
let app;
|
|
79
|
+
for (const field of element.properties) {
|
|
80
|
+
const fieldKey = propName(field);
|
|
81
|
+
if (!fieldKey || !ts.isPropertyAssignment(field))
|
|
82
|
+
continue;
|
|
83
|
+
const value = field.initializer;
|
|
84
|
+
if (fieldKey === 'path' && ts.isStringLiteral(value)) {
|
|
85
|
+
path = value.text;
|
|
86
|
+
}
|
|
87
|
+
else if (fieldKey === 'title' && ts.isStringLiteral(value)) {
|
|
88
|
+
screenTitle = value.text;
|
|
89
|
+
}
|
|
90
|
+
else if (fieldKey === 'nav' &&
|
|
91
|
+
(value.kind === ts.SyntaxKind.TrueKeyword ||
|
|
92
|
+
value.kind === ts.SyntaxKind.FalseKeyword)) {
|
|
93
|
+
nav = value.kind === ts.SyntaxKind.TrueKeyword;
|
|
94
|
+
}
|
|
95
|
+
else if (fieldKey === 'scopes') {
|
|
96
|
+
scopes = stringArray(value);
|
|
97
|
+
if (!scopes) {
|
|
98
|
+
fail(`A screen's scopes must be an array of string literals, got: ${value.getText()}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else if (fieldKey === 'component') {
|
|
102
|
+
component = lazyImportSpecifier(value);
|
|
103
|
+
if (!component) {
|
|
104
|
+
fail(`A screen's component must be () => import('<literal>'), got: ${value.getText()}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else if (fieldKey === 'app' && ts.isStringLiteral(value)) {
|
|
108
|
+
app = value.text;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!path || !screenTitle || (!component && !app)) {
|
|
112
|
+
fail(`A defineScreens screen needs a literal path and title, and either a lazy component or an app: ${element.getText()}`);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (component && app) {
|
|
116
|
+
fail(`Screen '${path}' sets both component and app; a screen is one or the other`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
screens.push({
|
|
120
|
+
path,
|
|
121
|
+
title: screenTitle,
|
|
122
|
+
...(nav !== undefined ? { nav } : {}),
|
|
123
|
+
...(scopes ? { scopes } : {}),
|
|
124
|
+
...(component ? { component } : { app: app }),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!title) {
|
|
130
|
+
fail(`defineScreens needs a literal title`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
logger.debug(`• Found defineScreens: ${title} (${screens.length} screens)`);
|
|
134
|
+
state.screensManifest = {
|
|
135
|
+
title,
|
|
136
|
+
...(icon ? { icon } : {}),
|
|
137
|
+
file: node.getSourceFile().fileName,
|
|
138
|
+
screens,
|
|
139
|
+
};
|
|
140
|
+
}
|
package/dist/add/add-scope.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import type { AddWiring } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Folds `declareScopes` ids into `state.scopes.definitions`. An id the
|
|
4
|
+
* definitions already hold (an addon's tree, a `defineScope`) is left alone;
|
|
5
|
+
* a missing one is added as a bare node under its root, creating the root when
|
|
6
|
+
* nothing declared it.
|
|
7
|
+
*/
|
|
8
|
+
export declare const applyDeclaredScopes: (state: Parameters<AddWiring>[3]) => void;
|
|
2
9
|
export declare const addScope: AddWiring;
|
package/dist/add/add-scope.js
CHANGED
|
@@ -84,11 +84,63 @@ const extractScopeNodes = (obj, scopeName, logger) => {
|
|
|
84
84
|
const unwrapAs = (node) => ts.isAsExpression(node) || ts.isSatisfiesExpression(node)
|
|
85
85
|
? unwrapAs(node.expression)
|
|
86
86
|
: node;
|
|
87
|
+
/**
|
|
88
|
+
* Records the ids of a `declareScopes([...])` call. They are merged into the
|
|
89
|
+
* definitions later (`applyDeclaredScopes`), after addon scope trees have
|
|
90
|
+
* loaded, so an addon's richer declaration is never shadowed by a bare id.
|
|
91
|
+
*/
|
|
92
|
+
const addDeclareScopes = (logger, node, state) => {
|
|
93
|
+
const arg = node.arguments[0] && unwrapAs(node.arguments[0]);
|
|
94
|
+
if (!arg || !ts.isArrayLiteralExpression(arg)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const sourceFile = node.getSourceFile().fileName;
|
|
98
|
+
for (const element of arg.elements) {
|
|
99
|
+
if (!ts.isStringLiteralLike(element)) {
|
|
100
|
+
logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, 'declareScopes takes string literal scope ids.');
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const id = element.text;
|
|
104
|
+
if (!id.split(SEPARATOR).every((s) => isValidSegment(s, id, logger))) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
state.scopes.declared ??= new Map();
|
|
108
|
+
if (!state.scopes.declared.has(id)) {
|
|
109
|
+
state.scopes.declared.set(id, sourceFile);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
state.scopes.files.add(sourceFile);
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Folds `declareScopes` ids into `state.scopes.definitions`. An id the
|
|
116
|
+
* definitions already hold (an addon's tree, a `defineScope`) is left alone;
|
|
117
|
+
* a missing one is added as a bare node under its root, creating the root when
|
|
118
|
+
* nothing declared it.
|
|
119
|
+
*/
|
|
120
|
+
export const applyDeclaredScopes = (state) => {
|
|
121
|
+
for (const [id, sourceFile] of state.scopes.declared ?? []) {
|
|
122
|
+
const [rootName, ...rest] = id.split(SEPARATOR);
|
|
123
|
+
let root = state.scopes.definitions.find((d) => d.name === rootName);
|
|
124
|
+
if (!root) {
|
|
125
|
+
root = { name: rootName, sourceFile };
|
|
126
|
+
state.scopes.definitions.push(root);
|
|
127
|
+
}
|
|
128
|
+
let level = root;
|
|
129
|
+
for (const segment of rest) {
|
|
130
|
+
level.scopes ??= {};
|
|
131
|
+
level = level.scopes[segment] ??= {};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
87
135
|
export const addScope = (logger, node, checker, state, _options) => {
|
|
88
136
|
if (!ts.isCallExpression(node)) {
|
|
89
137
|
return;
|
|
90
138
|
}
|
|
91
139
|
const expression = node.expression;
|
|
140
|
+
if (ts.isIdentifier(expression) && expression.text === 'declareScopes') {
|
|
141
|
+
addDeclareScopes(logger, node, state);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
92
144
|
if (!ts.isIdentifier(expression) || expression.text !== 'defineScope') {
|
|
93
145
|
return;
|
|
94
146
|
}
|
|
@@ -101,6 +153,11 @@ export const addScope = (logger, node, checker, state, _options) => {
|
|
|
101
153
|
return;
|
|
102
154
|
}
|
|
103
155
|
const sourceFile = node.getSourceFile().fileName;
|
|
156
|
+
// A `.gen.ts` is one the CLI wrote into the project, the same test
|
|
157
|
+
// `claimSingleDeclaration` exempts it by.
|
|
158
|
+
const origin = sourceFile.endsWith('.gen.ts')
|
|
159
|
+
? { kind: 'generated' }
|
|
160
|
+
: { kind: 'app' };
|
|
104
161
|
// Roots are keyed exactly like the nodes beneath them, so each property of
|
|
105
162
|
// the call's single argument is one tree.
|
|
106
163
|
for (const prop of unwrapped.properties) {
|
|
@@ -141,6 +198,7 @@ export const addScope = (logger, node, checker, state, _options) => {
|
|
|
141
198
|
description: descriptionValue || undefined,
|
|
142
199
|
scopes,
|
|
143
200
|
sourceFile,
|
|
201
|
+
origin,
|
|
144
202
|
});
|
|
145
203
|
}
|
|
146
204
|
};
|
|
@@ -87,7 +87,8 @@ export function addWireAddon(node, state, logger) {
|
|
|
87
87
|
if (!ts.isCallExpression(node))
|
|
88
88
|
return;
|
|
89
89
|
const { expression, arguments: args } = node;
|
|
90
|
-
if (!ts.isIdentifier(expression) ||
|
|
90
|
+
if (!ts.isIdentifier(expression) ||
|
|
91
|
+
expression.text !== 'wireAddon')
|
|
91
92
|
return;
|
|
92
93
|
const [firstArg] = args;
|
|
93
94
|
if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
|
|
@@ -99,8 +100,10 @@ export function addWireAddon(node, state, logger) {
|
|
|
99
100
|
let expose;
|
|
100
101
|
let mcpEndpoint;
|
|
101
102
|
let auth;
|
|
103
|
+
let ui;
|
|
102
104
|
let tags;
|
|
103
105
|
let scopes;
|
|
106
|
+
let uses;
|
|
104
107
|
let secretOverrides;
|
|
105
108
|
let variableOverrides;
|
|
106
109
|
let credentialOverrides;
|
|
@@ -108,6 +111,9 @@ export function addWireAddon(node, state, logger) {
|
|
|
108
111
|
let credentialGrants;
|
|
109
112
|
let globalSecrets;
|
|
110
113
|
let globalCredentials;
|
|
114
|
+
let contentBucket;
|
|
115
|
+
let contentGrants;
|
|
116
|
+
let globalContent;
|
|
111
117
|
for (const prop of firstArg.properties) {
|
|
112
118
|
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
113
119
|
continue;
|
|
@@ -154,12 +160,25 @@ export function addWireAddon(node, state, logger) {
|
|
|
154
160
|
prop.initializer.kind === ts.SyntaxKind.FalseKeyword)) {
|
|
155
161
|
auth = prop.initializer.kind === ts.SyntaxKind.TrueKeyword;
|
|
156
162
|
}
|
|
163
|
+
else if (key === 'ui' &&
|
|
164
|
+
(prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
|
|
165
|
+
prop.initializer.kind === ts.SyntaxKind.FalseKeyword)) {
|
|
166
|
+
ui = prop.initializer.kind === ts.SyntaxKind.TrueKeyword;
|
|
167
|
+
}
|
|
157
168
|
else if (key === 'tags') {
|
|
158
169
|
tags = parseStringArray(prop.initializer);
|
|
159
170
|
}
|
|
160
171
|
else if (key === 'scopes') {
|
|
161
172
|
scopes = parseStringArray(prop.initializer);
|
|
162
173
|
}
|
|
174
|
+
else if (key === 'uses') {
|
|
175
|
+
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
176
|
+
uses = parseStringRecord(prop.initializer);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
logger.critical(ErrorCode.ADDON_EXPOSE_NOT_STATIC, `wireAddon's uses must be an object literal, got: ${prop.initializer.getText()}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
163
182
|
else if (key === 'secretOverrides' &&
|
|
164
183
|
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
165
184
|
secretOverrides = parseStringRecord(prop.initializer);
|
|
@@ -190,12 +209,26 @@ export function addWireAddon(node, state, logger) {
|
|
|
190
209
|
? prop.initializer.text
|
|
191
210
|
: prop.initializer.getText();
|
|
192
211
|
}
|
|
212
|
+
else if (key === 'contentBucket' &&
|
|
213
|
+
ts.isStringLiteral(prop.initializer)) {
|
|
214
|
+
contentBucket = prop.initializer.text;
|
|
215
|
+
}
|
|
216
|
+
else if (key === 'contentGrants' &&
|
|
217
|
+
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
218
|
+
contentGrants = parseStringRecord(prop.initializer);
|
|
219
|
+
}
|
|
220
|
+
else if (key === 'globalContent') {
|
|
221
|
+
globalContent = ts.isStringLiteral(prop.initializer)
|
|
222
|
+
? prop.initializer.text
|
|
223
|
+
: prop.initializer.getText();
|
|
224
|
+
}
|
|
193
225
|
}
|
|
194
226
|
if (!name || !pkg)
|
|
195
227
|
return;
|
|
196
|
-
logger.debug(`• Found
|
|
228
|
+
logger.debug(`• Found ${expression.text}: ${name} → ${pkg}`);
|
|
197
229
|
state.rpc.wireAddonDeclarations.set(name, {
|
|
198
230
|
package: pkg,
|
|
231
|
+
...(ui ? { ui } : {}),
|
|
199
232
|
file: node.getSourceFile().fileName,
|
|
200
233
|
rpcEndpoint,
|
|
201
234
|
mcp,
|
|
@@ -204,6 +237,7 @@ export function addWireAddon(node, state, logger) {
|
|
|
204
237
|
auth,
|
|
205
238
|
tags,
|
|
206
239
|
scopes,
|
|
240
|
+
uses,
|
|
207
241
|
secretOverrides,
|
|
208
242
|
variableOverrides,
|
|
209
243
|
credentialOverrides,
|
|
@@ -211,6 +245,9 @@ export function addWireAddon(node, state, logger) {
|
|
|
211
245
|
credentialGrants,
|
|
212
246
|
globalSecrets,
|
|
213
247
|
globalCredentials,
|
|
248
|
+
contentBucket,
|
|
249
|
+
contentGrants,
|
|
250
|
+
globalContent,
|
|
214
251
|
});
|
|
215
252
|
state.rpc.usedAddons.add(name);
|
|
216
253
|
state.rpc.wireAddonFiles.add(node.getSourceFile().fileName);
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -36,6 +36,11 @@ export declare enum ErrorCode {
|
|
|
36
36
|
ADDON_MCP_FUNCTION_NOT_FOUND = "PKU341",
|
|
37
37
|
ADDON_EXPOSE_FUNCTION_NOT_FOUND = "PKU343",
|
|
38
38
|
ADDON_EXPOSE_NOT_STATIC = "PKU344",
|
|
39
|
+
ADDON_SCREENS_NOT_STATIC = "PKU345",
|
|
40
|
+
ADDON_UI_HAS_NO_SCREENS = "PKU346",
|
|
41
|
+
ADDON_USES_NOT_WIRED = "PKU347",
|
|
42
|
+
ADDON_NEEDS_NOT_MAPPED = "PKU348",
|
|
43
|
+
ADDON_CONTENT_GRANT_INVALID = "PKU349",
|
|
39
44
|
DB_COLUMN_NAME_TYPE_CONTRADICTION = "PKU480",
|
|
40
45
|
DB_JSON_COLUMN_UNTYPED = "PKU481",
|
|
41
46
|
DB_FORMAT_ON_NON_STRING = "PKU482",
|
package/dist/error-codes.js
CHANGED
|
@@ -39,6 +39,11 @@ export var ErrorCode;
|
|
|
39
39
|
ErrorCode["ADDON_MCP_FUNCTION_NOT_FOUND"] = "PKU341";
|
|
40
40
|
ErrorCode["ADDON_EXPOSE_FUNCTION_NOT_FOUND"] = "PKU343";
|
|
41
41
|
ErrorCode["ADDON_EXPOSE_NOT_STATIC"] = "PKU344";
|
|
42
|
+
ErrorCode["ADDON_SCREENS_NOT_STATIC"] = "PKU345";
|
|
43
|
+
ErrorCode["ADDON_UI_HAS_NO_SCREENS"] = "PKU346";
|
|
44
|
+
ErrorCode["ADDON_USES_NOT_WIRED"] = "PKU347";
|
|
45
|
+
ErrorCode["ADDON_NEEDS_NOT_MAPPED"] = "PKU348";
|
|
46
|
+
ErrorCode["ADDON_CONTENT_GRANT_INVALID"] = "PKU349";
|
|
42
47
|
// Database schema codegen warnings
|
|
43
48
|
ErrorCode["DB_COLUMN_NAME_TYPE_CONTRADICTION"] = "PKU480";
|
|
44
49
|
ErrorCode["DB_JSON_COLUMN_UNTYPED"] = "PKU481";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { inspect, getInitialInspectorState } from './inspector.js';
|
|
2
2
|
export type { TypesMap } from './types-map.js';
|
|
3
|
-
export type { AddonConfig, AuthDefinition, CredentialOverrideMeta, InspectorDiagnostic, InspectorFilters, InspectorHTTPState, InspectorLogger, InspectorMiddlewareState, InspectorOptions, InspectorPermissionState, InspectorState, MetaInputTypes, MiddlewareGroupMeta, MiddlewareRegistration, SchemaRef, } from './types.js';
|
|
3
|
+
export type { AddonConfig, AuthDefinition, CredentialOverrideMeta, ScreensManifestMeta, AddonScreenMeta, InspectorDiagnostic, InspectorFilters, InspectorHTTPState, InspectorLogger, InspectorMiddlewareState, InspectorOptions, InspectorPermissionState, InspectorState, MetaInputTypes, MiddlewareGroupMeta, MiddlewareRegistration, SchemaRef, } from './types.js';
|
|
4
4
|
export type { FilesAndMethodsErrors } from './utils/get-files-and-methods.js';
|
|
5
5
|
export { ErrorCode } from './error-codes.js';
|
|
6
6
|
export type { DiagnosticSeverity, CodedDiagnostic } from './error-codes.js';
|
|
@@ -25,5 +25,6 @@ export { resolveFunctionMeta } from './utils/resolve-function-meta.js';
|
|
|
25
25
|
export type { SerializedWorkflowGraph, SerializedWorkflowGraphs, } from './utils/workflow/graph/index.js';
|
|
26
26
|
export { resolveCoreType } from './utils/resolve-core-type.js';
|
|
27
27
|
export { readModuleSpecifiers } from './utils/read-module-specifiers.js';
|
|
28
|
+
export { readJsxLiteralText } from './utils/read-jsx-literal-text.js';
|
|
28
29
|
export { readTsconfigOutDir } from './utils/read-tsconfig-out-dir.js';
|
|
29
30
|
export { addonResolutionDirs, createAddonResolver, } from './utils/addon-resolution.js';
|
package/dist/index.js
CHANGED
|
@@ -14,5 +14,6 @@ export { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
|
14
14
|
export { resolveFunctionMeta } from './utils/resolve-function-meta.js';
|
|
15
15
|
export { resolveCoreType } from './utils/resolve-core-type.js';
|
|
16
16
|
export { readModuleSpecifiers } from './utils/read-module-specifiers.js';
|
|
17
|
+
export { readJsxLiteralText } from './utils/read-jsx-literal-text.js';
|
|
17
18
|
export { readTsconfigOutDir } from './utils/read-tsconfig-out-dir.js';
|
|
18
19
|
export { addonResolutionDirs, createAddonResolver, } from './utils/addon-resolution.js';
|
package/dist/inspector.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
+
import { applyDeclaredScopes } from './add/add-scope.js';
|
|
2
3
|
import { performance } from 'perf_hooks';
|
|
3
4
|
import { dirname, relative, resolve } from 'path';
|
|
4
5
|
import { visitSetup, visitFunctions, visitRoutes } from './visit.js';
|
|
@@ -8,7 +9,7 @@ import { findCommonAncestor } from './utils/find-root-dir.js';
|
|
|
8
9
|
import { createSourceFileCacheHost } from './source-file-cache.js';
|
|
9
10
|
import { createNestedProjectFilter } from './utils/nested-project-filter.js';
|
|
10
11
|
import { toProgramPath } from './utils/program-path.js';
|
|
11
|
-
import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateAgentToolReferences, validateAgentScorerReferences, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, validateSystemRoleScopes, validatePersonaRoles, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, validateNoSecretAliasServices, registerDerivedOAuth2AppSecrets, validateSecretUsage, computeDiagnostics, validateSchemaReferences, validateSchemaWiringSeparation, validateScenarioServices, validateScenarioSteps, validateScenarioFeatures, validateWorkflowGraphAddons, } from './utils/post-process.js';
|
|
12
|
+
import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateAgentToolReferences, validateAgentScorerReferences, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateAddonUses, validateAddonContentGrants, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, validateSystemRoleScopes, validatePersonaRoles, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, validateNoSecretAliasServices, registerDerivedOAuth2AppSecrets, validateSecretUsage, computeDiagnostics, validateSchemaReferences, validateSchemaWiringSeparation, validateScenarioServices, validateScenarioSteps, validateScenarioFeatures, validateWorkflowGraphAddons, } from './utils/post-process.js';
|
|
12
13
|
import { validateExposedFunctionsGated } from './utils/validate-exposed-functions-gated.js';
|
|
13
14
|
import { validateTagsResolveToMiddleware } from './utils/validate-tags-resolve-to-middleware.js';
|
|
14
15
|
import { annotateHttpRouteAuth } from './utils/annotate-http-route-auth.js';
|
|
@@ -426,8 +427,11 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
426
427
|
validateSecretOverrides(logger, state);
|
|
427
428
|
validateVariableOverrides(logger, state);
|
|
428
429
|
validateCredentialOverrides(logger, state);
|
|
430
|
+
validateAddonUses(logger, state);
|
|
431
|
+
validateAddonContentGrants(logger, state);
|
|
429
432
|
validateRemoteAddonDependencies(logger, state);
|
|
430
433
|
validateRemoteAddonAuth(logger, state);
|
|
434
|
+
applyDeclaredScopes(state);
|
|
431
435
|
validateScopeReferences(logger, state);
|
|
432
436
|
validateSystemRoleScopes(logger, state);
|
|
433
437
|
validatePersonaRoles(logger, state);
|
package/dist/types.d.ts
CHANGED
|
@@ -491,7 +491,38 @@ export type CredentialOverrideMeta = string | {
|
|
|
491
491
|
name?: string;
|
|
492
492
|
mode?: 'singleton' | 'wire';
|
|
493
493
|
};
|
|
494
|
+
/** A screen of an addon as `defineScreens` declares it, read statically. */
|
|
495
|
+
export type AddonScreenMeta = {
|
|
496
|
+
path: string;
|
|
497
|
+
title: string;
|
|
498
|
+
nav?: boolean;
|
|
499
|
+
scopes?: string[];
|
|
500
|
+
} & ({
|
|
501
|
+
component: string;
|
|
502
|
+
app?: undefined;
|
|
503
|
+
} | {
|
|
504
|
+
app: string;
|
|
505
|
+
component?: undefined;
|
|
506
|
+
});
|
|
507
|
+
export type ScreensManifestMeta = {
|
|
508
|
+
title: string;
|
|
509
|
+
icon?: string;
|
|
510
|
+
/** The source file whose `defineScreens` call declared this manifest. */
|
|
511
|
+
file: string;
|
|
512
|
+
screens: AddonScreenMeta[];
|
|
513
|
+
/**
|
|
514
|
+
* The addon's role: every scope its own functions require, derived by
|
|
515
|
+
* `pikku all` and never declared. Absent from the raw `defineScreens` read.
|
|
516
|
+
* On a wired instance it also carries the host's `wireAddon` scopes, which
|
|
517
|
+
* the runner demands of every function in the package.
|
|
518
|
+
*/
|
|
519
|
+
scopes?: string[];
|
|
520
|
+
};
|
|
494
521
|
export interface InspectorState {
|
|
522
|
+
/** Set when this package calls `defineScreens`: the screens it ships. */
|
|
523
|
+
screensManifest?: ScreensManifestMeta;
|
|
524
|
+
/** The screens of the addons this project wires with `ui: true`, by namespace. */
|
|
525
|
+
addonScreens?: Record<string, ScreensManifestMeta>;
|
|
495
526
|
rootDir: string;
|
|
496
527
|
singletonServicesTypeImportMap: PathToNameAndType;
|
|
497
528
|
wireServicesTypeImportMap: PathToNameAndType;
|
|
@@ -605,7 +636,11 @@ export interface InspectorState {
|
|
|
605
636
|
package: string;
|
|
606
637
|
/** The app source file whose `wireAddon` call declared this instance. */
|
|
607
638
|
file?: string;
|
|
639
|
+
/** Declared with `ui: true`: the package must ship screens, and the host mounts them. */
|
|
640
|
+
ui?: boolean;
|
|
608
641
|
rpcEndpoint?: string;
|
|
642
|
+
/** The other addons this instance may call: package -> the name it is wired under. */
|
|
643
|
+
uses?: Record<string, string>;
|
|
609
644
|
/**
|
|
610
645
|
* `true` offers every function the addon declared `mcp: true`; a list
|
|
611
646
|
* names the functions to offer, whatever the addon declared. Absent
|
|
@@ -660,6 +695,13 @@ export interface InspectorState {
|
|
|
660
695
|
* `CredentialService` instead of one scoped to its declared credentials.
|
|
661
696
|
*/
|
|
662
697
|
globalCredentials?: string;
|
|
698
|
+
contentBucket?: string;
|
|
699
|
+
contentGrants?: Record<string, string>;
|
|
700
|
+
/**
|
|
701
|
+
* The app's stated reason for handing this instance the whole
|
|
702
|
+
* `ContentService` instead of one scoped to its own folder.
|
|
703
|
+
*/
|
|
704
|
+
globalContent?: string;
|
|
663
705
|
}>;
|
|
664
706
|
wireAddonFiles: Set<string>;
|
|
665
707
|
};
|
|
@@ -731,6 +773,8 @@ export interface InspectorState {
|
|
|
731
773
|
scopes: {
|
|
732
774
|
definitions: ScopeDefinitions;
|
|
733
775
|
files: Set<string>;
|
|
776
|
+
/** Ids from `declareScopes([...])`, merged into `definitions` once addon scopes are loaded. */
|
|
777
|
+
declared?: Map<string, string>;
|
|
734
778
|
};
|
|
735
779
|
systemRoles: {
|
|
736
780
|
definitions: SystemRoleDefinitions;
|
|
@@ -8,11 +8,6 @@ export declare const describeMissingAddonMeta: (namespace: string, packageName:
|
|
|
8
8
|
* Every source starts off: the app turns one on at runtime.
|
|
9
9
|
*/
|
|
10
10
|
export declare const namespaceAddonWebhookSources: (sources: WebhookSourcesMeta, namespace: string) => WebhookSourcesMeta;
|
|
11
|
-
/**
|
|
12
|
-
* After the setup sweep discovers wireAddon() declarations, load each addon
|
|
13
|
-
* package's function metadata so that wiring handlers (channels, HTTP routes,
|
|
14
|
-
* schedules, etc.) can look up addon function types during the routes sweep.
|
|
15
|
-
*/
|
|
16
11
|
export declare function loadAddonFunctionsMeta(logger: InspectorLogger, state: InspectorState): Promise<void>;
|
|
17
12
|
/**
|
|
18
13
|
* Load addon schemas into state.schemas. Called after generateAllSchemas
|
|
@@ -181,6 +181,27 @@ export const namespaceAddonWebhookSources = (sources, namespace) => {
|
|
|
181
181
|
* package's function metadata so that wiring handlers (channels, HTTP routes,
|
|
182
182
|
* schedules, etc.) can look up addon function types during the routes sweep.
|
|
183
183
|
*/
|
|
184
|
+
const loadScreensManifest = async (require, packageName) => {
|
|
185
|
+
const path = resolveAddonMeta(require, packageName, 'screens/pikku-screens-meta');
|
|
186
|
+
if (!path)
|
|
187
|
+
return null;
|
|
188
|
+
return JSON.parse(await readFile(path, 'utf-8'));
|
|
189
|
+
};
|
|
190
|
+
const readAddonNeeds = async (dirs, packageName) => {
|
|
191
|
+
const dir = findInstalledPackageDir(dirs, packageName);
|
|
192
|
+
if (!dir)
|
|
193
|
+
return [];
|
|
194
|
+
try {
|
|
195
|
+
const pkg = JSON.parse(await readFile(join(dir, 'package.json'), 'utf-8'));
|
|
196
|
+
const uses = pkg?.pikku?.uses;
|
|
197
|
+
return Array.isArray(uses)
|
|
198
|
+
? uses.filter((u) => typeof u === 'string')
|
|
199
|
+
: [];
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
};
|
|
184
205
|
export async function loadAddonFunctionsMeta(logger, state) {
|
|
185
206
|
const { wireAddonDeclarations } = state.rpc;
|
|
186
207
|
if (wireAddonDeclarations.size === 0)
|
|
@@ -194,6 +215,37 @@ export async function loadAddonFunctionsMeta(logger, state) {
|
|
|
194
215
|
const dirs = addonResolutionDirs(state.rootDir, decl.file);
|
|
195
216
|
const require = createAddonResolver(dirs);
|
|
196
217
|
try {
|
|
218
|
+
for (const needed of await readAddonNeeds(dirs, decl.package)) {
|
|
219
|
+
if (!decl.uses?.[needed]) {
|
|
220
|
+
logger.critical(ErrorCode.ADDON_NEEDS_NOT_MAPPED, `${decl.package} needs ${needed}, but wireAddon('${namespace}') does not map it. Wire ${needed} and add uses: { '${needed}': '<name>' } to this wireAddon.`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (decl.ui) {
|
|
224
|
+
let manifest;
|
|
225
|
+
let unreadable = false;
|
|
226
|
+
try {
|
|
227
|
+
manifest = await loadScreensManifest(require, decl.package);
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
logger.critical(ErrorCode.ADDON_UI_HAS_NO_SCREENS, `wireAddon('${namespace}') sets ui: true, but the screens manifest of ${decl.package} could not be read: ${e instanceof Error ? e.message : e}`);
|
|
231
|
+
unreadable = true;
|
|
232
|
+
}
|
|
233
|
+
if (unreadable) {
|
|
234
|
+
// reported above; the addon's functions and scopes still load
|
|
235
|
+
}
|
|
236
|
+
else if (!manifest || manifest.screens.length === 0) {
|
|
237
|
+
logger.critical(ErrorCode.ADDON_UI_HAS_NO_SCREENS, `wireAddon('${namespace}') sets ui: true, but ${decl.package} declares no screens. Add a defineScreens call to the package, or drop ui.`);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
;
|
|
241
|
+
(state.addonScreens ??= {})[namespace] = {
|
|
242
|
+
...manifest,
|
|
243
|
+
scopes: [
|
|
244
|
+
...new Set([...(manifest.scopes ?? []), ...(decl.scopes ?? [])]),
|
|
245
|
+
].sort(),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
197
249
|
// Verbose first. `description` is one of the fields stripped from the
|
|
198
250
|
// minimal copy, and it is what an addon's function is offered to a model
|
|
199
251
|
// under — both as an MCP tool below and as an agent tool. Read the minimal
|
|
@@ -296,7 +348,16 @@ export async function loadAddonFunctionsMeta(logger, state) {
|
|
|
296
348
|
for (const [key, def] of Object.entries(scopesMeta)) {
|
|
297
349
|
const existing = state.scopes.definitions.find((d) => d.name === key);
|
|
298
350
|
if (!existing) {
|
|
299
|
-
|
|
351
|
+
// The addon's build stamped its own name on the trees it declared;
|
|
352
|
+
// the package it was installed as is the one this host knows.
|
|
353
|
+
const origin = {
|
|
354
|
+
kind: 'addon',
|
|
355
|
+
package: decl.package,
|
|
356
|
+
displayName: def.origin?.kind === 'addon'
|
|
357
|
+
? def.origin.displayName
|
|
358
|
+
: undefined,
|
|
359
|
+
};
|
|
360
|
+
state.scopes.definitions.push({ ...def, origin });
|
|
300
361
|
logger.debug(`Loaded addon scope '${key}' from ${decl.package}`);
|
|
301
362
|
}
|
|
302
363
|
}
|
|
@@ -31,6 +31,8 @@ export declare function extractWireNames(list?: Array<MiddlewareMetadata | Permi
|
|
|
31
31
|
*/
|
|
32
32
|
export declare function aggregateRequiredServices(state: InspectorState | Omit<InspectorState, "typesLookup">): void;
|
|
33
33
|
export declare function validateSecretOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
|
|
34
|
+
export declare function validateAddonUses(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
|
|
35
|
+
export declare function validateAddonContentGrants(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
|
|
34
36
|
export declare function validateCredentialOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
|
|
35
37
|
export declare function validateVariableOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, "typesLookup">): void;
|
|
36
38
|
/**
|
|
@@ -393,6 +393,39 @@ export function validateSecretOverrides(logger, state) {
|
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
|
+
export function validateAddonUses(logger, state) {
|
|
397
|
+
const { wireAddonDeclarations } = state.rpc;
|
|
398
|
+
if (!wireAddonDeclarations || wireAddonDeclarations.size === 0)
|
|
399
|
+
return;
|
|
400
|
+
for (const [namespace, addonDecl] of wireAddonDeclarations.entries()) {
|
|
401
|
+
for (const [pkg, target] of Object.entries(addonDecl.uses ?? {})) {
|
|
402
|
+
const wired = wireAddonDeclarations.get(target);
|
|
403
|
+
if (!wired) {
|
|
404
|
+
logger.critical(ErrorCode.ADDON_USES_NOT_WIRED, `Addon '${namespace}' uses '${pkg}' as '${target}', but no addon is wired under the name '${target}'. Wired: ${Array.from(wireAddonDeclarations.keys()).join(", ")}`);
|
|
405
|
+
}
|
|
406
|
+
else if (wired.package !== pkg) {
|
|
407
|
+
logger.critical(ErrorCode.ADDON_USES_NOT_WIRED, `Addon '${namespace}' uses '${pkg}' as '${target}', but '${target}' is wired from '${wired.package}'.`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
export function validateAddonContentGrants(logger, state) {
|
|
413
|
+
const { wireAddonDeclarations } = state.rpc;
|
|
414
|
+
if (!wireAddonDeclarations || wireAddonDeclarations.size === 0)
|
|
415
|
+
return;
|
|
416
|
+
const buckets = new Set(Array.from(wireAddonDeclarations.entries()).map(([namespace, decl]) => decl.contentBucket ?? namespace));
|
|
417
|
+
for (const [namespace, decl] of wireAddonDeclarations.entries()) {
|
|
418
|
+
for (const [prefix, mode] of Object.entries(decl.contentGrants ?? {})) {
|
|
419
|
+
if (mode !== "read" && mode !== "write") {
|
|
420
|
+
logger.critical(ErrorCode.ADDON_CONTENT_GRANT_INVALID, `Addon '${namespace}' grants '${prefix}' as '${mode}', but a content grant must be 'read' or 'write'.`);
|
|
421
|
+
}
|
|
422
|
+
const first = prefix.split("/").find((segment) => segment !== "");
|
|
423
|
+
if (!first || !buckets.has(first)) {
|
|
424
|
+
logger.critical(ErrorCode.ADDON_CONTENT_GRANT_INVALID, `Addon '${namespace}' grants content under '${prefix}', but '${first ?? ""}' is not the content bucket of any wired addon. Buckets: ${Array.from(buckets).join(", ")}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
396
429
|
export function validateCredentialOverrides(logger, state) {
|
|
397
430
|
const { wireAddonDeclarations } = state.rpc;
|
|
398
431
|
if (!wireAddonDeclarations || wireAddonDeclarations.size === 0)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
const TEXT_ATTRIBUTES = new Set([
|
|
3
|
+
'placeholder',
|
|
4
|
+
'title',
|
|
5
|
+
'alt',
|
|
6
|
+
'aria-label',
|
|
7
|
+
'label',
|
|
8
|
+
'description',
|
|
9
|
+
]);
|
|
10
|
+
const TEXT_PROPERTIES = new Set([
|
|
11
|
+
...TEXT_ATTRIBUTES,
|
|
12
|
+
'text',
|
|
13
|
+
'body',
|
|
14
|
+
'why',
|
|
15
|
+
'hint',
|
|
16
|
+
'caption',
|
|
17
|
+
'heading',
|
|
18
|
+
'subtitle',
|
|
19
|
+
'message',
|
|
20
|
+
'tooltip',
|
|
21
|
+
]);
|
|
22
|
+
const NON_TEXT_ATTRIBUTES = new Set([
|
|
23
|
+
'className',
|
|
24
|
+
'class',
|
|
25
|
+
'style',
|
|
26
|
+
'd',
|
|
27
|
+
'viewBox',
|
|
28
|
+
'preserveAspectRatio',
|
|
29
|
+
'points',
|
|
30
|
+
'transform',
|
|
31
|
+
'fill',
|
|
32
|
+
'stroke',
|
|
33
|
+
'href',
|
|
34
|
+
'src',
|
|
35
|
+
'rel',
|
|
36
|
+
'target',
|
|
37
|
+
'role',
|
|
38
|
+
'to',
|
|
39
|
+
'id',
|
|
40
|
+
'name',
|
|
41
|
+
'type',
|
|
42
|
+
'value',
|
|
43
|
+
'key',
|
|
44
|
+
]);
|
|
45
|
+
const isProse = (text) => /^\p{Lu}[^\s]*(\s+[^\s]+)+/u.test(text.trim()) &&
|
|
46
|
+
!/[:/[\]=_#]/.test(text.split(/\s+/)[0] ?? '');
|
|
47
|
+
const isSentence = (text) => /\p{L}+\s+\p{L}+/u.test(text);
|
|
48
|
+
const hasLetter = (text) => /\p{L}/u.test(text);
|
|
49
|
+
const isLiteral = (node) => ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node);
|
|
50
|
+
const rendered = (node) => {
|
|
51
|
+
if (isLiteral(node))
|
|
52
|
+
return [node];
|
|
53
|
+
if (ts.isParenthesizedExpression(node))
|
|
54
|
+
return rendered(node.expression);
|
|
55
|
+
if (ts.isConditionalExpression(node)) {
|
|
56
|
+
return [...rendered(node.whenTrue), ...rendered(node.whenFalse)];
|
|
57
|
+
}
|
|
58
|
+
if (ts.isBinaryExpression(node)) {
|
|
59
|
+
const operator = node.operatorToken.kind;
|
|
60
|
+
if (operator === ts.SyntaxKind.AmpersandAmpersandToken) {
|
|
61
|
+
return rendered(node.right);
|
|
62
|
+
}
|
|
63
|
+
if (operator === ts.SyntaxKind.BarBarToken ||
|
|
64
|
+
operator === ts.SyntaxKind.QuestionQuestionToken) {
|
|
65
|
+
return [...rendered(node.left), ...rendered(node.right)];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return [];
|
|
69
|
+
};
|
|
70
|
+
export const readJsxLiteralText = (fileName, content) => {
|
|
71
|
+
const source = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
72
|
+
const found = [];
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
const add = (node, text) => {
|
|
75
|
+
if (!hasLetter(text) || seen.has(node.getStart(source)))
|
|
76
|
+
return;
|
|
77
|
+
seen.add(node.getStart(source));
|
|
78
|
+
const { line } = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
79
|
+
found.push({ text: text.trim(), line: line + 1 });
|
|
80
|
+
};
|
|
81
|
+
const visit = (node) => {
|
|
82
|
+
if (ts.isJsxText(node)) {
|
|
83
|
+
add(node, node.text);
|
|
84
|
+
}
|
|
85
|
+
else if (ts.isJsxExpression(node) &&
|
|
86
|
+
node.expression &&
|
|
87
|
+
(ts.isJsxElement(node.parent) || ts.isJsxFragment(node.parent))) {
|
|
88
|
+
for (const literal of rendered(node.expression))
|
|
89
|
+
add(literal, literal.text);
|
|
90
|
+
}
|
|
91
|
+
else if (ts.isJsxAttribute(node) && node.initializer) {
|
|
92
|
+
const name = node.name.getText(source);
|
|
93
|
+
const value = ts.isJsxExpression(node.initializer)
|
|
94
|
+
? node.initializer.expression
|
|
95
|
+
: node.initializer;
|
|
96
|
+
if (value &&
|
|
97
|
+
(ts.isStringLiteral(value) ||
|
|
98
|
+
ts.isNoSubstitutionTemplateLiteral(value)) &&
|
|
99
|
+
(TEXT_ATTRIBUTES.has(name) ||
|
|
100
|
+
(!NON_TEXT_ATTRIBUTES.has(name) &&
|
|
101
|
+
!name.startsWith('data-') &&
|
|
102
|
+
isSentence(value.text)))) {
|
|
103
|
+
add(value, value.text);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (ts.isPropertyAssignment(node) &&
|
|
107
|
+
TEXT_PROPERTIES.has(node.name.getText(source)) &&
|
|
108
|
+
(ts.isStringLiteral(node.initializer) ||
|
|
109
|
+
ts.isNoSubstitutionTemplateLiteral(node.initializer))) {
|
|
110
|
+
add(node.initializer, node.initializer.text);
|
|
111
|
+
}
|
|
112
|
+
if (isLiteral(node) &&
|
|
113
|
+
isProse(node.text) &&
|
|
114
|
+
!ts.isImportDeclaration(node.parent) &&
|
|
115
|
+
!ts.isExportDeclaration(node.parent) &&
|
|
116
|
+
!ts.isLiteralTypeNode(node.parent) &&
|
|
117
|
+
!(ts.isPropertyAssignment(node.parent) && node.parent.name === node) &&
|
|
118
|
+
!(ts.isJsxAttribute(node.parent) &&
|
|
119
|
+
NON_TEXT_ATTRIBUTES.has(node.parent.name.getText(source)))) {
|
|
120
|
+
add(node, node.text);
|
|
121
|
+
}
|
|
122
|
+
ts.forEachChild(node, visit);
|
|
123
|
+
};
|
|
124
|
+
visit(source);
|
|
125
|
+
return found;
|
|
126
|
+
};
|
|
@@ -204,6 +204,9 @@ export interface SerializableInspectorState {
|
|
|
204
204
|
credentialGrants?: string[];
|
|
205
205
|
globalSecrets?: string;
|
|
206
206
|
globalCredentials?: string;
|
|
207
|
+
contentBucket?: string;
|
|
208
|
+
contentGrants?: Record<string, string>;
|
|
209
|
+
globalContent?: string;
|
|
207
210
|
}
|
|
208
211
|
]>;
|
|
209
212
|
wireAddonFiles: string[];
|
|
@@ -317,6 +320,8 @@ export interface SerializableInspectorState {
|
|
|
317
320
|
openAPISpec: Record<string, any> | null;
|
|
318
321
|
diagnostics: InspectorDiagnostic[];
|
|
319
322
|
addonFunctions: InspectorState['addonFunctions'];
|
|
323
|
+
screensManifest?: InspectorState['screensManifest'];
|
|
324
|
+
addonScreens?: InspectorState['addonScreens'];
|
|
320
325
|
exportedContracts: InspectorState['exportedContracts'];
|
|
321
326
|
}
|
|
322
327
|
/**
|
|
@@ -182,6 +182,8 @@ export function serializeInspectorState(state) {
|
|
|
182
182
|
openAPISpec: state.openAPISpec,
|
|
183
183
|
diagnostics: state.diagnostics,
|
|
184
184
|
addonFunctions: state.addonFunctions,
|
|
185
|
+
screensManifest: state.screensManifest,
|
|
186
|
+
addonScreens: state.addonScreens,
|
|
185
187
|
exportedContracts: state.exportedContracts,
|
|
186
188
|
};
|
|
187
189
|
}
|
|
@@ -378,6 +380,8 @@ export function deserializeInspectorState(data) {
|
|
|
378
380
|
openAPISpec: data.openAPISpec || null,
|
|
379
381
|
diagnostics: data.diagnostics || [],
|
|
380
382
|
addonFunctions: data.addonFunctions || {},
|
|
383
|
+
screensManifest: data.screensManifest,
|
|
384
|
+
addonScreens: data.addonScreens,
|
|
381
385
|
exportedContracts: data.exportedContracts || {
|
|
382
386
|
http: {},
|
|
383
387
|
cli: {},
|
package/dist/visit.js
CHANGED
|
@@ -15,6 +15,7 @@ import { addChannel } from './add/add-channel.js';
|
|
|
15
15
|
import { addGateway } from './add/add-gateway.js';
|
|
16
16
|
import { addRPCInvocations } from './add/add-rpc-invocations.js';
|
|
17
17
|
import { addWireAddon } from './add/add-wire-addon.js';
|
|
18
|
+
import { addDefineScreens } from './add/add-define-screens.js';
|
|
18
19
|
import { addWireRemoteAddon } from './add/add-wire-remote-addon.js';
|
|
19
20
|
import { addMiddleware } from './add/add-middleware.js';
|
|
20
21
|
import { addPermission } from './add/add-permission.js';
|
|
@@ -47,6 +48,7 @@ export const visitSetup = (logger, checker, node, state, options) => {
|
|
|
47
48
|
addOutgoingWebhook(logger, node, checker, state);
|
|
48
49
|
addRPCInvocations(node, state, logger);
|
|
49
50
|
addWireAddon(node, state, logger);
|
|
51
|
+
addDefineScreens(node, state, logger);
|
|
50
52
|
addWireRemoteAddon(node, state, logger);
|
|
51
53
|
addMiddleware(logger, node, checker, state, options);
|
|
52
54
|
addPermission(logger, node, checker, state, options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.103",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/pikkujs/pikku.git",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"zod": "^4.4.3"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@pikku/core": "^0.12.
|
|
57
|
+
"@pikku/core": "^0.12.139"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@pikku/core": "^0.12.
|
|
60
|
+
"@pikku/core": "^0.12.139",
|
|
61
61
|
"@types/node": "^24.13.3"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|