Browse Source

Fix paths

Use correct resolutions for paths.
master
TheoryOfNekomata 1 year ago
parent
commit
16fb4ab27e
11 changed files with 242 additions and 138 deletions
  1. +0
    -15
      .amanuensis/components/Wrapper.tsx
  2. +0
    -0
      amanuensis.config.json
  3. +51
    -50
      packages/amanuensis/src/commands/analyze.ts
  4. +34
    -9
      packages/amanuensis/src/commands/init.ts
  5. +61
    -43
      packages/amanuensis/src/commands/refresh.ts
  6. +11
    -5
      packages/amanuensis/src/commands/serve.ts
  7. +22
    -0
      packages/amanuensis/src/mixins/base-path.ts
  8. +7
    -0
      packages/amanuensis/src/mixins/internal-path.ts
  9. +26
    -16
      packages/amanuensis/src/utils/data.ts
  10. +7
    -0
      packages/amanuensis/src/utils/error.ts
  11. +23
    -0
      packages/amanuensis/src/utils/paths.ts

+ 0
- 15
.amanuensis/components/Wrapper.tsx View File

@@ -1,15 +0,0 @@
import {FC, ReactNode} from 'react';

export interface WrapperProps {
children: ReactNode;
}

export const Wrapper: FC<WrapperProps> = ({
children,
}) => {
return (
<div className="amanuensis-wrapper">
{children}
</div>
)
};

.amanuensis/config.json → amanuensis.config.json View File


+ 51
- 50
packages/amanuensis/src/commands/analyze.ts View File

@@ -1,8 +1,18 @@
import { readFile, stat, writeFile } from 'fs/promises';
import { stat, writeFile } from 'fs/promises';
import { resolve } from 'path';
import { Argv } from 'yargs';
import { Stats } from 'fs';
import { getPackages, TypedocData } from '../utils/data';
import { getPackages } from '../utils/data';
import { useBasePath } from '../mixins/base-path';
import { CommandError } from '../utils/error';
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,
}

const ensureTypedocJson = async (typedocPath: string) => {
const trueTypedocPath = resolve(typedocPath);
@@ -15,62 +25,59 @@ const ensureTypedocJson = async (typedocPath: string) => {
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');
throw new CommandError(GenerateReturnCode.NO_TYPEDOC_JSON, 'Could not find typedoc.json');
}
process.stdout.write('maybe?\n');
process.stderr.write('Could not ensure typedoc.json\n');
throw err;
throw new CommandError(GenerateReturnCode.NO_TYPEDOC_JSON, 'Could not ensure typedoc.json', err);
}

if (statResult.isDirectory()) {
process.stdout.write('no\n');
process.stderr.write('typedoc.json is a directory\n');
throw new Error('typedoc.json is a directory');
throw new CommandError(GenerateReturnCode.NO_TYPEDOC_JSON, 'typedoc.json is a directory');
}
process.stdout.write('yes\n');
};

