diff --git a/.changeset/spicy-singers-unite.md b/.changeset/spicy-singers-unite.md new file mode 100644 index 0000000..51a3cb1 --- /dev/null +++ b/.changeset/spicy-singers-unite.md @@ -0,0 +1,5 @@ +--- +"@t1mmen/srtd": patch +--- + +Attempt to identify "root of project", so srtd can be run from subdirectories as well. diff --git a/src/commands/register.tsx b/src/commands/register.tsx index 56288ac..31477c5 100644 --- a/src/commands/register.tsx +++ b/src/commands/register.tsx @@ -8,6 +8,7 @@ import Branding from '../components/Branding.js'; import Quittable from '../components/Quittable.js'; import { COLOR_ERROR, COLOR_SUCCESS, COLOR_WARNING } from '../components/customTheme.js'; import { useTemplateState } from '../hooks/useTemplateState.js'; +import { findProjectRoot } from '../utils/findProjectRoot.js'; import { registerTemplate } from '../utils/registerTemplate.js'; // Support both array of filenames as arguments and interactive selection @@ -45,15 +46,23 @@ export default function Register({ args: templateArgs }: Props) { let successCount = 0; let failCount = 0; - for (const path of templates) { - try { - await registerTemplate(path, process.cwd()); - successCount++; - } catch { - failCount++; + try { + const projectRoot = await findProjectRoot(); + for (const path of templates) { + try { + await registerTemplate(path, projectRoot); + successCount++; + } catch (err) { + console.error(err); + failCount++; + } } + } catch (err) { + if (err instanceof Error) { + setErrorMessage(err.message); + } + process.exit(1); } - if (failCount > 0) { setErrorMessage(`Failed to register ${failCount} template(s).`); } @@ -122,7 +131,9 @@ export default function Register({ args: templateArgs }: Props) { {options.length === 0 ? ( - {figures.warning} No templates found + + {figures.warning} No templates {!showAll && 'unregistered'} found + {!showAll && !!items.length && ( {figures.info} Press r to show registered templates )} diff --git a/src/hooks/useTemplateManager.ts b/src/hooks/useTemplateManager.ts index 5666c67..d8051ed 100644 --- a/src/hooks/useTemplateManager.ts +++ b/src/hooks/useTemplateManager.ts @@ -3,6 +3,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { TemplateManager } from '../lib/templateManager.js'; import type { TemplateStatus } from '../types.js'; import { getConfig } from '../utils/config.js'; +import { findProjectRoot } from '../utils/findProjectRoot.js'; export type TemplateUpdate = { type: 'applied' | 'changed' | 'error'; @@ -26,7 +27,7 @@ export interface UseTemplateManager { templateDir?: string; } -export function useTemplateManager(cwd: string = process.cwd()): UseTemplateManager { +export function useTemplateManager(baseDir?: string): UseTemplateManager { const [templates, setTemplates] = useState([]); const [updates, setUpdates] = useState([]); const [errors, setErrors] = useState>(new Map()); @@ -66,6 +67,7 @@ export function useTemplateManager(cwd: string = process.cwd()): UseTemplateMana async function init() { try { + const cwd = baseDir || (await findProjectRoot()); const config = await getConfig(cwd); setTemplateDir(config.templateDir); @@ -162,7 +164,7 @@ export function useTemplateManager(cwd: string = process.cwd()): UseTemplateMana mounted = false; managerRef.current?.[Symbol.dispose](); }; - }, [cwd]); + }, [baseDir]); return { templates: sortedTemplates, diff --git a/src/hooks/useTemplateProcessor.ts b/src/hooks/useTemplateProcessor.ts index eb4cf0e..6e9e95c 100644 --- a/src/hooks/useTemplateProcessor.ts +++ b/src/hooks/useTemplateProcessor.ts @@ -1,6 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { TemplateManager } from '../lib/templateManager.js'; import type { ProcessedTemplateResult } from '../types.js'; +import { findProjectRoot } from '../utils/findProjectRoot.js'; interface ProcessorOptions { force?: boolean; @@ -25,7 +26,8 @@ export function useTemplateProcessor(options: ProcessorOptions) { async function doProcessing() { try { - using manager = await TemplateManager.create(process.cwd(), { silent: true }); + const projectRoot = await findProjectRoot(); + using manager = await TemplateManager.create(projectRoot, { silent: true }); const result = await manager.processTemplates(options); setResult(result); setIsProcessing(false); diff --git a/src/hooks/useTemplateState.ts b/src/hooks/useTemplateState.ts index 2b23d2c..92866fb 100644 --- a/src/hooks/useTemplateState.ts +++ b/src/hooks/useTemplateState.ts @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { TemplateManager } from '../lib/templateManager.js'; import type { TemplateStatus } from '../types.js'; +import { findProjectRoot } from '../utils/findProjectRoot.js'; export function useTemplateState() { const [loading, setLoading] = useState(true); @@ -11,7 +12,7 @@ export function useTemplateState() { useEffect(() => { async function fetchStatus() { try { - const baseDir = process.cwd(); + const baseDir = await findProjectRoot(); const manager = await TemplateManager.create(baseDir); const templates = await manager.findTemplates(); const statuses = await Promise.all(templates.map(t => manager.getTemplateStatus(t))); diff --git a/src/utils/findProjectRoot.ts b/src/utils/findProjectRoot.ts new file mode 100644 index 0000000..990a1dd --- /dev/null +++ b/src/utils/findProjectRoot.ts @@ -0,0 +1,28 @@ +import path from 'node:path'; +import { fileExists } from './fileExists.js'; + +async function isProjectDir(dir: string): Promise { + // Check for srtd config files + if (await fileExists(path.join(dir, 'srtd.config.json'))) return true; + + // Check for package.json + if (await fileExists(path.join(dir, 'package.json'))) return true; + + // Check for supabase directory + if (await fileExists(path.join(dir, 'supabase'))) return true; + + return false; +} + +export async function findProjectRoot(startDir?: string): Promise { + let currentDir = startDir || process.cwd(); + + while (currentDir !== path.parse(currentDir).root) { + if (await isProjectDir(currentDir)) { + return currentDir; + } + currentDir = path.dirname(currentDir); + } + + throw new Error('Could not find project root. Are you in a Supabase project directory?'); +} diff --git a/src/utils/registerTemplate.ts b/src/utils/registerTemplate.ts index 3342592..2f65704 100644 --- a/src/utils/registerTemplate.ts +++ b/src/utils/registerTemplate.ts @@ -7,10 +7,12 @@ import { loadBuildLog } from './loadBuildLog.js'; import { saveBuildLog } from './saveBuildLog.js'; export async function registerTemplate(templatePath: string, baseDir: string): Promise { + const config = await getConfig(); + const pathsToTry = [ path.resolve(templatePath), path.resolve(baseDir, templatePath), - path.join(baseDir, templatePath) + path.join(baseDir, templatePath), ]; let resolvedPath: string | null = null;