@teamwork/get-bearer-token
Version:
CLI tool to obtain bearer tokens for Teamwork API using OAuth flow
255 lines (221 loc) • 7.84 kB
JavaScript
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;
}
}