@quantum-hub/qhubctl 1.1.5 → 1.2.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.
@@ -1,13 +1,11 @@
1
1
  import { AbstractCommand } from '../../model/command.js';
2
2
  export default class Serve extends AbstractCommand {
3
- static CONTAINER_NAME: string;
4
- static IMAGE: string;
5
3
  static description: string;
6
4
  static examples: string[];
7
5
  static flags: {
8
- port: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
- log_level: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
6
+ port: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
7
+ 'log-level': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
9
  };
11
10
  run(): Promise<void>;
12
- private isProjectSupported;
13
11
  }
@@ -1,87 +1,51 @@
1
1
  import { Flags, ux } from '@oclif/core';
2
- import fs from 'fs-extra';
3
- import Listr from 'listr';
4
- import path from "node:path";
5
- import terminalLink from 'terminal-link';
6
- import { executeAsyncCommand, executeCommand } from '../../lib/command.js';
7
- import { randomString } from '../../lib/random-string.js';
2
+ import { spawnSync } from 'node:child_process';
8
3
  import { AbstractCommand } from '../../model/command.js';
9
- import { Runtime } from "../../model/managed-service-config.js";
10
- import { readServiceConfig } from "../../service/service-config-service.js";
11
4
  export default class Serve extends AbstractCommand {
12
- static CONTAINER_NAME = 'planqk-cli-serve';
13
- static IMAGE = 'registry.gitlab.com/planqk-foss/planqk-cli-serve:latest';
14
- static description = 'Runs your project in a containerized environment and expose it through a ' +
15
- 'local web server, similarly to how Kipu Quantum Hub would run your code. The local web server exposes the ' +
16
- 'same HTTP endpoints to start a service execution, to check the status of running executions, to cancel ' +
17
- 'executions, and to retrieve execution results.';
5
+ static description = 'Runs your project locally via qhub-serve inside the workspace virtual environment, ' +
6
+ 'exposing the same HTTP endpoints as Kipu Quantum Hub (start execution, check status, ' +
7
+ 'cancel, retrieve results).';
18
8
  static examples = [
19
9
  '$ qhubctl serve',
20
- '$ qhubctl serve -p <port> --log_level <log_level>',
10
+ '$ qhubctl serve -p 8081',
11
+ '$ qhubctl serve --workspace /path/to/workspace',
21
12
  ];
22
13
  static flags = {
23
14
  port: Flags.integer({
24
15
  char: 'p',
25
16
  description: 'The port on which the local web server accepts requests',
26
17
  required: false,
18
+ default: 8081,
27
19
  }),
28
- log_level: Flags.string({
29
- description: 'The log level for the service, possible values: DEBUG, INFO, WARNING, ERROR, CRITICAL',
20
+ 'log-level': Flags.string({
21
+ description: 'Log level for qhub-serve. Possible values: TRACE, DEBUG, INFO, SUCCESS, WARNING, ERROR, CRITICAL',
30
22
  required: false,
31
23
  default: 'INFO',
24
+ options: ['TRACE', 'DEBUG', 'INFO', 'SUCCESS', 'WARNING', 'ERROR', 'CRITICAL'],
25
+ }),
26
+ workspace: Flags.string({
27
+ char: 'w',
28
+ description: 'Path to the workspace directory (must be a valid uv project). Defaults to the current directory.',
29
+ required: false,
32
30
  }),
33
31
  };
34
32
  async run() {
35
33
  const { flags } = await this.parse(Serve);
36
- const hostPort = flags.port ?? 8081;
37
- const logLevel = flags.log_level ?? 'INFO';
38
- if (!this.isProjectSupported()) {
39
- this.error('Please make sure you have a "./src/program.py" file and "runtime" is set to "PYTHON_TEMPLATE" (qhub.json).');
34
+ const workspaceDir = flags.workspace ?? process.cwd();
35
+ const port = flags.port;
36
+ const logLevel = flags['log-level'];
37
+ // Step 1: Add (or upgrade) qhub-serve as a dev dependency
38
+ ux.stdout('Ensuring qhub-serve is up to date...');
39
+ const addResult = spawnSync('uv', ['add', '--dev', '--upgrade', 'qhub-serve', '--project', workspaceDir], { stdio: 'inherit' });
40
+ if (addResult.status !== 0) {
41
+ this.error('Failed to install qhub-serve via uv. ' +
42
+ 'Make sure uv is installed and the workspace is a valid uv project.');
43
+ }
44
+ // Step 2: Run qhub-serve inside the workspace venv
45
+ ux.stdout('\nStarting qhub-serve...');
46
+ const runResult = spawnSync('uv', ['run', '--project', workspaceDir, 'qhub-serve', '--workspace', workspaceDir, '--port', String(port), '--log-level', logLevel], { stdio: 'inherit' });
47
+ if (runResult.status !== 0) {
48
+ this.error('qhub-serve exited with a non-zero status code.');
40
49
  }
41
- const containerName = `${Serve.CONTAINER_NAME}-${randomString(10)}`;
42
- const tasks = new Listr([
43
- {
44
- task: () => executeAsyncCommand(`docker pull ${Serve.IMAGE}`),
45
- title: 'Ensuring latest base image',
46
- },
47
- {
48
- task: () => executeAsyncCommand(`docker run -e LOG_LEVEL=${logLevel} -e PORT=${hostPort} -p ${hostPort}:${hostPort} -v "$(pwd):/workspace" --name ${containerName} ${Serve.IMAGE}`),
49
- title: 'Building container',
50
- },
51
- {
52
- task: () => Promise.resolve(),
53
- title: 'Starting container',
54
- },
55
- ]);
56
- ux.stdout('Starting service locally...');
57
- tasks.run()
58
- .then(() => {
59
- const baseUrl = `http://localhost:${hostPort}`;
60
- const docsUrl = `${baseUrl}/docs`;
61
- ux.stdout('\nLocal service running at:');
62
- ux.stdout(terminalLink(baseUrl, baseUrl));
63
- ux.stdout('\nDocumentation and interactive API client available at:');
64
- ux.stdout(terminalLink(docsUrl, docsUrl));
65
- ux.stdout('');
66
- try {
67
- executeCommand(`docker start -i -a ${containerName}`);
68
- }
69
- catch {
70
- this.error(`There might be problems with your dependencies. Don't forget to verify the requirements.txt file`);
71
- }
72
- })
73
- .catch(error => this.error(error))
74
- .finally(() => {
75
- ux.stdout('\nStopping and removing container...');
76
- executeCommand(`docker stop ${containerName} > /dev/null 2>&1`);
77
- return executeAsyncCommand(`docker rm ${containerName} > /dev/null 2>&1`);
78
- });
79
- }
80
- isProjectSupported() {
81
- const programFile = path.join(process.cwd(), 'src', 'program.py');
82
- const hasProgramFile = fs.existsSync(programFile);
83
- const serviceConfig = readServiceConfig(process.cwd());
84
- const isPythonTemplate = serviceConfig.runtime == Runtime.PYTHON_TEMPLATE;
85
- return isPythonTemplate && hasProgramFile;
86
50
  }
87
51
  }
@@ -90,29 +90,6 @@
90
90
  "index.js"
91
91
  ]
92
92
  },
93
- "list-contexts": {
94
- "aliases": [],
95
- "args": {},
96
- "description": "Retrieves the available contexts, i.e., the personal or organizational accounts available to you to work with.",
97
- "examples": [
98
- "$ qhubctl list-contexts"
99
- ],
100
- "flags": {},
101
- "hasDynamicHelp": false,
102
- "hiddenAliases": [],
103
- "id": "list-contexts",
104
- "pluginAlias": "@quantum-hub/qhubctl",
105
- "pluginName": "@quantum-hub/qhubctl",
106
- "pluginType": "core",
107
- "strict": true,
108
- "isESM": true,
109
- "relativePath": [
110
- "dist",
111
- "commands",
112
- "list-contexts",
113
- "index.js"
114
- ]
115
- },
116
93
  "init": {
117
94
  "aliases": [],
118
95
  "args": {},
@@ -152,6 +129,29 @@
152
129
  "index.js"
153
130
  ]
154
131
  },
