@getpaseo/cli 0.3.0-beta.4 → 0.3.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.
Files changed (41) hide show
  1. package/dist/commands/hub/authority.d.ts +17 -0
  2. package/dist/commands/hub/authority.js +18 -0
  3. package/dist/commands/hub/client.d.ts +55 -3
  4. package/dist/commands/hub/client.js +158 -38
  5. package/dist/commands/hub/connect.d.ts +21 -0
  6. package/dist/commands/hub/connect.js +34 -0
  7. package/dist/commands/hub/credentials.d.ts +21 -0
  8. package/dist/commands/hub/credentials.js +143 -0
  9. package/dist/commands/hub/daemon-client.d.ts +27 -0
  10. package/dist/commands/hub/daemon-client.js +14 -0
  11. package/dist/commands/hub/deploy-input.js +28 -28
  12. package/dist/commands/hub/deploy.d.ts +23 -3
  13. package/dist/commands/hub/deploy.js +51 -42
  14. package/dist/commands/hub/disconnect.d.ts +16 -0
  15. package/dist/commands/hub/disconnect.js +24 -0
  16. package/dist/commands/hub/error.d.ts +1 -1
  17. package/dist/commands/hub/error.js +2 -2
  18. package/dist/commands/hub/help.d.ts +3 -0
  19. package/dist/commands/hub/help.js +5 -0
  20. package/dist/commands/hub/index.d.ts +15 -25
  21. package/dist/commands/hub/index.js +64 -74
  22. package/dist/commands/hub/{device-authorization.d.ts → login-flow.d.ts} +12 -15
  23. package/dist/commands/hub/login-flow.js +78 -0
  24. package/dist/commands/hub/login.d.ts +21 -0
  25. package/dist/commands/hub/login.js +34 -0
  26. package/dist/commands/hub/logout.d.ts +31 -0
  27. package/dist/commands/hub/logout.js +65 -0
  28. package/dist/commands/hub/origin.d.ts +2 -0
  29. package/dist/commands/hub/origin.js +27 -0
  30. package/dist/commands/hub/projects.d.ts +24 -0
  31. package/dist/commands/hub/projects.js +49 -0
  32. package/dist/commands/hub/reporter.d.ts +10 -0
  33. package/dist/commands/hub/reporter.js +11 -0
  34. package/dist/commands/hub/status-output.d.ts +13 -0
  35. package/dist/commands/hub/status-output.js +30 -0
  36. package/dist/commands/schedule/types.d.ts +1 -4
  37. package/dist/output/types.d.ts +1 -1
  38. package/package.json +4 -4
  39. package/dist/commands/hub/cloud-device-authorization.d.ts +0 -45
  40. package/dist/commands/hub/cloud-device-authorization.js +0 -92
  41. package/dist/commands/hub/device-authorization.js +0 -87
