@quantum-hub/qhubctl 2.2.2-next.1 → 2.2.2

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.
Files changed (74) hide show
  1. package/README.md +4 -2
  2. package/dist/commands/compress.d.ts +1 -1
  3. package/dist/commands/compress.js +15 -15
  4. package/dist/commands/datapool-upload.d.ts +1 -1
  5. package/dist/commands/datapool-upload.js +35 -35
  6. package/dist/commands/get-context.d.ts +1 -1
  7. package/dist/commands/get-context.js +5 -5
  8. package/dist/commands/init.d.ts +1 -1
  9. package/dist/commands/init.js +33 -29
  10. package/dist/commands/list-contexts.d.ts +1 -1
  11. package/dist/commands/list-contexts.js +21 -13
  12. package/dist/commands/login.d.ts +1 -1
  13. package/dist/commands/login.js +16 -16
  14. package/dist/commands/logout.d.ts +1 -1
  15. package/dist/commands/logout.js +10 -10
  16. package/dist/commands/openapi.d.ts +1 -1
  17. package/dist/commands/openapi.js +112 -94
  18. package/dist/commands/run.d.ts +2 -2
  19. package/dist/commands/run.js +37 -34
  20. package/dist/commands/serve.d.ts +1 -1
  21. package/dist/commands/serve.js +30 -19
  22. package/dist/commands/services-build-logs.d.ts +2 -2
  23. package/dist/commands/services-build-logs.js +26 -26
  24. package/dist/commands/services-build-status.d.ts +1 -1
  25. package/dist/commands/services-build-status.js +21 -21
  26. package/dist/commands/services-list.d.ts +1 -1
  27. package/dist/commands/services-list.js +20 -17
  28. package/dist/commands/set-context.d.ts +1 -1
  29. package/dist/commands/set-context.js +18 -16
  30. package/dist/commands/up.d.ts +1 -1
  31. package/dist/commands/up.js +50 -48
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.js +1 -1
  34. package/dist/lib/command.js +2 -2
  35. package/dist/lib/config-dir.js +3 -3
  36. package/dist/lib/debug.js +1 -1
  37. package/dist/lib/entrypoint-resolver.js +15 -15
  38. package/dist/lib/error.js +4 -4
  39. package/dist/lib/fetch.js +3 -1
  40. package/dist/lib/init-user-input.d.ts +1 -1
  41. package/dist/lib/init-user-input.js +28 -26
  42. package/dist/lib/output.d.ts +2 -2
  43. package/dist/lib/output.js +3 -3
  44. package/dist/lib/random-name.js +147 -147
  45. package/dist/lib/random-string.js +2 -2
  46. package/dist/lib/repository.js +5 -5
  47. package/dist/lib/stream-to-blob.d.ts +1 -1
  48. package/dist/lib/stream-to-blob.js +5 -5
  49. package/dist/lib/up/get-description.js +8 -8
  50. package/dist/lib/up/ignore.js +8 -8
  51. package/dist/lib/up/qhubignore-help.js +10 -10
  52. package/dist/lib/up/required-files.js +17 -12
  53. package/dist/lib/up/source.d.ts +2 -2
  54. package/dist/lib/up/source.js +7 -7
  55. package/dist/lib/up/zip.js +8 -8
  56. package/dist/main.js +3 -3
  57. package/dist/model/command.d.ts +2 -2
  58. package/dist/model/command.js +9 -9
  59. package/dist/model/managed-service-config.d.ts +1 -1
  60. package/dist/model/user-config.js +1 -1
  61. package/dist/program.d.ts +1 -1
  62. package/dist/program.js +24 -24
  63. package/dist/service/auth-service.d.ts +3 -3
  64. package/dist/service/auth-service.js +11 -9
  65. package/dist/service/command-service.d.ts +1 -1
  66. package/dist/service/quantum-hub-service.d.ts +3 -3
  67. package/dist/service/quantum-hub-service.js +34 -20
  68. package/dist/service/service-config-service.d.ts +1 -1
  69. package/dist/service/service-config-service.js +8 -8
  70. package/dist/service/user-config-service.d.ts +1 -1
  71. package/dist/service/user-config-service.js +7 -7
  72. package/dist/service/user-service.d.ts +2 -2
  73. package/dist/service/user-service.js +4 -4
  74. package/package.json +8 -2
