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.
274 lines
11 KiB
JavaScript
274 lines
11 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.pluginCommand = void 0;
|
|
/**
|
|
* `inkreach-teable plugin` — plugin management commands.
|
|
*/
|
|
const commander_1 = require("commander");
|
|
const ctx_1 = require("../lib/ctx");
|
|
const output_1 = require("../lib/output");
|
|
const request_1 = require("../lib/request");
|
|
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() {
|
|
const cmd = new commander_1.Command('plugin');
|
|
cmd.description('Manage plugins (list, register, config, create, install, dev)');
|
|
cmd
|
|
.command('list')
|
|
.description('List registered plugins')
|
|
.action((0, ctx_1.run)(async (ctx) => {
|
|
try {
|
|
const list = (await ctx.client.getPlugins());
|
|
if (ctx.json) {
|
|
(0, output_1.printJson)(list);
|
|
}
|
|
else {
|
|
(0, output_1.printTable)([
|
|
{ key: 'id', header: 'ID' },
|
|
{ key: 'name', header: 'NAME' },
|
|
{ key: 'status', header: 'STATUS' },
|
|
{ key: 'runtimeType', header: 'RUNTIME' },
|
|
], list.map((p) => ({
|
|
id: p.id,
|
|
name: p.name,
|
|
status: p.status,
|
|
runtimeType: p.runtimeType ?? 'url',
|
|
})));
|
|
}
|
|
}
|
|
catch (e) {
|
|
if (e instanceof request_1.ApiError && e.status === 401) {
|
|
process.stderr.write('Unauthorized — log in via "auth login" or provide --token\n');
|
|
process.exit(1);
|
|
}
|
|
throw e;
|
|
}
|
|
}));
|
|
cmd
|
|
.command('register')
|
|
.description('Register a plugin by URL')
|
|
.requiredOption('--url <url>', 'Plugin URL')
|
|
.requiredOption('--name <name>', 'Plugin name')
|
|
.option('--description <desc>', 'Plugin description')
|
|
.option('--logo <url>', 'Logo URL')
|
|
.option('--positions <json>', 'Positions array, e.g. \'["panel"]\'', '["panel"]')
|
|
.action((0, ctx_1.run)(async (ctx, _args, o) => {
|
|
const opts = o;
|
|
const positions = JSON.parse(opts.positions);
|
|
const created = await ctx.client.registerPlugin({
|
|
name: opts.name,
|
|
url: opts.url,
|
|
description: opts.description,
|
|
logo: opts.logo ?? '',
|
|
positions,
|
|
status: 'published',
|
|
});
|
|
if (ctx.json)
|
|
(0, output_1.printJson)(created);
|
|
else
|
|
process.stdout.write(`✓ Plugin registered: ${created.id}\n`);
|
|
}));
|
|
const config = cmd.command('config').description('Manage per-table plugin enablement');
|
|
config
|
|
.command('list')
|
|
.description('List plugin configs for the base')
|
|
.action((0, ctx_1.run)(async (ctx) => {
|
|
const list = await ctx.client.listPluginConfigs(ctx.baseId);
|
|
if (ctx.json)
|
|
(0, output_1.printJson)(list);
|
|
else {
|
|
(0, output_1.printTable)([
|
|
{ key: 'pluginId', header: 'PLUGIN' },
|
|
{ key: 'tableId', header: 'TABLE' },
|
|
{ key: 'enabled', header: 'ON' },
|
|
{ key: 'runtimeForm', header: 'FORM' },
|
|
{ key: 'writeEnabled', header: 'WRITE' },
|
|
], list.map((r) => ({
|
|
pluginId: r.pluginId,
|
|
tableId: r.tableId || '-',
|
|
enabled: r.enabled,
|
|
runtimeForm: r.runtimeForm,
|
|
writeEnabled: r.writeEnabled,
|
|
})));
|
|
}
|
|
}));
|
|
config
|
|
.command('enable')
|
|
.description('Enable a plugin for a table (or base-wide if no --table)')
|
|
.requiredOption('--plugin <id>', 'Plugin ID')
|
|
.option('--table <id>', 'Table ID (omit for base-wide)')
|
|
.option('--write', 'Allow write-back')
|
|
.action((0, ctx_1.run)(async (ctx, _args, o) => {
|
|
const opts = o;
|
|
const result = await ctx.client.upsertPluginConfig(ctx.baseId, opts.plugin, {
|
|
enabled: true,
|
|
runtimeForm: 'panel',
|
|
writeEnabled: !!opts.write,
|
|
tableIds: opts.table ? [opts.table] : [],
|
|
});
|
|
if (ctx.json)
|
|
(0, output_1.printJson)(result);
|
|
else
|
|
process.stdout.write(`✓ Plugin ${opts.plugin} enabled${opts.table ? ` for table ${opts.table}` : ' base-wide'}\n`);
|
|
}));
|
|
config
|
|
.command('disable')
|
|
.description('Disable a plugin for a table (or base-wide if no --table)')
|
|
.requiredOption('--plugin <id>', 'Plugin ID')
|
|
.option('--table <id>', 'Table ID')
|
|
.action((0, ctx_1.run)(async (ctx, _args, o) => {
|
|
const opts = o;
|
|
await ctx.client.upsertPluginConfig(ctx.baseId, opts.plugin, {
|
|
enabled: false,
|
|
runtimeForm: 'panel',
|
|
writeEnabled: false,
|
|
tableIds: opts.table ? [opts.table] : [],
|
|
});
|
|
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;
|
|
}
|
|
exports.pluginCommand = pluginCommand;
|