mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-28 09:02:12 +08:00
* feat(cli): add unified Commander CLI for QingLong 2.x * fix(cli): publish via npm and address security review feedback * ci(cli): package npm artifacts and remove evaluation collateral * test(cli): use a fixed shell fixture for log retention * refactor(cli): separate remote npm client from panel tools * feat(cli): cover active panel OpenAPI resources * docs(cli): unify authentication and skill guidance * refactor(cli): isolate internal commands and generate Commander help * refactor(cli): organize remote and internal modules by responsibility * ci(cli): publish verified npm archives from master * fix(cli): publish under the whyour npm scope * ci: use npm trusted publishing for both packages * docs: introduce the published CLI on the project homepage * fix(cli): preserve server log truncation and correct login hints * fix(cli): accept dashboard record request bodies * fix(cli): preserve stdin for local task execution * fix(cli): resolve task executables after changing directory * fix(cli): preserve shell function tasks and sanitize test failures * fix(cli): preserve shell hook state and resolve workdir after hooks * fix(cli): preserve cleanup across shared shell task timeouts * fix(cli): isolate shell control descriptors and reap timed-out descendants
62 lines
4.7 KiB
JavaScript
62 lines
4.7 KiB
JavaScript
// Fresh disposable official panel only; never run against an initialized panel.
|
|
const assert = require('node:assert/strict');
|
|
const { randomUUID } = require('node:crypto');
|
|
const { spawnSync } = require('node:child_process');
|
|
const path = require('node:path');
|
|
const fs = require('node:fs/promises');
|
|
|
|
(async () => {
|
|
assert.equal(process.env.QL_PANEL_INTEGRATION, '1');
|
|
const url = 'http://127.0.0.1:5700';
|
|
const api = async (route, method = 'GET', body) => {
|
|
const response = await fetch(url + '/api/' + route, { method,
|
|
headers: { 'content-type': 'application/json' }, body: body === undefined ? undefined : JSON.stringify(body) });
|
|
const result = await response.json(); assert.equal(result.code, 200); return result.data;
|
|
};
|
|
const info = await api('system'); assert.equal(info.isInitialized, false, 'Refuse initialized panels');
|
|
const credentials = { username: 'fixture-' + randomUUID(), password: randomUUID() };
|
|
await api('user/init', 'PUT', credentials);
|
|
const owner = (await api('user/login', 'POST', credentials)).token;
|
|
const config = '/tmp/ql-openapi-auth.json';
|
|
const entry = path.resolve(__dirname, '../../dist/npm/ql.js');
|
|
const cli = (args, ownerMode = false, extra = {}) => {
|
|
const result = spawnSync(process.execPath, [entry, ...args, '--json'], {
|
|
encoding: 'utf8', timeout: 30000,
|
|
env: { ...process.env, QL_CLI_CONFIG: config, QL_URL: ownerMode ? url : '', QL_ACCESS_TOKEN: ownerMode ? owner : '', ...extra },
|
|
});
|
|
// Report only the command name, never request bodies or application credentials.
|
|
assert.equal(result.status, 0, args.slice(0, 2).join(' ') + ': ' + result.stderr);
|
|
return JSON.parse(result.stdout).data;
|
|
};
|
|
const app = cli(['app', 'create', '--name', 'cli-fixture', '--scopes', 'crons,subscriptions,envs,configs,scripts,logs,dependencies,system,dashboard,apps', '--show-secrets'], true);
|
|
assert.ok(app.client_id && app.client_secret);
|
|
cli(['login', '--url', url], false, { QL_CLIENT_ID: app.client_id, QL_CLIENT_SECRET: app.client_secret });
|
|
cli(['auth', 'status', '--scope', 'apps']);
|
|
cli(['app', 'update', String(app.id), '--name', 'cli-updated', '--scopes', 'crons,subscriptions,envs,configs,scripts,logs,dependencies,system,dashboard,apps']);
|
|
assert.ok(cli(['app', 'list']).every(item => !('client_secret' in item) && !('tokens' in item)));
|
|
const task = cli(['task', 'create', '--name', 'cli-task', '--command', 'echo OPENAPI_FIXTURE', '--schedule', '0 0 * * *']);
|
|
cli(['task', 'update', String(task.id), '--name', 'cli-task-updated', '--command', 'echo OPENAPI_UPDATED', '--schedule', '0 1 * * *']);
|
|
cli(['task', 'disable', String(task.id)]); cli(['task', 'enable', String(task.id)]);
|
|
assert.equal(cli(['task', 'get', String(task.id)]).name, 'cli-task-updated');
|
|
const sub = cli(['subscription', 'create', '--type', 'file', '--url', url + '/fixture.js', '--alias', 'cli-sub', '--schedule-type', 'crontab', '--schedule', '0 0 * * *']);
|
|
cli(['subscription', 'update', String(sub.id), '--data', JSON.stringify({ type: 'file', url: url + '/fixture.js', alias: 'cli-sub', name: 'cli-sub-updated', schedule_type: 'crontab', schedule: '0 1 * * *' })]);
|
|
cli(['subscription', 'disable', String(sub.id)]); cli(['subscription', 'enable', String(sub.id)]);
|
|
assert.equal(cli(['subscription', 'get', String(sub.id)]).name, 'cli-sub-updated');
|
|
const env = cli(['env', 'create', '--data', '[{"name":"CLI_FIXTURE","value":"one"}]'])[0];
|
|
cli(['env', 'update', String(env.id), '--data', '{"name":"CLI_FIXTURE","value":"two"}']);
|
|
assert.equal(cli(['env', 'get', String(env.id)]).value, 'two');
|
|
cli(['env', 'delete', String(env.id)]);
|
|
cli(['script', 'create', '--data', '{"filename":"cli-openapi.js","content":"console.log(1)","path":""}']);
|
|
assert.equal(cli(['script', 'get', '--query', '{"file":"cli-openapi.js"}']), 'console.log(1)');
|
|
cli(['config', 'save', '--data', '{"name":"cli-openapi.sh","content":"# fixture"}']);
|
|
assert.equal(cli(['config', 'get', '--query', '{"path":"cli-openapi.sh"}']), '# fixture');
|
|
cli(['log', 'list']); cli(['dependency', 'list']);
|
|
cli(['task', 'delete', String(task.id)]); cli(['subscription', 'delete', String(sub.id)]);
|
|
const reset = cli(['app', 'reset-secret', String(app.id), '--show-secrets'], true);
|
|
assert.ok(reset.client_secret !== app.client_secret);
|
|
cli(['login', '--url', url], false, { QL_CLIENT_ID: app.client_id, QL_CLIENT_SECRET: reset.client_secret });
|
|
cli(['auth', 'status']); cli(['app', 'delete', String(app.id)], true);
|
|
await fs.rm(config, { force: true });
|
|
console.log(JSON.stringify({ panelVersion: info.version, taskCrud: true, subscriptionCrud: true, appCrudAndSecretRotation: true, envCrud: true, scriptCreateRead: true, configSaveRead: true, logAndDependencyRead: true }));
|
|
})().catch(error => { console.error(error.message); process.exitCode = 1; });
|