Skip to content

Conversation

@fredrikekelund
Copy link
Contributor

@fredrikekelund fredrikekelund commented Nov 5, 2025

Related issues

Proposed Changes

This PR is based on @youknowriad's work in #1958. It implements a studio auth login command that:

  1. Opens a WordPress.com OAuth2 authorization URL in the browser
  2. The OAuth2 authorization page redirects to a developer.wordpress.com page that renders a convenient button for copying the generated access token
  3. Once copied, the access token can be pasted back to the terminal

Testing Instructions

  1. Check out 156890-ghe-Automattic/wpcom-a8c-themes on your sandbox
  2. Sandbox developer.wordpress.com
  3. Open Studio and log out from WordPress.com
  4. Close Studio
  5. Run npm run cli:build
  6. Run node dist/cli/main.js auth login
  7. Finish the authorization process and paste the token back to the terminal
  8. Open Studio again
  9. Ensure you are logged in to WordPress.com

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

@fredrikekelund fredrikekelund requested review from a team and bcotrim November 5, 2025 15:16
@fredrikekelund fredrikekelund self-assigned this Nov 5, 2025
Comment on lines +19 to +28
cmd = 'PowerShell';
args = [
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Bypass',
'-Command',
'Start',
`"${ url }"`,
];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have yet to test this. This was taken from Sindre Sorhus's open module.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 5, 2025

📊 Performance Test Results

Comparing 28ef7cd vs trunk

site-editor

Metric trunk 28ef7cd Diff Change
load 14493.00 ms 14549.00 ms +56.00 ms 🔴 0.4%

site-startup

Metric trunk 28ef7cd Diff Change
siteCreation 18158.00 ms 22289.00 ms +4131.00 ms 🔴 22.8%
siteStartup 7963.00 ms 7995.00 ms +32.00 ms 🔴 0.4%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change

@fredrikekelund
Copy link
Contributor Author

Working on a test right now.

@fredrikekelund
Copy link
Contributor Author

Looking at how many changes I had to make today, I see that I opened this PR a little early. In any case, it's good for a full review now 👍

@sejas sejas requested a review from Copilot November 6, 2025 12:39
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a CLI authentication command (studio auth login) to allow users to authenticate with WordPress.com directly from the command line. The implementation opens a browser for OAuth authentication and stores the resulting token with user information.

Key changes:

  • Adds @inquirer/prompts dependency for interactive token input
  • Implements OAuth-based login flow with browser integration
  • Enhances auth token storage to include expiration time and user details

Reviewed Changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
package.json, package-lock.json Added @inquirer/prompts dependency for interactive CLI prompts
common/logger-actions.ts Added AuthCommandLoggerAction enum for login/logout tracking
common/lib/oauth.ts Made redirect URI configurable for CLI authentication flow
cli/lib/i18n.ts Extracted getAppLocale() function for reusability
cli/lib/browser.ts New utility to open default browser across platforms
cli/lib/appdata.ts Enhanced auth token schema with expiration, email, and display name
cli/lib/api.ts Added getUserInfo() function to fetch user details from WordPress.com
cli/index.ts Registered auth command group with login subcommand
cli/commands/auth/login.ts Main login command implementation
cli/commands/auth/tests/login.test.ts Comprehensive test coverage for login command
cli/lib/tests/snapshots.test.ts, appdata.test.ts Updated test mocks to include new auth token fields
Comments suppressed due to low confidence (1)

cli/lib/browser.ts:2

  • Unused import sprintf.
import { __, sprintf } from '@wordpress/i18n';

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

email: user.email,
displayName: user.display_name,
expiresIn: twoWeeksInSeconds,
expirationTime: now.getTime() + twoWeeksInSeconds,
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expirationTime calculation is incorrect. twoWeeksInSeconds is in seconds (1209600), but getTime() returns milliseconds. This should be now.getTime() + twoWeeksInSeconds * 1000 to convert seconds to milliseconds, or the calculation should use twoWeeksInSeconds * 1000 directly.

