@@ -0,0 +1,9 @@ | |||||
export const Wrapper = ({ | |||||
children, | |||||
}) => { | |||||
return ( | |||||
<div className="amanuensis-wrapper"> | |||||
{children} | |||||
</div> | |||||
) | |||||
}; |
@@ -1,8 +1,16 @@ | |||||
{ | { | ||||
"root": true, | "root": true, | ||||
"ignorePatterns": [ | |||||
"pages/**/*.tsx", | |||||
"components/**/*.tsx" | |||||
], | |||||
"extends": [ | "extends": [ | ||||
"lxsmnsyc/typescript" | "lxsmnsyc/typescript" | ||||
], | ], | ||||
"rules": { | |||||
"no-tabs": "off", | |||||
"indent": "off" | |||||
}, | |||||
"parserOptions": { | "parserOptions": { | ||||
"project": "./tsconfig.eslint.json" | "project": "./tsconfig.eslint.json" | ||||
} | } | ||||
@@ -108,3 +108,4 @@ dist | |||||
.next/ | .next/ | ||||
types/ | types/ | ||||
.amanuensis/ | .amanuensis/ | ||||
components/ |
@@ -0,0 +1,9 @@ | |||||
export const Wrapper = ({ | |||||
children, | |||||
}) => { | |||||
return ( | |||||
<div> | |||||
{children} | |||||
</div> | |||||
) | |||||
}; |
@@ -55,9 +55,11 @@ | |||||
}, | }, | ||||
"dependencies": { | "dependencies": { | ||||
"execa": "^7.2.0", | "execa": "^7.2.0", | ||||
"mkdirp": "^3.0.1", | |||||
"next": "13.4.7", | "next": "13.4.7", | ||||
"react": "^18.2.0", | "react": "^18.2.0", | ||||
"react-dom": "^18.2.0", | "react-dom": "^18.2.0", | ||||
"react-markdown": "^8.0.7", | |||||
"typedoc": "^0.24.8", | "typedoc": "^0.24.8", | ||||
"yargs": "^17.7.2" | "yargs": "^17.7.2" | ||||
}, | }, | ||||
@@ -1,6 +1,7 @@ | |||||
import type { AppProps } from 'next/app'; | import type { AppProps } from 'next/app'; | ||||
import { FC } from 'react'; | |||||
const App = ({ Component, pageProps }: AppProps) => { | |||||
const App: FC = ({ Component, pageProps }: AppProps) => { | |||||
return ( | return ( | ||||
<Component {...pageProps} /> | <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 { stat } from 'fs/promises'; | ||||
import { resolve } from 'path'; | import { resolve } from 'path'; | ||||
import { Argv } from 'yargs'; | 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); | 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? '); | process.stdout.write('Does the file exist? '); | ||||
let statResult: Stats; | let statResult: Stats; | ||||
try { | try { | ||||
statResult = await stat(trueTypedocPath); | statResult = await stat(trueTypedocPath); | ||||
} catch (errRaw) { | } catch (errRaw) { | ||||
if (errRaw.code === 'ENOENT') { | |||||
const err = errRaw as NodeJS.ErrnoException; | |||||
if (err.code === 'ENOENT') { | |||||
process.stdout.write('no\n'); | process.stdout.write('no\n'); | ||||
process.stderr.write('Could not find typedoc.json\n'); | process.stderr.write('Could not find typedoc.json\n'); | ||||
throw new Error('Could not find typedoc.json'); | 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()) { | if (statResult.isDirectory()) { | ||||
process.stdout.write('no\n'); | process.stdout.write('no\n'); | ||||
@@ -31,38 +29,58 @@ const ensureTypedocJson = async (typedocPath = resolve(process.cwd(), 'typedoc.j | |||||
process.stdout.write('yes\n'); | process.stdout.write('yes\n'); | ||||
}; | }; | ||||
const generateTypedocData = async (cwd = process.cwd()) => { | |||||
const generateTypedocData = async () => { | |||||
process.stdout.write('Generating typedoc data...\n'); | process.stdout.write('Generating typedoc data...\n'); | ||||
const outPath = resolve(__dirname, '..', '..', '..', '.amanuensis', 'data.json'); | const outPath = resolve(__dirname, '..', '..', '..', '.amanuensis', 'data.json'); | ||||
const typedocBinPath = resolve(__dirname, '..', '..', '..', 'node_modules', '.bin', 'typedoc'); | const typedocBinPath = resolve(__dirname, '..', '..', '..', 'node_modules', '.bin', 'typedoc'); | ||||
const { execa } = await import('execa'); | 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'); | 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 | export const builder = (yargs: Argv) => yargs | ||||
.option('typedocJsonPath', { | .option('typedocJsonPath', { | ||||
type: 'string', | type: 'string', | ||||
default: 'typedoc.json', | |||||
alias: 't', | alias: 't', | ||||
}); | }); | ||||
const generate = async (args: GenerateArgs) => { | const generate = async (args: GenerateArgs) => { | ||||
const { | |||||
typedocJsonPath = resolve(process.cwd(), 'typedoc.json'), | |||||
} = args; | |||||
try { | try { | ||||
await ensureTypedocJson(args.typedocJsonPath); | |||||
await ensureTypedocJson(typedocJsonPath); | |||||
} catch { | } catch { | ||||
return -1; | |||||
return GenerateReturnCode.NO_TYPEDOC_JSON; | |||||
} | } | ||||
try { | try { | ||||
await generateTypedocData(); | await generateTypedocData(); | ||||
} catch { | } catch { | ||||
return -2; | |||||
return GenerateReturnCode.COULD_NOT_GENERATE_TYPEDOC_DATA; | |||||
} | } | ||||
return 0; | |||||
return GenerateReturnCode.SUCCESS; | |||||
}; | }; | ||||
export default generate; | 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 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; | 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 { hideBin } from 'yargs/helpers'; | ||||
import yargs from 'yargs'; | 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 main = async (args: string[]) => { | ||||
const COMMANDS = { | |||||
serve: await import('./commands/serve'), | |||||
generate: await import('./commands/generate'), | |||||
}; | |||||
const yargsBuilder = Object.entries(COMMANDS).reduce( | const yargsBuilder = Object.entries(COMMANDS).reduce( | ||||
(theYargs, [name, command]) => { | |||||
return theYargs.command( | |||||
(theYargs, [name, command]) => theYargs.command( | |||||
name, | name, | ||||
command.description ?? '', | command.description ?? '', | ||||
command.builder ?? (yargs => yargs), | |||||
); | |||||
}, | |||||
command.builder ?? ((commandYargs) => commandYargs), | |||||
), | |||||
yargs | yargs | ||||
.scriptName('amanuensis'), | .scriptName('amanuensis'), | ||||
); | ); | ||||
const { _: commandNamesRaw, ...etcArgs } = await yargsBuilder.parse(args); | 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') { | if (typeof commandName === 'undefined') { | ||||
yargsBuilder.showHelp(); | yargsBuilder.showHelp(); | ||||
return; | |||||
return -1; | |||||
} | } | ||||
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(); | ||||
process.exit(-1); | |||||
return; | |||||
return -1; | |||||
} | } | ||||
const { default: handler } = commandDef; | 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"], | "include": ["src", "types", "test"], | ||||
"compilerOptions": { | "compilerOptions": { | ||||
"module": "ESNext", | "module": "ESNext", | ||||
@@ -1,6 +1,8 @@ | |||||
{ | { | ||||
"exclude": [ | "exclude": [ | ||||
"node_modules" | |||||
"node_modules", | |||||
"pages/**", | |||||
"components/**", | |||||
], | ], | ||||
"include": [ | "include": [ | ||||
"src", | "src", | ||||
@@ -715,6 +715,9 @@ importers: | |||||
execa: | execa: | ||||
specifier: ^7.2.0 | specifier: ^7.2.0 | ||||
version: 7.2.0 | version: 7.2.0 | ||||
mkdirp: | |||||
specifier: ^3.0.1 | |||||
version: 3.0.1 | |||||
next: | next: | ||||
specifier: 13.4.7 | specifier: 13.4.7 | ||||
version: 13.4.7(@babel/core@7.22.5)(react-dom@18.2.0)(react@18.2.0) | 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: | react-dom: | ||||
specifier: ^18.2.0 | specifier: ^18.2.0 | ||||
version: 18.2.0(react@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: | typedoc: | ||||
specifier: ^0.24.8 | specifier: ^0.24.8 | ||||
version: 0.24.8(typescript@4.9.5) | version: 0.24.8(typescript@4.9.5) | ||||
@@ -5741,6 +5747,12 @@ packages: | |||||
/minimist@1.2.8: | /minimist@1.2.8: | ||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} | 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: | /mlly@1.4.0: | ||||
resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==} | resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==} | ||||
dependencies: | dependencies: | ||||
@@ -6461,6 +6473,33 @@ packages: | |||||
- supports-color | - supports-color | ||||
dev: false | 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): | /react-phone-number-input@3.3.0(react-dom@18.2.0)(react@18.2.0): | ||||
resolution: {integrity: sha512-6d1lq9parRGnVz6laEN7ijU7MeUCkFEJsTnzB/97nVrm/WE48EDEV5/2bu08mzfZjvX6shpryqG0DUCNiP07Cg==} | resolution: {integrity: sha512-6d1lq9parRGnVz6laEN7ijU7MeUCkFEJsTnzB/97nVrm/WE48EDEV5/2bu08mzfZjvX6shpryqG0DUCNiP07Cg==} | ||||
peerDependencies: | peerDependencies: | ||||