UNPKG

@teamwork/get-bearer-token

Version:

CLI tool to obtain bearer tokens for Teamwork API using OAuth flow

255 lines (221 loc) 7.84 kB
import dotenv from "dotenv"; import inquirer from "inquirer"; import chalk from "chalk"; import getAvailablePort from "get-port"; // Load environment variables dotenv.config(); /** * Get Teamwork credentials from environment variables or prompt user * @param {number} [suggestedPort] - Port to suggest for redirect URI * @returns {Promise<import('./types.js').TeamworkCredentials>} */ export async function getCredentials(suggestedPort = 3000) { const clientId = process.env.CLIENT_ID; const clientSecret = process.env.CLIENT_SECRET; const redirectUri = process.env.REDIRECT_URI; const siteName = process.env.TEAMWORK_SITE_NAME; const region = process.env.TEAMWORK_REGION; // If all credentials are in env, use them if (clientId && clientSecret && redirectUri) { return { clientId, clientSecret, redirectUri, siteName: siteName || undefined, region: region || undefined, }; } // Otherwise, prompt for missing credentials console.log("\n🔧 Setting up Teamwork API credentials...\n"); const setupQuestions = []; // Ask for Teamwork URL (unless site name is in env) if (!siteName) { console.log(chalk.gray("Enter your Teamwork URL:")); console.log(chalk.gray(" Examples: https://mycompany.teamwork.com")); console.log(chalk.gray(" https://custom.domain.com\n")); setupQuestions.push({ type: "input", name: "teamworkUrl", message: "Teamwork URL:", validate: (input) => { if (!input || input.trim() === "") { return "Teamwork URL is required"; } const trimmed = input.trim(); // Allow URLs with or without https:// let url; try { if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) { url = new URL(trimmed); } else { url = new URL("https://" + trimmed); } if (!url.hostname) { return "Please enter a valid URL"; } return true; } catch (error) { return "Please enter a valid URL"; } }, }); } // Get setup info first const setupAnswers = setupQuestions.length > 0 ? await inquirer.prompt(setupQuestions) : {}; // Extract hostname and determine site info from URL let finalSiteName = siteName; let finalRegion = region; let teamworkHost; if (setupAnswers.teamworkUrl) { const urlInput = setupAnswers.teamworkUrl.trim(); let url; try { if (urlInput.startsWith("http://") || urlInput.startsWith("https://")) { url = new URL(urlInput); } else { url = new URL("https://" + urlInput); } teamworkHost = url.hostname; // Try to determine region and site name from standard teamwork domains if (teamworkHost.endsWith(".teamwork.com")) { const parts = teamworkHost.split("."); if (parts.length >= 3) { // Handle cases like mysite.eu.teamwork.com or mysite.staging.teamwork.com if (parts[parts.length - 3] === "eu") { finalRegion = finalRegion || "eu"; finalSiteName = finalSiteName || parts[parts.length - 4]; } else if (parts[parts.length - 3] === "staging") { finalRegion = finalRegion || "staging"; finalSiteName = finalSiteName || parts[parts.length - 4]; } else { // Standard US domain like mysite.teamwork.com finalRegion = finalRegion || "us"; finalSiteName = finalSiteName || parts[parts.length - 3]; } } } else { // Custom domain - we need to ask for region since we can't detect it finalSiteName = finalSiteName || teamworkHost; // Don't set finalRegion yet - we'll ask for it below } } catch (error) { // Fallback - shouldn't happen due to validation teamworkHost = urlInput; finalSiteName = finalSiteName || urlInput; } } // If we detected a custom domain and don't have a region, ask for it if (teamworkHost && !teamworkHost.endsWith(".teamwork.com") && !finalRegion) { console.log(chalk.yellow(`\n🌐 Custom domain detected: ${teamworkHost}`)); console.log(chalk.gray("Please specify which Teamwork region your custom domain points to:\n")); const regionAnswer = await inquirer.prompt([ { type: "list", name: "region", message: "Select region:", choices: [ { name: "US (teamwork.com infrastructure)", value: "us", }, { name: "EU (eu.teamwork.com infrastructure)", value: "eu", }, { name: "Staging (staging.teamwork.com infrastructure)", value: "staging", }, ], default: "us", }, ]); finalRegion = regionAnswer.region; } else { // Use environment variables if no URL was entered finalRegion = finalRegion || "us"; // Build teamwork host from site name and region for env usage if (finalSiteName) { const getTeamworkDomain = (region) => { switch (region) { case "eu": return "eu.teamwork.com"; case "staging": return "staging.teamwork.com"; case "us": default: return "teamwork.com"; } }; teamworkHost = `${finalSiteName}.${getTeamworkDomain(finalRegion)}`; } } // Build the redirect URI using the suggested port const redirectUriToUse = redirectUri || `http://localhost:${suggestedPort}/callback`; // Now show the appropriate Developer Portal URL and setup instructions console.log("\n📋 Setting up your Teamwork OAuth App:\n"); console.log("1. Go to your Teamwork Developer Portal:"); console.log(chalk.cyan(` https://${teamworkHost}/developer`)); console.log("\n2. Create a new app or edit an existing one"); console.log("\n3. Set the Redirect URI to:"); console.log(chalk.green(` ${redirectUriToUse}`)); console.log("\n4. Copy your Client ID and Client Secret from the app\n"); // Now ask for the actual credentials const credentialQuestions = []; if (!clientId) { credentialQuestions.push({ type: "input", name: "clientId", message: "Enter your Client ID:", validate: (input) => input.trim() !== "" || "Client ID is required", }); } if (!clientSecret) { credentialQuestions.push({ type: "password", name: "clientSecret", message: "Enter your Client Secret:", validate: (input) => input.trim() !== "" || "Client Secret is required", }); } const credentialAnswers = credentialQuestions.length > 0 ? await inquirer.prompt(credentialQuestions) : {}; return { clientId: clientId || credentialAnswers.clientId, clientSecret: clientSecret || credentialAnswers.clientSecret, redirectUri: redirectUriToUse, siteName: finalSiteName, region: finalRegion, teamworkHost: teamworkHost, }; } /** * Get an available port, preferring the one from environment or defaulting to 3000 * @returns {Promise<number>} */ export async function getPort() { const preferredPort = parseInt(process.env.PORT || "3000", 10); try { // Try to get the preferred port, or find the next available one const availablePort = await getAvailablePort({ port: preferredPort }); if (availablePort !== preferredPort) { console.log( chalk.yellow( `⚠️ Port ${preferredPort} is in use, using port ${availablePort} instead` ) ); } return availablePort; } catch (error) { console.log( chalk.yellow( `⚠️ Could not find available port, defaulting to ${preferredPort}` ) ); return preferredPort; } }