Suggested change
expirationTime: now.getTime() + twoWeeksInSeconds,
expirationTime: now.getTime() + twoWeeksInSeconds * 1000,

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +11
let cmd: string;
let args: string[];
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variables cmd and args are not initialized and will be undefined if process.platform doesn't match 'darwin', 'win32', or 'linux'. This will cause the spawn() call on line 37 to fail. Add a default case to the switch statement that throws an error for unsupported platforms.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,49 @@
import { spawn } from 'child_process';
import { __, sprintf } from '@wordpress/i18n';
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sprintf function is imported but never used in this file. Remove the unused import.

Suggested change
import { __, sprintf } from '@wordpress/i18n';
import { __ } from '@wordpress/i18n';

Copilot uses AI. Check for mistakes.
Comment on lines 3 to 4
import { LoggerError } from 'cli/logger';

Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import LoggerError.

Suggested change
import { LoggerError } from 'cli/logger';

Copilot uses AI. Check for mistakes.
Copy link
Member

@sejas sejas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I confirm that the login works as expected, and I was able to authenticate by copying and pasting the token. Here are a couple of suggestions to improve the experience:

  • We could avoid hiding the token so the user can see they pasted the correct value.
  • We could automatically try three times when the user enters an invalid token, similarly to SSH connections. Currently the feedback is very rough ✖ Failed to fetch user info: The OAuth2 token is invalid., we could at least invite the user to try again.
Image


export enum AuthCommandLoggerAction {
LOGIN = 'login',
LOGOUT = 'logout',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We don't really need the logout at this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll move this to #2027

const userData = await readAppdata();

const now = new Date();
const twoWeeksInSeconds = 2 * 7 * 24 * 60 * 60;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just saw that the expiresIn in appData is 1209600 seconds or two weeks, but I always thought the token was valid for months. 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is governed by the back-end. We could look into this further if we think two weeks is too short

@youknowriad
Copy link
Contributor

I like how simple this is. Good work :)

@claude
Copy link

claude bot commented Nov 17, 2025

Claude finished @fredrikekelund's task —— View job


Code Review Complete ✓

I've performed a comprehensive code review of the auth login command implementation. Overall, this is a well-structured implementation with good test coverage. Below are my findings organized by priority and category.


🔴 Critical Issues

1. Uninitialized Variables in browser.ts (Platform Compatibility Bug)

Location: cli/lib/browser.ts:9-10

Variables cmd and args are declared but not initialized. If process.platform doesn't match any case (which shouldn't happen, but TypeScript doesn't know that), these will be undefined and cause spawn() to fail.

Impact: Type safety issue, potential runtime error
Recommendation: Add a default case or initialize variables with type assertions

let cmd: string;
let args: string[];

switch (platform) {
  // ... cases ...
  default:
    throw new Error(__('Unsupported platform'));
}

2. Windows Path Compatibility Not Verified

Location: cli/lib/appdata.ts:70-78

The getAppdataDirectory() function only handles Windows and assumes macOS path structure for all other platforms (including Linux). This could fail on Linux systems.

Current code:

export function getAppdataDirectory(): string {
	if ( process.platform === 'win32' ) {
		if ( ! process.env.APPDATA ) {
			throw new LoggerError( __( 'Studio config file path not found.' ) );
		}
		return path.join( process.env.APPDATA, 'Studio' );
	}
	// Assumes macOS structure for everything else
	return path.join( os.homedir(), 'Library', 'Application Support', 'Studio' );
}

Recommendation: Add explicit Linux support:

if (process.platform === 'win32') {
  // Windows path
} else if (process.platform === 'darwin') {
  return path.join(os.homedir(), 'Library', 'Application Support', 'Studio');
} else {
  // Linux/other
  return path.join(os.homedir(), '.config', 'Studio');
}

🟡 High Priority Issues

3. Unused Import in browser.ts

Location: cli/lib/browser.ts:2

sprintf is imported but never used in the file.

Recommendation: Remove unused import:

import { __ } from '@wordpress/i18n';

4. Token Validation Error Messages Need Improvement

Location: cli/commands/auth/login.ts:61-64

