diff --git a/.amanuensis/components/Wrapper.tsx b/.amanuensis/components/Wrapper.tsx
new file mode 100644
index 0000000..1d68959
--- /dev/null
+++ b/.amanuensis/components/Wrapper.tsx
@@ -0,0 +1,9 @@
+export const Wrapper = ({
+ children,
+}) => {
+ return (
+
+ {children}
+
+ )
+};
diff --git a/packages/amanuensis/.eslintrc b/packages/amanuensis/.eslintrc
index 35cfb79..e8684d5 100644
--- a/packages/amanuensis/.eslintrc
+++ b/packages/amanuensis/.eslintrc
@@ -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"
}
diff --git a/packages/amanuensis/.gitignore b/packages/amanuensis/.gitignore
index 06b929d..64fbf8b 100644
--- a/packages/amanuensis/.gitignore
+++ b/packages/amanuensis/.gitignore
@@ -108,3 +108,4 @@ dist
.next/
types/
.amanuensis/
+components/
diff --git a/packages/amanuensis/default/components/Wrapper.tsx b/packages/amanuensis/default/components/Wrapper.tsx
new file mode 100644
index 0000000..9a63374
--- /dev/null
+++ b/packages/amanuensis/default/components/Wrapper.tsx
@@ -0,0 +1,9 @@
+export const Wrapper = ({
+ children,
+}) => {
+ return (
+
+ {children}
+
+ )
+};
diff --git a/packages/amanuensis/package.json b/packages/amanuensis/package.json
index 108c40f..ac2d7b3 100644
--- a/packages/amanuensis/package.json
+++ b/packages/amanuensis/package.json
@@ -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"
},
diff --git a/packages/amanuensis/src/pages/_app.tsx b/packages/amanuensis/pages/_app.tsx
similarity index 56%
rename from packages/amanuensis/src/pages/_app.tsx
rename to packages/amanuensis/pages/_app.tsx
index 3d4b080..5c3b54a 100644
--- a/packages/amanuensis/src/pages/_app.tsx
+++ b/packages/amanuensis/pages/_app.tsx
@@ -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 (
);
diff --git a/packages/amanuensis/pages/index.tsx b/packages/amanuensis/pages/index.tsx
new file mode 100644
index 0000000..5a75574
--- /dev/null
+++ b/packages/amanuensis/pages/index.tsx
@@ -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 = ({
+ readmeType,
+ readmeText,
+}) => {
+ return (
+
+ {readmeType === 'markdown' && (
+
+ {readmeText}
+
+ )}
+
+ );
+};
+
+export const getStaticProps: GetStaticProps = async () => {
+ const readmeText = await getReadmeText();
+ return {
+ props: {
+ readmeType: 'markdown',
+ readmeText,
+ },
+ };
+};
+
+export default IndexPage;
diff --git a/packages/amanuensis/src/commands/generate.ts b/packages/amanuensis/src/commands/generate.ts
index 54f047e..2ee105f 100644
--- a/packages/amanuensis/src/commands/generate.ts
+++ b/packages/amanuensis/src/commands/generate.ts
@@ -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;
diff --git a/packages/amanuensis/src/commands/serve.ts b/packages/amanuensis/src/commands/serve.ts
index 42885a8..4bb867e 100644
--- a/packages/amanuensis/src/commands/serve.ts
+++ b/packages/amanuensis/src/commands/serve.ts
@@ -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) => {
- console.log('serve', args);
- return 0;
+ await linkComponents();
+ await buildApp();
+ await serveApp(port);
+ return ServeReturnCode.SUCCESS;
};
export default serve;
diff --git a/packages/amanuensis/src/data.ts b/packages/amanuensis/src/data.ts
new file mode 100644
index 0000000..8f0b511
--- /dev/null
+++ b/packages/amanuensis/src/data.ts
@@ -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;
+ },
+ '',
+ );
+};
diff --git a/packages/amanuensis/src/index.ts b/packages/amanuensis/src/index.ts
index 92d3c63..1135c6b 100644
--- a/packages/amanuensis/src/index.ts
+++ b/packages/amanuensis/src/index.ts
@@ -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);
+ });
diff --git a/packages/amanuensis/src/pages/index.tsx b/packages/amanuensis/src/pages/index.tsx
deleted file mode 100644
index a749644..0000000
--- a/packages/amanuensis/src/pages/index.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-const IndexPage = () => {
- return (
-
- Hello
-
- );
-};
-
-export default IndexPage;
diff --git a/packages/amanuensis/tsconfig.eslint.json b/packages/amanuensis/tsconfig.eslint.json
index 459f2a1..8cc71b1 100644
--- a/packages/amanuensis/tsconfig.eslint.json
+++ b/packages/amanuensis/tsconfig.eslint.json
@@ -1,5 +1,9 @@
{
- "exclude": ["node_modules"],
+ "exclude": [
+ "node_modules",
+ "pages/**",
+ "components/**"
+ ],
"include": ["src", "types", "test"],
"compilerOptions": {
"module": "ESNext",
diff --git a/packages/amanuensis/tsconfig.json b/packages/amanuensis/tsconfig.json
index 70e049f..09bbf1c 100644
--- a/packages/amanuensis/tsconfig.json
+++ b/packages/amanuensis/tsconfig.json
@@ -1,6 +1,8 @@
{
"exclude": [
- "node_modules"
+ "node_modules",
+ "pages/**",
+ "components/**",
],
"include": [
"src",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6519ecd..753af77 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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: