diff --git a/package-lock.json b/package-lock.json index 6eb6093..5250bec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "chalk": "^5.3.0", "cli-table": "^0.3.11", "commander": "^14.0.0", + "open": "^10.2.0", "openai": "^5.0.1", "pdf2pic": "^3.2.0", "prompt-sync": "^4.2.0", @@ -3503,6 +3504,21 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -4129,6 +4145,46 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", + "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -5746,6 +5802,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5785,6 +5856,24 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5845,6 +5934,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -10290,6 +10394,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/openai": { "version": "5.16.0", "resolved": "https://registry.npmjs.org/openai/-/openai-5.16.0.tgz", @@ -11229,6 +11351,18 @@ "node": ">= 18" } }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -12699,6 +12833,21 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 76014fa..2a53eb2 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "chalk": "^5.3.0", "cli-table": "^0.3.11", "commander": "^14.0.0", + "open": "^10.2.0", "openai": "^5.0.1", "pdf2pic": "^3.2.0", "prompt-sync": "^4.2.0", diff --git a/src/commands/login.ts b/src/commands/login.ts index 653a352..f561937 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -1,17 +1,14 @@ import * as commander from 'commander'; import chalk from 'chalk'; +import open from 'open'; import { actionRunner, prompt } from '../utils.js'; import { storeConfig, getEmail } from '../config.js'; -import { exchangeIdAndTkForAccessToken, requestMagicLink } from '../forma.js'; +import { exchangeIdAndTkForAccessToken } from '../forma.js'; import VERSION from '../version.js'; const command = new commander.Command(); -interface Arguments { - email?: string; -} - const parseEmailedFormaMagicLink = (input: string): { id: string; tk: string } => { const parsedUrl = new URL(input); @@ -50,31 +47,12 @@ const parseEmailedFormaMagicLink = (input: string): { id: string; tk: string } = return { id, tk }; }; -const EMAIL_REGEX = - /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; - -const promptForEmail = (isFirstRun = true): string => { - const promptMessage = isFirstRun - ? 'Enter the email address you use to log on to Forma, then press Enter.' - : chalk.yellow("That doesn't look like a valid email address. Please try again."); - console.log(promptMessage); - - const email = prompt('> '); - - if (!EMAIL_REGEX.test(email)) { - return promptForEmail(false); - } else { - return email; - } -}; - const promptForEmailedMagicLink = ( - email: string, errorMessage: string | null = null, ): { id: string; tk: string } => { const promptMessage = errorMessage ? chalk.yellow("That doesn't look like a valid magic link. Please try again.") - : `Copy and paste the magic link sent to you at ${email}, then press Enter.`; + : 'Copy and paste the magic link sent to you, then press Enter.'; console.log(promptMessage); const emailedMagicLink = prompt('> '); @@ -82,29 +60,30 @@ const promptForEmailedMagicLink = ( try { return parseEmailedFormaMagicLink(emailedMagicLink); } catch (e) { - return promptForEmailedMagicLink(email, e); + return promptForEmailedMagicLink(e); } }; command .name('login') .version(VERSION) - .description( - 'Connect Formanator to your Forma account with a magic link. Your email address will be remembered after logging in for the first time.', - ) - .option( - '--email ', - 'The email address to use to log in to Forma. Defaults to the FORMA_EMAIL environment variable, or the email you last used to log in. If no email address is provided, you will be prompted for your email.', - process.env.FORMA_EMAIL || getEmail(), - ) + .description('Connect Formanator to your Forma account with a magic link.') .action( - actionRunner(async (opts: Arguments) => { - const email = opts.email ?? promptForEmail(); - await requestMagicLink(email); + actionRunner(async () => { + console.log(chalk.cyan('\nWe will now open your browser to the Forma login page.')); + console.log('Please enter your email address there and request a magic link.'); + console.log( + 'Once you receive the magic link in your email, come back to this terminal and paste it below.\n', + ); - const { id, tk } = promptForEmailedMagicLink(email); + await open('https://client.joinforma.com/login?type=magic'); + + const { id, tk } = promptForEmailedMagicLink(); const accessToken = await exchangeIdAndTkForAccessToken(id, tk); - storeConfig({ accessToken, email }); + + // Preserve existing email if it was previously stored + const existingEmail = getEmail(); + storeConfig({ accessToken, email: existingEmail }); console.log(chalk.green('You are now logged in! 🥳')); }),