handoff-app 2.0.0-7 → 2.0.0-8

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 (98) hide show
  1. package/README.md +111 -11
  2. package/UPGRADE.md +20 -0
  3. package/dist/app-builder/build.js +10 -2
  4. package/dist/app-builder/client-config.js +3 -1
  5. package/dist/app-builder/watchers.js +17 -7
  6. package/dist/cache/build-cache.js +5 -1
  7. package/dist/cli/auth/device.d.ts +6 -1
  8. package/dist/cli/auth/device.js +5 -2
  9. package/dist/cli/auth/store.d.ts +15 -6
  10. package/dist/cli/auth/store.js +72 -19
  11. package/dist/commands/login/index.d.ts +5 -0
  12. package/dist/commands/login/index.js +18 -6
  13. package/dist/commands/logout/index.d.ts +5 -0
  14. package/dist/commands/logout/index.js +54 -15
  15. package/dist/commands/types.d.ts +2 -0
  16. package/dist/commands/utils.d.ts +21 -7
  17. package/dist/commands/utils.js +11 -10
  18. package/dist/config/catalog-include.d.ts +2 -2
  19. package/dist/config/defaults.d.ts +3 -3
  20. package/dist/config/defaults.js +54 -48
  21. package/dist/config/env.d.ts +9 -0
  22. package/dist/config/env.js +43 -0
  23. package/dist/config/errors.d.ts +4 -0
  24. package/dist/config/errors.js +11 -0
  25. package/dist/config/from-env.d.ts +24 -0
  26. package/dist/config/from-env.js +7 -0
  27. package/dist/config/helpers.d.ts +2 -0
  28. package/dist/config/helpers.js +3 -1
  29. package/dist/config/index.d.ts +2 -1
  30. package/dist/config/index.js +6 -1
  31. package/dist/config/loader.d.ts +29 -11
  32. package/dist/config/loader.js +119 -46
  33. package/dist/config/resolve-env.d.ts +5 -0
  34. package/dist/config/resolve-env.js +102 -0
  35. package/dist/config/runtime.d.ts +3 -3
  36. package/dist/config/validator.d.ts +2 -2
  37. package/dist/config/validator.js +2 -4
  38. package/dist/index.d.ts +33 -4
  39. package/dist/index.js +60 -16
  40. package/dist/pipeline/index.d.ts +1 -1
  41. package/dist/pipeline/index.js +1 -1
  42. package/dist/registry/asset-storage/adapters/vercel-blob.js +1 -1
  43. package/dist/registry/asset-storage/resolve.d.ts +2 -2
  44. package/dist/registry/asset-storage/resolve.js +4 -1
  45. package/dist/registry/checkout/index.js +4 -4
  46. package/dist/registry/connection.d.ts +28 -24
  47. package/dist/registry/connection.js +53 -67
  48. package/dist/registry/db/driver.d.ts +4 -11
  49. package/dist/registry/db/driver.js +4 -16
  50. package/dist/registry/db/index.d.ts +1 -1
  51. package/dist/registry/db/index.js +1 -2
  52. package/dist/registry/publish/index.js +4 -4
  53. package/dist/store/filesystem.d.ts +2 -2
  54. package/dist/types/config.d.ts +48 -29
  55. package/docs/api.md +31 -0
  56. package/docs/cli.md +2 -2
  57. package/package.json +1 -1
  58. package/src/app/pages/account/tokens.tsx +3 -1
  59. package/src/app-builder/build.ts +10 -2
  60. package/src/app-builder/client-config.ts +5 -3
  61. package/src/app-builder/watchers.ts +18 -8
  62. package/src/cache/build-cache.ts +5 -1
  63. package/src/cli/auth/device.ts +8 -3
  64. package/src/cli/auth/store.ts +75 -18
  65. package/src/commands/login/index.ts +20 -6
  66. package/src/commands/logout/index.ts +74 -20
  67. package/src/commands/types.ts +2 -0
  68. package/src/commands/utils.ts +14 -5
  69. package/src/config/README.md +61 -1
  70. package/src/config/catalog-include.ts +2 -2
  71. package/src/config/defaults.ts +24 -14
  72. package/src/config/env.ts +41 -0
  73. package/src/config/errors.ts +7 -0
  74. package/src/config/from-env.ts +26 -0
  75. package/src/config/helpers.ts +4 -2
  76. package/src/config/index.ts +4 -1
  77. package/src/config/loader.ts +156 -50
  78. package/src/config/resolve-env.ts +94 -0
  79. package/src/config/runtime.ts +3 -3
  80. package/src/config/validator.ts +4 -6
  81. package/src/index.d.ts +2 -2
  82. package/src/index.ts +85 -8
  83. package/src/pipeline/index.ts +1 -1
  84. package/src/registry/asset-storage/adapters/vercel-blob.ts +1 -1
  85. package/src/registry/asset-storage/resolve.ts +6 -3
  86. package/src/registry/checkout/index.ts +5 -5
  87. package/src/registry/connection.ts +72 -71
  88. package/src/registry/db/driver.ts +6 -17
  89. package/src/registry/db/index.ts +0 -1
  90. package/src/registry/publish/index.ts +5 -5
  91. package/src/store/filesystem.ts +2 -2
  92. package/src/types/config.ts +45 -29
  93. package/templates/default/env.tpl +10 -0
  94. package/templates/default/gitignore.tpl +7 -0
  95. package/templates/default/handoff.config.js.tpl +21 -172
  96. package/templates/default/handoff.config.ts.tpl +21 -156
  97. package/templates/default/handoff.config.with-examples.js.tpl +20 -171
  98. package/templates/default/handoff.config.with-examples.ts.tpl +20 -155
@@ -6,7 +6,7 @@
6
6
  * {@link import('../db/driver')}: only provider selection, module location, non-secret options, and
7
7
  * env-var *names* are ever persisted; secret *values* are read from `process.env` at request time.
8
8
  */
9
- import type { Config } from '../../types/config';
9
+ import type { ResolvedConfig } from '../../types/config';
10
10
  import type { AssetStorageProvider } from '../db/schema';
11
11
  /** Which adapter implementation is active. */
12
12
  export type AssetStorageAdapterKind = 'database' | 'vercel-blob' | 'custom';
