@quantum-hub/qhubctl 1.3.0 → 1.4.1
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/init.js +14 -4
- package/dist/commands/services-list.d.ts +2 -0
- package/dist/commands/{services.js → services-list.js} +6 -6
- package/dist/lib/init-user-input.d.ts +2 -4
- package/dist/lib/init-user-input.js +8 -6
- package/dist/lib/repository.d.ts +9 -1
- package/dist/lib/repository.js +41 -2
- package/dist/program.js +6 -3
- package/package.json +3 -2
- package/dist/commands/services.d.ts +0 -2
package/dist/commands/init.js
CHANGED
|
@@ -3,10 +3,11 @@ import fs from 'fs-extra';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { DEFAULT_STARTER, promptUserInput } from '../lib/init-user-input.js';
|
|
5
5
|
import { randomName } from '../lib/random-name.js';
|
|
6
|
-
import { downloadRepository, extractStarter } from '../lib/repository.js';
|
|
6
|
+
import { cloneRepository, downloadRepository, extractStarter } from '../lib/repository.js';
|
|
7
7
|
import { error, stdout } from '../lib/output.js';
|
|
8
8
|
import { Runtime } from '../model/managed-service-config.js';
|
|
9
9
|
import { writeServiceConfig } from '../service/service-config-service.js';
|
|
10
|
+
import { getUserConfig } from '../model/command.js';
|
|
10
11
|
const EXAMPLES = `
|
|
11
12
|
Examples:
|
|
12
13
|
$ qhubctl init`;
|
|
@@ -51,9 +52,18 @@ export const initCommand = new Command('init')
|
|
|
51
52
|
}
|
|
52
53
|
writeServiceConfig(targetDirectory, serviceConfig);
|
|
53
54
|
if (input.starter) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
if (input.starter.requiresAuth) {
|
|
56
|
+
const userConfig = getUserConfig();
|
|
57
|
+
if (!userConfig.auth?.value) {
|
|
58
|
+
error('This starter template requires authentication. Use the "login" command to log in first.');
|
|
59
|
+
}
|
|
60
|
+
await cloneRepository(input.starter.url, targetDirectory, userConfig.auth.value);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const zip = await downloadRepository(input.starter.url);
|
|
64
|
+
const starterName = path.basename(input.starter.url, '.zip');
|
|
65
|
+
extractStarter(zip, starterName, targetDirectory);
|
|
66
|
+
}
|
|
57
67
|
}
|
|
58
68
|
stdout('\u{1F389} Initialized project. Happy hacking!');
|
|
59
69
|
if (input.starter) {
|
|
@@ -5,12 +5,12 @@ import { requireAuth } from '../model/command.js';
|
|
|
5
5
|
import QuantumHubService from '../service/quantum-hub-service.js';
|
|
6
6
|
const EXAMPLES = `
|
|
7
7
|
Examples:
|
|
8
|
-
$ qhubctl services
|
|
9
|
-
$ qhubctl services --json
|
|
10
|
-
$ qhubctl services --name "my-service"
|
|
11
|
-
$ qhubctl services --name "my-service" --json
|
|
12
|
-
$ qhubctl services --json --all`;
|
|
13
|
-
export const
|
|
8
|
+
$ qhubctl services list
|
|
9
|
+
$ qhubctl services list --json
|
|
10
|
+
$ qhubctl services list --name "my-service"
|
|
11
|
+
$ qhubctl services list --name "my-service" --json
|
|
12
|
+
$ qhubctl services list --json --all`;
|
|
13
|
+
export const servicesListCommand = new Command('list')
|
|
14
14
|
.description('List all services of the current context.')
|
|
15
15
|
.option('--json', 'Output in JSON format', false)
|
|
16
16
|
.option('--name <value>', 'Filter services by name (case-insensitive full match)')
|
|
@@ -2,6 +2,7 @@ import { GpuType, Runtime } from '../model/managed-service-config.js';
|
|
|
2
2
|
export interface StarterInput {
|
|
3
3
|
url: string;
|
|
4
4
|
runtime: Runtime;
|
|
5
|
+
requiresAuth?: boolean;
|
|
5
6
|
}
|
|
6
7
|
export interface UserInput {
|
|
7
8
|
cpu: number;
|
|
@@ -11,8 +12,5 @@ export interface UserInput {
|
|
|
11
12
|
name: string;
|
|
12
13
|
starter?: StarterInput;
|
|
13
14
|
}
|
|
14
|
-
export declare const DEFAULT_STARTER:
|
|
15
|
-
url: string;
|
|
16
|
-
runtime: Runtime;
|
|
17
|
-
};
|
|
15
|
+
export declare const DEFAULT_STARTER: StarterInput;
|
|
18
16
|
export declare const promptUserInput: (defaultName: string) => Promise<UserInput>;
|
|
@@ -2,7 +2,7 @@ import { input, select, number } from '@inquirer/prompts';
|
|
|
2
2
|
import { GpuType, Runtime } from '../model/managed-service-config.js';
|
|
3
3
|
export const DEFAULT_STARTER = {
|
|
4
4
|
url: 'https://gitlab.com/planqk-foss/planqk-starter-python/-/archive/main/planqk-starter-python-main.zip',
|
|
5
|
-
runtime: Runtime.PYTHON_TEMPLATE
|
|
5
|
+
runtime: Runtime.PYTHON_TEMPLATE,
|
|
6
6
|
};
|
|
7
7
|
export const promptUserInput = async (defaultName) => {
|
|
8
8
|
const name = await input({
|
|
@@ -19,15 +19,17 @@ export const promptUserInput = async (defaultName) => {
|
|
|
19
19
|
{
|
|
20
20
|
name: 'Docker Starter (setup for experienced users)',
|
|
21
21
|
value: {
|
|
22
|
-
url: 'https://
|
|
23
|
-
runtime: Runtime.DOCKER
|
|
22
|
+
url: 'https://repository.hub.kipu-quantum.com/cec4a31d-5856-4387-b4cf-718345803558/qhub-starter-docker.git',
|
|
23
|
+
runtime: Runtime.DOCKER,
|
|
24
|
+
requiresAuth: true,
|
|
24
25
|
},
|
|
25
26
|
},
|
|
26
27
|
{
|
|
27
28
|
name: 'NVIDIA CUDA-Q Starter (for GPU accelerated applications; setup for experienced users)',
|
|
28
29
|
value: {
|
|
29
|
-
url: 'https://
|
|
30
|
-
runtime: Runtime.DOCKER
|
|
30
|
+
url: 'https://repository.hub.kipu-quantum.com/cec4a31d-5856-4387-b4cf-718345803558/qhub-starter-gpu.git',
|
|
31
|
+
runtime: Runtime.DOCKER,
|
|
32
|
+
requiresAuth: true,
|
|
31
33
|
},
|
|
32
34
|
},
|
|
33
35
|
{ name: 'None (creates "qhub.json" in current directory)', value: undefined },
|
|
@@ -61,7 +63,7 @@ export const promptUserInput = async (defaultName) => {
|
|
|
61
63
|
},
|
|
62
64
|
});
|
|
63
65
|
let gpu = undefined;
|
|
64
|
-
if (!starter || starter.url.includes('
|
|
66
|
+
if (!starter || starter.url.includes('qhub-starter-gpu')) {
|
|
65
67
|
gpu = await select({
|
|
66
68
|
message: 'Choose your GPU configuration',
|
|
67
69
|
choices: [
|
package/dist/lib/repository.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import AdmZip from 'adm-zip';
|
|
2
|
-
export declare const downloadRepository: (url: string) => Promise<AdmZip>;
|
|
2
|
+
export declare const downloadRepository: (url: string, apiKey?: string) => Promise<AdmZip>;
|
|
3
|
+
/**
|
|
4
|
+
* Clones a git repository (shallow, depth=1) into targetDirectory and removes
|
|
5
|
+
* the .git metadata afterwards, leaving only the working-tree files.
|
|
6
|
+
*
|
|
7
|
+
* Authentication uses HTTP Basic Auth with username "planqk" and the provided
|
|
8
|
+
* API key as the password.
|
|
9
|
+
*/
|
|
10
|
+
export declare const cloneRepository: (url: string, targetDirectory: string, apiKey: string) => Promise<void>;
|
|
3
11
|
export declare const extractStarter: (zip: AdmZip, starterName: string, targetDirectory: string) => void;
|
package/dist/lib/repository.js
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import AdmZip from 'adm-zip';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import git from 'isomorphic-git';
|
|
4
|
+
import http from 'isomorphic-git/http/node';
|
|
5
|
+
import nodeFs from 'node:fs';
|
|
2
6
|
import { debugEnabled } from './debug.js';
|
|
3
7
|
import { fetchOrThrow, PlatformError } from './fetch.js';
|
|
4
|
-
export const downloadRepository = async (url) => {
|
|
8
|
+
export const downloadRepository = async (url, apiKey) => {
|
|
9
|
+
const headers = {};
|
|
10
|
+
if (apiKey) {
|
|
11
|
+
// Use HTTP Basic Auth: username "planqk", password is the API key
|
|
12
|
+
const credentials = Buffer.from(`qhubctl:${apiKey}`).toString('base64');
|
|
13
|
+
headers['Authorization'] = `Basic ${credentials}`;
|
|
14
|
+
}
|
|
5
15
|
const config = {
|
|
6
16
|
cache: 'no-cache',
|
|
7
17
|
method: 'GET',
|
|
8
18
|
mode: 'same-origin',
|
|
9
19
|
referrerPolicy: 'no-referrer',
|
|
10
|
-
|
|
20
|
+
headers,
|
|
11
21
|
};
|
|
12
22
|
let zip;
|
|
13
23
|
try {
|
|
@@ -27,6 +37,35 @@ export const downloadRepository = async (url) => {
|
|
|
27
37
|
}
|
|
28
38
|
return zip;
|
|
29
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Clones a git repository (shallow, depth=1) into targetDirectory and removes
|
|
42
|
+
* the .git metadata afterwards, leaving only the working-tree files.
|
|
43
|
+
*
|
|
44
|
+
* Authentication uses HTTP Basic Auth with username "planqk" and the provided
|
|
45
|
+
* API key as the password.
|
|
46
|
+
*/
|
|
47
|
+
export const cloneRepository = async (url, targetDirectory, apiKey) => {
|
|
48
|
+
const credentials = Buffer.from(`qhubctl:${apiKey}`).toString('base64');
|
|
49
|
+
try {
|
|
50
|
+
await git.clone({
|
|
51
|
+
fs: nodeFs,
|
|
52
|
+
http,
|
|
53
|
+
dir: targetDirectory,
|
|
54
|
+
url,
|
|
55
|
+
depth: 1,
|
|
56
|
+
singleBranch: true,
|
|
57
|
+
// Inject Authorization on every request so the proxy doesn't need to
|
|
58
|
+
// issue a 401 challenge first (onAuth is only triggered after a 401).
|
|
59
|
+
headers: { 'Authorization': `Basic ${credentials}` },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error(error);
|
|
64
|
+
throw new Error('Failed to download starter template. Make sure you are logged in and try again.');
|
|
65
|
+
}
|
|
66
|
+
// Remove git metadata so the project directory is a clean working tree
|
|
67
|
+
await fs.remove(`${targetDirectory}/.git`);
|
|
68
|
+
};
|
|
30
69
|
export const extractStarter = (zip, starterName, targetDirectory) => {
|
|
31
70
|
const templateFolder = `${starterName}/`;
|
|
32
71
|
for (const entry of zip.getEntries()) {
|
package/dist/program.js
CHANGED
|
@@ -11,7 +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 {
|
|
14
|
+
import { servicesListCommand } from './commands/services-list.js';
|
|
15
15
|
import { setContextCommand } from './commands/set-context.js';
|
|
16
16
|
import { upCommand } from './commands/up.js';
|
|
17
17
|
const require = createRequire(import.meta.url);
|
|
@@ -32,6 +32,9 @@ program.addCommand(logoutCommand);
|
|
|
32
32
|
program.addCommand(openapiCommand);
|
|
33
33
|
program.addCommand(runCommand);
|
|
34
34
|
program.addCommand(serveCommand);
|
|
35
|
+
// Nested: services <subcommand>
|
|
36
|
+
const servicesCommand = new Command('services').description('Manage services');
|
|
37
|
+
servicesCommand.addCommand(servicesListCommand);
|
|
35
38
|
program.addCommand(servicesCommand);
|
|
36
39
|
program.addCommand(setContextCommand);
|
|
37
40
|
program.addCommand(upCommand);
|
|
@@ -41,7 +44,7 @@ datapoolCommand.addCommand(datapoolUploadCommand);
|
|
|
41
44
|
program.addCommand(datapoolCommand);
|
|
42
45
|
// Surface a clear error for unknown commands instead of silently doing nothing
|
|
43
46
|
program.on('command:*', (operands) => {
|
|
44
|
-
process.stderr.write(`\n
|
|
45
|
-
process.stderr.write(` Run 'qhubctl
|
|
47
|
+
process.stderr.write(`\n Error: Unknown command '${operands[0]}'\n\n`);
|
|
48
|
+
process.stderr.write(` Run 'qhubctl help' to see available commands.\n\n`);
|
|
46
49
|
process.exit(1);
|
|
47
50
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quantum-hub/qhubctl",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Quantum Hub CLI",
|
|
3
|
+
"version": "1.4.1",
|
|
4
|
+
"description": "Kipu Quantum Hub CLI",
|
|
5
5
|
"author": "Kipu Quantum GmbH",
|
|
6
6
|
"contributors": [
|
|
7
7
|
"Wurster, Michael <michael.wurster@kipu-quantum.com>",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"commander": "^14",
|
|
33
33
|
"fs-extra": "^11",
|
|
34
34
|
"ignore": "^7",
|
|
35
|
+
"isomorphic-git": "^1.37.2",
|
|
35
36
|
"ora": "^9",
|
|
36
37
|
"tty-table": "^5",
|
|
37
38
|
"update-notifier": "^7",
|