@oktis-works/plugin-runtime 0.6.1 → 0.7.1
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/bootstrap.d.ts +90 -22
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +301 -52
- package/dist/bootstrap.js.map +1 -1
- package/dist/content-query.d.ts +29 -0
- package/dist/content-query.d.ts.map +1 -0
- package/dist/content-query.js +109 -0
- package/dist/content-query.js.map +1 -0
- package/dist/hook-registry.d.ts +21 -21
- package/dist/hook-registry.d.ts.map +1 -1
- package/dist/hook-registry.js +26 -26
- package/dist/hook-registry.js.map +1 -1
- package/dist/http.d.ts +50 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +314 -0
- package/dist/http.js.map +1 -0
- package/dist/i18n.d.ts +16 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +73 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +8 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +98 -10
- package/dist/loader.js.map +1 -1
- package/dist/manifest.d.ts +11 -6
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +290 -17
- package/dist/manifest.js.map +1 -1
- package/dist/plugin-api.d.ts +194 -40
- package/dist/plugin-api.d.ts.map +1 -1
- package/dist/plugin-api.js +655 -13
- package/dist/plugin-api.js.map +1 -1
- package/dist/plugin-migrations.d.ts +15 -0
- package/dist/plugin-migrations.d.ts.map +1 -0
- package/dist/plugin-migrations.js +68 -0
- package/dist/plugin-migrations.js.map +1 -0
- package/dist/sandbox.d.ts +13 -11
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/sandbox.js +11 -9
- package/dist/sandbox.js.map +1 -1
- package/dist/style-isolation.d.ts +3 -3
- package/dist/style-isolation.js +6 -6
- package/dist/style-isolation.js.map +1 -1
- package/package.json +6 -5
package/dist/http.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// @oktis-works/plugin-runtime - api.http: cliente HTTP externo anti-SSRF
|
|
2
|
+
//
|
|
3
|
+
// Item 4 do plugin-parity-plan (+ Item 16 egress isolation). Camadas:
|
|
4
|
+
//
|
|
5
|
+
// 1. allowlist `httpPolicy.allowHosts` do manifesto (quando declarada);
|
|
6
|
+
// 2. apenas scheme http/https;
|
|
7
|
+
// 3. DNS pre-validation per HOP — EVERY resolved IP must be
|
|
8
|
+
// public (blocks RFC1918, loopback, link-local 169.254.169.254 =
|
|
9
|
+
// metadata de cloud, CGNAT, multicast, ULA/link-local IPv6);
|
|
10
|
+
// 4. DNS pinning: o dispatcher (undici Agent) conecta exatamente no IP
|
|
11
|
+
// validado (elimina a janela de DNS rebinding entre lookup e connect);
|
|
12
|
+
// 5. `manual` redirects with re-validation on every hop (max 3);
|
|
13
|
+
// 6. timeout por tentativa e teto de corpo;
|
|
14
|
+
// 7. retries only on network errors/5xx (GET/HEAD by default).
|
|
15
|
+
//
|
|
16
|
+
// Fail-closed: DNS that fails or resolves to a non-public IP NEVER lets the
|
|
17
|
+
// request through, even if the runtime fetch ignored the dispatcher (Bun).
|
|
18
|
+
import { promises as dnsPromises } from 'node:dns';
|
|
19
|
+
import { Agent } from 'undici';
|
|
20
|
+
import { PERMISSIONS } from '@oktis-works/plugin-sdk';
|
|
21
|
+
export const DEFAULT_TIMEOUT_MS = 10_000;
|
|
22
|
+
export const MAX_TIMEOUT_MS = 60_000;
|
|
23
|
+
export const DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
|
|
24
|
+
export const MAX_BODY_BYTES = 50 * 1024 * 1024;
|
|
25
|
+
export const MAX_REDIRECTS = 3;
|
|
26
|
+
/** Erro de api.http com flag de retentativa. */
|
|
27
|
+
export class HttpError extends Error {
|
|
28
|
+
retriable;
|
|
29
|
+
constructor(message, retriable) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.retriable = retriable;
|
|
32
|
+
this.name = 'HttpError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Is the IPv4 publicly routable? Fail-closed on any special/reserved range. */
|
|
36
|
+
export function isPublicIpv4(ip) {
|
|
37
|
+
const parts = ip.split('.');
|
|
38
|
+
if (parts.length !== 4)
|
|
39
|
+
return false;
|
|
40
|
+
const octets = parts.map((part) => Number(part));
|
|
41
|
+
if (octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255))
|
|
42
|
+
return false;
|
|
43
|
+
const a = octets[0] ?? 0;
|
|
44
|
+
const b = octets[1] ?? 0;
|
|
45
|
+
const c = octets[2] ?? 0;
|
|
46
|
+
if (a === 0)
|
|
47
|
+
return false; // 0.0.0.0/8 "este host"
|
|
48
|
+
if (a === 10)
|
|
49
|
+
return false; // RFC1918
|
|
50
|
+
if (a === 127)
|
|
51
|
+
return false; // loopback
|
|
52
|
+
if (a === 169 && b === 254)
|
|
53
|
+
return false; // link-local = metadata cloud (169.254.169.254)
|
|
54
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
55
|
+
return false; // RFC1918
|
|
56
|
+
if (a === 192 && b === 168)
|
|
57
|
+
return false; // RFC1918
|
|
58
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
59
|
+
return false; // CGNAT RFC6598
|
|
60
|
+
if (a === 192 && b === 0 && c === 0)
|
|
61
|
+
return false; // RFC6890 IETF
|
|
62
|
+
if (a === 198 && (b === 18 || b === 19))
|
|
63
|
+
return false; // benchmark RFC2544
|
|
64
|
+
if (a >= 224)
|
|
65
|
+
return false; // multicast (224/4) + broadcast/reservado (240/4)
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
/** Is the IPv6 publicly routable? Fail-closed: only 2000::/3 (global unicast). */
|
|
69
|
+
export function isPublicIpv6(ip) {
|
|
70
|
+
const bare = (ip.split('%')[0] ?? '').toLowerCase();
|
|
71
|
+
if (bare === '::' || bare === '::1')
|
|
72
|
+
return false;
|
|
73
|
+
// IPv4-mapped ::ffff:a.b.c.d → aplica as regras de IPv4
|
|
74
|
+
const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(bare);
|
|
75
|
+
if (mapped?.[1])
|
|
76
|
+
return isPublicIpv4(mapped[1]);
|
|
77
|
+
// leading '::' compression outside mapped → special/deprecated → blocked
|
|
78
|
+
if (bare.startsWith('::'))
|
|
79
|
+
return false;
|
|
80
|
+
const firstBlock = bare.split(':')[0] ?? '';
|
|
81
|
+
if (firstBlock.includes('.'))
|
|
82
|
+
return false; // v4-suffix not handled above
|
|
83
|
+
const hextet = Number.parseInt(firstBlock, 16);
|
|
84
|
+
if (Number.isNaN(hextet))
|
|
85
|
+
return false;
|
|
86
|
+
// 2000::/3 = the only assigned global unicast block
|
|
87
|
+
return hextet >= 0x2000 && hextet <= 0x3fff;
|
|
88
|
+
}
|
|
89
|
+
/** Any non-public IP in the list → false. */
|
|
90
|
+
export function isPublicIp(ip) {
|
|
91
|
+
return ip.includes(':') ? isPublicIpv6(ip) : isPublicIpv4(ip);
|
|
92
|
+
}
|
|
93
|
+
/** Picks the first public address (for pinning). Throws when none. */
|
|
94
|
+
export function selectPublicAddresses(addresses) {
|
|
95
|
+
const safe = addresses.find((entry) => isPublicIp(entry.address));
|
|
96
|
+
if (!safe) {
|
|
97
|
+
throw new HttpError(`host resolves only to non-public address(es): ${addresses.map((entry) => entry.address).join(', ')}`, false);
|
|
98
|
+
}
|
|
99
|
+
return safe;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Manifest allowlist: exact entry or `*.suffix.com` (subdomains — the apex
|
|
103
|
+
* needs its own entry). Empty/absent list = any host (advertising is still
|
|
104
|
+
* guaranteed by IP validation).
|
|
105
|
+
*/
|
|
106
|
+
export function isAllowedHost(hostname, allowHosts) {
|
|
107
|
+
if (!allowHosts || allowHosts.length === 0)
|
|
108
|
+
return true;
|
|
109
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
110
|
+
return allowHosts.some((entry) => {
|
|
111
|
+
const pattern = entry.trim().toLowerCase();
|
|
112
|
+
if (!pattern)
|
|
113
|
+
return false;
|
|
114
|
+
if (pattern.startsWith('*.')) {
|
|
115
|
+
const suffix = pattern.slice(1); // ".sufixo.com"
|
|
116
|
+
return host.endsWith(suffix) && host.length > suffix.length;
|
|
117
|
+
}
|
|
118
|
+
return host === pattern;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const defaultResolver = async (hostname) => {
|
|
122
|
+
const bare = hostname.replace(/^\[|\]$/g, '');
|
|
123
|
+
const addresses = await dnsPromises.lookup(bare, { all: true, verbatim: true });
|
|
124
|
+
return addresses.map((entry) => ({ address: entry.address, family: entry.family }));
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Lookup "puro" usado pelo undici Agent: resolve E valida no MESMO passo —
|
|
128
|
+
* the returned IP is exactly what the connection uses (real pinning, no
|
|
129
|
+
* de rebinding).
|
|
130
|
+
*/
|
|
131
|
+
export function createSafeLookup(resolve = defaultResolver) {
|
|
132
|
+
return (hostname, _options, callback) => {
|
|
133
|
+
resolve(hostname).then((addresses) => {
|
|
134
|
+
try {
|
|
135
|
+
const safe = selectPublicAddresses(addresses);
|
|
136
|
+
callback(null, safe.address, safe.family);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
callback(error);
|
|
140
|
+
}
|
|
141
|
+
}, (error) => {
|
|
142
|
+
callback(error);
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
let sharedAgent = null;
|
|
147
|
+
/** Dispatcher compartilhado com lookup validado (pinning). */
|
|
148
|
+
export function getSafeAgent() {
|
|
149
|
+
if (!sharedAgent) {
|
|
150
|
+
sharedAgent = new Agent({
|
|
151
|
+
connect: {
|
|
152
|
+
// Signature compatible with net.LookupFunction via a structural cast
|
|
153
|
+
lookup: createSafeLookup(),
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return sharedAgent;
|
|
158
|
+
}
|
|
159
|
+
/** Validates a target before touching the network (scheme + allowlist + public DNS). */
|
|
160
|
+
export async function validateTargetUrl(rawUrl, allowHosts) {
|
|
161
|
+
let url;
|
|
162
|
+
try {
|
|
163
|
+
url = new URL(rawUrl);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
throw new HttpError(`invalid URL: ${rawUrl}`, false);
|
|
167
|
+
}
|
|
168
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
169
|
+
throw new HttpError(`scheme not allowed: ${url.protocol}`, false);
|
|
170
|
+
}
|
|
171
|
+
if (!isAllowedHost(url.hostname, allowHosts)) {
|
|
172
|
+
throw new HttpError(`host "${url.hostname}" is not in httpPolicy.allowHosts`, false);
|
|
173
|
+
}
|
|
174
|
+
let addresses;
|
|
175
|
+
try {
|
|
176
|
+
addresses = await defaultResolver(url.hostname);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
throw new HttpError(`DNS resolution failed for ${url.hostname}: ${error instanceof Error ? error.message : String(error)}`, false);
|
|
180
|
+
}
|
|
181
|
+
if (addresses.length === 0) {
|
|
182
|
+
throw new HttpError(`no addresses for ${url.hostname}`, false);
|
|
183
|
+
}
|
|
184
|
+
// EVERY IP must be public (round-robin with one private IP is also blocked)
|
|
185
|
+
const unsafe = addresses.filter((entry) => !isPublicIp(entry.address));
|
|
186
|
+
if (unsafe.length > 0) {
|
|
187
|
+
throw new HttpError(`host ${url.hostname} resolves to non-public address(es): ${unsafe.map((entry) => entry.address).join(', ')}`, false);
|
|
188
|
+
}
|
|
189
|
+
return url;
|
|
190
|
+
}
|
|
191
|
+
async function readBodyCapped(response, maxBytes) {
|
|
192
|
+
if (!response.body)
|
|
193
|
+
return '';
|
|
194
|
+
const reader = response.body.getReader();
|
|
195
|
+
const chunks = [];
|
|
196
|
+
let total = 0;
|
|
197
|
+
try {
|
|
198
|
+
for (;;) {
|
|
199
|
+
const { done, value } = await reader.read();
|
|
200
|
+
if (done)
|
|
201
|
+
break;
|
|
202
|
+
if (value) {
|
|
203
|
+
total += value.byteLength;
|
|
204
|
+
if (total > maxBytes) {
|
|
205
|
+
await reader.cancel().catch(() => undefined);
|
|
206
|
+
throw new HttpError(`response body exceeds limit of ${maxBytes} bytes`, false);
|
|
207
|
+
}
|
|
208
|
+
chunks.push(value);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
reader.releaseLock();
|
|
214
|
+
}
|
|
215
|
+
const merged = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)));
|
|
216
|
+
return merged.toString('utf8');
|
|
217
|
+
}
|
|
218
|
+
async function readResponse(response, maxBytes) {
|
|
219
|
+
const body = await readBodyCapped(response, maxBytes);
|
|
220
|
+
const headers = {};
|
|
221
|
+
response.headers.forEach((value, key) => {
|
|
222
|
+
headers[key.toLowerCase()] = value;
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
status: response.status,
|
|
226
|
+
ok: response.ok,
|
|
227
|
+
headers,
|
|
228
|
+
body,
|
|
229
|
+
json() {
|
|
230
|
+
return JSON.parse(body);
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/** Executa a cadeia de redirects (cada hop revalidado) — 1 tentativa. */
|
|
235
|
+
async function runRedirects(rawUrl, allowHosts, config) {
|
|
236
|
+
let currentUrl = rawUrl;
|
|
237
|
+
let method = config.method;
|
|
238
|
+
let body = config.body;
|
|
239
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop += 1) {
|
|
240
|
+
const target = await validateTargetUrl(currentUrl, allowHosts);
|
|
241
|
+
const fetchInit = {
|
|
242
|
+
method,
|
|
243
|
+
headers: config.headers,
|
|
244
|
+
body: method === 'GET' || method === 'HEAD' ? undefined : body,
|
|
245
|
+
redirect: 'manual',
|
|
246
|
+
signal: AbortSignal.timeout(config.timeoutMs),
|
|
247
|
+
// Node/undici: conecta no IP validado (pinning). Runtimes que ignoram
|
|
248
|
+
// `dispatcher` (Bun) remain protected by the pre-validation above.
|
|
249
|
+
dispatcher: getSafeAgent(),
|
|
250
|
+
};
|
|
251
|
+
const response = await fetch(target.toString(), fetchInit);
|
|
252
|
+
if (response.status >= 300 && response.status < 400) {
|
|
253
|
+
const location = response.headers.get('location');
|
|
254
|
+
if (!location)
|
|
255
|
+
return readResponse(response, config.maxBytes);
|
|
256
|
+
if (hop === MAX_REDIRECTS) {
|
|
257
|
+
throw new HttpError(`too many redirects (max ${MAX_REDIRECTS})`, false);
|
|
258
|
+
}
|
|
259
|
+
// Libera o corpo do redirect sem consumir
|
|
260
|
+
await response.body?.cancel().catch(() => undefined);
|
|
261
|
+
currentUrl = new URL(location, target).toString();
|
|
262
|
+
if (response.status === 303 ||
|
|
263
|
+
((response.status === 301 || response.status === 302) && method === 'POST')) {
|
|
264
|
+
method = 'GET';
|
|
265
|
+
body = undefined;
|
|
266
|
+
}
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
return readResponse(response, config.maxBytes);
|
|
270
|
+
}
|
|
271
|
+
throw new HttpError(`too many redirects (max ${MAX_REDIRECTS})`, false);
|
|
272
|
+
}
|
|
273
|
+
export function createHttpApi(config) {
|
|
274
|
+
async function request(rawUrl, options = {}) {
|
|
275
|
+
config.requirePermission(PERMISSIONS.HTTP_OUTBOUND);
|
|
276
|
+
const method = (options.method ?? 'GET').toUpperCase();
|
|
277
|
+
const timeoutMs = Math.min(Math.max(options.timeoutMs ?? DEFAULT_TIMEOUT_MS, 1), MAX_TIMEOUT_MS);
|
|
278
|
+
const maxBytes = Math.min(Math.max(options.maxBytes ?? DEFAULT_MAX_BYTES, 1), MAX_BODY_BYTES);
|
|
279
|
+
const retries = Math.min(Math.max(Math.floor(options.retries ?? (method === 'GET' || method === 'HEAD' ? 2 : 0)), 0), 3);
|
|
280
|
+
const attemptConfig = {
|
|
281
|
+
method,
|
|
282
|
+
headers: options.headers,
|
|
283
|
+
body: options.body,
|
|
284
|
+
timeoutMs,
|
|
285
|
+
maxBytes,
|
|
286
|
+
};
|
|
287
|
+
let lastError;
|
|
288
|
+
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
|
289
|
+
try {
|
|
290
|
+
const response = await runRedirects(rawUrl, config.allowHosts, attemptConfig);
|
|
291
|
+
if (response.status >= 500 && attempt < retries) {
|
|
292
|
+
lastError = new HttpError(`upstream ${response.status}`, true);
|
|
293
|
+
await new Promise((resolve) => setTimeout(resolve, 250 * 2 ** attempt));
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
return response;
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
lastError = error;
|
|
300
|
+
const retriable = error instanceof HttpError ? error.retriable : true; // rede/timeout
|
|
301
|
+
if (!retriable || attempt >= retries)
|
|
302
|
+
break;
|
|
303
|
+
await new Promise((resolve) => setTimeout(resolve, 250 * 2 ** attempt));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
request,
|
|
310
|
+
get: (url, options) => request(url, { ...options, method: 'GET' }),
|
|
311
|
+
post: (url, body, options) => request(url, { ...options, method: 'POST', body }),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PluginManifest, PluginI18nApi } from '@oktis-works/plugin-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Fallback chain for a locale: `pt-BR` → [`pt-BR`, `pt`, `en`];
|
|
4
|
+
* `en` → [`en`]; `fr` → [`fr`, `en`]. Duplicates removed.
|
|
5
|
+
*/
|
|
6
|
+
export declare function i18nFallbackChain(locale: string): string[];
|
|
7
|
+
/** Replaces `{token}` placeholders with stringified params (missing → kept). */
|
|
8
|
+
export declare function i18nInterpolate(template: string, params?: Record<string, unknown>): string;
|
|
9
|
+
/**
|
|
10
|
+
* Loads manifest `locales[]` JSON files (flat key/value) into the plugin's
|
|
11
|
+
* i18n store. The filename defines the locale (`locales/pt-BR.json` →
|
|
12
|
+
* `pt-BR`). Malformed/out-of-tree files are skipped — never breaks boot.
|
|
13
|
+
* Returns the number of successfully loaded files.
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadPluginLocales(pluginDir: string, manifest: PluginManifest, i18n: PluginI18nApi): Promise<number>;
|
|
16
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7E;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAU1D;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAM1F;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,MAAM,CAAC,CAwBjB"}
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// @oktis-works/plugin-runtime - Plugin i18n helpers (Item 9 do plugin-parity-plan)
|
|
2
|
+
//
|
|
3
|
+
// Server-side translations for plugins:
|
|
4
|
+
// - `api.i18n.load(locale, bundle)` merges flat { key: string } bundles;
|
|
5
|
+
// - `api.i18n.translate(key, params?, locale?)` resolves through the
|
|
6
|
+
// fallback chain (requested locale → base language → 'en' → key) with
|
|
7
|
+
// {param} interpolation;
|
|
8
|
+
// - manifest `locales[]` JSON files (flat key/value) are loaded at boot.
|
|
9
|
+
import { readFile } from 'node:fs/promises';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
/**
|
|
12
|
+
* Fallback chain for a locale: `pt-BR` → [`pt-BR`, `pt`, `en`];
|
|
13
|
+
* `en` → [`en`]; `fr` → [`fr`, `en`]. Duplicates removed.
|
|
14
|
+
*/
|
|
15
|
+
export function i18nFallbackChain(locale) {
|
|
16
|
+
const chain = [];
|
|
17
|
+
const push = (value) => {
|
|
18
|
+
if (value && !chain.includes(value))
|
|
19
|
+
chain.push(value);
|
|
20
|
+
};
|
|
21
|
+
push(locale);
|
|
22
|
+
const base = locale.split('-')[0];
|
|
23
|
+
if (base)
|
|
24
|
+
push(base);
|
|
25
|
+
push('en');
|
|
26
|
+
return chain;
|
|
27
|
+
}
|
|
28
|
+
/** Replaces `{token}` placeholders with stringified params (missing → kept). */
|
|
29
|
+
export function i18nInterpolate(template, params) {
|
|
30
|
+
if (!params || typeof params !== 'object')
|
|
31
|
+
return template;
|
|
32
|
+
return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (match, token) => {
|
|
33
|
+
const value = params[token];
|
|
34
|
+
return value === undefined || value === null ? match : String(value);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Loads manifest `locales[]` JSON files (flat key/value) into the plugin's
|
|
39
|
+
* i18n store. The filename defines the locale (`locales/pt-BR.json` →
|
|
40
|
+
* `pt-BR`). Malformed/out-of-tree files are skipped — never breaks boot.
|
|
41
|
+
* Returns the number of successfully loaded files.
|
|
42
|
+
*/
|
|
43
|
+
export async function loadPluginLocales(pluginDir, manifest, i18n) {
|
|
44
|
+
const files = manifest.locales;
|
|
45
|
+
if (!Array.isArray(files) || files.length === 0)
|
|
46
|
+
return 0;
|
|
47
|
+
let loaded = 0;
|
|
48
|
+
for (const relative of files) {
|
|
49
|
+
if (typeof relative !== 'string' || relative.includes('..') || relative.startsWith('/'))
|
|
50
|
+
continue;
|
|
51
|
+
try {
|
|
52
|
+
const raw = await readFile(join(pluginDir, relative), 'utf8');
|
|
53
|
+
const parsed = JSON.parse(raw);
|
|
54
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
55
|
+
continue;
|
|
56
|
+
const locale = relative.replace(/\.json$/, '').split('/').pop() ?? '';
|
|
57
|
+
if (!locale)
|
|
58
|
+
continue;
|
|
59
|
+
const bundle = {};
|
|
60
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
61
|
+
if (typeof value === 'string')
|
|
62
|
+
bundle[key] = value;
|
|
63
|
+
}
|
|
64
|
+
i18n.load(locale, bundle);
|
|
65
|
+
loaded += 1;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// malformed locale file — skip (translation falls back to the key)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return loaded;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=i18n.js.map
|
package/dist/i18n.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { readPluginManifest, normalizePluginManifest } from './manifest.js';
|
|
|
6
6
|
export type { ManifestSource, ReadManifestResult } from './manifest.js';
|
|
7
7
|
export { HookRegistry, HookRegistryStore, getHookRegistry, getHookRegistryStore, setHookRegistry, setHookRegistryStore, setHookTenantResolver, getCurrentHookTenantId, } from './hook-registry.js';
|
|
8
8
|
export type { AddHookOptions, AuditLogEntry, HookCallback } from './hook-registry.js';
|
|
9
|
-
export { bootstrapPlugins, resetLoadedPlugins, listLoadedScopes, unloadScope, unloadTenant, getLoadedPluginRoutes } from './bootstrap.js';
|
|
9
|
+
export { bootstrapPlugins, resetLoadedPlugins, listLoadedScopes, unloadScope, unloadTenant, getLoadedPluginRoutes, getLoadedPluginJobHandlers } from './bootstrap.js';
|
|
10
10
|
export type { BootstrapPluginsOptions, BootstrapPluginsSql, PluginBootstrapResult, } from './bootstrap.js';
|
|
11
11
|
export { createPluginApi, type PluginAPI, type PluginContext, type RouteDefinition, type TenantInfo, type UserInfo, type PluginApiDependencies, type PluginApiInternal, } from './plugin-api.js';
|
|
12
12
|
export type { PluginFieldTypeManifest } from '@oktis-works/plugin-sdk';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AACtK,YAAY,EACV,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,eAAe,EACf,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,EACL,cAAc,EACd,uBAAuB,EACvB,mBAAmB,EACnB,aAAa,GACd,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { PluginSandbox } from './sandbox.js';
|
|
|
2
2
|
export { PluginLoader } from './loader.js';
|
|
3
3
|
export { readPluginManifest, normalizePluginManifest } from './manifest.js';
|
|
4
4
|
export { HookRegistry, HookRegistryStore, getHookRegistry, getHookRegistryStore, setHookRegistry, setHookRegistryStore, setHookTenantResolver, getCurrentHookTenantId, } from './hook-registry.js';
|
|
5
|
-
export { bootstrapPlugins, resetLoadedPlugins, listLoadedScopes, unloadScope, unloadTenant, getLoadedPluginRoutes } from './bootstrap.js';
|
|
5
|
+
export { bootstrapPlugins, resetLoadedPlugins, listLoadedScopes, unloadScope, unloadTenant, getLoadedPluginRoutes, getLoadedPluginJobHandlers } from './bootstrap.js';
|
|
6
6
|
export { createPluginApi, } from './plugin-api.js';
|
|
7
7
|
export { scopePluginCss, getPluginScopeAttribute, wrapWithPluginScope, lintPluginCss, } from './style-isolation.js';
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAE5E,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAE5E,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAMtK,OAAO,EACL,eAAe,GAQhB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,cAAc,EACd,uBAAuB,EACvB,mBAAmB,EACnB,aAAa,GACd,MAAM,sBAAsB,CAAC"}
|
package/dist/loader.d.ts
CHANGED
|
@@ -6,10 +6,18 @@ export interface DiscoveredPlugin {
|
|
|
6
6
|
}
|
|
7
7
|
export declare class PluginLoader {
|
|
8
8
|
private pluginsDir;
|
|
9
|
+
private dependencyFailures;
|
|
9
10
|
constructor(pluginsDir: string);
|
|
10
11
|
discoverPlugins(): Promise<DiscoveredPlugin[]>;
|
|
11
12
|
loadPlugin(manifest: PluginManifest, pluginPath: string): Promise<LoadedPlugin>;
|
|
12
13
|
resolveDependencies(plugins: DiscoveredPlugin[]): DiscoveredPlugin[][];
|
|
14
|
+
getDependencyFailures(): Array<{
|
|
15
|
+
name: string;
|
|
16
|
+
error: string;
|
|
17
|
+
}>;
|
|
18
|
+
private propagateDependencyFailures;
|
|
19
|
+
private propagateBlockedDependencies;
|
|
20
|
+
private propagateDependencyFailuresOnce;
|
|
13
21
|
topologicalSort(plugins: DiscoveredPlugin[], pluginMap: Map<string, DiscoveredPlugin>): string[];
|
|
14
22
|
private visit;
|
|
15
23
|
private groupByLevel;
|
package/dist/loader.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAS;
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,kBAAkB,CAA6B;gBAE3C,UAAU,EAAE,MAAM;IAIxB,eAAe,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAgB9C,UAAU,CAAC,QAAQ,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAyBrF,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,EAAE;IAuBtE,qBAAqB,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAI/D,OAAO,CAAC,2BAA2B;IAyBnC,OAAO,CAAC,4BAA4B;IAmBpC,OAAO,CAAC,+BAA+B;IAYvC,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,MAAM,EAAE;IAYhG,OAAO,CAAC,KAAK;IAyBb,OAAO,CAAC,YAAY;IA2BpB,OAAO,CAAC,kBAAkB;YAYZ,YAAY;CAc3B"}
|
package/dist/loader.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { readdir } from 'node:fs/promises';
|
|
2
|
-
import { join, resolve } from 'node:path';
|
|
1
|
+
import { readdir, realpath } from 'node:fs/promises';
|
|
2
|
+
import { join, relative, resolve } from 'node:path';
|
|
3
3
|
import { satisfies, validRange } from 'semver';
|
|
4
4
|
import { CMS_VERSION } from '@oktis-works/validation';
|
|
5
5
|
import { readPluginManifest } from './manifest.js';
|
|
6
6
|
export class PluginLoader {
|
|
7
7
|
pluginsDir;
|
|
8
|
+
dependencyFailures = new Map();
|
|
8
9
|
constructor(pluginsDir) {
|
|
9
10
|
this.pluginsDir = resolve(pluginsDir);
|
|
10
11
|
}
|
|
@@ -23,13 +24,15 @@ export class PluginLoader {
|
|
|
23
24
|
return plugins;
|
|
24
25
|
}
|
|
25
26
|
async loadPlugin(manifest, pluginPath) {
|
|
26
|
-
// RULE-semver-compatibility:
|
|
27
|
+
// RULE-semver-compatibility: the compatibility.okcms range must satisfy the CMS version
|
|
27
28
|
const compat = manifest.compatibility?.okcms;
|
|
28
29
|
if (typeof compat === 'string' && validRange(compat) && !satisfies(CMS_VERSION, compat)) {
|
|
29
|
-
throw new Error(`Plugin "${manifest.name}"
|
|
30
|
+
throw new Error(`Plugin "${manifest.name}" requires OkCMS ${compat}, but the current version is ${CMS_VERSION}`);
|
|
30
31
|
}
|
|
31
|
-
const entryPath = resolve(join(pluginPath, manifest.main));
|
|
32
|
-
|
|
32
|
+
const entryPath = await realpath(resolve(join(pluginPath, manifest.main)));
|
|
33
|
+
const pluginRoot = await realpath(this.pluginsDir);
|
|
34
|
+
const relativeEntry = relative(pluginRoot, entryPath);
|
|
35
|
+
if (relativeEntry.startsWith('..') || relativeEntry === '..' || relativeEntry.includes('\0')) {
|
|
33
36
|
throw new Error(`Plugin entry point escapes plugins directory: ${manifest.main}`);
|
|
34
37
|
}
|
|
35
38
|
const mod = await import(entryPath);
|
|
@@ -42,16 +45,94 @@ export class PluginLoader {
|
|
|
42
45
|
};
|
|
43
46
|
}
|
|
44
47
|
resolveDependencies(plugins) {
|
|
48
|
+
this.dependencyFailures.clear();
|
|
45
49
|
const pluginMap = new Map(plugins.map(p => [p.manifest.name, p]));
|
|
46
|
-
const
|
|
50
|
+
for (const plugin of plugins) {
|
|
51
|
+
for (const dependency of plugin.manifest.dependencies ?? []) {
|
|
52
|
+
const target = pluginMap.get(dependency.name);
|
|
53
|
+
if (!target) {
|
|
54
|
+
if (dependency.optional)
|
|
55
|
+
continue;
|
|
56
|
+
this.dependencyFailures.set(plugin.manifest.name, `Plugin "${plugin.manifest.name}" requires missing dependency "${dependency.name}"`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const range = dependency.versionRange ?? dependency.version;
|
|
60
|
+
if (range && (!validRange(range) || !satisfies(target.manifest.version, range))) {
|
|
61
|
+
this.dependencyFailures.set(plugin.manifest.name, `Plugin "${plugin.manifest.name}" requires ${dependency.name} ${range}, found ${target.manifest.version}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
this.propagateDependencyFailures(plugins, pluginMap);
|
|
66
|
+
const eligible = plugins.filter((plugin) => !this.dependencyFailures.has(plugin.manifest.name));
|
|
67
|
+
const sorted = this.topologicalSort(eligible, new Map(eligible.map((plugin) => [plugin.manifest.name, plugin])));
|
|
47
68
|
return this.groupByLevel(sorted, pluginMap);
|
|
48
69
|
}
|
|
70
|
+
getDependencyFailures() {
|
|
71
|
+
return [...this.dependencyFailures.entries()].map(([name, error]) => ({ name, error }));
|
|
72
|
+
}
|
|
73
|
+
propagateDependencyFailures(plugins, pluginMap) {
|
|
74
|
+
this.propagateBlockedDependencies(plugins);
|
|
75
|
+
const state = new Map();
|
|
76
|
+
const stack = [];
|
|
77
|
+
const visit = (name) => {
|
|
78
|
+
if (this.dependencyFailures.has(name))
|
|
79
|
+
return;
|
|
80
|
+
if (state.get(name) === 'visited')
|
|
81
|
+
return;
|
|
82
|
+
if (state.get(name) === 'visiting') {
|
|
83
|
+
const start = stack.indexOf(name);
|
|
84
|
+
for (const cycleName of stack.slice(start)) {
|
|
85
|
+
this.dependencyFailures.set(cycleName, `Circular dependency detected: ${cycleName}`);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
state.set(name, 'visiting');
|
|
90
|
+
stack.push(name);
|
|
91
|
+
for (const dependency of pluginMap.get(name)?.manifest.dependencies ?? [])
|
|
92
|
+
visit(dependency.name);
|
|
93
|
+
stack.pop();
|
|
94
|
+
state.set(name, 'visited');
|
|
95
|
+
};
|
|
96
|
+
for (const plugin of plugins)
|
|
97
|
+
visit(plugin.manifest.name);
|
|
98
|
+
this.propagateBlockedDependencies(plugins);
|
|
99
|
+
}
|
|
100
|
+
propagateBlockedDependencies(plugins) {
|
|
101
|
+
let changed = true;
|
|
102
|
+
while (changed) {
|
|
103
|
+
changed = false;
|
|
104
|
+
for (const plugin of plugins) {
|
|
105
|
+
if (this.dependencyFailures.has(plugin.manifest.name))
|
|
106
|
+
continue;
|
|
107
|
+
const dependency = (plugin.manifest.dependencies ?? []).find((candidate) => {
|
|
108
|
+
return !candidate.optional && this.dependencyFailures.has(candidate.name);
|
|
109
|
+
});
|
|
110
|
+
if (!dependency)
|
|
111
|
+
continue;
|
|
112
|
+
this.dependencyFailures.set(plugin.manifest.name, `Plugin "${plugin.manifest.name}" is blocked by failed dependency "${dependency.name}"`);
|
|
113
|
+
changed = true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
propagateDependencyFailuresOnce(plugins) {
|
|
118
|
+
for (const plugin of plugins) {
|
|
119
|
+
if (this.dependencyFailures.has(plugin.manifest.name))
|
|
120
|
+
continue;
|
|
121
|
+
const dependency = (plugin.manifest.dependencies ?? []).find((candidate) => {
|
|
122
|
+
return !candidate.optional && this.dependencyFailures.has(candidate.name);
|
|
123
|
+
});
|
|
124
|
+
if (dependency) {
|
|
125
|
+
this.dependencyFailures.set(plugin.manifest.name, `Plugin "${plugin.manifest.name}" is blocked by failed dependency "${dependency.name}"`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
49
129
|
topologicalSort(plugins, pluginMap) {
|
|
50
130
|
const visited = new Set();
|
|
51
131
|
const visiting = new Set();
|
|
52
132
|
const result = [];
|
|
53
133
|
for (const plugin of plugins) {
|
|
54
|
-
this.
|
|
134
|
+
if (!this.dependencyFailures.has(plugin.manifest.name))
|
|
135
|
+
this.visit(plugin.manifest.name, pluginMap, visited, visiting, result);
|
|
55
136
|
}
|
|
56
137
|
return result;
|
|
57
138
|
}
|
|
@@ -109,10 +190,17 @@ export class PluginLoader {
|
|
|
109
190
|
return result.length > 0 ? result : [0];
|
|
110
191
|
}
|
|
111
192
|
async loadManifest(pluginPath) {
|
|
112
|
-
//
|
|
193
|
+
// Single source (WP6): the canonical `manifest.json` wins over `package.json` (npm).
|
|
113
194
|
// Antes a ordem era inversa — um plugin com ambos os arquivos era lido de
|
|
114
195
|
// um jeito no loader e de outro no sync/CLI.
|
|
115
|
-
|
|
196
|
+
// Runtime discovery is intentionally lenient: malformed declarations are
|
|
197
|
+
// allowed to reach bootstrap, where the plugin is isolated as a failed
|
|
198
|
+
// load instead of silently disappearing from runtime state. Installation
|
|
199
|
+
// and synchronization use strict validation separately.
|
|
200
|
+
const { manifest, issues } = await readPluginManifest(pluginPath);
|
|
201
|
+
if (!manifest && issues.length > 0) {
|
|
202
|
+
console.warn(`[plugins] rejected manifest in ${pluginPath}: ${issues.join('; ')}`);
|
|
203
|
+
}
|
|
116
204
|
return manifest;
|
|
117
205
|
}
|
|
118
206
|
}
|