dxcomplete 0.2.1 → 0.2.2

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 (47) hide show
  1. package/.env.example +0 -7
  2. package/README.md +17 -45
  3. package/dist/cli.js +0 -22
  4. package/dist/validate.js +10 -26
  5. package/docs/model.md +3 -3
  6. package/docs/taxonomy.md +1 -1
  7. package/package.json +23 -23
  8. package/templates/process/README.md +1 -1
  9. package/dist/http/service.d.ts +0 -7
  10. package/dist/http/service.js +0 -725
  11. package/dist/mcp/docs.d.ts +0 -114
  12. package/dist/mcp/docs.js +0 -626
  13. package/dist/mcp/server.d.ts +0 -20
  14. package/dist/mcp/server.js +0 -3059
  15. package/dist/runtime/auth.d.ts +0 -162
  16. package/dist/runtime/auth.js +0 -394
  17. package/dist/runtime/check.d.ts +0 -7
  18. package/dist/runtime/check.js +0 -16
  19. package/dist/runtime/config.d.ts +0 -17
  20. package/dist/runtime/config.js +0 -93
  21. package/dist/runtime/mongo.d.ts +0 -9
  22. package/dist/runtime/mongo.js +0 -56
  23. package/dist/runtime/records.d.ts +0 -427
  24. package/dist/runtime/records.js +0 -2092
  25. package/scripts/check-env-surface.mjs +0 -136
  26. package/scripts/check-public-copy.mjs +0 -263
  27. package/scripts/check-service-boundary.mjs +0 -63
  28. package/scripts/runtime-work-order.mjs +0 -506
  29. package/scripts/smoke-mcp-http.mjs +0 -4026
  30. package/src/cli.ts +0 -268
  31. package/src/http/server.ts +0 -314
  32. package/src/http/service.ts +0 -934
  33. package/src/init.ts +0 -262
  34. package/src/install-manifest.ts +0 -144
  35. package/src/mcp/docs.ts +0 -777
  36. package/src/mcp/server.ts +0 -4580
  37. package/src/package-root.ts +0 -31
  38. package/src/runtime/actor.ts +0 -61
  39. package/src/runtime/auth.ts +0 -673
  40. package/src/runtime/check.ts +0 -18
  41. package/src/runtime/config.ts +0 -128
  42. package/src/runtime/mongo.ts +0 -89
  43. package/src/runtime/records.ts +0 -3205
  44. package/src/runtime/workspace.ts +0 -155
  45. package/src/upgrade.ts +0 -356
  46. package/src/validate.ts +0 -141
  47. package/src/version.ts +0 -16