const generateTypedocData = async () => {
const generateTypedocData = async (outPath: string, basePath: string) => {
process.stdout.write('Generating typedoc data...\n');

const outPath = resolve(__dirname, '..', '..', '..', '.amanuensis', 'data.json');
const typedocBinPath = resolve(__dirname, '..', '..', '..', 'node_modules', '.bin', 'typedoc');
const typedocBinPath = await useInternalPath('node_modules', '.bin', 'typedoc');
const { execa } = await import('execa');

await execa(typedocBinPath, ['--json', outPath], {
const result = await execa(typedocBinPath, ['--json', outPath, '--pretty', 'false'], {
stdout: 'inherit',
stderr: 'inherit',
cwd: basePath,
});

if (result.exitCode !== 0) {
process.stdout.write('failed\n');
throw new CommandError(GenerateReturnCode.COULD_NOT_GENERATE_TYPEDOC_DATA, 'Could not generate typedoc data');
}

process.stdout.write('done\n');
};

const produceGroupings = async () => {
const generatePackageData = async (
typedocDataJsonPath: string,
configPath: string,
basePath: string,
) => {
process.stdout.write('Grouping typedoc data...\n');

const typedocDataJsonPath = resolve(__dirname, '..', '..', '..', '.amanuensis', 'data.json');
const typedocDataJson = await readFile(typedocDataJsonPath, 'utf-8');
const typedocData = JSON.parse(typedocDataJson) as TypedocData;

const packages = await getPackages(process.cwd());
const groupings = {
packages,
typedocData,
};

await writeFile(typedocDataJsonPath, JSON.stringify(groupings, null, 2));
const packages = await getPackages(configPath, basePath);

process.stdout.write(`File written to ${typedocDataJsonPath}\n`);
try {
await writeFile(typedocDataJsonPath, JSON.stringify(packages));
process.stdout.write(`File written to ${typedocDataJsonPath}\n`);
} catch (errRaw) {
const err = errRaw as NodeJS.ErrnoException;
process.stderr.write(`Could not write to ${typedocDataJsonPath}: ${err.message}\n`);
throw new CommandError(GenerateReturnCode.COULD_NOT_GENERATE_PACKAGE_DATA, 'Could not generate package data', err);
}
};

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,
COULD_NOT_PRODUCE_GROUPINGS = -3,
}
export const description = 'Analyze project for fetching documentation data' as const;

export interface AnalyzeArgs {
typedocJsonPath?: string;
@@ -84,26 +91,20 @@ export const builder = (yargs: Argv) => yargs
});

const analyze = async (args: AnalyzeArgs) => {
const {
typedocJsonPath = resolve(process.cwd(), 'typedoc.json'),
} = args;
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 {
await ensureTypedocJson(typedocJsonPath);
} catch {
return GenerateReturnCode.NO_TYPEDOC_JSON;
}

try {
await generateTypedocData();
} catch {
return GenerateReturnCode.COULD_NOT_GENERATE_TYPEDOC_DATA;
}

try {
await produceGroupings();
} catch {
return GenerateReturnCode.COULD_NOT_PRODUCE_GROUPINGS;
await generateTypedocData(typedocOutPath, basePath);
await generatePackageData(packagesPath, configPath, basePath);
} catch (errRaw) {
const err = errRaw as CommandError;
process.stderr.write(`${err.message}\n`);
return err.exitCode;
}

return GenerateReturnCode.SUCCESS;


+ 34
- 9
packages/amanuensis/src/commands/init.ts View File

@@ -1,11 +1,26 @@
import { cp } from 'fs/promises';
import { resolve } from 'path';
import { Argv } from 'yargs';
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,
}

const copyFiles = async () => {
const srcPath = resolve(__dirname, '..', '..', '..', 'src', 'next');
const destPath = resolve(__dirname, '..', '..', '..', '.amanuensis', 'next');
await cp(srcPath, destPath, { recursive: true });
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;
throw new CommandError(
InitReturnCode.COULD_NOT_COPY_FILES,
`Could not copy files: ${err.message}`,
);
}
};

interface PackageManager {
@@ -54,14 +69,18 @@ const installDependencies = async () => {
}

const { [selectedPackageManagerIndex]: selectedPackageManager } = packageManagers;
const cwd = resolve(__dirname, '..', '..', '..', '.amanuensis', 'next');
const cwd = await useInternalPath('.amanuensis', 'next');
process.stdout.write(`In path: ${cwd}\n`);
process.stdout.write(`Installing dependencies with ${selectedPackageManager.name}\n`);
await execa(
const result = await execa(
selectedPackageManager.installCmd[0],
selectedPackageManager.installCmd[1],
{ cwd },
);

if (result.exitCode !== 0) {
throw new CommandError(InitReturnCode.COULD_NOT_INSTALL_DEPENDENCIES, 'Could not install dependencies');
}
};

export const description = 'Initialize a new Amanuensis project' as const;
@@ -69,9 +88,15 @@ export const description = 'Initialize a new Amanuensis project' as const;
export const builder = (yargsBuilder: Argv) => yargsBuilder;

const init = async () => {
await copyFiles();
await installDependencies();
return 0;
try {
await copyFiles();
await installDependencies();
} catch (errRaw) {
const err = errRaw as CommandError;
process.stderr.write(`${err.message}\n`);
return err.exitCode;
}
return InitReturnCode.SUCCESS;
};

export default init;

+ 61
- 43
packages/amanuensis/src/commands/refresh.ts View File

@@ -1,17 +1,27 @@
import {
cp, readFile, rm, stat, writeFile,
} from 'fs/promises';
import { dirname, resolve, basename, extname, join } from 'path';
import {
dirname, resolve, basename, extname, join,
} from 'path';
import { Argv } from 'yargs';
import { mkdirp } from 'mkdirp';
import { TypedocData } from '../utils/data';
import { PackageData } from '../utils/data';
import { CommandError } from '../utils/error';
import { useBasePath } from '../mixins/base-path';
import { useInternalPath } from '../mixins/internal-path';

export enum GenerateReturnCode {
SUCCESS = 0,
COULD_NOT_GENERATE_PAGES = -2,
}

const linkComponents = async () => {
const linkComponents = async (cwd: string) => {
process.stdout.write('Linking components...\n');

const projectCwd = resolve(process.cwd(), '.amanuensis');
const defaultCwd = resolve(__dirname, '..', '..', '..', 'src', 'next');
const destCwd = resolve(__dirname, '..', '..', '..', '.amanuensis', 'next');
const projectCwd = resolve(cwd, '.amanuensis');
const defaultCwd = await useInternalPath('src', 'next');
const destCwd = await useInternalPath('.amanuensis', 'next');
const componentsList = [
'components/Wrapper.tsx',
];
@@ -21,6 +31,7 @@ const linkComponents = async () => {
} catch {
// noop
}

await Promise.all(componentsList.map(async (componentPath) => {
const destPath = resolve(destCwd, componentPath);
let baseCwd = projectCwd;
@@ -42,23 +53,24 @@ const linkComponents = async () => {
process.stdout.write(`Linked ${componentPath}\n`);
}));

const typedocDataJsonPath = resolve(__dirname, '..', '..', '..', '.amanuensis', 'data.json');
const typedocDataJson = await readFile(typedocDataJsonPath, 'utf-8');
const typedocData = JSON.parse(typedocDataJson) as TypedocData;

await Promise.all(
typedocData.packages.map(async (pkg: any) => {
await mkdirp(resolve(destCwd, 'content', pkg.basePath));
await mkdirp(resolve(destCwd, 'pages', pkg.basePath));
await Promise.all(
pkg.markdown.map(async (m: any) => {
const srcPath = resolve(process.cwd(), pkg.basePath, m.filePath);
const destPath = resolve(destCwd, 'content', pkg.basePath, m.name);
const pageDestPath = resolve(destCwd, 'pages', pkg.basePath, `${basename(m.name, extname(m.name))}.tsx`);
await cp(srcPath, destPath);
await writeFile(
pageDestPath,
`import {NextPage} from 'next';
const packagesPath = await useInternalPath('.amanuensis', 'packages.json');
const typedocDataJson = await readFile(packagesPath, 'utf-8');
const typedocData = JSON.parse(typedocDataJson) as PackageData[];

try {
await Promise.all(
typedocData.map(async (pkg) => {
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);
const pageDestPath = resolve(destCwd, 'pages', pkg.basePath, `${basename(m.name, extname(m.name))}.tsx`);
await cp(srcPath, destPath);
await writeFile(
pageDestPath,
`import {NextPage} from 'next';
import {Wrapper} from '@/components/Wrapper';
import Content from '@/${join('content', pkg.basePath, m.name)}';

@@ -72,29 +84,32 @@ const IndexPage: NextPage = () => {

export default IndexPage;
`,
// todo fix problem when building with import aliases
// todo find a way to build with tailwind
// todo link components to next project
);
}),
);
}),
);

const srcPath = resolve(process.cwd(), 'README.md');
const destPath = resolve(destCwd, 'content', 'index.md');
await cp(srcPath, destPath);
// todo fix problem when building with import aliases
// todo find a way to build with tailwind
// todo link components to next project
);
}),
);
}),
);
} catch (errRaw) {
console.log(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 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 enum GenerateReturnCode {
SUCCESS = 0,
COULD_NOT_GENERATE_PAGES = -1,
}

export interface RefreshArgs {
subcommands?: string[];
}
@@ -107,9 +122,12 @@ export const builder = (yargs: Argv) => yargs

const refresh = async (args: RefreshArgs) => {
try {
await linkComponents();
} catch {
return GenerateReturnCode.COULD_NOT_GENERATE_PAGES;
const basePath = await useBasePath();
await linkComponents(basePath);
} catch (errRaw) {
const err = errRaw as CommandError;
process.stderr.write(`${err.message}\n`);
return err.exitCode;
}

return GenerateReturnCode.SUCCESS;


+ 11
- 5
packages/amanuensis/src/commands/serve.ts View File

@@ -1,5 +1,6 @@
import { Argv } from 'yargs';
import { resolve } from 'path';
import {CommandError} from '../utils/error';

const DEFAULT_PORT = 3000 as const;

@@ -53,12 +54,17 @@ export const builder = (yargs: Argv) => yargs
});

const serve = async (args: ServeArgs) => {
const {
port = DEFAULT_PORT,
} = args;
const { port = DEFAULT_PORT } = args;

try {
await buildApp();
await serveApp(port);
} catch (errRaw) {
const err = errRaw as CommandError;
process.stderr.write(`${err.message}\n`);
return err.exitCode;
}

await buildApp();
await serveApp(port);
return ServeReturnCode.SUCCESS;
};



+ 22
- 0
packages/amanuensis/src/mixins/base-path.ts View File

@@ -0,0 +1,22 @@
import { resolve } from 'path';
import { searchForConfigFile } from '../utils/paths';
import { CommandError } from '../utils/error';

export enum UseBasePathReturnCode {
COULD_NOT_FIND_CONFIG_FILE = -1,
}

export const useBasePath = async (...args: string[]) => {
const basePathPrefix = await searchForConfigFile();

if (typeof basePathPrefix !== 'string') {
throw new CommandError(
UseBasePathReturnCode.COULD_NOT_FIND_CONFIG_FILE,
'Could not find config file',
);
}

const basePath = resolve(basePathPrefix, ...args);
process.stdout.write(`Using base path: ${basePath}\n`);
return basePath;
};

+ 7
- 0
packages/amanuensis/src/mixins/internal-path.ts View File

@@ -0,0 +1,7 @@
import { resolve } from 'path';

export const useInternalPath = (...args: string[]) => {
return Promise.resolve(
resolve(__dirname, '..', '..', '..', ...args),
);
};

+ 26
- 16
packages/amanuensis/src/utils/data.ts View File

@@ -31,15 +31,19 @@ interface SymbolIdMapEntry {
type TypedocDataNode = TypedocDataTextNode | TypedocDataInlineTagNode;

export interface TypedocData {
packages: any[];
typedocData: {
readme: TypedocDataNode[];
symbolIdMap: Record<string, SymbolIdMapEntry>;
};
readme: TypedocDataNode[];
symbolIdMap: Record<string, SymbolIdMapEntry>;
}

export const getPackages = async (cwd = process.cwd()) => {
const configPath = resolve(cwd, '.amanuensis', 'config.json');
export interface PackageData {
name: string;
packageJson: Record<string, unknown>;
basePath: string;
markdown: { name: string; filePath: string; content: string }[];
classifications: Record<string, string | undefined>;
}

export const getPackages = async (configPath: string, cwd: string): Promise<PackageData[]> => {
const configString = await readFile(configPath, 'utf-8');
const config = JSON.parse(configString) as AmanuensisConfig;
const searchPatternsRaw = config.package.searchPatterns;
@@ -50,21 +54,26 @@ export const getPackages = async (cwd = process.cwd()) => {
? searchPattern
: `${searchPattern}/package.json`,
{
cwd,
ignore: ['**/node_modules/**'],
},
)),
);
const packagePaths = patternPackagePaths.flat();
const markdownFilePaths = await glob(
const markdownFilePathsRaw = await glob(
'**/*.{md,mdx}',
{
cwd,
ignore: ['**/node_modules/**'],
},
);
return Promise.all(
packagePaths.map(async (packagePath) => {
const packageString = await readFile(packagePath, 'utf-8');
const basePath = dirname(packagePath);
const markdownFilePaths = markdownFilePathsRaw.map((p) => resolve(cwd, p));

const readPackages = await Promise.all(
packagePaths.map(async (packagePathRaw) => {
const absolutePackagePath = resolve(cwd, packagePathRaw);
const packageString = await readFile(absolutePackagePath, 'utf-8');
const basePath = dirname(absolutePackagePath);
const packageJson = JSON.parse(packageString) as { name: string };
const classifications = Object.fromEntries(
Object.entries(config.package.classifications)
@@ -100,23 +109,24 @@ export const getPackages = async (cwd = process.cwd()) => {
return {
name: packageJson.name,
packageJson,
basePath,
basePath: basePath.slice(cwd.length + 1),
markdown,
classifications,
};
}),
);

return readPackages.sort((a, b) => a.name.localeCompare(b.name));
};

export const getFileSources = async (cwd = process.cwd()) => {
const typedocDataJsonPath = resolve(cwd, '.amanuensis', 'data.json');
const typedocDataJson = await readFile(typedocDataJsonPath, 'utf-8');
const typedocData = JSON.parse(typedocDataJson) as TypedocData;
const symbolIdMapEntries = Object.values(typedocData.typedocData.symbolIdMap);
const symbolIdMapEntries = Object.values(typedocData.symbolIdMap);
const firstPartySources = symbolIdMapEntries.filter(
({ sourceFileName }) => !sourceFileName.startsWith('node_modules'),
);
const firstPartySourceFiles = firstPartySources.map(({ sourceFileName }) => sourceFileName);
const uniqueFirstPartySourceFiles = Array.from(new Set(firstPartySourceFiles));
return uniqueFirstPartySourceFiles;
return Array.from(new Set(firstPartySourceFiles));
};

+ 7
- 0
packages/amanuensis/src/utils/error.ts View File

@@ -0,0 +1,7 @@
export class CommandError extends Error {
constructor(readonly exitCode: number, message: string, cause?: Error) {
super(message);
this.name = 'CommandError';
this.cause = cause;
}
}

+ 23
- 0
packages/amanuensis/src/utils/paths.ts View File

@@ -0,0 +1,23 @@
import * as fs from 'fs/promises';
import * as path from 'path';

export const searchForConfigFile = async () => {
const filePath: string = path.resolve(process.cwd(), 'amanuensis.config.json');

async function searchInDir(dirPath: string): Promise<string | null> {
const configFile: string = path.join(dirPath, 'amanuensis.config.json');
try {
await fs.access(configFile, fs.constants.F_OK);
return dirPath;
} catch (err) {
if (dirPath === path.dirname(dirPath)) {
// Reached the root directory, config file not found
return null;
}
const parentDir: string = path.dirname(dirPath);
return searchInDir(parentDir);
}
}

return searchInDir(filePath);
};

Loading…
Cancel
Save