From 102f17870044385429c84bec9fd26aa04351b39c Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Fri, 4 Aug 2023 19:01:50 +0800 Subject: [PATCH] Organize paths Use a single source of truth for derived paths in commands. --- packages/amanuensis/src/commands/analyze.ts | 33 ++-- packages/amanuensis/src/commands/init.ts | 27 +-- packages/amanuensis/src/commands/refresh.ts | 163 ++++++++++-------- packages/amanuensis/src/index.ts | 8 +- packages/amanuensis/src/mixins/base-path.ts | 9 +- .../amanuensis/src/mixins/internal-path.ts | 4 +- 6 files changed, 136 insertions(+), 108 deletions(-) diff --git a/packages/amanuensis/src/commands/analyze.ts b/packages/amanuensis/src/commands/analyze.ts index 98dc026..a2f6202 100644 --- a/packages/amanuensis/src/commands/analyze.ts +++ b/packages/amanuensis/src/commands/analyze.ts @@ -9,9 +9,9 @@ import { useInternalPath } from '../mixins/internal-path'; export enum GenerateReturnCode { SUCCESS = 0, - NO_TYPEDOC_JSON = -2, - COULD_NOT_GENERATE_TYPEDOC_DATA = -3, - COULD_NOT_GENERATE_PACKAGE_DATA = -4, + NO_TYPEDOC_JSON = -3, + COULD_NOT_GENERATE_TYPEDOC_DATA = -4, + COULD_NOT_GENERATE_PACKAGE_DATA = -5, } const ensureTypedocJson = async (typedocPath: string) => { @@ -38,10 +38,10 @@ const ensureTypedocJson = async (typedocPath: string) => { process.stdout.write('yes\n'); }; -const generateTypedocData = async (outPath: string, basePath: string) => { +const generateTypedocData = async (basePath: string, internalPath: string) => { process.stdout.write('Generating typedoc data...\n'); - - const typedocBinPath = await useInternalPath('node_modules', '.bin', 'typedoc'); + const outPath = resolve(internalPath, '.amanuensis', 'types.json'); + const typedocBinPath = resolve(internalPath, 'node_modules', '.bin', 'typedoc'); const { execa } = await import('execa'); const result = await execa(typedocBinPath, ['--json', outPath, '--pretty', 'false'], { @@ -59,11 +59,12 @@ const generateTypedocData = async (outPath: string, basePath: string) => { }; const generatePackageData = async ( - typedocDataJsonPath: string, - configPath: string, basePath: string, + internalPath: string, ) => { process.stdout.write('Grouping typedoc data...\n'); + const configPath = resolve(basePath, 'amanuensis.config.json'); + const typedocDataJsonPath = resolve(internalPath, '.amanuensis', 'packages.json'); const packages = await getPackages(configPath, basePath); @@ -91,16 +92,16 @@ export const builder = (yargs: Argv) => yargs }); const analyze = async (args: AnalyzeArgs) => { - const basePath = await useBasePath(); - const configPath = await useBasePath('amanuensis.config.json'); - const typedocJsonPath = args.typedocJsonPath ?? await useBasePath('typedoc.json'); - const typedocOutPath = await useInternalPath('.amanuensis', 'types.json'); - const packagesPath = await useInternalPath('.amanuensis', 'packages.json'); - try { + const basePath = await useBasePath(); + process.stdout.write(`Using base path: ${basePath}\n`); + + const typedocJsonPath = args.typedocJsonPath ?? resolve(basePath, 'typedoc.json'); await ensureTypedocJson(typedocJsonPath); - await generateTypedocData(typedocOutPath, basePath); - await generatePackageData(packagesPath, configPath, basePath); + + const internalPath = await useInternalPath(); + await generateTypedocData(basePath, internalPath); + await generatePackageData(basePath, internalPath); } catch (errRaw) { const err = errRaw as CommandError; process.stderr.write(`${err.message}\n`); diff --git a/packages/amanuensis/src/commands/init.ts b/packages/amanuensis/src/commands/init.ts index ecdd19f..c1ab7ca 100644 --- a/packages/amanuensis/src/commands/init.ts +++ b/packages/amanuensis/src/commands/init.ts @@ -1,18 +1,19 @@ import { cp } from 'fs/promises'; import { Argv } from 'yargs'; +import { resolve } from 'path'; import { useInternalPath } from '../mixins/internal-path'; import { CommandError } from '../utils/error'; export enum InitReturnCode { SUCCESS = 0, - COULD_NOT_COPY_FILES = -2, - COULD_NOT_INSTALL_DEPENDENCIES = -3, + COULD_NOT_COPY_FILES = -3, + COULD_NOT_INSTALL_DEPENDENCIES = -4, } -const copyFiles = async () => { +const copyFiles = async (internalPath: string) => { + const srcPath = resolve(internalPath, 'src', 'next'); + const destPath = resolve(internalPath, '.amanuensis', 'next'); try { - const srcPath = await useInternalPath('src', 'next'); - const destPath = await useInternalPath('.amanuensis', 'next'); await cp(srcPath, destPath, { recursive: true }); } catch (errRaw) { const err = errRaw as NodeJS.ErrnoException; @@ -29,7 +30,7 @@ interface PackageManager { installCmd: [string, string[]]; } -const packageManagers: PackageManager[] = [ +const PACKAGE_MANAGERS: PackageManager[] = [ { name: 'pnpm', testCmd: ['pnpm', ['--version']], @@ -47,10 +48,10 @@ const packageManagers: PackageManager[] = [ }, ]; -const installDependencies = async () => { +const installDependencies = async (internalPath: string) => { const { execa } = await import('execa'); - const selectedPackageManagerIndex = await packageManagers.reduce( + const selectedPackageManagerIndex = await PACKAGE_MANAGERS.reduce( async (prevPmPromise, pkgm, i) => { const prevPm = await prevPmPromise; const { testCmd } = pkgm; @@ -68,8 +69,8 @@ const installDependencies = async () => { process.exit(-1); } - const { [selectedPackageManagerIndex]: selectedPackageManager } = packageManagers; - const cwd = await useInternalPath('.amanuensis', 'next'); + const { [selectedPackageManagerIndex]: selectedPackageManager } = PACKAGE_MANAGERS; + const cwd = resolve(internalPath, '.amanuensis', 'next'); process.stdout.write(`In path: ${cwd}\n`); process.stdout.write(`Installing dependencies with ${selectedPackageManager.name}\n`); const result = await execa( @@ -89,8 +90,10 @@ export const builder = (yargsBuilder: Argv) => yargsBuilder; const init = async () => { try { - await copyFiles(); - await installDependencies(); + const internalPath = await useInternalPath(); + + await copyFiles(internalPath); + await installDependencies(internalPath); } catch (errRaw) { const err = errRaw as CommandError; process.stderr.write(`${err.message}\n`); diff --git a/packages/amanuensis/src/commands/refresh.ts b/packages/amanuensis/src/commands/refresh.ts index a5ff08d..c2c4eee 100644 --- a/packages/amanuensis/src/commands/refresh.ts +++ b/packages/amanuensis/src/commands/refresh.ts @@ -22,93 +22,73 @@ import { useInternalPath } from '../mixins/internal-path'; export enum GenerateReturnCode { SUCCESS = 0, - COULD_NOT_GENERATE_PAGES = -2, + COULD_NOT_GENERATE_PAGES = -3, } -const linkComponents = async (cwd: string) => { - process.stdout.write('Linking components...\n'); - - const projectCwd = resolve(cwd, '.amanuensis'); - const defaultCwd = await useInternalPath('src', 'next'); - const destCwd = await useInternalPath('.amanuensis', 'next', 'src'); - - // todo merge package.json - - const componentsList = [ - 'next/src/components/Wrapper.tsx', - 'next/src/components/PageLayout.tsx', - 'next/src/components/theme.ts', - 'next/src/components/postcss.config.js', - 'next/src/components/tailwind.config.js', - ]; - +const cleanContent = async (internalPath: string) => { try { + const destCwd = resolve(internalPath, '.amanuensis', 'next', 'src'); await rm(resolve(destCwd, 'content'), { recursive: true }); } catch { // noop } +}; +const resetPages = async (internalPath: string) => { try { + const defaultCwd = resolve(internalPath, 'src', 'next'); + const destCwd = resolve(internalPath, '.amanuensis', 'next', 'src'); await rm(resolve(destCwd, 'pages'), { recursive: true }); await cp(resolve(defaultCwd, 'pages'), resolve(destCwd, 'pages'), { recursive: true }); } catch { // noop } +}; + +const linkComponents = async (basePath: string, internalPath: string) => { + process.stdout.write('Linking components...\n'); + + const customComponentDir = resolve(basePath, '.amanuensis'); + const defaultComponentDir = resolve(internalPath, 'src', 'next'); + const destCwd = resolve(internalPath, '.amanuensis', 'next', 'src'); + + // todo merge package.json + + const componentsList = [ + 'next/src/components/Wrapper.tsx', + 'next/src/components/PageLayout.tsx', + 'next/src/components/theme.ts', + 'next/src/components/postcss.config.js', + 'next/src/components/tailwind.config.js', + ]; await Promise.all(componentsList.map(async (componentPath) => { const destPath = resolve(destCwd, componentPath); - let baseCwd = projectCwd; + let componentDir = customComponentDir; try { - await stat(resolve(baseCwd, componentPath)); + await stat(resolve(componentDir, componentPath)); } catch (errRaw) { const err = errRaw as NodeJS.ErrnoException; if (err.code === 'ENOENT') { - baseCwd = defaultCwd; + componentDir = defaultComponentDir; } } await mkdirp(dirname(destPath)); await cp( - resolve(baseCwd, componentPath), + resolve(componentDir, componentPath), destPath, ); process.stdout.write(`Linked ${componentPath}\n`); })); - const packagesPath = await useInternalPath('.amanuensis', 'packages.json'); - const packagesDataJson = await readFile(packagesPath, 'utf-8'); - const packagesData = JSON.parse(packagesDataJson) as PackageData[]; + process.stdout.write('done\n'); +}; - try { - await Promise.all( - packagesData.map(async (pkg) => { - const packageDir = await useBasePath(pkg.basePath); - const packageLinkDir = await useInternalPath('.amanuensis', 'next', 'node_modules', pkg.name); - await mkdirp(dirname(packageLinkDir)); - - try { - await symlink(packageDir, packageLinkDir); - } catch { - // noop - } - - await mkdirp(resolve(destCwd, 'content', pkg.basePath)); - await mkdirp(resolve(destCwd, 'pages', pkg.basePath)); - await Promise.all( - pkg.markdown.map(async (m) => { - const srcPath = resolve(cwd, pkg.basePath, m.filePath); - const destPath = resolve(destCwd, 'content', pkg.basePath, m.name); - await cp(srcPath, destPath); - - const pageDestPath = resolve(destCwd, 'pages', pkg.basePath, `${basename(m.name, extname(m.name))}.tsx`); - const preambleImport = `@/${join('content', pkg.basePath, m.name)}`; - - await writeFile( - pageDestPath, - `import {NextPage} from 'next'; -import {Wrapper} from '@/components/Wrapper'; +const pageContent = (preambleImport: string) => `import {NextPage} from 'next'; import {ComponentContext} from '@/contexts/Component'; +import {Wrapper} from '@/components/Wrapper'; import {PreambleContext} from '@/contexts/Preamble'; import Preamble from '${preambleImport}'; import {PageLayout} from '@/components/PageLayout'; @@ -124,44 +104,89 @@ const IndexPage: NextPage = () => { }; export default IndexPage; -`, - // todo fetch components for display to props and preamble - // todo fix problem when building with import aliases - // todo find a way to build with tailwind - // todo link components to next project (done) - // todo merge contents of .amanuensis with next project - ); - }), - ); - }), - ); +`; + +const writeContent = async (basePath: string, internalPath: string) => { + const destCwd = resolve(internalPath, '.amanuensis', 'next', 'src'); + const packagesPath = resolve(internalPath, '.amanuensis', 'packages.json'); + + const packagesDataJson = await readFile(packagesPath, 'utf-8'); + const packagesData = JSON.parse(packagesDataJson) as PackageData[]; + + try { + await Promise.all(packagesData.map(async (pkg) => { + const packageDir = resolve(basePath, pkg.basePath); + const packageLinkDir = resolve(internalPath, '.amanuensis', 'next', 'node_modules', pkg.name); + await mkdirp(dirname(packageLinkDir)); + + try { + await symlink(packageDir, packageLinkDir); + } catch { + // noop + } + + await mkdirp(resolve(destCwd, 'content', pkg.basePath)); + await mkdirp(resolve(destCwd, 'pages', pkg.basePath)); + await Promise.all( + pkg.markdown.map(async (m) => { + const srcPath = resolve(basePath, pkg.basePath, m.filePath); + const destPath = resolve(destCwd, 'content', pkg.basePath, m.name); + await cp(srcPath, destPath); + + const pageDestPath = resolve(destCwd, 'pages', pkg.basePath, `${basename(m.name, extname(m.name))}.tsx`); + const preambleImport = `@/${join('content', pkg.basePath, m.name)}`; + + await writeFile(pageDestPath, pageContent(preambleImport)); + + // todo fetch components for display to props and preamble + // todo fix problem when building with import aliases + // todo find a way to build with tailwind + // todo link components to next project (done) + // todo merge contents of .amanuensis with next project + }), + ); + })); } catch (errRaw) { throw new CommandError(GenerateReturnCode.COULD_NOT_GENERATE_PAGES, 'Could not write inner page file', errRaw as Error); } try { - const srcPath = resolve(cwd, 'README.md'); + const srcPath = resolve(basePath, 'README.md'); const destPath = resolve(destCwd, 'content', 'index.md'); await cp(srcPath, destPath); } catch (errRaw) { throw new CommandError(GenerateReturnCode.COULD_NOT_GENERATE_PAGES, 'Could not write index file', errRaw as Error); } - - process.stdout.write('done\n'); }; export const description = 'Generate documentation from typedoc.json' as const; export interface RefreshArgs { subcommands?: string[]; + clean?: boolean; } -export const builder = (yargs: Argv) => yargs; +export const builder = (yargs: Argv) => yargs + .option('clean', { + alias: 'c', + type: 'boolean', + description: 'Reset generated content', + default: true, + }); const refresh = async (args: RefreshArgs) => { try { + const internalPath = await useInternalPath(); + const { clean = true } = args; + + if (clean) { + await cleanContent(internalPath); + await resetPages(internalPath); + } + const basePath = await useBasePath(); - await linkComponents(basePath); + await linkComponents(basePath, internalPath); + await writeContent(basePath, internalPath); } catch (errRaw) { const err = errRaw as CommandError; process.stderr.write(`${err.message}\n`); diff --git a/packages/amanuensis/src/index.ts b/packages/amanuensis/src/index.ts index e49c055..2be86a4 100644 --- a/packages/amanuensis/src/index.ts +++ b/packages/amanuensis/src/index.ts @@ -3,6 +3,8 @@ import { hideBin } from 'yargs/helpers'; import yargs from 'yargs'; +const UNKNOWN_COMMAND_EXIT_CODE = -1 as const; + const main = async (args: string[]) => { const COMMANDS = { init: await import('./commands/init'), @@ -26,14 +28,14 @@ const main = async (args: string[]) => { const [commandName, ...subcommands] = commandNamesRaw as [keyof typeof COMMANDS, ...string[]]; if (typeof commandName === 'undefined') { yargsBuilder.showHelp(); - return -1; + return UNKNOWN_COMMAND_EXIT_CODE; } const { [commandName]: commandDef } = COMMANDS; if (typeof commandDef === 'undefined') { process.stderr.write(`Unknown command: ${commandName}\n`); yargsBuilder.showHelp(); - return -1; + return UNKNOWN_COMMAND_EXIT_CODE; } const { default: handler } = commandDef; @@ -49,5 +51,5 @@ main(hideBin(process.argv)) process.exit(code); }) .catch(() => { - process.exit(-1); + process.exit(-255); }); diff --git a/packages/amanuensis/src/mixins/base-path.ts b/packages/amanuensis/src/mixins/base-path.ts index 330aea9..80ca7db 100644 --- a/packages/amanuensis/src/mixins/base-path.ts +++ b/packages/amanuensis/src/mixins/base-path.ts @@ -1,12 +1,11 @@ -import { resolve } from 'path'; import { searchForConfigFile } from '../utils/paths'; import { CommandError } from '../utils/error'; export enum UseBasePathReturnCode { - COULD_NOT_FIND_CONFIG_FILE = -1, + COULD_NOT_FIND_CONFIG_FILE = -2, } -export const useBasePath = async (...args: string[]) => { +export const useBasePath = async () => { const basePathPrefix = await searchForConfigFile(); if (typeof basePathPrefix !== 'string') { @@ -16,7 +15,5 @@ export const useBasePath = async (...args: string[]) => { ); } - const basePath = resolve(basePathPrefix, ...args); - process.stdout.write(`Using base path: ${basePath}\n`); - return basePath; + return basePathPrefix; }; diff --git a/packages/amanuensis/src/mixins/internal-path.ts b/packages/amanuensis/src/mixins/internal-path.ts index 42396fd..ac668b3 100644 --- a/packages/amanuensis/src/mixins/internal-path.ts +++ b/packages/amanuensis/src/mixins/internal-path.ts @@ -1,7 +1,7 @@ import { resolve } from 'path'; -export const useInternalPath = (...args: string[]) => { +export const useInternalPath = () => { return Promise.resolve( - resolve(__dirname, '..', '..', '..', ...args), + resolve(__dirname, '..', '..', '..'), ); };