@@ -42,4 +42,4 @@ export interface ResolvedAssetStorage {
42
42
  /** Resolve raw asset storage settings into a fully-defaulted shape. */
43
43
  export declare const resolveAssetStorageSettings: (settings: AssetStorageSettings | null | undefined) => ResolvedAssetStorage;
44
44
  /** Resolve the active asset storage settings from a loaded config (CLI/build side). */
45
- export declare const resolveAssetStorageFromConfig: (config: Config | null | undefined) => ResolvedAssetStorage;
45
+ export declare const resolveAssetStorageFromConfig: (config: ResolvedConfig | null | undefined) => ResolvedAssetStorage;
@@ -32,5 +32,8 @@ const resolveAssetStorageSettings = (settings) => {
32
32
  };
33
33
  exports.resolveAssetStorageSettings = resolveAssetStorageSettings;
34
34
  /** Resolve the active asset storage settings from a loaded config (CLI/build side). */
35
- const resolveAssetStorageFromConfig = (config) => { var _a, _b; return (0, exports.resolveAssetStorageSettings)((_b = (_a = config === null || config === void 0 ? void 0 : config.runtime) === null || _a === void 0 ? void 0 : _a.registry) === null || _b === void 0 ? void 0 : _b.assetStorage); };
35
+ const resolveAssetStorageFromConfig = (config) => {
36
+ var _a, _b, _c, _d, _e, _f;
37
+ return (0, exports.resolveAssetStorageSettings)(Object.assign(Object.assign({}, (_b = (_a = config === null || config === void 0 ? void 0 : config.runtime) === null || _a === void 0 ? void 0 : _a.registry) === null || _b === void 0 ? void 0 : _b.assetStorage), { tokenEnv: (_f = (_e = (_d = (_c = config === null || config === void 0 ? void 0 : config.runtime) === null || _c === void 0 ? void 0 : _c.registry) === null || _d === void 0 ? void 0 : _d.assetStorage) === null || _e === void 0 ? void 0 : _e.token) === null || _f === void 0 ? void 0 : _f.$env }));
38
+ };
36
39
  exports.resolveAssetStorageFromConfig = resolveAssetStorageFromConfig;
@@ -106,14 +106,14 @@ const resolveConnectionOrThrow = (handoff) => __awaiter(void 0, void 0, void 0,
106
106
  throw new CheckoutError(`checkout is only available from a connected workspace (runtime.mode: "workspace"); this project is "${mode}". ` +
107
107
  'A registry host stores and serves entities; it has no local workspace to check them out into.');
108
108
  }
109
- const connection = yield (0, connection_1.resolveAuthenticatedRegistryConnection)(handoff.config, handoff.workingPath);
109
+ const connection = yield (0, connection_1.resolveAuthenticatedRegistryConnection)(handoff.config, handoff.workingPath, handoff.getProfile());
110
110
  if (!connection.url) {
111
- throw new CheckoutError(`No registry is configured. Run \`handoff-app login --url <registry-url>\`, set runtime.registryConnection.url, ` +
111
+ throw new CheckoutError(`No registry is configured. Run \`${(0, connection_1.loginCommandHint)(connection)}\`, set runtime.registryConnection.url, ` +
112
112
  `or set the "${connection.urlEnv}" environment variable to the base URL of the registry to checkout from.`);
113
113
  }
114
114
  if (!connection.accessToken) {
115
- throw new CheckoutError(`No registry access token is configured. Run \`handoff-app login --url ${connection.url}\`, or set the ` +
116
- `"${connection.accessTokenEnv}" environment variable to a user-issued token for CI.`);
115
+ throw new CheckoutError(`No registry access token is configured. Run \`${(0, connection_1.loginCommandHint)(connection)}\`, or set the ` +
116
+ `"${connection.accessTokenEnv}" environment variable to a user-issued token for CI.${(0, connection_1.unusedLoginNote)(connection)}`);
117
117
  }
118
118
  return connection;
119
119
  });
@@ -1,20 +1,17 @@
1
1
  /**
2
- * Connected-workspace registry connection resolution.
3
- *
4
- * A connected workspace is `runtime.mode: 'workspace'` plus a `runtime.registryConnection` block
5
- * pointing at a remote registry — it is not a third runtime mode. The connection is resolved from
6
- * config + environment: the registry URL may be given inline or via an env-var *name*, and the
7
- * access token is always read from an env-var name (never persisted as a value). This is the single
8
- * source the publish (and later checkout) client reads its target + credentials from.
9
- *
10
- * The older `HANDOFF_CLOUD_*` names still work as deprecated fallbacks; see
11
- * {@link DEPRECATED_ENV_ALIASES}.
2
+ * Registry connection values resolve at config load. Saved device login credentials can fill
3
+ * missing values for CLI operations; neither source is persisted in the app's client config.
12
4
  */
13
- import type { Config } from '../types/config';
5
+ import type { ResolvedConfig } from '../types/config';
14
6
  /** Default env-var name holding the remote registry base URL. */
15
7
  export declare const DEFAULT_REGISTRY_URL_ENV = "HANDOFF_REGISTRY_URL";
16
8
  /** Default env-var name holding the connected-workspace access token. */
17
9
  export declare const DEFAULT_REGISTRY_ACCESS_TOKEN_ENV = "HANDOFF_REGISTRY_ACCESS_TOKEN";
10
+ /** A saved login and the profile it is stored under, which is not always the selected profile. */
11
+ interface SavedLogin {
12
+ profile: string;
13
+ url: string;
14
+ }
18
15
  /** Fully resolved connected-workspace registry connection. */