132
+ "list-contexts": {
133
+ "aliases": [],
134
+ "args": {},
135
+ "description": "Retrieves the available contexts, i.e., the personal or organizational accounts available to you to work with.",
136
+ "examples": [
137
+ "$ qhubctl list-contexts"
138
+ ],
139
+ "flags": {},
140
+ "hasDynamicHelp": false,
141
+ "hiddenAliases": [],
142
+ "id": "list-contexts",
143
+ "pluginAlias": "@quantum-hub/qhubctl",
144
+ "pluginName": "@quantum-hub/qhubctl",
145
+ "pluginType": "core",
146
+ "strict": true,
147
+ "isESM": true,
148
+ "relativePath": [
149
+ "dist",
150
+ "commands",
151
+ "list-contexts",
152
+ "index.js"
153
+ ]
154
+ },
155
155
  "login": {
156
156
  "aliases": [],
157
157
  "args": {},
@@ -217,6 +217,65 @@
217
217
  "index.js"
218
218
  ]
219
219
  },
220
+ "run": {
221
+ "aliases": [],
222
+ "args": {
223
+ "serviceId": {
224
+ "name": "serviceId"
225
+ }
226
+ },
227
+ "description": "Creates a job execution",
228
+ "examples": [
229
+ "$ qhubctl run",
230
+ "$ qhubctl run --detached",
231
+ "$ qhubctl run -i '{\"values\": [10,12], \"round_up\": true}'",
232
+ "$ qhubctl run --input-files=./input/data.json,./input/params.json",
233
+ "$ qhubctl run --input-files=./config.json,./overrides.json"
234
+ ],
235
+ "flags": {
236
+ "input": {
237
+ "char": "i",
238
+ "description": "Input as JSON string.",
239
+ "name": "input",
240
+ "required": false,
241
+ "hasDynamicHelp": false,
242
+ "multiple": false,
243
+ "type": "option"
244
+ },
245
+ "input-files": {
246
+ "aliases": [
247
+ "input-files"
248
+ ],
249
+ "description": "Comma-separated paths to files containing input to be merged (default: ./input/data.json,./input/params.json).",
250
+ "name": "input-files",
251
+ "required": false,
252
+ "hasDynamicHelp": false,
253
+ "multiple": false,
254
+ "type": "option"
255
+ },
256
+ "detached": {
257
+ "description": "Executes the job in detached mode, i.e., without waiting for it to finish.",
258
+ "name": "detached",
259
+ "required": false,
260
+ "allowNo": false,
261
+ "type": "boolean"
262
+ }
263
+ },
264
+ "hasDynamicHelp": false,
265
+ "hiddenAliases": [],
266
+ "id": "run",
267
+ "pluginAlias": "@quantum-hub/qhubctl",
268
+ "pluginName": "@quantum-hub/qhubctl",
269
+ "pluginType": "core",
270
+ "strict": true,
271
+ "isESM": true,
272
+ "relativePath": [
273
+ "dist",
274
+ "commands",
275
+ "run",
276
+ "index.js"
277
+ ]
278
+ },
220
279
  "openapi": {
221
280
  "aliases": [],
222
281
  "args": {},
@@ -296,72 +355,14 @@
296
355
  "index.js"
297
356
  ]
