@quantum-hub/qhubctl 1.13.0 → 1.15.0
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/commands/run.d.ts +6 -0
- package/dist/commands/run.js +19 -3
- package/dist/commands/services-build-logs.d.ts +6 -0
- package/dist/commands/services-build-logs.js +85 -0
- package/dist/program.js +2 -0
- package/dist/service/quantum-hub-service.d.ts +1 -0
- package/dist/service/quantum-hub-service.js +13 -1
- package/package.json +1 -1
package/dist/commands/run.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { HubPlatform } from '@quantum-hub/qhub-api/platform';
|
|
3
|
+
export interface RunCommandOptions {
|
|
4
|
+
tag: string[];
|
|
5
|
+
storeInput: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function buildCreateJobRequest(serviceId: string, input: Record<string, never> | undefined, opts: RunCommandOptions): HubPlatform.CreateServiceJobRequest;
|
|
2
8
|
export declare const runCommand: Command;
|
package/dist/commands/run.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { confirm } from '@inquirer/prompts';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import
|
|
3
|
+
import asyncWaitUntil from 'async-wait-until';
|
|
4
|
+
const { waitUntil } = asyncWaitUntil;
|
|
4
5
|
import fsExtra from 'fs-extra';
|
|
5
6
|
const { readJsonSync } = fsExtra;
|
|
6
7
|
import fs from 'node:fs';
|
|
@@ -39,18 +40,33 @@ async function getInputFromFiles(filePaths) {
|
|
|
39
40
|
}
|
|
40
41
|
return mergedInput;
|
|
41
42
|
}
|
|
43
|
+
export function buildCreateJobRequest(serviceId, input, opts) {
|
|
44
|
+
const payload = {
|
|
45
|
+
serviceId,
|
|
46
|
+
tags: ['cli', ...opts.tag],
|
|
47
|
+
input,
|
|
48
|
+
};
|
|
49
|
+
if (opts.storeInput) {
|
|
50
|
+
payload.storeInput = true;
|
|
51
|
+
}
|
|
52
|
+
return payload;
|
|
53
|
+
}
|
|
42
54
|
const EXAMPLES = `
|
|
43
55
|
Examples:
|
|
44
56
|
$ qhubctl run
|
|
45
57
|
$ qhubctl run --detached
|
|
46
58
|
$ qhubctl run -i '{"values": [10,12], "round_up": true}'
|
|
47
59
|
$ qhubctl run --input-files=./input/data.json,./input/params.json
|
|
48
|
-
$ qhubctl run --input-files=./config.json,./overrides.json
|
|
60
|
+
$ qhubctl run --input-files=./config.json,./overrides.json
|
|
61
|
+
$ qhubctl run --store-input
|
|
62
|
+
$ qhubctl run --tag experiment --tag run-42`;
|
|
49
63
|
export const runCommand = new Command('run')
|
|
50
64
|
.description('Creates a job execution')
|
|
51
65
|
.argument('[serviceId]', 'The ID of the service to run (optional — reads from qhub.json if omitted)')
|
|
52
66
|
.option('-i, --input <value>', 'Input as JSON string.')
|
|
53
67
|
.option('--input-files <value>', 'Comma-separated paths to files containing input to be merged (default: ./input/data.json,./input/params.json).')
|
|
68
|
+
.option('--store-input', 'Persist the input data alongside the job on the server.', false)
|
|
69
|
+
.option('--tag <value>', 'Tag to attach to the job. Repeat the flag to attach multiple tags.', (value, previous) => [...previous, value], [])
|
|
54
70
|
.option('--detached', 'Executes the job in detached mode, i.e., without waiting for it to finish.', false)
|
|
55
71
|
.addHelpText('after', EXAMPLES)
|
|
56
72
|
.action(async (serviceId, opts) => {
|
|
@@ -82,7 +98,7 @@ export const runCommand = new Command('run')
|
|
|
82
98
|
input = await getInputFromFiles(opts.inputFiles || './input/data.json,./input/params.json');
|
|
83
99
|
}
|
|
84
100
|
let job;
|
|
85
|
-
const payload = {
|
|
101
|
+
const payload = buildCreateJobRequest(service.id, input, { tag: opts.tag, storeInput: opts.storeInput });
|
|
86
102
|
job = await hubService.runJob(payload);
|
|
87
103
|
if (opts.detached) {
|
|
88
104
|
stdout(`Job (${job.id}) created.`);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { input } from '@inquirer/prompts';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { error, spinner, stdout } from '../lib/output.js';
|
|
4
|
+
import { requireAuth } from '../model/command.js';
|
|
5
|
+
import QuantumHubService from '../service/quantum-hub-service.js';
|
|
6
|
+
import { readServiceConfig } from '../service/service-config-service.js';
|
|
7
|
+
const SEVERITY_COLUMN_WIDTH = 7;
|
|
8
|
+
const COLUMN_GAP = ' ';
|
|
9
|
+
const ANSI_RESET = '\x1b[0m';
|
|
10
|
+
const ANSI_BY_SEVERITY = {
|
|
11
|
+
DEBUG: '\x1b[2m',
|
|
12
|
+
WARNING: '\x1b[33m',
|
|
13
|
+
ERROR: '\x1b[31m',
|
|
14
|
+
};
|
|
15
|
+
export function formatLogEntry(entry, opts) {
|
|
16
|
+
const timestamp = entry.timestamp ?? '-';
|
|
17
|
+
const severity = entry.severity ?? 'INFO';
|
|
18
|
+
const message = entry.message ?? '';
|
|
19
|
+
const paddedSeverity = severity.padEnd(SEVERITY_COLUMN_WIDTH, ' ');
|
|
20
|
+
const ansi = opts.isTTY ? ANSI_BY_SEVERITY[severity] : undefined;
|
|
21
|
+
const severityCell = ansi ? `${ansi}${paddedSeverity}${ANSI_RESET}` : paddedSeverity;
|
|
22
|
+
return `${timestamp}${COLUMN_GAP}${severityCell}${COLUMN_GAP}${message}`;
|
|
23
|
+
}
|
|
24
|
+
const EXAMPLES = `
|
|
25
|
+
Examples:
|
|
26
|
+
$ qhubctl services build-logs
|
|
27
|
+
$ qhubctl services build-logs --id <serviceId>
|
|
28
|
+
$ qhubctl services build-logs --id <serviceId> --json`;
|
|
29
|
+
export const servicesBuildLogsCommand = new Command('build-logs')
|
|
30
|
+
.description('Show the build logs of a service.')
|
|
31
|
+
.option('--id <value>', 'The ID of the service (reads from qhub.json if omitted)')
|
|
32
|
+
.option('--json', 'Output in JSON format', false)
|
|
33
|
+
.addHelpText('after', EXAMPLES)
|
|
34
|
+
.action(async (opts) => {
|
|
35
|
+
const userConfig = await requireAuth({ printContext: !opts.json });
|
|
36
|
+
const quantumHubService = new QuantumHubService(userConfig);
|
|
37
|
+
let serviceId = opts.id;
|
|
38
|
+
if (!serviceId) {
|
|
39
|
+
try {
|
|
40
|
+
const serviceConfig = readServiceConfig(process.cwd());
|
|
41
|
+
serviceId = serviceConfig.serviceId;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
serviceId = undefined;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!serviceId) {
|
|
48
|
+
try {
|
|
49
|
+
serviceId = await input({ message: 'Enter the service ID:' });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// user cancelled (Ctrl+C)
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!serviceId?.trim()) {
|
|
57
|
+
error('No service ID provided.');
|
|
58
|
+
}
|
|
59
|
+
const spin = spinner('Loading build logs');
|
|
60
|
+
let logs;
|
|
61
|
+
try {
|
|
62
|
+
logs = await quantumHubService.getBuildLogs(serviceId);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
spin.fail('Failed to load build logs');
|
|
66
|
+
error(err instanceof Error ? err.message : String(err));
|
|
67
|
+
}
|
|
68
|
+
spin.succeed('Build logs loaded');
|
|
69
|
+
if (opts.json) {
|
|
70
|
+
stdout(JSON.stringify(logs, null, 2));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (logs.length === 0) {
|
|
74
|
+
stdout();
|
|
75
|
+
stdout('No build logs available.');
|
|
76
|
+
stdout();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const isTTY = Boolean(process.stdout.isTTY);
|
|
80
|
+
stdout();
|
|
81
|
+
for (const entry of logs) {
|
|
82
|
+
stdout(formatLogEntry(entry, { isTTY }));
|
|
83
|
+
}
|
|
84
|
+
stdout();
|
|
85
|
+
});
|
package/dist/program.js
CHANGED
|
@@ -11,6 +11,7 @@ import { logoutCommand } from './commands/logout.js';
|
|
|
11
11
|
import { openapiCommand } from './commands/openapi.js';
|
|
12
12
|
import { runCommand } from './commands/run.js';
|
|
13
13
|
import { serveCommand } from './commands/serve.js';
|
|
14
|
+
import { servicesBuildLogsCommand } from './commands/services-build-logs.js';
|
|
14
15
|
import { servicesBuildStatusCommand } from './commands/services-build-status.js';
|
|
15
16
|
import { servicesListCommand } from './commands/services-list.js';
|
|
16
17
|
import { setContextCommand } from './commands/set-context.js';
|
|
@@ -37,6 +38,7 @@ program.addCommand(serveCommand);
|
|
|
37
38
|
const servicesCommand = new Command('services').description('Manage services');
|
|
38
39
|
servicesCommand.addCommand(servicesListCommand);
|
|
39
40
|
servicesCommand.addCommand(servicesBuildStatusCommand);
|
|
41
|
+
servicesCommand.addCommand(servicesBuildLogsCommand);
|
|
40
42
|
program.addCommand(servicesCommand);
|
|
41
43
|
program.addCommand(setContextCommand);
|
|
42
44
|
program.addCommand(upCommand);
|
|
@@ -8,6 +8,7 @@ export default class QuantumHubService extends CommandService {
|
|
|
8
8
|
getAccounts(): Promise<HubPlatform.AccountDto[]>;
|
|
9
9
|
createService(serviceConfig: ManagedServiceConfig, sourceCode: Blob, apiDefinition?: Blob): Promise<HubPlatform.ServiceDto>;
|
|
10
10
|
getBuildJob(id: string): Promise<HubPlatform.BuildJobDto>;
|
|
11
|
+
getBuildLogs(id: string): Promise<HubPlatform.LogEntry[]>;
|
|
11
12
|
getJobById(id: string): Promise<HubPlatform.ServiceJobDto>;
|
|
12
13
|
getService(id: string): Promise<HubPlatform.ServiceDto>;
|
|
13
14
|
getServices(): Promise<HubPlatform.ServiceOverviewDto[]>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HubPlatformClient } from "@quantum-hub/qhub-api/platform";
|
|
1
|
+
import { HubPlatform, HubPlatformClient } from "@quantum-hub/qhub-api/platform";
|
|
2
2
|
import { handleError } from '../lib/error.js';
|
|
3
3
|
import { defaultBasePath } from '../model/user-config.js';
|
|
4
4
|
import { CommandService } from './command-service.js';
|
|
@@ -69,6 +69,18 @@ export default class QuantumHubService extends CommandService {
|
|
|
69
69
|
throw new Error(errorMessage);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
+
async getBuildLogs(id) {
|
|
73
|
+
try {
|
|
74
|
+
return await this.apiClient.managedServices.getManagedServiceBuildLogs(id);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof HubPlatform.NotFoundError) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
const errorMessage = await handleError(error);
|
|
81
|
+
throw new Error(errorMessage);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
72
84
|
async getJobById(id) {
|
|
73
85
|
try {
|
|
74
86
|
return await this.apiClient.serviceJobs.getServiceJob(id);
|