@quantum-hub/qhubctl 2.1.0 → 2.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.
@@ -3,22 +3,13 @@ import fs from 'fs-extra';
3
3
  import path from 'node:path';
4
4
  import { zipProjectFolder } from '../lib/up/zip.js';
5
5
  import { enforceRequiredFiles } from '../lib/up/required-files.js';
6
+ import { QHUBIGNORE_HELP } from '../lib/up/qhubignore-help.js';
6
7
  import { error, spinner, stdout } from '../lib/output.js';
7
8
  const EXAMPLES = `
8
9
  Examples:
9
- $ qhubctl compress
10
-
11
- The '.qhubignore' file uses gitignore syntax. To ship only 'src' and the
12
- Dockerfile, re-include the directory AND its contents:
13
-
14
- *
15
- !src
16
- !src/**
17
- !Dockerfile
18
-
19
- These paths are always excluded: .git, node_modules, .venv, __pycache__, service.zip`;
10
+ $ qhubctl compress`;
20
11
  export const compressCommand = new Command('compress')
21
- .description("Compresses the current project and creates a ZIP file. Use a '.qhubignore' file (gitignore syntax) to exclude files and directories from the ZIP file.")
12
+ .description(`Compresses the current project and creates a ZIP file. ${QHUBIGNORE_HELP}`)
22
13
  .addHelpText('after', EXAMPLES)
23
14
  .action(async () => {
24
15
  const spin = spinner('Compressing project');
@@ -1,8 +1,8 @@
1
1
  import { password } from '@inquirer/prompts';
2
2
  import { Command } from 'commander';
3
3
  import { spinner } from '../lib/output.js';
4
- import { configDir, getUserConfig, showCustomBasePathWarning } from '../model/command.js';
5
- import { AuthType, defaultBasePath } from '../model/user-config.js';
4
+ import { configDir, getUserConfig, showBasePathRevertNotice, showCustomBasePathWarning } from '../model/command.js';
5
+ import { AuthType, defaultBasePath, hasCustomBasePath, resolveEndpoint } from '../model/user-config.js';
6
6
  import AuthService from '../service/auth-service.js';
7
7
  import { writeUserConfig } from '../service/user-config-service.js';
8
8
  const EXAMPLES = `
@@ -23,19 +23,14 @@ export const loginCommand = new Command('login')
23
23
  if (!token) {
24
24
  throw new Error('Invalid credentials. Please try again.');
25
25
  }
26
+ const userConfig = getUserConfig();
27
+ const endpoint = resolveEndpoint(userConfig, basePath);
28
+ const revertingToDefault = !basePath && hasCustomBasePath(userConfig);
26
29
  if (basePath) {
27
30
  showCustomBasePathWarning();
28
31
  }
29
32
  const spin = spinner('Login...');
30
- const userConfig = getUserConfig();
31
- const tempUserConfig = basePath ? {
32
- ...userConfig,
33
- endpoint: {
34
- basePath,
35
- defaultHeaders: userConfig.endpoint?.defaultHeaders || {}
36
- }
37
- } : userConfig;
38
- const user = await new AuthService(tempUserConfig).authenticate(token);
33
+ const user = await new AuthService({ ...userConfig, endpoint }).authenticate(token);
39
34
  const newUserConfig = {
40
35
  ...userConfig,
41
36
  auth: {
@@ -47,13 +42,11 @@ export const loginCommand = new Command('login')
47
42
  displayName: `${user.firstname} ${user.lastname}`,
48
43
  isOrganization: false,
49
44
  },
50
- ...(basePath && {
51
- endpoint: {
52
- basePath,
53
- defaultHeaders: userConfig.endpoint?.defaultHeaders || {}
54
- }
55
- })
45
+ endpoint,
56
46
  };
57
47
  writeUserConfig(configDir, newUserConfig);
58
48
  spin.succeed('Login successful');
49
+ if (revertingToDefault) {
50
+ showBasePathRevertNotice();
51
+ }
59
52
  });
@@ -1,6 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import { spinner } from '../lib/output.js';
3
- import { configDir, getUserConfig } from '../model/command.js';
3
+ import { configDir, getUserConfig, showBasePathRevertNotice } from '../model/command.js';
4
+ import { hasCustomBasePath } from '../model/user-config.js';
4
5
  import { writeUserConfig } from '../service/user-config-service.js';
5
6
  const EXAMPLES = `
6
7
  Examples:
@@ -9,11 +10,17 @@ export const logoutCommand = new Command('logout')
9
10
  .description('Logout')
10
11
  .addHelpText('after', EXAMPLES)
11
12
  .action(async () => {
13
+ const userConfig = getUserConfig();
14
+ const revertingToDefault = hasCustomBasePath(userConfig);
12
15
  const spin = spinner('Logout');
13
16
  writeUserConfig(configDir, {
14
- ...getUserConfig(),
17
+ ...userConfig,
15
18
  auth: undefined,
16
19
  context: undefined,
20
+ endpoint: undefined,
17
21
  });
18
22
  spin.succeed('Logout successful');
23
+ if (revertingToDefault) {
24
+ showBasePathRevertNotice();
25
+ }
19
26
  });
@@ -7,6 +7,7 @@ import path from 'node:path';
7
7
  import { error, spinner, stdout, warn } from '../lib/output.js';
8
8
  import { streamToBlob } from '../lib/stream-to-blob.js';
9
9
  import { getDescriptionText } from '../lib/up/get-description.js';
10
+ import { QHUBIGNORE_HELP } from '../lib/up/qhubignore-help.js';
10
11
  import { enforceRequiredFiles } from '../lib/up/required-files.js';
11
12
  import { buildServiceSource, isRegistrySource, resolveRegistryCredentials } from '../lib/up/source.js';
12
13
  import { zipProjectFolder } from '../lib/up/zip.js';
@@ -233,7 +234,7 @@ Examples:
233
234
  $ qhubctl up --name "test-service" --no-save-id
234
235
  $ qhubctl up --image ghcr.io/acme/my-service:1.2.3`;
235
236
  export const upCommand = new Command('up')
236
- .description('Creates or updates a service')
237
+ .description(`Creates or updates a service.\n\n${QHUBIGNORE_HELP}`)
237
238
  .option('--silent', 'Suppresses all outputs, helpful when executed in a CI/CD pipeline.', false)
238
239
  .option('--no-save-id', 'Prevents storing the created service ID in qhub.json file.')
239
240
  .option('--name <value>', 'Override the service name from qhub.json with a custom name.')
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Shared Markdown help text describing '.qhubignore' behaviour. Used in the
3
+ * command descriptions of both `compress` and `up`, which each package the
4
+ * project into a ZIP and honour the same ignore rules. Markdown so the
5
+ * generated CLI reference (docs:generate) renders the fenced code block.
6
+ */
7
+ export declare const QHUBIGNORE_HELP: string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shared Markdown help text describing '.qhubignore' behaviour. Used in the
3
+ * command descriptions of both `compress` and `up`, which each package the
4
+ * project into a ZIP and honour the same ignore rules. Markdown so the
5
+ * generated CLI reference (docs:generate) renders the fenced code block.
6
+ */
7
+ export const QHUBIGNORE_HELP = [
8
+ "Use a '.qhubignore' file (gitignore syntax) to exclude files and directories from the ZIP file.",
9
+ '',
10
+ '`.git`, `node_modules`, `.venv`, `__pycache__`, and `service.zip` are always excluded.',
11
+ "Because `.qhubignore` uses gitignore syntax, re-including a directory's contents requires both the directory and its contents — for example, to ship only `src` and the `Dockerfile`:",
12
+ '',
13
+ '```',
14
+ '*',
15
+ '!src',
16
+ '!src/**',
17
+ '!Dockerfile',
18
+ '```',
19
+ ].join('\n');
@@ -3,6 +3,7 @@ export { configDir } from '../lib/config-dir.js';
3
3
  export declare function getUserConfig(): UserConfig;
4
4
  export declare function getContextDisplayString(userConfig: UserConfig): string;
5
5
  export declare function showCustomBasePathWarning(): void;
6
+ export declare function showBasePathRevertNotice(): void;
6
7
  /**
7
8
  * Validates that the user is authenticated and returns the current UserConfig.
8
9
  * Replaces AuthenticatedCommand.init().
@@ -17,6 +17,10 @@ export function showCustomBasePathWarning() {
17
17
  warn('Using custom base path. This is intended for internal development use only.');
18
18
  stdout();
19
19
  }
20
+ export function showBasePathRevertNotice() {
21
+ warn('Custom base path removed. Using the default base path.');
22
+ stdout();
23
+ }
20
24
  /**
21
25
  * Validates that the user is authenticated and returns the current UserConfig.
22
26
  * Replaces AuthenticatedCommand.init().
@@ -20,3 +20,12 @@ export default interface UserConfig {
20
20
  context?: Context;
21
21
  endpoint?: Endpoint;
22
22
  }
23
+ /**
24
+ * Resolves the endpoint to use for a login attempt.
25
+ *
26
+ * A truthy `basePath` always yields a custom endpoint (carrying over any
27
+ * previously stored defaultHeaders); an absent `basePath` always reverts
28
+ * to the default base path, regardless of what `current` holds.
29
+ */
30
+ export declare function resolveEndpoint(current: UserConfig, basePath?: string): Endpoint | undefined;
31
+ export declare function hasCustomBasePath(config: UserConfig): boolean;
@@ -3,3 +3,22 @@ export var AuthType;
3
3
  AuthType["API_KEY"] = "API_KEY";
4
4
  })(AuthType || (AuthType = {}));
5
5
  export const defaultBasePath = 'https://api.hub.kipu-quantum.com';
6
+ /**
7
+ * Resolves the endpoint to use for a login attempt.
8
+ *
9
+ * A truthy `basePath` always yields a custom endpoint (carrying over any
10
+ * previously stored defaultHeaders); an absent `basePath` always reverts
11
+ * to the default base path, regardless of what `current` holds.
12
+ */
13
+ export function resolveEndpoint(current, basePath) {
14
+ if (!basePath) {
15
+ return undefined;
16
+ }
17
+ return {
18
+ basePath,
19
+ defaultHeaders: current.endpoint?.defaultHeaders ?? {},
20
+ };
21
+ }
22
+ export function hasCustomBasePath(config) {
23
+ return Boolean(config.endpoint?.basePath);
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantum-hub/qhubctl",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Kipu Quantum Hub CLI",
5
5
  "author": "Kipu Quantum GmbH",
6
6
  "contributors": [