@@ -1,27 +1,29 @@
1
- import { confirm } from '@inquirer/prompts';
2
- import { Command } from 'commander';
3
- import fs from 'fs-extra';
4
- import path from 'node:path';
5
- import { spawnSync } from 'node:child_process';
6
- import { executeAsyncCommand } from '../lib/command.js';
7
- import { EntrypointResolver } from '../lib/entrypoint-resolver.js';
8
- import { randomString } from '../lib/random-string.js';
9
- import { error, spinner, stdout, warn } from '../lib/output.js';
10
- import { Runtime } from '../model/managed-service-config.js';
11
- import { readServiceConfig } from '../service/service-config-service.js';
12
- const SUPPORTED_FORMATS = ['yaml'];
13
- const DEFAULT_OUTPUT_FILE = 'openapi.yaml';
14
- const DEPRECATED_FILE_NAME = 'openapi-spec.yml';
1
+ import { confirm } from "@inquirer/prompts";
2
+ import { Command } from "commander";
3
+ import fs from "fs-extra";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import { executeAsyncCommand } from "../lib/command.js";
7
+ import { EntrypointResolver } from "../lib/entrypoint-resolver.js";
8
+ import { randomString } from "../lib/random-string.js";
9
+ import { error, spinner, stdout, warn } from "../lib/output.js";
10
+ import { Runtime } from "../model/managed-service-config.js";
11
+ import { readServiceConfig } from "../service/service-config-service.js";
12
+ const SUPPORTED_FORMATS = ["yaml"];
13
+ const DEFAULT_OUTPUT_FILE = "openapi.yaml";
14
+ const DEPRECATED_FILE_NAME = "openapi-spec.yml";
15
15
  function getErrorMessage(err) {
16
- if (typeof err === 'object' && err !== null && 'message' in err &&
17
- typeof err.message === 'string') {
16
+ if (typeof err === "object" &&
17
+ err !== null &&
18
+ "message" in err &&
19
+ typeof err.message === "string") {
18
20
  return err.message;
19
21
  }
20
- if (typeof err === 'string')
22
+ if (typeof err === "string")
21
23
  return err;
22
24
  if (err instanceof Error)
23
25
  return err.message;
24
- return 'An unknown error occurred';
26
+ return "An unknown error occurred";
25
27
  }
26
28
  function checkDeprecatedFiles() {
27
29
  const deprecatedFile = path.join(process.cwd(), DEPRECATED_FILE_NAME);
@@ -40,7 +42,9 @@ async function determineOutputPath(opts) {
40
42
  fs.mkdirpSync(outputDir);
41
43
  }
42
44
  catch (err) {
43
- throw new Error(`Cannot create output directory: ${outputDir}. ${getErrorMessage(err)}`, { cause: err });
45
+ throw new Error(`Cannot create output directory: ${outputDir}. ${getErrorMessage(err)}`, {
46
+ cause: err,
47
+ });
44
48
  }
45
49
  }
46
50
  }
@@ -50,7 +54,7 @@ async function determineOutputPath(opts) {
50
54
  default: false,
51
55
  });
52
56
  if (!shouldOverwrite) {
53
- const newPath = path.join(path.dirname(outputPath), `${path.basename(outputPath, '.yaml')}-${randomString(5)}.yaml`);
57
+ const newPath = path.join(path.dirname(outputPath), `${path.basename(outputPath, ".yaml")}-${randomString(5)}.yaml`);
54
58
  stdout(`Creating new file: ${path.basename(newPath)}`);
55
59
  return newPath;
56
60
  }
@@ -64,7 +68,7 @@ function isProjectSupported(opts) {
64
68
  }
65
69
  catch (err) {
66
70
  warn(`Failed to read project configuration: ${getErrorMessage(err)}`);
67
- warn('Ensure you have a valid qhub.json file in your project root.');
71
+ warn("Ensure you have a valid qhub.json file in your project root.");
68
72
  return false;
69
73
  }
70
74
  const hasExplicitEntrypoint = !!(opts.entrypoint || opts.package || opts.method);
@@ -73,52 +77,52 @@ function isProjectSupported(opts) {
73
77
  const isSupported = supportedRuntimes.includes(serviceConfig.runtime);
74
78
  if (!isSupported) {
75
79
  warn(`Runtime "${serviceConfig.runtime}" is not supported for OpenAPI generation.`);
76
- warn('');
77
- warn('Supported configurations:');
78
- warn(' \u2022 PYTHON_TEMPLATE: Automatic (requires src/program.py)');
79
- warn(' \u2022 DOCKER: With explicit entrypoint flags');
80
- warn('');
81
- warn('Examples:');
82
- warn(' qhubctl openapi --entrypoint=myapp.main:handler');
83
- warn(' qhubctl openapi --package=myapp.core --method=process');
80
+ warn("");
81
+ warn("Supported configurations:");
82
+ warn(" \u2022 PYTHON_TEMPLATE: Automatic (requires src/program.py)");
83
+ warn(" \u2022 DOCKER: With explicit entrypoint flags");
84
+ warn("");
85
+ warn("Examples:");
86
+ warn(" qhubctl openapi --entrypoint=myapp.main:handler");
87
+ warn(" qhubctl openapi --package=myapp.core --method=process");
84
88
  }
85
89
  return isSupported;
86
90
  }
