mirror of
https://github.com/whyour/qinglong.git
synced 2026-10-17 07:20:51 +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
127 lines
3.7 KiB
JavaScript
127 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
import { translate } from '../shared/i18n/index';
|
|
import { standaloneHelp } from '../internal/help/standalone';
|
|
import {
|
|
withCommandCancellation,
|
|
interruptedCode,
|
|
} from '../internal/runtime/cancellation';
|
|
import { CliError, fail } from '../shared/errors';
|
|
|
|
export interface CompatibilityRoute {
|
|
surface: 'local' | 'subscription';
|
|
args: string[];
|
|
}
|
|
|
|
export function compatibilityRoute(input: string[]): CompatibilityRoute {
|
|
const args = input[0] === '-l' ? input.slice(1) : [...input];
|
|
const [action, ...values] = args;
|
|
if (action === 'repo' || action === 'raw')
|
|
return { surface: 'subscription', args };
|
|
if (action === 'update') {
|
|
if (
|
|
values.length > 1 ||
|
|
(values.length && !['true', 'false'].includes(values[0]!))
|
|
)
|
|
fail(translate(process.env, '用法:ql-compat update [true|false]'), 2);
|
|
return {
|
|
surface: 'local',
|
|
args: ['update', ...(values[0] === 'false' ? ['--download-only'] : [])],
|
|
};
|
|
}
|
|
if (action === 'reload') {
|
|
if (
|
|
values.length > 1 ||
|
|
(values.length && !['services', 'system', 'data'].includes(values[0]!))
|
|
)
|
|
fail(
|
|
translate(process.env, '用法:ql-compat reload [services|system|data]'),
|
|
2,
|
|
);
|
|
return {
|
|
surface: 'local',
|
|
args: ['reload', '--target', values[0] || 'services'],
|
|
};
|
|
}
|
|
if (
|
|
action &&
|
|
[
|
|
'rmlog',
|
|
'extra',
|
|
'bot',
|
|
'check',
|
|
'resetlet',
|
|
'resettfa',
|
|
'resetpwd',
|
|
'resetname',
|
|
].includes(action)
|
|
) {
|
|
// A legacy positional value beginning with '-' is data, never a CLI option.
|
|
return { surface: 'local', args: [action, '--', ...values] };
|
|
}
|
|
fail(translate(process.env, '未知旧命令,请运行 ql-compat --help。'), 2);
|
|
}
|
|
|
|
export async function compatibilityMain(
|
|
args = process.argv.slice(2),
|
|
): Promise<number> {
|
|
return withCommandCancellation(async (signal) => {
|
|
try {
|
|
if (
|
|
!args.length ||
|
|
(args.length === 1 && ['--help', '-h'].includes(args[0]!))
|
|
) {
|
|
process.stdout.write(standaloneHelp('compat'));
|
|
return 0;
|
|
}
|
|
const route = compatibilityRoute(args);
|
|
if (route.surface === 'subscription') {
|
|
const { subscriptionWorker } = await import(
|
|
'../internal/subscription/worker'
|
|
);
|
|
return subscriptionWorker(route.args);
|
|
}
|
|
const { parse } = await import('../shared/cli/arguments');
|
|
const { localContext } = await import('../internal/runtime/context');
|
|
const { maintenance } = await import(
|
|
'../internal/maintenance/maintenance'
|
|
);
|
|
const { loggedOperation } = await import(
|
|
'../internal/runtime/commandLog'
|
|
);
|
|
const { registry } = await import('../internal/commands/registry');
|
|
const command = parse(route.args, registry);
|
|
const context = await localContext(command, { signal });
|
|
const { result, logPath } = await loggedOperation(
|
|
context,
|
|
route.args[0]!,
|
|
() => maintenance(command, context),
|
|
signal,
|
|
);
|
|
process.stdout.write(
|
|
JSON.stringify({ code: 200, data: result, logPath }) + '\n',
|
|
);
|
|
return 0;
|
|
} catch (error) {
|
|
const code =
|
|
interruptedCode(signal) ??
|
|
(error instanceof CliError ? error.exitCode : 1);
|
|
process.stderr.write(
|
|
JSON.stringify({
|
|
code,
|
|
message: signal.aborted
|
|
? translate(process.env, '命令已中断。')
|
|
: error instanceof CliError
|
|
? error.message
|
|
: translate(process.env, '旧命令适配器执行失败。'),
|
|
}) + '\n',
|
|
);
|
|
return code;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (require.main === module)
|
|
void compatibilityMain().then((code) => {
|
|
process.exitCode = code;
|
|
});
|