19
16
  export interface ResolvedRegistryConnection {
20
17
  /** Resolved registry base URL (inline config value wins, else the named env var), or `''`. */
@@ -25,25 +22,32 @@ export interface ResolvedRegistryConnection {
25
22
  urlEnv: string;
26
23
  /** Name of the env var the access token is read from. */
27
24
  accessTokenEnv: string;
25
+ /** Profile the saved login was looked up under. */
26
+ loginProfile: string;
27
+ /** Set when a saved login was skipped because it was issued for another registry. */
28
+ unmatchedLogin?: SavedLogin;
28
29
  }
29
- /**
30
- * Resolve the connected-workspace registry connection from config + environment. Never throws —
31
- * Callers inspect the resolved URL and access token to surface specific configuration errors.
32
- *
33
- * `urlEnv` and `accessTokenEnv` always report the canonical name, even when an alias supplied the
34
- * value, so errors built from them point at the variable to adopt.
35
- */
36
- export declare const resolveRegistryConnection: (config: Config | null | undefined) => ResolvedRegistryConnection;
30
+ /** Read values resolved at config load, retaining variable names for CLI diagnostics. */
31
+ export declare const resolveRegistryConnection: (config: ResolvedConfig | null | undefined) => ResolvedRegistryConnection;
37
32
  /**
38
33
  * Resolve the same connection for an authenticated CLI operation, letting a saved device login fill
39
34
  * in what the environment does not provide. A valid login supplies both the registry URL and the
40
- * access token, so `handoff-app login` alone is enough to publish and checkout. Environment values
41
- * keep precedence for deterministic CI: `HANDOFF_REGISTRY_URL` (or inline config) wins over the
42
- * saved URL, and `HANDOFF_REGISTRY_ACCESS_TOKEN` wins over the saved token. The saved token only
43
- * applies to the URL it was issued for.
35
+ * access token, so `handoff-app login` alone is enough to publish and checkout. The login is the one
36
+ * saved under the selected profile, which is how one workspace addresses several registries.
37
+ * Environment values keep precedence for deterministic CI: `HANDOFF_REGISTRY_URL` (or inline config)
38
+ * wins over the saved URL, and `HANDOFF_REGISTRY_ACCESS_TOKEN` wins over the saved token. The saved
39
+ * token only applies to the URL it was issued for.
44
40
  *
45
41
  * A configured `HANDOFF_SYNC_SECRET` is the last resort. Being deployment-wide rather than personal,
46
42
  * it fills in only where there is no explicit token and no login (CI, in practice), instead of quietly
47
43
  * speaking for a developer who is signed in.
48
44
  */
49
- export declare const resolveAuthenticatedRegistryConnection: (config: Config | null | undefined, workingPath?: string) => Promise<ResolvedRegistryConnection>;
45
+ export declare const resolveAuthenticatedRegistryConnection: (config: ResolvedConfig | null | undefined, workingPath?: string, profile?: string) => Promise<ResolvedRegistryConnection>;
46
+ /** The `handoff-app login` call that would authorize this connection, naming the selected profile. */
47
+ export declare const loginCommandHint: (connection: ResolvedRegistryConnection) => string;
48
+ /**
49
+ * Names a saved login that was skipped because it belongs to another registry. Without it, a run
50
+ * that resolves its URL from the environment reports a missing token while a login is in place.
51
+ */
52
+ export declare const unusedLoginNote: (connection: ResolvedRegistryConnection) => string;
53
+ export {};
@@ -1,15 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
- * Connected-workspace registry connection resolution.
4
- *
5
- * A connected workspace is `runtime.mode: 'workspace'` plus a `runtime.registryConnection` block
6
- * pointing at a remote registry — it is not a third runtime mode. The connection is resolved from
7
- * config + environment: the registry URL may be given inline or via an env-var *name*, and the
8
- * access token is always read from an env-var name (never persisted as a value). This is the single
9
- * source the publish (and later checkout) client reads its target + credentials from.
10
- *
11
- * The older `HANDOFF_CLOUD_*` names still work as deprecated fallbacks; see
12
- * {@link DEPRECATED_ENV_ALIASES}.
3
+ * Registry connection values resolve at config load. Saved device login credentials can fill
4
+ * missing values for CLI operations; neither source is persisted in the app's client config.
13
5
  */
14
6
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
15
7
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
@@ -21,89 +13,83 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
21
13
  });
22
14
  };
23
15
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.resolveAuthenticatedRegistryConnection = exports.resolveRegistryConnection = exports.DEFAULT_REGISTRY_ACCESS_TOKEN_ENV = exports.DEFAULT_REGISTRY_URL_ENV = void 0;
16
+ exports.unusedLoginNote = exports.loginCommandHint = exports.resolveAuthenticatedRegistryConnection = exports.resolveRegistryConnection = exports.DEFAULT_REGISTRY_ACCESS_TOKEN_ENV = exports.DEFAULT_REGISTRY_URL_ENV = void 0;
17
+ const resolve_env_1 = require("../config/resolve-env");
25
18
  const store_1 = require("../cli/auth/store");
26
19
  // Imported straight from the module, not the `auth` barrel, to keep the database schema off the CLI
27
20
  // startup path; this file is resolved for every command.
28
21
  const sync_secret_1 = require("./auth/sync-secret");
29
- const logger_1 = require("../utils/logger");
30
22
  /** Default env-var name holding the remote registry base URL. */
31
23
  exports.DEFAULT_REGISTRY_URL_ENV = 'HANDOFF_REGISTRY_URL';
32
24
  /** Default env-var name holding the connected-workspace access token. */
33
25
  exports.DEFAULT_REGISTRY_ACCESS_TOKEN_ENV = 'HANDOFF_REGISTRY_ACCESS_TOKEN';