87
- const programFile = path.join(process.cwd(), 'src', 'program.py');
91
+ const programFile = path.join(process.cwd(), "src", "program.py");
88
92
  const hasProgramFile = fs.existsSync(programFile);
89
93
  const isPythonTemplate = serviceConfig.runtime === Runtime.PYTHON_TEMPLATE;
90
94
  const isSupported = isPythonTemplate && hasProgramFile;
91
95
  if (!isSupported) {
92
- warn('Cannot generate OpenAPI description from your code.');
93
- warn('');
96
+ warn("Cannot generate OpenAPI description from your code.");
97
+ warn("");
94
98
  if (isPythonTemplate && !hasProgramFile) {
95
- warn('Missing required file: src/program.py');
96
- warn('');
97
- warn('Solutions:');
98
- warn(' \u2022 Create src/program.py with your main function, or');
99
- warn(' \u2022 Use explicit entrypoint: --entrypoint=yourmodule.main:function');
99
+ warn("Missing required file: src/program.py");
100
+ warn("");
101
+ warn("Solutions:");
102
+ warn(" \u2022 Create src/program.py with your main function, or");
103
+ warn(" \u2022 Use explicit entrypoint: --entrypoint=yourmodule.main:function");
100
104
  }
101
105
  else if (!isPythonTemplate) {
102
106
  warn(`Runtime "${serviceConfig.runtime}" requires explicit entrypoint specification.`);
103
- warn('');
104
- warn('Use one of these flags:');
105
- warn(' --entrypoint=package.module:function');
106
- warn(' --package=package.module --method=function');
107
- warn('');
108
- warn('Examples:');
109
- warn(' qhubctl openapi --entrypoint=myapp.main:handler');
110
- warn(' qhubctl openapi --package=myapp.core --method=process');
107
+ warn("");
108
+ warn("Use one of these flags:");
109
+ warn(" --entrypoint=package.module:function");
110
+ warn(" --package=package.module --method=function");
111
+ warn("");
112
+ warn("Examples:");
113
+ warn(" qhubctl openapi --entrypoint=myapp.main:handler");
114
+ warn(" qhubctl openapi --package=myapp.core --method=process");
111
115
  }
112
116
  }
113
117
  return isSupported;
114
118
  }
