@@ -9,9 +9,9 @@ import { useInternalPath } from '../mixins/internal-path'; | |||||
export enum GenerateReturnCode { | export enum GenerateReturnCode { | ||||
SUCCESS = 0, | 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) => { | const ensureTypedocJson = async (typedocPath: string) => { | ||||
@@ -38,10 +38,10 @@ const ensureTypedocJson = async (typedocPath: string) => { | |||||
process.stdout.write('yes\n'); | 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'); | 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 { execa } = await import('execa'); | ||||
const result = await execa(typedocBinPath, ['--json', outPath, '--pretty', 'false'], { | const result = await execa(typedocBinPath, ['--json', outPath, '--pretty', 'false'], { | ||||
@@ -59,11 +59,12 @@ const generateTypedocData = async (outPath: string, basePath: string) => { | |||||
}; | }; | ||||
const generatePackageData = async ( | const generatePackageData = async ( | ||||
typedocDataJsonPath: string, | |||||
configPath: string, | |||||
basePath: string, | basePath: string, | ||||
internalPath: string, | |||||
) => { | ) => { | ||||
process.stdout.write('Grouping typedoc data...\n'); | 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); | const packages = await getPackages(configPath, basePath); | ||||
@@ -91,16 +92,16 @@ export const builder = (yargs: Argv) => yargs | |||||
}); | }); | ||||
const analyze = async (args: AnalyzeArgs) => { | 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 { | 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 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) { | } catch (errRaw) { | ||||
const err = errRaw as CommandError; | const err = errRaw as CommandError; | ||||
process.stderr.write(`${err.message}\n`); | process.stderr.write(`${err.message}\n`); | ||||
@@ -1,18 +1,19 @@ | |||||
import { cp } from 'fs/promises'; | import { cp } from 'fs/promises'; | ||||
import { Argv } from 'yargs'; | import { Argv } from 'yargs'; | ||||
import { resolve } from 'path'; | |||||
import { useInternalPath } from '../mixins/internal-path'; | import { useInternalPath } from '../mixins/internal-path'; | ||||
import { CommandError } from '../utils/error'; | import { CommandError } from '../utils/error'; | ||||
export enum InitReturnCode { | export enum InitReturnCode { | ||||
SUCCESS = 0, | 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 { | try { | ||||
const srcPath = await useInternalPath('src', 'next'); | |||||
const destPath = await useInternalPath('.amanuensis', 'next'); | |||||
await cp(srcPath, destPath, { recursive: true }); | await cp(srcPath, destPath, { recursive: true }); | ||||
} catch (errRaw) { | } catch (errRaw) { | ||||
const err = errRaw as NodeJS.ErrnoException; | const err = errRaw as NodeJS.ErrnoException; | ||||
@@ -29,7 +30,7 @@ interface PackageManager { | |||||
installCmd: [string, string[]]; | installCmd: [string, string[]]; | ||||
} | } | ||||
const packageManagers: PackageManager[] = [ | |||||
const PACKAGE_MANAGERS: PackageManager[] = [ | |||||
{ | { | ||||
name: 'pnpm', | name: 'pnpm', | ||||
testCmd: ['pnpm', ['--version']], | testCmd: ['pnpm', ['--version']], | ||||
@@ -47,10 +48,10 @@ const packageManagers: PackageManager[] = [ | |||||
}, | }, | ||||
]; | ]; | ||||
const installDependencies = async () => { | |||||
const installDependencies = async (internalPath: string) => { | |||||
const { execa } = await import('execa'); | const { execa } = await import('execa'); | ||||
const selectedPackageManagerIndex = await packageManagers.reduce( | |||||
const selectedPackageManagerIndex = await PACKAGE_MANAGERS.reduce( | |||||
async (prevPmPromise, pkgm, i) => { | async (prevPmPromise, pkgm, i) => { | ||||
const prevPm = await prevPmPromise; | const prevPm = await prevPmPromise; | ||||
const { testCmd } = pkgm; | const { testCmd } = pkgm; | ||||
@@ -68,8 +69,8 @@ const installDependencies = async () => { | |||||
process.exit(-1); | 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(`In path: ${cwd}\n`); | ||||
process.stdout.write(`Installing dependencies with ${selectedPackageManager.name}\n`); | process.stdout.write(`Installing dependencies with ${selectedPackageManager.name}\n`); | ||||
const result = await execa( | const result = await execa( | ||||
@@ -89,8 +90,10 @@ export const builder = (yargsBuilder: Argv) => yargsBuilder; | |||||
const init = async () => { | const init = async () => { | ||||
try { | try { | ||||
await copyFiles(); | |||||
await installDependencies(); | |||||
const internalPath = await useInternalPath(); | |||||
await copyFiles(internalPath); | |||||
await installDependencies(internalPath); | |||||
} catch (errRaw) { | } catch (errRaw) { | ||||
const err = errRaw as CommandError; | const err = errRaw as CommandError; | ||||
process.stderr.write(`${err.message}\n`); | process.stderr.write(`${err.message}\n`); | ||||
@@ -22,93 +22,73 @@ import { useInternalPath } from '../mixins/internal-path'; | |||||
export enum GenerateReturnCode { | export enum GenerateReturnCode { | ||||
SUCCESS = 0, | 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 { | try { | ||||
const destCwd = resolve(internalPath, '.amanuensis', 'next', 'src'); | |||||
await rm(resolve(destCwd, 'content'), { recursive: true }); | await rm(resolve(destCwd, 'content'), { recursive: true }); | ||||
} catch { | } catch { | ||||
// noop | // noop | ||||
} | } | ||||
}; | |||||
const resetPages = async (internalPath: string) => { | |||||
try { | try { | ||||
const defaultCwd = resolve(internalPath, 'src', 'next'); | |||||
const destCwd = resolve(internalPath, '.amanuensis', 'next', 'src'); | |||||
await rm(resolve(destCwd, 'pages'), { recursive: true }); | await rm(resolve(destCwd, 'pages'), { recursive: true }); | ||||
await cp(resolve(defaultCwd, 'pages'), resolve(destCwd, 'pages'), { recursive: true }); | await cp(resolve(defaultCwd, 'pages'), resolve(destCwd, 'pages'), { recursive: true }); | ||||
} catch { | } catch { | ||||
// noop | // 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) => { | await Promise.all(componentsList.map(async (componentPath) => { | ||||
const destPath = resolve(destCwd, componentPath); | const destPath = resolve(destCwd, componentPath); | ||||
let baseCwd = projectCwd; | |||||
let componentDir = customComponentDir; | |||||
try { | try { | ||||
await stat(resolve(baseCwd, componentPath)); | |||||
await stat(resolve(componentDir, componentPath)); | |||||
} catch (errRaw) { | } catch (errRaw) { | ||||
const err = errRaw as NodeJS.ErrnoException; | const err = errRaw as NodeJS.ErrnoException; | ||||
if (err.code === 'ENOENT') { | if (err.code === 'ENOENT') { | ||||
baseCwd = defaultCwd; | |||||
componentDir = defaultComponentDir; | |||||
} | } | ||||
} | } | ||||
await mkdirp(dirname(destPath)); | await mkdirp(dirname(destPath)); | ||||
await cp( | await cp( | ||||
resolve(baseCwd, componentPath), | |||||
resolve(componentDir, componentPath), | |||||
destPath, | destPath, | ||||
); | ); | ||||
process.stdout.write(`Linked ${componentPath}\n`); | 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 {ComponentContext} from '@/contexts/Component'; | ||||
import {Wrapper} from '@/components/Wrapper'; | |||||
import {PreambleContext} from '@/contexts/Preamble'; | import {PreambleContext} from '@/contexts/Preamble'; | ||||
import Preamble from '${preambleImport}'; | import Preamble from '${preambleImport}'; | ||||
import {PageLayout} from '@/components/PageLayout'; | import {PageLayout} from '@/components/PageLayout'; | ||||
@@ -124,44 +104,89 @@ const IndexPage: NextPage = () => { | |||||
}; | }; | ||||
export default IndexPage; | 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) { | } catch (errRaw) { | ||||
throw new CommandError(GenerateReturnCode.COULD_NOT_GENERATE_PAGES, 'Could not write inner page file', errRaw as Error); | throw new CommandError(GenerateReturnCode.COULD_NOT_GENERATE_PAGES, 'Could not write inner page file', errRaw as Error); | ||||
} | } | ||||
try { | try { | ||||
const srcPath = resolve(cwd, 'README.md'); | |||||
const srcPath = resolve(basePath, 'README.md'); | |||||
const destPath = resolve(destCwd, 'content', 'index.md'); | const destPath = resolve(destCwd, 'content', 'index.md'); | ||||
await cp(srcPath, destPath); | await cp(srcPath, destPath); | ||||
} catch (errRaw) { | } catch (errRaw) { | ||||
throw new CommandError(GenerateReturnCode.COULD_NOT_GENERATE_PAGES, 'Could not write index file', errRaw as Error); | 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 const description = 'Generate documentation from typedoc.json' as const; | ||||
export interface RefreshArgs { | export interface RefreshArgs { | ||||
subcommands?: string[]; | 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) => { | const refresh = async (args: RefreshArgs) => { | ||||
try { | try { | ||||
const internalPath = await useInternalPath(); | |||||
const { clean = true } = args; | |||||
if (clean) { | |||||
await cleanContent(internalPath); | |||||
await resetPages(internalPath); | |||||
} | |||||
const basePath = await useBasePath(); | const basePath = await useBasePath(); | ||||
await linkComponents(basePath); | |||||
await linkComponents(basePath, internalPath); | |||||
await writeContent(basePath, internalPath); | |||||
} catch (errRaw) { | } catch (errRaw) { | ||||
const err = errRaw as CommandError; | const err = errRaw as CommandError; | ||||
process.stderr.write(`${err.message}\n`); | process.stderr.write(`${err.message}\n`); | ||||
@@ -3,6 +3,8 @@ | |||||
import { hideBin } from 'yargs/helpers'; | import { hideBin } from 'yargs/helpers'; | ||||
import yargs from 'yargs'; | import yargs from 'yargs'; | ||||
const UNKNOWN_COMMAND_EXIT_CODE = -1 as const; | |||||
const main = async (args: string[]) => { | const main = async (args: string[]) => { | ||||
const COMMANDS = { | const COMMANDS = { | ||||
init: await import('./commands/init'), | init: await import('./commands/init'), | ||||
@@ -26,14 +28,14 @@ const main = async (args: string[]) => { | |||||
const [commandName, ...subcommands] = commandNamesRaw as [keyof typeof COMMANDS, ...string[]]; | const [commandName, ...subcommands] = commandNamesRaw as [keyof typeof COMMANDS, ...string[]]; | ||||
if (typeof commandName === 'undefined') { | if (typeof commandName === 'undefined') { | ||||
yargsBuilder.showHelp(); | yargsBuilder.showHelp(); | ||||
return -1; | |||||
return UNKNOWN_COMMAND_EXIT_CODE; | |||||
} | } | ||||
const { [commandName]: commandDef } = COMMANDS; | const { [commandName]: commandDef } = COMMANDS; | ||||
if (typeof commandDef === 'undefined') { | if (typeof commandDef === 'undefined') { | ||||
process.stderr.write(`Unknown command: ${commandName}\n`); | process.stderr.write(`Unknown command: ${commandName}\n`); | ||||
yargsBuilder.showHelp(); | yargsBuilder.showHelp(); | ||||
return -1; | |||||
return UNKNOWN_COMMAND_EXIT_CODE; | |||||
} | } | ||||
const { default: handler } = commandDef; | const { default: handler } = commandDef; | ||||
@@ -49,5 +51,5 @@ main(hideBin(process.argv)) | |||||
process.exit(code); | process.exit(code); | ||||
}) | }) | ||||
.catch(() => { | .catch(() => { | ||||
process.exit(-1); | |||||
process.exit(-255); | |||||
}); | }); |
@@ -1,12 +1,11 @@ | |||||
import { resolve } from 'path'; | |||||
import { searchForConfigFile } from '../utils/paths'; | import { searchForConfigFile } from '../utils/paths'; | ||||
import { CommandError } from '../utils/error'; | import { CommandError } from '../utils/error'; | ||||
export enum UseBasePathReturnCode { | 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(); | const basePathPrefix = await searchForConfigFile(); | ||||
if (typeof basePathPrefix !== 'string') { | 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; | |||||
}; | }; |
@@ -1,7 +1,7 @@ | |||||
import { resolve } from 'path'; | import { resolve } from 'path'; | ||||
export const useInternalPath = (...args: string[]) => { | |||||
export const useInternalPath = () => { | |||||
return Promise.resolve( | return Promise.resolve( | ||||
resolve(__dirname, '..', '..', '..', ...args), | |||||
resolve(__dirname, '..', '..', '..'), | |||||
); | ); | ||||
}; | }; |