@quonfig/cli 0.0.29 → 0.0.30
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/README.md +35 -35
- package/dist/codegen/fetch-workspace-snapshot.js +3 -3
- package/dist/commands/delete.js +2 -1
- package/dist/commands/login.js +0 -1
- package/dist/commands/migrate/doctor.js +3 -1
- package/dist/commands/migrate.js +10 -13
- package/dist/commands/override.js +1 -1
- package/dist/commands/pull.js +12 -12
- package/dist/commands/push.d.ts +1 -1
- package/dist/commands/push.js +5 -5
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +6 -13
- package/dist/commands/whoami.js +15 -3
- package/dist/commands/workspace/bootstrap.js +8 -12
- package/dist/commands/workspace/create.d.ts +5 -1
- package/dist/commands/workspace/create.js +18 -7
- package/dist/commands/workspace/switch.d.ts +7 -1
- package/dist/commands/workspace/switch.js +45 -27
- package/dist/commands/workspace.d.ts +5 -0
- package/dist/commands/workspace.js +28 -9
- package/dist/migrate/sources/launch/translate.js +6 -3
- package/dist/migrate/sources/launch/zod-to-json-schema.js +4 -0
- package/dist/push/config-push-client.d.ts +1 -1
- package/dist/push/config-push-client.js +3 -5
- package/dist/util/get-client.js +12 -12
- package/dist/util/get-valid-token.d.ts +7 -7
- package/dist/util/get-valid-token.js +19 -13
- package/dist/util/gitea-api.d.ts +2 -2
- package/dist/util/gitea-api.js +5 -7
- package/dist/util/resolve-workspace.d.ts +14 -4
- package/dist/util/resolve-workspace.js +32 -20
- package/dist/util/token-storage.d.ts +10 -5
- package/dist/util/token-storage.js +14 -7
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { getApiUrl } from './domain-urls.js';
|
|
2
|
-
import { getValidAccessToken
|
|
2
|
+
import { getValidAccessToken } from './get-valid-token.js';
|
|
3
3
|
import { tryParseWorkspacePin } from './quonfig-json.js';
|
|
4
4
|
import { findOrgIdBySlug, getActiveProfile, loadAuthConfig, loadTokens, saveAuthConfig, } from './token-storage.js';
|
|
5
5
|
const UUID_PATTERN = /^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/i;
|
|
6
6
|
const BARE_SLUG_ENV_MIGRATION_MESSAGE = 'QUONFIG_WORKSPACE must be in org/workspace form (e.g. acme/foo). ' +
|
|
7
7
|
'Bare workspace slugs are no longer accepted. Update your .env and run `qfg login` if you have not yet migrated.';
|
|
8
8
|
/**
|
|
9
|
-
* Resolve the target workspace UUID for commands that
|
|
10
|
-
* (e.g. pull, push — which talk to Gitea, not the
|
|
11
|
-
*
|
|
9
|
+
* Resolve the target workspace UUID + owning org slug for commands that
|
|
10
|
+
* don't extend APICommand (e.g. pull, push — which talk to Gitea, not the
|
|
11
|
+
* oRPC API, but still need to mint a Gitea token for the right org).
|
|
12
12
|
*
|
|
13
13
|
* Handles all three auth paths in the same order as util/get-client.ts:
|
|
14
14
|
* 1. QUONFIG_API_KEY + QUONFIG_WORKSPACE (or --workspace flag) — CI/headless.
|
|
@@ -26,7 +26,7 @@ export async function resolveWorkspaceUuid(command, flagOverride) {
|
|
|
26
26
|
}
|
|
27
27
|
if (UUID_PATTERN.test(override)) {
|
|
28
28
|
command.verboseLog('ApiKey auth', { source: 'uuid', workspaceId: override });
|
|
29
|
-
return override;
|
|
29
|
+
return { workspaceId: override, orgSlug: '' };
|
|
30
30
|
}
|
|
31
31
|
// Slug — resolve via the server since there's no local auth config in CI mode.
|
|
32
32
|
// API-key path short-circuits before consulting the orgId.
|
|
@@ -55,12 +55,9 @@ export async function resolveWorkspaceUuid(command, flagOverride) {
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
command.verboseLog('ApiKey auth', { source: 'slug', slug: override, workspaceId: match.workspaceId });
|
|
58
|
-
return match.workspaceId;
|
|
58
|
+
return { workspaceId: match.workspaceId, orgSlug: '' };
|
|
59
59
|
}
|
|
60
|
-
// OAuth path with an explicit override → must be in org/ws form.
|
|
61
|
-
// look up the workosOrgId for that org slug in the local token store, and
|
|
62
|
-
// resolve the workspaceSlug *within that org*. Bare slug is rejected with
|
|
63
|
-
// a migration message (the API-key branch above is the one exception).
|
|
60
|
+
// OAuth path with an explicit override → must be in org/ws form.
|
|
64
61
|
if (override) {
|
|
65
62
|
return resolveOrgScopedWorkspace(command, override);
|
|
66
63
|
}
|
|
@@ -68,11 +65,6 @@ export async function resolveWorkspaceUuid(command, flagOverride) {
|
|
|
68
65
|
// auth config from tokens if the config file is absent.
|
|
69
66
|
let authConfig = await loadAuthConfig();
|
|
70
67
|
if (!authConfig) {
|
|
71
|
-
// Auth config can be missing while tokens.json still holds a valid
|
|
72
|
-
// refresh_token — e.g. after the domain-scoped config split (commit
|
|
73
|
-
// 0f8bee6) or a partial login that wrote tokens but never the config
|
|
74
|
-
// (qfg-2qj). getValidAccessToken already knows how to refresh, so try
|
|
75
|
-
// that path before declaring "Not logged in".
|
|
76
68
|
const store = await loadTokens();
|
|
77
69
|
const tokens = store ? Object.values(store.tokensByOrg)[0] : undefined;
|
|
78
70
|
if (!tokens?.refresh_token) {
|
|
@@ -85,7 +77,10 @@ export async function resolveWorkspaceUuid(command, flagOverride) {
|
|
|
85
77
|
if (!profileData?.workspace) {
|
|
86
78
|
command.error('No active workspace. Run `qfg login` / `qfg workspace switch`, or set QUONFIG_API_KEY + QUONFIG_WORKSPACE.', { exit: 1 });
|
|
87
79
|
}
|
|
88
|
-
|
|
80
|
+
if (!profileData.organizationSlug) {
|
|
81
|
+
command.error('Saved profile is missing organization_slug. Run `qfg login` to refresh, or `qfg workspace switch <org>/<ws>`.', { exit: 1 });
|
|
82
|
+
}
|
|
83
|
+
return { workspaceId: profileData.workspace, orgSlug: profileData.organizationSlug };
|
|
89
84
|
}
|
|
90
85
|
/**
|
|
91
86
|
* OAuth-mode resolver for an explicit `<org-slug>/<workspace-slug>` override.
|
|
@@ -142,7 +137,7 @@ async function resolveOrgScopedWorkspace(command, override) {
|
|
|
142
137
|
command.error(`Workspace "${workspaceSlug}" not found in org "${orgSlug}". Available: ${available}.`, { exit: 1 });
|
|
143
138
|
}
|
|
144
139
|
command.verboseLog('OAuth workspace lookup', { orgSlug, workspaceSlug, workosOrgId, workspaceId: match.workspaceId });
|
|
145
|
-
return match.workspaceId;
|
|
140
|
+
return { workspaceId: match.workspaceId, orgSlug };
|
|
146
141
|
}
|
|
147
142
|
/**
|
|
148
143
|
* Best-effort rebuild of the on-disk auth config from a working refresh_token.
|
|
@@ -153,11 +148,21 @@ async function resolveOrgScopedWorkspace(command, override) {
|
|
|
153
148
|
* a message that points the user at `qfg login`.
|
|
154
149
|
*/
|
|
155
150
|
async function recoverAuthConfigFromTokens(command) {
|
|
151
|
+
const store = await loadTokens();
|
|
152
|
+
if (!store) {
|
|
153
|
+
command.error('Not logged in. Run `qfg login` first.', { exit: 401 });
|
|
154
|
+
}
|
|
155
|
+
// Pick any logged-in org's tokens to call /userWorkspaces/list. The list is
|
|
156
|
+
// org-scoped, so a single first-org JWT only sees that org's workspaces —
|
|
157
|
+
// but we only need one workspace to seed the default profile, and the user
|
|
158
|
+
// can always `qfg workspace switch` to a different one afterwards.
|
|
159
|
+
const [seedOrgId, seedTokens] = Object.entries(store.tokensByOrg)[0] ?? [];
|
|
160
|
+
if (!seedOrgId || !seedTokens) {
|
|
161
|
+
command.error('Not logged in. Run `qfg login` first.', { exit: 401 });
|
|
162
|
+
}
|
|
156
163
|
let jwt;
|
|
157
164
|
try {
|
|
158
|
-
|
|
159
|
-
const orgId = await resolveDefaultOrgId();
|
|
160
|
-
jwt = await getValidAccessToken(orgId, command.verboseLog);
|
|
165
|
+
jwt = await getValidAccessToken(seedOrgId, command.verboseLog);
|
|
161
166
|
}
|
|
162
167
|
catch (error) {
|
|
163
168
|
const detail = error instanceof Error ? error.message : String(error);
|
|
@@ -186,6 +191,12 @@ async function recoverAuthConfigFromTokens(command) {
|
|
|
186
191
|
command.error('Tokens found but no workspace profile on disk. Run `qfg login` to repopulate.', { exit: 401 });
|
|
187
192
|
}
|
|
188
193
|
const match = candidates[0];
|
|
194
|
+
// Prefer the slug the server returned; fall back to the slug we have
|
|
195
|
+
// cached on the seed token (qfg-kr7 stores this on every login).
|
|
196
|
+
const orgSlug = match.organizationSlug ?? seedTokens.org_slug;
|
|
197
|
+
if (!orgSlug) {
|
|
198
|
+
command.error('Tokens found but org slug is missing. Run `qfg login` to repopulate.', { exit: 401 });
|
|
199
|
+
}
|
|
189
200
|
const recovered = {
|
|
190
201
|
defaultProfile: 'default',
|
|
191
202
|
profiles: {
|
|
@@ -194,6 +205,7 @@ async function recoverAuthConfigFromTokens(command) {
|
|
|
194
205
|
workspaceName: match.workspaceSlug,
|
|
195
206
|
workspaceSlug: match.workspaceSlug,
|
|
196
207
|
organizationName: match.organizationName,
|
|
208
|
+
organizationSlug: orgSlug,
|
|
197
209
|
},
|
|
198
210
|
},
|
|
199
211
|
};
|
|
@@ -17,7 +17,14 @@ export interface TokenSet {
|
|
|
17
17
|
/** Org display name for `qfg whoami` / `qfg workspace`. */
|
|
18
18
|
org_name?: string;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Schema version of tokens.json. Stamped on every saveTokens write so a
|
|
22
|
+
* future CLI can refuse files written by an even-newer CLI with a clear
|
|
23
|
+
* "upgrade your CLI" error.
|
|
24
|
+
*/
|
|
25
|
+
export declare const TOKEN_STORE_VERSION = 2;
|
|
20
26
|
export interface TokenStore {
|
|
27
|
+
version?: number;
|
|
21
28
|
defaultOrgId?: string;
|
|
22
29
|
tokensByOrg: {
|
|
23
30
|
[workosOrgId: string]: TokenSet;
|
|
@@ -40,12 +47,10 @@ export interface AuthConfig {
|
|
|
40
47
|
workspaceSlug?: string;
|
|
41
48
|
organizationName?: string;
|
|
42
49
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
50
|
+
* Org URL slug. Used both for rendering the org/ws pin form and as
|
|
51
|
+
* the lookup key into the token store (slug → workosOrgId via
|
|
52
|
+
* findOrgIdBySlug) when a command needs an org-scoped JWT.
|
|
46
53
|
*/
|
|
47
|
-
workosOrgId?: string;
|
|
48
|
-
/** Org URL slug — used when rendering the org/ws pin form. */
|
|
49
54
|
organizationSlug?: string;
|
|
50
55
|
};
|
|
51
56
|
};
|
|
@@ -13,6 +13,12 @@ const getConfigFile = (options) => {
|
|
|
13
13
|
const filename = domain === 'quonfig.com' ? 'config' : `config-${domain.replaceAll('.', '-')}`;
|
|
14
14
|
return path.join(getQuonfigDir(options), filename);
|
|
15
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Schema version of tokens.json. Stamped on every saveTokens write so a
|
|
18
|
+
* future CLI can refuse files written by an even-newer CLI with a clear
|
|
19
|
+
* "upgrade your CLI" error.
|
|
20
|
+
*/
|
|
21
|
+
export const TOKEN_STORE_VERSION = 2;
|
|
16
22
|
export const getTokenForOrg = (store, workosOrgId) => store.tokensByOrg[workosOrgId];
|
|
17
23
|
/**
|
|
18
24
|
* Find the workosOrgId in the per-org token store whose TokenSet has the
|
|
@@ -67,7 +73,8 @@ export const saveTokens = async (store, options) => {
|
|
|
67
73
|
await ensureQuonfigDir(options);
|
|
68
74
|
const targetFile = getTokenFile(options);
|
|
69
75
|
const tmpFile = `${targetFile}.tmp`;
|
|
70
|
-
|
|
76
|
+
const stamped = { ...store, version: TOKEN_STORE_VERSION };
|
|
77
|
+
await fs.promises.writeFile(tmpFile, JSON.stringify(stamped, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
71
78
|
await fs.promises.rename(tmpFile, targetFile);
|
|
72
79
|
await verifyWritten(targetFile, 'tokens');
|
|
73
80
|
// Round-trip parse to confirm the file on disk is the JSON we just wrote.
|
|
@@ -91,7 +98,12 @@ export const loadTokens = async (options) => {
|
|
|
91
98
|
}
|
|
92
99
|
const parsed = JSON.parse(data);
|
|
93
100
|
if (!isTokenStore(parsed)) {
|
|
94
|
-
throw new Error('Token store
|
|
101
|
+
throw new Error('Token store malformed. Run `qfg login` to re-authenticate.');
|
|
102
|
+
}
|
|
103
|
+
const fileVersion = parsed.version ?? TOKEN_STORE_VERSION;
|
|
104
|
+
if (fileVersion > TOKEN_STORE_VERSION) {
|
|
105
|
+
throw new Error(`Token store version ${fileVersion} is from a newer CLI than this one supports (max ${TOKEN_STORE_VERSION}). ` +
|
|
106
|
+
'Upgrade with `npm i -g @quonfig/cli@latest`.');
|
|
95
107
|
}
|
|
96
108
|
return parsed;
|
|
97
109
|
};
|
|
@@ -119,9 +131,6 @@ export const saveAuthConfig = async (config, options) => {
|
|
|
119
131
|
if (profileData.organizationSlug) {
|
|
120
132
|
configContent += `organization_slug = ${profileData.organizationSlug}\n`;
|
|
121
133
|
}
|
|
122
|
-
if (profileData.workosOrgId) {
|
|
123
|
-
configContent += `workos_org_id = ${profileData.workosOrgId}\n`;
|
|
124
|
-
}
|
|
125
134
|
configContent += '\n';
|
|
126
135
|
}
|
|
127
136
|
const configTarget = getConfigFile(options);
|
|
@@ -152,7 +161,6 @@ export const loadAuthConfig = async (options) => {
|
|
|
152
161
|
const slugMatch = block.match(/workspace_slug\s*=\s*(\S+)/);
|
|
153
162
|
const orgMatch = block.match(/organization_name\s*=\s*(.+)/);
|
|
154
163
|
const orgSlugMatch = block.match(/organization_slug\s*=\s*(\S+)/);
|
|
155
|
-
const workosOrgIdMatch = block.match(/workos_org_id\s*=\s*(\S+)/);
|
|
156
164
|
if (!workspaceMatch)
|
|
157
165
|
continue;
|
|
158
166
|
config.profiles[profileName] = {
|
|
@@ -161,7 +169,6 @@ export const loadAuthConfig = async (options) => {
|
|
|
161
169
|
workspaceSlug: slugMatch?.[1]?.trim(),
|
|
162
170
|
organizationName: orgMatch?.[1]?.trim(),
|
|
163
171
|
organizationSlug: orgSlugMatch?.[1]?.trim(),
|
|
164
|
-
workosOrgId: workosOrgIdMatch?.[1]?.trim(),
|
|
165
172
|
};
|
|
166
173
|
}
|
|
167
174
|
return Object.keys(config.profiles).length > 0 ? config : null;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "0.0.
|
|
1
|
+
declare const _default: "0.0.30";
|
|
2
2
|
export default _default;
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// AUTO-GENERATED from package.json by scripts/generate-version.mjs — do not edit.
|
|
2
|
-
export default '0.0.
|
|
2
|
+
export default '0.0.30';
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"packageManager": "yarn@4.11.0+sha512.4e54aeace9141df2f0177c266b05ec50dc044638157dae128c471ba65994ac802122d7ab35bcd9e81641228b7dcf24867d28e750e0bcae8a05277d600008ad54",
|
|
3
3
|
"name": "@quonfig/cli",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.30",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/quonfig/cli/issues"
|
|
7
7
|
},
|