Browse Source

Organize paths

Use a single source of truth for derived paths in commands.
master
TheoryOfNekomata 1 year ago
parent
commit
102f178700
6 changed files with 136 additions and 108 deletions
  1. +17
    -16
      packages/amanuensis/src/commands/analyze.ts
  2. +15
    -12
      packages/amanuensis/src/commands/init.ts
  3. +94
    -69
      packages/amanuensis/src/commands/refresh.ts
  4. +5
    -3
      packages/amanuensis/src/index.ts
  5. +3
    -6
      packages/amanuensis/src/mixins/base-path.ts
  6. +2
    -2
      packages/amanuensis/src/mixins/internal-path.ts

+ 17
- 16
packages/amanuensis/src/commands/analyze.ts View File

@@ -9,9 +9,9 @@ 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,
NO_TYPEDOC_JSON = -3,
COULD_NOT_GENERATE_TYPEDOC_DATA = -4,
COULD_NOT_GENERATE_PACKAGE_DATA = -5,
}

const ensureTypedocJson = async (typedocPath: string) => {
@@ -38,10 +38,10 @@ const ensureTypedocJson = async (typedocPath: string) => {
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');
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 result = await execa(typedocBinPath, ['--json', outPath, '--pretty', 'false'], {
@@ -59,11 +59,12 @@ const generateTypedocData = async (outPath: string, basePath: string) => {
};

const generatePackageData = async (
typedocDataJsonPath: string,
configPath: string,
basePath: string,
internalPath: string,
) => {
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);

@@ -91,16 +92,16 @@ export const builder = (yargs: Argv) => yargs
});

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 {
const basePath = await useBasePath();
process.stdout.write(`Using base path: ${basePath}\n`);

const typedocJsonPath = args.typedocJsonPath ?? resolve(basePath, 'typedoc.json');
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) {
const err = errRaw as CommandError;
process.stderr.write(`${err.message}\n`);


+ 15
- 12
packages/amanuensis/src/commands/init.ts View File

@@ -1,18 +1,19 @@
import { cp } from 'fs/promises';
import { Argv } from 'yargs';
import { resolve } from 'path';
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,
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 {
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;
@@ -29,7 +30,7 @@ interface PackageManager {
installCmd: [string, string[]];
}

const packageManagers: PackageManager[] = [
const PACKAGE_MANAGERS: PackageManager[] = [
{
name: 'pnpm',
testCmd: ['pnpm', ['--version']],
@@ -47,10 +48,10 @@ const packageManagers: PackageManager[] = [
},
];

const installDependencies = async () => {
const installDependencies = async (internalPath: string) => {
const { execa } = await import('execa');

const selectedPackageManagerIndex = await packageManagers.reduce(
const selectedPackageManagerIndex = await PACKAGE_MANAGERS.reduce(
async (prevPmPromise, pkgm, i) => {
const prevPm = await prevPmPromise;
const { testCmd } = pkgm;
@@ -68,8 +69,8 @@ const installDependencies = async () => {
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(`Installing dependencies with ${selectedPackageManager.name}\n`);
const result = await execa(
@@ -89,8 +90,10 @@ export const builder = (yargsBuilder: Argv) => yargsBuilder;

const init = async () => {
try {
await copyFiles();
await installDependencies();
const internalPath = await useInternalPath();

await copyFiles(internalPath);
await installDependencies(internalPath);
} catch (errRaw) {
const err = errRaw as CommandError;
process.stderr.write(`${err.message}\n`);


+ 94
- 69
packages/amanuensis/src/commands/refresh.ts View File

@@ -22,93 +22,73 @@ import { useInternalPath } from '../mixins/internal-path';

export enum GenerateReturnCode {
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 {
const destCwd = resolve(internalPath, '.amanuensis', 'next', 'src');
await rm(resolve(destCwd, 'content'), { recursive: true });
} catch {
// noop
}
};

const resetPages = async (internalPath: string) => {
try {
const defaultCwd = resolve(internalPath, 'src', 'next');
const destCwd = resolve(internalPath, '.amanuensis', 'next', 'src');
await rm(resolve(destCwd, 'pages'), { recursive: true });
await cp(resolve(defaultCwd, 'pages'), resolve(destCwd, 'pages'), { recursive: true });
} catch {
// 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) => {
const destPath = resolve(destCwd, componentPath);
let baseCwd = projectCwd;
let componentDir = customComponentDir;

try {
await stat(resolve(baseCwd, componentPath));
await stat(resolve(componentDir, componentPath));
} catch (errRaw) {
const err = errRaw as NodeJS.ErrnoException;
if (err.code === 'ENOENT') {
baseCwd = defaultCwd;
componentDir = defaultComponentDir;
}
}

await mkdirp(dirname(destPath));
await cp(
resolve(baseCwd, componentPath),
resolve(componentDir, componentPath),
destPath,
);
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 {Wrapper} from '@/components/Wrapper';
import {PreambleContext} from '@/contexts/Preamble';
import Preamble from '${preambleImport}';
import {PageLayout} from '@/components/PageLayout';
@@ -124,44 +104,89 @@ const IndexPage: NextPage = () => {
};

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) {
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 srcPath = resolve(basePath, '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 interface RefreshArgs {
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) => {
try {
const internalPath = await useInternalPath();
const { clean = true } = args;

if (clean) {
await cleanContent(internalPath);
await resetPages(internalPath);
}

const basePath = await useBasePath();
await linkComponents(basePath);
await linkComponents(basePath, internalPath);
await writeContent(basePath, internalPath);
} catch (errRaw) {
const err = errRaw as CommandError;
process.stderr.write(`${err.message}\n`);


+ 5
- 3
packages/amanuensis/src/index.ts View File

@@ -3,6 +3,8 @@
import { hideBin } from 'yargs/helpers';
import yargs from 'yargs';

const UNKNOWN_COMMAND_EXIT_CODE = -1 as const;

const main = async (args: string[]) => {
const COMMANDS = {
init: await import('./commands/init'),
@@ -26,14 +28,14 @@ const main = async (args: string[]) => {
const [commandName, ...subcommands] = commandNamesRaw as [keyof typeof COMMANDS, ...string[]];
if (typeof commandName === 'undefined') {
yargsBuilder.showHelp();
return -1;
return UNKNOWN_COMMAND_EXIT_CODE;
}

const { [commandName]: commandDef } = COMMANDS;
if (typeof commandDef === 'undefined') {
process.stderr.write(`Unknown command: ${commandName}\n`);
yargsBuilder.showHelp();
return -1;
return UNKNOWN_COMMAND_EXIT_CODE;
}

const { default: handler } = commandDef;
@@ -49,5 +51,5 @@ main(hideBin(process.argv))
process.exit(code);
})
.catch(() => {
process.exit(-1);
process.exit(-255);
});

+ 3
- 6
packages/amanuensis/src/mixins/base-path.ts View File

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

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();

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;
};

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

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

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

Loading…
Cancel
Save