@pikku/inspector 0.12.111 → 0.12.112
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/add/add-define-contributors.d.ts +10 -0
- package/dist/add/add-define-contributors.js +98 -0
- package/dist/add/add-pikku-studio.d.ts +2 -2
- package/dist/add/add-pikku-studio.js +13 -13
- package/dist/add/add-wire-addon.js +11 -2
- package/dist/error-codes.d.ts +5 -0
- package/dist/error-codes.js +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +35 -21
- package/dist/utils/load-addon-functions-meta.d.ts +1 -8
- package/dist/utils/load-addon-functions-meta.js +56 -39
- package/dist/utils/serialize-inspector-state.d.ts +3 -1
- package/dist/utils/serialize-inspector-state.js +4 -2
- package/dist/visit.js +2 -0
- package/package.json +3 -3
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import type { InspectorLogger, InspectorState } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Detect `defineContributors({ [name]: { dev?, deploy? } })` and record, per
|
|
5
|
+
* name, the module and export of each variant. A name defined twice, or a
|
|
6
|
+
* variant a host could not import by itself, is a critical error: a contributor
|
|
7
|
+
* that silently drops out would leave the host without the service it opted in
|
|
8
|
+
* to.
|
|
9
|
+
*/
|
|
10
|
+
export declare function addDefineContributors(logger: InspectorLogger, node: ts.Node, checker: ts.TypeChecker, state: InspectorState): void;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { ErrorCode } from '../error-codes.js';
|
|
3
|
+
import { callsImportedDefiner, exportedName } from './add-analytics.js';
|
|
4
|
+
const DEFINE_CONTRIBUTORS = 'defineContributors';
|
|
5
|
+
const VARIANTS = ['dev', 'deploy'];
|
|
6
|
+
const propertyKey = (name) => ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Where an exported contributor const lives and what it is exported as. A
|
|
9
|
+
* host imports exactly this, so a variant that is inline, not exported, or
|
|
10
|
+
* built by a call has no answer and is reported instead.
|
|
11
|
+
*/
|
|
12
|
+
const resolveVariant = (value, checker) => {
|
|
13
|
+
if (!ts.isIdentifier(value)) {
|
|
14
|
+
return `must be a reference to an exported const so a host can import just that variant, got: ${value.getText()}`;
|
|
15
|
+
}
|
|
16
|
+
let symbol = checker.getSymbolAtLocation(value);
|
|
17
|
+
if (symbol && symbol.flags & ts.SymbolFlags.Alias) {
|
|
18
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
19
|
+
}
|
|
20
|
+
const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
|
|
21
|
+
if (!declaration ||
|
|
22
|
+
!ts.isVariableDeclaration(declaration) ||
|
|
23
|
+
!ts.isIdentifier(declaration.name)) {
|
|
24
|
+
return `'${value.text}' does not resolve to a const declaration`;
|
|
25
|
+
}
|
|
26
|
+
const exported = exportedName(declaration, declaration.name, checker);
|
|
27
|
+
if (!exported) {
|
|
28
|
+
return `'${value.text}' is not exported by ${declaration.getSourceFile().fileName}`;
|
|
29
|
+
}
|
|
30
|
+
return { module: declaration.getSourceFile().fileName, export: exported };
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Detect `defineContributors({ [name]: { dev?, deploy? } })` and record, per
|
|
34
|
+
* name, the module and export of each variant. A name defined twice, or a
|
|
35
|
+
* variant a host could not import by itself, is a critical error: a contributor
|
|
36
|
+
* that silently drops out would leave the host without the service it opted in
|
|
37
|
+
* to.
|
|
38
|
+
*/
|
|
39
|
+
export function addDefineContributors(logger, node, checker, state) {
|
|
40
|
+
if (!ts.isCallExpression(node))
|
|
41
|
+
return;
|
|
42
|
+
if (!callsImportedDefiner(node.expression, checker, DEFINE_CONTRIBUTORS))
|
|
43
|
+
return;
|
|
44
|
+
const fail = (code, message) => logger.critical(code, message);
|
|
45
|
+
const notStatic = (message) => fail(ErrorCode.CONTRIBUTORS_NOT_STATIC, message);
|
|
46
|
+
const [argument] = node.arguments;
|
|
47
|
+
if (!argument || !ts.isObjectLiteralExpression(argument)) {
|
|
48
|
+
notStatic(`defineContributors must be called with an object literal keyed by contributor name${argument ? `, got: ${argument.getText()}` : ''}`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
for (const property of argument.properties) {
|
|
52
|
+
const name = ts.isPropertyAssignment(property)
|
|
53
|
+
? propertyKey(property.name)
|
|
54
|
+
: undefined;
|
|
55
|
+
if (!name || !ts.isPropertyAssignment(property)) {
|
|
56
|
+
notStatic(`defineContributors takes { name: { dev?, deploy? } } entries, got: ${property.getText()}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (!ts.isObjectLiteralExpression(property.initializer)) {
|
|
60
|
+
notStatic(`Contributor '${name}' must be an object literal with dev and/or deploy, got: ${property.initializer.getText()}`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const variants = {};
|
|
64
|
+
let valid = true;
|
|
65
|
+
for (const variant of property.initializer.properties) {
|
|
66
|
+
const key = ts.isPropertyAssignment(variant)
|
|
67
|
+
? propertyKey(variant.name)
|
|
68
|
+
: undefined;
|
|
69
|
+
if (!key ||
|
|
70
|
+
!ts.isPropertyAssignment(variant) ||
|
|
71
|
+
!VARIANTS.includes(key)) {
|
|
72
|
+
notStatic(`Contributor '${name}' takes only dev and deploy, got: ${variant.getText()}`);
|
|
73
|
+
valid = false;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const ref = resolveVariant(variant.initializer, checker);
|
|
77
|
+
if (typeof ref === 'string') {
|
|
78
|
+
notStatic(`Contributor '${name}' ${key} ${ref}`);
|
|
79
|
+
valid = false;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
variants[key] = ref;
|
|
83
|
+
}
|
|
84
|
+
if (!valid)
|
|
85
|
+
continue;
|
|
86
|
+
if (!variants.dev && !variants.deploy) {
|
|
87
|
+
notStatic(`Contributor '${name}' declares neither dev nor deploy`);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const manifest = (state.contributorsManifest ??= {});
|
|
91
|
+
if (manifest[name]) {
|
|
92
|
+
fail(ErrorCode.CONTRIBUTOR_NAME_DUPLICATED, `Contributor '${name}' is defined twice. A contributor name belongs to one defineContributors entry.`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
logger.debug(`• Found ${DEFINE_CONTRIBUTORS}: ${name}`);
|
|
96
|
+
manifest[name] = variants;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import type { InspectorState, InspectorLogger } from '../types.js';
|
|
3
3
|
/**
|
|
4
|
-
* Detect `
|
|
4
|
+
* Detect `definePlugin({ title, screens })` and record it as the package's
|
|
5
5
|
* `studio` metadata. Every field must be a literal: one that is not is
|
|
6
6
|
* reported and its screen left out, since a screen the host cannot read would
|
|
7
7
|
* otherwise vanish with no sign.
|
|
8
8
|
*/
|
|
9
9
|
export declare function addPikkuStudio(node: ts.Node, state: InspectorState, logger: InspectorLogger): void;
|
|
10
10
|
/**
|
|
11
|
-
* The checks on `
|
|
11
|
+
* The checks on `definePlugin` that need the whole package: every title is a
|
|
12
12
|
* key in the addon's `messages/en.json` (a key missing from another locale is
|
|
13
13
|
* a warning), and every nav badge names one of the addon's functions.
|
|
14
14
|
*/
|
|
@@ -97,7 +97,7 @@ const readNav = (node, fail) => {
|
|
|
97
97
|
};
|
|
98
98
|
const readScreen = (element, fail) => {
|
|
99
99
|
if (!ts.isObjectLiteralExpression(element)) {
|
|
100
|
-
fail(`A
|
|
100
|
+
fail(`A definePlugin screen must be an object literal, got: ${element.getText()}`);
|
|
101
101
|
return undefined;
|
|
102
102
|
}
|
|
103
103
|
let path;
|
|
@@ -166,7 +166,7 @@ const readScreen = (element, fail) => {
|
|
|
166
166
|
if (!ok)
|
|
167
167
|
return undefined;
|
|
168
168
|
if (!path || !title || !scope || (!component && !app)) {
|
|
169
|
-
fail(`A
|
|
169
|
+
fail(`A definePlugin screen needs a literal path, title and scope, and either a lazy component or an app: ${element.getText()}`);
|
|
170
170
|
return undefined;
|
|
171
171
|
}
|
|
172
172
|
if (component && app) {
|
|
@@ -186,7 +186,7 @@ const readScreen = (element, fail) => {
|
|
|
186
186
|
};
|
|
187
187
|
};
|
|
188
188
|
/**
|
|
189
|
-
* Detect `
|
|
189
|
+
* Detect `definePlugin({ title, screens })` and record it as the package's
|
|
190
190
|
* `studio` metadata. Every field must be a literal: one that is not is
|
|
191
191
|
* reported and its screen left out, since a screen the host cannot read would
|
|
192
192
|
* otherwise vanish with no sign.
|
|
@@ -195,7 +195,7 @@ export function addPikkuStudio(node, state, logger) {
|
|
|
195
195
|
if (!ts.isCallExpression(node))
|
|
196
196
|
return;
|
|
197
197
|
const { expression, arguments: args } = node;
|
|
198
|
-
if (!ts.isIdentifier(expression) || expression.text !== 'pikkuStudio')
|
|
198
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'definePlugin' && expression.text !== 'pikkuStudio')
|
|
199
199
|
return;
|
|
200
200
|
const [firstArg] = args;
|
|
201
201
|
if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
|
|
@@ -206,7 +206,7 @@ export function addPikkuStudio(node, state, logger) {
|
|
|
206
206
|
for (const prop of firstArg.properties) {
|
|
207
207
|
const key = propName(prop);
|
|
208
208
|
if (!key || !ts.isPropertyAssignment(prop)) {
|
|
209
|
-
fail(`A
|
|
209
|
+
fail(`A definePlugin field must be a literal, got: ${prop.getText()}`);
|
|
210
210
|
continue;
|
|
211
211
|
}
|
|
212
212
|
if (key === 'title') {
|
|
@@ -214,12 +214,12 @@ export function addPikkuStudio(node, state, logger) {
|
|
|
214
214
|
title = prop.initializer.text;
|
|
215
215
|
}
|
|
216
216
|
else {
|
|
217
|
-
fail(`
|
|
217
|
+
fail(`definePlugin's title must be a literal, got: ${prop.initializer.getText()}`);
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
else if (key === 'screens') {
|
|
221
221
|
if (!ts.isArrayLiteralExpression(prop.initializer)) {
|
|
222
|
-
fail(`
|
|
222
|
+
fail(`definePlugin's screens must be an array literal, got: ${prop.initializer.getText()}`);
|
|
223
223
|
continue;
|
|
224
224
|
}
|
|
225
225
|
for (const element of prop.initializer.elements) {
|
|
@@ -230,17 +230,17 @@ export function addPikkuStudio(node, state, logger) {
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
if (!title) {
|
|
233
|
-
fail(`
|
|
233
|
+
fail(`definePlugin needs a literal title`);
|
|
234
234
|
return;
|
|
235
235
|
}
|
|
236
236
|
const seen = new Set();
|
|
237
237
|
for (const screen of screens) {
|
|
238
238
|
if (seen.has(screen.path)) {
|
|
239
|
-
logger.critical(ErrorCode.STUDIO_SCREEN_PATH_DUPLICATE, `
|
|
239
|
+
logger.critical(ErrorCode.STUDIO_SCREEN_PATH_DUPLICATE, `definePlugin declares two screens at '${screen.path}'; each screen needs its own path`);
|
|
240
240
|
}
|
|
241
241
|
seen.add(screen.path);
|
|
242
242
|
}
|
|
243
|
-
logger.debug(`• Found
|
|
243
|
+
logger.debug(`• Found definePlugin: ${title} (${screens.length} screens)`);
|
|
244
244
|
state.studio = {
|
|
245
245
|
title,
|
|
246
246
|
file: node.getSourceFile().fileName,
|
|
@@ -268,7 +268,7 @@ const readCatalog = (file) => {
|
|
|
268
268
|
}
|
|
269
269
|
};
|
|
270
270
|
/**
|
|
271
|
-
* The checks on `
|
|
271
|
+
* The checks on `definePlugin` that need the whole package: every title is a
|
|
272
272
|
* key in the addon's `messages/en.json` (a key missing from another locale is
|
|
273
273
|
* a warning), and every nav badge names one of the addon's functions.
|
|
274
274
|
*/
|
|
@@ -282,7 +282,7 @@ export function validatePikkuStudio(logger, state) {
|
|
|
282
282
|
const en = messagesDir ? readCatalog(join(messagesDir, 'en.json')) : undefined;
|
|
283
283
|
for (const key of new Set(keys)) {
|
|
284
284
|
if (!en || !Object.hasOwn(en, key)) {
|
|
285
|
-
logger.critical(ErrorCode.STUDIO_MESSAGE_KEY_MISSING, `
|
|
285
|
+
logger.critical(ErrorCode.STUDIO_MESSAGE_KEY_MISSING, `definePlugin uses the message key '${key}', which is missing from the addon's messages/en.json`);
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
288
|
if (messagesDir && existsSync(messagesDir)) {
|
|
@@ -293,7 +293,7 @@ export function validatePikkuStudio(logger, state) {
|
|
|
293
293
|
const catalog = readCatalog(join(messagesDir, name)) ?? {};
|
|
294
294
|
for (const key of new Set(keys)) {
|
|
295
295
|
if (!Object.hasOwn(catalog, key)) {
|
|
296
|
-
logger.warn(`
|
|
296
|
+
logger.warn(`definePlugin uses the message key '${key}', which is missing from the addon's messages/${name}`);
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
}
|
|
@@ -87,8 +87,7 @@ 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) ||
|
|
91
|
-
expression.text !== 'wireAddon')
|
|
90
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireAddon')
|
|
92
91
|
return;
|
|
93
92
|
const [firstArg] = args;
|
|
94
93
|
if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
|
|
@@ -114,6 +113,7 @@ export function addWireAddon(node, state, logger) {
|
|
|
114
113
|
let contentBucket;
|
|
115
114
|
let contentGrants;
|
|
116
115
|
let globalContent;
|
|
116
|
+
let contributors;
|
|
117
117
|
for (const prop of firstArg.properties) {
|
|
118
118
|
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
119
119
|
continue;
|
|
@@ -217,6 +217,14 @@ export function addWireAddon(node, state, logger) {
|
|
|
217
217
|
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
218
218
|
contentGrants = parseStringRecord(prop.initializer);
|
|
219
219
|
}
|
|
220
|
+
else if (key === 'contributors') {
|
|
221
|
+
contributors = parseStringArray(prop.initializer);
|
|
222
|
+
// An unreadable list read as "none" would silently drop services the
|
|
223
|
+
// project asked for.
|
|
224
|
+
if (contributors === undefined) {
|
|
225
|
+
logger.critical(ErrorCode.ADDON_CONTRIBUTORS_NOT_STATIC, `wireAddon's contributors must be an array of string literals, got: ${prop.initializer.getText()}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
220
228
|
else if (key === 'globalContent') {
|
|
221
229
|
globalContent = ts.isStringLiteral(prop.initializer)
|
|
222
230
|
? prop.initializer.text
|
|
@@ -248,6 +256,7 @@ export function addWireAddon(node, state, logger) {
|
|
|
248
256
|
contentBucket,
|
|
249
257
|
contentGrants,
|
|
250
258
|
globalContent,
|
|
259
|
+
...(contributors ? { contributors } : {}),
|
|
251
260
|
});
|
|
252
261
|
state.rpc.usedAddons.add(name);
|
|
253
262
|
state.rpc.wireAddonFiles.add(node.getSourceFile().fileName);
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -52,6 +52,11 @@ export declare enum ErrorCode {
|
|
|
52
52
|
STUDIO_SCREEN_PATH_DUPLICATE = "PKU357",
|
|
53
53
|
STUDIO_MESSAGE_KEY_MISSING = "PKU358",
|
|
54
54
|
STUDIO_BADGE_FUNCTION_NOT_FOUND = "PKU359",
|
|
55
|
+
CONTRIBUTORS_NOT_STATIC = "PKU360",
|
|
56
|
+
CONTRIBUTOR_NAME_DUPLICATED = "PKU361",
|
|
57
|
+
ADDON_CONTRIBUTORS_NOT_STATIC = "PKU362",
|
|
58
|
+
ADDON_CONTRIBUTOR_UNKNOWN = "PKU363",
|
|
59
|
+
ADDON_LEGACY_CONTRIBUTORS = "PKU364",
|
|
55
60
|
DB_COLUMN_NAME_TYPE_CONTRADICTION = "PKU480",
|
|
56
61
|
DB_JSON_COLUMN_UNTYPED = "PKU481",
|
|
57
62
|
DB_FORMAT_ON_NON_STRING = "PKU482",
|
package/dist/error-codes.js
CHANGED
|
@@ -55,6 +55,11 @@ export var ErrorCode;
|
|
|
55
55
|
ErrorCode["STUDIO_SCREEN_PATH_DUPLICATE"] = "PKU357";
|
|
56
56
|
ErrorCode["STUDIO_MESSAGE_KEY_MISSING"] = "PKU358";
|
|
57
57
|
ErrorCode["STUDIO_BADGE_FUNCTION_NOT_FOUND"] = "PKU359";
|
|
58
|
+
ErrorCode["CONTRIBUTORS_NOT_STATIC"] = "PKU360";
|
|
59
|
+
ErrorCode["CONTRIBUTOR_NAME_DUPLICATED"] = "PKU361";
|
|
60
|
+
ErrorCode["ADDON_CONTRIBUTORS_NOT_STATIC"] = "PKU362";
|
|
61
|
+
ErrorCode["ADDON_CONTRIBUTOR_UNKNOWN"] = "PKU363";
|
|
62
|
+
ErrorCode["ADDON_LEGACY_CONTRIBUTORS"] = "PKU364";
|
|
58
63
|
// Database schema codegen warnings
|
|
59
64
|
ErrorCode["DB_COLUMN_NAME_TYPE_CONTRADICTION"] = "PKU480";
|
|
60
65
|
ErrorCode["DB_JSON_COLUMN_UNTYPED"] = "PKU481";
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { inspect, analyzeProgram } from './inspector.js';
|
|
|
2
2
|
export { getInitialInspectorState } from './initial-state.js';
|
|
3
3
|
export { finalizeInspectorState } from './finalize.js';
|
|
4
4
|
export type { TypesMap } from './types-map.js';
|
|
5
|
-
export type { AddonConfig, AuthDefinition, CredentialOverrideMeta, StudioMeta, StudioScreenMeta, StudioNavMeta,
|
|
5
|
+
export type { AddonConfig, AuthDefinition, CredentialOverrideMeta, StudioMeta, StudioScreenMeta, StudioNavMeta, ContributorRef, ContributorVariantsMeta, ContributorsManifestMeta, OptedInContributorsMeta, InspectorDiagnostic, InspectorFilters, InspectorHTTPState, InspectorLogger, InspectorMiddlewareState, InspectorOptions, InspectorPermissionState, InspectorServiceTypes, InspectorState, MetaInputTypes, MiddlewareGroupMeta, MiddlewareRegistration, SchemaRef, } from './types.js';
|
|
6
6
|
export type { FilesAndMethodsErrors } from './utils/get-files-and-methods.js';
|
|
7
7
|
export { ErrorCode } from './error-codes.js';
|
|
8
8
|
export type { DiagnosticSeverity, CodedDiagnostic } from './error-codes.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -531,14 +531,14 @@ export type CredentialOverrideMeta = string | {
|
|
|
531
531
|
name?: string;
|
|
532
532
|
mode?: 'singleton' | 'wire';
|
|
533
533
|
};
|
|
534
|
-
/** A screen's nav entry as `
|
|
534
|
+
/** A screen's nav entry as `definePlugin` declares it, read statically. */
|
|
535
535
|
export type StudioNavMeta = {
|
|
536
536
|
area: 'rail' | 'team' | 'project' | 'account';
|
|
537
537
|
icon: string;
|
|
538
538
|
order: number;
|
|
539
539
|
badge?: string;
|
|
540
540
|
};
|
|
541
|
-
/** A screen of an addon as `
|
|
541
|
+
/** A screen of an addon as `definePlugin` declares it, read statically. */
|
|
542
542
|
export type StudioScreenMeta = {
|
|
543
543
|
path: string;
|
|
544
544
|
title: string;
|
|
@@ -557,17 +557,40 @@ export type StudioScreenMeta = {
|
|
|
557
557
|
});
|
|
558
558
|
export type StudioMeta = {
|
|
559
559
|
title: string;
|
|
560
|
-
/** The source file whose `
|
|
560
|
+
/** The source file whose `definePlugin` call declared this manifest. */
|
|
561
561
|
file: string;
|
|
562
562
|
screens: StudioScreenMeta[];
|
|
563
563
|
/**
|
|
564
564
|
* The addon's role: every scope its own functions require, derived by
|
|
565
|
-
* `pikku all` and never declared. Absent from the raw `
|
|
565
|
+
* `pikku all` and never declared. Absent from the raw `definePlugin` read.
|
|
566
566
|
* On a wired instance it also carries the host's `wireAddon` scopes, which
|
|
567
567
|
* the runner demands of every function in the package.
|
|
568
568
|
*/
|
|
569
569
|
scopes?: string[];
|
|
570
570
|
};
|
|
571
|
+
/**
|
|
572
|
+
* Where a contributor variant lives, so a host can import only that variant.
|
|
573
|
+
* `module` is the source file that declares it (absolute while it is in
|
|
574
|
+
* inspector state, relative to the addon package in the published meta);
|
|
575
|
+
* `export` is the name that module exports it under.
|
|
576
|
+
*/
|
|
577
|
+
export type ContributorRef = {
|
|
578
|
+
module: string;
|
|
579
|
+
export: string;
|
|
580
|
+
};
|
|
581
|
+
/** One `defineContributors` entry: each variant optional, each its own import. */
|
|
582
|
+
export type ContributorVariantsMeta = {
|
|
583
|
+
dev?: ContributorRef;
|
|
584
|
+
deploy?: ContributorRef;
|
|
585
|
+
};
|
|
586
|
+
/** The contributors an addon registers, keyed by name. */
|
|
587
|
+
export type ContributorsManifestMeta = Record<string, ContributorVariantsMeta>;
|
|
588
|
+
/** The contributors a project opted in to for one wired addon. */
|
|
589
|
+
export type OptedInContributorsMeta = {
|
|
590
|
+
packageName: string;
|
|
591
|
+
/** Each opted-in name with the variants the addon recorded; `module` is relative to the addon package. */
|
|
592
|
+
contributors: ContributorsManifestMeta;
|
|
593
|
+
};
|
|
571
594
|
export interface InspectorServiceTypes {
|
|
572
595
|
/** Property names of the project's SingletonServices type, sorted. */
|
|
573
596
|
singletonServices: string[] | null;
|
|
@@ -576,26 +599,15 @@ export interface InspectorServiceTypes {
|
|
|
576
599
|
/** Singleton services that are a `SecretService` under another name. */
|
|
577
600
|
secretAliases: string[];
|
|
578
601
|
}
|
|
579
|
-
export interface AddonContributorRef {
|
|
580
|
-
module: string;
|
|
581
|
-
export?: string;
|
|
582
|
-
}
|
|
583
|
-
export interface AddonContributorsMeta {
|
|
584
|
-
packageName: string;
|
|
585
|
-
packageDir: string;
|
|
586
|
-
contributors: AddonContributorRef[];
|
|
587
|
-
}
|
|
588
602
|
export interface InspectorState {
|
|
589
|
-
/** Set when this package calls `
|
|
603
|
+
/** Set when this package calls `definePlugin`: the screens it ships. */
|
|
590
604
|
studio?: StudioMeta;
|
|
591
|
-
/** The `
|
|
605
|
+
/** The `definePlugin` metadata of the addons this project wires with `ui: true`, by namespace. */
|
|
592
606
|
addonStudio?: Record<string, StudioMeta>;
|
|
593
|
-
/**
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
*/
|
|
598
|
-
addonContributors?: Record<string, AddonContributorsMeta>;
|
|
607
|
+
/** Set when this package calls `defineContributors`: the contributors it registers, by name. */
|
|
608
|
+
contributorsManifest?: ContributorsManifestMeta;
|
|
609
|
+
/** Per wired addon namespace, the contributors `wireAddon({ contributors })` opted in to. */
|
|
610
|
+
optedInContributors?: Record<string, OptedInContributorsMeta>;
|
|
599
611
|
rootDir: string;
|
|
600
612
|
singletonServicesTypeImportMap: PathToNameAndType;
|
|
601
613
|
wireServicesTypeImportMap: PathToNameAndType;
|
|
@@ -793,6 +805,8 @@ export interface InspectorState {
|
|
|
793
805
|
* `ContentService` instead of one scoped to its own folder.
|
|
794
806
|
*/
|
|
795
807
|
globalContent?: string;
|
|
808
|
+
/** The addon contributor names this project opted in to, as `wireAddon({ contributors })` listed them. */
|
|
809
|
+
contributors?: string[];
|
|
796
810
|
}>;
|
|
797
811
|
wireAddonFiles: Set<string>;
|
|
798
812
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { InspectorState, InspectorLogger } from '../types.js';
|
|
2
2
|
import type { WebhookSourcesMeta } from '@pikku/core/trigger';
|
|
3
3
|
export declare const describeMissingAddonMeta: (namespace: string, packageName: string, dirs: string[], declFile?: string) => string;
|
|
4
4
|
/**
|
|
@@ -8,13 +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
|
-
* The platform service contributors an addon declares in its package.json,
|
|
13
|
-
* `"pikku": { "contributors": [{ "module", "export" }] }` — the same shape as
|
|
14
|
-
* `dev.contributors`. `wireAddon` has no place for them: a contributor emits
|
|
15
|
-
* code into the host's entry, so it belongs to the package, not the wiring.
|
|
16
|
-
*/
|
|
17
|
-
export declare const readAddonContributors: (dirs: string[], packageName: string) => Promise<AddonContributorsMeta | null>;
|
|
18
11
|
export declare function loadAddonFunctionsMeta(logger: InspectorLogger, state: InspectorState): Promise<void>;
|
|
19
12
|
/**
|
|
20
13
|
* Load addon schemas into state.schemas. Called after generateAllSchemas
|
|
@@ -172,41 +172,62 @@ const loadStudioMeta = async (require, packageName) => {
|
|
|
172
172
|
return null;
|
|
173
173
|
return JSON.parse(await readFile(path, 'utf-8'));
|
|
174
174
|
};
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
const loadContributorsManifest = async (require, packageName) => {
|
|
176
|
+
const path = resolveAddonMeta(require, packageName, 'contributors/pikku-contributors-meta');
|
|
177
|
+
if (!path)
|
|
178
|
+
return {};
|
|
179
|
+
const meta = JSON.parse(await readFile(path, 'utf-8'));
|
|
180
|
+
return (meta?.contributors ?? {});
|
|
181
|
+
};
|
|
180
182
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* code into the host's entry, so it belongs to the package, not the wiring.
|
|
183
|
+
* Resolves `wireAddon({ contributors })` against what the addon registered.
|
|
184
|
+
* A name the addon does not register is an error rather than a skip: the
|
|
185
|
+
* project asked for a service and would otherwise build without it.
|
|
185
186
|
*/
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (!packageDir)
|
|
189
|
-
return null;
|
|
190
|
-
let declared;
|
|
187
|
+
const recordOptedInContributors = async (logger, state, require, namespace, packageName, names) => {
|
|
188
|
+
let registered;
|
|
191
189
|
try {
|
|
192
|
-
|
|
193
|
-
declared = pkg?.pikku?.contributors;
|
|
190
|
+
registered = await loadContributorsManifest(require, packageName);
|
|
194
191
|
}
|
|
195
|
-
catch {
|
|
196
|
-
|
|
192
|
+
catch (e) {
|
|
193
|
+
logger.critical(ErrorCode.ADDON_CONTRIBUTOR_UNKNOWN, `wireAddon('${namespace}') opts in to contributors, but the contributors manifest of ${packageName} could not be read: ${e instanceof Error ? e.message : e}`);
|
|
194
|
+
return;
|
|
197
195
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
196
|
+
const selected = {};
|
|
197
|
+
for (const name of names) {
|
|
198
|
+
if (!Object.hasOwn(registered, name)) {
|
|
199
|
+
const known = Object.keys(registered);
|
|
200
|
+
logger.critical(ErrorCode.ADDON_CONTRIBUTOR_UNKNOWN, `wireAddon('${namespace}') opts in to contributor '${name}', but ${packageName} registers no such contributor. ${known.length > 0
|
|
201
|
+
? `It registers: ${known.join(', ')}.`
|
|
202
|
+
: 'It registers none; add a defineContributors call to the package and run pikku all in it.'}`);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
selected[name] = registered[name];
|
|
206
|
+
}
|
|
207
|
+
if (Object.keys(selected).length > 0) {
|
|
208
|
+
;
|
|
209
|
+
(state.optedInContributors ??= {})[namespace] = {
|
|
210
|
+
packageName,
|
|
211
|
+
contributors: selected,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* `pikku.contributors` in an addon's package.json was the string-emitting
|
|
217
|
+
* contributor route. It is no longer read, so it is reported rather than left
|
|
218
|
+
* to stop applying silently.
|
|
219
|
+
*/
|
|
220
|
+
const hasLegacyContributors = async (dirs, packageName) => {
|
|
221
|
+
const dir = findInstalledPackageDir(dirs, packageName);
|
|
222
|
+
if (!dir)
|
|
223
|
+
return false;
|
|
224
|
+
try {
|
|
225
|
+
const pkg = JSON.parse(await readFile(join(dir, 'package.json'), 'utf-8'));
|
|
226
|
+
return pkg?.pikku?.contributors !== undefined;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return false;
|
|
202
230
|
}
|
|
203
|
-
if (declared.length === 0)
|
|
204
|
-
return null;
|
|
205
|
-
return {
|
|
206
|
-
packageName,
|
|
207
|
-
packageDir,
|
|
208
|
-
contributors: declared.map(({ module, export: name }) => name === undefined ? { module } : { module, export: name }),
|
|
209
|
-
};
|
|
210
231
|
};
|
|
211
232
|
/**
|
|
212
233
|
* A versioned remote addon's function metadata comes from the SDK `pikku addon
|
|
@@ -275,17 +296,13 @@ export async function loadAddonFunctionsMeta(logger, state) {
|
|
|
275
296
|
const dirs = addonResolutionDirs(state.rootDir, decl.file);
|
|
276
297
|
const require = createAddonResolver(dirs);
|
|
277
298
|
try {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
;
|
|
282
|
-
(state.addonContributors ??= {})[namespace] = contributors;
|
|
283
|
-
}
|
|
299
|
+
recordDeclaredUses(logger, namespace, decl, dirs);
|
|
300
|
+
if (decl.contributors && decl.contributors.length > 0) {
|
|
301
|
+
await recordOptedInContributors(logger, state, require, namespace, decl.package, decl.contributors);
|
|
284
302
|
}
|
|
285
|
-
|
|
286
|
-
logger.
|
|
303
|
+
if (await hasLegacyContributors(dirs, decl.package)) {
|
|
304
|
+
logger.critical(ErrorCode.ADDON_LEGACY_CONTRIBUTORS, `${decl.package} still declares "pikku.contributors" in its package.json, which is no longer read. Remove it, register the contributors with defineContributors in the addon's source, and opt in with wireAddon('${namespace}', { contributors: [...] }). See docs/contributors.md.`);
|
|
287
305
|
}
|
|
288
|
-
recordDeclaredUses(logger, namespace, decl, dirs);
|
|
289
306
|
if (decl.ui) {
|
|
290
307
|
let manifest;
|
|
291
308
|
let unreadable = false;
|
|
@@ -300,7 +317,7 @@ export async function loadAddonFunctionsMeta(logger, state) {
|
|
|
300
317
|
// reported above; the addon's functions and scopes still load
|
|
301
318
|
}
|
|
302
319
|
else if (!manifest || manifest.screens.length === 0) {
|
|
303
|
-
logger.critical(ErrorCode.ADDON_UI_HAS_NO_SCREENS, `wireAddon('${namespace}') sets ui: true, but ${decl.package} declares no screens. Add a
|
|
320
|
+
logger.critical(ErrorCode.ADDON_UI_HAS_NO_SCREENS, `wireAddon('${namespace}') sets ui: true, but ${decl.package} declares no screens. Add a definePlugin call to the package, or drop ui.`);
|
|
304
321
|
}
|
|
305
322
|
else {
|
|
306
323
|
;
|
|
@@ -212,6 +212,7 @@ export interface SerializableInspectorState {
|
|
|
212
212
|
contentBucket?: string;
|
|
213
213
|
contentGrants?: Record<string, string>;
|
|
214
214
|
globalContent?: string;
|
|
215
|
+
contributors?: string[];
|
|
215
216
|
}
|
|
216
217
|
]>;
|
|
217
218
|
wireAddonFiles: string[];
|
|
@@ -331,7 +332,8 @@ export interface SerializableInspectorState {
|
|
|
331
332
|
remoteAddonFunctions?: InspectorState['remoteAddonFunctions'];
|
|
332
333
|
studio?: InspectorState['studio'];
|
|
333
334
|
addonStudio?: InspectorState['addonStudio'];
|
|
334
|
-
|
|
335
|
+
contributorsManifest?: InspectorState['contributorsManifest'];
|
|
336
|
+
optedInContributors?: InspectorState['optedInContributors'];
|
|
335
337
|
exportedContracts: InspectorState['exportedContracts'];
|
|
336
338
|
}
|
|
337
339
|
/**
|
|
@@ -196,7 +196,8 @@ export function serializeInspectorState(state) {
|
|
|
196
196
|
remoteAddonFunctions: state.remoteAddonFunctions,
|
|
197
197
|
studio: state.studio,
|
|
198
198
|
addonStudio: state.addonStudio,
|
|
199
|
-
|
|
199
|
+
contributorsManifest: state.contributorsManifest,
|
|
200
|
+
optedInContributors: state.optedInContributors,
|
|
200
201
|
exportedContracts: state.exportedContracts,
|
|
201
202
|
};
|
|
202
203
|
}
|
|
@@ -406,7 +407,8 @@ export function deserializeInspectorState(data) {
|
|
|
406
407
|
: {}),
|
|
407
408
|
studio: data.studio,
|
|
408
409
|
addonStudio: data.addonStudio,
|
|
409
|
-
|
|
410
|
+
contributorsManifest: data.contributorsManifest,
|
|
411
|
+
optedInContributors: data.optedInContributors,
|
|
410
412
|
exportedContracts: data.exportedContracts || {
|
|
411
413
|
http: {},
|
|
412
414
|
cli: {},
|
package/dist/visit.js
CHANGED
|
@@ -16,6 +16,7 @@ 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
18
|
import { addPikkuStudio } from './add/add-pikku-studio.js';
|
|
19
|
+
import { addDefineContributors } from './add/add-define-contributors.js';
|
|
19
20
|
import { addWireRemoteAddon } from './add/add-wire-remote-addon.js';
|
|
20
21
|
import { addMiddleware } from './add/add-middleware.js';
|
|
21
22
|
import { addPermission } from './add/add-permission.js';
|
|
@@ -52,6 +53,7 @@ export const visitSetup = (logger, checker, node, state, options) => {
|
|
|
52
53
|
addRPCInvocations(node, state, logger);
|
|
53
54
|
addWireAddon(node, state, logger);
|
|
54
55
|
addPikkuStudio(node, state, logger);
|
|
56
|
+
addDefineContributors(logger, node, checker, state);
|
|
55
57
|
addWireRemoteAddon(node, state, logger);
|
|
56
58
|
addMiddleware(logger, node, checker, state, options);
|
|
57
59
|
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.112",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/pikkujs/pikku.git",
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
"zod": "^4.6.7"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@pikku/core": "^0.12.
|
|
73
|
+
"@pikku/core": "^0.12.148"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@pikku/core": "^0.12.
|
|
76
|
+
"@pikku/core": "^0.12.148",
|
|
77
77
|
"@types/node": "^26.6.5"
|
|
78
78
|
},
|
|
79
79
|
"engines": {
|