@quantum-hub/qhubctl 1.1.4 → 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
  }
@@ -29,8 +29,7 @@ export default class SetContext extends AuthenticatedCommand {
29
29
  }
30
30
  async run() {
31
31
  const { args } = await this.parse(SetContext);
32
- const { contextId } = args;
33
- let selectedContext;
32
+ let { contextId } = args;
34
33
  if (!contextId) {
35
34
  ux.action.start('Loading available contexts');
36
35
  }
@@ -38,17 +37,15 @@ export default class SetContext extends AuthenticatedCommand {
38
37
  if (!contextId) {
39
38
  ux.action.stop('Done');
40
39
  }
41
- if (contextId) {
42
- selectedContext = accounts.find(account => account.id === contextId);
43
- }
44
- else {
40
+ if (!contextId) {
45
41
  const response = await select({
46
42
  choices: this.getPromptOptions(accounts),
47
43
  message: 'Choose a context',
48
44
  default: accounts[0],
49
45
  });
50
- selectedContext = accounts.find(account => account.id === response.id);
46
+ contextId = response.id;
51
47
  }
48
+ const selectedContext = accounts.find(account => account.id === contextId);
52
49
  if (!selectedContext) {
53
50
  this.error(`Context with id "${contextId}" not found.`);
54
51
  }
@@ -129,6 +129,29 @@
129
129
  "index.js"
130
130
  ]
131
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
+ },
132
155
  "login": {
133
156
  "aliases": [],
134
157
  "args": {},
@@ -171,17 +194,17 @@
171
194
  "index.js"
172
195
  ]
173
196
  },
174
- "list-contexts": {
197
+ "logout": {
175
198
  "aliases": [],
176
199
  "args": {},
177
- "description": "Retrieves the available contexts, i.e., the personal or organizational accounts available to you to work with.",
200
+ "description": "Logout",
178
201
  "examples": [
179
- "$ qhubctl list-contexts"
202
+ "$ qhubctl logout"
180
203
  ],
181
204
  "flags": {},
182
205
  "hasDynamicHelp": false,
183
206
  "hiddenAliases": [],
184
- "id": "list-contexts",
207
+ "id": "logout",
185
208
  "pluginAlias": "@quantum-hub/qhubctl",
186
209
  "pluginName": "@quantum-hub/qhubctl",
187
210
  "pluginType": "core",
@@ -190,21 +213,57 @@
190
213
  "relativePath": [
191
214
  "dist",
192
215
  "commands",
193
- "list-contexts",
216
+ "logout",
194
217
  "index.js"
195
218
  ]
196
219
  },
197
- "logout": {
220
+ "run": {
198
221
  "aliases": [],
199
- "args": {},
200
- "description": "Logout",
222
+ "args": {
223
+ "serviceId": {
224
+ "name": "serviceId"
225
+ }
226
+ },
227
+ "description": "Creates a job execution",
201
228
  "examples": [
202
- "$ qhubctl logout"
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"
203
234
  ],
204
- "flags": {},
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
+ },
205
264
  "hasDynamicHelp": false,
206
265
  "hiddenAliases": [],
207
- "id": "logout",
266
+ "id": "run",
208
267
  "pluginAlias": "@quantum-hub/qhubctl",
209
268
  "pluginName": "@quantum-hub/qhubctl",
210
269
  "pluginType": "core",
@@ -213,7 +272,7 @@
213
272
  "relativePath": [
214
273
  "dist",
215
274
  "commands",
216
- "logout",
275
+ "run",
217
276
  "index.js"
218
277
  ]
219
278
  },
@@ -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",
@@ -526,5 +544,5 @@
526
544
  ]
527
545
  }
528
546
  },
529
- "version": "1.1.4"
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.4",
3
+ "version": "1.2.0",
4
4
  "description": "Quantum Hub CLI",
5
5
  "author": "Kipu Quantum GmbH",
6
6
  "contributors": [