298
357
  },
299
- "run": {
300
- "aliases": [],
301
- "args": {
302
- "serviceId": {
303
- "name": "serviceId"
304
- }
305
- },
306
- "description": "Creates a job execution",
307
- "examples": [
308
- "$ qhubctl run",
309
- "$ qhubctl run --detached",
310
- "$ qhubctl run -i '{\"values\": [10,12], \"round_up\": true}'",
311
- "$ qhubctl run --input-files=./input/data.json,./input/params.json",
312
- "$ qhubctl run --input-files=./config.json,./overrides.json"
313
- ],
314
- "flags": {
315
- "input": {
316
- "char": "i",
317
- "description": "Input as JSON string.",
318
- "name": "input",
319
- "required": false,
320
- "hasDynamicHelp": false,
321
- "multiple": false,
322
- "type": "option"
323
- },
324
- "input-files": {
325
- "aliases": [
326
- "input-files"
327
- ],
328
- "description": "Comma-separated paths to files containing input to be merged (default: ./input/data.json,./input/params.json).",
329
- "name": "input-files",
330
- "required": false,
331
- "hasDynamicHelp": false,
332
- "multiple": false,
333
- "type": "option"
334
- },
335
- "detached": {
336
- "description": "Executes the job in detached mode, i.e., without waiting for it to finish.",
337
- "name": "detached",
338
- "required": false,
339
- "allowNo": false,
340
- "type": "boolean"
341
- }
342
- },
343
- "hasDynamicHelp": false,
344
- "hiddenAliases": [],
345
- "id": "run",
346
- "pluginAlias": "@quantum-hub/qhubctl",
347
- "pluginName": "@quantum-hub/qhubctl",
348
- "pluginType": "core",
349
- "strict": true,
350
- "isESM": true,
351
- "relativePath": [
352
- "dist",
353
- "commands",
354
- "run",
355
- "index.js"
356
- ]
357
- },
358
358
  "serve": {
359
359
  "aliases": [],
360
360
  "args": {},
361
- "description": "Runs your project in a containerized environment and expose it through a local web server, similarly to how Kipu Quantum Hub would run your code. The local web server exposes the same HTTP endpoints to start a service execution, to check the status of running executions, to cancel executions, and to retrieve execution results.",
361
+ "description": "Runs your project locally via qhub-serve inside the workspace virtual environment, exposing the same HTTP endpoints as Kipu Quantum Hub (start execution, check status, cancel, retrieve results).",
362
362
  "examples": [
363
363
  "$ qhubctl serve",
364
- "$ qhubctl serve -p <port> --log_level <log_level>"
364
+ "$ qhubctl serve -p 8081",
365
+ "$ qhubctl serve --workspace /path/to/workspace"
365
366
  ],
366
367
  "flags": {
367
368
  "port": {
@@ -369,17 +370,36 @@
369
370
  "description": "The port on which the local web server accepts requests",
370
371
  "name": "port",
371
372
  "required": false,
373
+ "default": 8081,
372
374
  "hasDynamicHelp": false,
373
375
  "multiple": false,
374
376
  "type": "option"
375
377
  },
376
- "log_level": {
377
- "description": "The log level for the service, possible values: DEBUG, INFO, WARNING, ERROR, CRITICAL",
378
- "name": "log_level",
378
+ "log-level": {
379
+ "description": "Log level for qhub-serve. Possible values: TRACE, DEBUG, INFO, SUCCESS, WARNING, ERROR, CRITICAL",
380
+ "name": "log-level",
379
381
  "required": false,
380
382
  "default": "INFO",
381
383
  "hasDynamicHelp": false,
382
384
  "multiple": false,
385
+ "options": [
386
+ "TRACE",
387
+ "DEBUG",
388
+ "INFO",
389
+ "SUCCESS",
390
+ "WARNING",
391
+ "ERROR",
392
+ "CRITICAL"
393
+ ],
394
+ "type": "option"
395
+ },
396
+ "workspace": {
397
+ "char": "w",
398
+ "description": "Path to the workspace directory (must be a valid uv project). Defaults to the current directory.",
399
+ "name": "workspace",
400
+ "required": false,
401
+ "hasDynamicHelp": false,
402
+ "multiple": false,
383
403
  "type": "option"
384
404
  }
385
405
  },
@@ -390,8 +410,6 @@
390
410
  "pluginName": "@quantum-hub/qhubctl",
391
411
  "pluginType": "core",
392
412
  "strict": true,
393
- "CONTAINER_NAME": "planqk-cli-serve",
394
- "IMAGE": "registry.gitlab.com/planqk-foss/planqk-cli-serve:latest",
395
413
  "isESM": true,
396
414
  "relativePath": [
397
415
  "dist",
@@ -448,6 +466,34 @@
448
466
  "index.js"
449
467
  ]
450
468
  },
469
+ "set-context": {
470
+ "aliases": [],
471
+ "args": {
472
+ "contextId": {
473
+ "name": "contextId"
474
+ }
475
+ },
476
+ "description": "Set the current context, i.e., the personal or organization account you are currently working with.",
477
+ "examples": [
478
+ "$ qhubctl set-context",
479
+ "$ qhubctl set-context <context-id>"
480
+ ],
481
+ "flags": {},
482
+ "hasDynamicHelp": false,
483
+ "hiddenAliases": [],
484
+ "id": "set-context",
485
+ "pluginAlias": "@quantum-hub/qhubctl",
486
+ "pluginName": "@quantum-hub/qhubctl",
487
+ "pluginType": "core",
488
+ "strict": true,
489
+ "isESM": true,
490
+ "relativePath": [
491
+ "dist",
492
+ "commands",
493
+ "set-context",
494
+ "index.js"
495
+ ]
496
+ },
451
497
  "up": {
452
498
  "aliases": [],
453
499
  "args": {},
@@ -496,35 +542,7 @@
496
542
  "up",
497
543
  "index.js"
498
544
  ]
499
- },
500
- "set-context": {
501
- "aliases": [],
502
- "args": {
503
- "contextId": {
504
- "name": "contextId"
505
- }
506
- },
507
- "description": "Set the current context, i.e., the personal or organization account you are currently working with.",
508
- "examples": [
509
- "$ qhubctl set-context",
510
- "$ qhubctl set-context <context-id>"
511
- ],
512
- "flags": {},
513
- "hasDynamicHelp": false,
514
- "hiddenAliases": [],
515
- "id": "set-context",
516
- "pluginAlias": "@quantum-hub/qhubctl",
517
- "pluginName": "@quantum-hub/qhubctl",
518
- "pluginType": "core",
519
- "strict": true,
520
- "isESM": true,
521
- "relativePath": [
522
- "dist",
523
- "commands",
524
- "set-context",
525
- "index.js"
526
- ]
527
545
  }
528
546
  },
529
- "version": "1.1.5"
547
+ "version": "1.2.0"
530
548
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantum-hub/qhubctl",
3
- "version": "1.1.5",
3
+ "version": "1.2.0",
4
4
  "description": "Quantum Hub CLI",
5
5
  "author": "Kipu Quantum GmbH",
6
6
  "contributors": [