115
119
  function formatPythonError(stderr) {
116
120
  const commonErrors = {
117
- 'ModuleNotFoundError': 'Missing dependency. Install qhub-commons: pip install qhub-commons',
118
- 'ImportError': 'Cannot import module. Check your entrypoint path and ensure the module exists',
119
- 'AttributeError': 'Function not found. Check your method name and ensure it exists in the specified module',
120
- 'SyntaxError': 'Python syntax error in your code. Fix the syntax errors and try again',
121
- 'TypeError': 'Type error in your function. Ensure proper type annotations for parameters and return values',
121
+ ModuleNotFoundError: "Missing dependency. Install qhub-commons: pip install qhub-commons",
122
+ ImportError: "Cannot import module. Check your entrypoint path and ensure the module exists",
123
+ AttributeError: "Function not found. Check your method name and ensure it exists in the specified module",
124
+ SyntaxError: "Python syntax error in your code. Fix the syntax errors and try again",
125
+ TypeError: "Type error in your function. Ensure proper type annotations for parameters and return values",
122
126
  };
123
127
  for (const [errorType, helpText] of Object.entries(commonErrors)) {
124
128
  if (stderr.includes(errorType))
@@ -127,36 +131,48 @@ function formatPythonError(stderr) {
127
131
  return `Failed to generate OpenAPI description: ${stderr}`;
128
132
  }
129
133
  function validateCommandResult(result) {
130
- if (typeof result === 'object' && result !== null &&
131
- 'stdout' in result && 'stderr' in result &&
132
- typeof result.stdout === 'string' &&
133
- typeof result.stderr === 'string') {
134
+ if (typeof result === "object" &&
135
+ result !== null &&
136
+ "stdout" in result &&
137
+ "stderr" in result &&
138
+ typeof result.stdout === "string" &&
139
+ typeof result.stderr === "string") {
134
140
  const { stdout: out, stderr } = result;
135
141
  if (!out.trim()) {
136
- const detail = stderr.trim() ? formatPythonError(stderr) : 'OpenAPI generation returned empty output. Check your entrypoint and ensure it has proper type annotations.';
142
+ const detail = stderr.trim()
143
+ ? formatPythonError(stderr)
144
+ : "OpenAPI generation returned empty output. Check your entrypoint and ensure it has proper type annotations.";
137
145
  throw new Error(detail);
138
146
  }
139
147
  return { stdout: out, stderr };
140
148
  }
141
- throw new Error('Invalid command result format');
149
+ throw new Error("Invalid command result format");
142
150
  }
143
151
  async function generateFromCode(entrypoint) {
144
- let title = 'Service API';
152
+ let title = "Service API";
145
153
  let description;
146
154
  try {
147
155
  const serviceConfig = readServiceConfig(process.cwd());
148
- title = serviceConfig.name || 'Service API';
149
- description = serviceConfig.summary || '';
156
+ title = serviceConfig.name || "Service API";
157
+ description = serviceConfig.summary || "";
150
158
  }
151
159
  catch {
152
160
  warn(`Could not load qhub.json configuration. Using default title: ${title}`);
153
161
  }
154
- const pySnippet = 'import sys; from qhub.commons.openapi.generator import generate_openapi; ' +
155
- 'generate_openapi(entrypoint=sys.argv[1], title=sys.argv[2], description=sys.argv[3] or None)';
162
+ const pySnippet = "import sys; from qhub.commons.openapi.generator import generate_openapi; " +
163
+ "generate_openapi(entrypoint=sys.argv[1], title=sys.argv[2], description=sys.argv[3] or None)";
156
164
  try {
157
- const result = await executeAsyncCommand('uv', [
158
- 'run', '--no-sync', '--project', process.cwd(),
159
- 'python', '-c', pySnippet, entrypoint, title, description ?? '',
165
+ const result = await executeAsyncCommand("uv", [
166
+ "run",
167
+ "--no-sync",
168
+ "--project",
169
+ process.cwd(),
170
+ "python",
171
+ "-c",
172
+ pySnippet,
173
+ entrypoint,
174
+ title,
175
+ description ?? "",
160
176
  ]);
161
177
  const { stdout: out } = validateCommandResult(result);
162
178
  return out;
@@ -173,22 +189,22 @@ Examples:
173
189
  $ qhubctl openapi --entrypoint=src.program:run
174
190
  $ qhubctl openapi --package=src.program --method=run
175
191
  $ qhubctl openapi --package=src.program`;
176
- export const openapiCommand = new Command('openapi')
177
- .description('Generates the OpenAPI description for your project based on the parameter and return types of your run() method. ' +
178
- 'The output of this command will be used when creating or updating your service. ' +
179
- 'Supports PYTHON_TEMPLATE (with src/program.py) and DOCKER runtime (with explicit entrypoint). ' +
192
+ export const openapiCommand = new Command("openapi")
193
+ .description("Generates the OpenAPI description for your project based on the parameter and return types of your run() method. " +
194
+ "The output of this command will be used when creating or updating your service. " +
195
+ "Supports PYTHON_TEMPLATE (with src/program.py) and DOCKER runtime (with explicit entrypoint). " +
180
196
  'Requires uv and a valid uv project in the workspace. The "qhub-commons" dependency is installed/upgraded automatically as a dependency.')
181
- .option('-f, --force', 'Overwrite the output file if it already exists', false)
182
- .option('--file <value>', 'The file to write the OpenAPI description to')
183
- .option('--format <value>', `The format to generate the OpenAPI description [possible values: ${SUPPORTED_FORMATS.join(', ')}]`)
184
- .option('--entrypoint <value>', 'The entrypoint to your program in the format "package.module:function" (default: "src.program:run"). Overrides package and method flags.')
185
- .option('--package <value>', 'The package/module path to your program (e.g., "src.program" or "my_package.main"). Only used if entrypoint is not provided.')
186
- .option('--method <value>', 'The method/function name in your program (default: "run"). Only used if entrypoint is not provided.')
187
- .addHelpText('after', EXAMPLES)
197
+ .option("-f, --force", "Overwrite the output file if it already exists", false)
198
+ .option("--file <value>", "The file to write the OpenAPI description to")
199
+ .option("--format <value>", `The format to generate the OpenAPI description [possible values: ${SUPPORTED_FORMATS.join(", ")}]`)
200
+ .option("--entrypoint <value>", 'The entrypoint to your program in the format "package.module:function" (default: "src.program:run"). Overrides package and method flags.')
201
+ .option("--package <value>", 'The package/module path to your program (e.g., "src.program" or "my_package.main"). Only used if entrypoint is not provided.')
202
+ .option("--method <value>", 'The method/function name in your program (default: "run"). Only used if entrypoint is not provided.')
203
+ .addHelpText("after", EXAMPLES)
188
204
  .action(async (opts) => {
189
205
  checkDeprecatedFiles();
190
206
  if (opts.format && !SUPPORTED_FORMATS.includes(opts.format)) {
191
- throw new Error(`Unsupported format: ${opts.format}. Supported formats: ${SUPPORTED_FORMATS.join(', ')}`);
207
+ throw new Error(`Unsupported format: ${opts.format}. Supported formats: ${SUPPORTED_FORMATS.join(", ")}`);
192
208
  }
193
209
  let entrypoint;
194
210
  try {
@@ -197,48 +213,50 @@ export const openapiCommand = new Command('openapi')
197
213
  catch (err) {
198
214
  throw new Error(`Invalid entrypoint format: ${getErrorMessage(err)}\n\n` +
199
215
  'Expected format: "package.module:function"\nExample: "src.program:run"\n\n' +
200
- 'Use --help for more examples.', { cause: err });
216
+ "Use --help for more examples.", { cause: err });
201
217
  }
202
218
  const outputPath = await determineOutputPath(opts);
203
219
  const shouldGenerateFile = isProjectSupported(opts);
204
220
  if (!shouldGenerateFile) {
205
221
  warn(`Cannot generate OpenAPI description from your code. Supported configurations:\n` +
206
222
  ' \u2022 PYTHON_TEMPLATE runtime with "./src/program.py" file\n' +
207
- ' \u2022 DOCKER or PYTHON_TEMPLATE runtimes with explicit entrypoint (--entrypoint, or (--package, --method))');
223
+ " \u2022 DOCKER or PYTHON_TEMPLATE runtimes with explicit entrypoint (--entrypoint, or (--package, --method))");
208
224
  return;
209
225
  }
210
226
  // Ensure qhub-commons is installed (or upgraded) in the project venv
211
- stdout('Ensuring qhub-commons is up to date...');
212
- const addResult = spawnSync('uv', ['add', '--upgrade', 'qhub-commons', '--project', process.cwd()], { stdio: 'inherit' });
227
+ stdout("Ensuring qhub-commons is up to date...");
228
+ const addResult = spawnSync("uv", ["add", "--upgrade", "qhub-commons", "--project", process.cwd()], { stdio: "inherit" });
213
229
  if (addResult.status !== 0) {
214
- error('Failed to install qhub-commons via uv. ' +
215
- 'Make sure uv is installed and the workspace is a valid uv project.');
230
+ error("Failed to install qhub-commons via uv. " +
231
+ "Make sure uv is installed and the workspace is a valid uv project.");
216
232
  }
217
- const spin = spinner('Generating OpenAPI description');
233
+ const spin = spinner("Generating OpenAPI description");
218
234
  let fileContent;
219
235
  try {
220
236
  fileContent = await generateFromCode(entrypoint);
221
237
  }
222
238
  catch (err) {
223
- spin.fail('Failed');
239
+ spin.fail("Failed");
224
240
  throw err;
225
241
  }
226
242
  try {
227
- fs.writeFileSync(outputPath, fileContent, 'utf8');
228
- spin.succeed('Done');
243
+ fs.writeFileSync(outputPath, fileContent, "utf8");
244
+ spin.succeed("Done");
229
245
  }
230
246
  catch (err) {
231
- spin.fail('Failed');
232
- throw new Error(`Failed to write file ${outputPath}: ${getErrorMessage(err)}`, { cause: err });
247
+ spin.fail("Failed");
248
+ throw new Error(`Failed to write file ${outputPath}: ${getErrorMessage(err)}`, {
249
+ cause: err,
250
+ });
233
251
  }
234
252
  const relativePath = path.relative(process.cwd(), outputPath);
235
- const displayPath = relativePath.startsWith('..') ? outputPath : relativePath;
236
- stdout('');
253
+ const displayPath = relativePath.startsWith("..") ? outputPath : relativePath;
254
+ stdout("");
237
255
  stdout(`OpenAPI description created: ${displayPath}`);
238
256
  if (fs.statSync(outputPath).size > 0) {
239
- stdout('');
240
- stdout('Next steps:');
241
- stdout(' \u2022 Review the generated OpenAPI description');
242
- stdout(' \u2022 Use it to create or update your service');
257
+ stdout("");
258
+ stdout("Next steps:");
259
+ stdout(" \u2022 Review the generated OpenAPI description");
260
+ stdout(" \u2022 Use it to create or update your service");
243
261
  }
244
262
  });
@@ -1,5 +1,5 @@
1
- import { Command } from 'commander';
2
- import { HubPlatform } from '@quantum-hub/qhub-api/platform';
1
+ import { Command } from "commander";
2
+ import { HubPlatform } from "@quantum-hub/qhub-api/platform";
3
3
  export interface RunCommandOptions {
4
4
  tag: string[];
5
5
  storeInput: boolean;
@@ -1,18 +1,18 @@
1
- import { confirm } from '@inquirer/prompts';
2
- import { Command } from 'commander';
3
- import { waitUntil } from 'async-wait-until';
4
- import fsExtra from 'fs-extra';
1
+ import { confirm } from "@inquirer/prompts";
2
+ import { Command } from "commander";
3
+ import { waitUntil } from "async-wait-until";
4
+ import fsExtra from "fs-extra";
5
5
  const { readJsonSync } = fsExtra;
6
- import fs from 'node:fs';
7
- import path from 'node:path';
8
- import { error, spinner, stdout, warn } from '../lib/output.js';
9
- import { requireAuth } from '../model/command.js';
10
- import { defaultBasePath } from '../model/user-config.js';
11
- import QuantumHubService from '../service/quantum-hub-service.js';
12
- import { readServiceConfig } from '../service/service-config-service.js';
13
- import { HubPlatform } from '@quantum-hub/qhub-api/platform';
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import { error, spinner, stdout, warn } from "../lib/output.js";
9
+ import { requireAuth } from "../model/command.js";
10
+ import { defaultBasePath } from "../model/user-config.js";
11
+ import QuantumHubService from "../service/quantum-hub-service.js";
12
+ import { readServiceConfig } from "../service/service-config-service.js";
13
+ import { HubPlatform } from "@quantum-hub/qhub-api/platform";
14
14
  async function getInputFromFiles(filePaths) {
15
- const paths = filePaths.split(',').map(p => p.trim());
15
+ const paths = filePaths.split(",").map((p) => p.trim());
16
16
  const mergedInput = {};
17
17
  let hasValidFile = false;
18
18
  for (const filePath of paths) {
@@ -22,7 +22,7 @@ async function getInputFromFiles(filePaths) {
22
22
  }
23
23
  if (fs.existsSync(file)) {
24
24
  try {
25
- const content = readJsonSync(file, { encoding: 'utf8' });
25
+ const content = readJsonSync(file, { encoding: "utf8" });
26
26
  Object.assign(mergedInput, content);
27
27
  hasValidFile = true;
28
28
  }
@@ -32,7 +32,7 @@ async function getInputFromFiles(filePaths) {
32
32
  }
33
33
  }
34
34
  if (!hasValidFile) {
35
- const confirmed = await confirm({ message: 'No valid input files found. Run without input?' });
35
+ const confirmed = await confirm({ message: "No valid input files found. Run without input?" });
36
36
  if (confirmed)
37
37
  return {};
38
38
  process.exit(0);
@@ -42,7 +42,7 @@ async function getInputFromFiles(filePaths) {
42
42
  export function buildCreateJobRequest(serviceId, input, opts) {
43
43
  const payload = {
44
44
  serviceId,
45
- tags: ['cli', ...opts.tag],
45
+ tags: ["cli", ...opts.tag],
46
46
  input,
47
47
  };
48
48
  if (opts.storeInput) {
@@ -59,15 +59,15 @@ Examples:
59
59
  $ qhubctl run --input-files=./config.json,./overrides.json
60
60
  $ qhubctl run --store-input
61
61
  $ qhubctl run --tag experiment --tag run-42`;
62
- export const runCommand = new Command('run')
63
- .description('Creates a job execution')
64
- .argument('[serviceId]', 'The ID of the service to run (optional — reads from qhub.json if omitted)')
65
- .option('-i, --input <value>', 'Input as JSON string.')
66
- .option('--input-files <value>', 'Comma-separated paths to files containing input to be merged (default: ./input/data.json,./input/params.json).')
67
- .option('--store-input', 'Persist the input data alongside the job on the server.', false)
68
- .option('--tag <value>', 'Tag to attach to the job. Repeat the flag to attach multiple tags.', (value, previous) => [...previous, value], [])
69
- .option('--detached', 'Executes the job in detached mode, i.e., without waiting for it to finish.', false)
70
- .addHelpText('after', EXAMPLES)
62
+ export const runCommand = new Command("run")
63
+ .description("Creates a job execution")
64
+ .argument("[serviceId]", "The ID of the service to run (optional — reads from qhub.json if omitted)")
65
+ .option("-i, --input <value>", "Input as JSON string.")
66
+ .option("--input-files <value>", "Comma-separated paths to files containing input to be merged (default: ./input/data.json,./input/params.json).")
67
+ .option("--store-input", "Persist the input data alongside the job on the server.", false)
68
+ .option("--tag <value>", "Tag to attach to the job. Repeat the flag to attach multiple tags.", (value, previous) => [...previous, value], [])
69
+ .option("--detached", "Executes the job in detached mode, i.e., without waiting for it to finish.", false)
70
+ .addHelpText("after", EXAMPLES)
71
71
  .action(async (serviceId, opts) => {
72
72
  const userConfig = await requireAuth();
73
73
  const hubService = new QuantumHubService(userConfig);
@@ -81,7 +81,7 @@ export const runCommand = new Command('run')
81
81
  }
82
82
  }
83
83
  if (!serviceId) {
84
- error('Missing service ID. Please provide a service ID as argument or set it in the qhub.json of your project.');
84
+ error("Missing service ID. Please provide a service ID as argument or set it in the qhub.json of your project.");
85
85
  }
86
86
  const service = await hubService.getService(serviceId);
87
87
  let input;
@@ -90,14 +90,17 @@ export const runCommand = new Command('run')
90
90
  input = JSON.parse(opts.input);
91
91
  }
92
92
  catch {
93
- error('Invalid JSON provided.');
93
+ error("Invalid JSON provided.");
94
94
  }
95
95
  }
96
96
  else {
97
- input = await getInputFromFiles(opts.inputFiles || './input/data.json,./input/params.json');
97
+ input = await getInputFromFiles(opts.inputFiles || "./input/data.json,./input/params.json");
98
98
  }
99
99
  let job;
100
- const payload = buildCreateJobRequest(service.id, input, { tag: opts.tag, storeInput: opts.storeInput });
100
+ const payload = buildCreateJobRequest(service.id, input, {
101
+ tag: opts.tag,
102
+ storeInput: opts.storeInput,
103
+ });
101
104
  job = await hubService.runJob(payload);
102
105
  if (opts.detached) {
103
106
  stdout(`Job (${job.id}) created.`);
@@ -119,24 +122,24 @@ export const runCommand = new Command('run')
119
122
  // ignore — status printed below
120
123
  }
121
124
  const basePath = userConfig.endpoint?.basePath || defaultBasePath;
122
- const dashboardBase = basePath.replace('//api.hub.', '//dashboard.hub.');
125
+ const dashboardBase = basePath.replace("//api.hub.", "//dashboard.hub.");
123
126
  const jobDetailsLink = `${dashboardBase}/service-jobs/${job.id}`;
124
127
  switch (job.status) {
125
128
  case HubPlatform.ServiceJobDto.Status.Cancelled:
126
- spin.warn('Job canceled.');
129
+ spin.warn("Job canceled.");
127
130
  stdout(`See details at \u001B]8;;${jobDetailsLink}\u0007${jobDetailsLink}\u001B]8;;\u0007`);
128
131
  break;
129
132
  case HubPlatform.ServiceJobDto.Status.Failed:
130
- spin.fail('Job failed.');
133
+ spin.fail("Job failed.");
131
134
  stdout(`See details at \u001B]8;;${jobDetailsLink}\u0007${jobDetailsLink}\u001B]8;;\u0007`);
132
135
  break;
133
136
  case HubPlatform.ServiceJobDto.Status.Succeeded:
134
- spin.succeed('Job succeeded.');
137
+ spin.succeed("Job succeeded.");
135
138
  stdout(`See result at \u001B]8;;${jobDetailsLink}\u0007${jobDetailsLink}\u001B]8;;\u0007`);
136
139
  break;
137
140
  default:
138
141
  spin.stop();
139
- stdout('Timeout polling status.');
142
+ stdout("Timeout polling status.");
140
143
  stdout(`See status details at \u001B]8;;${jobDetailsLink}\u0007${jobDetailsLink}\u001B]8;;\u0007`);
141
144
  }
142
145
  });
@@ -1,2 +1,2 @@
1
- import { Command } from 'commander';
1
+ import { Command } from "commander";
2
2
  export declare const serveCommand: Command;
@@ -1,35 +1,46 @@
1
- import { Command } from 'commander';
2
- import { spawnSync } from 'node:child_process';
3
- import { error, stdout } from '../lib/output.js';
4
- const LOG_LEVELS = ['TRACE', 'DEBUG', 'INFO', 'SUCCESS', 'WARNING', 'ERROR', 'CRITICAL'];
1
+ import { Command } from "commander";
2
+ import { spawnSync } from "node:child_process";
3
+ import { error, stdout } from "../lib/output.js";
4
+ const LOG_LEVELS = ["TRACE", "DEBUG", "INFO", "SUCCESS", "WARNING", "ERROR", "CRITICAL"];
5
5
  const EXAMPLES = `
6
6
  Examples:
7
7
  $ qhubctl serve
8
8
  $ qhubctl serve -p 8081
9
9
  $ qhubctl serve --workspace /path/to/workspace`;
10
- export const serveCommand = new Command('serve')
11
- .description('Runs your project locally via qhub-serve inside the workspace virtual environment, ' +
12
- 'exposing the same HTTP endpoints as Kipu Quantum Hub (start execution, check status, ' +
13
- 'cancel, retrieve results).')
14
- .option('-p, --port <number>', 'The port on which the local web server accepts requests', '8081')
15
- .option('--log-level <level>', `Log level for qhub-serve. Possible values: ${LOG_LEVELS.join(', ')}`, 'INFO')
16
- .option('-w, --workspace <path>', 'Path to the workspace directory (must be a valid uv project). Defaults to the current directory.')
17
- .addHelpText('after', EXAMPLES)
10
+ export const serveCommand = new Command("serve")
11
+ .description("Runs your project locally via qhub-serve inside the workspace virtual environment, " +
12
+ "exposing the same HTTP endpoints as Kipu Quantum Hub (start execution, check status, " +
13
+ "cancel, retrieve results).")
14
+ .option("-p, --port <number>", "The port on which the local web server accepts requests", "8081")
15
+ .option("--log-level <level>", `Log level for qhub-serve. Possible values: ${LOG_LEVELS.join(", ")}`, "INFO")
16
+ .option("-w, --workspace <path>", "Path to the workspace directory (must be a valid uv project). Defaults to the current directory.")
17
+ .addHelpText("after", EXAMPLES)
18
18
  .action(async (opts) => {
19
19
  const workspaceDir = opts.workspace ?? process.cwd();
20
20
  const port = opts.port;
21
21
  const logLevel = opts.logLevel;
22
22
  // Step 1: Add (or upgrade) qhub-serve as a dev dependency
23
- stdout('Ensuring qhub-serve is up to date...');
24
- const addResult = spawnSync('uv', ['add', '--dev', '--upgrade', 'qhub-serve', '--project', workspaceDir], { stdio: 'inherit' });
23
+ stdout("Ensuring qhub-serve is up to date...");
24
+ const addResult = spawnSync("uv", ["add", "--dev", "--upgrade", "qhub-serve", "--project", workspaceDir], { stdio: "inherit" });
25
25
  if (addResult.status !== 0) {
26
- error('Failed to install qhub-serve via uv. ' +
27
- 'Make sure uv is installed and the workspace is a valid uv project.');
26
+ error("Failed to install qhub-serve via uv. " +
27
+ "Make sure uv is installed and the workspace is a valid uv project.");
28
28
  }
29
29
  // Step 2: Run qhub-serve inside the workspace venv
30
- stdout('\nStarting qhub-serve...');
31
- const runResult = spawnSync('uv', ['run', '--project', workspaceDir, 'qhub-serve', '--workspace', workspaceDir, '--port', String(port), '--log-level', logLevel], { stdio: 'inherit' });
30
+ stdout("\nStarting qhub-serve...");
31
+ const runResult = spawnSync("uv", [
32
+ "run",
33
+ "--project",
34
+ workspaceDir,
35
+ "qhub-serve",
36
+ "--workspace",
37
+ workspaceDir,
38
+ "--port",
39
+ String(port),
40
+ "--log-level",
41
+ logLevel,
42
+ ], { stdio: "inherit" });
32
43
  if (runResult.status !== 0) {
33
- error('qhub-serve exited with a non-zero status code.');
44
+ error("qhub-serve exited with a non-zero status code.");
34
45
  }
35
46
  });
@@ -1,5 +1,5 @@
1
- import { HubPlatform } from '@quantum-hub/qhub-api/platform';
2
- import { Command } from 'commander';
1
+ import { HubPlatform } from "@quantum-hub/qhub-api/platform";
2
+ import { Command } from "commander";
3
3
  export declare function formatLogEntry(entry: HubPlatform.LogEntry, opts: {
4
4
  isTTY: boolean;
5
5
  }): string;