@formigio/fazemos-cli 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +63 -2
- package/dist/api.js +107 -5
- package/dist/api.js.map +1 -1
- package/dist/config.d.ts +90 -0
- package/dist/config.js +90 -0
- package/dist/config.js.map +1 -1
- package/dist/connectionErrorCopy.d.ts +46 -0
- package/dist/connectionErrorCopy.js +102 -0
- package/dist/connectionErrorCopy.js.map +1 -0
- package/dist/index.js +851 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { type AuthMeResponse } from './config.js';
|
|
1
2
|
/**
|
|
2
3
|
* Error thrown when an API request returns a non-2xx response. Carries the
|
|
3
4
|
* `code` field from the API's error envelope (when present) so command
|
|
4
|
-
* handlers can branch on specific failure modes (e.g., EMAIL_SEND_FAILED
|
|
5
|
+
* handlers can branch on specific failure modes (e.g., EMAIL_SEND_FAILED,
|
|
6
|
+
* F15's MISSING_PROJECT_CONTEXT / PROJECT_NOT_FOUND / PROJECT_MISMATCH).
|
|
5
7
|
* Also exposes the parsed body so callers can read extra fields like
|
|
6
8
|
* `inviteEmail`/`sessionEmail` on INVITE_EMAIL_MISMATCH.
|
|
7
9
|
*/
|
|
@@ -11,4 +13,63 @@ export declare class ApiError extends Error {
|
|
|
11
13
|
body?: unknown;
|
|
12
14
|
constructor(message: string, status: number, code?: string, body?: unknown);
|
|
13
15
|
}
|
|
14
|
-
|
|
16
|
+
/**
|
|
17
|
+
* F15 — options for `api()` that controls project context threading.
|
|
18
|
+
*
|
|
19
|
+
* - `projectSlug` — override active project for this call. The CLI resolves
|
|
20
|
+
* slug → id from the cached /auth/me response, falling back to a single
|
|
21
|
+
* refresh + retry on miss (per §8.3 slug-miss cache refresh behavior).
|
|
22
|
+
* - `allProjects` — send `?view=all` query param on GET list endpoints. Set
|
|
23
|
+
* by `--all-projects` on any scoped list command (§8.2 of the tech spec).
|
|
24
|
+
* - `noProjectHeader` — for endpoints that are not project-scoped (e.g.,
|
|
25
|
+
* /auth/me itself, /api/organizations/mine). Skip header emission so the
|
|
26
|
+
* API doesn't misinterpret a stale projectId.
|
|
27
|
+
*/
|
|
28
|
+
export interface ApiOptions {
|
|
29
|
+
projectSlug?: string;
|
|
30
|
+
allProjects?: boolean;
|
|
31
|
+
noProjectHeader?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the project id to send as X-Fazemos-Project-Id on a scoped call.
|
|
35
|
+
*
|
|
36
|
+
* Resolution order:
|
|
37
|
+
* 1. If a slug override is provided (--project <slug>), look it up in the
|
|
38
|
+
* cached /auth/me response. On a cache miss, refresh once (covers the
|
|
39
|
+
* "just created on another host" case) and retry.
|
|
40
|
+
* 2. Otherwise, return the active project id from config.
|
|
41
|
+
*
|
|
42
|
+
* On a slug that refuses to resolve after refresh → throws ApiError with
|
|
43
|
+
* code UNKNOWN_PROJECT. The outer command handler prints the standard
|
|
44
|
+
* "unknown project: <slug>" and exits 1.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveProjectIdBySlug(slugOverride?: string): Promise<string | null>;
|
|
47
|
+
/**
|
|
48
|
+
* Fetch /auth/me fresh from the API and cache the response. Used on slug
|
|
49
|
+
* miss and by any command that has just mutated orgs/projects (create,
|
|
50
|
+
* archive) and needs subsequent lookups to see the new data.
|
|
51
|
+
*/
|
|
52
|
+
export declare function refreshAuthMeCache(): Promise<AuthMeResponse | null>;
|
|
53
|
+
/**
|
|
54
|
+
* Core API helper. Always authenticates, always sends X-Org-Id when one is
|
|
55
|
+
* active, and sends X-Fazemos-Project-Id when a project is active (or one
|
|
56
|
+
* has been resolved via `projectSlug` override).
|
|
57
|
+
*
|
|
58
|
+
* Error handling (F15): if the API returns `code: 'MISSING_PROJECT_CONTEXT'`
|
|
59
|
+
* we re-shape the thrown ApiError to the uniform KD9 shape — `message` is
|
|
60
|
+
* the literal string `requirement missing: project`, `code` is preserved.
|
|
61
|
+
* This keeps the CLI's "missing required input" surface uniform and lets
|
|
62
|
+
* outer handlers print the standard hint block without substring-matching
|
|
63
|
+
* API message text.
|
|
64
|
+
*
|
|
65
|
+
* Other codes (PROJECT_NOT_FOUND, PROJECT_MISMATCH, VALIDATION_ERROR) pass
|
|
66
|
+
* through verbatim so the caller sees the API's precise message. This is
|
|
67
|
+
* the "thin validator" of KD9: translate one specific code, pass the rest.
|
|
68
|
+
*/
|
|
69
|
+
export declare function api(method: string, path: string, body?: unknown, opts?: ApiOptions): Promise<unknown>;
|
|
70
|
+
/**
|
|
71
|
+
* Invalidate the /auth/me cache after any mutation that changes the set of
|
|
72
|
+
* projects (create/archive/unarchive) or orgs. Called from command
|
|
73
|
+
* handlers to keep the next slug-resolution honest.
|
|
74
|
+
*/
|
|
75
|
+
export declare function invalidateAuthMeCache(): void;
|
package/dist/api.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { getEnv, getToken, getActiveOrgId } from './config.js';
|
|
1
|
+
import { getEnv, getToken, getActiveOrgId, getActiveProjectId, findProjectBySlug, setAuthMeCache, clearAuthMeCache, } from './config.js';
|
|
2
2
|
import { refreshSession } from './auth.js';
|
|
3
3
|
/**
|
|
4
4
|
* Error thrown when an API request returns a non-2xx response. Carries the
|
|
5
5
|
* `code` field from the API's error envelope (when present) so command
|
|
6
|
-
* handlers can branch on specific failure modes (e.g., EMAIL_SEND_FAILED
|
|
6
|
+
* handlers can branch on specific failure modes (e.g., EMAIL_SEND_FAILED,
|
|
7
|
+
* F15's MISSING_PROJECT_CONTEXT / PROJECT_NOT_FOUND / PROJECT_MISMATCH).
|
|
7
8
|
* Also exposes the parsed body so callers can read extra fields like
|
|
8
9
|
* `inviteEmail`/`sessionEmail` on INVITE_EMAIL_MISMATCH.
|
|
9
10
|
*/
|
|
@@ -19,7 +20,76 @@ export class ApiError extends Error {
|
|
|
19
20
|
this.body = body;
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the project id to send as X-Fazemos-Project-Id on a scoped call.
|
|
25
|
+
*
|
|
26
|
+
* Resolution order:
|
|
27
|
+
* 1. If a slug override is provided (--project <slug>), look it up in the
|
|
28
|
+
* cached /auth/me response. On a cache miss, refresh once (covers the
|
|
29
|
+
* "just created on another host" case) and retry.
|
|
30
|
+
* 2. Otherwise, return the active project id from config.
|
|
31
|
+
*
|
|
32
|
+
* On a slug that refuses to resolve after refresh → throws ApiError with
|
|
33
|
+
* code UNKNOWN_PROJECT. The outer command handler prints the standard
|
|
34
|
+
* "unknown project: <slug>" and exits 1.
|
|
35
|
+
*/
|
|
36
|
+
export async function resolveProjectIdBySlug(slugOverride) {
|
|
37
|
+
const orgId = getActiveOrgId();
|
|
38
|
+
if (!orgId)
|
|
39
|
+
return null;
|
|
40
|
+
if (slugOverride) {
|
|
41
|
+
let project = findProjectBySlug(orgId, slugOverride);
|
|
42
|
+
if (!project) {
|
|
43
|
+
// Force-refresh the /auth/me cache. A slug miss is cheap to verify
|
|
44
|
+
// and catches the common "created via another host or via the web
|
|
45
|
+
// UI and not yet cached here" path.
|
|
46
|
+
await refreshAuthMeCache();
|
|
47
|
+
project = findProjectBySlug(orgId, slugOverride);
|
|
48
|
+
}
|
|
49
|
+
if (!project) {
|
|
50
|
+
throw new ApiError(`unknown project: ${slugOverride}`, 400, 'UNKNOWN_PROJECT');
|
|
51
|
+
}
|
|
52
|
+
return project.id;
|
|
53
|
+
}
|
|
54
|
+
return getActiveProjectId();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Fetch /auth/me fresh from the API and cache the response. Used on slug
|
|
58
|
+
* miss and by any command that has just mutated orgs/projects (create,
|
|
59
|
+
* archive) and needs subsequent lookups to see the new data.
|
|
60
|
+
*/
|
|
61
|
+
export async function refreshAuthMeCache() {
|
|
62
|
+
try {
|
|
63
|
+
const me = (await api('GET', '/auth/me', undefined, { noProjectHeader: true }));
|
|
64
|
+
if (me && typeof me === 'object' && 'orgs' in me) {
|
|
65
|
+
setAuthMeCache(me);
|
|
66
|
+
return me;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Swallow — caller will surface any follow-up error naturally. We
|
|
71
|
+
// don't want a /auth/me refresh failure to mask the user's real
|
|
72
|
+
// command error.
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Core API helper. Always authenticates, always sends X-Org-Id when one is
|
|
78
|
+
* active, and sends X-Fazemos-Project-Id when a project is active (or one
|
|
79
|
+
* has been resolved via `projectSlug` override).
|
|
80
|
+
*
|
|
81
|
+
* Error handling (F15): if the API returns `code: 'MISSING_PROJECT_CONTEXT'`
|
|
82
|
+
* we re-shape the thrown ApiError to the uniform KD9 shape — `message` is
|
|
83
|
+
* the literal string `requirement missing: project`, `code` is preserved.
|
|
84
|
+
* This keeps the CLI's "missing required input" surface uniform and lets
|
|
85
|
+
* outer handlers print the standard hint block without substring-matching
|
|
86
|
+
* API message text.
|
|
87
|
+
*
|
|
88
|
+
* Other codes (PROJECT_NOT_FOUND, PROJECT_MISMATCH, VALIDATION_ERROR) pass
|
|
89
|
+
* through verbatim so the caller sees the API's precise message. This is
|
|
90
|
+
* the "thin validator" of KD9: translate one specific code, pass the rest.
|
|
91
|
+
*/
|
|
92
|
+
export async function api(method, path, body, opts = {}) {
|
|
23
93
|
const env = getEnv();
|
|
24
94
|
let token = getToken();
|
|
25
95
|
const orgId = getActiveOrgId();
|
|
@@ -39,7 +109,25 @@ export async function api(method, path, body) {
|
|
|
39
109
|
if (orgId) {
|
|
40
110
|
headers['X-Org-Id'] = orgId;
|
|
41
111
|
}
|
|
42
|
-
|
|
112
|
+
// F15 — project context header. Skipped for non-project-scoped endpoints
|
|
113
|
+
// (auth/me, /api/organizations/mine, org CRUD). The API ignores the header
|
|
114
|
+
// on endpoints that don't require it, but omitting it keeps intent clear
|
|
115
|
+
// in request logs.
|
|
116
|
+
if (!opts.noProjectHeader) {
|
|
117
|
+
const projectId = await resolveProjectIdBySlug(opts.projectSlug);
|
|
118
|
+
if (projectId) {
|
|
119
|
+
headers['X-Fazemos-Project-Id'] = projectId;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Build URL with optional ?view=all append. We don't try to be clever
|
|
123
|
+
// about merging with existing query strings here — callers that use
|
|
124
|
+
// --all-projects know they're on GET list endpoints with no other query.
|
|
125
|
+
// If a future endpoint wants both --all-projects and a manual query,
|
|
126
|
+
// extend this helper rather than working around it.
|
|
127
|
+
let url = `${env.apiUrl}${path}`;
|
|
128
|
+
if (opts.allProjects) {
|
|
129
|
+
url += (url.includes('?') ? '&' : '?') + 'view=all';
|
|
130
|
+
}
|
|
43
131
|
const options = {
|
|
44
132
|
method,
|
|
45
133
|
headers,
|
|
@@ -60,9 +148,23 @@ export async function api(method, path, body) {
|
|
|
60
148
|
const obj = typeof data === 'object' && data !== null
|
|
61
149
|
? data
|
|
62
150
|
: null;
|
|
151
|
+
const code = obj?.code;
|
|
63
152
|
const msg = obj?.error ?? `HTTP ${response.status}`;
|
|
64
|
-
|
|
153
|
+
// KD9 — translate MISSING_PROJECT_CONTEXT into the uniform CLI
|
|
154
|
+
// missing-input shape. Other F15 codes pass through verbatim.
|
|
155
|
+
if (code === 'MISSING_PROJECT_CONTEXT') {
|
|
156
|
+
throw new ApiError('requirement missing: project', response.status, code, data);
|
|
157
|
+
}
|
|
158
|
+
throw new ApiError(msg, response.status, code, data);
|
|
65
159
|
}
|
|
66
160
|
return data;
|
|
67
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Invalidate the /auth/me cache after any mutation that changes the set of
|
|
164
|
+
* projects (create/archive/unarchive) or orgs. Called from command
|
|
165
|
+
* handlers to keep the next slug-resolution honest.
|
|
166
|
+
*/
|
|
167
|
+
export function invalidateAuthMeCache() {
|
|
168
|
+
clearAuthMeCache();
|
|
169
|
+
}
|
|
68
170
|
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,47 @@
|
|
|
1
1
|
import Conf from 'conf';
|
|
2
|
+
/**
|
|
3
|
+
* Shape of a project as cached in /auth/me response.
|
|
4
|
+
*/
|
|
5
|
+
export interface CachedProject {
|
|
6
|
+
id: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
name: string;
|
|
9
|
+
colorIndex: number;
|
|
10
|
+
status: 'active' | 'archived';
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Shape of an org membership as cached in /auth/me response.
|
|
14
|
+
*/
|
|
15
|
+
export interface CachedOrg {
|
|
16
|
+
id: string;
|
|
17
|
+
slug: string;
|
|
18
|
+
name: string;
|
|
19
|
+
role: 'owner' | 'admin' | 'member';
|
|
20
|
+
memberId: string;
|
|
21
|
+
projects: CachedProject[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Cached /auth/me response. Used to resolve project slug → id without a
|
|
25
|
+
* network round-trip on every scoped command (§8.4 of the F15 tech spec).
|
|
26
|
+
*/
|
|
27
|
+
export interface AuthMeResponse {
|
|
28
|
+
user: {
|
|
29
|
+
id: string;
|
|
30
|
+
email: string;
|
|
31
|
+
};
|
|
32
|
+
member: {
|
|
33
|
+
id: string;
|
|
34
|
+
orgId: string;
|
|
35
|
+
displayName: string;
|
|
36
|
+
email: string | null;
|
|
37
|
+
role: string;
|
|
38
|
+
memberType: string;
|
|
39
|
+
status: string;
|
|
40
|
+
};
|
|
41
|
+
orgs: CachedOrg[];
|
|
42
|
+
activeOrgId: string;
|
|
43
|
+
activeProjectId: string | null;
|
|
44
|
+
}
|
|
2
45
|
interface FazemosConfig {
|
|
3
46
|
environments: Record<string, {
|
|
4
47
|
apiUrl: string;
|
|
@@ -8,13 +51,31 @@ interface FazemosConfig {
|
|
|
8
51
|
}>;
|
|
9
52
|
activeEnv: string;
|
|
10
53
|
activeOrgId: string;
|
|
54
|
+
/**
|
|
55
|
+
* F15 — Active project per org. When the user switches orgs, their last
|
|
56
|
+
* active project for the destination org is restored from this map (per
|
|
57
|
+
* UX §1.8). Keyed by orgId so a single machine can track multiple orgs'
|
|
58
|
+
* project selections independently.
|
|
59
|
+
*/
|
|
60
|
+
activeProjectByOrg: Record<string, string>;
|
|
11
61
|
auth: Record<string, {
|
|
12
62
|
email: string;
|
|
13
63
|
idToken: string;
|
|
14
64
|
refreshToken: string;
|
|
15
65
|
expiresAt: number;
|
|
16
66
|
}>;
|
|
67
|
+
/**
|
|
68
|
+
* F15 — Cached /auth/me response. 5-minute TTL. Invalidated explicitly
|
|
69
|
+
* on any mutating projects/orgs command (create, archive) and refreshed
|
|
70
|
+
* lazily on first use by resolveProjectId() when a slug miss happens.
|
|
71
|
+
*/
|
|
72
|
+
authMeCache?: {
|
|
73
|
+
activeEnv: string;
|
|
74
|
+
fetchedAt: number;
|
|
75
|
+
data: AuthMeResponse;
|
|
76
|
+
};
|
|
17
77
|
}
|
|
78
|
+
export declare const AUTH_ME_CACHE_TTL_MS: number;
|
|
18
79
|
export declare const config: Conf<FazemosConfig>;
|
|
19
80
|
export declare function addEnvironment(name: string, env: {
|
|
20
81
|
apiUrl: string;
|
|
@@ -34,4 +95,33 @@ export declare function getToken(): string | null;
|
|
|
34
95
|
export declare function getActiveOrgId(): string | null;
|
|
35
96
|
export declare function setActiveOrgId(orgId: string): void;
|
|
36
97
|
export declare function setAuth(env: string, email: string, idToken: string, refreshToken: string, expiresInSeconds: number): void;
|
|
98
|
+
/**
|
|
99
|
+
* Returns the active project id for the currently-active org, or null if
|
|
100
|
+
* none is set. Follows KD9 — no auto-pick, no interactive prompt. The
|
|
101
|
+
* scoped-command handlers translate a null return into the uniform
|
|
102
|
+
* "requirement missing: project" error when the API says so.
|
|
103
|
+
*/
|
|
104
|
+
export declare function getActiveProjectId(): string | null;
|
|
105
|
+
/**
|
|
106
|
+
* Sets the active project for the given org. Stored as a per-org map so
|
|
107
|
+
* switching orgs restores the last project you used in each.
|
|
108
|
+
*/
|
|
109
|
+
export declare function setActiveProjectId(orgId: string, projectId: string): void;
|
|
110
|
+
/**
|
|
111
|
+
* Clears the active project for the given org (e.g., when the project
|
|
112
|
+
* is archived and was the active one).
|
|
113
|
+
*/
|
|
114
|
+
export declare function clearActiveProjectId(orgId: string): void;
|
|
115
|
+
export declare function getAuthMeCache(): AuthMeResponse | null;
|
|
116
|
+
export declare function setAuthMeCache(data: AuthMeResponse): void;
|
|
117
|
+
export declare function clearAuthMeCache(): void;
|
|
118
|
+
/**
|
|
119
|
+
* Look up a project by slug in the currently cached /auth/me response.
|
|
120
|
+
* Returns null if the cache is empty, stale, or the slug is not present.
|
|
121
|
+
* Callers should refresh the cache once and retry on miss — see
|
|
122
|
+
* resolveProjectIdBySlug in api.ts.
|
|
123
|
+
*/
|
|
124
|
+
export declare function findProjectBySlug(orgId: string, slug: string): CachedProject | null;
|
|
125
|
+
export declare function findProjectById(orgId: string, projectId: string): CachedProject | null;
|
|
126
|
+
export declare function findOrgById(orgId: string): CachedOrg | null;
|
|
37
127
|
export {};
|
package/dist/config.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import Conf from 'conf';
|
|
2
|
+
export const AUTH_ME_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
2
3
|
export const config = new Conf({
|
|
3
4
|
projectName: 'fazemos-cli',
|
|
4
5
|
defaults: {
|
|
5
6
|
environments: {},
|
|
6
7
|
activeEnv: '',
|
|
7
8
|
activeOrgId: '',
|
|
9
|
+
activeProjectByOrg: {},
|
|
8
10
|
auth: {},
|
|
9
11
|
},
|
|
10
12
|
});
|
|
@@ -53,4 +55,92 @@ export function setAuth(env, email, idToken, refreshToken, expiresInSeconds) {
|
|
|
53
55
|
};
|
|
54
56
|
config.set('auth', auth);
|
|
55
57
|
}
|
|
58
|
+
// ── F15 — Project context helpers ───────────────────────────
|
|
59
|
+
/**
|
|
60
|
+
* Returns the active project id for the currently-active org, or null if
|
|
61
|
+
* none is set. Follows KD9 — no auto-pick, no interactive prompt. The
|
|
62
|
+
* scoped-command handlers translate a null return into the uniform
|
|
63
|
+
* "requirement missing: project" error when the API says so.
|
|
64
|
+
*/
|
|
65
|
+
export function getActiveProjectId() {
|
|
66
|
+
const orgId = getActiveOrgId();
|
|
67
|
+
if (!orgId)
|
|
68
|
+
return null;
|
|
69
|
+
const map = config.get('activeProjectByOrg') ?? {};
|
|
70
|
+
return map[orgId] ?? null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Sets the active project for the given org. Stored as a per-org map so
|
|
74
|
+
* switching orgs restores the last project you used in each.
|
|
75
|
+
*/
|
|
76
|
+
export function setActiveProjectId(orgId, projectId) {
|
|
77
|
+
const map = config.get('activeProjectByOrg') ?? {};
|
|
78
|
+
map[orgId] = projectId;
|
|
79
|
+
config.set('activeProjectByOrg', map);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Clears the active project for the given org (e.g., when the project
|
|
83
|
+
* is archived and was the active one).
|
|
84
|
+
*/
|
|
85
|
+
export function clearActiveProjectId(orgId) {
|
|
86
|
+
const map = config.get('activeProjectByOrg') ?? {};
|
|
87
|
+
delete map[orgId];
|
|
88
|
+
config.set('activeProjectByOrg', map);
|
|
89
|
+
}
|
|
90
|
+
// ── F15 — /auth/me cache helpers ────────────────────────────
|
|
91
|
+
export function getAuthMeCache() {
|
|
92
|
+
const cache = config.get('authMeCache');
|
|
93
|
+
if (!cache)
|
|
94
|
+
return null;
|
|
95
|
+
const activeEnv = config.get('activeEnv');
|
|
96
|
+
if (cache.activeEnv !== activeEnv)
|
|
97
|
+
return null;
|
|
98
|
+
if (Date.now() - cache.fetchedAt > AUTH_ME_CACHE_TTL_MS)
|
|
99
|
+
return null;
|
|
100
|
+
return cache.data;
|
|
101
|
+
}
|
|
102
|
+
export function setAuthMeCache(data) {
|
|
103
|
+
const activeEnv = config.get('activeEnv');
|
|
104
|
+
config.set('authMeCache', {
|
|
105
|
+
activeEnv,
|
|
106
|
+
fetchedAt: Date.now(),
|
|
107
|
+
data,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
export function clearAuthMeCache() {
|
|
111
|
+
// conf's .delete strips the key entirely; using set(undefined) keeps the
|
|
112
|
+
// field present but null which confuses narrowing callers. Delete is the
|
|
113
|
+
// cleaner surface here.
|
|
114
|
+
config.delete('authMeCache');
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Look up a project by slug in the currently cached /auth/me response.
|
|
118
|
+
* Returns null if the cache is empty, stale, or the slug is not present.
|
|
119
|
+
* Callers should refresh the cache once and retry on miss — see
|
|
120
|
+
* resolveProjectIdBySlug in api.ts.
|
|
121
|
+
*/
|
|
122
|
+
export function findProjectBySlug(orgId, slug) {
|
|
123
|
+
const me = getAuthMeCache();
|
|
124
|
+
if (!me)
|
|
125
|
+
return null;
|
|
126
|
+
const org = me.orgs.find(o => o.id === orgId);
|
|
127
|
+
if (!org)
|
|
128
|
+
return null;
|
|
129
|
+
return org.projects.find(p => p.slug === slug) ?? null;
|
|
130
|
+
}
|
|
131
|
+
export function findProjectById(orgId, projectId) {
|
|
132
|
+
const me = getAuthMeCache();
|
|
133
|
+
if (!me)
|
|
134
|
+
return null;
|
|
135
|
+
const org = me.orgs.find(o => o.id === orgId);
|
|
136
|
+
if (!org)
|
|
137
|
+
return null;
|
|
138
|
+
return org.projects.find(p => p.id === projectId) ?? null;
|
|
139
|
+
}
|
|
140
|
+
export function findOrgById(orgId) {
|
|
141
|
+
const me = getAuthMeCache();
|
|
142
|
+
if (!me)
|
|
143
|
+
return null;
|
|
144
|
+
return me.orgs.find(o => o.id === orgId) ?? null;
|
|
145
|
+
}
|
|
56
146
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F16 — Role-aware copy for the `PROJECT_CONNECTION_UNAVAILABLE`
|
|
3
|
+
* structured error (tech spec §5.14).
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the web's `renderProjectConnectionUnavailableCopy` so admin
|
|
6
|
+
* and member experiences stay consistent across surfaces. CLI variant
|
|
7
|
+
* adds the settings URL as a trailing line for admins; members see
|
|
8
|
+
* "Ask your admin" without a URL.
|
|
9
|
+
*
|
|
10
|
+
* The `getEnv()` helper supplies the web origin so we can construct
|
|
11
|
+
* fully-qualified URLs the user can click in the terminal.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Structured payload returned by pipeline-run endpoints when the
|
|
15
|
+
* Project's Connection is missing/revoked/suspended/uninstalled.
|
|
16
|
+
* Matches the contract in tech spec §5.14.
|
|
17
|
+
*/
|
|
18
|
+
export interface ProjectConnectionUnavailableErrorBody {
|
|
19
|
+
error: 'project_connection_unavailable';
|
|
20
|
+
code: 'PROJECT_CONNECTION_UNAVAILABLE';
|
|
21
|
+
reason: 'missing' | 'revoked' | 'suspended' | 'uninstalled';
|
|
22
|
+
orgId: string;
|
|
23
|
+
orgSlug: string;
|
|
24
|
+
projectId: string;
|
|
25
|
+
projectSlug: string;
|
|
26
|
+
connectionId: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface ConnectionErrorLines {
|
|
29
|
+
/** Lead headline. */
|
|
30
|
+
title: string;
|
|
31
|
+
/** Body explanation. */
|
|
32
|
+
body: string;
|
|
33
|
+
/** Optional fully-qualified URL line, only set for admin role. */
|
|
34
|
+
ctaUrl?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Type guard — narrow `ApiError.body` (or any unknown payload) to the
|
|
38
|
+
* structured shape so callers can render copy.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isProjectConnectionUnavailable(body: unknown): body is ProjectConnectionUnavailableErrorBody;
|
|
41
|
+
/**
|
|
42
|
+
* Produce the role-aware lines for terminal rendering. Caller decides
|
|
43
|
+
* how to color / format. The shape is plain so a JSON test fixture
|
|
44
|
+
* stays readable.
|
|
45
|
+
*/
|
|
46
|
+
export declare function renderProjectConnectionUnavailableCopy(error: Pick<ProjectConnectionUnavailableErrorBody, 'reason' | 'orgSlug' | 'connectionId'>, role: 'owner' | 'admin' | 'member' | string): ConnectionErrorLines;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F16 — Role-aware copy for the `PROJECT_CONNECTION_UNAVAILABLE`
|
|
3
|
+
* structured error (tech spec §5.14).
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the web's `renderProjectConnectionUnavailableCopy` so admin
|
|
6
|
+
* and member experiences stay consistent across surfaces. CLI variant
|
|
7
|
+
* adds the settings URL as a trailing line for admins; members see
|
|
8
|
+
* "Ask your admin" without a URL.
|
|
9
|
+
*
|
|
10
|
+
* The `getEnv()` helper supplies the web origin so we can construct
|
|
11
|
+
* fully-qualified URLs the user can click in the terminal.
|
|
12
|
+
*/
|
|
13
|
+
import { getEnv } from './config.js';
|
|
14
|
+
/**
|
|
15
|
+
* Type guard — narrow `ApiError.body` (or any unknown payload) to the
|
|
16
|
+
* structured shape so callers can render copy.
|
|
17
|
+
*/
|
|
18
|
+
export function isProjectConnectionUnavailable(body) {
|
|
19
|
+
if (!body || typeof body !== 'object')
|
|
20
|
+
return false;
|
|
21
|
+
const b = body;
|
|
22
|
+
return (b.code === 'PROJECT_CONNECTION_UNAVAILABLE' &&
|
|
23
|
+
typeof b.reason === 'string' &&
|
|
24
|
+
['missing', 'revoked', 'suspended', 'uninstalled'].includes(b.reason) &&
|
|
25
|
+
typeof b.orgSlug === 'string' &&
|
|
26
|
+
typeof b.projectSlug === 'string');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the web origin (where the settings URLs live). The CLI's
|
|
30
|
+
* `env` config holds an `apiUrl` like `https://api.fazemos.../api`.
|
|
31
|
+
* The web origin is conventionally the same host minus `api`. For
|
|
32
|
+
* local dev (`http://localhost:8080`) we point at the dev web URL.
|
|
33
|
+
*
|
|
34
|
+
* Conservative behavior: if we can't derive cleanly, render the path
|
|
35
|
+
* without an origin (the user can still see what to do).
|
|
36
|
+
*/
|
|
37
|
+
function deriveWebOrigin() {
|
|
38
|
+
try {
|
|
39
|
+
const env = getEnv();
|
|
40
|
+
// Prod: api.fazemos.journeyjuntos.com → fazemos.journeyjuntos.com
|
|
41
|
+
// Dev: api.fazemos-dev.journeyjuntos.com → fazemos-dev.journeyjuntos.com
|
|
42
|
+
// Local: http://localhost:8080 → http://localhost:5173
|
|
43
|
+
const apiUrl = env.apiUrl;
|
|
44
|
+
if (!apiUrl)
|
|
45
|
+
return null;
|
|
46
|
+
// Local — vite dev port for the web app.
|
|
47
|
+
if (apiUrl.includes('localhost') || apiUrl.includes('127.0.0.1')) {
|
|
48
|
+
return 'http://localhost:5173';
|
|
49
|
+
}
|
|
50
|
+
// Strip a leading `api.` if present in the host.
|
|
51
|
+
const url = new URL(apiUrl);
|
|
52
|
+
const host = url.host.replace(/^api\./, '');
|
|
53
|
+
return `${url.protocol}//${host}`;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Produce the role-aware lines for terminal rendering. Caller decides
|
|
61
|
+
* how to color / format. The shape is plain so a JSON test fixture
|
|
62
|
+
* stays readable.
|
|
63
|
+
*/
|
|
64
|
+
export function renderProjectConnectionUnavailableCopy(error, role) {
|
|
65
|
+
const isAdmin = role === 'owner' || role === 'admin';
|
|
66
|
+
const origin = deriveWebOrigin();
|
|
67
|
+
const settingsPath = error.connectionId
|
|
68
|
+
? `/org/${error.orgSlug}/settings/connections/${error.connectionId}`
|
|
69
|
+
: `/org/${error.orgSlug}/settings/connections`;
|
|
70
|
+
const ctaUrl = origin ? `${origin}${settingsPath}` : settingsPath;
|
|
71
|
+
if (error.reason === 'missing') {
|
|
72
|
+
return isAdmin
|
|
73
|
+
? {
|
|
74
|
+
title: 'Connect GitHub to run this pipeline.',
|
|
75
|
+
body: 'This project needs a GitHub connection to clone its repos.',
|
|
76
|
+
ctaUrl,
|
|
77
|
+
}
|
|
78
|
+
: {
|
|
79
|
+
title: 'This project needs a GitHub connection.',
|
|
80
|
+
body: 'Ask your admin to add one in Project Settings.',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (error.reason === 'revoked' ||
|
|
84
|
+
error.reason === 'uninstalled' ||
|
|
85
|
+
error.reason === 'suspended') {
|
|
86
|
+
return isAdmin
|
|
87
|
+
? {
|
|
88
|
+
title: `GitHub connection ${error.reason}.`,
|
|
89
|
+
body: 'New pipeline steps that need GitHub will fail until the project is bound to a different connection.',
|
|
90
|
+
ctaUrl,
|
|
91
|
+
}
|
|
92
|
+
: {
|
|
93
|
+
title: 'This project needs a GitHub connection.',
|
|
94
|
+
body: 'Ask your admin — the existing connection is not usable.',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
title: 'GitHub connection is unavailable.',
|
|
99
|
+
body: '',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=connectionErrorCopy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectionErrorCopy.js","sourceRoot":"","sources":["../src/connectionErrorCopy.ts"],"names":[],"mappings":"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"}
|