@@ -1,162 +0,0 @@
1
- import type { Db } from "mongodb";
2
- import { type ActorContext } from "./actor.js";
3
- import type { RuntimeConfig } from "./config.js";
4
- import type { WorkspaceConfig } from "./workspace.js";
5
- export declare const WORKSPACE_MEMBERSHIPS_COLLECTION = "workspace_memberships";
6
- export declare const OAUTH_CLIENTS_COLLECTION = "oauth_clients";
7
- export declare const OAUTH_AUTH_REQUESTS_COLLECTION = "oauth_authorization_requests";
8
- export declare const OAUTH_CODES_COLLECTION = "oauth_authorization_codes";
9
- export declare const OAUTH_TOKENS_COLLECTION = "oauth_tokens";
10
- export declare const WORKSPACE_SERVICE_CLIENTS_COLLECTION = "workspace_service_clients";
11
- export type WorkspaceRole = "owner" | "engineer" | "tester" | "operator" | "support_agent" | "end_user";
12
- export type WorkspaceMembership = {
13
- _id: string;
14
- workspaceId: string;
15
- email: string;
16
- roles: WorkspaceRole[];
17
- role?: "owner" | "member";
18
- provider?: "google";
19
- providerSubject?: string;
20
- createdAt: string;
21
- updatedAt: string;
22
- updatedBy: string;
23
- };
24
- export type WorkspaceServiceClientRecord = {
25
- _id: string;
26
- workspaceId: string;
27
- clientId: string;
28
- secretHash: string;
29
- name: string;
30
- createdAt: string;
31
- createdBy: string;
32
- updatedAt: string;
33
- updatedBy: string;
34
- revokedAt?: string;
35
- revokedBy?: string;
36
- };
37
- export type OAuthClientRecord = {
38
- _id: string;
39
- clientId: string;
40
- clientName?: string;
41
- redirectUris: string[];
42
- grantTypes: string[];
43
- responseTypes: string[];
44
- createdAt: string;
45
- updatedAt: string;
46
- };
47
- export type OAuthAuthorizationRequest = {
48
- _id: string;
49
- clientId: string;
50
- redirectUri: string;
51
- codeChallenge: string;
52
- codeChallengeMethod: "S256";
53
- state?: string;
54
- scope: string;
55
- resource: string;
56
- workspaceId: string;
57
- createdAt: string;
58
- expiresAt: Date;
59
- };
60
- export type OAuthAuthorizationCode = OAuthAuthorizationRequest & {
61
- actor: ActorContext;
62
- };
63
- export type OAuthTokenRecord = {
64
- _id: string;
65
- kind: "access" | "refresh";
66
- clientId: string;
67
- workspaceId: string;
68
- actor: ActorContext;
69
- scope: string;
70
- resource: string;
71
- createdAt: string;
72
- expiresAt: Date;
73
- revokedAt?: string;
74
- };
75
- export type GoogleProfile = {
76
- email: string;
77
- subject: string;
78
- displayName?: string;
79
- };
80
- export declare function ensureWorkspaceBootstrap(db: Db, config: WorkspaceConfig, actorId: string): Promise<void>;
81
- export declare function upsertWorkspaceMembership(db: Db, input: {
82
- workspaceId: string;
83
- email: string;
84
- role?: WorkspaceRole | "member";
85
- roles?: WorkspaceRole[];
86
- actorId: string;
87
- provider?: "google";
88
- providerSubject?: string;
89
- }): Promise<WorkspaceMembership>;
90
- export declare function assertWorkspaceMembership(db: Db, workspaceId: string, actor: ActorContext): Promise<WorkspaceMembership>;
91
- export declare function createWorkspaceServiceClient(db: Db, input: {
92
- workspaceId: string;
93
- name: string;
94
- actorId: string;
95
- }): Promise<{
96
- record: WorkspaceServiceClientRecord;
97
- secret: string;
98
- }>;
99
- export declare function verifyWorkspaceServiceClient(db: Db, input: {
100
- clientId?: string;
101
- secret?: string;
102
- workspaceId?: string;
103
- }): Promise<WorkspaceServiceClientRecord>;
104
- export declare function registerOAuthClient(db: Db, input: {
105
- clientName?: string;
106
- redirectUris: string[];
107
- grantTypes?: string[];
108
- responseTypes?: string[];
109
- }): Promise<OAuthClientRecord>;
110
- export declare function getOAuthClient(db: Db, clientId: string): Promise<OAuthClientRecord | null>;
111
- export declare function createOAuthAuthorizationRequest(db: Db, input: Omit<OAuthAuthorizationRequest, "_id" | "createdAt" | "expiresAt">): Promise<OAuthAuthorizationRequest>;
112
- export declare function consumeOAuthAuthorizationRequest(db: Db, state: string): Promise<OAuthAuthorizationRequest>;
113
- export declare function createOAuthAuthorizationCode(db: Db, request: OAuthAuthorizationRequest, actor: ActorContext): Promise<string>;
114
- export declare function exchangeOAuthAuthorizationCode(db: Db, input: {
115
- code: string;
116
- clientId: string;
117
- redirectUri: string;
118
- codeVerifier: string;
119
- resource: string;
120
- }): Promise<{
121
- accessToken: string;
122
- refreshToken: string;
123
- expiresIn: number;
124
- scope: string;
125
- }>;
126
- export declare function exchangeOAuthRefreshToken(db: Db, input: {
127
- refreshToken: string;
128
- clientId: string;
129
- resource: string;
130
- }): Promise<{
131
- accessToken: string;
132
- refreshToken: string;
133
- expiresIn: number;
134
- scope: string;
135
- }>;
136
- export declare function issueMcpTokenPair(db: Db, input: {
137
- clientId: string;
138
- scope: string;
139
- resource: string;
140
- workspaceId: string;
141
- actor: ActorContext;
142
- }): Promise<{
143
- accessToken: string;
144
- refreshToken: string;
145
- expiresIn: number;
146
- scope: string;
147
- }>;
148
- export declare function verifyMcpAccessToken(db: Db, input: {
149
- token: string;
150
- workspaceId: string;
151
- resource: string;
152
- }): Promise<OAuthTokenRecord>;
153
- export declare function exchangeGoogleCodeForProfile(config: RuntimeConfig, input: {
154
- code: string;
155
- redirectUri: string;
156
- }): Promise<GoogleProfile>;
157
- export declare function googleAuthorizationUrl(config: RuntimeConfig, input: {
158
- redirectUri: string;
159
- state: string;
160
- }): string;
161
- export declare function createGoogleActor(profile: GoogleProfile): ActorContext;
162
- export declare function membershipRoles(membership: WorkspaceMembership): WorkspaceRole[];
@@ -1,394 +0,0 @@
1
- import { createHash, randomBytes, randomUUID } from "node:crypto";
2
- import { createGoogleActorContext, normalizeEmail } from "./actor.js";
3
- export const WORKSPACE_MEMBERSHIPS_COLLECTION = "workspace_memberships";
4
- export const OAUTH_CLIENTS_COLLECTION = "oauth_clients";
5
- export const OAUTH_AUTH_REQUESTS_COLLECTION = "oauth_authorization_requests";
6
- export const OAUTH_CODES_COLLECTION = "oauth_authorization_codes";
7
- export const OAUTH_TOKENS_COLLECTION = "oauth_tokens";
8
- export const WORKSPACE_SERVICE_CLIENTS_COLLECTION = "workspace_service_clients";
9
- const MCP_SCOPE = "mcp:tools";
10
- const ACCESS_TOKEN_TTL_SECONDS = 60 * 60;
11
- const REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
12
- const AUTH_REQUEST_TTL_SECONDS = 10 * 60;
13
- const SERVICE_CLIENT_SECRET_PREFIX = "dxc_service_secret_";
14
- const SERVICE_CLIENT_HASH_PREFIX = "sha256:";
15
- export async function ensureWorkspaceBootstrap(db, config, actorId) {
16
- const now = new Date().toISOString();
17
- await db.collection("workspaces").updateOne({ _id: config.workspaceId }, {
18
- $set: {
19
- recordType: "workspaces",
20
- title: config.name,
21
- fields: {
22
- name: config.name,
23
- ...(config.mode ? { mode: config.mode } : {})
24
- },
25
- updatedAt: now,
26
- updatedBy: actorId
27
- },
28
- $setOnInsert: {
29
- _id: config.workspaceId,
30
- links: [],
31
- createdAt: now,
32
- createdBy: actorId
33
- }
34
- }, { upsert: true });
35
- await Promise.all(config.bootstrapMembers.map((member) => upsertWorkspaceMembership(db, {
36
- workspaceId: config.workspaceId,
37
- email: member.email,
38
- roles: member.roles,
39
- actorId
40
- })));
41
- }
42
- export async function upsertWorkspaceMembership(db, input) {
43
- const email = normalizeEmail(input.email);
44
- const now = new Date().toISOString();
45
- const id = membershipId(input.workspaceId, email);
46
- const roles = normalizeWorkspaceRoles(input.roles ?? (input.role ? [input.role] : []));
47
- await db.collection(WORKSPACE_MEMBERSHIPS_COLLECTION).updateOne({ _id: id }, {
48
- $set: {
49
- workspaceId: input.workspaceId,
50
- email,
51
- roles,
52
- ...(input.provider ? { provider: input.provider } : {}),
53
- ...(input.providerSubject ? { providerSubject: input.providerSubject } : {}),
54
- updatedAt: now,
55
- updatedBy: input.actorId
56
- },
57
- $unset: {
58
- role: ""
59
- },
60
- $setOnInsert: {
61
- _id: id,
62
- createdAt: now
63
- }
64
- }, { upsert: true });
65
- const record = await db.collection(WORKSPACE_MEMBERSHIPS_COLLECTION).findOne({ _id: id });
66
- if (!record) {
67
- throw new Error("Workspace membership was not written.");
68
- }
69
- return record;
70
- }
71
- export async function assertWorkspaceMembership(db, workspaceId, actor) {
72
- if (!actor.email) {
73
- throw new Error("Authenticated actor email is required for workspace authorization.");
74
- }
75
- const email = normalizeEmail(actor.email);
76
- const membership = await db
77
- .collection(WORKSPACE_MEMBERSHIPS_COLLECTION)
78
- .findOne({ _id: membershipId(workspaceId, email) });
79
- if (!membership) {
80
- throw new Error(`Workspace access denied for ${email}.`);
81
- }
82
- if (membership.providerSubject && actor.providerSubject && membership.providerSubject !== actor.providerSubject) {
83
- throw new Error(`Workspace access denied for ${email}.`);
84
- }
85
- if (actor.provider === "google" && actor.providerSubject && !membership.providerSubject) {
86
- await upsertWorkspaceMembership(db, {
87
- workspaceId,
88
- email,
89
- roles: membershipRoles(membership),
90
- actorId: actor.actorId,
91
- provider: "google",
92
- providerSubject: actor.providerSubject
93
- });
94
- }
95
- if (!Array.isArray(membership.roles) || membership.roles.length === 0 || membership.role) {
96
- return upsertWorkspaceMembership(db, {
97
- workspaceId,
98
- email,
99
- roles: membershipRoles(membership),
100
- actorId: actor.actorId,
101
- ...(membership.provider ? { provider: membership.provider } : {}),
102
- ...(membership.providerSubject ? { providerSubject: membership.providerSubject } : {})
103
- });
104
- }
105
- return membership;
106
- }
107
- export async function createWorkspaceServiceClient(db, input) {
108
- const now = new Date().toISOString();
109
- const clientId = `dxc_service_client_${randomUUID()}`;
110
- const secret = `${SERVICE_CLIENT_SECRET_PREFIX}${randomBytes(32).toString("base64url")}`;
111
- const record = {
112
- _id: clientId,
113
- workspaceId: input.workspaceId,
114
- clientId,
115
- secretHash: hashServiceClientSecret(secret),
116
- name: input.name,
117
- createdAt: now,
118
- createdBy: input.actorId,
119
- updatedAt: now,
120
- updatedBy: input.actorId
121
- };
122
- await db.collection(WORKSPACE_SERVICE_CLIENTS_COLLECTION).insertOne(record);
123
- return { record, secret };
124
- }
125
- export async function verifyWorkspaceServiceClient(db, input) {
126
- if (!input.clientId || !input.secret) {
127
- throw new Error("DX Complete workspace service credentials are required.");
128
- }
129
- const record = await db
130
- .collection(WORKSPACE_SERVICE_CLIENTS_COLLECTION)
131
- .findOne({ _id: input.clientId });
132
- if (!record || record.revokedAt) {
133
- throw new Error("DX Complete workspace service client was not found or is revoked.");
134
- }
135
- if (input.workspaceId && record.workspaceId !== input.workspaceId) {
136
- throw new Error("DX Complete workspace service client is not bound to this workspace.");
137
- }
138
- if (record.secretHash !== hashServiceClientSecret(input.secret)) {
139
- throw new Error("DX Complete workspace service client secret is invalid.");
140
- }
141
- return record;
142
- }
143
- export async function registerOAuthClient(db, input) {
144
- if (input.redirectUris.length === 0) {
145
- throw new Error("redirect_uris must contain at least one URI.");
146
- }
147
- const now = new Date().toISOString();
148
- const clientId = `dxc_client_${randomUUID()}`;
149
- const record = {
150
- _id: clientId,
151
- clientId,
152
- clientName: input.clientName,
153
- redirectUris: input.redirectUris,
154
- grantTypes: input.grantTypes?.length ? input.grantTypes : ["authorization_code", "refresh_token"],
155
- responseTypes: input.responseTypes?.length ? input.responseTypes : ["code"],
156
- createdAt: now,
157
- updatedAt: now
158
- };
159
- await db.collection(OAUTH_CLIENTS_COLLECTION).insertOne(record);
160
- return record;
161
- }
162
- export async function getOAuthClient(db, clientId) {
163
- return db.collection(OAUTH_CLIENTS_COLLECTION).findOne({ _id: clientId });
164
- }
165
- export async function createOAuthAuthorizationRequest(db, input) {
166
- const now = new Date();
167
- const record = {
168
- _id: `dxc_state_${randomBytes(24).toString("base64url")}`,
169
- ...input,
170
- createdAt: now.toISOString(),
171
- expiresAt: new Date(now.getTime() + AUTH_REQUEST_TTL_SECONDS * 1000)
172
- };
173
- await db.collection(OAUTH_AUTH_REQUESTS_COLLECTION).insertOne(record);
174
- return record;
175
- }
176
- export async function consumeOAuthAuthorizationRequest(db, state) {
177
- const collection = db.collection(OAUTH_AUTH_REQUESTS_COLLECTION);
178
- const record = await collection.findOne({ _id: state });
179
- if (!record || isExpired(record.expiresAt)) {
180
- throw new Error("OAuth authorization request expired or was not found.");
181
- }
182
- await collection.deleteOne({ _id: state });
183
- return record;
184
- }
185
- export async function createOAuthAuthorizationCode(db, request, actor) {
186
- const code = `dxc_code_${randomBytes(32).toString("base64url")}`;
187
- const record = {
188
- ...request,
189
- _id: code,
190
- actor
191
- };
192
- await db.collection(OAUTH_CODES_COLLECTION).insertOne(record);
193
- return code;
194
- }
195
- export async function exchangeOAuthAuthorizationCode(db, input) {
196
- const collection = db.collection(OAUTH_CODES_COLLECTION);
197
- const record = await collection.findOne({ _id: input.code });
198
- if (!record || isExpired(record.expiresAt)) {
199
- throw new Error("Authorization code expired or was not found.");
200
- }
201
- if (record.clientId !== input.clientId ||
202
- record.redirectUri !== input.redirectUri ||
203
- record.resource !== input.resource) {
204
- throw new Error("Authorization code request does not match the original authorization.");
205
- }
206
- if (!verifyPkce(input.codeVerifier, record.codeChallenge)) {
207
- throw new Error("Invalid PKCE verifier.");
208
- }
209
- await collection.deleteOne({ _id: input.code });
210
- return issueMcpTokenPair(db, record);
211
- }
212
- export async function exchangeOAuthRefreshToken(db, input) {
213
- const existing = await findUsableToken(db, input.refreshToken, "refresh");
214
- if (existing.clientId !== input.clientId || existing.resource !== input.resource) {
215
- throw new Error("Refresh token does not match this client or resource.");
216
- }
217
- await db.collection(OAUTH_TOKENS_COLLECTION).updateOne({ _id: existing._id }, {
218
- $set: {
219
- revokedAt: new Date().toISOString()
220
- }
221
- });
222
- return issueMcpTokenPair(db, {
223
- clientId: existing.clientId,
224
- scope: existing.scope,
225
- resource: existing.resource,
226
- workspaceId: existing.workspaceId,
227
- actor: existing.actor
228
- });
229
- }
230
- export async function issueMcpTokenPair(db, input) {
231
- const accessToken = `dxc_access_${randomBytes(32).toString("base64url")}`;
232
- const refreshToken = `dxc_refresh_${randomBytes(32).toString("base64url")}`;
233
- const now = new Date();
234
- await db.collection(OAUTH_TOKENS_COLLECTION).insertMany([
235
- {
236
- _id: hashToken(accessToken),
237
- kind: "access",
238
- clientId: input.clientId,
239
- workspaceId: input.workspaceId,
240
- actor: input.actor,
241
- scope: input.scope || MCP_SCOPE,
242
- resource: input.resource,
243
- createdAt: now.toISOString(),
244
- expiresAt: new Date(now.getTime() + ACCESS_TOKEN_TTL_SECONDS * 1000)
245
- },
246
- {
247
- _id: hashToken(refreshToken),
248
- kind: "refresh",
249
- clientId: input.clientId,
250
- workspaceId: input.workspaceId,
251
- actor: input.actor,
252
- scope: input.scope || MCP_SCOPE,
253
- resource: input.resource,
254
- createdAt: now.toISOString(),
255
- expiresAt: new Date(now.getTime() + REFRESH_TOKEN_TTL_SECONDS * 1000)
256
- }
257
- ]);
258
- return {
259
- accessToken,
260
- refreshToken,
261
- expiresIn: ACCESS_TOKEN_TTL_SECONDS,
262
- scope: input.scope || MCP_SCOPE
263
- };
264
- }
265
- export async function verifyMcpAccessToken(db, input) {
266
- const record = await findUsableToken(db, input.token, "access");
267
- if (record.workspaceId !== input.workspaceId || record.resource !== input.resource) {
268
- throw new Error("Access token is not valid for this workspace resource.");
269
- }
270
- await assertWorkspaceMembership(db, input.workspaceId, record.actor);
271
- return record;
272
- }
273
- export async function exchangeGoogleCodeForProfile(config, input) {
274
- if (!config.googleClientId || !config.googleClientSecret) {
275
- throw new Error("Google OAuth is not configured.");
276
- }
277
- const response = await fetch("https://oauth2.googleapis.com/token", {
278
- method: "POST",
279
- headers: {
280
- "content-type": "application/x-www-form-urlencoded"
281
- },
282
- body: new URLSearchParams({
283
- code: input.code,
284
- client_id: config.googleClientId,
285
- client_secret: config.googleClientSecret,
286
- redirect_uri: input.redirectUri,
287
- grant_type: "authorization_code"
288
- })
289
- });
290
- if (!response.ok) {
291
- throw new Error(`Google token exchange failed: ${await response.text()}`);
292
- }
293
- const tokens = (await response.json());
294
- if (!tokens.id_token) {
295
- throw new Error("Google token exchange did not return an id_token.");
296
- }
297
- const profileResponse = await fetch(`https://oauth2.googleapis.com/tokeninfo?id_token=${encodeURIComponent(tokens.id_token)}`);
298
- if (!profileResponse.ok) {
299
- throw new Error(`Google token verification failed: ${await profileResponse.text()}`);
300
- }
301
- const profile = (await profileResponse.json());
302
- if (profile.aud !== config.googleClientId) {
303
- throw new Error("Google token audience did not match this DX Complete deployment.");
304
- }
305
- if (profile.email_verified !== true && profile.email_verified !== "true") {
306
- throw new Error("Google email is not verified.");
307
- }
308
- if (!profile.email || !profile.sub) {
309
- throw new Error("Google profile did not include email and subject.");
310
- }
311
- return {
312
- email: normalizeEmail(profile.email),
313
- subject: profile.sub,
314
- displayName: profile.name
315
- };
316
- }
317
- export function googleAuthorizationUrl(config, input) {
318
- if (!config.googleClientId) {
319
- throw new Error("Google OAuth is not configured.");
320
- }
321
- const url = new URL("https://accounts.google.com/o/oauth2/v2/auth");
322
- url.searchParams.set("client_id", config.googleClientId);
323
- url.searchParams.set("redirect_uri", input.redirectUri);
324
- url.searchParams.set("response_type", "code");
325
- url.searchParams.set("scope", "openid email profile");
326
- url.searchParams.set("state", input.state);
327
- url.searchParams.set("prompt", "select_account");
328
- return url.href;
329
- }
330
- export function createGoogleActor(profile) {
331
- return createGoogleActorContext({
332
- email: profile.email,
333
- subject: profile.subject,
334
- displayName: profile.displayName
335
- });
336
- }
337
- export function membershipRoles(membership) {
338
- return normalizeWorkspaceRoles(Array.isArray(membership.roles) && membership.roles.length > 0
339
- ? membership.roles
340
- : membership.role
341
- ? [membership.role]
342
- : []);
343
- }
344
- async function findUsableToken(db, token, kind) {
345
- const record = await db.collection(OAUTH_TOKENS_COLLECTION).findOne({
346
- _id: hashToken(token),
347
- kind,
348
- revokedAt: { $exists: false }
349
- });
350
- if (!record || isExpired(record.expiresAt)) {
351
- throw new Error("Token expired or was not found.");
352
- }
353
- return record;
354
- }
355
- function verifyPkce(verifier, challenge) {
356
- const actual = createHash("sha256").update(verifier).digest("base64url");
357
- return actual === challenge;
358
- }
359
- function hashToken(token) {
360
- return createHash("sha256").update(token).digest("hex");
361
- }
362
- function hashServiceClientSecret(secret) {
363
- return `${SERVICE_CLIENT_HASH_PREFIX}${createHash("sha256").update(secret).digest("hex")}`;
364
- }
365
- function isExpired(value) {
366
- return new Date(value).getTime() <= Date.now();
367
- }
368
- function membershipId(workspaceId, email) {
369
- return `${workspaceId}:${normalizeEmail(email)}`;
370
- }
371
- function normalizeWorkspaceRoles(values) {
372
- const roles = new Set();
373
- for (const value of values) {
374
- if (value === "member") {
375
- roles.add("end_user");
376
- continue;
377
- }
378
- if (isWorkspaceRole(value)) {
379
- roles.add(value);
380
- }
381
- }
382
- if (roles.size === 0) {
383
- roles.add("end_user");
384
- }
385
- return [...roles].sort();
386
- }
387
- function isWorkspaceRole(value) {
388
- return (value === "owner" ||
389
- value === "engineer" ||
390
- value === "tester" ||
391
- value === "operator" ||
392
- value === "support_agent" ||
393
- value === "end_user");
394
- }
@@ -1,7 +0,0 @@
1
- import type { RuntimeOptions } from "./config.js";
2
- export declare function checkRuntime(options?: RuntimeOptions): Promise<{
3
- ok: boolean;
4
- databaseName: string;
5
- envFilePath: string | undefined;
6
- collections: readonly ["workspaces", "statements", "journal_entries", "environments", "components", "estimates", "benefits", "expectations", "requirements", "tasks", "commitments", "deferrals", "changes", "incidents", "problems", "maintenance_schedules", "support_requests", "value_realizations", "decisions", "risks"];
7
- }>;
@@ -1,16 +0,0 @@
1
- import { connectRuntime } from "./mongo.js";
2
- import { COLLECTION_NAMES } from "./records.js";
3
- export async function checkRuntime(options = {}) {
4
- const runtime = await connectRuntime(options);
5
- try {
6
- return {
7
- ok: true,
8
- databaseName: runtime.config.databaseName,
9
- envFilePath: runtime.config.envFilePath,
10
- collections: COLLECTION_NAMES
11
- };
12
- }
13
- finally {
14
- await runtime.close();
15
- }
16
- }
@@ -1,17 +0,0 @@
1
- export type RuntimeOptions = {
2
- cwd?: string;
3
- env?: NodeJS.ProcessEnv;
4
- envFile?: string;
5
- };
6
- export type RuntimeConfig = {
7
- mongodbUri: string;
8
- databaseName: string;
9
- googleClientId?: string;
10
- googleClientSecret?: string;
11
- serviceProvisioningSecret?: string;
12
- envFilePath?: string;
13
- };
14
- export declare class RuntimeConfigError extends Error {
15
- constructor(message: string);
16
- }
17
- export declare function loadRuntimeConfig(options?: RuntimeOptions): Promise<RuntimeConfig>;
@@ -1,93 +0,0 @@
1
- import { constants as fsConstants } from "node:fs";
2
- import { access, readFile } from "node:fs/promises";
3
- import path from "node:path";
4
- export class RuntimeConfigError extends Error {
5
- constructor(message) {
6
- super(message);
7
- this.name = "RuntimeConfigError";
8
- }
9
- }
10
- const DEFAULT_ENV_FILE = ".env.local";
11
- const DEFAULT_DATABASE_NAME = "dxcomplete";
12
- export async function loadRuntimeConfig(options = {}) {
13
- const cwd = path.resolve(options.cwd ?? process.cwd());
14
- const envFilePath = await resolveEnvFile(cwd, options.envFile);
15
- const fileEnv = envFilePath ? await parseEnvFile(envFilePath) : {};
16
- const env = {
17
- ...fileEnv,
18
- ...(options.env ?? process.env)
19
- };
20
- const mongodbUri = readRequiredEnv(env, "DXC_MONGODB_URI");
21
- const databaseName = readOptionalEnv(env, "DXC_DATABASE_NAME") ?? DEFAULT_DATABASE_NAME;
22
- const googleClientId = readOptionalEnv(env, "DXC_GOOGLE_CLIENT_ID");
23
- const googleClientSecret = readOptionalEnv(env, "DXC_GOOGLE_CLIENT_SECRET");
24
- const serviceProvisioningSecret = readOptionalEnv(env, "DXC_SERVICE_PROVISIONING_SECRET");
25
- return {
26
- mongodbUri,
27
- databaseName,
28
- ...(googleClientId ? { googleClientId } : {}),
29
- ...(googleClientSecret ? { googleClientSecret } : {}),
30
- ...(serviceProvisioningSecret ? { serviceProvisioningSecret } : {}),
31
- envFilePath
32
- };
33
- }
34
- async function resolveEnvFile(cwd, envFile) {
35
- if (envFile) {
36
- const explicitPath = path.resolve(cwd, envFile);
37
- if (!(await fileExists(explicitPath))) {
38
- throw new RuntimeConfigError(`Env file not found: ${explicitPath}`);
39
- }
40
- return explicitPath;
41
- }
42
- const defaultPath = path.join(cwd, DEFAULT_ENV_FILE);
43
- return (await fileExists(defaultPath)) ? defaultPath : undefined;
44
- }
45
- async function parseEnvFile(filePath) {
46
- const content = await readFile(filePath, "utf8");
47
- const parsed = {};
48
- for (const [index, rawLine] of content.split(/\r?\n/).entries()) {
49
- const trimmed = rawLine.trim();
50
- if (!trimmed || trimmed.startsWith("#")) {
51
- continue;
52
- }
53
- const line = trimmed.startsWith("export ") ? trimmed.slice("export ".length).trim() : trimmed;
54
- const equalsIndex = line.indexOf("=");
55
- if (equalsIndex === -1) {
56
- throw new RuntimeConfigError(`Invalid env line ${index + 1} in ${filePath}. Expected KEY=value.`);
57
- }
58
- const key = line.slice(0, equalsIndex).trim();
59
- const value = line.slice(equalsIndex + 1).trim();
60
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
61
- throw new RuntimeConfigError(`Invalid env key "${key}" on line ${index + 1} in ${filePath}.`);
62
- }
63
- parsed[key] = unquote(value);
64
- }
65
- return parsed;
66
- }
67
- function readRequiredEnv(env, key) {
68
- const value = readOptionalEnv(env, key);
69
- if (!value) {
70
- throw new RuntimeConfigError(`${key} is required. Create .env.local from .env.example or export ${key} before running this command.`);
71
- }
72
- return value;
73
- }
74
- function readOptionalEnv(env, key) {
75
- const value = env[key]?.trim();
76
- return value ? value : undefined;
77
- }
78
- function unquote(value) {
79
- if ((value.startsWith("\"") && value.endsWith("\"")) ||
80
- (value.startsWith("'") && value.endsWith("'"))) {
81
- return value.slice(1, -1);
82
- }
83
- return value;
84
- }
85
- async function fileExists(filePath) {
86
- try {
87
- await access(filePath, fsConstants.F_OK);
88
- return true;
89
- }
90
- catch {
91
- return false;
92
- }
93
- }