@fluid-app/fluid-cli 0.1.9 → 0.1.10
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/bin/fluid.mjs +24 -12
- package/dist/bin/fluid.mjs.map +1 -1
- package/dist/index.d.mts +6 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +60 -17
- package/dist/index.mjs.map +1 -1
- package/dist/{token-C9SrlBRK.mjs → token-D4DJFSdm.mjs} +8 -6
- package/dist/token-D4DJFSdm.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/token-C9SrlBRK.mjs.map +0 -1
package/dist/bin/fluid.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as readConfig, c as getConfigDir, d as FLUID_API_ERROR, f as confirmMfa, g as
|
|
2
|
+
import { _ as validateToken, a as readConfig, c as getConfigDir, d as FLUID_API_ERROR, f as confirmMfa, g as switchCompany, h as sendMfa, m as getFluidApiBase, n as getAuthToken, o as updateConfig, p as fetchUserCompanies, s as writeConfig, t as getActiveProfile } from "../token-D4DJFSdm.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import chalk from "chalk";
|
|
@@ -8,6 +8,17 @@ import ora from "ora";
|
|
|
8
8
|
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
11
|
+
//#region src/auth/profiles.ts
|
|
12
|
+
function createFluidProfile({ name, token, companyName, baseUrl, storedAt }) {
|
|
13
|
+
return {
|
|
14
|
+
name,
|
|
15
|
+
token,
|
|
16
|
+
companyName,
|
|
17
|
+
storedAt: storedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
18
|
+
baseUrl
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
11
22
|
//#region src/commands/login.ts
|
|
12
23
|
/**
|
|
13
24
|
* fluid login — authenticate via email MFA or API token
|
|
@@ -29,12 +40,12 @@ function storeProfile(profileName, token, companyName) {
|
|
|
29
40
|
activeProfile: profileName,
|
|
30
41
|
profiles: {
|
|
31
42
|
...config.profiles,
|
|
32
|
-
[profileName]: {
|
|
43
|
+
[profileName]: createFluidProfile({
|
|
33
44
|
name: profileName,
|
|
34
45
|
token,
|
|
35
46
|
companyName,
|
|
36
|
-
|
|
37
|
-
}
|
|
47
|
+
baseUrl: getFluidApiBase()
|
|
48
|
+
})
|
|
38
49
|
}
|
|
39
50
|
}));
|
|
40
51
|
}
|
|
@@ -285,9 +296,9 @@ async function selectCompany(companies, activeCompanyName) {
|
|
|
285
296
|
if (companyId === void 0) return void 0;
|
|
286
297
|
return companies.find((c) => c.id === companyId);
|
|
287
298
|
}
|
|
288
|
-
async function performSwitch(token, company) {
|
|
299
|
+
async function performSwitch(token, company, baseUrl) {
|
|
289
300
|
const spinner = ora(`Switching to ${chalk.bold(company.name)}...`).start();
|
|
290
|
-
const result = await switchCompany(token, company.id);
|
|
301
|
+
const result = await switchCompany(token, company.id, baseUrl);
|
|
291
302
|
if (!result.success) {
|
|
292
303
|
if (result.error.code === FLUID_API_ERROR.INVALID_TOKEN.code) spinner.fail(chalk.red("Your session has expired. Please run `fluid login` to re-authenticate."));
|
|
293
304
|
else {
|
|
@@ -303,12 +314,12 @@ async function performSwitch(token, company) {
|
|
|
303
314
|
activeProfile: companyName,
|
|
304
315
|
profiles: {
|
|
305
316
|
...config.profiles,
|
|
306
|
-
[companyName]: {
|
|
317
|
+
[companyName]: createFluidProfile({
|
|
307
318
|
name: companyName,
|
|
308
319
|
token: jwt,
|
|
309
320
|
companyName,
|
|
310
|
-
|
|
311
|
-
}
|
|
321
|
+
baseUrl: baseUrl ?? getFluidApiBase()
|
|
322
|
+
})
|
|
312
323
|
}
|
|
313
324
|
}));
|
|
314
325
|
spinner.succeed(chalk.green(`Switched to ${chalk.bold(companyName)} (profile: ${chalk.bold(companyName)})`));
|
|
@@ -322,8 +333,9 @@ const switchCommand = new Command("switch").description("Switch between companie
|
|
|
322
333
|
}
|
|
323
334
|
const token = activeProfile.token;
|
|
324
335
|
const activeCompanyName = activeProfile.companyName;
|
|
336
|
+
const activeBaseUrl = activeProfile.baseUrl ?? getFluidApiBase();
|
|
325
337
|
const spinner = ora("Fetching companies...").start();
|
|
326
|
-
const result = await fetchUserCompanies(token);
|
|
338
|
+
const result = await fetchUserCompanies(token, activeBaseUrl);
|
|
327
339
|
if (!result.success) {
|
|
328
340
|
if (result.error.code === FLUID_API_ERROR.INVALID_TOKEN.code) {
|
|
329
341
|
spinner.fail(chalk.red("Your session has expired. Please run `fluid login` to re-authenticate."));
|
|
@@ -345,7 +357,7 @@ const switchCommand = new Command("switch").description("Switch between companie
|
|
|
345
357
|
if (profileArg) {
|
|
346
358
|
const match = companies.find((c) => c.name.toLowerCase() === profileArg.toLowerCase());
|
|
347
359
|
if (match) {
|
|
348
|
-
await performSwitch(token, match);
|
|
360
|
+
await performSwitch(token, match, activeBaseUrl);
|
|
349
361
|
return;
|
|
350
362
|
}
|
|
351
363
|
if (readConfig().profiles[profileArg]) {
|
|
@@ -365,7 +377,7 @@ const switchCommand = new Command("switch").description("Switch between companie
|
|
|
365
377
|
console.log(chalk.dim("Cancelled."));
|
|
366
378
|
return;
|
|
367
379
|
}
|
|
368
|
-
await performSwitch(token, selected);
|
|
380
|
+
await performSwitch(token, selected, activeBaseUrl);
|
|
369
381
|
});
|
|
370
382
|
//#endregion
|
|
371
383
|
//#region src/plugins/discovery.ts
|
package/dist/bin/fluid.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fluid.mjs","names":[],"sources":["../../src/commands/login.ts","../../src/commands/logout.ts","../../src/commands/whoami.ts","../../src/commands/switch.ts","../../src/plugins/discovery.ts","../../src/plugins/loader.ts","../../src/bin/fluid.ts"],"sourcesContent":["/**\n * fluid login — authenticate via email MFA or API token\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport ora from \"ora\";\nimport {\n validateToken,\n sendMfa,\n confirmMfa,\n type CompanyChoice,\n} from \"../auth/fluid-api.js\";\nimport { updateConfig, readConfig } from \"../config/config.js\";\n\nasync function confirmOverwrite(profileName: string): Promise<boolean> {\n const existing = readConfig().profiles[profileName];\n if (!existing) return true;\n\n const response = await prompts({\n type: \"confirm\",\n name: \"overwrite\",\n message: `Profile \"${profileName}\" already exists (${existing.companyName}). Overwrite?`,\n initial: false,\n });\n return Boolean(response[\"overwrite\"]);\n}\n\nfunction storeProfile(\n profileName: string,\n token: string,\n companyName: string,\n): void {\n updateConfig((config) => ({\n ...config,\n activeProfile: profileName,\n profiles: {\n ...config.profiles,\n [profileName]: {\n name: profileName,\n token,\n companyName,\n storedAt: new Date().toISOString(),\n },\n },\n }));\n}\n\nasync function loginWithToken(\n token: string,\n profileName?: string,\n): Promise<void> {\n const spinner = ora(\"Validating token...\").start();\n const result = await validateToken(token);\n\n if (!result.success) {\n spinner.fail(chalk.red(result.error.message));\n if (result.error.details) {\n console.log(chalk.dim(result.error.details));\n }\n process.exitCode = 1;\n return;\n }\n\n const name = profileName ?? result.value.name;\n\n // Stop spinner before potential interactive prompt to avoid terminal corruption\n spinner.succeed(\n chalk.green(`Token valid — ${chalk.bold(result.value.name)}`),\n );\n\n if (!(await confirmOverwrite(name))) {\n console.log(\n chalk.yellow(\"Login cancelled — existing profile not overwritten.\"),\n );\n return;\n }\n\n storeProfile(name, token, result.value.name);\n console.log(\n chalk.green(\n `Logged in as ${chalk.bold(result.value.name)} (profile: ${chalk.bold(name)})`,\n ),\n );\n}\n\nasync function loginWithEmail(\n initialEmail?: string,\n profileName?: string,\n): Promise<void> {\n // 1. Prompt for email\n let email = initialEmail;\n if (!email) {\n const response = await prompts({\n type: \"text\",\n name: \"email\",\n message: \"Enter your email\",\n });\n email = response[\"email\"] as string | undefined;\n if (!email) {\n console.log(chalk.red(\"No email provided. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n }\n\n // 2. Send MFA code\n const sendSpinner = ora(\"Sending verification code...\").start();\n const sendResult = await sendMfa(email);\n\n if (!sendResult.success) {\n sendSpinner.fail(chalk.red(sendResult.error.message));\n if (sendResult.error.details) {\n console.log(chalk.dim(sendResult.error.details));\n }\n process.exitCode = 1;\n return;\n }\n\n const expiresAt = new Date(sendResult.value.expiresAt);\n const expiresInMs = expiresAt.getTime() - Date.now();\n const expiresInMin = Math.round(expiresInMs / 1000 / 60);\n const expiryNote =\n expiresInMin > 0\n ? `expires in ~${expiresInMin} min`\n : \"code may expire soon — check your email quickly\";\n sendSpinner.succeed(\n `Verification code sent — check your email (${expiryNote})`,\n );\n\n // 3. Prompt for verification code (retry up to 3 times on invalid code)\n const MAX_CODE_ATTEMPTS = 3;\n let confirmResult;\n for (let attempt = 1; attempt <= MAX_CODE_ATTEMPTS; attempt++) {\n const codeResponse = await prompts({\n type: \"text\",\n name: \"code\",\n message: \"Enter the 6-digit code\",\n });\n const code = codeResponse[\"code\"] as string | undefined;\n if (!code) {\n console.log(chalk.red(\"No code provided. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n if (!/^\\d{6}$/.test(code)) {\n console.log(chalk.red(\"Code must be exactly 6 digits.\"));\n if (attempt < MAX_CODE_ATTEMPTS) continue;\n console.log(chalk.red(\"Too many invalid attempts. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n\n const confirmSpinner = ora(\"Verifying code...\").start();\n confirmResult = await confirmMfa(sendResult.value.uuid, code);\n\n if (confirmResult.success) {\n confirmSpinner.succeed(\"Verified\");\n break;\n }\n\n // Retryable: wrong code, still have attempts left\n if (\n confirmResult.error.code === \"INVALID_CODE\" &&\n attempt < MAX_CODE_ATTEMPTS\n ) {\n confirmSpinner.fail(\n chalk.red(\n `${confirmResult.error.message} (attempt ${attempt}/${MAX_CODE_ATTEMPTS})`,\n ),\n );\n continue;\n }\n\n // Non-retryable (expired, unreachable, etc.) or final attempt\n confirmSpinner.fail(chalk.red(confirmResult.error.message));\n if (confirmResult.error.details) {\n console.log(chalk.dim(confirmResult.error.details));\n }\n process.exitCode = 1;\n return;\n }\n\n if (!confirmResult?.success) return;\n\n const { companies } = confirmResult.value;\n\n if (companies.length === 0) {\n console.log(chalk.red(\"No companies found for this account.\"));\n process.exitCode = 1;\n return;\n }\n\n // 4. Select company (auto-select if only one)\n let selected: CompanyChoice;\n if (companies.length === 1) {\n const first = companies[0];\n if (!first) {\n console.log(chalk.red(\"No companies found for this account.\"));\n process.exitCode = 1;\n return;\n }\n selected = first;\n } else {\n const companyChoices = companies.map((c, i) => ({\n title: `${c.name} (${c.shopName})`,\n value: i,\n }));\n\n const selectResponse = await prompts({\n type: \"autocomplete\",\n name: \"companyIndex\",\n message: \"Select a company (type to search)\",\n choices: companyChoices,\n suggest: (input, choices) =>\n Promise.resolve(\n input\n ? choices.filter((c) =>\n c.title.toLowerCase().includes(input.toLowerCase()),\n )\n : choices,\n ),\n });\n\n const idx = selectResponse[\"companyIndex\"] as number | undefined;\n\n if (idx === undefined) {\n console.log(chalk.red(\"No company selected. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n const choice = companies[idx];\n if (!choice) {\n console.log(chalk.red(\"Invalid selection. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n selected = choice;\n }\n\n // 5. Store profile\n const name = profileName ?? selected.name;\n\n if (!(await confirmOverwrite(name))) {\n console.log(\n chalk.yellow(\"Login cancelled — existing profile not overwritten.\"),\n );\n return;\n }\n\n storeProfile(name, selected.jwt, selected.name);\n console.log(\n chalk.green(\n `Logged in as ${chalk.bold(selected.name)} (profile: ${chalk.bold(name)})`,\n ),\n );\n}\n\nexport const loginCommand = new Command(\"login\")\n .description(\"Authenticate with the Fluid API\")\n .option(\n \"-t, --token <token>\",\n \"API token (skips email flow). Prefer FLUID_TOKEN env var to avoid shell history exposure\",\n )\n .option(\"-e, --email <email>\", \"Email address for MFA login\")\n .option(\"-n, --name <name>\", \"Profile name (defaults to company name)\")\n .action(async (opts: { token?: string; email?: string; name?: string }) => {\n // Accept token from env var to avoid shell history / process listing exposure\n const token = opts.token ?? process.env[\"FLUID_TOKEN\"];\n if (token) {\n if (opts.token) {\n console.log(\n chalk.yellow(\n \"Warning: token passed via --token flag is visible in shell history and process listings. \" +\n \"Prefer the FLUID_TOKEN environment variable.\",\n ),\n );\n }\n await loginWithToken(token, opts.name);\n } else {\n await loginWithEmail(opts.email, opts.name);\n }\n });\n","/**\n * fluid logout — remove stored auth profile(s)\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { readConfig, writeConfig } from \"../config/config.js\";\n\nexport const logoutCommand = new Command(\"logout\")\n .description(\"Remove stored authentication\")\n .option(\"-a, --all\", \"Remove all profiles\")\n .action((opts: { all?: boolean }) => {\n const config = readConfig();\n\n if (opts.all) {\n writeConfig({\n ...config,\n activeProfile: null,\n profiles: {},\n });\n console.log(chalk.green(\"All profiles removed.\"));\n return;\n }\n\n if (!config.activeProfile) {\n console.log(chalk.yellow(\"Not currently logged in.\"));\n return;\n }\n\n const profileName = config.activeProfile;\n const { [profileName]: _, ...remainingProfiles } = config.profiles;\n\n // Pick the first remaining profile as active, or null\n const nextActive = Object.keys(remainingProfiles)[0] ?? null;\n\n writeConfig({\n ...config,\n activeProfile: nextActive,\n profiles: remainingProfiles,\n });\n\n console.log(\n chalk.green(`Logged out of profile ${chalk.bold(profileName)}.`),\n );\n if (nextActive) {\n console.log(chalk.dim(`Switched to profile ${nextActive}.`));\n }\n });\n","/**\n * fluid whoami — show current auth profile and validate against API\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { getActiveProfile } from \"../auth/token.js\";\nimport { validateToken } from \"../auth/fluid-api.js\";\n\nexport const whoamiCommand = new Command(\"whoami\")\n .description(\"Show the current authenticated profile\")\n .action(async () => {\n const profile = getActiveProfile();\n\n if (!profile) {\n console.log(\n chalk.yellow(\"Not logged in. Run `fluid login` to authenticate.\"),\n );\n process.exitCode = 1;\n return;\n }\n\n const spinner = ora(\"Verifying token...\").start();\n const result = await validateToken(profile.token);\n\n if (!result.success) {\n spinner.fail(chalk.red(\"Token is no longer valid.\"));\n console.log(chalk.dim(`Profile: ${profile.name}`));\n console.log(chalk.dim(\"Run `fluid login` to re-authenticate.\"));\n process.exitCode = 1;\n return;\n }\n\n spinner.succeed(chalk.green(\"Authenticated\"));\n console.log(` Profile: ${chalk.bold(profile.name)}`);\n console.log(` Company: ${chalk.bold(result.value.name)}`);\n console.log(` Stored: ${chalk.dim(profile.storedAt)}`);\n });\n","/**\n * fluid switch — switch between companies (fetched from API, with local fallback)\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport ora from \"ora\";\nimport {\n fetchUserCompanies,\n switchCompany,\n FLUID_API_ERROR,\n type UserCompany,\n} from \"../auth/fluid-api.js\";\nimport { getActiveProfile } from \"../auth/token.js\";\nimport { readConfig, updateConfig } from \"../config/config.js\";\n\nasync function localSwitch(profileArg?: string): Promise<void> {\n const config = readConfig();\n const profileNames = Object.keys(config.profiles);\n\n if (profileNames.length === 0) {\n console.log(chalk.yellow(\"No profiles stored. Run `fluid login` first.\"));\n process.exitCode = 1;\n return;\n }\n\n let targetProfile = profileArg;\n\n if (!targetProfile) {\n const profileChoices = profileNames.map((name) => ({\n title: name === config.activeProfile ? `${name} (active)` : name,\n value: name,\n }));\n\n const response = await prompts({\n type: \"autocomplete\",\n name: \"profile\",\n message: \"Select a profile (type to search)\",\n choices: profileChoices,\n suggest: (input, choices) =>\n Promise.resolve(\n input\n ? choices.filter((c) =>\n c.value.toLowerCase().includes(input.toLowerCase()),\n )\n : choices,\n ),\n });\n targetProfile = response[\"profile\"] as string | undefined;\n\n if (!targetProfile) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n }\n\n if (!config.profiles[targetProfile]) {\n console.log(chalk.red(`Profile \"${targetProfile}\" not found.`));\n console.log(chalk.dim(`Available: ${profileNames.join(\", \")}`));\n process.exitCode = 1;\n return;\n }\n\n updateConfig((c) => ({ ...c, activeProfile: targetProfile! }));\n console.log(chalk.green(`Switched to profile ${chalk.bold(targetProfile)}.`));\n}\n\nasync function selectCompany(\n companies: UserCompany[],\n activeCompanyName: string | undefined,\n): Promise<UserCompany | undefined> {\n const companyChoices = companies.map((c) => ({\n title:\n c.name === activeCompanyName\n ? `${c.name} ${chalk.dim(\"(active)\")}`\n : c.name,\n value: c.id,\n }));\n\n const response = await prompts({\n type: \"autocomplete\",\n name: \"companyId\",\n message: \"Select a company (type to search)\",\n choices: companyChoices,\n suggest: (input, choices) =>\n Promise.resolve(\n input\n ? choices.filter((c) =>\n c.title.toLowerCase().includes(input.toLowerCase()),\n )\n : choices,\n ),\n });\n\n const companyId = response[\"companyId\"] as number | undefined;\n if (companyId === undefined) return undefined;\n return companies.find((c) => c.id === companyId);\n}\n\nasync function performSwitch(\n token: string,\n company: UserCompany,\n): Promise<void> {\n const spinner = ora(`Switching to ${chalk.bold(company.name)}...`).start();\n const result = await switchCompany(token, company.id);\n\n if (!result.success) {\n if (result.error.code === FLUID_API_ERROR.INVALID_TOKEN.code) {\n spinner.fail(\n chalk.red(\n \"Your session has expired. Please run `fluid login` to re-authenticate.\",\n ),\n );\n } else {\n spinner.fail(chalk.red(result.error.message));\n if (result.error.details) {\n console.log(chalk.dim(result.error.details));\n }\n }\n process.exitCode = 1;\n return;\n }\n\n const { companyName, jwt } = result.value;\n\n updateConfig((config) => ({\n ...config,\n activeProfile: companyName,\n profiles: {\n ...config.profiles,\n [companyName]: {\n name: companyName,\n token: jwt,\n companyName,\n storedAt: new Date().toISOString(),\n },\n },\n }));\n\n spinner.succeed(\n chalk.green(\n `Switched to ${chalk.bold(companyName)} (profile: ${chalk.bold(companyName)})`,\n ),\n );\n}\n\nexport const switchCommand = new Command(\"switch\")\n .description(\"Switch between companies\")\n .argument(\"[profile]\", \"Company or profile name to switch to\")\n .action(async (profileArg?: string) => {\n const activeProfile = getActiveProfile();\n\n if (!activeProfile) {\n console.log(chalk.yellow(\"Not logged in. Run `fluid login` first.\"));\n process.exitCode = 1;\n return;\n }\n\n const token = activeProfile.token;\n const activeCompanyName = activeProfile.companyName;\n\n // Fetch companies from API\n const spinner = ora(\"Fetching companies...\").start();\n const result = await fetchUserCompanies(token);\n\n if (!result.success) {\n if (result.error.code === FLUID_API_ERROR.INVALID_TOKEN.code) {\n spinner.fail(\n chalk.red(\n \"Your session has expired. Please run `fluid login` to re-authenticate.\",\n ),\n );\n process.exitCode = 1;\n return;\n }\n spinner.warn(\n chalk.yellow(\n `Could not fetch companies from API: ${result.error.message}`,\n ),\n );\n console.log(chalk.dim(\"Falling back to locally stored profiles.\"));\n await localSwitch(profileArg);\n return;\n }\n\n const companies = result.value;\n spinner.succeed(\n `Found ${companies.length} company${companies.length === 1 ? \"\" : \"ies\"}`,\n );\n\n if (companies.length === 0) {\n console.log(chalk.yellow(\"No companies found for this account.\"));\n process.exitCode = 1;\n return;\n }\n\n // If a profile argument was passed, match against API companies first\n if (profileArg) {\n const match = companies.find(\n (c) => c.name.toLowerCase() === profileArg.toLowerCase(),\n );\n\n if (match) {\n await performSwitch(token, match);\n return;\n }\n\n // Fall back to local profile matching\n const config = readConfig();\n if (config.profiles[profileArg]) {\n updateConfig((c) => ({ ...c, activeProfile: profileArg! }));\n console.log(\n chalk.green(`Switched to profile ${chalk.bold(profileArg)}.`),\n );\n return;\n }\n\n console.log(chalk.red(`Company or profile \"${profileArg}\" not found.`));\n process.exitCode = 1;\n return;\n }\n\n // Interactive selection\n const selected = await selectCompany(companies, activeCompanyName);\n if (!selected) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n\n await performSwitch(token, selected);\n });\n","/**\n * Auto-discover @fluid-app CLI plugins.\n *\n * Three discovery strategies run in order:\n *\n * 1a. **node_modules scan (cwd)** — look in `<cwd>/node_modules/@fluid-app/`\n * for directories whose names match the plugin naming convention.\n * This is the primary mechanism for project-local plugin installs.\n *\n * 1b. **node_modules scan (CLI install location)** — look in the\n * `node_modules/@fluid-app/` directory containing the CLI core package\n * itself. This covers global installs where plugins are sibling packages\n * under the same global `node_modules/`.\n *\n * 2. **Workspace scan** — walk upward from the CLI core package root to the\n * monorepo workspace root, then scan `packages/` for sibling plugin\n * packages. This covers the pnpm-workspace development case where\n * plugins are not symlinked into `node_modules`.\n *\n * Only first-party @fluid-app scoped packages are loaded.\n */\n\nimport { existsSync, readFileSync, readdirSync, realpathSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\n\nconst PLUGIN_SCOPE = \"@fluid-app\";\n\n/**\n * A plugin package name must match one of these patterns:\n * - @fluid-app/fluid-cli-* (standard)\n * - @fluid-app/*-cli-commands (v2025-06 style)\n */\nfunction isPluginName(packageName: string): boolean {\n if (!packageName.startsWith(`${PLUGIN_SCOPE}/`)) return false;\n const bare = packageName.slice(`${PLUGIN_SCOPE}/`.length);\n return bare.startsWith(\"fluid-cli-\") || bare.endsWith(\"-cli-commands\");\n}\n\n// ---------------------------------------------------------------------------\n// Strategy 1: node_modules scan (production / published installs)\n// ---------------------------------------------------------------------------\n\nfunction discoverFromNodeModules(basePath: string): DiscoveredPlugin[] {\n const scopeDir = join(basePath, \"node_modules\", PLUGIN_SCOPE);\n\n if (!existsSync(scopeDir)) return [];\n\n return readdirSync(scopeDir, { withFileTypes: true })\n .filter(\n (entry) =>\n (entry.isDirectory() || entry.isSymbolicLink()) &&\n isPluginName(`${PLUGIN_SCOPE}/${entry.name}`),\n )\n .map((entry) => {\n const name = `${PLUGIN_SCOPE}/${entry.name}`;\n const entryDir = join(scopeDir, entry.name);\n // Resolve symlinks (pnpm workspace links) to file:// URLs so the\n // dynamic import works regardless of pnpm's strict module resolution.\n try {\n const realDir = realpathSync(entryDir);\n const distEntry = join(realDir, \"dist\", \"index.mjs\");\n if (existsSync(distEntry)) {\n return { name, importSpecifier: pathToFileURL(distEntry).href };\n }\n } catch {\n // Broken symlink or other fs error — fall through to bare specifier\n }\n // Fallback to bare specifier for non-workspace (published) installs\n return { name, importSpecifier: name };\n });\n}\n\n// ---------------------------------------------------------------------------\n// Strategy 2: workspace scan (pnpm monorepo development)\n// ---------------------------------------------------------------------------\n\nfunction findWorkspaceRoot(startDir: string): string | null {\n let dir = startDir;\n while (true) {\n if (\n existsSync(join(dir, \"pnpm-workspace.yaml\")) ||\n existsSync(join(dir, \"pnpm-workspace.yml\"))\n ) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nfunction readPackageName(dir: string): string | null {\n const pkgPath = join(dir, \"package.json\");\n if (!existsSync(pkgPath)) return null;\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as {\n name?: string;\n };\n return pkg.name ?? null;\n } catch {\n return null;\n }\n}\n\nexport interface DiscoveredPlugin {\n name: string;\n importSpecifier: string;\n}\n\nfunction discoverFromWorkspace(startDir: string): DiscoveredPlugin[] {\n const workspaceRoot = findWorkspaceRoot(startDir);\n if (!workspaceRoot) return [];\n\n const results: DiscoveredPlugin[] = [];\n const packagesDir = join(workspaceRoot, \"packages\");\n if (!existsSync(packagesDir)) return [];\n\n // Scan exactly two levels deep under packages/ (e.g. packages/cli/themes,\n // packages/cli/v2025-06). This matches the monorepo convention where all\n // packages live at packages/<domain>/<package>/.\n // If a plugin is added at a different depth, update this scan accordingly.\n let domainDirs: string[];\n try {\n domainDirs = readdirSync(packagesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => join(packagesDir, e.name));\n } catch {\n return [];\n }\n\n for (const domainDir of domainDirs) {\n let subDirs: string[];\n try {\n subDirs = readdirSync(domainDir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => join(domainDir, e.name));\n } catch {\n continue;\n }\n\n for (const subDir of subDirs) {\n const name = readPackageName(subDir);\n if (!name || !name.startsWith(`${PLUGIN_SCOPE}/`) || !isPluginName(name))\n continue;\n\n const distEntry = join(subDir, \"dist\", \"index.mjs\");\n if (!existsSync(distEntry)) {\n continue;\n }\n\n results.push({\n name,\n importSpecifier: pathToFileURL(distEntry).href,\n });\n }\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Exported API\n// ---------------------------------------------------------------------------\n\n/** Resolved once — the CLI core package root (`packages/cli/core/`). */\nexport const corePackageDir = dirname(\n dirname(dirname(fileURLToPath(import.meta.url))),\n);\n\n/**\n * Discover installed plugin packages. Returns a de-duplicated, sorted list\n * of `{ name, importSpecifier }` objects.\n *\n * @param basePath - Directory to scan for `node_modules/@fluid-app/`\n * @param searchFrom - Starting directory for workspace root detection\n * (walked upward via `findWorkspaceRoot`). Pass `null` to skip workspace\n * scanning (useful in tests). Defaults to `corePackageDir`.\n */\nexport function discoverPlugins(\n basePath: string,\n searchFrom?: string | null,\n): DiscoveredPlugin[] {\n const seen = new Set<string>();\n const results: DiscoveredPlugin[] = [];\n\n // Strategy 1a: node_modules relative to cwd (project-local plugins)\n for (const plugin of discoverFromNodeModules(basePath)) {\n if (!seen.has(plugin.name)) {\n seen.add(plugin.name);\n results.push(plugin);\n }\n }\n\n // Strategy 1b: node_modules relative to the CLI's own install location\n // (for global installs where plugins are siblings under the same\n // node_modules/@fluid-app/ directory)\n const cliParent = dirname(dirname(dirname(corePackageDir)));\n if (cliParent !== basePath) {\n for (const plugin of discoverFromNodeModules(cliParent)) {\n if (!seen.has(plugin.name)) {\n seen.add(plugin.name);\n results.push(plugin);\n }\n }\n }\n\n // Strategy 2: workspace siblings\n if (searchFrom !== null) {\n const wsDir = searchFrom ?? corePackageDir;\n for (const wp of discoverFromWorkspace(wsDir)) {\n if (!seen.has(wp.name)) {\n seen.add(wp.name);\n results.push({ name: wp.name, importSpecifier: wp.importSpecifier });\n }\n }\n }\n\n return results.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Dynamically import a plugin module and return its default export.\n *\n * @internal Not intended for external callers. Only called with specifiers\n * produced by {@link discoverPlugins}:\n * - bare package names (e.g. `@fluid-app/fluid-cli-portal`) from the\n * node_modules scan, validated by {@link isPluginName}.\n * - `file://` URLs from {@link discoverFromWorkspace}, which constructs\n * them internally from validated workspace paths (`packages/` tree,\n * `dist/index.mjs`). These are trusted internal specifiers.\n */\nexport async function importPlugin(specifier: string): Promise<unknown> {\n // file:// URLs are trusted — they originate from discoverFromWorkspace\n // which builds them from validated workspace paths under packages/.\n // For bare package names, verify they match the plugin naming convention.\n if (!specifier.startsWith(\"file://\") && !isPluginName(specifier)) {\n throw new Error(`Refusing to import non-plugin package: ${specifier}`);\n }\n const mod = (await import(specifier)) as Record<string, unknown>;\n return mod[\"default\"] ?? mod;\n}\n","/**\n * Plugin loader — orchestrates discovery and registration\n */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { getConfigDir } from \"../config/paths.js\";\nimport { getAuthToken } from \"../auth/token.js\";\nimport { readConfig } from \"../config/config.js\";\nimport type { FluidPlugin, PluginContext } from \"./types.js\";\nimport { discoverPlugins, importPlugin } from \"./discovery.js\";\n\nfunction isFluidPlugin(value: unknown): value is FluidPlugin {\n if (typeof value !== \"object\" || value === null) return false;\n const obj = value as Record<string, unknown>;\n return (\n typeof obj[\"name\"] === \"string\" &&\n typeof obj[\"version\"] === \"string\" &&\n typeof obj[\"register\"] === \"function\"\n );\n}\n\n/**\n * Discover, import, and register all installed plugins\n */\nexport async function loadPlugins(\n program: Command,\n basePath: string,\n): Promise<void> {\n const discovered = discoverPlugins(basePath);\n const { enabledPlugins } = readConfig();\n\n // If enabledPlugins is set, only load explicitly allowed plugins.\n // This acts as an allow-list to prevent accidental supply-chain execution.\n const allowedSet = enabledPlugins !== null ? new Set(enabledPlugins) : null;\n const plugins = allowedSet\n ? discovered.filter((p) => allowedSet.has(p.name))\n : discovered;\n\n if (plugins.length === 0) return;\n\n const ctx: PluginContext = {\n program,\n getAuthToken,\n configDir: getConfigDir(),\n };\n\n for (const { name, importSpecifier } of plugins) {\n try {\n const exported = await importPlugin(importSpecifier);\n\n if (!isFluidPlugin(exported)) {\n console.warn(\n chalk.yellow(`Warning: ${name} does not export a valid FluidPlugin`),\n );\n continue;\n }\n\n await exported.register(ctx);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(\n chalk.yellow(`Warning: Failed to load plugin ${name}: ${message}`),\n );\n }\n }\n}\n","#!/usr/bin/env node\n\nimport { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { loginCommand } from \"../commands/login.js\";\nimport { logoutCommand } from \"../commands/logout.js\";\nimport { whoamiCommand } from \"../commands/whoami.js\";\nimport { switchCommand } from \"../commands/switch.js\";\nimport { loadPlugins } from \"../plugins/loader.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../../package.json\") as { version: string };\n\nconst packageRoot = process.cwd();\n\nconst program = new Command();\n\nprogram.name(\"fluid\").description(\"Fluid Commerce CLI\").version(version);\n\n// Built-in auth commands\nprogram.addCommand(loginCommand);\nprogram.addCommand(logoutCommand);\nprogram.addCommand(whoamiCommand);\nprogram.addCommand(switchCommand);\n\n// Discover and load all plugins (auto-discovered from node_modules and workspace)\nawait loadPlugins(program, packageRoot);\n\nprogram.parse();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fluid.mjs","names":[],"sources":["../../src/auth/profiles.ts","../../src/commands/login.ts","../../src/commands/logout.ts","../../src/commands/whoami.ts","../../src/commands/switch.ts","../../src/plugins/discovery.ts","../../src/plugins/loader.ts","../../src/bin/fluid.ts"],"sourcesContent":["/**\n * Helpers for constructing stored auth profiles.\n */\n\nimport type { FluidProfile } from \"../config/types.js\";\n\ninterface CreateFluidProfileInput {\n readonly name: string;\n readonly token: string;\n readonly companyName: string;\n readonly baseUrl: string;\n readonly storedAt?: string;\n}\n\nexport function createFluidProfile({\n name,\n token,\n companyName,\n baseUrl,\n storedAt,\n}: CreateFluidProfileInput): FluidProfile {\n return {\n name,\n token,\n companyName,\n storedAt: storedAt ?? new Date().toISOString(),\n baseUrl,\n };\n}\n","/**\n * fluid login — authenticate via email MFA or API token\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport ora from \"ora\";\nimport {\n validateToken,\n sendMfa,\n confirmMfa,\n getFluidApiBase,\n type CompanyChoice,\n} from \"../auth/fluid-api.js\";\nimport { createFluidProfile } from \"../auth/profiles.js\";\nimport { updateConfig, readConfig } from \"../config/config.js\";\n\nasync function confirmOverwrite(profileName: string): Promise<boolean> {\n const existing = readConfig().profiles[profileName];\n if (!existing) return true;\n\n const response = await prompts({\n type: \"confirm\",\n name: \"overwrite\",\n message: `Profile \"${profileName}\" already exists (${existing.companyName}). Overwrite?`,\n initial: false,\n });\n return Boolean(response[\"overwrite\"]);\n}\n\nfunction storeProfile(\n profileName: string,\n token: string,\n companyName: string,\n): void {\n updateConfig((config) => ({\n ...config,\n activeProfile: profileName,\n profiles: {\n ...config.profiles,\n [profileName]: createFluidProfile({\n name: profileName,\n token,\n companyName,\n baseUrl: getFluidApiBase(),\n }),\n },\n }));\n}\n\nasync function loginWithToken(\n token: string,\n profileName?: string,\n): Promise<void> {\n const spinner = ora(\"Validating token...\").start();\n const result = await validateToken(token);\n\n if (!result.success) {\n spinner.fail(chalk.red(result.error.message));\n if (result.error.details) {\n console.log(chalk.dim(result.error.details));\n }\n process.exitCode = 1;\n return;\n }\n\n const name = profileName ?? result.value.name;\n\n // Stop spinner before potential interactive prompt to avoid terminal corruption\n spinner.succeed(\n chalk.green(`Token valid — ${chalk.bold(result.value.name)}`),\n );\n\n if (!(await confirmOverwrite(name))) {\n console.log(\n chalk.yellow(\"Login cancelled — existing profile not overwritten.\"),\n );\n return;\n }\n\n storeProfile(name, token, result.value.name);\n console.log(\n chalk.green(\n `Logged in as ${chalk.bold(result.value.name)} (profile: ${chalk.bold(name)})`,\n ),\n );\n}\n\nasync function loginWithEmail(\n initialEmail?: string,\n profileName?: string,\n): Promise<void> {\n // 1. Prompt for email\n let email = initialEmail;\n if (!email) {\n const response = await prompts({\n type: \"text\",\n name: \"email\",\n message: \"Enter your email\",\n });\n email = response[\"email\"] as string | undefined;\n if (!email) {\n console.log(chalk.red(\"No email provided. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n }\n\n // 2. Send MFA code\n const sendSpinner = ora(\"Sending verification code...\").start();\n const sendResult = await sendMfa(email);\n\n if (!sendResult.success) {\n sendSpinner.fail(chalk.red(sendResult.error.message));\n if (sendResult.error.details) {\n console.log(chalk.dim(sendResult.error.details));\n }\n process.exitCode = 1;\n return;\n }\n\n const expiresAt = new Date(sendResult.value.expiresAt);\n const expiresInMs = expiresAt.getTime() - Date.now();\n const expiresInMin = Math.round(expiresInMs / 1000 / 60);\n const expiryNote =\n expiresInMin > 0\n ? `expires in ~${expiresInMin} min`\n : \"code may expire soon — check your email quickly\";\n sendSpinner.succeed(\n `Verification code sent — check your email (${expiryNote})`,\n );\n\n // 3. Prompt for verification code (retry up to 3 times on invalid code)\n const MAX_CODE_ATTEMPTS = 3;\n let confirmResult;\n for (let attempt = 1; attempt <= MAX_CODE_ATTEMPTS; attempt++) {\n const codeResponse = await prompts({\n type: \"text\",\n name: \"code\",\n message: \"Enter the 6-digit code\",\n });\n const code = codeResponse[\"code\"] as string | undefined;\n if (!code) {\n console.log(chalk.red(\"No code provided. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n if (!/^\\d{6}$/.test(code)) {\n console.log(chalk.red(\"Code must be exactly 6 digits.\"));\n if (attempt < MAX_CODE_ATTEMPTS) continue;\n console.log(chalk.red(\"Too many invalid attempts. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n\n const confirmSpinner = ora(\"Verifying code...\").start();\n confirmResult = await confirmMfa(sendResult.value.uuid, code);\n\n if (confirmResult.success) {\n confirmSpinner.succeed(\"Verified\");\n break;\n }\n\n // Retryable: wrong code, still have attempts left\n if (\n confirmResult.error.code === \"INVALID_CODE\" &&\n attempt < MAX_CODE_ATTEMPTS\n ) {\n confirmSpinner.fail(\n chalk.red(\n `${confirmResult.error.message} (attempt ${attempt}/${MAX_CODE_ATTEMPTS})`,\n ),\n );\n continue;\n }\n\n // Non-retryable (expired, unreachable, etc.) or final attempt\n confirmSpinner.fail(chalk.red(confirmResult.error.message));\n if (confirmResult.error.details) {\n console.log(chalk.dim(confirmResult.error.details));\n }\n process.exitCode = 1;\n return;\n }\n\n if (!confirmResult?.success) return;\n\n const { companies } = confirmResult.value;\n\n if (companies.length === 0) {\n console.log(chalk.red(\"No companies found for this account.\"));\n process.exitCode = 1;\n return;\n }\n\n // 4. Select company (auto-select if only one)\n let selected: CompanyChoice;\n if (companies.length === 1) {\n const first = companies[0];\n if (!first) {\n console.log(chalk.red(\"No companies found for this account.\"));\n process.exitCode = 1;\n return;\n }\n selected = first;\n } else {\n const companyChoices = companies.map((c, i) => ({\n title: `${c.name} (${c.shopName})`,\n value: i,\n }));\n\n const selectResponse = await prompts({\n type: \"autocomplete\",\n name: \"companyIndex\",\n message: \"Select a company (type to search)\",\n choices: companyChoices,\n suggest: (input, choices) =>\n Promise.resolve(\n input\n ? choices.filter((c) =>\n c.title.toLowerCase().includes(input.toLowerCase()),\n )\n : choices,\n ),\n });\n\n const idx = selectResponse[\"companyIndex\"] as number | undefined;\n\n if (idx === undefined) {\n console.log(chalk.red(\"No company selected. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n const choice = companies[idx];\n if (!choice) {\n console.log(chalk.red(\"Invalid selection. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n selected = choice;\n }\n\n // 5. Store profile\n const name = profileName ?? selected.name;\n\n if (!(await confirmOverwrite(name))) {\n console.log(\n chalk.yellow(\"Login cancelled — existing profile not overwritten.\"),\n );\n return;\n }\n\n storeProfile(name, selected.jwt, selected.name);\n console.log(\n chalk.green(\n `Logged in as ${chalk.bold(selected.name)} (profile: ${chalk.bold(name)})`,\n ),\n );\n}\n\nexport const loginCommand = new Command(\"login\")\n .description(\"Authenticate with the Fluid API\")\n .option(\n \"-t, --token <token>\",\n \"API token (skips email flow). Prefer FLUID_TOKEN env var to avoid shell history exposure\",\n )\n .option(\"-e, --email <email>\", \"Email address for MFA login\")\n .option(\"-n, --name <name>\", \"Profile name (defaults to company name)\")\n .action(async (opts: { token?: string; email?: string; name?: string }) => {\n // Accept token from env var to avoid shell history / process listing exposure\n const token = opts.token ?? process.env[\"FLUID_TOKEN\"];\n if (token) {\n if (opts.token) {\n console.log(\n chalk.yellow(\n \"Warning: token passed via --token flag is visible in shell history and process listings. \" +\n \"Prefer the FLUID_TOKEN environment variable.\",\n ),\n );\n }\n await loginWithToken(token, opts.name);\n } else {\n await loginWithEmail(opts.email, opts.name);\n }\n });\n","/**\n * fluid logout — remove stored auth profile(s)\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { readConfig, writeConfig } from \"../config/config.js\";\n\nexport const logoutCommand = new Command(\"logout\")\n .description(\"Remove stored authentication\")\n .option(\"-a, --all\", \"Remove all profiles\")\n .action((opts: { all?: boolean }) => {\n const config = readConfig();\n\n if (opts.all) {\n writeConfig({\n ...config,\n activeProfile: null,\n profiles: {},\n });\n console.log(chalk.green(\"All profiles removed.\"));\n return;\n }\n\n if (!config.activeProfile) {\n console.log(chalk.yellow(\"Not currently logged in.\"));\n return;\n }\n\n const profileName = config.activeProfile;\n const { [profileName]: _, ...remainingProfiles } = config.profiles;\n\n // Pick the first remaining profile as active, or null\n const nextActive = Object.keys(remainingProfiles)[0] ?? null;\n\n writeConfig({\n ...config,\n activeProfile: nextActive,\n profiles: remainingProfiles,\n });\n\n console.log(\n chalk.green(`Logged out of profile ${chalk.bold(profileName)}.`),\n );\n if (nextActive) {\n console.log(chalk.dim(`Switched to profile ${nextActive}.`));\n }\n });\n","/**\n * fluid whoami — show current auth profile and validate against API\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { getActiveProfile } from \"../auth/token.js\";\nimport { validateToken } from \"../auth/fluid-api.js\";\n\nexport const whoamiCommand = new Command(\"whoami\")\n .description(\"Show the current authenticated profile\")\n .action(async () => {\n const profile = getActiveProfile();\n\n if (!profile) {\n console.log(\n chalk.yellow(\"Not logged in. Run `fluid login` to authenticate.\"),\n );\n process.exitCode = 1;\n return;\n }\n\n const spinner = ora(\"Verifying token...\").start();\n const result = await validateToken(profile.token);\n\n if (!result.success) {\n spinner.fail(chalk.red(\"Token is no longer valid.\"));\n console.log(chalk.dim(`Profile: ${profile.name}`));\n console.log(chalk.dim(\"Run `fluid login` to re-authenticate.\"));\n process.exitCode = 1;\n return;\n }\n\n spinner.succeed(chalk.green(\"Authenticated\"));\n console.log(` Profile: ${chalk.bold(profile.name)}`);\n console.log(` Company: ${chalk.bold(result.value.name)}`);\n console.log(` Stored: ${chalk.dim(profile.storedAt)}`);\n });\n","/**\n * fluid switch — switch between companies (fetched from API, with local fallback)\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport ora from \"ora\";\nimport {\n fetchUserCompanies,\n switchCompany,\n FLUID_API_ERROR,\n getFluidApiBase,\n type UserCompany,\n} from \"../auth/fluid-api.js\";\nimport { createFluidProfile } from \"../auth/profiles.js\";\nimport { getActiveProfile } from \"../auth/token.js\";\nimport { readConfig, updateConfig } from \"../config/config.js\";\n\nasync function localSwitch(profileArg?: string): Promise<void> {\n const config = readConfig();\n const profileNames = Object.keys(config.profiles);\n\n if (profileNames.length === 0) {\n console.log(chalk.yellow(\"No profiles stored. Run `fluid login` first.\"));\n process.exitCode = 1;\n return;\n }\n\n let targetProfile = profileArg;\n\n if (!targetProfile) {\n const profileChoices = profileNames.map((name) => ({\n title: name === config.activeProfile ? `${name} (active)` : name,\n value: name,\n }));\n\n const response = await prompts({\n type: \"autocomplete\",\n name: \"profile\",\n message: \"Select a profile (type to search)\",\n choices: profileChoices,\n suggest: (input, choices) =>\n Promise.resolve(\n input\n ? choices.filter((c) =>\n c.value.toLowerCase().includes(input.toLowerCase()),\n )\n : choices,\n ),\n });\n targetProfile = response[\"profile\"] as string | undefined;\n\n if (!targetProfile) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n }\n\n if (!config.profiles[targetProfile]) {\n console.log(chalk.red(`Profile \"${targetProfile}\" not found.`));\n console.log(chalk.dim(`Available: ${profileNames.join(\", \")}`));\n process.exitCode = 1;\n return;\n }\n\n updateConfig((c) => ({ ...c, activeProfile: targetProfile! }));\n console.log(chalk.green(`Switched to profile ${chalk.bold(targetProfile)}.`));\n}\n\nasync function selectCompany(\n companies: UserCompany[],\n activeCompanyName: string | undefined,\n): Promise<UserCompany | undefined> {\n const companyChoices = companies.map((c) => ({\n title:\n c.name === activeCompanyName\n ? `${c.name} ${chalk.dim(\"(active)\")}`\n : c.name,\n value: c.id,\n }));\n\n const response = await prompts({\n type: \"autocomplete\",\n name: \"companyId\",\n message: \"Select a company (type to search)\",\n choices: companyChoices,\n suggest: (input, choices) =>\n Promise.resolve(\n input\n ? choices.filter((c) =>\n c.title.toLowerCase().includes(input.toLowerCase()),\n )\n : choices,\n ),\n });\n\n const companyId = response[\"companyId\"] as number | undefined;\n if (companyId === undefined) return undefined;\n return companies.find((c) => c.id === companyId);\n}\n\nasync function performSwitch(\n token: string,\n company: UserCompany,\n baseUrl?: string,\n): Promise<void> {\n const spinner = ora(`Switching to ${chalk.bold(company.name)}...`).start();\n const result = await switchCompany(token, company.id, baseUrl);\n\n if (!result.success) {\n if (result.error.code === FLUID_API_ERROR.INVALID_TOKEN.code) {\n spinner.fail(\n chalk.red(\n \"Your session has expired. Please run `fluid login` to re-authenticate.\",\n ),\n );\n } else {\n spinner.fail(chalk.red(result.error.message));\n if (result.error.details) {\n console.log(chalk.dim(result.error.details));\n }\n }\n process.exitCode = 1;\n return;\n }\n\n const { companyName, jwt } = result.value;\n\n updateConfig((config) => ({\n ...config,\n activeProfile: companyName,\n profiles: {\n ...config.profiles,\n [companyName]: createFluidProfile({\n name: companyName,\n token: jwt,\n companyName,\n baseUrl: baseUrl ?? getFluidApiBase(),\n }),\n },\n }));\n\n spinner.succeed(\n chalk.green(\n `Switched to ${chalk.bold(companyName)} (profile: ${chalk.bold(companyName)})`,\n ),\n );\n}\n\nexport const switchCommand = new Command(\"switch\")\n .description(\"Switch between companies\")\n .argument(\"[profile]\", \"Company or profile name to switch to\")\n .action(async (profileArg?: string) => {\n const activeProfile = getActiveProfile();\n\n if (!activeProfile) {\n console.log(chalk.yellow(\"Not logged in. Run `fluid login` first.\"));\n process.exitCode = 1;\n return;\n }\n\n const token = activeProfile.token;\n const activeCompanyName = activeProfile.companyName;\n const activeBaseUrl = activeProfile.baseUrl ?? getFluidApiBase();\n\n // Fetch companies from API\n const spinner = ora(\"Fetching companies...\").start();\n const result = await fetchUserCompanies(token, activeBaseUrl);\n\n if (!result.success) {\n if (result.error.code === FLUID_API_ERROR.INVALID_TOKEN.code) {\n spinner.fail(\n chalk.red(\n \"Your session has expired. Please run `fluid login` to re-authenticate.\",\n ),\n );\n process.exitCode = 1;\n return;\n }\n spinner.warn(\n chalk.yellow(\n `Could not fetch companies from API: ${result.error.message}`,\n ),\n );\n console.log(chalk.dim(\"Falling back to locally stored profiles.\"));\n await localSwitch(profileArg);\n return;\n }\n\n const companies = result.value;\n spinner.succeed(\n `Found ${companies.length} company${companies.length === 1 ? \"\" : \"ies\"}`,\n );\n\n if (companies.length === 0) {\n console.log(chalk.yellow(\"No companies found for this account.\"));\n process.exitCode = 1;\n return;\n }\n\n // If a profile argument was passed, match against API companies first\n if (profileArg) {\n const match = companies.find(\n (c) => c.name.toLowerCase() === profileArg.toLowerCase(),\n );\n\n if (match) {\n await performSwitch(token, match, activeBaseUrl);\n return;\n }\n\n // Fall back to local profile matching\n const config = readConfig();\n if (config.profiles[profileArg]) {\n updateConfig((c) => ({ ...c, activeProfile: profileArg! }));\n console.log(\n chalk.green(`Switched to profile ${chalk.bold(profileArg)}.`),\n );\n return;\n }\n\n console.log(chalk.red(`Company or profile \"${profileArg}\" not found.`));\n process.exitCode = 1;\n return;\n }\n\n // Interactive selection\n const selected = await selectCompany(companies, activeCompanyName);\n if (!selected) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n\n await performSwitch(token, selected, activeBaseUrl);\n });\n","/**\n * Auto-discover @fluid-app CLI plugins.\n *\n * Three discovery strategies run in order:\n *\n * 1a. **node_modules scan (cwd)** — look in `<cwd>/node_modules/@fluid-app/`\n * for directories whose names match the plugin naming convention.\n * This is the primary mechanism for project-local plugin installs.\n *\n * 1b. **node_modules scan (CLI install location)** — look in the\n * `node_modules/@fluid-app/` directory containing the CLI core package\n * itself. This covers global installs where plugins are sibling packages\n * under the same global `node_modules/`.\n *\n * 2. **Workspace scan** — walk upward from the CLI core package root to the\n * monorepo workspace root, then scan `packages/` for sibling plugin\n * packages. This covers the pnpm-workspace development case where\n * plugins are not symlinked into `node_modules`.\n *\n * Only first-party @fluid-app scoped packages are loaded.\n */\n\nimport { existsSync, readFileSync, readdirSync, realpathSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\n\nconst PLUGIN_SCOPE = \"@fluid-app\";\n\n/**\n * A plugin package name must match one of these patterns:\n * - @fluid-app/fluid-cli-* (standard)\n * - @fluid-app/*-cli-commands (v2025-06 style)\n */\nfunction isPluginName(packageName: string): boolean {\n if (!packageName.startsWith(`${PLUGIN_SCOPE}/`)) return false;\n const bare = packageName.slice(`${PLUGIN_SCOPE}/`.length);\n return bare.startsWith(\"fluid-cli-\") || bare.endsWith(\"-cli-commands\");\n}\n\n// ---------------------------------------------------------------------------\n// Strategy 1: node_modules scan (production / published installs)\n// ---------------------------------------------------------------------------\n\nfunction discoverFromNodeModules(basePath: string): DiscoveredPlugin[] {\n const scopeDir = join(basePath, \"node_modules\", PLUGIN_SCOPE);\n\n if (!existsSync(scopeDir)) return [];\n\n return readdirSync(scopeDir, { withFileTypes: true })\n .filter(\n (entry) =>\n (entry.isDirectory() || entry.isSymbolicLink()) &&\n isPluginName(`${PLUGIN_SCOPE}/${entry.name}`),\n )\n .map((entry) => {\n const name = `${PLUGIN_SCOPE}/${entry.name}`;\n const entryDir = join(scopeDir, entry.name);\n // Resolve symlinks (pnpm workspace links) to file:// URLs so the\n // dynamic import works regardless of pnpm's strict module resolution.\n try {\n const realDir = realpathSync(entryDir);\n const distEntry = join(realDir, \"dist\", \"index.mjs\");\n if (existsSync(distEntry)) {\n return { name, importSpecifier: pathToFileURL(distEntry).href };\n }\n } catch {\n // Broken symlink or other fs error — fall through to bare specifier\n }\n // Fallback to bare specifier for non-workspace (published) installs\n return { name, importSpecifier: name };\n });\n}\n\n// ---------------------------------------------------------------------------\n// Strategy 2: workspace scan (pnpm monorepo development)\n// ---------------------------------------------------------------------------\n\nfunction findWorkspaceRoot(startDir: string): string | null {\n let dir = startDir;\n while (true) {\n if (\n existsSync(join(dir, \"pnpm-workspace.yaml\")) ||\n existsSync(join(dir, \"pnpm-workspace.yml\"))\n ) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nfunction readPackageName(dir: string): string | null {\n const pkgPath = join(dir, \"package.json\");\n if (!existsSync(pkgPath)) return null;\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as {\n name?: string;\n };\n return pkg.name ?? null;\n } catch {\n return null;\n }\n}\n\nexport interface DiscoveredPlugin {\n name: string;\n importSpecifier: string;\n}\n\nfunction discoverFromWorkspace(startDir: string): DiscoveredPlugin[] {\n const workspaceRoot = findWorkspaceRoot(startDir);\n if (!workspaceRoot) return [];\n\n const results: DiscoveredPlugin[] = [];\n const packagesDir = join(workspaceRoot, \"packages\");\n if (!existsSync(packagesDir)) return [];\n\n // Scan exactly two levels deep under packages/ (e.g. packages/cli/themes,\n // packages/cli/v2025-06). This matches the monorepo convention where all\n // packages live at packages/<domain>/<package>/.\n // If a plugin is added at a different depth, update this scan accordingly.\n let domainDirs: string[];\n try {\n domainDirs = readdirSync(packagesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => join(packagesDir, e.name));\n } catch {\n return [];\n }\n\n for (const domainDir of domainDirs) {\n let subDirs: string[];\n try {\n subDirs = readdirSync(domainDir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => join(domainDir, e.name));\n } catch {\n continue;\n }\n\n for (const subDir of subDirs) {\n const name = readPackageName(subDir);\n if (!name || !name.startsWith(`${PLUGIN_SCOPE}/`) || !isPluginName(name))\n continue;\n\n const distEntry = join(subDir, \"dist\", \"index.mjs\");\n if (!existsSync(distEntry)) {\n continue;\n }\n\n results.push({\n name,\n importSpecifier: pathToFileURL(distEntry).href,\n });\n }\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Exported API\n// ---------------------------------------------------------------------------\n\n/** Resolved once — the CLI core package root (`packages/cli/core/`). */\nexport const corePackageDir = dirname(\n dirname(dirname(fileURLToPath(import.meta.url))),\n);\n\n/**\n * Discover installed plugin packages. Returns a de-duplicated, sorted list\n * of `{ name, importSpecifier }` objects.\n *\n * @param basePath - Directory to scan for `node_modules/@fluid-app/`\n * @param searchFrom - Starting directory for workspace root detection\n * (walked upward via `findWorkspaceRoot`). Pass `null` to skip workspace\n * scanning (useful in tests). Defaults to `corePackageDir`.\n */\nexport function discoverPlugins(\n basePath: string,\n searchFrom?: string | null,\n): DiscoveredPlugin[] {\n const seen = new Set<string>();\n const results: DiscoveredPlugin[] = [];\n\n // Strategy 1a: node_modules relative to cwd (project-local plugins)\n for (const plugin of discoverFromNodeModules(basePath)) {\n if (!seen.has(plugin.name)) {\n seen.add(plugin.name);\n results.push(plugin);\n }\n }\n\n // Strategy 1b: node_modules relative to the CLI's own install location\n // (for global installs where plugins are siblings under the same\n // node_modules/@fluid-app/ directory)\n const cliParent = dirname(dirname(dirname(corePackageDir)));\n if (cliParent !== basePath) {\n for (const plugin of discoverFromNodeModules(cliParent)) {\n if (!seen.has(plugin.name)) {\n seen.add(plugin.name);\n results.push(plugin);\n }\n }\n }\n\n // Strategy 2: workspace siblings\n if (searchFrom !== null) {\n const wsDir = searchFrom ?? corePackageDir;\n for (const wp of discoverFromWorkspace(wsDir)) {\n if (!seen.has(wp.name)) {\n seen.add(wp.name);\n results.push({ name: wp.name, importSpecifier: wp.importSpecifier });\n }\n }\n }\n\n return results.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Dynamically import a plugin module and return its default export.\n *\n * @internal Not intended for external callers. Only called with specifiers\n * produced by {@link discoverPlugins}:\n * - bare package names (e.g. `@fluid-app/fluid-cli-portal`) from the\n * node_modules scan, validated by {@link isPluginName}.\n * - `file://` URLs from {@link discoverFromWorkspace}, which constructs\n * them internally from validated workspace paths (`packages/` tree,\n * `dist/index.mjs`). These are trusted internal specifiers.\n */\nexport async function importPlugin(specifier: string): Promise<unknown> {\n // file:// URLs are trusted — they originate from discoverFromWorkspace\n // which builds them from validated workspace paths under packages/.\n // For bare package names, verify they match the plugin naming convention.\n if (!specifier.startsWith(\"file://\") && !isPluginName(specifier)) {\n throw new Error(`Refusing to import non-plugin package: ${specifier}`);\n }\n const mod = (await import(specifier)) as Record<string, unknown>;\n return mod[\"default\"] ?? mod;\n}\n","/**\n * Plugin loader — orchestrates discovery and registration\n */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { getConfigDir } from \"../config/paths.js\";\nimport { getAuthToken } from \"../auth/token.js\";\nimport { readConfig } from \"../config/config.js\";\nimport type { FluidPlugin, PluginContext } from \"./types.js\";\nimport { discoverPlugins, importPlugin } from \"./discovery.js\";\n\nfunction isFluidPlugin(value: unknown): value is FluidPlugin {\n if (typeof value !== \"object\" || value === null) return false;\n const obj = value as Record<string, unknown>;\n return (\n typeof obj[\"name\"] === \"string\" &&\n typeof obj[\"version\"] === \"string\" &&\n typeof obj[\"register\"] === \"function\"\n );\n}\n\n/**\n * Discover, import, and register all installed plugins\n */\nexport async function loadPlugins(\n program: Command,\n basePath: string,\n): Promise<void> {\n const discovered = discoverPlugins(basePath);\n const { enabledPlugins } = readConfig();\n\n // If enabledPlugins is set, only load explicitly allowed plugins.\n // This acts as an allow-list to prevent accidental supply-chain execution.\n const allowedSet = enabledPlugins !== null ? new Set(enabledPlugins) : null;\n const plugins = allowedSet\n ? discovered.filter((p) => allowedSet.has(p.name))\n : discovered;\n\n if (plugins.length === 0) return;\n\n const ctx: PluginContext = {\n program,\n getAuthToken,\n configDir: getConfigDir(),\n };\n\n for (const { name, importSpecifier } of plugins) {\n try {\n const exported = await importPlugin(importSpecifier);\n\n if (!isFluidPlugin(exported)) {\n console.warn(\n chalk.yellow(`Warning: ${name} does not export a valid FluidPlugin`),\n );\n continue;\n }\n\n await exported.register(ctx);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(\n chalk.yellow(`Warning: Failed to load plugin ${name}: ${message}`),\n );\n }\n }\n}\n","#!/usr/bin/env node\n\nimport { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { loginCommand } from \"../commands/login.js\";\nimport { logoutCommand } from \"../commands/logout.js\";\nimport { whoamiCommand } from \"../commands/whoami.js\";\nimport { switchCommand } from \"../commands/switch.js\";\nimport { loadPlugins } from \"../plugins/loader.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../../package.json\") as { version: string };\n\nconst packageRoot = process.cwd();\n\nconst program = new Command();\n\nprogram.name(\"fluid\").description(\"Fluid Commerce CLI\").version(version);\n\n// Built-in auth commands\nprogram.addCommand(loginCommand);\nprogram.addCommand(logoutCommand);\nprogram.addCommand(whoamiCommand);\nprogram.addCommand(switchCommand);\n\n// Discover and load all plugins (auto-discovered from node_modules and workspace)\nawait loadPlugins(program, packageRoot);\n\nprogram.parse();\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -9,6 +9,7 @@ interface FluidProfile {
|
|
|
9
9
|
readonly token: string;
|
|
10
10
|
readonly companyName: string;
|
|
11
11
|
readonly storedAt: string;
|
|
12
|
+
readonly baseUrl?: string;
|
|
12
13
|
}
|
|
13
14
|
interface FluidConfig {
|
|
14
15
|
activeProfile: string | null;
|
|
@@ -115,6 +116,7 @@ declare function isNodeError(value: unknown): value is NodeJS.ErrnoException;
|
|
|
115
116
|
declare function getErrorMessage(error: unknown): string;
|
|
116
117
|
//#endregion
|
|
117
118
|
//#region src/auth/fluid-api.d.ts
|
|
119
|
+
declare function getFluidApiBase(): string;
|
|
118
120
|
interface FluidApiError extends CliError {
|
|
119
121
|
readonly code: string;
|
|
120
122
|
readonly message: string;
|
|
@@ -163,14 +165,14 @@ declare function confirmMfa(uuid: string, code: string): Promise<Result<ConfirmM
|
|
|
163
165
|
/**
|
|
164
166
|
* Fetch all companies the authenticated user has access to.
|
|
165
167
|
*/
|
|
166
|
-
declare function fetchUserCompanies(token: string): Promise<Result<UserCompany[], FluidApiError>>;
|
|
168
|
+
declare function fetchUserCompanies(token: string, baseUrl?: string): Promise<Result<UserCompany[], FluidApiError>>;
|
|
167
169
|
/**
|
|
168
170
|
* Switch to a different company and receive a new JWT.
|
|
169
171
|
*
|
|
170
172
|
* Response shape (from @fluid-app/auth SwitchCompanyResponse):
|
|
171
173
|
* { company: { id, name, fluid_shop, jwt }, meta: { request_id, timestamp } }
|
|
172
174
|
*/
|
|
173
|
-
declare function switchCompany(token: string, companyId: number): Promise<Result<SwitchCompanyResult, FluidApiError>>;
|
|
175
|
+
declare function switchCompany(token: string, companyId: number, baseUrl?: string): Promise<Result<SwitchCompanyResult, FluidApiError>>;
|
|
174
176
|
//#endregion
|
|
175
177
|
//#region src/auth/token.d.ts
|
|
176
178
|
/**
|
|
@@ -215,6 +217,7 @@ interface RequestOptions {
|
|
|
215
217
|
params?: Record<string, unknown>;
|
|
216
218
|
body?: unknown;
|
|
217
219
|
signal?: AbortSignal;
|
|
220
|
+
priority?: RequestInit["priority"];
|
|
218
221
|
}
|
|
219
222
|
interface FetchClientInstance {
|
|
220
223
|
request: <TResponse = unknown>(endpoint: string, options?: RequestOptions) => Promise<TResponse>;
|
|
@@ -260,5 +263,5 @@ declare function createCommandContext(opts: {
|
|
|
260
263
|
//#region src/domain/output.d.ts
|
|
261
264
|
declare function formatOutput(data: unknown, options: OutputOptions): string;
|
|
262
265
|
//#endregion
|
|
263
|
-
export { type CliError, type CommandContext, type CompanyChoice, type CompanyInfo, type ConfirmMfaResponse, type Failure, type FetchClient, type FluidApiError, type FluidConfig, type FluidPlugin, type FluidProfile, type MfaResponse, type OutputOptions, type PluginContext, type ProjectRcConfig, type Result, type Success, type SwitchCompanyResult, type UserCompany, confirmMfa, createCommandContext, createDefaultConfig, createDomainCommand, failure, fetchUserCompanies, findProjectConfig, formatOutput, getActiveProfile, getAuthToken, getConfigDir, getConfigFilePath, getErrorMessage, isError, isFailure, isNodeError, isSuccess, listProfileNames, mapError, mapResult, readConfig, sendMfa, success, switchCompany, tryCatch, tryCatchAsync, unwrap, unwrapOr, updateConfig, validateToken, writeConfig };
|
|
266
|
+
export { type CliError, type CommandContext, type CompanyChoice, type CompanyInfo, type ConfirmMfaResponse, type Failure, type FetchClient, type FluidApiError, type FluidConfig, type FluidPlugin, type FluidProfile, type MfaResponse, type OutputOptions, type PluginContext, type ProjectRcConfig, type Result, type Success, type SwitchCompanyResult, type UserCompany, confirmMfa, createCommandContext, createDefaultConfig, createDomainCommand, failure, fetchUserCompanies, findProjectConfig, formatOutput, getActiveProfile, getAuthToken, getConfigDir, getConfigFilePath, getErrorMessage, getFluidApiBase, isError, isFailure, isNodeError, isSuccess, listProfileNames, mapError, mapResult, readConfig, sendMfa, success, switchCompany, tryCatch, tryCatchAsync, unwrap, unwrapOr, updateConfig, validateToken, writeConfig };
|
|
264
267
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/config/types.ts","../src/config/paths.ts","../src/config/config.ts","../src/config/project-config.ts","../src/utils/errors.ts","../src/utils/result.ts","../src/auth/fluid-api.ts","../src/auth/token.ts","../src/plugins/types.ts","../../../platform/api-client-core/src/fetch-client.ts","../src/domain/types.ts","../src/domain/command.ts","../src/domain/context.ts","../src/domain/output.ts"],"mappings":";;;;;;UAIiB,YAAA;EAAA,SACN,IAAA;EAAA,SACA,KAAA;EAAA,SACA,WAAA;EAAA,SACA,QAAA;AAAA;AAAA,UAGM,WAAA;EACf,aAAA;EACA,QAAA,EAAU,MAAA,SAAe,YAAA;EACzB,OAAA,EAAS,MAAA;EAHM;;;;;;;;;;EAcf,cAAA;AAAA;AAAA,iBAGc,mBAAA,CAAA,GAAuB,WAAA;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/config/types.ts","../src/config/paths.ts","../src/config/config.ts","../src/config/project-config.ts","../src/utils/errors.ts","../src/utils/result.ts","../src/auth/fluid-api.ts","../src/auth/token.ts","../src/plugins/types.ts","../../../platform/api-client-core/src/fetch-client.ts","../src/domain/types.ts","../src/domain/command.ts","../src/domain/context.ts","../src/domain/output.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { A as unwrapOr, C as isSuccess, D as tryCatch, E as success, O as tryCatchAsync, S as isNodeError, T as mapResult, _ as validateToken, a as readConfig, b as isError, c as getConfigDir, f as confirmMfa, g as switchCompany, h as sendMfa, i as findProjectConfig, k as unwrap, l as getConfigFilePath, m as getFluidApiBase, n as getAuthToken, o as updateConfig, p as fetchUserCompanies, r as listProfileNames, s as writeConfig, t as getActiveProfile, u as createDefaultConfig, v as failure, w as mapError, x as isFailure, y as getErrorMessage } from "./token-D4DJFSdm.mjs";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
//#region ../../platform/api-client-core/src/fetch-client.ts
|
|
4
4
|
/**
|
|
@@ -7,11 +7,13 @@ import { Command } from "commander";
|
|
|
7
7
|
var ApiError = class ApiError extends Error {
|
|
8
8
|
status;
|
|
9
9
|
data;
|
|
10
|
-
|
|
10
|
+
requestId;
|
|
11
|
+
constructor(message, status, data, requestId) {
|
|
11
12
|
super(message);
|
|
12
13
|
this.name = "ApiError";
|
|
13
14
|
this.status = status;
|
|
14
15
|
this.data = data;
|
|
16
|
+
this.requestId = requestId;
|
|
15
17
|
if ("captureStackTrace" in Error) Error.captureStackTrace(this, ApiError);
|
|
16
18
|
}
|
|
17
19
|
toJSON() {
|
|
@@ -19,15 +21,32 @@ var ApiError = class ApiError extends Error {
|
|
|
19
21
|
name: this.name,
|
|
20
22
|
message: this.message,
|
|
21
23
|
status: this.status,
|
|
22
|
-
data: this.data
|
|
24
|
+
data: this.data,
|
|
25
|
+
requestId: this.requestId
|
|
23
26
|
};
|
|
24
27
|
}
|
|
25
28
|
};
|
|
29
|
+
function getStringRequestId(value) {
|
|
30
|
+
if (typeof value !== "string") return;
|
|
31
|
+
const trimmed = value.trim();
|
|
32
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
33
|
+
}
|
|
34
|
+
function getRequestIdFromHeaders(headers) {
|
|
35
|
+
return getStringRequestId(headers.get("x-request-id")) ?? getStringRequestId(headers.get("request-id")) ?? getStringRequestId(headers.get("X-Request-ID"));
|
|
36
|
+
}
|
|
37
|
+
function getRequestIdFromJsonBody(body) {
|
|
38
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return;
|
|
39
|
+
const record = body;
|
|
40
|
+
const meta = record.meta;
|
|
41
|
+
return getStringRequestId(record.request_id) ?? getStringRequestId(record.requestId) ?? (meta && typeof meta === "object" && !Array.isArray(meta) ? getStringRequestId(meta.request_id) ?? getStringRequestId(meta.requestId) : void 0);
|
|
42
|
+
}
|
|
26
43
|
/**
|
|
27
44
|
* Creates a configured fetch client instance
|
|
28
45
|
*/
|
|
29
46
|
function createFetchClient(config) {
|
|
30
|
-
const { baseUrl, getAuthToken, onAuthError, defaultHeaders = {}, credentials } = config;
|
|
47
|
+
const { baseUrl, getAuthToken, onAuthError, defaultHeaders = {}, credentials, cache, networkRetry, throwOnInvalidJson = false } = config;
|
|
48
|
+
const maxNetworkRetries = Math.max(0, networkRetry?.maxRetries ?? 0);
|
|
49
|
+
const baseNetworkRetryDelayMs = Math.max(0, networkRetry?.baseDelayMs ?? 0);
|
|
31
50
|
/**
|
|
32
51
|
* Build headers for a request
|
|
33
52
|
*/
|
|
@@ -78,6 +97,7 @@ function createFetchClient(config) {
|
|
|
78
97
|
* Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.
|
|
79
98
|
*/
|
|
80
99
|
async function handleResponse(response, method, _url) {
|
|
100
|
+
const headerRequestId = getRequestIdFromHeaders(response.headers);
|
|
81
101
|
if (response.status === 401 && onAuthError) onAuthError();
|
|
82
102
|
if (!response.ok) {
|
|
83
103
|
const errorText = await response.text().catch(() => "");
|
|
@@ -86,29 +106,48 @@ function createFetchClient(config) {
|
|
|
86
106
|
try {
|
|
87
107
|
data = JSON.parse(errorText);
|
|
88
108
|
} catch {
|
|
89
|
-
throw new ApiError(errorText.slice(0, 200) || `${method} request failed with status ${response.status}`, response.status, null);
|
|
109
|
+
throw new ApiError(errorText.slice(0, 200) || `${method} request failed with status ${response.status}`, response.status, null, headerRequestId);
|
|
90
110
|
}
|
|
91
111
|
const nestedError = typeof data.error === "object" && data.error !== null ? data.error.message : void 0;
|
|
92
|
-
throw new ApiError(data.message || data.error_message || (typeof nestedError === "string" ? nestedError : void 0) || `${method} request failed`, response.status, data.errors || data);
|
|
93
|
-
} else throw new ApiError(`${method} request failed with status ${response.status}`, response.status, null);
|
|
112
|
+
throw new ApiError(data.message || data.error_message || (typeof nestedError === "string" ? nestedError : void 0) || `${method} request failed`, response.status, data.errors || data, headerRequestId ?? getRequestIdFromJsonBody(data));
|
|
113
|
+
} else throw new ApiError(`${method} request failed with status ${response.status}`, response.status, null, headerRequestId);
|
|
94
114
|
}
|
|
95
115
|
if (response.status === 204 || response.headers.get("content-length") === "0") return null;
|
|
96
|
-
if (response.headers.get("content-type")?.includes("application/json"))
|
|
97
|
-
|
|
98
|
-
} catch {
|
|
116
|
+
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
117
|
+
const responseText = await response.text();
|
|
99
118
|
try {
|
|
100
|
-
return
|
|
119
|
+
return JSON.parse(responseText);
|
|
101
120
|
} catch {
|
|
102
|
-
|
|
121
|
+
if (throwOnInvalidJson) throw new ApiError("Failed to parse response as JSON", response.status, null, headerRequestId);
|
|
122
|
+
return responseText ? responseText : null;
|
|
103
123
|
}
|
|
104
124
|
}
|
|
105
125
|
return null;
|
|
106
126
|
}
|
|
127
|
+
function getNetworkRetryDelayMs(retryAttempt) {
|
|
128
|
+
return baseNetworkRetryDelayMs * 2 ** (retryAttempt - 1);
|
|
129
|
+
}
|
|
130
|
+
async function waitForNetworkRetry(retryAttempt) {
|
|
131
|
+
const delayMs = getNetworkRetryDelayMs(retryAttempt);
|
|
132
|
+
if (delayMs <= 0) return;
|
|
133
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
134
|
+
}
|
|
135
|
+
async function fetchWithNetworkRetry(url, fetchOptions, signal) {
|
|
136
|
+
let retryCount = 0;
|
|
137
|
+
while (true) try {
|
|
138
|
+
return await fetch(url, fetchOptions);
|
|
139
|
+
} catch (networkError) {
|
|
140
|
+
if (signal?.aborted || retryCount >= maxNetworkRetries) throw networkError;
|
|
141
|
+
retryCount += 1;
|
|
142
|
+
await waitForNetworkRetry(retryCount);
|
|
143
|
+
if (signal?.aborted) throw networkError;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
107
146
|
/**
|
|
108
147
|
* Main request function
|
|
109
148
|
*/
|
|
110
149
|
async function request(endpoint, options = {}) {
|
|
111
|
-
const { method = "GET", headers: customHeaders, params, body, signal } = options;
|
|
150
|
+
const { method = "GET", headers: customHeaders, params, body, signal, priority } = options;
|
|
112
151
|
const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);
|
|
113
152
|
const headers = await buildHeaders(customHeaders);
|
|
114
153
|
let response;
|
|
@@ -118,10 +157,12 @@ function createFetchClient(config) {
|
|
|
118
157
|
headers
|
|
119
158
|
};
|
|
120
159
|
if (credentials) fetchOptions.credentials = credentials;
|
|
160
|
+
if (cache) fetchOptions.cache = cache;
|
|
161
|
+
if (priority) fetchOptions.priority = priority;
|
|
121
162
|
const serializedBody = body && method !== "GET" ? JSON.stringify(body) : null;
|
|
122
163
|
if (serializedBody) fetchOptions.body = serializedBody;
|
|
123
164
|
if (signal) fetchOptions.signal = signal;
|
|
124
|
-
response = await
|
|
165
|
+
response = await fetchWithNetworkRetry(url, fetchOptions, signal);
|
|
125
166
|
} catch (networkError) {
|
|
126
167
|
throw new ApiError(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
|
|
127
168
|
}
|
|
@@ -131,7 +172,7 @@ function createFetchClient(config) {
|
|
|
131
172
|
* Request with FormData (for file uploads)
|
|
132
173
|
*/
|
|
133
174
|
async function requestWithFormData(endpoint, formData, options = {}) {
|
|
134
|
-
const { method = "POST", headers: customHeaders, signal } = options;
|
|
175
|
+
const { method = "POST", headers: customHeaders, signal, priority } = options;
|
|
135
176
|
const url = joinUrl(endpoint);
|
|
136
177
|
const headers = await buildHeaders(customHeaders);
|
|
137
178
|
delete headers["Content-Type"];
|
|
@@ -143,8 +184,10 @@ function createFetchClient(config) {
|
|
|
143
184
|
body: formData
|
|
144
185
|
};
|
|
145
186
|
if (credentials) fetchOptions.credentials = credentials;
|
|
187
|
+
if (cache) fetchOptions.cache = cache;
|
|
188
|
+
if (priority) fetchOptions.priority = priority;
|
|
146
189
|
if (signal) fetchOptions.signal = signal;
|
|
147
|
-
response = await
|
|
190
|
+
response = await fetchWithNetworkRetry(url, fetchOptions, signal);
|
|
148
191
|
} catch (networkError) {
|
|
149
192
|
throw new ApiError(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
|
|
150
193
|
}
|
|
@@ -304,6 +347,6 @@ function createDomainCommand(name, description, register) {
|
|
|
304
347
|
return cmd;
|
|
305
348
|
}
|
|
306
349
|
//#endregion
|
|
307
|
-
export { confirmMfa, createCommandContext, createDefaultConfig, createDomainCommand, failure, fetchUserCompanies, findProjectConfig, formatOutput, getActiveProfile, getAuthToken, getConfigDir, getConfigFilePath, getErrorMessage, isError, isFailure, isNodeError, isSuccess, listProfileNames, mapError, mapResult, readConfig, sendMfa, success, switchCompany, tryCatch, tryCatchAsync, unwrap, unwrapOr, updateConfig, validateToken, writeConfig };
|
|
350
|
+
export { confirmMfa, createCommandContext, createDefaultConfig, createDomainCommand, failure, fetchUserCompanies, findProjectConfig, formatOutput, getActiveProfile, getAuthToken, getConfigDir, getConfigFilePath, getErrorMessage, getFluidApiBase, isError, isFailure, isNodeError, isSuccess, listProfileNames, mapError, mapResult, readConfig, sendMfa, success, switchCompany, tryCatch, tryCatchAsync, unwrap, unwrapOr, updateConfig, validateToken, writeConfig };
|
|
308
351
|
|
|
309
352
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../platform/api-client-core/src/fetch-client.ts","../src/domain/output.ts","../src/domain/context.ts","../src/domain/command.ts"],"sourcesContent":["/**\n * Minimal, framework-agnostic fetch client for Fluid APIs\n * Compatible with fluid-admin patterns but usable standalone\n */\n\nexport interface FetchClientConfig {\n /**\n * Base URL for all requests (e.g., \"https://api.fluid.app/api\")\n */\n baseUrl: string;\n\n /**\n * Optional function to get auth token\n * Return null/undefined if no token available\n */\n getAuthToken?: () => string | null | Promise<string | null>;\n\n /**\n * Optional callback when 401 auth error occurs\n */\n onAuthError?: () => void;\n\n /**\n * Default headers to include in all requests\n * Example: { \"x-fluid-client\": \"admin\" }\n */\n defaultHeaders?: Record<string, string>;\n\n /**\n * Credentials mode for fetch requests.\n * Set to `\"include\"` for cookie-based (same-origin BFF) authentication.\n * @default undefined (browser default: \"same-origin\")\n */\n credentials?: RequestCredentials;\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n body?: unknown;\n signal?: AbortSignal;\n}\n\n/**\n * API Error class compatible with fluid-admin's ApiError\n */\nexport class ApiError extends Error {\n public readonly status: number;\n public readonly data: unknown;\n\n constructor(message: string, status: number, data?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.data = data;\n\n if (\"captureStackTrace\" in Error) {\n (\n Error as {\n captureStackTrace: (\n target: Error,\n constructor: NewableFunction,\n ) => void;\n }\n ).captureStackTrace(this, ApiError);\n }\n }\n\n toJSON(): { name: string; message: string; status: number; data: unknown } {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n data: this.data,\n };\n }\n}\n\n/**\n * Type guard for ApiError\n */\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n\nexport interface FetchClientInstance {\n request: <TResponse = unknown>(\n endpoint: string,\n options?: RequestOptions,\n ) => Promise<TResponse>;\n requestWithFormData: <TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options?: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n },\n ) => Promise<TResponse>;\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ) => Promise<TResponse>;\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ) => Promise<TResponse>;\n}\n\n/**\n * Creates a configured fetch client instance\n */\nexport function createFetchClient(\n config: FetchClientConfig,\n): FetchClientInstance {\n const {\n baseUrl,\n getAuthToken,\n onAuthError,\n defaultHeaders = {},\n credentials,\n } = config;\n\n /**\n * Build headers for a request\n */\n async function buildHeaders(\n customHeaders?: Record<string, string>,\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...defaultHeaders,\n ...customHeaders,\n };\n\n // Add auth token if available\n if (getAuthToken) {\n const token = await getAuthToken();\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n /**\n * Join baseUrl + endpoint via string concatenation (matches fetchApi).\n * Using `new URL(endpoint, baseUrl)` would strip any path prefix from\n * baseUrl (e.g. \"/api\") when the endpoint starts with \"/\".\n */\n function joinUrl(endpoint: string): string {\n return `${baseUrl}${endpoint}`;\n }\n\n /**\n * Build URL with query parameters for GET requests\n * Compatible with fluid-admin's query param handling\n */\n function buildUrl(\n endpoint: string,\n params?: Record<string, unknown>,\n ): string {\n const fullUrl = joinUrl(endpoint);\n\n if (!params || Object.keys(params).length === 0) {\n return fullUrl;\n }\n\n const queryString = new URLSearchParams();\n\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) {\n return; // Skip undefined/null values\n }\n\n if (Array.isArray(value)) {\n // Handle arrays like Rails expects: key[]\n value.forEach((item) => queryString.append(`${key}[]`, String(item)));\n } else if (typeof value === \"object\") {\n // Handle nested objects: key[subkey]\n Object.entries(value).forEach(([subKey, subValue]) => {\n if (subValue === undefined || subValue === null) {\n return;\n }\n\n if (Array.isArray(subValue)) {\n subValue.forEach((item) =>\n queryString.append(`${key}[${subKey}][]`, String(item)),\n );\n } else {\n queryString.append(`${key}[${subKey}]`, String(subValue));\n }\n });\n } else {\n queryString.append(key, String(value));\n }\n });\n\n const qs = queryString.toString();\n return qs ? `${fullUrl}?${qs}` : fullUrl;\n }\n\n /**\n * Shared response handler for both JSON and FormData requests.\n * Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.\n */\n async function handleResponse<TResponse>(\n response: Response,\n method: string,\n _url: string,\n ): Promise<TResponse> {\n if (response.status === 401 && onAuthError) {\n onAuthError();\n }\n\n if (!response.ok) {\n // Read body as text first to avoid SyntaxError from response.json()\n // when server returns non-JSON bodies with application/json content-type.\n const errorText = await response.text().catch(() => \"\");\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(errorText);\n } catch {\n throw new ApiError(\n errorText.slice(0, 200) ||\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n );\n }\n // Some Rails BFF endpoints return `{ error: { message, details } }`\n // instead of `{ message }` or `{ error_message }`. Fall through to\n // that shape last so callers always surface the real reason.\n const nestedError =\n typeof data.error === \"object\" && data.error !== null\n ? (data.error as { message?: unknown }).message\n : undefined;\n const msg =\n (data.message as string | undefined) ||\n (data.error_message as string | undefined) ||\n (typeof nestedError === \"string\" ? nestedError : undefined);\n throw new ApiError(\n msg || `${method} request failed`,\n response.status,\n data.errors || data,\n );\n } else {\n throw new ApiError(\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n );\n }\n }\n\n if (\n response.status === 204 ||\n response.headers.get(\"content-length\") === \"0\"\n ) {\n return null as TResponse;\n }\n\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n try {\n const data = await response.json();\n return data as TResponse;\n } catch {\n try {\n // API declared JSON content-type but body isn't valid JSON\n const text = await response.text();\n return text as TResponse;\n } catch {\n return null as TResponse;\n }\n }\n }\n\n // Non-JSON response (text/plain, text/html, etc.)\n return null as TResponse;\n }\n\n /**\n * Main request function\n */\n async function request<TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> {\n const {\n method = \"GET\",\n headers: customHeaders,\n params,\n body,\n signal,\n } = options;\n\n const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);\n\n const headers = await buildHeaders(customHeaders);\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers };\n if (credentials) fetchOptions.credentials = credentials;\n const serializedBody =\n body && method !== \"GET\" ? JSON.stringify(body) : null;\n if (serializedBody) fetchOptions.body = serializedBody;\n if (signal) fetchOptions.signal = signal;\n response = await fetch(url, fetchOptions);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n /**\n * Request with FormData (for file uploads)\n */\n async function requestWithFormData<TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n } = {},\n ): Promise<TResponse> {\n const { method = \"POST\", headers: customHeaders, signal } = options;\n\n const url = joinUrl(endpoint);\n const headers = await buildHeaders(customHeaders);\n\n // Remove Content-Type to let browser set it with boundary\n delete headers[\"Content-Type\"];\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers, body: formData };\n if (credentials) fetchOptions.credentials = credentials;\n if (signal) fetchOptions.signal = signal;\n response = await fetch(url, fetchOptions);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n // Return client with convenience methods\n return {\n request: request,\n requestWithFormData: requestWithFormData,\n\n // Convenience methods for common HTTP verbs\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"GET\" as const,\n ...(params && { params }),\n }),\n\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"POST\",\n body,\n }),\n\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PUT\",\n body,\n }),\n\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PATCH\",\n body,\n }),\n\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"DELETE\",\n }),\n };\n}\n\nexport type FetchClient = FetchClientInstance;\n","import type { OutputOptions } from \"./types.js\";\n\nexport function formatOutput(data: unknown, options: OutputOptions): string {\n let result = data;\n\n if (options.jq) {\n result = extractPath(result, options.jq);\n }\n\n if (options.format === \"table\") {\n return formatTable(result);\n }\n\n if (options.compact) {\n return JSON.stringify(result);\n }\n return JSON.stringify(result, null, 2);\n}\n\nfunction extractPath(data: unknown, path: string): unknown {\n const parts = path\n .replace(/^\\.*/, \"\")\n .split(/\\.|\\[/)\n .filter(Boolean)\n .map((p) => p.replace(/\\]$/, \"\"));\n\n let current: any = data;\n for (const part of parts) {\n if (current == null) return undefined;\n current = current[part];\n }\n return current;\n}\n\nfunction formatTable(data: unknown): string {\n if (Array.isArray(data)) {\n if (data.length === 0) return \"(empty)\";\n\n if (typeof data[0] === \"object\" && data[0] !== null) {\n const keys = Object.keys(data[0]);\n const widths = keys.map((k) =>\n Math.max(k.length, ...data.map((row) => String(row[k] ?? \"\").length)),\n );\n\n const header = keys\n .map((k, i) => k.toUpperCase().padEnd(widths[i]!))\n .join(\" \");\n const separator = widths.map((w) => \"-\".repeat(w)).join(\" \");\n const rows = data.map((row) =>\n keys.map((k, i) => String(row[k] ?? \"\").padEnd(widths[i]!)).join(\" \"),\n );\n\n return [header, separator, ...rows].join(\"\\n\");\n }\n\n return data.map(String).join(\"\\n\");\n }\n\n if (typeof data === \"object\" && data !== null) {\n const entries = Object.entries(data);\n const maxKeyLen = Math.max(...entries.map(([k]) => k.length));\n return entries\n .map(([k, v]) => `${k.padEnd(maxKeyLen)} ${JSON.stringify(v)}`)\n .join(\"\\n\");\n }\n\n return String(data);\n}\n","import { createFetchClient } from \"@fluid-app/api-client-core\";\nimport type { FetchClient } from \"@fluid-app/api-client-core\";\n\nimport { getAuthToken } from \"../auth/token.js\";\nimport { findProjectConfig } from \"../config/project-config.js\";\nimport { formatOutput } from \"./output.js\";\nimport type { CommandContext, OutputOptions } from \"./types.js\";\n\nexport function createCommandContext(opts: {\n token?: string;\n baseUrl?: string;\n profile?: string;\n format: \"json\" | \"table\";\n compact: boolean;\n jq?: string;\n verbose: boolean;\n}): CommandContext {\n let clientInstance: FetchClient | null = null;\n\n const outputOptions: OutputOptions = {\n format: opts.format,\n compact: opts.compact,\n jq: opts.jq,\n };\n\n return {\n verbose: opts.verbose,\n\n async getClient(): Promise<FetchClient> {\n if (clientInstance) return clientInstance;\n\n const token = resolveToken(opts);\n if (!token) {\n if (opts.profile) {\n throw new Error(\n `No API token found for profile \"${opts.profile}\". Run \\`fluid login\\` for that profile or provide --token <token>`,\n );\n }\n\n throw new Error(\n \"No API token found. Run `fluid login` or provide --token <token>\",\n );\n }\n\n const baseUrl =\n opts.baseUrl ??\n process.env[\"FLUID_API_BASE\"] ??\n \"https://api.fluid.app\";\n\n clientInstance = createFetchClient({\n baseUrl,\n getAuthToken: () => token,\n });\n\n return clientInstance;\n },\n\n output(data: unknown): void {\n const formatted = formatOutput(data, outputOptions);\n process.stdout.write(formatted + \"\\n\");\n },\n\n parseBody(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n throw new Error(`Invalid JSON body: ${raw}`);\n }\n },\n };\n}\n\nfunction resolveToken(opts: {\n token?: string;\n profile?: string;\n}): string | null {\n if (opts.token) return opts.token;\n\n if (opts.profile) return getAuthToken(opts.profile);\n\n const envToken = process.env[\"FLUID_TOKEN\"] ?? process.env[\"FLUID_API_TOKEN\"];\n if (envToken) return envToken;\n\n // Project-level .fluidrc profile (before global active profile)\n const projectConfig = findProjectConfig(process.cwd());\n if (projectConfig?.profile) {\n const rcToken = getAuthToken(projectConfig.profile);\n if (rcToken) return rcToken;\n }\n\n return getAuthToken();\n}\n","import { Command } from \"commander\";\n\nimport { createCommandContext } from \"./context.js\";\nimport type { CommandContext } from \"./types.js\";\n\ntype RegisterFn = (parent: Command, ctx: CommandContext) => void;\n\nexport function createDomainCommand(\n name: string,\n description: string,\n register: RegisterFn,\n): Command {\n const cmd = new Command(name)\n .description(description)\n // Keep global domain flags usable before or after nested subcommands.\n .enablePositionalOptions(false)\n .option(\"--token <token>\", \"API authentication token\")\n .option(\"--base-url <url>\", \"API base URL\")\n .option(\"--profile <name>\", \"Config profile name\")\n .option(\"--format <format>\", \"Output format (json|table)\", \"json\")\n .option(\"--compact\", \"Compact JSON output\", false)\n .option(\"--jq <path>\", \"Extract value at JSON path\")\n .option(\"--verbose\", \"Print request details to stderr\", false);\n\n let resolvedCtx: CommandContext | null = null;\n\n function getCtx(): CommandContext {\n if (!resolvedCtx) {\n const opts = cmd.opts();\n resolvedCtx = createCommandContext({\n token: opts.token,\n baseUrl: opts.baseUrl,\n profile: opts.profile,\n format: opts.format as \"json\" | \"table\",\n compact: opts.compact,\n jq: opts.jq,\n verbose: opts.verbose,\n });\n }\n return resolvedCtx;\n }\n\n const lazyCtx: CommandContext = {\n get verbose() {\n return getCtx().verbose;\n },\n getClient() {\n return getCtx().getClient();\n },\n output(data: unknown) {\n return getCtx().output(data);\n },\n parseBody(raw: string) {\n return getCtx().parseBody(raw);\n },\n };\n\n register(cmd, lazyCtx);\n\n return cmd;\n}\n"],"mappings":";;;;;;AA+CA,IAAa,WAAb,MAAa,iBAAiB,MAAM;CAClC;CACA;CAEA,YAAY,SAAiB,QAAgB,MAAgB;AAC3D,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS;AACd,OAAK,OAAO;AAEZ,MAAI,uBAAuB,MAEvB,OAMA,kBAAkB,MAAM,SAAS;;CAIvC,SAA2E;AACzE,SAAO;GACL,MAAM,KAAK;GACX,SAAS,KAAK;GACd,QAAQ,KAAK;GACb,MAAM,KAAK;GACZ;;;;;;AAoDL,SAAgB,kBACd,QACqB;CACrB,MAAM,EACJ,SACA,cACA,aACA,iBAAiB,EAAE,EACnB,gBACE;;;;CAKJ,eAAe,aACb,eACiC;EACjC,MAAM,UAAkC;GACtC,QAAQ;GACR,gBAAgB;GAChB,GAAG;GACH,GAAG;GACJ;AAGD,MAAI,cAAc;GAChB,MAAM,QAAQ,MAAM,cAAc;AAClC,OAAI,MACF,SAAQ,gBAAgB,UAAU;;AAItC,SAAO;;;;;;;CAQT,SAAS,QAAQ,UAA0B;AACzC,SAAO,GAAG,UAAU;;;;;;CAOtB,SAAS,SACP,UACA,QACQ;EACR,MAAM,UAAU,QAAQ,SAAS;AAEjC,MAAI,CAAC,UAAU,OAAO,KAAK,OAAO,CAAC,WAAW,EAC5C,QAAO;EAGT,MAAM,cAAc,IAAI,iBAAiB;AAEzC,SAAO,QAAQ,OAAO,CAAC,SAAS,CAAC,KAAK,WAAW;AAC/C,OAAI,UAAU,KAAA,KAAa,UAAU,KACnC;AAGF,OAAI,MAAM,QAAQ,MAAM,CAEtB,OAAM,SAAS,SAAS,YAAY,OAAO,GAAG,IAAI,KAAK,OAAO,KAAK,CAAC,CAAC;YAC5D,OAAO,UAAU,SAE1B,QAAO,QAAQ,MAAM,CAAC,SAAS,CAAC,QAAQ,cAAc;AACpD,QAAI,aAAa,KAAA,KAAa,aAAa,KACzC;AAGF,QAAI,MAAM,QAAQ,SAAS,CACzB,UAAS,SAAS,SAChB,YAAY,OAAO,GAAG,IAAI,GAAG,OAAO,MAAM,OAAO,KAAK,CAAC,CACxD;QAED,aAAY,OAAO,GAAG,IAAI,GAAG,OAAO,IAAI,OAAO,SAAS,CAAC;KAE3D;OAEF,aAAY,OAAO,KAAK,OAAO,MAAM,CAAC;IAExC;EAEF,MAAM,KAAK,YAAY,UAAU;AACjC,SAAO,KAAK,GAAG,QAAQ,GAAG,OAAO;;;;;;CAOnC,eAAe,eACb,UACA,QACA,MACoB;AACpB,MAAI,SAAS,WAAW,OAAO,YAC7B,cAAa;AAGf,MAAI,CAAC,SAAS,IAAI;GAGhB,MAAM,YAAY,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;AAGvD,OAFoB,SAAS,QAAQ,IAAI,eAAe,EAEvC,SAAS,mBAAmB,EAAE;IAC7C,IAAI;AACJ,QAAI;AACF,YAAO,KAAK,MAAM,UAAU;YACtB;AACN,WAAM,IAAI,SACR,UAAU,MAAM,GAAG,IAAI,IACrB,GAAG,OAAO,8BAA8B,SAAS,UACnD,SAAS,QACT,KACD;;IAKH,MAAM,cACJ,OAAO,KAAK,UAAU,YAAY,KAAK,UAAU,OAC5C,KAAK,MAAgC,UACtC,KAAA;AAKN,UAAM,IAAI,SAHP,KAAK,WACL,KAAK,kBACL,OAAO,gBAAgB,WAAW,cAAc,KAAA,MAE1C,GAAG,OAAO,kBACjB,SAAS,QACT,KAAK,UAAU,KAChB;SAED,OAAM,IAAI,SACR,GAAG,OAAO,8BAA8B,SAAS,UACjD,SAAS,QACT,KACD;;AAIL,MACE,SAAS,WAAW,OACpB,SAAS,QAAQ,IAAI,iBAAiB,KAAK,IAE3C,QAAO;AAKT,MAFoB,SAAS,QAAQ,IAAI,eAAe,EAEvC,SAAS,mBAAmB,CAC3C,KAAI;AAEF,UADa,MAAM,SAAS,MAAM;UAE5B;AACN,OAAI;AAGF,WADa,MAAM,SAAS,MAAM;WAE5B;AACN,WAAO;;;AAMb,SAAO;;;;;CAMT,eAAe,QACb,UACA,UAA0B,EAAE,EACR;EACpB,MAAM,EACJ,SAAS,OACT,SAAS,eACT,QACA,MACA,WACE;EAEJ,MAAM,MAAM,SAAS,SAAS,UAAU,OAAO,GAAG,QAAQ,SAAS;EAEnE,MAAM,UAAU,MAAM,aAAa,cAAc;EAEjD,IAAI;AAEJ,MAAI;GACF,MAAM,eAA4B;IAAE;IAAQ;IAAS;AACrD,OAAI,YAAa,cAAa,cAAc;GAC5C,MAAM,iBACJ,QAAQ,WAAW,QAAQ,KAAK,UAAU,KAAK,GAAG;AACpD,OAAI,eAAgB,cAAa,OAAO;AACxC,OAAI,OAAQ,cAAa,SAAS;AAClC,cAAW,MAAM,MAAM,KAAK,aAAa;WAClC,cAAc;AACrB,SAAM,IAAI,SACR,kBAAkB,wBAAwB,QAAQ,aAAa,UAAU,2BACzE,GACA,KACD;;AAGH,SAAO,eAA0B,UAAU,QAAQ,IAAI;;;;;CAMzD,eAAe,oBACb,UACA,UACA,UAEI,EAAE,EACc;EACpB,MAAM,EAAE,SAAS,QAAQ,SAAS,eAAe,WAAW;EAE5D,MAAM,MAAM,QAAQ,SAAS;EAC7B,MAAM,UAAU,MAAM,aAAa,cAAc;AAGjD,SAAO,QAAQ;EAEf,IAAI;AAEJ,MAAI;GACF,MAAM,eAA4B;IAAE;IAAQ;IAAS,MAAM;IAAU;AACrE,OAAI,YAAa,cAAa,cAAc;AAC5C,OAAI,OAAQ,cAAa,SAAS;AAClC,cAAW,MAAM,MAAM,KAAK,aAAa;WAClC,cAAc;AACrB,SAAM,IAAI,SACR,kBAAkB,wBAAwB,QAAQ,aAAa,UAAU,2BACzE,GACA,KACD;;AAGH,SAAO,eAA0B,UAAU,QAAQ,IAAI;;AAIzD,QAAO;EACI;EACY;EAGrB,MACE,UACA,QACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACR,GAAI,UAAU,EAAE,QAAQ;GACzB,CAAC;EAEJ,OACE,UACA,MACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACR;GACD,CAAC;EAEJ,MACE,UACA,MACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACR;GACD,CAAC;EAEJ,QACE,UACA,MACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACR;GACD,CAAC;EAEJ,SACE,UACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACT,CAAC;EACL;;;;ACnbH,SAAgB,aAAa,MAAe,SAAgC;CAC1E,IAAI,SAAS;AAEb,KAAI,QAAQ,GACV,UAAS,YAAY,QAAQ,QAAQ,GAAG;AAG1C,KAAI,QAAQ,WAAW,QACrB,QAAO,YAAY,OAAO;AAG5B,KAAI,QAAQ,QACV,QAAO,KAAK,UAAU,OAAO;AAE/B,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;;AAGxC,SAAS,YAAY,MAAe,MAAuB;CACzD,MAAM,QAAQ,KACX,QAAQ,QAAQ,GAAG,CACnB,MAAM,QAAQ,CACd,OAAO,QAAQ,CACf,KAAK,MAAM,EAAE,QAAQ,OAAO,GAAG,CAAC;CAEnC,IAAI,UAAe;AACnB,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,WAAW,KAAM,QAAO,KAAA;AAC5B,YAAU,QAAQ;;AAEpB,QAAO;;AAGT,SAAS,YAAY,MAAuB;AAC1C,KAAI,MAAM,QAAQ,KAAK,EAAE;AACvB,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,MAAI,OAAO,KAAK,OAAO,YAAY,KAAK,OAAO,MAAM;GACnD,MAAM,OAAO,OAAO,KAAK,KAAK,GAAG;GACjC,MAAM,SAAS,KAAK,KAAK,MACvB,KAAK,IAAI,EAAE,QAAQ,GAAG,KAAK,KAAK,QAAQ,OAAO,IAAI,MAAM,GAAG,CAAC,OAAO,CAAC,CACtE;AAUD,UAAO;IARQ,KACZ,KAAK,GAAG,MAAM,EAAE,aAAa,CAAC,OAAO,OAAO,GAAI,CAAC,CACjD,KAAK,KAAK;IACK,OAAO,KAAK,MAAM,IAAI,OAAO,EAAE,CAAC,CAAC,KAAK,KAAK;IAKlC,GAJd,KAAK,KAAK,QACrB,KAAK,KAAK,GAAG,MAAM,OAAO,IAAI,MAAM,GAAG,CAAC,OAAO,OAAO,GAAI,CAAC,CAAC,KAAK,KAAK,CACvE;IAEkC,CAAC,KAAK,KAAK;;AAGhD,SAAO,KAAK,IAAI,OAAO,CAAC,KAAK,KAAK;;AAGpC,KAAI,OAAO,SAAS,YAAY,SAAS,MAAM;EAC7C,MAAM,UAAU,OAAO,QAAQ,KAAK;EACpC,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC;AAC7D,SAAO,QACJ,KAAK,CAAC,GAAG,OAAO,GAAG,EAAE,OAAO,UAAU,CAAC,IAAI,KAAK,UAAU,EAAE,GAAG,CAC/D,KAAK,KAAK;;AAGf,QAAO,OAAO,KAAK;;;;AC1DrB,SAAgB,qBAAqB,MAQlB;CACjB,IAAI,iBAAqC;CAEzC,MAAM,gBAA+B;EACnC,QAAQ,KAAK;EACb,SAAS,KAAK;EACd,IAAI,KAAK;EACV;AAED,QAAO;EACL,SAAS,KAAK;EAEd,MAAM,YAAkC;AACtC,OAAI,eAAgB,QAAO;GAE3B,MAAM,QAAQ,aAAa,KAAK;AAChC,OAAI,CAAC,OAAO;AACV,QAAI,KAAK,QACP,OAAM,IAAI,MACR,mCAAmC,KAAK,QAAQ,oEACjD;AAGH,UAAM,IAAI,MACR,mEACD;;AAQH,oBAAiB,kBAAkB;IACjC,SALA,KAAK,WACL,QAAQ,IAAI,qBACZ;IAIA,oBAAoB;IACrB,CAAC;AAEF,UAAO;;EAGT,OAAO,MAAqB;GAC1B,MAAM,YAAY,aAAa,MAAM,cAAc;AACnD,WAAQ,OAAO,MAAM,YAAY,KAAK;;EAGxC,UAAU,KAAsB;AAC9B,OAAI;AACF,WAAO,KAAK,MAAM,IAAI;WAChB;AACN,UAAM,IAAI,MAAM,sBAAsB,MAAM;;;EAGjD;;AAGH,SAAS,aAAa,MAGJ;AAChB,KAAI,KAAK,MAAO,QAAO,KAAK;AAE5B,KAAI,KAAK,QAAS,QAAO,aAAa,KAAK,QAAQ;CAEnD,MAAM,WAAW,QAAQ,IAAI,kBAAkB,QAAQ,IAAI;AAC3D,KAAI,SAAU,QAAO;CAGrB,MAAM,gBAAgB,kBAAkB,QAAQ,KAAK,CAAC;AACtD,KAAI,eAAe,SAAS;EAC1B,MAAM,UAAU,aAAa,cAAc,QAAQ;AACnD,MAAI,QAAS,QAAO;;AAGtB,QAAO,cAAc;;;;ACnFvB,SAAgB,oBACd,MACA,aACA,UACS;CACT,MAAM,MAAM,IAAI,QAAQ,KAAK,CAC1B,YAAY,YAAY,CAExB,wBAAwB,MAAM,CAC9B,OAAO,mBAAmB,2BAA2B,CACrD,OAAO,oBAAoB,eAAe,CAC1C,OAAO,oBAAoB,sBAAsB,CACjD,OAAO,qBAAqB,8BAA8B,OAAO,CACjE,OAAO,aAAa,uBAAuB,MAAM,CACjD,OAAO,eAAe,6BAA6B,CACnD,OAAO,aAAa,mCAAmC,MAAM;CAEhE,IAAI,cAAqC;CAEzC,SAAS,SAAyB;AAChC,MAAI,CAAC,aAAa;GAChB,MAAM,OAAO,IAAI,MAAM;AACvB,iBAAc,qBAAqB;IACjC,OAAO,KAAK;IACZ,SAAS,KAAK;IACd,SAAS,KAAK;IACd,QAAQ,KAAK;IACb,SAAS,KAAK;IACd,IAAI,KAAK;IACT,SAAS,KAAK;IACf,CAAC;;AAEJ,SAAO;;AAkBT,UAAS,KAfuB;EAC9B,IAAI,UAAU;AACZ,UAAO,QAAQ,CAAC;;EAElB,YAAY;AACV,UAAO,QAAQ,CAAC,WAAW;;EAE7B,OAAO,MAAe;AACpB,UAAO,QAAQ,CAAC,OAAO,KAAK;;EAE9B,UAAU,KAAa;AACrB,UAAO,QAAQ,CAAC,UAAU,IAAI;;EAEjC,CAEqB;AAEtB,QAAO"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../platform/api-client-core/src/fetch-client.ts","../src/domain/output.ts","../src/domain/context.ts","../src/domain/command.ts"],"sourcesContent":["/**\n * Minimal, framework-agnostic fetch client for Fluid APIs\n * Compatible with fluid-admin patterns but usable standalone\n */\n\nexport interface FetchClientConfig {\n /**\n * Base URL for all requests (e.g., \"https://api.fluid.app/api\")\n */\n baseUrl: string;\n\n /**\n * Optional function to get auth token\n * Return null/undefined if no token available\n */\n getAuthToken?: () => string | null | Promise<string | null>;\n\n /**\n * Optional callback when 401 auth error occurs\n */\n onAuthError?: () => void;\n\n /**\n * Default headers to include in all requests\n * Example: { \"x-fluid-client\": \"admin\" }\n */\n defaultHeaders?: Record<string, string>;\n\n /**\n * Credentials mode for fetch requests.\n * Set to `\"include\"` for cookie-based (same-origin BFF) authentication.\n * @default undefined (browser default: \"same-origin\")\n */\n credentials?: RequestCredentials;\n\n /**\n * Request cache mode for fetch requests.\n * @default undefined (browser default)\n */\n cache?: RequestCache;\n\n /**\n * Retry configuration for thrown network errors from fetch.\n * Does not retry HTTP error responses or aborted requests.\n * @default undefined (no retries)\n */\n networkRetry?: {\n maxRetries?: number;\n baseDelayMs?: number;\n };\n\n /**\n * Throw ApiError when a successful response declares JSON but cannot be parsed.\n * Defaults to false to preserve the legacy generated-client behavior.\n * @default false\n */\n throwOnInvalidJson?: boolean;\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n body?: unknown;\n signal?: AbortSignal;\n priority?: RequestInit[\"priority\"];\n}\n\n/**\n * API Error class compatible with fluid-admin's ApiError\n */\nexport class ApiError extends Error {\n public readonly status: number;\n public readonly data: unknown;\n public readonly requestId?: string;\n\n constructor(\n message: string,\n status: number,\n data?: unknown,\n requestId?: string,\n ) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.data = data;\n this.requestId = requestId;\n\n if (\"captureStackTrace\" in Error) {\n (\n Error as {\n captureStackTrace: (\n target: Error,\n constructor: NewableFunction,\n ) => void;\n }\n ).captureStackTrace(this, ApiError);\n }\n }\n\n toJSON(): {\n name: string;\n message: string;\n status: number;\n data: unknown;\n requestId?: string;\n } {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n data: this.data,\n requestId: this.requestId,\n };\n }\n}\n\nfunction getStringRequestId(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction getRequestIdFromHeaders(headers: Headers): string | undefined {\n return (\n getStringRequestId(headers.get(\"x-request-id\")) ??\n getStringRequestId(headers.get(\"request-id\")) ??\n getStringRequestId(headers.get(\"X-Request-ID\"))\n );\n}\n\nfunction getRequestIdFromJsonBody(body: unknown): string | undefined {\n if (!body || typeof body !== \"object\" || Array.isArray(body)) {\n return undefined;\n }\n\n const record = body as Record<string, unknown>;\n const meta = record.meta;\n\n return (\n getStringRequestId(record.request_id) ??\n getStringRequestId(record.requestId) ??\n (meta && typeof meta === \"object\" && !Array.isArray(meta)\n ? (getStringRequestId((meta as Record<string, unknown>).request_id) ??\n getStringRequestId((meta as Record<string, unknown>).requestId))\n : undefined)\n );\n}\n\n/**\n * Type guard for ApiError\n */\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n\nexport interface FetchClientInstance {\n request: <TResponse = unknown>(\n endpoint: string,\n options?: RequestOptions,\n ) => Promise<TResponse>;\n requestWithFormData: <TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options?: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n },\n ) => Promise<TResponse>;\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ) => Promise<TResponse>;\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ) => Promise<TResponse>;\n}\n\n/**\n * Creates a configured fetch client instance\n */\nexport function createFetchClient(\n config: FetchClientConfig,\n): FetchClientInstance {\n const {\n baseUrl,\n getAuthToken,\n onAuthError,\n defaultHeaders = {},\n credentials,\n cache,\n networkRetry,\n throwOnInvalidJson = false,\n } = config;\n const maxNetworkRetries = Math.max(0, networkRetry?.maxRetries ?? 0);\n const baseNetworkRetryDelayMs = Math.max(0, networkRetry?.baseDelayMs ?? 0);\n\n /**\n * Build headers for a request\n */\n async function buildHeaders(\n customHeaders?: Record<string, string>,\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...defaultHeaders,\n ...customHeaders,\n };\n\n // Add auth token if available\n if (getAuthToken) {\n const token = await getAuthToken();\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n /**\n * Join baseUrl + endpoint via string concatenation (matches fetchApi).\n * Using `new URL(endpoint, baseUrl)` would strip any path prefix from\n * baseUrl (e.g. \"/api\") when the endpoint starts with \"/\".\n */\n function joinUrl(endpoint: string): string {\n return `${baseUrl}${endpoint}`;\n }\n\n /**\n * Build URL with query parameters for GET requests\n * Compatible with fluid-admin's query param handling\n */\n function buildUrl(\n endpoint: string,\n params?: Record<string, unknown>,\n ): string {\n const fullUrl = joinUrl(endpoint);\n\n if (!params || Object.keys(params).length === 0) {\n return fullUrl;\n }\n\n const queryString = new URLSearchParams();\n\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) {\n return; // Skip undefined/null values\n }\n\n if (Array.isArray(value)) {\n // Handle arrays like Rails expects: key[]\n value.forEach((item) => queryString.append(`${key}[]`, String(item)));\n } else if (typeof value === \"object\") {\n // Handle nested objects: key[subkey]\n Object.entries(value).forEach(([subKey, subValue]) => {\n if (subValue === undefined || subValue === null) {\n return;\n }\n\n if (Array.isArray(subValue)) {\n subValue.forEach((item) =>\n queryString.append(`${key}[${subKey}][]`, String(item)),\n );\n } else {\n queryString.append(`${key}[${subKey}]`, String(subValue));\n }\n });\n } else {\n queryString.append(key, String(value));\n }\n });\n\n const qs = queryString.toString();\n return qs ? `${fullUrl}?${qs}` : fullUrl;\n }\n\n /**\n * Shared response handler for both JSON and FormData requests.\n * Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.\n */\n async function handleResponse<TResponse>(\n response: Response,\n method: string,\n _url: string,\n ): Promise<TResponse> {\n const headerRequestId = getRequestIdFromHeaders(response.headers);\n\n if (response.status === 401 && onAuthError) {\n onAuthError();\n }\n\n if (!response.ok) {\n // Read body as text first to avoid SyntaxError from response.json()\n // when server returns non-JSON bodies with application/json content-type.\n const errorText = await response.text().catch(() => \"\");\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(errorText);\n } catch {\n throw new ApiError(\n errorText.slice(0, 200) ||\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\n );\n }\n // Some Rails BFF endpoints return `{ error: { message, details } }`\n // instead of `{ message }` or `{ error_message }`. Fall through to\n // that shape last so callers always surface the real reason.\n const nestedError =\n typeof data.error === \"object\" && data.error !== null\n ? (data.error as { message?: unknown }).message\n : undefined;\n const msg =\n (data.message as string | undefined) ||\n (data.error_message as string | undefined) ||\n (typeof nestedError === \"string\" ? nestedError : undefined);\n throw new ApiError(\n msg || `${method} request failed`,\n response.status,\n data.errors || data,\n headerRequestId ?? getRequestIdFromJsonBody(data),\n );\n } else {\n throw new ApiError(\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\n );\n }\n }\n\n if (\n response.status === 204 ||\n response.headers.get(\"content-length\") === \"0\"\n ) {\n return null as TResponse;\n }\n\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n const responseText = await response.text();\n\n try {\n const data = JSON.parse(responseText);\n return data as TResponse;\n } catch {\n if (throwOnInvalidJson) {\n throw new ApiError(\n \"Failed to parse response as JSON\",\n response.status,\n null,\n headerRequestId,\n );\n }\n\n // API declared JSON content-type but body isn't valid JSON.\n // Return the raw payload to preserve the legacy non-strict path.\n return responseText ? (responseText as TResponse) : (null as TResponse);\n }\n }\n\n // Non-JSON response (text/plain, text/html, etc.)\n return null as TResponse;\n }\n\n function getNetworkRetryDelayMs(retryAttempt: number): number {\n return baseNetworkRetryDelayMs * 2 ** (retryAttempt - 1);\n }\n\n async function waitForNetworkRetry(retryAttempt: number): Promise<void> {\n const delayMs = getNetworkRetryDelayMs(retryAttempt);\n if (delayMs <= 0) {\n return;\n }\n\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n async function fetchWithNetworkRetry(\n url: string,\n fetchOptions: RequestInit,\n signal?: AbortSignal,\n ): Promise<Response> {\n let retryCount = 0;\n\n while (true) {\n try {\n return await fetch(url, fetchOptions);\n } catch (networkError) {\n if (signal?.aborted || retryCount >= maxNetworkRetries) {\n throw networkError;\n }\n\n retryCount += 1;\n await waitForNetworkRetry(retryCount);\n\n if (signal?.aborted) {\n throw networkError;\n }\n }\n }\n }\n\n /**\n * Main request function\n */\n async function request<TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> {\n const {\n method = \"GET\",\n headers: customHeaders,\n params,\n body,\n signal,\n priority,\n } = options;\n\n const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);\n\n const headers = await buildHeaders(customHeaders);\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers };\n if (credentials) fetchOptions.credentials = credentials;\n if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n const serializedBody =\n body && method !== \"GET\" ? JSON.stringify(body) : null;\n if (serializedBody) fetchOptions.body = serializedBody;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n /**\n * Request with FormData (for file uploads)\n */\n async function requestWithFormData<TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n } = {},\n ): Promise<TResponse> {\n const {\n method = \"POST\",\n headers: customHeaders,\n signal,\n priority,\n } = options;\n\n const url = joinUrl(endpoint);\n const headers = await buildHeaders(customHeaders);\n\n // Remove Content-Type to let browser set it with boundary\n delete headers[\"Content-Type\"];\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers, body: formData };\n if (credentials) fetchOptions.credentials = credentials;\n if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n // Return client with convenience methods\n return {\n request: request,\n requestWithFormData: requestWithFormData,\n\n // Convenience methods for common HTTP verbs\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"GET\" as const,\n ...(params && { params }),\n }),\n\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"POST\",\n body,\n }),\n\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PUT\",\n body,\n }),\n\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PATCH\",\n body,\n }),\n\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"DELETE\",\n }),\n };\n}\n\nexport type FetchClient = FetchClientInstance;\n","import type { OutputOptions } from \"./types.js\";\n\nexport function formatOutput(data: unknown, options: OutputOptions): string {\n let result = data;\n\n if (options.jq) {\n result = extractPath(result, options.jq);\n }\n\n if (options.format === \"table\") {\n return formatTable(result);\n }\n\n if (options.compact) {\n return JSON.stringify(result);\n }\n return JSON.stringify(result, null, 2);\n}\n\nfunction extractPath(data: unknown, path: string): unknown {\n const parts = path\n .replace(/^\\.*/, \"\")\n .split(/\\.|\\[/)\n .filter(Boolean)\n .map((p) => p.replace(/\\]$/, \"\"));\n\n let current: any = data;\n for (const part of parts) {\n if (current == null) return undefined;\n current = current[part];\n }\n return current;\n}\n\nfunction formatTable(data: unknown): string {\n if (Array.isArray(data)) {\n if (data.length === 0) return \"(empty)\";\n\n if (typeof data[0] === \"object\" && data[0] !== null) {\n const keys = Object.keys(data[0]);\n const widths = keys.map((k) =>\n Math.max(k.length, ...data.map((row) => String(row[k] ?? \"\").length)),\n );\n\n const header = keys\n .map((k, i) => k.toUpperCase().padEnd(widths[i]!))\n .join(\" \");\n const separator = widths.map((w) => \"-\".repeat(w)).join(\" \");\n const rows = data.map((row) =>\n keys.map((k, i) => String(row[k] ?? \"\").padEnd(widths[i]!)).join(\" \"),\n );\n\n return [header, separator, ...rows].join(\"\\n\");\n }\n\n return data.map(String).join(\"\\n\");\n }\n\n if (typeof data === \"object\" && data !== null) {\n const entries = Object.entries(data);\n const maxKeyLen = Math.max(...entries.map(([k]) => k.length));\n return entries\n .map(([k, v]) => `${k.padEnd(maxKeyLen)} ${JSON.stringify(v)}`)\n .join(\"\\n\");\n }\n\n return String(data);\n}\n","import { createFetchClient } from \"@fluid-app/api-client-core\";\nimport type { FetchClient } from \"@fluid-app/api-client-core\";\n\nimport { getAuthToken } from \"../auth/token.js\";\nimport { findProjectConfig } from \"../config/project-config.js\";\nimport { formatOutput } from \"./output.js\";\nimport type { CommandContext, OutputOptions } from \"./types.js\";\n\nexport function createCommandContext(opts: {\n token?: string;\n baseUrl?: string;\n profile?: string;\n format: \"json\" | \"table\";\n compact: boolean;\n jq?: string;\n verbose: boolean;\n}): CommandContext {\n let clientInstance: FetchClient | null = null;\n\n const outputOptions: OutputOptions = {\n format: opts.format,\n compact: opts.compact,\n jq: opts.jq,\n };\n\n return {\n verbose: opts.verbose,\n\n async getClient(): Promise<FetchClient> {\n if (clientInstance) return clientInstance;\n\n const token = resolveToken(opts);\n if (!token) {\n if (opts.profile) {\n throw new Error(\n `No API token found for profile \"${opts.profile}\". Run \\`fluid login\\` for that profile or provide --token <token>`,\n );\n }\n\n throw new Error(\n \"No API token found. Run `fluid login` or provide --token <token>\",\n );\n }\n\n const baseUrl =\n opts.baseUrl ??\n process.env[\"FLUID_API_BASE\"] ??\n \"https://api.fluid.app\";\n\n clientInstance = createFetchClient({\n baseUrl,\n getAuthToken: () => token,\n });\n\n return clientInstance;\n },\n\n output(data: unknown): void {\n const formatted = formatOutput(data, outputOptions);\n process.stdout.write(formatted + \"\\n\");\n },\n\n parseBody(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n throw new Error(`Invalid JSON body: ${raw}`);\n }\n },\n };\n}\n\nfunction resolveToken(opts: {\n token?: string;\n profile?: string;\n}): string | null {\n if (opts.token) return opts.token;\n\n if (opts.profile) return getAuthToken(opts.profile);\n\n const envToken = process.env[\"FLUID_TOKEN\"] ?? process.env[\"FLUID_API_TOKEN\"];\n if (envToken) return envToken;\n\n // Project-level .fluidrc profile (before global active profile)\n const projectConfig = findProjectConfig(process.cwd());\n if (projectConfig?.profile) {\n const rcToken = getAuthToken(projectConfig.profile);\n if (rcToken) return rcToken;\n }\n\n return getAuthToken();\n}\n","import { Command } from \"commander\";\n\nimport { createCommandContext } from \"./context.js\";\nimport type { CommandContext } from \"./types.js\";\n\ntype RegisterFn = (parent: Command, ctx: CommandContext) => void;\n\nexport function createDomainCommand(\n name: string,\n description: string,\n register: RegisterFn,\n): Command {\n const cmd = new Command(name)\n .description(description)\n // Keep global domain flags usable before or after nested subcommands.\n .enablePositionalOptions(false)\n .option(\"--token <token>\", \"API authentication token\")\n .option(\"--base-url <url>\", \"API base URL\")\n .option(\"--profile <name>\", \"Config profile name\")\n .option(\"--format <format>\", \"Output format (json|table)\", \"json\")\n .option(\"--compact\", \"Compact JSON output\", false)\n .option(\"--jq <path>\", \"Extract value at JSON path\")\n .option(\"--verbose\", \"Print request details to stderr\", false);\n\n let resolvedCtx: CommandContext | null = null;\n\n function getCtx(): CommandContext {\n if (!resolvedCtx) {\n const opts = cmd.opts();\n resolvedCtx = createCommandContext({\n token: opts.token,\n baseUrl: opts.baseUrl,\n profile: opts.profile,\n format: opts.format as \"json\" | \"table\",\n compact: opts.compact,\n jq: opts.jq,\n verbose: opts.verbose,\n });\n }\n return resolvedCtx;\n }\n\n const lazyCtx: CommandContext = {\n get verbose() {\n return getCtx().verbose;\n },\n getClient() {\n return getCtx().getClient();\n },\n output(data: unknown) {\n return getCtx().output(data);\n },\n parseBody(raw: string) {\n return getCtx().parseBody(raw);\n },\n };\n\n register(cmd, lazyCtx);\n\n return cmd;\n}\n"],"mappings":"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"}
|
|
@@ -216,10 +216,11 @@ async function confirmMfa(uuid, code) {
|
|
|
216
216
|
/**
|
|
217
217
|
* Fetch all companies the authenticated user has access to.
|
|
218
218
|
*/
|
|
219
|
-
async function fetchUserCompanies(token) {
|
|
219
|
+
async function fetchUserCompanies(token, baseUrl) {
|
|
220
220
|
const { signal, cleanup } = makeSignal();
|
|
221
|
+
const apiBase = baseUrl ?? getFluidApiBase();
|
|
221
222
|
try {
|
|
222
|
-
const response = await fetch(`${
|
|
223
|
+
const response = await fetch(`${apiBase}/api/me`, {
|
|
223
224
|
method: "GET",
|
|
224
225
|
headers: { Authorization: `Bearer ${token}` },
|
|
225
226
|
signal
|
|
@@ -247,10 +248,11 @@ async function fetchUserCompanies(token) {
|
|
|
247
248
|
* Response shape (from @fluid-app/auth SwitchCompanyResponse):
|
|
248
249
|
* { company: { id, name, fluid_shop, jwt }, meta: { request_id, timestamp } }
|
|
249
250
|
*/
|
|
250
|
-
async function switchCompany(token, companyId) {
|
|
251
|
+
async function switchCompany(token, companyId, baseUrl) {
|
|
251
252
|
const { signal, cleanup } = makeSignal();
|
|
253
|
+
const apiBase = baseUrl ?? getFluidApiBase();
|
|
252
254
|
try {
|
|
253
|
-
const response = await fetch(`${
|
|
255
|
+
const response = await fetch(`${apiBase}/api/authentication/company/${companyId}/switch`, {
|
|
254
256
|
method: "PUT",
|
|
255
257
|
headers: {
|
|
256
258
|
Authorization: `Bearer ${token}`,
|
|
@@ -460,6 +462,6 @@ function listProfileNames() {
|
|
|
460
462
|
return Object.keys(config.profiles);
|
|
461
463
|
}
|
|
462
464
|
//#endregion
|
|
463
|
-
export {
|
|
465
|
+
export { unwrapOr as A, isSuccess as C, tryCatch as D, success as E, tryCatchAsync as O, isNodeError as S, mapResult as T, validateToken as _, readConfig as a, isError as b, getConfigDir as c, FLUID_API_ERROR as d, confirmMfa as f, switchCompany as g, sendMfa as h, findProjectConfig as i, unwrap as k, getConfigFilePath as l, getFluidApiBase as m, getAuthToken as n, updateConfig as o, fetchUserCompanies as p, listProfileNames as r, writeConfig as s, getActiveProfile as t, createDefaultConfig as u, failure as v, mapError as w, isFailure as x, getErrorMessage as y };
|
|
464
466
|
|
|
465
|
-
//# sourceMappingURL=token-
|
|
467
|
+
//# sourceMappingURL=token-D4DJFSdm.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-D4DJFSdm.mjs","names":[],"sources":["../src/utils/result.ts","../src/auth/fluid-api.ts","../src/config/types.ts","../src/config/paths.ts","../src/config/config.ts","../src/config/project-config.ts","../src/auth/token.ts"],"sourcesContent":["/**\n * Result type utilities for type-safe error handling\n *\n * The Result<T, E> pattern provides a discriminated union for fallible operations,\n * enabling exhaustive handling without try/catch blocks.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Result type - discriminated union for success/failure\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Success<T> {\n readonly success: true;\n readonly value: T;\n}\n\nexport interface Failure<E> {\n readonly success: false;\n readonly error: E;\n}\n\nexport type Result<T, E = Error> = Success<T> | Failure<E>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Constructor functions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function success<T>(value: T): Success<T> {\n return { success: true, value };\n}\n\nexport function failure<E>(error: E): Failure<E> {\n return { success: false, error };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Type guards\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function isSuccess<T, E>(result: Result<T, E>): result is Success<T> {\n return result.success === true;\n}\n\nexport function isFailure<T, E>(result: Result<T, E>): result is Failure<E> {\n return result.success === false;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Utility functions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function tryCatch<T>(fn: () => T): Result<T, Error> {\n try {\n return success(fn());\n } catch (error) {\n return failure(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\nexport async function tryCatchAsync<T>(\n fn: () => Promise<T>,\n): Promise<Result<T, Error>> {\n try {\n return success(await fn());\n } catch (error) {\n return failure(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\nexport function unwrap<T, E>(result: Result<T, E>): T {\n if (isSuccess(result)) return result.value;\n // Always throw an Error instance so V8 attaches a stack trace and\n // `instanceof Error` checks in catch blocks work as expected.\n if (result.error instanceof Error) throw result.error;\n throw new Error(\n typeof result.error === \"object\" && result.error !== null\n ? JSON.stringify(result.error)\n : String(result.error),\n );\n}\n\nexport function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {\n if (isSuccess(result)) return result.value;\n return defaultValue;\n}\n\nexport function mapResult<T, U, E>(\n result: Result<T, E>,\n fn: (value: T) => U,\n): Result<U, E> {\n if (isSuccess(result)) return success(fn(result.value));\n return result;\n}\n\nexport function mapError<T, E, F>(\n result: Result<T, E>,\n fn: (error: E) => F,\n): Result<T, F> {\n if (isFailure(result)) return failure(fn(result.error));\n return result;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error narrowing utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function isError(value: unknown): value is Error {\n return value instanceof Error;\n}\n\nexport function isNodeError(value: unknown): value is NodeJS.ErrnoException {\n return value instanceof Error && \"code\" in value;\n}\n\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === \"string\") return error;\n return String(error);\n}\n","/**\n * Fluid API client for authentication operations\n */\n\nimport type { CliError } from \"../utils/errors.js\";\nimport { type Result, success, failure } from \"../utils/result.js\";\n\nconst API_TIMEOUT_MS = 10_000;\n\nexport function getFluidApiBase(): string {\n return process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n}\n\nfunction makeSignal(): { signal: AbortSignal; cleanup: () => void } {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);\n return { signal: controller.signal, cleanup: () => clearTimeout(timer) };\n}\n\nexport interface FluidApiError extends CliError {\n readonly code: string;\n readonly message: string;\n readonly details?: string;\n}\n\nexport const FLUID_API_ERROR = {\n INVALID_TOKEN: {\n code: \"INVALID_TOKEN\",\n message: \"Token is invalid or expired\",\n },\n API_UNREACHABLE: {\n code: \"API_UNREACHABLE\",\n message: \"Could not reach the Fluid API\",\n },\n UUID_NOT_FOUND: {\n code: \"UUID_NOT_FOUND\",\n message: \"Verification session not found\",\n },\n CODE_EXPIRED: {\n code: \"CODE_EXPIRED\",\n message: \"Verification code has expired — please request a new one\",\n },\n INVALID_CODE: {\n code: \"INVALID_CODE\",\n message: \"Invalid verification code\",\n },\n COMPANY_NOT_FOUND: {\n code: \"COMPANY_NOT_FOUND\",\n message: \"Company not found\",\n },\n SWITCH_FAILED: {\n code: \"SWITCH_FAILED\",\n message: \"Failed to switch company\",\n },\n} as const;\n\nfunction createApiError(\n template: { readonly code: string; readonly message: string },\n details?: string,\n): FluidApiError {\n return { code: template.code, message: template.message, details };\n}\n\nexport interface CompanyInfo {\n readonly name: string;\n}\n\nexport interface UserCompany {\n readonly id: number;\n readonly name: string;\n}\n\nexport interface SwitchCompanyResult {\n readonly companyId: number;\n readonly companyName: string;\n readonly fluidShop: string;\n readonly jwt: string;\n}\n\nexport interface MfaResponse {\n readonly uuid: string;\n readonly expiresAt: string;\n}\n\nexport interface CompanyChoice {\n readonly id: number;\n readonly name: string;\n readonly shopName: string;\n readonly jwt: string;\n}\n\nexport interface ConfirmMfaResponse {\n readonly authType: string;\n readonly companies: CompanyChoice[];\n}\n\n/**\n * Validate a token against the Fluid API and return company info\n */\nexport async function validateToken(\n token: string,\n): Promise<Result<CompanyInfo, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/api/company/v1/companies/me`,\n {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${token}`,\n },\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n data?: { company?: { name?: string } };\n };\n const name = data?.data?.company?.name;\n if (!name) {\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n \"Unexpected response shape from /companies/me\",\n ),\n );\n }\n return success({ name });\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}: Check that your token is valid and non-expired.`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Send a multi-factor authentication code to the given email address.\n * Always returns 201 from the API (anti-enumeration).\n */\nexport async function sendMfa(\n email: string,\n): Promise<Result<MfaResponse, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/api/authentication/send_mfa`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email }),\n signal,\n },\n );\n\n if (response.status === 201) {\n const data = (await response.json()) as {\n multi_factor_authentication?: {\n uuid?: string;\n verification_code_expires_at?: string;\n };\n };\n const uuid = data.multi_factor_authentication?.uuid;\n const expiresAt =\n data.multi_factor_authentication?.verification_code_expires_at;\n if (!uuid || !expiresAt) {\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n \"Unexpected response shape from /send_mfa\",\n ),\n );\n }\n return success({ uuid, expiresAt });\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Confirm a multi-factor authentication code and retrieve company JWTs.\n */\nexport async function confirmMfa(\n uuid: string,\n code: string,\n): Promise<Result<ConfirmMfaResponse, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/api/authentication/confirm_mfa`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n uuid,\n multi_factor_authentication: { verification_code: code },\n }),\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n authenticated?: boolean;\n auth_type?: string;\n companies?:\n | {\n id: number;\n name: string;\n shop_name: string;\n jwt: string;\n }[]\n | null;\n };\n if (!data.authenticated) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_CODE,\n \"Authentication was not confirmed by the server\",\n ),\n );\n }\n const companies = Array.isArray(data.companies) ? data.companies : [];\n return success({\n authType: data.auth_type ?? \"\",\n companies: companies.map((c) => ({\n id: c.id,\n name: c.name,\n shopName: c.shop_name,\n jwt: c.jwt,\n })),\n });\n }\n\n if (response.status === 404) {\n return failure(createApiError(FLUID_API_ERROR.UUID_NOT_FOUND));\n }\n if (response.status === 410) {\n return failure(createApiError(FLUID_API_ERROR.CODE_EXPIRED));\n }\n if (response.status === 422) {\n return failure(createApiError(FLUID_API_ERROR.INVALID_CODE));\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Fetch all companies the authenticated user has access to.\n */\nexport async function fetchUserCompanies(\n token: string,\n baseUrl?: string,\n): Promise<Result<UserCompany[], FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n const apiBase = baseUrl ?? getFluidApiBase();\n try {\n const response = await fetch(`${apiBase}/api/me`, {\n method: \"GET\",\n headers: { Authorization: `Bearer ${token}` },\n signal,\n });\n\n if (response.ok) {\n const data = (await response.json()) as {\n companies?: { id: number; name: string }[];\n };\n const companies = Array.isArray(data.companies) ? data.companies : [];\n return success(companies.map((c) => ({ id: c.id, name: c.name })));\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Switch to a different company and receive a new JWT.\n *\n * Response shape (from @fluid-app/auth SwitchCompanyResponse):\n * { company: { id, name, fluid_shop, jwt }, meta: { request_id, timestamp } }\n */\nexport async function switchCompany(\n token: string,\n companyId: number,\n baseUrl?: string,\n): Promise<Result<SwitchCompanyResult, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n const apiBase = baseUrl ?? getFluidApiBase();\n try {\n const response = await fetch(\n `${apiBase}/api/authentication/company/${companyId}/switch`,\n {\n method: \"PUT\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n company?: {\n id: number;\n name: string;\n fluid_shop: string;\n jwt: string;\n };\n };\n const company = data.company;\n if (!company?.jwt) {\n return failure(\n createApiError(\n FLUID_API_ERROR.SWITCH_FAILED,\n \"Unexpected response shape from /switch\",\n ),\n );\n }\n return success({\n companyId: company.id,\n companyName: company.name,\n fluidShop: company.fluid_shop,\n jwt: company.jwt,\n });\n }\n\n if (response.status === 404) {\n return failure(\n createApiError(\n FLUID_API_ERROR.COMPANY_NOT_FOUND,\n `Company ${companyId} not found`,\n ),\n );\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.SWITCH_FAILED,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n","/**\n * Configuration types for the Fluid CLI\n */\n\nexport interface FluidProfile {\n readonly name: string;\n readonly token: string;\n readonly companyName: string;\n readonly storedAt: string; // ISO-8601\n readonly baseUrl?: string;\n}\n\nexport interface FluidConfig {\n activeProfile: string | null;\n profiles: Record<string, FluidProfile>;\n plugins: Record<string, unknown>;\n /**\n * Allow-list of plugin package names.\n *\n * - `null` (default) — auto-discover and load all `@fluid-app/fluid-cli-*`\n * and `*-cli-commands` packages found in `node_modules` or the pnpm\n * workspace. Only `@fluid-app`-scoped packages matching the naming\n * convention are eligible; no third-party code is loaded.\n * - `string[]` — only load plugins whose names appear in the array.\n * Set to `[]` to disable all plugins.\n */\n enabledPlugins: string[] | null;\n}\n\nexport function createDefaultConfig(): FluidConfig {\n return {\n activeProfile: null,\n profiles: {},\n plugins: {},\n enabledPlugins: null, // auto-discover all plugins\n };\n}\n","/**\n * XDG-compliant config directory resolution\n *\n * Priority:\n * 1. FLUID_CONFIG_DIR env var (explicit override)\n * 2. ~/.fluid/ on macOS (convention for CLI tools)\n * 3. %APPDATA%/fluid/ on Windows\n * 4. $XDG_CONFIG_HOME/fluid/ on Linux (XDG spec)\n * 5. ~/.config/fluid/ fallback on Linux\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport function getConfigDir(): string {\n const envOverride = process.env[\"FLUID_CONFIG_DIR\"];\n if (envOverride) return envOverride;\n\n if (process.platform === \"darwin\") {\n return join(homedir(), \".fluid\");\n }\n\n if (process.platform === \"win32\") {\n const appData =\n process.env[\"APPDATA\"] ?? join(homedir(), \"AppData\", \"Roaming\");\n return join(appData, \"fluid\");\n }\n\n const xdgConfig = process.env[\"XDG_CONFIG_HOME\"];\n if (xdgConfig) return join(xdgConfig, \"fluid\");\n\n return join(homedir(), \".config\", \"fluid\");\n}\n\nexport function getConfigFilePath(): string {\n return join(getConfigDir(), \"config.json\");\n}\n","/**\n * Read/write config.json with atomic writes and safe defaults\n */\n\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { randomBytes } from \"node:crypto\";\nimport { type FluidConfig, createDefaultConfig } from \"./types.js\";\nimport { getConfigFilePath } from \"./paths.js\";\n\nexport function readConfig(): FluidConfig {\n const configPath = getConfigFilePath();\n\n if (!existsSync(configPath)) {\n return createDefaultConfig();\n }\n\n const raw = readFileSync(configPath, \"utf-8\");\n\n let parsed: Partial<FluidConfig>;\n try {\n parsed = JSON.parse(raw) as Partial<FluidConfig>;\n } catch {\n // Corrupted config — fall back to defaults rather than crashing\n return createDefaultConfig();\n }\n\n // Merge with defaults to handle schema evolution\n return {\n ...createDefaultConfig(),\n ...parsed,\n };\n}\n\nexport function writeConfig(config: FluidConfig): void {\n const configPath = getConfigFilePath();\n const dir = dirname(configPath);\n\n // 0o700: owner-only access so other users cannot enumerate the config dir\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n\n // Atomic write: write to a temp file in the same directory, then rename.\n // rename() is a POSIX atomic operation — a crash mid-write will never leave\n // a partially-written config.json. The temp file must be on the same\n // filesystem as the destination for rename() to succeed.\n const suffix = randomBytes(6).toString(\"hex\");\n const tmpPath = join(dir, `.fluid-config-${suffix}.tmp`);\n\n try {\n writeFileSync(tmpPath, JSON.stringify(config, null, 2) + \"\\n\", {\n encoding: \"utf-8\",\n // NOTE: mode: 0o600 restricts to owner read/write on POSIX (macOS, Linux).\n // On Windows, Node.js ignores this option — NTFS permissions are not\n // set via the mode parameter. Windows file protection would require\n // icacls or Windows security descriptor APIs.\n mode: 0o600,\n });\n renameSync(tmpPath, configPath);\n } catch (err) {\n // Clean up the temp file if something went wrong before the rename\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore cleanup errors — the temp file may not exist yet\n }\n throw err;\n }\n}\n\nexport function updateConfig(\n updater: (config: FluidConfig) => FluidConfig,\n): FluidConfig {\n const config = readConfig();\n const updated = updater(config);\n writeConfig(updated);\n return updated;\n}\n","/**\n * Project-level config (.fluidrc) reader.\n *\n * Walks upward from a starting directory to find a `.fluidrc` file.\n * The file is JSON with a `profile` field that maps to a profile name\n * in `~/.fluid/config.json`.\n *\n * This allows portal repos to pin themselves to a specific CLI profile\n * so that `fluid portal pull` / `push` always use the correct company\n * credentials, regardless of which profile is globally active.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nexport interface ProjectRcConfig {\n readonly profile: string;\n}\n\nconst RC_FILENAME = \".fluidrc\";\n\n/** Cached result keyed by the starting directory. */\nlet cachedResult: { dir: string; config: ProjectRcConfig | null } | undefined;\n\n/**\n * Walk upward from `startDir` looking for a `.fluidrc` file.\n *\n * Returns the parsed config when found, or `null` if no valid `.fluidrc`\n * exists in any ancestor directory.\n *\n * The result is cached per starting directory for the lifetime of the\n * process — CLI commands are short-lived so stale-cache is not a concern.\n */\nexport function findProjectConfig(startDir: string): ProjectRcConfig | null {\n if (cachedResult && cachedResult.dir === startDir) {\n return cachedResult.config;\n }\n\n let dir = startDir;\n while (true) {\n const rcPath = join(dir, RC_FILENAME);\n if (existsSync(rcPath)) {\n try {\n const raw = readFileSync(rcPath, \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<ProjectRcConfig>;\n if (typeof parsed.profile === \"string\" && parsed.profile.length > 0) {\n const config: ProjectRcConfig = { profile: parsed.profile };\n cachedResult = { dir: startDir, config };\n return config;\n }\n } catch {\n // Malformed .fluidrc — treat as absent\n }\n\n // Found a .fluidrc but it was invalid or empty — stop searching\n // upward to avoid confusing inheritance from a parent directory.\n cachedResult = { dir: startDir, config: null };\n return null;\n }\n\n const parent = dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n\n cachedResult = { dir: startDir, config: null };\n return null;\n}\n\n/** Reset the cache. Exposed for tests only. */\nexport function _resetProjectConfigCache(): void {\n cachedResult = undefined;\n}\n","/**\n * Token storage and retrieval from config\n */\n\nimport { readConfig } from \"../config/config.js\";\nimport { findProjectConfig } from \"../config/project-config.js\";\nimport type { FluidProfile } from \"../config/types.js\";\n\n/**\n * Get the auth token for a named profile, or the active profile when omitted.\n *\n * Resolution order (when `profileName` is omitted):\n * 1. Project-level `.fluidrc` profile (walks upward from cwd)\n * 2. Global active profile from `~/.fluid/config.json`\n *\n * When `profileName` is provided explicitly (e.g. via `--profile` flag),\n * it takes precedence over both `.fluidrc` and the active profile.\n */\nexport function getAuthToken(profileName?: string): string | null {\n const config = readConfig();\n\n // Explicit profile name takes precedence\n if (profileName) {\n const profile = config.profiles[profileName];\n return profile?.token ?? null;\n }\n\n // Project-level .fluidrc profile\n const projectConfig = findProjectConfig(process.cwd());\n if (projectConfig?.profile) {\n const profile = config.profiles[projectConfig.profile];\n if (profile) return profile.token;\n }\n\n // Fall back to global active profile\n if (!config.activeProfile) return null;\n const profile = config.profiles[config.activeProfile];\n return profile?.token ?? null;\n}\n\n/**\n * Get the active profile, or null if not logged in\n */\nexport function getActiveProfile(): FluidProfile | null {\n const config = readConfig();\n if (!config.activeProfile) return null;\n\n return config.profiles[config.activeProfile] ?? null;\n}\n\n/**\n * List all stored profile names\n */\nexport function listProfileNames(): string[] {\n const config = readConfig();\n return Object.keys(config.profiles);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"token-C9SrlBRK.mjs","names":[],"sources":["../src/utils/result.ts","../src/auth/fluid-api.ts","../src/config/types.ts","../src/config/paths.ts","../src/config/config.ts","../src/config/project-config.ts","../src/auth/token.ts"],"sourcesContent":["/**\n * Result type utilities for type-safe error handling\n *\n * The Result<T, E> pattern provides a discriminated union for fallible operations,\n * enabling exhaustive handling without try/catch blocks.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Result type - discriminated union for success/failure\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Success<T> {\n readonly success: true;\n readonly value: T;\n}\n\nexport interface Failure<E> {\n readonly success: false;\n readonly error: E;\n}\n\nexport type Result<T, E = Error> = Success<T> | Failure<E>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Constructor functions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function success<T>(value: T): Success<T> {\n return { success: true, value };\n}\n\nexport function failure<E>(error: E): Failure<E> {\n return { success: false, error };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Type guards\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function isSuccess<T, E>(result: Result<T, E>): result is Success<T> {\n return result.success === true;\n}\n\nexport function isFailure<T, E>(result: Result<T, E>): result is Failure<E> {\n return result.success === false;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Utility functions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function tryCatch<T>(fn: () => T): Result<T, Error> {\n try {\n return success(fn());\n } catch (error) {\n return failure(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\nexport async function tryCatchAsync<T>(\n fn: () => Promise<T>,\n): Promise<Result<T, Error>> {\n try {\n return success(await fn());\n } catch (error) {\n return failure(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\nexport function unwrap<T, E>(result: Result<T, E>): T {\n if (isSuccess(result)) return result.value;\n // Always throw an Error instance so V8 attaches a stack trace and\n // `instanceof Error` checks in catch blocks work as expected.\n if (result.error instanceof Error) throw result.error;\n throw new Error(\n typeof result.error === \"object\" && result.error !== null\n ? JSON.stringify(result.error)\n : String(result.error),\n );\n}\n\nexport function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {\n if (isSuccess(result)) return result.value;\n return defaultValue;\n}\n\nexport function mapResult<T, U, E>(\n result: Result<T, E>,\n fn: (value: T) => U,\n): Result<U, E> {\n if (isSuccess(result)) return success(fn(result.value));\n return result;\n}\n\nexport function mapError<T, E, F>(\n result: Result<T, E>,\n fn: (error: E) => F,\n): Result<T, F> {\n if (isFailure(result)) return failure(fn(result.error));\n return result;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error narrowing utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function isError(value: unknown): value is Error {\n return value instanceof Error;\n}\n\nexport function isNodeError(value: unknown): value is NodeJS.ErrnoException {\n return value instanceof Error && \"code\" in value;\n}\n\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === \"string\") return error;\n return String(error);\n}\n","/**\n * Fluid API client for authentication operations\n */\n\nimport type { CliError } from \"../utils/errors.js\";\nimport { type Result, success, failure } from \"../utils/result.js\";\n\nconst API_TIMEOUT_MS = 10_000;\n\nfunction getFluidApiBase(): string {\n return process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n}\n\nfunction makeSignal(): { signal: AbortSignal; cleanup: () => void } {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);\n return { signal: controller.signal, cleanup: () => clearTimeout(timer) };\n}\n\nexport interface FluidApiError extends CliError {\n readonly code: string;\n readonly message: string;\n readonly details?: string;\n}\n\nexport const FLUID_API_ERROR = {\n INVALID_TOKEN: {\n code: \"INVALID_TOKEN\",\n message: \"Token is invalid or expired\",\n },\n API_UNREACHABLE: {\n code: \"API_UNREACHABLE\",\n message: \"Could not reach the Fluid API\",\n },\n UUID_NOT_FOUND: {\n code: \"UUID_NOT_FOUND\",\n message: \"Verification session not found\",\n },\n CODE_EXPIRED: {\n code: \"CODE_EXPIRED\",\n message: \"Verification code has expired — please request a new one\",\n },\n INVALID_CODE: {\n code: \"INVALID_CODE\",\n message: \"Invalid verification code\",\n },\n COMPANY_NOT_FOUND: {\n code: \"COMPANY_NOT_FOUND\",\n message: \"Company not found\",\n },\n SWITCH_FAILED: {\n code: \"SWITCH_FAILED\",\n message: \"Failed to switch company\",\n },\n} as const;\n\nfunction createApiError(\n template: { readonly code: string; readonly message: string },\n details?: string,\n): FluidApiError {\n return { code: template.code, message: template.message, details };\n}\n\nexport interface CompanyInfo {\n readonly name: string;\n}\n\nexport interface UserCompany {\n readonly id: number;\n readonly name: string;\n}\n\nexport interface SwitchCompanyResult {\n readonly companyId: number;\n readonly companyName: string;\n readonly fluidShop: string;\n readonly jwt: string;\n}\n\nexport interface MfaResponse {\n readonly uuid: string;\n readonly expiresAt: string;\n}\n\nexport interface CompanyChoice {\n readonly id: number;\n readonly name: string;\n readonly shopName: string;\n readonly jwt: string;\n}\n\nexport interface ConfirmMfaResponse {\n readonly authType: string;\n readonly companies: CompanyChoice[];\n}\n\n/**\n * Validate a token against the Fluid API and return company info\n */\nexport async function validateToken(\n token: string,\n): Promise<Result<CompanyInfo, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/api/company/v1/companies/me`,\n {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${token}`,\n },\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n data?: { company?: { name?: string } };\n };\n const name = data?.data?.company?.name;\n if (!name) {\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n \"Unexpected response shape from /companies/me\",\n ),\n );\n }\n return success({ name });\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}: Check that your token is valid and non-expired.`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Send a multi-factor authentication code to the given email address.\n * Always returns 201 from the API (anti-enumeration).\n */\nexport async function sendMfa(\n email: string,\n): Promise<Result<MfaResponse, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/api/authentication/send_mfa`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email }),\n signal,\n },\n );\n\n if (response.status === 201) {\n const data = (await response.json()) as {\n multi_factor_authentication?: {\n uuid?: string;\n verification_code_expires_at?: string;\n };\n };\n const uuid = data.multi_factor_authentication?.uuid;\n const expiresAt =\n data.multi_factor_authentication?.verification_code_expires_at;\n if (!uuid || !expiresAt) {\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n \"Unexpected response shape from /send_mfa\",\n ),\n );\n }\n return success({ uuid, expiresAt });\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Confirm a multi-factor authentication code and retrieve company JWTs.\n */\nexport async function confirmMfa(\n uuid: string,\n code: string,\n): Promise<Result<ConfirmMfaResponse, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/api/authentication/confirm_mfa`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n uuid,\n multi_factor_authentication: { verification_code: code },\n }),\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n authenticated?: boolean;\n auth_type?: string;\n companies?:\n | {\n id: number;\n name: string;\n shop_name: string;\n jwt: string;\n }[]\n | null;\n };\n if (!data.authenticated) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_CODE,\n \"Authentication was not confirmed by the server\",\n ),\n );\n }\n const companies = Array.isArray(data.companies) ? data.companies : [];\n return success({\n authType: data.auth_type ?? \"\",\n companies: companies.map((c) => ({\n id: c.id,\n name: c.name,\n shopName: c.shop_name,\n jwt: c.jwt,\n })),\n });\n }\n\n if (response.status === 404) {\n return failure(createApiError(FLUID_API_ERROR.UUID_NOT_FOUND));\n }\n if (response.status === 410) {\n return failure(createApiError(FLUID_API_ERROR.CODE_EXPIRED));\n }\n if (response.status === 422) {\n return failure(createApiError(FLUID_API_ERROR.INVALID_CODE));\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Fetch all companies the authenticated user has access to.\n */\nexport async function fetchUserCompanies(\n token: string,\n): Promise<Result<UserCompany[], FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(`${getFluidApiBase()}/api/me`, {\n method: \"GET\",\n headers: { Authorization: `Bearer ${token}` },\n signal,\n });\n\n if (response.ok) {\n const data = (await response.json()) as {\n companies?: { id: number; name: string }[];\n };\n const companies = Array.isArray(data.companies) ? data.companies : [];\n return success(companies.map((c) => ({ id: c.id, name: c.name })));\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Switch to a different company and receive a new JWT.\n *\n * Response shape (from @fluid-app/auth SwitchCompanyResponse):\n * { company: { id, name, fluid_shop, jwt }, meta: { request_id, timestamp } }\n */\nexport async function switchCompany(\n token: string,\n companyId: number,\n): Promise<Result<SwitchCompanyResult, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/api/authentication/company/${companyId}/switch`,\n {\n method: \"PUT\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n company?: {\n id: number;\n name: string;\n fluid_shop: string;\n jwt: string;\n };\n };\n const company = data.company;\n if (!company?.jwt) {\n return failure(\n createApiError(\n FLUID_API_ERROR.SWITCH_FAILED,\n \"Unexpected response shape from /switch\",\n ),\n );\n }\n return success({\n companyId: company.id,\n companyName: company.name,\n fluidShop: company.fluid_shop,\n jwt: company.jwt,\n });\n }\n\n if (response.status === 404) {\n return failure(\n createApiError(\n FLUID_API_ERROR.COMPANY_NOT_FOUND,\n `Company ${companyId} not found`,\n ),\n );\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.SWITCH_FAILED,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n","/**\n * Configuration types for the Fluid CLI\n */\n\nexport interface FluidProfile {\n readonly name: string;\n readonly token: string;\n readonly companyName: string;\n readonly storedAt: string; // ISO-8601\n}\n\nexport interface FluidConfig {\n activeProfile: string | null;\n profiles: Record<string, FluidProfile>;\n plugins: Record<string, unknown>;\n /**\n * Allow-list of plugin package names.\n *\n * - `null` (default) — auto-discover and load all `@fluid-app/fluid-cli-*`\n * and `*-cli-commands` packages found in `node_modules` or the pnpm\n * workspace. Only `@fluid-app`-scoped packages matching the naming\n * convention are eligible; no third-party code is loaded.\n * - `string[]` — only load plugins whose names appear in the array.\n * Set to `[]` to disable all plugins.\n */\n enabledPlugins: string[] | null;\n}\n\nexport function createDefaultConfig(): FluidConfig {\n return {\n activeProfile: null,\n profiles: {},\n plugins: {},\n enabledPlugins: null, // auto-discover all plugins\n };\n}\n","/**\n * XDG-compliant config directory resolution\n *\n * Priority:\n * 1. FLUID_CONFIG_DIR env var (explicit override)\n * 2. ~/.fluid/ on macOS (convention for CLI tools)\n * 3. %APPDATA%/fluid/ on Windows\n * 4. $XDG_CONFIG_HOME/fluid/ on Linux (XDG spec)\n * 5. ~/.config/fluid/ fallback on Linux\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport function getConfigDir(): string {\n const envOverride = process.env[\"FLUID_CONFIG_DIR\"];\n if (envOverride) return envOverride;\n\n if (process.platform === \"darwin\") {\n return join(homedir(), \".fluid\");\n }\n\n if (process.platform === \"win32\") {\n const appData =\n process.env[\"APPDATA\"] ?? join(homedir(), \"AppData\", \"Roaming\");\n return join(appData, \"fluid\");\n }\n\n const xdgConfig = process.env[\"XDG_CONFIG_HOME\"];\n if (xdgConfig) return join(xdgConfig, \"fluid\");\n\n return join(homedir(), \".config\", \"fluid\");\n}\n\nexport function getConfigFilePath(): string {\n return join(getConfigDir(), \"config.json\");\n}\n","/**\n * Read/write config.json with atomic writes and safe defaults\n */\n\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { randomBytes } from \"node:crypto\";\nimport { type FluidConfig, createDefaultConfig } from \"./types.js\";\nimport { getConfigFilePath } from \"./paths.js\";\n\nexport function readConfig(): FluidConfig {\n const configPath = getConfigFilePath();\n\n if (!existsSync(configPath)) {\n return createDefaultConfig();\n }\n\n const raw = readFileSync(configPath, \"utf-8\");\n\n let parsed: Partial<FluidConfig>;\n try {\n parsed = JSON.parse(raw) as Partial<FluidConfig>;\n } catch {\n // Corrupted config — fall back to defaults rather than crashing\n return createDefaultConfig();\n }\n\n // Merge with defaults to handle schema evolution\n return {\n ...createDefaultConfig(),\n ...parsed,\n };\n}\n\nexport function writeConfig(config: FluidConfig): void {\n const configPath = getConfigFilePath();\n const dir = dirname(configPath);\n\n // 0o700: owner-only access so other users cannot enumerate the config dir\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n\n // Atomic write: write to a temp file in the same directory, then rename.\n // rename() is a POSIX atomic operation — a crash mid-write will never leave\n // a partially-written config.json. The temp file must be on the same\n // filesystem as the destination for rename() to succeed.\n const suffix = randomBytes(6).toString(\"hex\");\n const tmpPath = join(dir, `.fluid-config-${suffix}.tmp`);\n\n try {\n writeFileSync(tmpPath, JSON.stringify(config, null, 2) + \"\\n\", {\n encoding: \"utf-8\",\n // NOTE: mode: 0o600 restricts to owner read/write on POSIX (macOS, Linux).\n // On Windows, Node.js ignores this option — NTFS permissions are not\n // set via the mode parameter. Windows file protection would require\n // icacls or Windows security descriptor APIs.\n mode: 0o600,\n });\n renameSync(tmpPath, configPath);\n } catch (err) {\n // Clean up the temp file if something went wrong before the rename\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore cleanup errors — the temp file may not exist yet\n }\n throw err;\n }\n}\n\nexport function updateConfig(\n updater: (config: FluidConfig) => FluidConfig,\n): FluidConfig {\n const config = readConfig();\n const updated = updater(config);\n writeConfig(updated);\n return updated;\n}\n","/**\n * Project-level config (.fluidrc) reader.\n *\n * Walks upward from a starting directory to find a `.fluidrc` file.\n * The file is JSON with a `profile` field that maps to a profile name\n * in `~/.fluid/config.json`.\n *\n * This allows portal repos to pin themselves to a specific CLI profile\n * so that `fluid portal pull` / `push` always use the correct company\n * credentials, regardless of which profile is globally active.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nexport interface ProjectRcConfig {\n readonly profile: string;\n}\n\nconst RC_FILENAME = \".fluidrc\";\n\n/** Cached result keyed by the starting directory. */\nlet cachedResult: { dir: string; config: ProjectRcConfig | null } | undefined;\n\n/**\n * Walk upward from `startDir` looking for a `.fluidrc` file.\n *\n * Returns the parsed config when found, or `null` if no valid `.fluidrc`\n * exists in any ancestor directory.\n *\n * The result is cached per starting directory for the lifetime of the\n * process — CLI commands are short-lived so stale-cache is not a concern.\n */\nexport function findProjectConfig(startDir: string): ProjectRcConfig | null {\n if (cachedResult && cachedResult.dir === startDir) {\n return cachedResult.config;\n }\n\n let dir = startDir;\n while (true) {\n const rcPath = join(dir, RC_FILENAME);\n if (existsSync(rcPath)) {\n try {\n const raw = readFileSync(rcPath, \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<ProjectRcConfig>;\n if (typeof parsed.profile === \"string\" && parsed.profile.length > 0) {\n const config: ProjectRcConfig = { profile: parsed.profile };\n cachedResult = { dir: startDir, config };\n return config;\n }\n } catch {\n // Malformed .fluidrc — treat as absent\n }\n\n // Found a .fluidrc but it was invalid or empty — stop searching\n // upward to avoid confusing inheritance from a parent directory.\n cachedResult = { dir: startDir, config: null };\n return null;\n }\n\n const parent = dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n\n cachedResult = { dir: startDir, config: null };\n return null;\n}\n\n/** Reset the cache. Exposed for tests only. */\nexport function _resetProjectConfigCache(): void {\n cachedResult = undefined;\n}\n","/**\n * Token storage and retrieval from config\n */\n\nimport { readConfig } from \"../config/config.js\";\nimport { findProjectConfig } from \"../config/project-config.js\";\nimport type { FluidProfile } from \"../config/types.js\";\n\n/**\n * Get the auth token for a named profile, or the active profile when omitted.\n *\n * Resolution order (when `profileName` is omitted):\n * 1. Project-level `.fluidrc` profile (walks upward from cwd)\n * 2. Global active profile from `~/.fluid/config.json`\n *\n * When `profileName` is provided explicitly (e.g. via `--profile` flag),\n * it takes precedence over both `.fluidrc` and the active profile.\n */\nexport function getAuthToken(profileName?: string): string | null {\n const config = readConfig();\n\n // Explicit profile name takes precedence\n if (profileName) {\n const profile = config.profiles[profileName];\n return profile?.token ?? null;\n }\n\n // Project-level .fluidrc profile\n const projectConfig = findProjectConfig(process.cwd());\n if (projectConfig?.profile) {\n const profile = config.profiles[projectConfig.profile];\n if (profile) return profile.token;\n }\n\n // Fall back to global active profile\n if (!config.activeProfile) return null;\n const profile = config.profiles[config.activeProfile];\n return profile?.token ?? null;\n}\n\n/**\n * Get the active profile, or null if not logged in\n */\nexport function getActiveProfile(): FluidProfile | null {\n const config = readConfig();\n if (!config.activeProfile) return null;\n\n return config.profiles[config.activeProfile] ?? null;\n}\n\n/**\n * List all stored profile names\n */\nexport function listProfileNames(): string[] {\n const config = readConfig();\n return Object.keys(config.profiles);\n}\n"],"mappings":"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"}
|