34
- /**
35
- * Old env-var names we still read, keyed by the name that replaced them. Keeps workspaces set up
36
- * from the older docs working instead of silently reporting no registry.
37
- */
38
- const DEPRECATED_ENV_ALIASES = {
39
- [exports.DEFAULT_REGISTRY_URL_ENV]: 'HANDOFF_CLOUD_URL',
40
- [exports.DEFAULT_REGISTRY_ACCESS_TOKEN_ENV]: 'HANDOFF_CLOUD_TOKEN',
41
- };
42
- /** Aliases we've already warned about, so each one warns once per run and not once per lookup. */
43
- const warnedAliases = new Set();
44
- /**
45
- * Value of the named env var, falling back to its deprecated alias. Only the default names have
46
- * aliases: a custom `urlEnv`/`accessTokenEnv` is a deliberate choice, so a stray `HANDOFF_CLOUD_*`
47
- * must not stand in for it. The warning goes through {@link Logger.warn}, so
48
- * `HANDOFF_LOG_LEVEL=error` silences it in CI.
49
- */
50
- const readConnectionEnv = (name) => {
51
- var _a, _b;
52
- const configured = (_a = process.env[name]) === null || _a === void 0 ? void 0 : _a.trim();
53
- if (configured)
54
- return configured;
55
- const alias = DEPRECATED_ENV_ALIASES[name];
56
- const inherited = alias ? (_b = process.env[alias]) === null || _b === void 0 ? void 0 : _b.trim() : '';
57
- if (!inherited)
58
- return '';
59
- if (!warnedAliases.has(alias)) {
60
- warnedAliases.add(alias);
61
- logger_1.Logger.warn(`${alias} is deprecated and will be removed in a future release; set ${name} instead.`);
62
- }
63
- return inherited;
64
- };
65
- /**
66
- * Resolve the connected-workspace registry connection from config + environment. Never throws —
67
- * Callers inspect the resolved URL and access token to surface specific configuration errors.
68
- *
69
- * `urlEnv` and `accessTokenEnv` always report the canonical name, even when an alias supplied the
70
- * value, so errors built from them point at the variable to adopt.
71
- */
26
+ /** Read values resolved at config load, retaining variable names for CLI diagnostics. */
72
27
  const resolveRegistryConnection = (config) => {
73
- var _a, _b, _c, _d;
28
+ var _a, _b, _c;
74
29
  const connection = (_a = config === null || config === void 0 ? void 0 : config.runtime) === null || _a === void 0 ? void 0 : _a.registryConnection;
75
- const urlEnv = ((_b = connection === null || connection === void 0 ? void 0 : connection.urlEnv) === null || _b === void 0 ? void 0 : _b.trim()) || exports.DEFAULT_REGISTRY_URL_ENV;
76
- const accessTokenEnv = ((_c = connection === null || connection === void 0 ? void 0 : connection.accessTokenEnv) === null || _c === void 0 ? void 0 : _c.trim()) || exports.DEFAULT_REGISTRY_ACCESS_TOKEN_ENV;
77
- const inlineUrl = (_d = connection === null || connection === void 0 ? void 0 : connection.url) === null || _d === void 0 ? void 0 : _d.trim();
78
- const url = inlineUrl || readConnectionEnv(urlEnv);
79
- const accessToken = readConnectionEnv(accessTokenEnv);
30
+ const urlEnv = (0, resolve_env_1.configEnvName)(config, 'runtime.registryConnection.url') || exports.DEFAULT_REGISTRY_URL_ENV;
31
+ const accessTokenEnv = (0, resolve_env_1.configEnvName)(config, 'runtime.registryConnection.accessToken') || exports.DEFAULT_REGISTRY_ACCESS_TOKEN_ENV;
32
+ const url = ((_b = connection === null || connection === void 0 ? void 0 : connection.url) === null || _b === void 0 ? void 0 : _b.trim()) || '';
33
+ const accessToken = ((_c = connection === null || connection === void 0 ? void 0 : connection.accessToken) === null || _c === void 0 ? void 0 : _c.trim()) || '';
80
34
  return {
81
35
  url,
82
36
  accessToken,
83
37
  urlEnv,
84
38
  accessTokenEnv,
39
+ loginProfile: store_1.DEFAULT_LOGIN_PROFILE,
85
40
  };
86
41
  };
87
42
  exports.resolveRegistryConnection = resolveRegistryConnection;
43
+ /**
44
+ * The valid login to use for a profile. A profile without its own login falls back to the default
45
+ * login, so a workspace that names profiles for other reasons keeps working after one plain
46
+ * `handoff-app login`. The fallback is safe because a token is only ever used for the registry URL
47
+ * it was issued for.
48
+ */
49
+ const readProfileLogin = (workingPath, profile) => __awaiter(void 0, void 0, void 0, function* () {
50
+ const own = yield (0, store_1.readCliAuth)(workingPath, profile);
51
+ if ((0, store_1.cliAuthTokenIsValid)(own))
52
+ return { auth: own, profile };
53
+ const fallback = profile === store_1.DEFAULT_LOGIN_PROFILE ? null : yield (0, store_1.readCliAuth)(workingPath);
54
+ return (0, store_1.cliAuthTokenIsValid)(fallback) ? { auth: fallback, profile: store_1.DEFAULT_LOGIN_PROFILE } : null;
55
+ });
88
56
  /**
89
57
  * Resolve the same connection for an authenticated CLI operation, letting a saved device login fill
90
58
  * in what the environment does not provide. A valid login supplies both the registry URL and the
91
- * access token, so `handoff-app login` alone is enough to publish and checkout. Environment values
92
- * keep precedence for deterministic CI: `HANDOFF_REGISTRY_URL` (or inline config) wins over the
93
- * saved URL, and `HANDOFF_REGISTRY_ACCESS_TOKEN` wins over the saved token. The saved token only
94
- * applies to the URL it was issued for.
59
+ * access token, so `handoff-app login` alone is enough to publish and checkout. The login is the one
60
+ * saved under the selected profile, which is how one workspace addresses several registries.
61
+ * Environment values keep precedence for deterministic CI: `HANDOFF_REGISTRY_URL` (or inline config)
62
+ * wins over the saved URL, and `HANDOFF_REGISTRY_ACCESS_TOKEN` wins over the saved token. The saved
63
+ * token only applies to the URL it was issued for.
95
64
  *
96
65
  * A configured `HANDOFF_SYNC_SECRET` is the last resort. Being deployment-wide rather than personal,
97
66
  * it fills in only where there is no explicit token and no login (CI, in practice), instead of quietly
98
67
  * speaking for a developer who is signed in.
99
68
  */