When authentication fails, the error message is generic and doesn't provide actionable feedback. As noted in the review comments, the UX could be improved with:

  • More specific error messages
  • Retry mechanism (similar to SSH's 3 attempts)
  • Token visibility option

Current:

catch ( error ) {
	logger.reportError( new LoggerError( __( 'Authentication failed. Please try again.' ) ) );
	return;
}

Recommendation: Enhance with retry logic and better error messages from the API response.

5. Missing Error Context in Token Validation

Location: cli/commands/auth/login.ts:61-64

When getUserInfo() fails, the original error is discarded. This makes debugging difficult.

Recommendation:

catch ( error ) {
	const message = error instanceof LoggerError 
		? error.message 
		: __( 'Authentication failed. Please try again.' );
	logger.reportError( new LoggerError( message, error ) );
	return;
}

🟢 Medium Priority Issues

6. Silent Early Return on Already Authenticated

Location: cli/commands/auth/login.ts:25-27

When a user is already authenticated, the command exits silently after a success message. It might be helpful to show the current user info.

Recommendation:

try {
	const token = await getAuthToken();
	logger.reportSuccess( 
		sprintf(__( 'Already authenticated as %s (%s)' ), token.displayName, token.email)
	);
	return;
}

7. Token Expiration Time Documentation

Location: cli/commands/auth/login.ts:75-76

As noted in review comments, the 2-week expiration might be confusing if users expect longer-lived tokens. Consider adding a comment explaining this is server-controlled.

// Token lifetime is controlled by the WordPress.com OAuth server
expiresIn: DEFAULT_TOKEN_LIFETIME_MS / 1000,
expirationTime: Date.now() + DEFAULT_TOKEN_LIFETIME_MS,

8. Console.log Instead of Logger

Location: cli/commands/auth/login.ts:49-52

Using console.log directly instead of the logger breaks consistency.

Recommendation: Use the logger for all output:

logger.reportInfo(
	__( 'Please complete authentication in your browser and paste the generated token here.' )
);

🔵 Code Quality & Best Practices

9. Redundant Type Annotations

Location: cli/commands/auth/login.ts:54-55

Type annotations using Awaited<ReturnType<>> are verbose when types could be inferred.

let accessToken: Awaited< ReturnType< typeof input > >;
let user: Awaited< ReturnType< typeof getUserInfo > >;

Recommendation: Let TypeScript infer or use explicit types:

let accessToken: string;
let user: { ID: number; email: string; display_name: string };

10. Magic String for Redirect URI

Location: cli/commands/auth/login.ts:19

The CLI redirect URI is a magic string that should be in constants.

Recommendation: Move to common/constants.ts:

export const CLI_REDIRECT_URI = 'https://developer.wordpress.com/copy-oauth-token';

11. Browser Error Handling Could Be More Specific

Location: cli/commands/auth/login.ts:40-47

Browser opening failure uses a generic error wrapper. Consider preserving the original error message.


✅ Security Review

12. OAuth Flow Implementation - Secure ✓

The OAuth implementation follows best practices:

  • Uses authorization code flow with redirect
  • Tokens stored with expiration metadata
  • Token validation before use
  • No hardcoded credentials

13. Token Storage - Secure ✓

  • Tokens stored in user-specific appdata directory
  • File locking mechanism prevents race conditions
  • Expiration time tracked and validated

14. Input Validation - Good ✓

  • User info response validated with Zod schema
  • Token format validated by API
  • Error boundaries in place

Minor recommendation: Consider adding rate limiting for failed authentication attempts to prevent brute force.


🧪 Test Coverage Review

15. Excellent Test Coverage ✓

The test suite (cli/commands/auth/tests/login.test.ts) covers:

  • Happy path (successful login)
  • Already authenticated scenario
  • Browser opening failure
  • API errors
  • Lock/unlock failures
  • Locale handling

Test quality: Very good with proper mocking and assertion structure.

Minor gap: No tests for network timeouts or malformed tokens (though these may be handled by integration tests).


📚 Documentation

16. Missing JSDoc Comments

Location: cli/commands/auth/login.ts:21, cli/lib/browser.ts:7

Public functions lack JSDoc documentation.

Recommendation: Add JSDoc:

/**
 * Runs the authentication login command.
 * Checks for existing authentication, opens browser for OAuth flow,
 * prompts for token, validates it, and stores user credentials.
 * 
 * @throws {LoggerError} If authentication or token storage fails
 */
export async function runCommand(): Promise<void>

17. CLAUDE.md Updates Not Required

The implementation follows existing patterns and doesn't introduce new architectural concepts, so no updates to CLAUDE.md needed.


🎯 Cross-Platform Testing Recommendations

Based on the code review, the following areas need testing on both macOS and Windows:

  1. Browser opening (cli/lib/browser.ts) - Windows uses PowerShell, macOS uses open
  2. Path handling in getAppdataDirectory() - Different on Windows vs macOS
  3. File locking mechanism - Ensure works on both platforms
  4. Terminal input with @inquirer/prompts - Verify token input works in cmd.exe, PowerShell, and Terminal.app

📊 Performance Review

18. No Performance Concerns ✓

  • Async operations properly awaited
  • No unnecessary blocking operations
  • File I/O uses atomic writes
  • Lock mechanism prevents race conditions

🎉 Strengths

  1. Clean architecture - Well-separated concerns (auth, browser, API)
  2. Error handling - Comprehensive try-catch blocks with proper cleanup
  3. Test coverage - Thorough unit tests with good mocking
  4. Type safety - Strong TypeScript usage with Zod validation
  5. Localization - Proper i18n support throughout
  6. Lock mechanism - Proper file locking to prevent data corruption

📋 Summary

This is a solid implementation with only minor issues to address. The most critical items are the uninitialized variables in browser.ts and cross-platform path handling, which should be fixed before merge.

Recommended actions before merge:

  1. ✅ Fix uninitialized variables in browser.ts (add default case)
  2. ✅ Remove unused sprintf import
  3. ✅ Verify Linux path handling in getAppdataDirectory()
  4. ⚠️ Consider adding retry logic for failed auth (can be follow-up)
  5. ⚠️ Add JSDoc comments (can be follow-up)

Great work overall! The OAuth flow is secure, tests are comprehensive, and the code follows WordPress Studio patterns well. I've also added inline comments on the most critical issues for easy reference.

@claude
Copy link

claude bot commented Nov 17, 2025

Claude finished @fredrikekelund's task —— View job


Documentation updated for auth login command

  • Read new CLI command implementation
  • Check current documentation structure
  • Update docs/ai-instructions.md with auth login command
  • Commit and push changes

Summary

I've updated the documentation in docs/ai-instructions.md to include comprehensive information about the new studio auth login command. The changes include:

Added new "CLI Commands" section with:

  • Overview of CLI command patterns
  • Detailed documentation for studio auth login command:
    • Usage instructions and syntax
    • Step-by-step flow description (OAuth2 process)
    • Important notes about token lifetime (2 weeks) and authentication sharing
    • Example showing the complete workflow
    • Error handling (browser opening failures)

Updated directory structure section to reflect:

  • New auth/ commands directory
  • New browser.ts utility for cross-platform browser opening

The documentation now provides clear guidance for anyone using or working with the authentication CLI command.

View commit |

Add comprehensive documentation for the new `studio auth login` command
including usage instructions, flow description, and important notes about
token lifetime and authentication sharing between CLI and desktop app.

Co-authored-by: Fredrik Rombach Ekelund <[email protected]>
export async function openBrowser( url: string ): Promise< void > {
const platform = process.platform;
let cmd: string;
let args: string[];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: Variables cmd and args are declared but not initialized. If process.platform doesn't match any case in the switch statement, these will be undefined causing spawn() to fail on line 38.

Recommendation: Add a default case to the switch statement:

default:
  throw new Error(__('Unsupported platform'));

This was also flagged in the Copilot review.

@@ -0,0 +1,50 @@
import { spawn } from 'child_process';
import { __ } from '@wordpress/i18n';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Minor: sprintf is imported but never used in this file. Should be removed:

import { __ } from '@wordpress/i18n';

logger.reportError( loggerError );
}

console.log(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Code quality: Consider using the logger for consistency instead of console.log directly. This ensures uniform output formatting and enables better control over logging behavior.

user = await getUserInfo( accessToken );
logger.reportSuccess( __( 'Authentication completed successfully!' ) );
} catch ( error ) {
logger.reportError( new LoggerError( __( 'Authentication failed. Please try again.' ) ) );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 UX Enhancement: As noted in the PR reviews, consider:

  1. Adding retry logic (e.g., 3 attempts like SSH)
  2. Providing more specific error messages from the API
  3. Preserving the original error context:
catch (error) {
  const message = error instanceof LoggerError 
    ? error.message 
    : __('Authentication failed. Please try again.');
  logger.reportError(new LoggerError(message, error));
  return;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that a retry logic would improve the UX to account for a mistake paste or enter

Image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another idea would be to have a "click here if you aren't automatically redirected" to allow users to manually open the browser tab.

Neither of these are blockers in my opinion, just considerations.

@claude
Copy link

claude bot commented Nov 17, 2025

Claude finished @fredrikekelund's task —— View job


Documentation Review Complete

I've reviewed the CLI changes in this PR and verified the documentation status. Here are my findings:

Summary

No additional documentation updates are needed. The docs/ai-instructions.md file (which is synced with CLAUDE.md) was already updated in commit fc816bf by a previous Claude run to include comprehensive documentation for the new studio auth login command.

What's Already Documented

The documentation already includes:

  1. Complete CLI Commands section with:

    • Overview of CLI command patterns
    • Full studio auth login command documentation including:
      • Usage syntax
      • Step-by-step OAuth2 flow description
      • Examples showing the complete workflow
      • Important notes about token lifetime, browser failures, and authentication sharing
  2. Directory Structure updates reflecting:

    • New auth/ commands directory
    • New browser.ts utility for cross-platform browser opening
    • Updated CLI lib utilities section

Verification

I verified that:

  • ✅ The documentation accurately reflects the implementation in cli/commands/auth/login.ts
  • ✅ All key features are documented (OAuth flow, token storage, browser opening, error handling)
  • ✅ Usage examples match the actual command structure
  • ✅ Important details like 2-week token lifetime are mentioned
  • CLAUDE.md and docs/ai-instructions.md are identical (in sync)

Conclusion

The documentation is up-to-date and complete. No commit is needed since the previous Claude run already handled the documentation updates appropriately when the CLI command was implemented.

@claude
Copy link

claude bot commented Nov 17, 2025

Claude finished @fredrikekelund's task —— View job


Code Review Complete ✓

I've performed a comprehensive code review of the auth login command implementation. Overall, this is a well-structured and secure implementation with good test coverage. The code follows WordPress Studio patterns and handles the OAuth flow correctly.


🟢 Strengths

  1. Secure OAuth Implementation - Proper OAuth2 flow with token validation
  2. Excellent Test Coverage - Comprehensive unit tests covering all major paths
  3. Good Error Handling - Try-catch blocks with proper cleanup
  4. Cross-Platform Browser Opening - Handles macOS, Windows, and Linux
  5. File Locking Mechanism - Prevents race conditions in appdata writes
  6. Type Safety - Strong TypeScript usage with Zod validation
  7. Internationalization - Proper i18n support throughout

🔴 Critical Issues

1. Linux Path Handling in getAppdataDirectory()

File: cli/lib/appdata.ts:69-79

The function assumes macOS path structure for all non-Windows platforms, which will fail on Linux:

export function getAppdataDirectory(): string {
	if ( process.platform === 'win32' ) {
		// ... Windows handling
		return path.join( process.env.APPDATA, 'Studio' );
	}
	// ❌ This assumes macOS for Linux too
	return path.join( os.homedir(), 'Library', 'Application Support', 'Studio' );
}

Impact: Linux users will have incorrect config path
Recommendation:

export function getAppdataDirectory(): string {
	if ( process.platform === 'win32' ) {
		if ( ! process.env.APPDATA ) {
			throw new LoggerError( __( 'Studio config file path not found.' ) );
		}
		return path.join( process.env.APPDATA, 'Studio' );
	}
	if ( process.platform === 'darwin' ) {
		return path.join( os.homedir(), 'Library', 'Application Support', 'Studio' );
	}
	// Linux and other Unix-like systems
	return path.join( os.homedir(), '.config', 'Studio' );
}

2. Previous Review Issue Already Fixed ✓

The uninitialized variables issue in browser.ts that was flagged in previous reviews has been fixed - the code now has a proper default case on line 34 that throws an error.


🟡 Medium Priority Issues

3. User Experience Could Be Enhanced

File: cli/commands/auth/login.ts:49-63

As noted by reviewer @sejas, the authentication UX could be improved:

  1. Token visibility: Currently hidden by default (inquirer behavior)
  2. Single attempt: No retry mechanism for invalid tokens
  3. Generic error message: "Authentication failed. Please try again." doesn't guide users

Recommendations:

  • Add retry logic (3 attempts like SSH)
  • Consider token visibility option
  • Provide more specific error messages from the API response

4. Using console.log Instead of Logger

File: cli/commands/auth/login.ts:49-52

console.log(
	__( 'Please complete authentication in your browser and paste the generated token here.' )
);

Impact: Inconsistent output formatting
Recommendation: Use logger for all output:

logger.reportInfo(
	__( 'Please complete authentication in your browser and paste the generated token here.' )
);

5. Error Context Lost in Catch Block

File: cli/commands/auth/login.ts:61-64

catch ( error ) {
	logger.reportError( new LoggerError( __( 'Authentication failed. Please try again.' ) ) );
	return;
}

The original error details are discarded, making debugging difficult.

Recommendation:

catch ( error ) {
	const message = error instanceof LoggerError 
		? error.message 
		: __( 'Authentication failed. Please try again.' );
	logger.reportError( new LoggerError( message, error ) );
	return;
}

6. Silent Success When Already Authenticated

File: cli/commands/auth/login.ts:25-27

When already authenticated, the command just says "Already authenticated" but doesn't show who you're authenticated as.

Recommendation:

try {
	const token = await getAuthToken();
	logger.reportSuccess( 
		sprintf(__( 'Already authenticated as %s (%s)' ), token.displayName, token.email)
	);
	return;
}

🔵 Code Quality Improvements

7. Verbose Type Annotations

File: cli/commands/auth/login.ts:54-55

let accessToken: Awaited< ReturnType< typeof input > >;
let user: Awaited< ReturnType< typeof getUserInfo > >;

These could be simplified:

let accessToken: string;
let user: { ID: number; email: string; display_name: string };

8. Magic String for CLI Redirect URI

File: cli/commands/auth/login.ts:19

const CLI_REDIRECT_URI = `https://developer.wordpress.com/copy-oauth-token`;

Should be moved to common/constants.ts for consistency.

9. Token Expiration Documentation

File: cli/commands/auth/login.ts:75-76

As noted by @sejas, the 2-week expiration might surprise users. Consider adding a comment:

// Token lifetime (2 weeks) is controlled by the WordPress.com OAuth server
expiresIn: DEFAULT_TOKEN_LIFETIME_MS / 1000,
expirationTime: Date.now() + DEFAULT_TOKEN_LIFETIME_MS,

✅ Security Analysis

OAuth Flow - Secure

  • Uses authorization code flow with proper redirect
  • No credentials hardcoded
  • Token validation before storage
  • Expiration tracking implemented

Token Storage - Secure

  • User-specific appdata directory with appropriate permissions
  • File locking prevents race conditions
  • Tokens include expiration metadata

Input Validation - Good

  • Zod schema validation for API responses
  • Token validated by API before storage
  • Error boundaries properly implemented

Minor recommendation: Consider rate limiting for failed auth attempts.


🧪 Test Coverage Analysis

Excellent coverage in cli/commands/auth/tests/login.test.ts:

  • ✅ Happy path (successful login)
  • ✅ Already authenticated scenario
  • ✅ Browser opening failure
  • ✅ API errors
  • ✅ Lock/unlock failures
  • ✅ Locale handling

Minor gap: No tests for:

  • Network timeouts (may be covered by integration tests)
  • Malformed token input
  • Multiple retry attempts (feature doesn't exist yet)

🖥️ Cross-Platform Compatibility

Path Handling - Good

Browser Opening - Excellent

// cli/lib/browser.ts
case 'darwin': cmd = 'open'; break;
case 'win32': cmd = 'PowerShell'; break;
case 'linux': cmd = 'xdg-open'; break;
default: return Promise.reject(new Error(__('Unsupported platform')));

Properly handles all three platforms with appropriate commands.

File Locking - Platform-Independent

Uses the existing lockfile library which handles platform differences.


📚 Documentation

Documentation Status - Good

  • docs/ai-instructions.md has been updated with CLI auth command details (by previous Claude run)
  • Command usage, flow, and notes clearly documented
  • JSDoc comments present in browser.ts

Recommendation: Consider adding JSDoc to the main runCommand() function:

/**
 * Runs the authentication login command.
 * Checks for existing authentication, opens browser for OAuth flow,
 * prompts for token, validates it, and stores user credentials.
 * 
 * @throws {LoggerError} If authentication or token storage fails
 */
export async function runCommand(): Promise<void>

🎯 Testing Recommendations

The following should be tested on both macOS and Windows before merge:

  1. Browser opening - Verify on both platforms
  2. Path handling - Test appdata location on Windows, macOS, and Linux
  3. File locking - Ensure concurrent CLI/app access works
  4. Terminal input - Test in cmd.exe, PowerShell, and Terminal.app

📊 Performance

No concerns - All operations are async, no blocking, efficient file I/O.


Summary & Recommendations

This PR is nearly ready to merge with one critical fix needed:

Before Merge (Required):

  1. Fix Linux path handling in getAppdataDirectory() (Critical Issue Support WordPress in a subdirectory #1)

Nice to Have (Can be follow-up PRs):

  1. Add retry logic for failed authentication (3 attempts)
  2. Use logger instead of console.log for consistency
  3. Preserve error context in catch blocks
  4. Show user info when already authenticated
  5. Move CLI_REDIRECT_URI to constants
  6. Add JSDoc to main command function

Overall Assessment

Quality: 🟢 High
Security: 🟢 Excellent
Testing: 🟢 Comprehensive
Cross-Platform: 🟡 Good (needs Linux fix)

Great work on this implementation! The OAuth flow is secure, tests are thorough, and the code follows WordPress Studio patterns well. The only blocking issue is the Linux path handling.


@claude
Copy link

claude bot commented Nov 17, 2025

Claude Code is working… Image

I'll analyze this and get back to you.

View job run

@claude
Copy link

claude bot commented Nov 17, 2025

Claude finished @fredrikekelund's task —— View job


Documentation Review for CLI Changes

  • Review current documentation in docs/ai-instructions.md
  • Review CLI command implementation
  • Compare documentation against implementation
  • Determine if updates are needed

Summary

No documentation updates are needed. The docs/ai-instructions.md file is already comprehensive and up-to-date with the current implementation.

Verification Findings

  1. Previous Documentation Update: The documentation was already updated in commit fc816bf5 (by a previous Claude run) to include comprehensive information about the studio auth login command.

  2. Changes Since Documentation Update: Only one commit has modified CLI files since the documentation update:

    • Commit d8feb6b3 ("Tweak"): This removed the unused sprintf import from cli/lib/browser.ts and simplified an error message to not use i18n for an internal error. This is an internal code quality improvement that doesn't affect user-facing behavior or documentation.
  3. Documentation Completeness: The existing documentation in docs/ai-instructions.md includes:

    • ✅ Command syntax: node dist/cli/main.js auth login
    • ✅ Complete description of the OAuth2 flow (browser opening, token generation, pasting)
    • ✅ Important notes about 2-week token lifetime
    • ✅ Authentication sharing between CLI and desktop app
    • ✅ Error handling (browser opening failures)
    • ✅ Usage examples
    • ✅ Directory structure updates (auth/ commands, browser.ts utility)
  4. Sync Status: CLAUDE.md and docs/ai-instructions.md are identical (verified via diff).

Conclusion

The documentation accurately reflects the implementation. The minor code quality improvement in commit d8feb6b3 doesn't change any user-facing functionality, so no documentation updates are required.

Copy link
Contributor

@bcotrim bcotrim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

user = await getUserInfo( accessToken );
logger.reportSuccess( __( 'Authentication completed successfully!' ) );
} catch ( error ) {
logger.reportError( new LoggerError( __( 'Authentication failed. Please try again.' ) ) );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that a retry logic would improve the UX to account for a mistake paste or enter

Image

user = await getUserInfo( accessToken );
logger.reportSuccess( __( 'Authentication completed successfully!' ) );
} catch ( error ) {
logger.reportError( new LoggerError( __( 'Authentication failed. Please try again.' ) ) );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another idea would be to have a "click here if you aren't automatically redirected" to allow users to manually open the browser tab.

Neither of these are blockers in my opinion, just considerations.

@fredrikekelund fredrikekelund merged commit 7286423 into trunk Nov 17, 2025
13 of 14 checks passed
@fredrikekelund fredrikekelund deleted the f26d/cli-auth-login-command branch November 17, 2025 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants