fix(init): Ensure hosted mode option available by creating .taskmasterconfig early
- Added ensureConfigFileExists() to create default config if missing - Call early in init flows before gateway check - Preserve email from initializeUser() - Add comprehensive tests
This commit is contained in:
@@ -34,6 +34,7 @@ import {
|
||||
initializeBYOKUser,
|
||||
initializeHostedUser,
|
||||
} from "./modules/user-management.js";
|
||||
import { ensureConfigFileExists } from "./modules/config-manager.js";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -391,6 +392,9 @@ async function initializeProject(options = {}) {
|
||||
let userSetupResult = null;
|
||||
let isGatewayAvailable = false;
|
||||
|
||||
// Ensure .taskmasterconfig exists before checking gateway availability
|
||||
ensureConfigFileExists(process.cwd());
|
||||
|
||||
// Try to initialize user, but don't throw errors if it fails
|
||||
try {
|
||||
userSetupResult = await initializeUser(process.cwd());
|
||||
@@ -452,6 +456,9 @@ async function initializeProject(options = {}) {
|
||||
let userSetupResult = null;
|
||||
let isGatewayAvailable = false;
|
||||
|
||||
// Ensure .taskmasterconfig exists before checking gateway availability
|
||||
ensureConfigFileExists(process.cwd());
|
||||
|
||||
try {
|
||||
userSetupResult = await initializeUser(process.cwd());
|
||||
if (userSetupResult.success) {
|
||||
@@ -866,7 +873,12 @@ function configureTaskmasterConfig(
|
||||
// Store account-specific configuration
|
||||
config.account.mode = selectedMode;
|
||||
config.account.userId = userId || null;
|
||||
config.account.email = gatewayRegistration?.email || "";
|
||||
|
||||
// Only set email if not already present (initializeUser may have already set it)
|
||||
if (!config.account.email) {
|
||||
config.account.email = gatewayRegistration?.email || "";
|
||||
}
|
||||
|
||||
config.account.telemetryEnabled = selectedMode === "hosted";
|
||||
|
||||
// Store remaining global config items
|
||||
|
||||
@@ -808,6 +808,57 @@ function getMode(explicitRoot = null) {
|
||||
return config.account?.mode || "byok";
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the .taskmasterconfig file exists, creating it with defaults if it doesn't.
|
||||
* This is called early in initialization to prevent chicken-and-egg problems.
|
||||
* @param {string|null} explicitRoot - Optional explicit path to the project root
|
||||
* @returns {boolean} True if file exists or was created successfully, false otherwise
|
||||
*/
|
||||
function ensureConfigFileExists(explicitRoot = null) {
|
||||
// ---> Determine root path reliably (following existing pattern) <---
|
||||
let rootPath = explicitRoot;
|
||||
if (explicitRoot === null || explicitRoot === undefined) {
|
||||
// Logic matching _loadAndValidateConfig and other functions
|
||||
const foundRoot = findProjectRoot(); // *** Explicitly call findProjectRoot ***
|
||||
if (!foundRoot) {
|
||||
console.warn(
|
||||
chalk.yellow(
|
||||
"Warning: Could not determine project root for config file creation."
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
rootPath = foundRoot;
|
||||
}
|
||||
// ---> End determine root path logic <---
|
||||
|
||||
const configPath = path.join(rootPath, CONFIG_FILE_NAME);
|
||||
|
||||
// If file already exists, we're good
|
||||
if (fs.existsSync(configPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create the default config file (following writeConfig pattern)
|
||||
fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
|
||||
console.log(chalk.blue(`ℹ️ Created default .taskmasterconfig file`));
|
||||
|
||||
// Clear any cached config to ensure fresh load
|
||||
loadedConfig = null;
|
||||
loadedConfigRoot = null;
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Error creating default .taskmasterconfig file: ${error.message}`
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
// Core config access
|
||||
getConfig,
|
||||
@@ -856,4 +907,6 @@ export {
|
||||
getTelemetryEnabled,
|
||||
getUserEmail,
|
||||
getMode,
|
||||
// New function
|
||||
ensureConfigFileExists,
|
||||
};
|
||||
|
||||
@@ -641,3 +641,52 @@ describe("getAllProviders", () => {
|
||||
|
||||
// Note: Tests for setMainModel, setResearchModel were removed as the functions were removed in the implementation.
|
||||
// If similar setter functions exist, add tests for them following the writeConfig pattern.
|
||||
|
||||
describe("ensureConfigFileExists", () => {
|
||||
it("should create .taskmasterconfig file if it doesn't exist", () => {
|
||||
// Override the default fs mocks for this test
|
||||
fsExistsSyncSpy.mockReturnValue(false);
|
||||
fsWriteFileSyncSpy.mockImplementation(() => {}); // Success, no throw
|
||||
|
||||
const result = configManager.ensureConfigFileExists(MOCK_PROJECT_ROOT);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(fsWriteFileSyncSpy).toHaveBeenCalledWith(
|
||||
MOCK_CONFIG_PATH,
|
||||
JSON.stringify(DEFAULT_CONFIG, null, 2)
|
||||
);
|
||||
});
|
||||
|
||||
it("should return true if .taskmasterconfig file already exists", () => {
|
||||
// Mock file exists (this is the default, but let's be explicit)
|
||||
fsExistsSyncSpy.mockReturnValue(true);
|
||||
|
||||
const result = configManager.ensureConfigFileExists(MOCK_PROJECT_ROOT);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(fsWriteFileSyncSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return false if project root cannot be determined", () => {
|
||||
// Override the default findProjectRoot mock to return null for this test
|
||||
mockFindProjectRoot.mockReturnValue(null);
|
||||
|
||||
const result = configManager.ensureConfigFileExists(); // No explicitRoot provided
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(fsWriteFileSyncSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle write errors gracefully", () => {
|
||||
// Mock file doesn't exist
|
||||
fsExistsSyncSpy.mockReturnValue(false);
|
||||
// Mock write operation to throw error
|
||||
fsWriteFileSyncSpy.mockImplementation(() => {
|
||||
throw new Error("Permission denied");
|
||||
});
|
||||
|
||||
const result = configManager.ensureConfigFileExists(MOCK_PROJECT_ROOT);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user