@@ -1,7 +1,7 @@
1
1
  import { lstat, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import YAML from "yaml";
4
- import { HubDeployError } from "./error.js";
4
+ import { HubCommandError } from "./error.js";
5
5
  const DEFAULT_CONFIGURATION_PATH = ".paseo/hub.yml";
6
6
  const PROMPT_PARTIAL_ROOT = ".paseo/partials";
7
7
  const PROMPT_PARTIAL_ROOT_PREFIX = `${PROMPT_PARTIAL_ROOT}/`;
@@ -19,10 +19,10 @@ export async function resolveHubDeployInput(input) {
19
19
  const configuration = parseConfiguration(yaml);
20
20
  const projectSlug = input.project ?? projectFromConfiguration(configuration);
21
21
  if (projectSlug === undefined) {
22
- throw new HubDeployError("HUB_PROJECT_REQUIRED", "Project is required. Pass --project <slug> or add top-level project to the YAML.");
22
+ throw new HubCommandError("HUB_PROJECT_REQUIRED", "Project is required. Pass --project <slug> or add top-level project to the YAML.");
23
23
  }
24
24
  if (!PROJECT_SLUG_PATTERN.test(projectSlug)) {
25
- throw new HubDeployError("HUB_INVALID_PROJECT", "Project must be a bare slug such as my-project.");
25
+ throw new HubCommandError("HUB_INVALID_PROJECT", "Project must be a bare slug such as my-project.");
26
26
  }
27
27
  const partials = await resolvePromptPartials(projectRoot, configuration);
28
28
  return {
@@ -47,7 +47,7 @@ function resolveConfigurationPath(cwd, file) {
47
47
  return resolved;
48
48
  }
49
49
  async function readConfiguration(projectRoot, configurationPath, displayPath) {
50
- const unsafePath = () => new HubDeployError("HUB_CONFIGURATION_UNSAFE_PATH", `Hub configuration at ${displayPath} must not use a symlink.`);
50
+ const unsafePath = () => new HubCommandError("HUB_CONFIGURATION_UNSAFE_PATH", `Hub configuration at ${displayPath} must not use a symlink.`);
51
51
  await rejectSymlinkComponents(projectRoot, configurationPath, unsafePath);
52
52
  let stats;
53
53
  try {
@@ -60,10 +60,10 @@ async function readConfiguration(projectRoot, configurationPath, displayPath) {
60
60
  throw unsafePath();
61
61
  }
62
62
  if (!stats.isFile()) {
63
- throw new HubDeployError("HUB_CONFIGURATION_NOT_FILE", `Hub configuration at ${displayPath} must be a regular file.`);
63
+ throw new HubCommandError("HUB_CONFIGURATION_NOT_FILE", `Hub configuration at ${displayPath} must be a regular file.`);
64
64
  }
65
65
  if (!hasReadPermission(stats.mode)) {
66
- throw new HubDeployError("HUB_CONFIGURATION_UNREADABLE", `Could not read Hub configuration at ${displayPath}. Check file permissions.`);
66
+ throw new HubCommandError("HUB_CONFIGURATION_UNREADABLE", `Could not read Hub configuration at ${displayPath}. Check file permissions.`);
67
67
  }
68
68
  let bytes;
69
69
  try {
@@ -74,7 +74,7 @@ async function readConfiguration(projectRoot, configurationPath, displayPath) {
74
74
  }
75
75
  const yaml = bytes.toString("utf8");
76
76
  if (yaml.length > MAX_CONFIGURATION_LENGTH) {
77
- throw new HubDeployError("HUB_CONFIGURATION_TOO_LARGE", `Hub configuration at ${displayPath} exceeds the ${MAX_CONFIGURATION_LENGTH}-character limit.`);
77
+ throw new HubCommandError("HUB_CONFIGURATION_TOO_LARGE", `Hub configuration at ${displayPath} exceeds the ${MAX_CONFIGURATION_LENGTH}-character limit.`);
78
78
  }
79
79
  return yaml;
80
80
  }
@@ -84,10 +84,10 @@ function parseConfiguration(yaml) {
84
84
  configuration = YAML.parse(yaml);
85
85
  }
86
86
  catch {
87
- throw new HubDeployError("HUB_INVALID_CONFIGURATION", "Hub configuration is not valid YAML.");
87
+ throw new HubCommandError("HUB_INVALID_CONFIGURATION", "Hub configuration is not valid YAML.");
88
88
  }
89
89
  if (!isRecord(configuration)) {
90
- throw new HubDeployError("HUB_INVALID_CONFIGURATION", "Hub configuration must be a YAML mapping.");
90
+ throw new HubCommandError("HUB_INVALID_CONFIGURATION", "Hub configuration must be a YAML mapping.");
91
91
  }
92
92
  return configuration;
93
93
  }
@@ -96,14 +96,14 @@ function projectFromConfiguration(configuration) {
96
96
  if (project === undefined)
97
97
  return undefined;
98
98
  if (typeof project !== "string") {
99
- throw new HubDeployError("HUB_INVALID_PROJECT", "Top-level project must be a bare project slug.");
99
+ throw new HubCommandError("HUB_INVALID_PROJECT", "Top-level project must be a bare project slug.");
100
100
  }
101
101
  return project;
102
102
  }
103
103
  async function resolvePromptPartials(projectRoot, configuration) {
104
104
  const references = collectPromptPartialReferences(configuration);
105
105
  if (references.length > MAX_PROMPT_PARTIAL_COUNT) {
106
- throw new HubDeployError("HUB_PARTIAL_LIMIT_EXCEEDED", `Hub configuration references ${references.length} partials; the limit is ${MAX_PROMPT_PARTIAL_COUNT}.`);
106
+ throw new HubCommandError("HUB_PARTIAL_LIMIT_EXCEEDED", `Hub configuration references ${references.length} partials; the limit is ${MAX_PROMPT_PARTIAL_COUNT}.`);
107
107
  }
108
108
  const partials = [];
109
109
  let bundleBytes = 0;
@@ -112,11 +112,11 @@ async function resolvePromptPartials(projectRoot, configuration) {
112
112
  const content = await readPartial(projectRoot, partialPath, reference.path);
113
113
  const contentBytes = Buffer.byteLength(content, "utf8");
114
114
  if (contentBytes > MAX_PROMPT_PARTIAL_CONTENT_BYTES) {
115
- throw new HubDeployError("HUB_PARTIAL_TOO_LARGE", `Referenced Hub partial ${reference.path} exceeds the ${MAX_PROMPT_PARTIAL_CONTENT_BYTES}-byte limit.`);
115
+ throw new HubCommandError("HUB_PARTIAL_TOO_LARGE", `Referenced Hub partial ${reference.path} exceeds the ${MAX_PROMPT_PARTIAL_CONTENT_BYTES}-byte limit.`);
116
116
  }
117
117
  bundleBytes += contentBytes;
118
118
  if (bundleBytes > MAX_PROMPT_PARTIAL_BUNDLE_BYTES) {
119
- throw new HubDeployError("HUB_PARTIAL_BUNDLE_TOO_LARGE", `Referenced Hub partials exceed the ${MAX_PROMPT_PARTIAL_BUNDLE_BYTES}-byte combined limit.`);
119
+ throw new HubCommandError("HUB_PARTIAL_BUNDLE_TOO_LARGE", `Referenced Hub partials exceed the ${MAX_PROMPT_PARTIAL_BUNDLE_BYTES}-byte combined limit.`);
120
120
  }
121
121
  partials.push({ path: reference.path, content });
122
122
  }
@@ -139,11 +139,11 @@ function collectPromptPartialReferences(configuration) {
139
139
  continue;
140
140
  const include = block["include"];
141
141
  if (typeof include !== "string") {
142
- throw new HubDeployError("HUB_PARTIAL_PATH_INVALID", "Hub partial include path must be a string.");
142
+ throw new HubCommandError("HUB_PARTIAL_PATH_INVALID", "Hub partial include path must be a string.");
143
143
  }
144
144
  const normalizedPath = normalizePromptPartialPath(include);
145
145
  if (seen.has(normalizedPath)) {
146
- throw new HubDeployError("HUB_PARTIAL_DUPLICATE", `Hub partial ${normalizedPath} is referenced more than once. Remove the duplicate include.`);
146
+ throw new HubCommandError("HUB_PARTIAL_DUPLICATE", `Hub partial ${normalizedPath} is referenced more than once. Remove the duplicate include.`);
147
147
  }
148
148
  seen.add(normalizedPath);
149
149
  references.push({ path: normalizedPath });
@@ -168,7 +168,7 @@ function normalizePromptPartialPath(value) {
168
168
  throw invalidPromptPartialPath(value);
169
169
  }
170
170
  if (canonical.length > MAX_PROMPT_PARTIAL_PATH_LENGTH) {
171
- throw new HubDeployError("HUB_PARTIAL_PATH_TOO_LONG", `Hub partial path ${value} exceeds the ${MAX_PROMPT_PARTIAL_PATH_LENGTH}-character limit.`);
171
+ throw new HubCommandError("HUB_PARTIAL_PATH_TOO_LONG", `Hub partial path ${value} exceeds the ${MAX_PROMPT_PARTIAL_PATH_LENGTH}-character limit.`);
172
172
  }
173
173
  return canonical.slice(PROMPT_PARTIAL_ROOT_PREFIX.length);
174
174
  }
@@ -192,31 +192,31 @@ function decodePromptPartialPath(value) {
192
192
  return decoded;
193
193
  }
194
194
  async function readPartial(projectRoot, partialPath, displayPath) {
195
- await rejectSymlinkComponents(projectRoot, partialPath, () => new HubDeployError("HUB_PARTIAL_UNSAFE_PATH", `Referenced Hub partial ${displayPath} must not use a symlink.`));
195
+ await rejectSymlinkComponents(projectRoot, partialPath, () => new HubCommandError("HUB_PARTIAL_UNSAFE_PATH", `Referenced Hub partial ${displayPath} must not use a symlink.`));
196
196
  let stats;
197
197
  try {
198
198
  stats = await lstat(partialPath);
199
199
  }
200
200
  catch (error) {
201
201
  if (errorCode(error) === "ENOENT") {
202
- throw new HubDeployError("HUB_PARTIAL_MISSING", `Referenced Hub partial ${displayPath} does not exist.`);
202
+ throw new HubCommandError("HUB_PARTIAL_MISSING", `Referenced Hub partial ${displayPath} does not exist.`);
203
203
  }
204
- throw new HubDeployError("HUB_PARTIAL_UNREADABLE", `Could not read referenced Hub partial ${displayPath}. Check the file and permissions.`);
204
+ throw new HubCommandError("HUB_PARTIAL_UNREADABLE", `Could not read referenced Hub partial ${displayPath}. Check the file and permissions.`);
205
205
  }
206
206
  if (stats.isSymbolicLink()) {
207
- throw new HubDeployError("HUB_PARTIAL_UNSAFE_PATH", `Referenced Hub partial ${displayPath} must not be a symlink.`);
207
+ throw new HubCommandError("HUB_PARTIAL_UNSAFE_PATH", `Referenced Hub partial ${displayPath} must not be a symlink.`);
208
208
  }
209
209
  if (!stats.isFile()) {
210
- throw new HubDeployError("HUB_PARTIAL_NOT_FILE", `Referenced Hub partial ${displayPath} must be a regular file.`);
210
+ throw new HubCommandError("HUB_PARTIAL_NOT_FILE", `Referenced Hub partial ${displayPath} must be a regular file.`);
211
211
  }
212
212
  if (!hasReadPermission(stats.mode)) {
213
- throw new HubDeployError("HUB_PARTIAL_UNREADABLE", `Could not read referenced Hub partial ${displayPath}. Check the file and permissions.`);
213
+ throw new HubCommandError("HUB_PARTIAL_UNREADABLE", `Could not read referenced Hub partial ${displayPath}. Check the file and permissions.`);
214
214
  }
215
215
  try {
216
216
  return (await readFile(partialPath)).toString("utf8");
217
217
  }
218
218
  catch {
219
- throw new HubDeployError("HUB_PARTIAL_UNREADABLE", `Could not read referenced Hub partial ${displayPath}. Check the file and permissions.`);
219
+ throw new HubCommandError("HUB_PARTIAL_UNREADABLE", `Could not read referenced Hub partial ${displayPath}. Check the file and permissions.`);
220
220
  }
221
221
  }
222
222
  async function rejectSymlinkComponents(root, target, error) {
@@ -229,7 +229,7 @@ async function rejectSymlinkComponents(root, target, error) {
229
229
  throw error();
230
230
  }
231
231
  catch (failure) {
232
- if (failure instanceof HubDeployError)
232
+ if (failure instanceof HubCommandError)
233
233
  throw failure;
234
234
  if (errorCode(failure) === "ENOENT")
235
235
  return;
@@ -239,15 +239,15 @@ async function rejectSymlinkComponents(root, target, error) {
239
239
  }
240
240
  function configurationReadError(displayPath, error) {
241
241
  if (errorCode(error) === "ENOENT") {
242
- return new HubDeployError("HUB_CONFIGURATION_UNREADABLE", `Could not read Hub configuration at ${displayPath}. Pass an existing YAML file.`);
242
+ return new HubCommandError("HUB_CONFIGURATION_UNREADABLE", `Could not read Hub configuration at ${displayPath}. Pass an existing YAML file.`);
243
243
  }
244
- return new HubDeployError("HUB_CONFIGURATION_UNREADABLE", `Could not read Hub configuration at ${displayPath}. Check the file and permissions.`);
244
+ return new HubCommandError("HUB_CONFIGURATION_UNREADABLE", `Could not read Hub configuration at ${displayPath}. Check the file and permissions.`);
245
245
  }
246
246
  function invalidConfigurationPath() {
247
- return new HubDeployError("HUB_CONFIGURATION_PATH_INVALID", "Hub configuration path must stay within the current project root; parent-directory paths are not allowed.");
247
+ return new HubCommandError("HUB_CONFIGURATION_PATH_INVALID", "Hub configuration path must stay within the current project root; parent-directory paths are not allowed.");
248
248
  }
249
249
  function invalidPromptPartialPath(value) {
250
- return new HubDeployError("HUB_PARTIAL_PATH_INVALID", `Hub partial path must be a safe relative path under .paseo/partials/: ${value}`);
250
+ return new HubCommandError("HUB_PARTIAL_PATH_INVALID", `Hub partial path must be a safe relative path under .paseo/partials/: ${value}`);
251
251
  }
252
252
  function hasReadPermission(mode) {
253
253
  return (mode & 0o444) !== 0;
@@ -1,17 +1,37 @@
1
1
  import type { Command } from "commander";
2
2
  import { type SingleResult } from "../../output/index.js";
3
- import { type HubInstallResult } from "./client.js";
3
+ import { HubHttpClient, type HubInstallResult, type HubValidationResult } from "./client.js";
4
+ import { type HubCredentialStore } from "./credentials.js";
5
+ import { type HubReporter } from "./reporter.js";
4
6
  export interface HubDeployOptions {
5
7
  file?: string;
6
8
  project?: string;
7
9
  hub?: string;
8
10
  apiKey?: string;
11
+ dryRun?: boolean;
12
+ json?: boolean;
9
13
  }
10
14
  interface HubDeployEnvironment {
11
15
  cwd: string;
12
16
  env: Readonly<Record<string, string | undefined>>;
17
+ credentials?: HubCredentialStore;
18
+ hub?: Pick<HubHttpClient, "installConfiguration" | "validateConfiguration">;
19
+ reporter?: HubReporter;
13
20
  }
14
- export declare function runHubDeploy(options: HubDeployOptions, environment?: HubDeployEnvironment): Promise<SingleResult<HubInstallResult>>;
15
- export declare function addHubDeployCommand(hub: Command): void;
21
+ export interface HubDeployCommandDependencies {
22
+ env: Readonly<Record<string, string | undefined>>;
23
+ credentials: HubCredentialStore;
24
+ hub: Pick<HubHttpClient, "installConfiguration" | "validateConfiguration">;
25
+ reporter: HubReporter;
26
+ cwd(): string;
27
+ }
28
+ interface HubDeployResult extends HubInstallResult {
29
+ origin: string;
30
+ }
31
+ interface HubDryRunResult extends HubValidationResult {
32
+ origin: string;
33
+ }
34
+ export declare function runHubDeploy(options: HubDeployOptions, environment?: HubDeployEnvironment): Promise<SingleResult<HubDeployResult> | SingleResult<HubDryRunResult>>;
35
+ export declare function addHubDeployCommand(hub: Command, dependencies: HubDeployCommandDependencies): void;
16
36
  export {};
17
37
  //# sourceMappingURL=deploy.d.ts.map
@@ -1,8 +1,11 @@
1
1
  import { withOutput } from "../../output/index.js";
2
2
  import { addJsonOption } from "../../utils/command-options.js";
3
- import { installHubConfiguration } from "./client.js";
3
+ import { resolveHubCredential, resolveHubOrigin } from "./authority.js";
4
+ import { HubHttpClient } from "./client.js";
5
+ import { PrivateHubCredentialStore } from "./credentials.js";
4
6
  import { resolveHubDeployInput } from "./deploy-input.js";
5
- import { HubDeployError } from "./error.js";
7
+ import { processHubReporter, reportHubProgress } from "./reporter.js";
8
+ import { addHubResolutionHelp } from "./help.js";
6
9
  const resultSchema = {
7
10
  idField: "versionId",
8
11
  columns: [
@@ -10,62 +13,68 @@ const resultSchema = {
10
13
  { header: "VERSION", field: "version" },
11
14
  { header: "VERSION ID", field: "versionId" },
12
15
  { header: "ACTIVE", field: "active" },
16
+ { header: "HUB", field: "origin" },
13
17
  ],
14
18
  };
15
- export async function runHubDeploy(options, environment = { cwd: process.cwd(), env: process.env }) {
16
- const origin = options.hub ?? environment.env.PASEO_HUB_URL;
17
- const apiKey = options.apiKey ?? environment.env.PASEO_HUB_API_KEY;
18
- if (!origin) {
19
- throw new HubDeployError("HUB_ORIGIN_REQUIRED", "Hub origin is required. Pass --hub <origin> or set PASEO_HUB_URL.");
20
- }
21
- if (!apiKey) {
22
- throw new HubDeployError("HUB_API_KEY_REQUIRED", "Hub API key is required. Pass --api-key <secret> or set PASEO_HUB_API_KEY.");
23
- }
24
- const normalizedOrigin = parseHubOrigin(origin);
19
+ const validationSchema = {
20
+ idField: "projectSlug",
21
+ columns: [
22
+ { header: "PROJECT", field: "projectSlug" },
23
+ { header: "VALID", field: "valid" },
24
+ { header: "HUB", field: "origin" },
25
+ ],
26
+ };
27
+ export async function runHubDeploy(options, environment = {
28
+ cwd: process.cwd(),
29
+ env: process.env,
30
+ credentials: new PrivateHubCredentialStore(),
31
+ hub: new HubHttpClient(),
32
+ }) {
33
+ const credentials = environment.credentials ?? new PrivateHubCredentialStore(environment.env);
34
+ const resolution = {
35
+ options: { origin: options.hub, apiKey: options.apiKey },
36
+ env: environment.env,
37
+ credentials,
38
+ };
39
+ const origin = resolveHubOrigin(resolution);
25
40
  const deployInput = await resolveHubDeployInput({
26
41
  cwd: environment.cwd,
27
42
  ...(options.file === undefined ? {} : { file: options.file }),
28
43
  ...(options.project === undefined ? {} : { project: options.project }),
29
44
  });
30
- const deployed = await installHubConfiguration({
31
- origin: normalizedOrigin,
32
- apiKey,
45
+ const action = options.dryRun === true ? "Validating" : "Deploying";
46
+ reportHubProgress(environment.reporter ?? processHubReporter, options, `${action} ${deployInput.projectSlug} ${options.dryRun === true ? "against" : "to"} ${origin}`);
47
+ const credential = resolveHubCredential({ ...resolution, origin });
48
+ const request = {
49
+ origin,
50
+ apiKey: credential,
33
51
  ...deployInput,
34
- });
35
- return { type: "single", data: deployed, schema: resultSchema };
52
+ };
53
+ if (options.dryRun === true) {
54
+ const validated = await (environment.hub ?? new HubHttpClient()).validateConfiguration(request);
55
+ return { type: "single", data: { ...validated, origin }, schema: validationSchema };
56
+ }
57
+ const deployed = await (environment.hub ?? new HubHttpClient()).installConfiguration(request);
58
+ return { type: "single", data: { ...deployed, origin }, schema: resultSchema };
36
59
  }
37
- export function addHubDeployCommand(hub) {
38
- addJsonOption(hub
60
+ export function addHubDeployCommand(hub, dependencies) {
61
+ addJsonOption(addHubResolutionHelp(hub
39
62
  .command("deploy")
40
63
  .description("Install and activate a Hub configuration")
41
64
  .argument("[file]", "Hub configuration YAML", ".paseo/hub.yml")
42
65
  .option("-p, --project <slug>", "Target project slug")
43
66
  .option("--hub <origin>", "Paseo Hub origin")
44
- .option("--api-key <secret>", "Organization API key")).action(withOutput(async (...args) => {
67
+ .option("--api-key <secret>", "Organization API key")
68
+ .option("--dry-run", "Validate without installing or activating"))).action(withOutput(async (...args) => {
45
69
  const file = args[0];
46
70
  const options = args.at(-2);
47
- return runHubDeploy({ ...options, file });
71
+ return runHubDeploy({ ...options, file }, {
72
+ cwd: dependencies.cwd(),
73
+ env: dependencies.env,
74
+ credentials: dependencies.credentials,
75
+ hub: dependencies.hub,
76
+ reporter: dependencies.reporter,
77
+ });
48
78
  }));
49
79
  }
50
- function parseHubOrigin(value) {
51
- let url;
52
- try {
53
- url = new URL(value);
54
- }
55
- catch {
56
- throw invalidHubOrigin();
57
- }
58
- if (!["http:", "https:"].includes(url.protocol) ||
59
- url.username ||
60
- url.password ||
61
- url.pathname !== "/" ||
62
- url.search ||
63
- url.hash) {
64
- throw invalidHubOrigin();
65
- }
66
- return url.origin;
67
- }
68
- function invalidHubOrigin() {
69
- return new HubDeployError("HUB_INVALID_ORIGIN", "Hub URL must be an HTTP or HTTPS origin without credentials, path, query, or hash.");
70
- }
71
80
  //# sourceMappingURL=deploy.js.map
@@ -0,0 +1,16 @@
1
+ import type { Command } from "commander";
2
+ import type { HubDaemonConnection } from "./daemon-client.js";
3
+ import { type HubReporter } from "./reporter.js";
4
+ interface HubDisconnectOptions {
5
+ force?: boolean;
6
+ host?: string;
7
+ json?: boolean;
8
+ }
9
+ interface HubDisconnectDependencies {
10
+ daemon: HubDaemonConnection;
11
+ reporter: HubReporter;
12
+ }
13
+ export declare function runHubDisconnect(options: HubDisconnectOptions, dependencies: HubDisconnectDependencies): Promise<import("../../output/types.js").ListResult<import("./status-output.js").HubRow>>;
14
+ export declare function addHubDisconnectCommand(parent: Command, dependencies: HubDisconnectDependencies): void;
15
+ export {};
16
+ //# sourceMappingURL=disconnect.d.ts.map
@@ -0,0 +1,24 @@
1
+ import { withOutput } from "../../output/index.js";
2
+ import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
3
+ import { withHubDaemon } from "./daemon-client.js";
4
+ import { reportHubProgress } from "./reporter.js";
5
+ import { hubStatusResult } from "./status-output.js";
6
+ export function runHubDisconnect(options, dependencies) {
7
+ return withHubDaemon(dependencies.daemon, options.host, async (client) => {
8
+ const current = (await client.getHubStatus()).status;
9
+ if (current.hubOrigin !== null) {
10
+ reportHubProgress(dependencies.reporter, options, `Disconnecting this daemon from ${current.hubOrigin}`);
11
+ }
12
+ const response = await client.disconnectHub(options.force ?? false);
13
+ return hubStatusResult(response.status, response.warning, current.hubOrigin);
14
+ });
15
+ }
16
+ export function addHubDisconnectCommand(parent, dependencies) {
17
+ addJsonAndDaemonHostOptions(parent
18
+ .command("disconnect")
19
+ .option("--force", "Remove local authority even if the Hub is offline")).action(withOutput(async (...args) => {
20
+ const options = args.at(-2);
21
+ return runHubDisconnect(options, dependencies);
22
+ }));
23
+ }
24
+ //# sourceMappingURL=disconnect.js.map
@@ -1,4 +1,4 @@
1
- export declare class HubDeployError extends Error {
1
+ export declare class HubCommandError extends Error {
2
2
  readonly code: string;
3
3
  readonly details?: string | undefined;
4
4
  constructor(code: string, message: string, details?: string | undefined);
@@ -1,9 +1,9 @@
1
- export class HubDeployError extends Error {
1
+ export class HubCommandError extends Error {
2
2
  constructor(code, message, details) {
3
3
  super(message);
4
4
  this.code = code;
5
5
  this.details = details;
6
- this.name = "HubDeployError";
6
+ this.name = "HubCommandError";
7
7
  }
8
8
  }
9
9
  //# sourceMappingURL=error.js.map
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function addHubResolutionHelp(command: Command): Command;
3
+ //# sourceMappingURL=help.d.ts.map
@@ -0,0 +1,5 @@
1
+ const resolutionHelp = "\nHub origin precedence: command origin/--hub, PASEO_HUB_URL, active stored login, then https://hub.paseo.sh.\nCredential precedence: --api-key, PASEO_HUB_API_KEY, then a stored login for the exact resolved origin.\n";
2
+ export function addHubResolutionHelp(command) {
3
+ return command.addHelpText("after", resolutionHelp);
4
+ }
5
+ //# sourceMappingURL=help.js.map
@@ -1,30 +1,20 @@
1
1
  import { Command } from "commander";
2
- interface HubCommandClient {
3
- connectHub(url: string, token: string): Promise<{
4
- status: HubStatus;
5
- }>;
6
- getHubStatus(): Promise<{
7
- status: HubStatus;
8
- }>;
9
- disconnectHub(force: boolean): Promise<{
10
- status: HubStatus;
11
- warning?: string;
12
- }>;
13
- close(): Promise<void>;
14
- }
15
- interface HubStatus {
16
- state: string;
17
- daemonId: string | null;
18
- hubOrigin: string | null;
19
- scopes: string[];
20
- connectedAt: string | null;
21
- lastError: string | null;
22
- }
2
+ import { HubHttpClient } from "./client.js";
3
+ import { type HubCredentialStore } from "./credentials.js";
4
+ import { type HubDaemonConnection } from "./daemon-client.js";
5
+ import { type CliLoginFlow } from "./login-flow.js";
6
+ import { type HubReporter } from "./reporter.js";
23
7
  interface HubCommandEnvironment {
24
- connect(host: string | undefined): Promise<HubCommandClient>;
25
- authorize(url: string, displayName: string): Promise<string>;
26
- displayName(): string;
8
+ env: Readonly<Record<string, string | undefined>>;
9
+ credentials: HubCredentialStore;
10
+ hub: HubHttpClient;
11
+ login: Pick<CliLoginFlow, "authorize">;
12
+ daemon: HubDaemonConnection;
13
+ isInteractive(): boolean;
14
+ confirmDisconnect(origin: string): Promise<boolean>;
15
+ reporter: HubReporter;
16
+ cwd(): string;
27
17
  }
28
- export declare function createHubCommand(environment?: HubCommandEnvironment): Command;
18
+ export declare function createHubCommand(overrides?: Partial<HubCommandEnvironment>): Command;
29
19
  export {};
30
20
  //# sourceMappingURL=index.d.ts.map
@@ -1,87 +1,77 @@
1
1
  import { Command } from "commander";
2
- import { hostname } from "node:os";
3
2
  import { withOutput } from "../../output/index.js";
4
3
  import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
5
- import { connectToDaemon } from "../../utils/client.js";
6
- import { createDeviceAuthorizationWorkflow } from "./device-authorization.js";
4
+ import { HubHttpClient } from "./client.js";
5
+ import { addHubConnectCommand } from "./connect.js";
6
+ import { PrivateHubCredentialStore } from "./credentials.js";
7
+ import { productionHubDaemonConnection, withHubDaemon, } from "./daemon-client.js";
7
8
  import { addHubDeployCommand } from "./deploy.js";
8
- const productionEnvironment = {
9
- connect: (host) => connectToDaemon({ host }),
10
- authorize: (url, displayName) => createDeviceAuthorizationWorkflow().authorize(url, displayName),
11
- displayName: hostname,
12
- };
13
- const schema = {
14
- idField: "state",
15
- columns: [
16
- { header: "STATE", field: "state" },
17
- { header: "HUB", field: "hub" },
18
- { header: "DAEMON", field: "daemonId" },
19
- { header: "SCOPES", field: "scopes" },
20
- { header: "CONNECTED", field: "connectedAt" },
21
- { header: "ERROR", field: "error" },
22
- { header: "WARNING", field: "warning" },
23
- ],
24
- };
25
- function result(status, warning) {
9
+ import { addHubDisconnectCommand } from "./disconnect.js";
10
+ import { createCliLoginFlow } from "./login-flow.js";
11
+ import { addHubLoginCommand } from "./login.js";
12
+ import { addHubLogoutCommand, productionLogoutPrompt } from "./logout.js";
13
+ import { addHubProjectsCommand } from "./projects.js";
14
+ import { processHubReporter } from "./reporter.js";
15
+ import { hubStatusResult } from "./status-output.js";
16
+ import { addHubResolutionHelp } from "./help.js";
17
+ function productionEnvironment() {
18
+ const env = process.env;
19
+ const hub = new HubHttpClient();
26
20
  return {
27
- type: "list",
28
- data: [
29
- {
30
- state: status.state,
31
- daemonId: status.daemonId,
32
- hub: status.hubOrigin,
33
- scopes: status.scopes.join(", "),
34
- connectedAt: status.connectedAt,
35
- error: status.lastError,
36
- warning,
37
- },
38
- ],
39
- schema,
21
+ env,
22
+ credentials: new PrivateHubCredentialStore(env),
23
+ hub,
24
+ login: createCliLoginFlow(hub),
25
+ daemon: productionHubDaemonConnection,
26
+ reporter: processHubReporter,
27
+ cwd: () => process.cwd(),
28
+ ...productionLogoutPrompt,
40
29
  };
41
30
  }
42
- async function withClient(environment, host, action) {
43
- const client = await environment.connect(host);
44
- try {
45
- return await action(client);
46
- }
47
- finally {
48
- await client.close().catch(() => undefined);
49
- }
50
- }
51
- export function createHubCommand(environment = productionEnvironment) {
52
- const hub = new Command("hub").description("Manage Paseo Hub");
53
- addJsonAndDaemonHostOptions(hub.command("connect").argument("<url>").option("--token <token>")).action(withOutput(async (...args) => {
54
- const url = args[0];
55
- const options = args.at(-2);
56
- return withClient(environment, options.host, async (client) => {
57
- if (options.token !== undefined) {
58
- return result((await client.connectHub(url, options.token)).status);
59
- }
60
- const existing = (await client.getHubStatus()).status;
61
- if (existing.state !== "not_connected" && existing.state !== "revoked") {
62
- throw new Error("This daemon already has a Hub relationship");
63
- }
64
- const token = await environment.authorize(url, suggestedDisplayName(environment.displayName()));
65
- return result((await client.connectHub(url, token)).status);
66
- });
67
- }));
31
+ export function createHubCommand(overrides = {}) {
32
+ const environment = { ...productionEnvironment(), ...overrides };
33
+ const hub = addHubResolutionHelp(new Command("hub").description("Manage Paseo Hub"));
34
+ addHubLoginCommand(hub, {
35
+ env: environment.env,
36
+ credentials: environment.credentials,
37
+ flow: environment.login,
38
+ reporter: environment.reporter,
39
+ });
40
+ addHubConnectCommand(hub, {
41
+ env: environment.env,
42
+ credentials: environment.credentials,
43
+ hub: environment.hub,
44
+ daemon: environment.daemon,
45
+ reporter: environment.reporter,
46
+ });
68
47
  addJsonAndDaemonHostOptions(hub.command("status")).action(withOutput(async (...args) => {
69
48
  const options = args.at(-2);
70
- return withClient(environment, options.host, async (client) => result((await client.getHubStatus()).status));
49
+ return withHubDaemon(environment.daemon, options.host, async (client) => hubStatusResult((await client.getHubStatus()).status));
71
50
  }));
72
- addJsonAndDaemonHostOptions(hub
73
- .command("disconnect")
74
- .option("--force", "Remove local authority even if the Hub is offline")).action(withOutput(async (...args) => {
75
- const options = args.at(-2);
76
- return withClient(environment, options.host, async (client) => {
77
- const response = await client.disconnectHub(options.force ?? false);
78
- return result(response.status, response.warning);
79
- });
80
- }));
81
- addHubDeployCommand(hub);
51
+ addHubDisconnectCommand(hub, {
52
+ daemon: environment.daemon,
53
+ reporter: environment.reporter,
54
+ });
55
+ addHubProjectsCommand(hub, {
56
+ env: environment.env,
57
+ credentials: environment.credentials,
58
+ hub: environment.hub,
59
+ reporter: environment.reporter,
60
+ });
61
+ addHubDeployCommand(hub, {
62
+ env: environment.env,
63
+ credentials: environment.credentials,
64
+ hub: environment.hub,
65
+ reporter: environment.reporter,
66
+ cwd: environment.cwd,
67
+ });
68
+ addHubLogoutCommand(hub, {
69
+ credentials: environment.credentials,
70
+ daemon: environment.daemon,
71
+ isInteractive: environment.isInteractive,
72
+ confirmDisconnect: environment.confirmDisconnect,
73
+ reporter: environment.reporter,
74
+ });
82
75
  return hub;
83
76
  }
84
- function suggestedDisplayName(value) {
85
- return value.trim().slice(0, 100) || "Paseo daemon";
86
- }
87
77
  //# sourceMappingURL=index.js.map