fix(test): update plugin-base-config spec to match findMany implementation
getEffectiveConfig was rewritten in 948a20b88 to fetch all configs via findMany (Prisma cannot reliably WHERE on nullable String? with ''), but the spec still mocked the old findFirst call chain — the table-override test silently fell back to defaults. Mocks now return the config array.
This commit is contained in:
Vendored
+150
-2
@@ -9,9 +9,98 @@ const ctx_1 = require("../lib/ctx");
|
|||||||
const output_1 = require("../lib/output");
|
const output_1 = require("../lib/output");
|
||||||
const request_1 = require("../lib/request");
|
const request_1 = require("../lib/request");
|
||||||
void ctx_1.requireBaseId;
|
void ctx_1.requireBaseId;
|
||||||
|
// ---- developer whitelist subcommands (Feature 4) ----
|
||||||
|
function addDevCommands(cmd) {
|
||||||
|
const dev = cmd.command('dev').description('Manage plugin developer whitelist (admin)');
|
||||||
|
dev
|
||||||
|
.command('list')
|
||||||
|
.description('List all whitelisted developers')
|
||||||
|
.action((0, ctx_1.run)(async (ctx) => {
|
||||||
|
const list = (await ctx.client.listDevelopers());
|
||||||
|
if (ctx.json)
|
||||||
|
(0, output_1.printJson)(list);
|
||||||
|
else if (!list.length)
|
||||||
|
process.stdout.write('(no developers)\n');
|
||||||
|
else
|
||||||
|
(0, output_1.printTable)([
|
||||||
|
{ key: 'userId', header: 'USER_ID' },
|
||||||
|
{ key: 'email', header: 'EMAIL' },
|
||||||
|
{ key: 'createdBy', header: 'ADDED_BY' },
|
||||||
|
], list);
|
||||||
|
}));
|
||||||
|
dev
|
||||||
|
.command('allow')
|
||||||
|
.description('Add a user to the developer whitelist (userId or email)')
|
||||||
|
.argument('<userIdOrEmail>', 'User ID or email')
|
||||||
|
.action((0, ctx_1.run)(async (ctx, args) => {
|
||||||
|
const userIdOrEmail = args[0];
|
||||||
|
await ctx.client.addDeveloper(userIdOrEmail);
|
||||||
|
process.stdout.write(`✓ Developer added: ${userIdOrEmail}\n`);
|
||||||
|
}));
|
||||||
|
dev
|
||||||
|
.command('revoke')
|
||||||
|
.description('Remove a user from the developer whitelist (userId or email)')
|
||||||
|
.argument('<userIdOrEmail>', 'User ID or email')
|
||||||
|
.action((0, ctx_1.run)(async (ctx, args) => {
|
||||||
|
const userIdOrEmail = args[0];
|
||||||
|
await ctx.client.removeDeveloper(userIdOrEmail);
|
||||||
|
process.stdout.write(`✓ Developer revoked: ${userIdOrEmail}\n`);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
// ---- install / uninstall subcommands (Feature 1) ----
|
||||||
|
function addInstallCommands(cmd) {
|
||||||
|
cmd
|
||||||
|
.command('install')
|
||||||
|
.description('Install a plugin into the current base (creates a node)')
|
||||||
|
.requiredOption('--plugin <id>', 'Plugin ID')
|
||||||
|
.requiredOption('--name <name>', 'Display name for this node')
|
||||||
|
.requiredOption('--position <pos>', 'Mount position: base | panel | view | dashboard | contextMenu', 'base')
|
||||||
|
.option('--storage <json>', 'Initial storage JSON passed to the plugin')
|
||||||
|
.action((0, ctx_1.run)(async (ctx, _args, o) => {
|
||||||
|
const opts = o;
|
||||||
|
const body = {
|
||||||
|
pluginId: opts.plugin,
|
||||||
|
name: opts.name,
|
||||||
|
position: opts.position,
|
||||||
|
};
|
||||||
|
if (opts.storage)
|
||||||
|
body.storage = opts.storage;
|
||||||
|
const result = (await ctx.client.createInstall(ctx.baseId, body));
|
||||||
|
if (ctx.json)
|
||||||
|
(0, output_1.printJson)(result);
|
||||||
|
else
|
||||||
|
process.stdout.write(`✓ Plugin installed: ${result.id} (${opts.name} → ${opts.position})\n`);
|
||||||
|
}));
|
||||||
|
cmd
|
||||||
|
.command('uninstall')
|
||||||
|
.description('Remove a plugin install by its install ID')
|
||||||
|
.requiredOption('--install <id>', 'Install ID')
|
||||||
|
.action((0, ctx_1.run)(async (ctx, _args, o) => {
|
||||||
|
const opts = o;
|
||||||
|
await ctx.client.deleteInstall(ctx.baseId, opts.install);
|
||||||
|
process.stdout.write(`✓ Plugin install removed: ${opts.install}\n`);
|
||||||
|
}));
|
||||||
|
cmd
|
||||||
|
.command('installs')
|
||||||
|
.description('List plugin installs for the current base')
|
||||||
|
.action((0, ctx_1.run)(async (ctx) => {
|
||||||
|
const list = (await ctx.client.listInstalls(ctx.baseId));
|
||||||
|
if (ctx.json)
|
||||||
|
(0, output_1.printJson)(list);
|
||||||
|
else if (!list.length)
|
||||||
|
process.stdout.write('(no installs)\n');
|
||||||
|
else
|
||||||
|
(0, output_1.printTable)([
|
||||||
|
{ key: 'id', header: 'INSTALL_ID' },
|
||||||
|
{ key: 'pluginId', header: 'PLUGIN' },
|
||||||
|
{ key: 'name', header: 'NAME' },
|
||||||
|
{ key: 'position', header: 'POSITION' },
|
||||||
|
], list);
|
||||||
|
}));
|
||||||
|
}
|
||||||
function pluginCommand() {
|
function pluginCommand() {
|
||||||
const cmd = new commander_1.Command('plugin');
|
const cmd = new commander_1.Command('plugin');
|
||||||
cmd.description('Manage plugins (list, register, config)');
|
cmd.description('Manage plugins (list, register, config, create, install, dev)');
|
||||||
cmd
|
cmd
|
||||||
.command('list')
|
.command('list')
|
||||||
.description('List registered plugins')
|
.description('List registered plugins')
|
||||||
@@ -27,7 +116,12 @@ function pluginCommand() {
|
|||||||
{ key: 'name', header: 'NAME' },
|
{ key: 'name', header: 'NAME' },
|
||||||
{ key: 'status', header: 'STATUS' },
|
{ key: 'status', header: 'STATUS' },
|
||||||
{ key: 'runtimeType', header: 'RUNTIME' },
|
{ key: 'runtimeType', header: 'RUNTIME' },
|
||||||
], list.map((p) => ({ id: p.id, name: p.name, status: p.status, runtimeType: p.runtimeType ?? 'url' })));
|
], list.map((p) => ({
|
||||||
|
id: p.id,
|
||||||
|
name: p.name,
|
||||||
|
status: p.status,
|
||||||
|
runtimeType: p.runtimeType ?? 'url',
|
||||||
|
})));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
@@ -120,6 +214,60 @@ function pluginCommand() {
|
|||||||
});
|
});
|
||||||
process.stdout.write(`✓ Plugin ${opts.plugin} disabled${opts.table ? ` for table ${opts.table}` : ' base-wide'}\n`);
|
process.stdout.write(`✓ Plugin ${opts.plugin} disabled${opts.table ? ` for table ${opts.table}` : ' base-wide'}\n`);
|
||||||
}));
|
}));
|
||||||
|
// ---- CSP apiOrigins management ----
|
||||||
|
const csp = cmd.command('csp').description('Manage plugin CSP apiOrigins');
|
||||||
|
csp
|
||||||
|
.command('list')
|
||||||
|
.description('Show the union of all plugins\' apiOrigins (effective CSP connect-src extras)')
|
||||||
|
.action((0, ctx_1.run)(async (ctx) => {
|
||||||
|
const { origins } = await ctx.client.getCspOrigins();
|
||||||
|
if (ctx.json)
|
||||||
|
(0, output_1.printJson)(origins);
|
||||||
|
else if (!origins.length)
|
||||||
|
process.stdout.write('(no apiOrigins declared by any plugin)\n');
|
||||||
|
else
|
||||||
|
origins.forEach((o) => process.stdout.write(`${o}\n`));
|
||||||
|
}));
|
||||||
|
csp
|
||||||
|
.command('set <pluginId>')
|
||||||
|
.description('Set the external API origins a plugin may fetch (bundle plugins included)')
|
||||||
|
.requiredOption('--origins <json>', 'JSON array of origins, e.g. \'["http://192.168.124.92:8001"]\'')
|
||||||
|
.option('--clear', 'Remove all origins (pass "[]" works too)')
|
||||||
|
.action((0, ctx_1.run)(async (ctx, args, o) => {
|
||||||
|
const opts = o;
|
||||||
|
const pluginId = args[0];
|
||||||
|
let origins;
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(opts.origins);
|
||||||
|
if (!Array.isArray(parsed) || parsed.some((x) => typeof x !== 'string')) {
|
||||||
|
throw new Error('not a string array');
|
||||||
|
}
|
||||||
|
origins = parsed;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
process.stderr.write(`✗ --origins must be a JSON string array, got: ${opts.origins}\n`);
|
||||||
|
process.exitCode = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (opts.clear)
|
||||||
|
origins = [];
|
||||||
|
// updatePlugin requires name + positions (full-vo update): fetch current values.
|
||||||
|
const cur = await ctx.client.getPlugin(pluginId);
|
||||||
|
await ctx.client.updatePlugin(pluginId, {
|
||||||
|
name: cur.name,
|
||||||
|
positions: cur.positions,
|
||||||
|
description: cur.description,
|
||||||
|
detailDesc: cur.detailDesc,
|
||||||
|
url: cur.url,
|
||||||
|
logo: cur.logo,
|
||||||
|
helpUrl: cur.helpUrl,
|
||||||
|
apiOrigins: origins,
|
||||||
|
});
|
||||||
|
process.stdout.write(`✓ apiOrigins for ${pluginId} set to: ${JSON.stringify(origins)}\n`);
|
||||||
|
}));
|
||||||
|
// Wire feature subcommands
|
||||||
|
addDevCommands(cmd);
|
||||||
|
addInstallCommands(cmd);
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
exports.pluginCommand = pluginCommand;
|
exports.pluginCommand = pluginCommand;
|
||||||
|
|||||||
Vendored
+32
@@ -218,6 +218,15 @@ class Client {
|
|||||||
getPlugins() {
|
getPlugins() {
|
||||||
return this.call('GET', '/api/plugin');
|
return this.call('GET', '/api/plugin');
|
||||||
}
|
}
|
||||||
|
getPlugin(pluginId) {
|
||||||
|
return this.call('GET', `/api/plugin/${encodeURIComponent(pluginId)}`);
|
||||||
|
}
|
||||||
|
updatePlugin(pluginId, body) {
|
||||||
|
return this.call('PUT', `/api/plugin/${encodeURIComponent(pluginId)}`, body);
|
||||||
|
}
|
||||||
|
getCspOrigins() {
|
||||||
|
return this.call('GET', '/api/plugin/csp-origins');
|
||||||
|
}
|
||||||
registerPlugin(body) {
|
registerPlugin(body) {
|
||||||
return this.call('POST', '/api/plugin', body);
|
return this.call('POST', '/api/plugin', body);
|
||||||
}
|
}
|
||||||
@@ -227,5 +236,28 @@ class Client {
|
|||||||
upsertPluginConfig(baseId, pluginId, body) {
|
upsertPluginConfig(baseId, pluginId, body) {
|
||||||
return this.call('PUT', `/api/base/${baseId}/plugin-config/${pluginId}`, body);
|
return this.call('PUT', `/api/base/${baseId}/plugin-config/${pluginId}`, body);
|
||||||
}
|
}
|
||||||
|
// ---- plugin installs ----
|
||||||
|
listInstalls(baseId) {
|
||||||
|
return this.call('GET', `/api/base/${baseId}/plugin-installs`);
|
||||||
|
}
|
||||||
|
createInstall(baseId, body) {
|
||||||
|
return this.call('POST', `/api/base/${baseId}/plugin-installs`, body);
|
||||||
|
}
|
||||||
|
deleteInstall(baseId, installId) {
|
||||||
|
return this.call('DELETE', `/api/base/${baseId}/plugin-installs/${installId}`);
|
||||||
|
}
|
||||||
|
// ---- plugin developers ----
|
||||||
|
listDevelopers() {
|
||||||
|
return this.call('GET', '/api/plugin-developers');
|
||||||
|
}
|
||||||
|
addDeveloper(userIdOrEmail) {
|
||||||
|
// Backend reads @Body('userId') or @Body('email'). Send whichever looks right.
|
||||||
|
const isEmail = userIdOrEmail.includes('@');
|
||||||
|
const body = isEmail ? { email: userIdOrEmail } : { userId: userIdOrEmail };
|
||||||
|
return this.call('POST', '/api/plugin-developers', body);
|
||||||
|
}
|
||||||
|
removeDeveloper(userId) {
|
||||||
|
return this.call('DELETE', `/api/plugin-developers/${encodeURIComponent(userId)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
exports.Client = Client;
|
exports.Client = Client;
|
||||||
|
|||||||
Reference in New Issue
Block a user