@@ -0,0 +1,9 @@ | |||
export const Wrapper = ({ | |||
children, | |||
}) => { | |||
return ( | |||
<div className="amanuensis-wrapper"> | |||
{children} | |||
</div> | |||
) | |||
}; |
@@ -1,8 +1,16 @@ | |||
{ | |||
"root": true, | |||
"ignorePatterns": [ | |||
"pages/**/*.tsx", | |||
"components/**/*.tsx" | |||
], | |||
"extends": [ | |||
"lxsmnsyc/typescript" | |||
], | |||
"rules": { | |||
"no-tabs": "off", | |||
"indent": "off" | |||
}, | |||
"parserOptions": { | |||
"project": "./tsconfig.eslint.json" | |||
} | |||
@@ -108,3 +108,4 @@ dist | |||
.next/ | |||
types/ | |||
.amanuensis/ | |||
components/ |
@@ -0,0 +1,9 @@ | |||
export const Wrapper = ({ | |||
children, | |||
}) => { | |||
return ( | |||
<div> | |||
{children} | |||
</div> | |||
) | |||
}; |
@@ -55,9 +55,11 @@ | |||
}, | |||
"dependencies": { | |||
"execa": "^7.2.0", | |||
"mkdirp": "^3.0.1", | |||
"next": "13.4.7", | |||
"react": "^18.2.0", | |||
"react-dom": "^18.2.0", | |||
"react-markdown": "^8.0.7", | |||
"typedoc": "^0.24.8", | |||
"yargs": "^17.7.2" | |||
}, | |||
@@ -1,6 +1,7 @@ | |||
import type { AppProps } from 'next/app'; | |||
import { FC } from 'react'; | |||
const App = ({ Component, pageProps }: AppProps) => { | |||
const App: FC = ({ Component, pageProps }: AppProps) => { | |||
return ( | |||
<Component {...pageProps} /> | |||
); |
@@ -0,0 +1,36 @@ | |||
import {GetStaticProps, NextPage} from 'next'; | |||
import {getReadmeText} from '../src/data'; | |||
import ReactMarkdown from 'react-markdown'; | |||
import {Wrapper} from '../components/Wrapper'; | |||
interface IndexPageProps { | |||
readmeType: 'markdown'; | |||
readmeText: string; | |||
} | |||
const IndexPage: NextPage<IndexPageProps> = ({ | |||
readmeType, | |||
readmeText, | |||
}) => { | |||
return ( | |||
<Wrapper> | |||
{readmeType === 'markdown' && ( | |||
<ReactMarkdown> | |||
{readmeText} | |||
</ReactMarkdown> | |||
)} | |||
</Wrapper> | |||
); | |||
}; | |||
export const getStaticProps: GetStaticProps<IndexPageProps> = async () => { | |||
const readmeText = await getReadmeText(); | |||
return { | |||
props: { | |||
readmeType: 'markdown', | |||
readmeText, | |||
}, | |||
}; | |||
}; | |||
export default IndexPage; |
@@ -1,27 +1,25 @@ | |||
import { stat } from 'fs/promises'; | |||
import { resolve } from 'path'; | |||
import { Argv } from 'yargs'; | |||
import {Stats} from 'fs'; | |||
import { Stats } from 'fs'; | |||
export const description = 'Generate documentation from typedoc.json' as const; | |||
interface GenerateArgs { | |||
typedocJsonPath: string; | |||
} | |||
const ensureTypedocJson = async (typedocPath = resolve(process.cwd(), 'typedoc.json')) => { | |||
const ensureTypedocJson = async (typedocPath: string) => { | |||
const trueTypedocPath = resolve(typedocPath); | |||
process.stdout.write(`In path: ${trueTypedocPath}\n`); | |||
process.stdout.write(`Using typedoc.json path: ${trueTypedocPath}\n`); | |||
process.stdout.write('Does the file exist? '); | |||
let statResult: Stats; | |||
try { | |||
statResult = await stat(trueTypedocPath); | |||
} catch (errRaw) { | |||
if (errRaw.code === 'ENOENT') { | |||
const err = errRaw as NodeJS.ErrnoException; | |||
if (err.code === 'ENOENT') { | |||
process.stdout.write('no\n'); | |||
process.stderr.write('Could not find typedoc.json\n'); | |||
throw new Error('Could not find typedoc.json'); | |||
} | |||
process.stdout.write('maybe?\n'); | |||
process.stderr.write('Could not ensure typedoc.json\n'); | |||
throw err; | |||
} | |||
if (statResult.isDirectory()) { | |||
process.stdout.write('no\n'); | |||
@@ -31,38 +29,58 @@ const ensureTypedocJson = async (typedocPath = resolve(process.cwd(), 'typedoc.j | |||
process.stdout.write('yes\n'); | |||
}; | |||
const generateTypedocData = async (cwd = process.cwd()) => { | |||
const generateTypedocData = async () => { | |||
process.stdout.write('Generating typedoc data...\n'); | |||
const outPath = resolve(__dirname, '..', '..', '..', '.amanuensis', 'data.json'); | |||
const typedocBinPath = resolve(__dirname, '..', '..', '..', 'node_modules', '.bin', 'typedoc'); | |||
const { execa } = await import('execa'); | |||
await execa(typedocBinPath, ['--json', outPath]) | |||
.pipeStdout(process.stdout) | |||
.pipeStderr(process.stderr) | |||
await execa(typedocBinPath, ['--json', outPath], { | |||
stdout: 'inherit', | |||
stderr: 'inherit', | |||
}); | |||
process.stdout.write('done\n'); | |||
}; | |||
export const description = 'Generate documentation from typedoc.json' as const; | |||
export enum GenerateReturnCode { | |||
SUCCESS = 0, | |||
NO_TYPEDOC_JSON = -1, | |||
COULD_NOT_GENERATE_TYPEDOC_DATA = -2, | |||
} | |||
export interface GenerateArgs { | |||
typedocJsonPath?: string; | |||
subcommands?: string[]; | |||
} | |||
export const builder = (yargs: Argv) => yargs | |||
.option('typedocJsonPath', { | |||
type: 'string', | |||
default: 'typedoc.json', | |||
alias: 't', | |||
}); | |||
const generate = async (args: GenerateArgs) => { | |||
const { | |||
typedocJsonPath = resolve(process.cwd(), 'typedoc.json'), | |||
} = args; | |||
try { | |||
await ensureTypedocJson(args.typedocJsonPath); | |||
await ensureTypedocJson(typedocJsonPath); | |||
} catch { | |||
return -1; | |||
return GenerateReturnCode.NO_TYPEDOC_JSON; | |||
} | |||
try { | |||
await generateTypedocData(); | |||
} catch { | |||
return -2; | |||
return GenerateReturnCode.COULD_NOT_GENERATE_TYPEDOC_DATA; | |||
} | |||
return 0; | |||
return GenerateReturnCode.SUCCESS; | |||
}; | |||
export default generate; |
@@ -1,12 +1,108 @@ | |||
import {Argv} from 'yargs'; | |||
import { Argv } from 'yargs'; | |||
import { resolve, dirname } from 'path'; | |||
import { cp, stat, unlink } from 'fs/promises'; | |||
import { mkdirp } from 'mkdirp'; | |||
const DEFAULT_PORT = 3000 as const; | |||
const linkComponents = async () => { | |||
process.stdout.write('Linking components...\n'); | |||
const projectCwd = resolve(process.cwd(), '.amanuensis'); | |||
const defaultCwd = resolve(__dirname, '..', '..', '..', 'default'); | |||
const destCwd = resolve(__dirname, '..', '..', '..'); | |||
const componentsList = [ | |||
'components/Wrapper.tsx', | |||
]; | |||
await Promise.all(componentsList.map(async (componentPath) => { | |||
const destPath = resolve(destCwd, componentPath); | |||
try { | |||
await unlink(destPath); | |||
} catch { | |||
// noop | |||
} | |||
let baseCwd = projectCwd; | |||
try { | |||
await stat(resolve(baseCwd, componentPath)); | |||
} catch (errRaw) { | |||
const err = errRaw as NodeJS.ErrnoException; | |||
if (err.code === 'ENOENT') { | |||
baseCwd = defaultCwd; | |||
} | |||
} | |||
await mkdirp(dirname(destPath)); | |||
await cp( | |||
resolve(baseCwd, componentPath), | |||
destPath, | |||
); | |||
process.stdout.write(`Linked ${componentPath}\n`); | |||
})); | |||
process.stdout.write('done\n'); | |||
}; | |||
const buildApp = async () => { | |||
process.stdout.write('Building app...\n'); | |||
const cwd = resolve(__dirname, '..', '..', '..'); | |||
const nextBinPath = resolve(__dirname, '..', '..', '..', 'node_modules', '.bin', 'next'); | |||
const { execa } = await import('execa'); | |||
await execa(nextBinPath, ['build'], { | |||
stdout: 'inherit', | |||
stderr: 'inherit', | |||
cwd, | |||
}); | |||
process.stdout.write('done\n'); | |||
}; | |||
const serveApp = async (port: number) => { | |||
process.stdout.write(`Using port: ${port}...\n`); | |||
process.stdout.write('Serving app...\n'); | |||
const cwd = resolve(__dirname, '..', '..', '..'); | |||
const nextBinPath = resolve(__dirname, '..', '..', '..', 'node_modules', '.bin', 'next'); | |||
const { execa } = await import('execa'); | |||
await execa(nextBinPath, ['start', '-p', port.toString()], { | |||
stdout: 'inherit', | |||
stderr: 'inherit', | |||
cwd, | |||
}); | |||
}; | |||
export const description = 'Start a development server' as const; | |||
export const builder = (yargs: Argv) => yargs; | |||
export enum ServeReturnCode { | |||
SUCCESS = 0, | |||
} | |||
export interface ServeArgs { | |||
port?: number; | |||
subcommands?: string[]; | |||
} | |||
export const builder = (yargs: Argv) => yargs | |||
.option('port', { | |||
type: 'number', | |||
default: DEFAULT_PORT, | |||
alias: 'p', | |||
}); | |||
const serve = async (args: ServeArgs) => { | |||
const { | |||
port = DEFAULT_PORT, | |||
} = args; | |||
const serve = async (args: Record<string, unknown>) => { | |||
console.log('serve', args); | |||
return 0; | |||
await linkComponents(); | |||
await buildApp(); | |||
await serveApp(port); | |||
return ServeReturnCode.SUCCESS; | |||
}; | |||
export default serve; |
@@ -0,0 +1,35 @@ | |||
import { readFile } from 'fs/promises'; | |||
import { resolve } from 'path'; | |||
interface TypedocDataTextNode { | |||
kind: 'text'; | |||
text: string; | |||
} | |||
interface TypedocDataInlineTagNode { | |||
kind: 'inline-tag'; | |||
tag: string; | |||
text: string; | |||
target: number; | |||
tsLinkText: string; | |||
} | |||
type TypedocDataNode = TypedocDataTextNode | TypedocDataInlineTagNode; | |||
export interface TypedocData { | |||
readme: TypedocDataNode[]; | |||
} | |||
export const getReadmeText = async () => { | |||
const typedocDataJson = await readFile(resolve('.amanuensis', 'data.json'), 'utf-8'); | |||
const typedocData = JSON.parse(typedocDataJson) as TypedocData; | |||
return typedocData.readme.reduce( | |||
(theText, node) => { | |||
if (node.kind === 'text') { | |||
return `${theText}${node.text}`; | |||
} | |||
return theText; | |||
}, | |||
'', | |||
); | |||
}; |
@@ -3,56 +3,49 @@ | |||
import { hideBin } from 'yargs/helpers'; | |||
import yargs from 'yargs'; | |||
import * as serve from './commands/serve'; | |||
import * as generate from './commands/generate'; | |||
const COMMANDS = { | |||
serve, | |||
generate, | |||
}; | |||
type CommandName = keyof typeof COMMANDS; | |||
const main = async (args: string[]) => { | |||
const COMMANDS = { | |||
serve: await import('./commands/serve'), | |||
generate: await import('./commands/generate'), | |||
}; | |||
const yargsBuilder = Object.entries(COMMANDS).reduce( | |||
(theYargs, [name, command]) => { | |||
return theYargs.command( | |||
(theYargs, [name, command]) => theYargs.command( | |||
name, | |||
command.description ?? '', | |||
command.builder ?? (yargs => yargs), | |||
); | |||
}, | |||
command.builder ?? ((commandYargs) => commandYargs), | |||
), | |||
yargs | |||
.scriptName('amanuensis'), | |||
); | |||
const { _: commandNamesRaw, ...etcArgs } = await yargsBuilder.parse(args); | |||
const [commandName, ...subcommands] = commandNamesRaw as [CommandName, ...string[]]; | |||
const [commandName, ...subcommands] = commandNamesRaw as [keyof typeof COMMANDS, ...string[]]; | |||
if (typeof commandName === 'undefined') { | |||
yargsBuilder.showHelp(); | |||
return; | |||
return -1; | |||
} | |||
const { [commandName]: commandDef } = COMMANDS; | |||
if (typeof commandDef === 'undefined') { | |||
process.stderr.write(`Unknown command: ${commandName}\n`); | |||
yargsBuilder.showHelp(); | |||
process.exit(-1); | |||
return; | |||
return -1; | |||
} | |||
const { default: handler } = commandDef; | |||
try { | |||
const returnCode = await handler({ | |||
...etcArgs, | |||
_: subcommands, | |||
}); | |||
process.exit(returnCode); | |||
} catch { | |||
process.exit(-1); | |||
} | |||
return handler({ | |||
...etcArgs, | |||
subcommands, | |||
}); | |||
}; | |||
void main(hideBin(process.argv)); | |||
main(hideBin(process.argv)) | |||
.then((code = 0) => { | |||
// noop | |||
process.exit(code); | |||
}) | |||
.catch(() => { | |||
process.exit(-1); | |||
}); |
@@ -1,9 +0,0 @@ | |||
const IndexPage = () => { | |||
return ( | |||
<div> | |||
Hello | |||
</div> | |||
); | |||
}; | |||
export default IndexPage; |
@@ -1,5 +1,9 @@ | |||
{ | |||
"exclude": ["node_modules"], | |||
"exclude": [ | |||
"node_modules", | |||
"pages/**", | |||
"components/**" | |||
], | |||
"include": ["src", "types", "test"], | |||
"compilerOptions": { | |||
"module": "ESNext", | |||
@@ -1,6 +1,8 @@ | |||
{ | |||
"exclude": [ | |||
"node_modules" | |||
"node_modules", | |||
"pages/**", | |||
"components/**", | |||
], | |||
"include": [ | |||
"src", | |||
@@ -715,6 +715,9 @@ importers: | |||
execa: | |||
specifier: ^7.2.0 | |||
version: 7.2.0 | |||
mkdirp: | |||
specifier: ^3.0.1 | |||
version: 3.0.1 | |||
next: | |||
specifier: 13.4.7 | |||
version: 13.4.7(@babel/core@7.22.5)(react-dom@18.2.0)(react@18.2.0) | |||
@@ -724,6 +727,9 @@ importers: | |||
react-dom: | |||
specifier: ^18.2.0 | |||
version: 18.2.0(react@18.2.0) | |||
react-markdown: | |||
specifier: ^8.0.7 | |||
version: 8.0.7(@types/react@18.2.18)(react@18.2.0) | |||
typedoc: | |||
specifier: ^0.24.8 | |||
version: 0.24.8(typescript@4.9.5) | |||
@@ -5741,6 +5747,12 @@ packages: | |||
/minimist@1.2.8: | |||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} | |||
/mkdirp@3.0.1: | |||
resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} | |||
engines: {node: '>=10'} | |||
hasBin: true | |||
dev: false | |||
/mlly@1.4.0: | |||
resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==} | |||
dependencies: | |||
@@ -6461,6 +6473,33 @@ packages: | |||
- supports-color | |||
dev: false | |||
/react-markdown@8.0.7(@types/react@18.2.18)(react@18.2.0): | |||
resolution: {integrity: sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==} | |||
peerDependencies: | |||
'@types/react': '>=16' | |||
react: '>=16' | |||
dependencies: | |||
'@types/hast': 2.3.4 | |||
'@types/prop-types': 15.7.5 | |||
'@types/react': 18.2.18 | |||
'@types/unist': 2.0.6 | |||
comma-separated-tokens: 2.0.3 | |||
hast-util-whitespace: 2.0.1 | |||
prop-types: 15.8.1 | |||
property-information: 6.2.0 | |||
react: 18.2.0 | |||
react-is: 18.2.0 | |||
remark-parse: 10.0.2 | |||
remark-rehype: 10.1.0 | |||
space-separated-tokens: 2.0.2 | |||
style-to-object: 0.4.1 | |||
unified: 10.1.2 | |||
unist-util-visit: 4.1.2 | |||
vfile: 5.3.7 | |||
transitivePeerDependencies: | |||
- supports-color | |||
dev: false | |||
/react-phone-number-input@3.3.0(react-dom@18.2.0)(react@18.2.0): | |||
resolution: {integrity: sha512-6d1lq9parRGnVz6laEN7ijU7MeUCkFEJsTnzB/97nVrm/WE48EDEV5/2bu08mzfZjvX6shpryqG0DUCNiP07Cg==} | |||
peerDependencies: | |||