100
- const resolveAuthenticatedRegistryConnection = (config_1, ...args_1) => __awaiter(void 0, [config_1, ...args_1], void 0, function* (config, workingPath = process.cwd()) {
69
+ const resolveAuthenticatedRegistryConnection = (config_1, ...args_1) => __awaiter(void 0, [config_1, ...args_1], void 0, function* (config, workingPath = process.cwd(), profile) {
101
70
  const connection = (0, exports.resolveRegistryConnection)(config);
102
- const auth = yield (0, store_1.readCliAuth)(workingPath);
103
- const login = (0, store_1.cliAuthTokenIsValid)(auth) ? auth : null;
104
- const url = connection.url || (login === null || login === void 0 ? void 0 : login.remoteUrl) || '';
105
- const savedToken = login && (0, store_1.cliAuthMatchesRegistry)(login, url) ? login.accessToken : '';
106
- const accessToken = connection.accessToken || savedToken || (0, sync_secret_1.resolveRegistrySyncSecret)();
107
- return Object.assign(Object.assign({}, connection), { url, accessToken });
71
+ const loginProfile = (profile === null || profile === void 0 ? void 0 : profile.trim()) || store_1.DEFAULT_LOGIN_PROFILE;
72
+ const login = yield readProfileLogin(workingPath, loginProfile);
73
+ const url = connection.url || (login === null || login === void 0 ? void 0 : login.auth.remoteUrl) || '';
74
+ const matches = Boolean(login) && (0, store_1.cliAuthMatchesRegistry)(login.auth, url);
75
+ const accessToken = connection.accessToken || (matches ? login.auth.accessToken : '') || (0, sync_secret_1.resolveRegistrySyncSecret)();
76
+ return Object.assign(Object.assign({}, connection), { url,
77
+ accessToken,
78
+ loginProfile, unmatchedLogin: login && !matches ? { profile: login.profile, url: login.auth.remoteUrl } : undefined });
108
79
  });
109
80
  exports.resolveAuthenticatedRegistryConnection = resolveAuthenticatedRegistryConnection;
81
+ /** The `handoff-app login` call that would authorize this connection, naming the selected profile. */
82
+ const loginCommandHint = (connection) => [
83
+ 'handoff-app login',
84
+ connection.loginProfile === store_1.DEFAULT_LOGIN_PROFILE ? '' : ` --profile ${connection.loginProfile}`,
85
+ ` --url ${connection.url || '<registry-url>'}`,
86
+ ].join('');
87
+ exports.loginCommandHint = loginCommandHint;
88
+ /**
89
+ * Names a saved login that was skipped because it belongs to another registry. Without it, a run
90
+ * that resolves its URL from the environment reports a missing token while a login is in place.
91
+ */
92
+ const unusedLoginNote = (connection) => connection.unmatchedLogin
93
+ ? ` The login saved for profile "${connection.unmatchedLogin.profile}" is for ${connection.unmatchedLogin.url}, not ${connection.url}.`
94
+ : '';
95
+ exports.unusedLoginNote = unusedLoginNote;
@@ -9,7 +9,7 @@
9
9
  * credentials are referenced by environment-variable name only and resolved to a value at runtime;
10
10
  * the value is never stored in config.
11
11
  */
12
- import type { Config } from '../../types/config';
12
+ import type { ResolvedConfig } from '../../types/config';
13
13
  /** Built-in registry database connection driver. Both ship with the package and target Postgres. */
14
14
  export type RegistryDatabaseDriver = 'pg' | 'neon';
15
15
  /** Default env-var name holding the database connection string. */
@@ -28,19 +28,12 @@ export interface ResolvedRegistryDatabase {
28
28
  connectionString: string;
29
29
  }
30
30
  /** Resolve the env-var name holding the database URL (defaults to `DATABASE_URL`). */
31
- export declare const resolveDatabaseUrlEnv: (config: Config | null | undefined) => string;
32
- /**
33
- * Resolve the legacy fixed-token env-var name for configuration compatibility.
34
- *
35
- * @deprecated The returned env var is intentionally ignored by registry authorization. Use a
36
- * user-issued access token or `handoff-app login`.
37
- */
38
- export declare const resolveApiTokenEnv: (config: Config | null | undefined) => string;
31
+ export declare const resolveDatabaseUrlEnv: (config: ResolvedConfig | null | undefined) => string;
39
32
  /** Resolve the configured registry database driver (defaults to `pg`). */
40
- export declare const resolveRegistryDriver: (config: Config | null | undefined) => RegistryDatabaseDriver;
33
+ export declare const resolveRegistryDriver: (config: ResolvedConfig | null | undefined) => RegistryDatabaseDriver;
41
34
  /**
42
35
  * Resolve the registry database connection from config + environment. Throws an actionable error
43
36
  * when the configured env var is unset so the CLI can surface a clear "missing database URL"
44
37
  * message instead of failing deep inside the driver.
45
38
  */
46
- export declare const resolveRegistryDatabase: (config: Config | null | undefined) => ResolvedRegistryDatabase;
39
+ export declare const resolveRegistryDatabase: (config: ResolvedConfig | null | undefined) => ResolvedRegistryDatabase;
@@ -11,7 +11,7 @@
11
11
  * the value is never stored in config.
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.resolveRegistryDatabase = exports.resolveRegistryDriver = exports.resolveApiTokenEnv = exports.resolveDatabaseUrlEnv = exports.DEFAULT_REGISTRY_DRIVER = exports.DEFAULT_REGISTRY_API_TOKEN_ENV = exports.DEFAULT_DATABASE_URL_ENV = void 0;
14
+ exports.resolveRegistryDatabase = exports.resolveRegistryDriver = exports.resolveDatabaseUrlEnv = exports.DEFAULT_REGISTRY_DRIVER = exports.DEFAULT_REGISTRY_API_TOKEN_ENV = exports.DEFAULT_DATABASE_URL_ENV = void 0;
15
15
  /** Default env-var name holding the database connection string. */
16
16
  exports.DEFAULT_DATABASE_URL_ENV = 'DATABASE_URL';
17
17
  /** @deprecated Fixed registry server secrets are no longer authorized. */
@@ -20,23 +20,11 @@ exports.DEFAULT_REGISTRY_API_TOKEN_ENV = 'HANDOFF_REGISTRY_API_TOKEN';
20
20
  exports.DEFAULT_REGISTRY_DRIVER = 'pg';
21
21
  /** Resolve the env-var name holding the database URL (defaults to `DATABASE_URL`). */
22
22
  const resolveDatabaseUrlEnv = (config) => {
23
- var _a, _b, _c;
24
- const configured = (_c = (_b = (_a = config === null || config === void 0 ? void 0 : config.runtime) === null || _a === void 0 ? void 0 : _a.registry) === null || _b === void 0 ? void 0 : _b.databaseUrlEnv) === null || _c === void 0 ? void 0 : _c.trim();
23
+ var _a, _b, _c, _d;
24
+ const configured = (_d = (_c = (_b = (_a = config === null || config === void 0 ? void 0 : config.runtime) === null || _a === void 0 ? void 0 : _a.registry) === null || _b === void 0 ? void 0 : _b.databaseUrl) === null || _c === void 0 ? void 0 : _c.$env) === null || _d === void 0 ? void 0 : _d.trim();
25
25
  return configured || exports.DEFAULT_DATABASE_URL_ENV;
26
26
  };
27
27
  exports.resolveDatabaseUrlEnv = resolveDatabaseUrlEnv;
28
- /**
29
- * Resolve the legacy fixed-token env-var name for configuration compatibility.
30
- *
31
- * @deprecated The returned env var is intentionally ignored by registry authorization. Use a
32
- * user-issued access token or `handoff-app login`.
33
- */
34
- const resolveApiTokenEnv = (config) => {
35
- var _a, _b, _c;
36
- const configured = (_c = (_b = (_a = config === null || config === void 0 ? void 0 : config.runtime) === null || _a === void 0 ? void 0 : _a.registry) === null || _b === void 0 ? void 0 : _b.apiTokenEnv) === null || _c === void 0 ? void 0 : _c.trim();
37
- return configured || exports.DEFAULT_REGISTRY_API_TOKEN_ENV;
38
- };
39
- exports.resolveApiTokenEnv = resolveApiTokenEnv;
40
28
  /** Resolve the configured registry database driver (defaults to `pg`). */
41
29
  const resolveRegistryDriver = (config) => {
42
30
  var _a, _b, _c, _d;
@@ -55,7 +43,7 @@ const resolveRegistryDatabase = (config) => {
55
43
  const connectionString = (_a = process.env[databaseUrlEnv]) === null || _a === void 0 ? void 0 : _a.trim();
56
44
  if (!connectionString) {
57
45
  throw new Error(`Registry database URL is not configured. Set the "${databaseUrlEnv}" environment variable to a ` +
58
- `PostgreSQL connection string (or change "runtime.registry.databaseUrlEnv" to point at the env var you use).`);
46
+ `PostgreSQL connection string (or change "runtime.registry.databaseUrl" to point at the env var you use).`);
59
47
  }
60
48
  return { driver, databaseUrlEnv, connectionString };
61
49
  };
@@ -1,5 +1,5 @@
1
1
  export { buildMetadata, componentFiles, components, docsArtifacts, patternFiles, patterns, registryAccessTokens, registryAuthActionTokens, registryAuthRateLimits, registryDeviceAuthorizations, registryInstallations, registrySchema, registryUsers, type BuildMetadataEntityKind, type DocsArtifactEntityKind, type RegistryAccessScope, type RegistryAuthActionPurpose, type RegistryDeviceAuthorizationStatus, type RegistryRateLimitBucket, type RegistryReviewMetadata, type RegistryUserRole, type RegistryUserStatus, } from './schema';
2
2
  export type { ArtifactBuildStatus as RegistryBuildStatus } from '../../artifacts/types';
3
- export { DEFAULT_DATABASE_URL_ENV, DEFAULT_REGISTRY_DRIVER, DEFAULT_REGISTRY_API_TOKEN_ENV, resolveApiTokenEnv, resolveDatabaseUrlEnv, resolveRegistryDriver, resolveRegistryDatabase, type RegistryDatabaseDriver, type ResolvedRegistryDatabase, } from './driver';
3
+ export { DEFAULT_DATABASE_URL_ENV, DEFAULT_REGISTRY_DRIVER, DEFAULT_REGISTRY_API_TOKEN_ENV, resolveDatabaseUrlEnv, resolveRegistryDriver, resolveRegistryDatabase, type RegistryDatabaseDriver, type ResolvedRegistryDatabase, } from './driver';
4
4
  export { createRegistryDbConnection, type CreateRegistryDbConnectionParams, type RegistryDatabase, type RegistryDbConnection, } from './client';
5
5
  export { getMigrationsFolder, runRegistryMigrations } from './migrate';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runRegistryMigrations = exports.getMigrationsFolder = exports.createRegistryDbConnection = exports.resolveRegistryDatabase = exports.resolveRegistryDriver = exports.resolveDatabaseUrlEnv = exports.resolveApiTokenEnv = exports.DEFAULT_REGISTRY_API_TOKEN_ENV = exports.DEFAULT_REGISTRY_DRIVER = exports.DEFAULT_DATABASE_URL_ENV = exports.registryUsers = exports.registrySchema = exports.registryInstallations = exports.registryDeviceAuthorizations = exports.registryAuthRateLimits = exports.registryAuthActionTokens = exports.registryAccessTokens = exports.patterns = exports.patternFiles = exports.docsArtifacts = exports.components = exports.componentFiles = exports.buildMetadata = void 0;
3
+ exports.runRegistryMigrations = exports.getMigrationsFolder = exports.createRegistryDbConnection = exports.resolveRegistryDatabase = exports.resolveRegistryDriver = exports.resolveDatabaseUrlEnv = exports.DEFAULT_REGISTRY_API_TOKEN_ENV = exports.DEFAULT_REGISTRY_DRIVER = exports.DEFAULT_DATABASE_URL_ENV = exports.registryUsers = exports.registrySchema = exports.registryInstallations = exports.registryDeviceAuthorizations = exports.registryAuthRateLimits = exports.registryAuthActionTokens = exports.registryAccessTokens = exports.patterns = exports.patternFiles = exports.docsArtifacts = exports.components = exports.componentFiles = exports.buildMetadata = void 0;
4
4
  var schema_1 = require("./schema");
5
5
  Object.defineProperty(exports, "buildMetadata", { enumerable: true, get: function () { return schema_1.buildMetadata; } });
6
6
  Object.defineProperty(exports, "componentFiles", { enumerable: true, get: function () { return schema_1.componentFiles; } });
@@ -19,7 +19,6 @@ var driver_1 = require("./driver");
19
19
  Object.defineProperty(exports, "DEFAULT_DATABASE_URL_ENV", { enumerable: true, get: function () { return driver_1.DEFAULT_DATABASE_URL_ENV; } });
20
20
  Object.defineProperty(exports, "DEFAULT_REGISTRY_DRIVER", { enumerable: true, get: function () { return driver_1.DEFAULT_REGISTRY_DRIVER; } });
21
21
  Object.defineProperty(exports, "DEFAULT_REGISTRY_API_TOKEN_ENV", { enumerable: true, get: function () { return driver_1.DEFAULT_REGISTRY_API_TOKEN_ENV; } });
22
- Object.defineProperty(exports, "resolveApiTokenEnv", { enumerable: true, get: function () { return driver_1.resolveApiTokenEnv; } });
23
22
  Object.defineProperty(exports, "resolveDatabaseUrlEnv", { enumerable: true, get: function () { return driver_1.resolveDatabaseUrlEnv; } });
24
23
  Object.defineProperty(exports, "resolveRegistryDriver", { enumerable: true, get: function () { return driver_1.resolveRegistryDriver; } });
25
24
  Object.defineProperty(exports, "resolveRegistryDatabase", { enumerable: true, get: function () { return driver_1.resolveRegistryDatabase; } });
@@ -79,14 +79,14 @@ const assertUniqueCatalogIds = (handoff) => {
79
79
  */
80
80
  const resolveConnectionOrThrow = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
81
81
  (0, exports.assertPublishableWorkspace)(handoff);
82
- const connection = yield (0, connection_1.resolveAuthenticatedRegistryConnection)(handoff.config, handoff.workingPath);
82
+ const connection = yield (0, connection_1.resolveAuthenticatedRegistryConnection)(handoff.config, handoff.workingPath, handoff.getProfile());
83
83
  if (!connection.url) {
84
- throw new PublishError(`No registry is configured. Run \`handoff-app login --url <registry-url>\`, set runtime.registryConnection.url, ` +
84
+ throw new PublishError(`No registry is configured. Run \`${(0, connection_1.loginCommandHint)(connection)}\`, set runtime.registryConnection.url, ` +
85
85
  `or set the "${connection.urlEnv}" environment variable to the base URL of the registry to publish to.`);
86
86
  }
87
87
  if (!connection.accessToken) {
88
- throw new PublishError(`No registry access token is configured. Run \`handoff-app login --url ${connection.url}\`, or set the ` +
89
- `"${connection.accessTokenEnv}" environment variable to a user-issued token for CI.`);
88
+ throw new PublishError(`No registry access token is configured. Run \`${(0, connection_1.loginCommandHint)(connection)}\`, or set the ` +
89
+ `"${connection.accessTokenEnv}" environment variable to a user-issued token for CI.${(0, connection_1.unusedLoginNote)(connection)}`);
90
90
  }
91
91
  return connection;
92
92
  });
@@ -9,7 +9,7 @@
9
9
  * without changing how anything is discovered or built.
10
10
  */
11
11
  import type { ComponentListObject, PageListObject, PatternListObject } from '../transformers/preview/types';
12
- import type { Config, RuntimeConfig } from '../types/config';
12
+ import type { ResolvedConfig, RuntimeConfig } from '../types/config';
13
13
  import type { ComponentStore, PageStore, PatternStore, SourceReference, TextFileResource, TokenArtifactResource, TokenSetRecord, TokenStore } from './types';
14
14
  /**
15
15
  * Minimal context needed to back the filesystem store. A `Handoff` instance satisfies this (it
@@ -18,7 +18,7 @@ import type { ComponentStore, PageStore, PatternStore, SourceReference, TextFile
18
18
  */
19
19
  export interface FilesystemStoreContext {
20
20
  runtimeConfig?: RuntimeConfig | null;
21
- config?: Config | null;
21
+ config?: ResolvedConfig | null;
22
22
  /** Workspace root (`Handoff.workingPath`), holding `public/` and `fonts/`. */
23
23
  workingPath?: string;
24
24
  /** Absolute path to the generated `tokens.json` (`Handoff.getTokensFilePath()`). */
@@ -1,3 +1,4 @@
1
+ import type { EnvValue, EnvSecret, RuntimeEnvReference } from '../config/from-env';
1
2
  import { BuildOptions } from 'esbuild';
2
3
  import type Handlebars from 'handlebars';
3
4
  import { Types as HandoffTypes } from 'handoff-core';
@@ -63,8 +64,8 @@ export type RegisterHandlebarsHelpersContext = {
63
64
  };
64
65
  export interface NextAppConfig {
65
66
  theme?: string;
66
- title: string;
67
- client: string;
67
+ title?: string;
68
+ client?: string;
68
69
  google_tag_manager?: string | null | undefined;
69
70
  googleTagManager?: string | null | undefined;
70
71
  type_copy?: string;
@@ -73,15 +74,17 @@ export interface NextAppConfig {
73
74
  typeSort?: string[];
74
75
  color_sort?: string[];
75
76
  colorSort?: string[];
76
- breakpoints: Breakpoints;
77
+ /** Omit to keep the defaults; a declared block merges onto them rather than replacing them. */
78
+ breakpoints?: Breakpoints;
77
79
  component_sort?: string[];
78
80
  componentSort?: string[];
79
81
  base_path?: string;
80
82
  basePath?: string;
81
- attribution: boolean;
83
+ /** @default true */
84
+ attribution?: boolean;
82
85
  ports?: {
83
- app: number;
84
- websocket: number;
86
+ app: EnvValue<number>;
87
+ websocket: EnvValue<number>;
85
88
  };
86
89
  }
87
90
  /**
@@ -139,13 +142,8 @@ export interface HandoffRuntimeConfig {
139
142
  };
140
143
  /** Registry-mode host settings. Env-var values are stored as names, never as secrets. */
141
144
  registry?: {
142
- /** Name of the env var holding the database URL. @default "DATABASE_URL" */
143
- databaseUrlEnv?: string;
144
- /**
145
- * @deprecated Fixed registry secrets are no longer authorized. This option remains parseable
146
- * only so existing configuration files do not break.
147
- */
148
- apiTokenEnv?: string;
145
+ /** Environment reference to the database URL. @default fromEnv('DATABASE_URL') */
146
+ databaseUrl?: RuntimeEnvReference<string>;
149
147
  database?: {
150
148
  /**
151
149
  * PostgreSQL is the supported database. `driver` selects the built-in connection driver — how
@@ -168,11 +166,11 @@ export interface HandoffRuntimeConfig {
168
166
  adapter?: 'database' | 'vercel-blob' | 'custom';
169
167
  /** For `adapter: "custom"` - server-only module path default-exporting a `defineAssetStorage` adapter. */
170
168
  module?: string;
171
- /** For `adapter: "vercel-blob"` - env var name holding the Blob read/write token. @default "BLOB_READ_WRITE_TOKEN" */
172
- tokenEnv?: string;
169
+ /** For `adapter: "vercel-blob"` - environment reference to the Blob read/write token. @default fromEnv('BLOB_READ_WRITE_TOKEN') */
170
+ token?: RuntimeEnvReference<string>;
173
171
  /** Max bytes kept inline in the database `bytea` column (larger uploads are rejected). @default 4194304 */
174
172
  maxInlineBytes?: number;
175
- /** Non-secret adapter options (bucket env-var names, region, a custom `providerId`, …). */
173
+ /** Non-secret options, JSON-encoded into the bundle. Environment references are rejected. Pass variable names instead. */
176
174
  options?: Record<string, unknown>;
177
175
  };
178
176
  };
@@ -181,21 +179,20 @@ export interface HandoffRuntimeConfig {
181
179
  * `mode: 'workspace'` plus this block — it is not a third runtime mode.
182
180
  */
183
181
  registryConnection?: {
184
- url?: string;
185
- /** @default "HANDOFF_REGISTRY_URL" */
186
- urlEnv?: string;
187
- /** @default "HANDOFF_REGISTRY_ACCESS_TOKEN" */
188
- accessTokenEnv?: string;
182
+ /** @default fromEnv('HANDOFF_REGISTRY_URL', { default: '' }) */
183
+ url?: EnvValue<string>;
184
+ /** @default fromEnv('HANDOFF_REGISTRY_ACCESS_TOKEN', { default: '' }) */
185
+ accessToken?: EnvSecret<string>;
189
186
  };
190
187
  }
191
188
  export interface Config {
192
- dev_access_token?: string | null | undefined;
193
- devAccessToken?: string | null | undefined;
194
- figma_project_id?: string | null | undefined;
195
- figmaProjectId?: string | null | undefined;
196
- exportsOutputDirectory?: string;
197
- sitesOutputDirectory?: string;
198
- useVariables?: boolean;
189
+ dev_access_token?: EnvSecret<string | null>;
190
+ devAccessToken?: EnvSecret<string | null>;
191
+ figma_project_id?: EnvValue<string | null>;
192
+ figmaProjectId?: EnvValue<string | null>;
193
+ exportsOutputDirectory?: EnvValue<string>;
194
+ sitesOutputDirectory?: EnvValue<string>;
195
+ useVariables?: EnvValue<boolean>;
199
196
  /**
200
197
  * Configuration for React component docs generation (handoff-docgen).
201
198
  */
@@ -387,7 +384,29 @@ export interface ClientRuntimeConfig {
387
384
  */
388
385
  connected: boolean;
389
386
  }
390
- export type ClientConfig = Pick<Config, 'app' | 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'assets_zip_links' | 'useVariables'> & {
387
+ /** Values available after config loading. Deferred references retain their names. */
388
+ export type ResolvedConfig = Omit<Config, 'dev_access_token' | 'devAccessToken' | 'figma_project_id' | 'figmaProjectId' | 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'useVariables' | 'app' | 'runtime'> & {
389
+ dev_access_token?: string | null;
390
+ devAccessToken?: string | null;
391
+ figma_project_id?: string | null;
392
+ figmaProjectId?: string | null;
393
+ exportsOutputDirectory?: string;
394
+ sitesOutputDirectory?: string;
395
+ useVariables?: boolean;
396
+ app?: Omit<NextAppConfig, 'ports'> & {
397
+ ports?: {
398
+ app: number;
399
+ websocket: number;
400
+ };
401
+ };
402
+ runtime?: Omit<HandoffRuntimeConfig, 'registryConnection'> & {
403
+ registryConnection?: {
404
+ url?: string;
405
+ accessToken?: string;
406
+ };
407
+ };
408
+ };
409
+ export type ClientConfig = Pick<ResolvedConfig, 'app' | 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'assets_zip_links' | 'useVariables'> & {
391
410
  runtime: ClientRuntimeConfig;
392
411
  };
393
412
  export interface RuntimeConfigComponentOptions {
package/docs/api.md CHANGED
@@ -29,6 +29,37 @@ handoff.fetch();
29
29
  3. Build your typescript `tsc`
30
30
  4. Run your project `node handoff.js`
31
31
 
32
+ ## Configuration profiles
33
+
34
+ A profile is a sidecar config file, `handoff.config.<profile>.*`, that merges
35
+ onto the base config. The CLI selects one with `--profile`, and the class takes
36
+ it as a constructor option. Handoff reads `HANDOFF_PROFILE` when neither is
37
+ given.
38
+
39
+ ```js
40
+ const handoff = new Handoff({ profile: 'registry' });
41
+ ```
42
+
43
+ A profile also names the saved registry login that `publish` and `checkout`
44
+ use, so one workspace can address several registries. `profileWithoutConfig`
45
+ decides what a profile may resolve to when it has no config file:
46
+ `'saved-login'` accepts a name a saved login uses, and `'any'` accepts any
47
+ valid name, which is how `login` creates a new one.
48
+
49
+ ```js
50
+ const handoff = new Handoff({ profile: 'staging', profileWithoutConfig: 'saved-login' });
51
+ ```
52
+
53
+ `defineConfig` types a profile as well as a base config.
54
+
55
+ ```ts
56
+ import { defineConfig } from 'handoff-app';
57
+
58
+ export default defineConfig({
59
+ runtime: { registry: { database: { driver: 'neon' } } },
60
+ });
61
+ ```
62
+
32
63
  ## Methods
33
64
 
34
65
  Methods of the handoff class can be called to run actions in the
package/docs/cli.md CHANGED
@@ -58,9 +58,9 @@ Commands:
58
58
 
59
59
  db:migrate [opts] - Runs registry database migrations (Drizzle / PostgreSQL). Reads the project config and database env vars, applies the package-owned migration set, and runs independently of `build`.
60
60
 
61
- login --url <registry-url> - Authorizes this workspace in the browser and stores a revocable token in `.handoff/cli-auth.json`.
61
+ login [--profile <name>] --url <registry-url> - Authorizes this workspace in the browser and stores a revocable token in `.handoff/cli-auth.json`, under the selected profile.
62
62
 
63
- logout [--url <registry-url>] - Revokes the saved token when possible, then removes the local credential.
63
+ logout [--profile <name>] [--url <registry-url>] [--all] - Revokes the saved token when possible, then removes the local credential of the selected profile, or of every profile with `--all`.
64
64
 
65
65
  make
66
66
  make:component <name> [opts] - Creates a new catalog item with a declaration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handoff-app",
3
- "version": "2.0.0-7",
3
+ "version": "2.0.0-8",
4
4
  "description": "Automated documentation toolchain for building client side documentation from figma",
5
5
  "author ": {
6
6
  "name": "Convertiv",