Skip to content

Commit

Permalink
feat: Generation Output mode (#68)
Browse files Browse the repository at this point in the history
* Models only output mode

* Update readme

* Don't re-create output directory in models only mode
  • Loading branch information
bcanfield authored Aug 17, 2023
1 parent fc237e9 commit e848086
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 106 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ npx prisma generate
## Options
| Option | Description | Default | Required |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -------- |
| schema | Path to prisma schema file | schema.prisma | false |
| output | Path to output directory | nexquikApp | false |
| include | Comma-separated list of model names to include from the top-level of the generated app | | false |
| exclude | Comma-separated list of model names to exclude from the top-level of the generated app (NOTE: If the 'include' option is used, this exclusion list will be ignored) | | false |
| depth | Maximum recursion depth for your models. (Changing this for large data models is not recommended, unless you filter down your models with the 'include' or 'exclude' flags also.) | 5 | false |
| schema \<value\> | Path to prisma schema file | schema.prisma | false |
| output \<value\> | Path to output directory | nexquikApp | false |
| include \<value\> | Comma-separated list of model names to include from the top-level of the generated app | | false |
| exclude \<value\> | Comma-separated list of model names to exclude from the top-level of the generated app (NOTE: If the 'include' option is used, this exclusion list will be ignored) | | false |
| depth \<value\> | Maximum recursion depth for your models. (Changing this for large data models is not recommended, unless you filter down your models with the 'include' or 'exclude' flags also.) | 5 | false |
| modelsOnly | Output only the model directories in your desired output location, excluding the main directory files. | | false |

### Disabled
To disable Nexquik from generating during a Prisma generate, add the following environmental variable.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const nexquikMain = "./dist/index.js";
const testOutputDirectory = path.join(
"__tests__",
"core",
"testOutputDirectory"
"defaultTestOutputDirectory"
);
const prismaSchemaDirectory = "prisma";
const prismaMain = "./node_modules/prisma/build/index.js";
Expand Down
29 changes: 29 additions & 0 deletions __tests__/core/schemas-models-only.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as child_process from "child_process";
import { readdirSync, statSync } from "fs";
import path from "path";
import { isDirectoryNotEmpty } from "../utils";

const nexquikMain = "./dist/index.js";
const testOutputDirectory = path.join(
"__tests__",
"core",
"modelsOnlyTestOutputDirectory"
);
const prismaSchemaDirectory = "prisma";
const prismaMain = "./node_modules/prisma/build/index.js";

test.each(readdirSync(prismaSchemaDirectory))(
`Schema Test: %p`,
(schemaPath: string) => {
child_process.execSync(`rm -rf ${testOutputDirectory}`);
let res = child_process.execSync(
`node ${nexquikMain} -schema ${path.join(
prismaSchemaDirectory,
schemaPath
)} -output ${testOutputDirectory} -modelsOnly`
);
console.log(`Schema Test: ${schemaPath}`);
expect(isDirectoryNotEmpty(testOutputDirectory)).toBeTruthy();
child_process.execSync(`rm -rf ${testOutputDirectory}`);
}
);
12 changes: 8 additions & 4 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import chalk from "chalk";
import { Command } from "commander";
import figlet from "figlet";
import { generate } from "./generators";
import { formatDirectory } from "./helpers";
import ora from "ora"; // Import 'ora'

export interface CliArgs {
prismaSchemaPath: string;
Expand Down Expand Up @@ -57,6 +55,10 @@ export async function run(options?: GeneratorOptions) {
"Maximum recursion depth for your models. (Changing this for large data models is not recommended, unless you filter down your models with the 'include' or 'exclude' flags also.)",
"5"
)
.option(
"-modelsOnly",
"Output only the model directories in your desired output location, excluding the main directory files."
)
.parse(process.argv);

const cliArgs = program.opts();
Expand All @@ -68,7 +70,8 @@ export async function run(options?: GeneratorOptions) {
? String(options.generator.config.include).split(",")
: [];
const maxDepth = parseInt(options?.generator.config.depth || cliArgs.Depth);

const modelsOnly =
options?.generator.config.modelsOnly || cliArgs.ModelsOnly || false;
const excludedModels =
includedModels.length > 0
? []
Expand All @@ -88,7 +91,8 @@ export async function run(options?: GeneratorOptions) {
outputDirectory,
excludedModels,
includedModels,
maxDepth
maxDepth,
modelsOnly
);

// await formatDirectory(outputDirectory);
Expand Down
209 changes: 113 additions & 96 deletions src/generators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,20 +353,21 @@ export async function generate(
outputDirectory: string,
excludedModels: string[],
includedModels: string[],
maxAllowedDepth: number
maxAllowedDepth: number,
modelsOnly: boolean
) {
// Read the Prisma schema file
const prismaSchema = await readFileAsync(prismaSchemaPath, "utf-8");

// Create the output directory
if (fs.existsSync(outputDirectory)) {
fs.rmSync(outputDirectory, { recursive: true });
if (modelsOnly === false) {
fs.rmSync(outputDirectory, { recursive: true });
fs.mkdirSync(outputDirectory);
}
} else {
fs.mkdirSync(outputDirectory);
}
fs.mkdirSync(outputDirectory);

// Create the app directory
const appDirectory = path.join(outputDirectory, "app");
fs.mkdirSync(appDirectory);

// Main section to build the app from the modelTree
const dmmf = await getDMMF({ datamodel: prismaSchema });
Expand All @@ -382,94 +383,100 @@ export async function generate(
throw new Error("No valid models detected in schema");
}

// Copy all files from the root dir except for app. (package.json, next.config, etc)
copyDirectory(
path.join(__dirname, "templateRoot"),
outputDirectory,
true,
"app"
let directoryToOutputFiles = outputDirectory;
const enums = getEnums(dmmf.datamodel);
console.log(
`${chalk.blue.bold(
"Generating directories for your models..."
)} ${chalk.gray("(For deeply-nested schemas, this may take a moment)")}`
);
// Create files in main directory
if (modelsOnly === false) {
// Create the app directory
directoryToOutputFiles = path.join(outputDirectory, "app");
fs.mkdirSync(directoryToOutputFiles);

// Copy all files from the root dir except for app. (package.json, next.config, etc)
copyDirectory(
path.join(__dirname, "templateRoot"),
outputDirectory,
true,
"app"
);

// Try copying over public folder
copyPublicDirectory(
path.join(__dirname, "templateRoot", "public"),
path.join(outputDirectory, "public"),
true,
"app"
);
// Try copying over public folder
copyPublicDirectory(
path.join(__dirname, "templateRoot", "public"),
path.join(outputDirectory, "public"),
true,
"app"
);

// copy over images
copyImage(
path.join(__dirname, "templateRoot", "app"),
"favicon.ico",
path.join(outputDirectory, "app")
);
copyImage(
path.join(__dirname, "templateRoot", "app"),
"icon.png",
path.join(outputDirectory, "app")
);
// copy over images
copyImage(
path.join(__dirname, "templateRoot", "app"),
"favicon.ico",
path.join(outputDirectory, "app")
);
copyImage(
path.join(__dirname, "templateRoot", "app"),
"icon.png",
path.join(outputDirectory, "app")
);

fs.mkdirSync(path.join(outputDirectory, "prisma"));
fs.mkdirSync(path.join(outputDirectory, "prisma"));

// Copy over the user's prisma schema and rename it to schema.prisma
copyAndRenameFile(
prismaSchemaPath,
path.join(outputDirectory, "prisma"),
"schema.prisma"
);
// Copy over the user's prisma schema and rename it to schema.prisma
copyAndRenameFile(
prismaSchemaPath,
path.join(outputDirectory, "prisma"),
"schema.prisma"
);

// Copy over tsconfig
fs.copyFile(
path.join(__dirname, "templateRoot", "tsconfig.json"),
path.join(outputDirectory, "tsconfig.json"),
(err) => {
if (err) {
console.error("An error occurred while copying the file:", err);
} else {
console.log(`File copied to ${outputDirectory}`);
// Copy over tsconfig
fs.copyFile(
path.join(__dirname, "templateRoot", "tsconfig.json"),
path.join(outputDirectory, "tsconfig.json"),
(err) => {
if (err) {
console.error("An error occurred while copying the file:", err);
} else {
console.log(`File copied to ${outputDirectory}`);
}
}
}
);

const enums = getEnums(dmmf.datamodel);

console.log(
`${chalk.blue.bold(
"Generating directories for your models..."
)} ${chalk.gray("(For deeply-nested schemas, this may take a moment)")}`
);
);

await generateAppDirectoryFromModelTree(
modelTree,
appDirectory,
enums,
maxAllowedDepth
);
await generateAppDirectoryFromModelTree(
modelTree,
directoryToOutputFiles,
enums,
maxAllowedDepth,
modelsOnly
);

// Home route list
const modelNames = modelTree.map((m) => m.model.name);
// Home route list
const modelNames = modelTree.map((m) => m.model.name);

const routeList = generateRouteList(modelTree.map((m) => m.model.name));
const routeList = generateRouteList(modelTree.map((m) => m.model.name));

// dynamic/edit/page.tsx
modifyFile(
path.join(appDirectory, "page.tsx"),
path.join(path.join(outputDirectory, "app", "page.tsx")),
[
{
startComment: "{/* @nexquik routeList start */}",
endComment: "{/* @nexquik routeList stop */}",
insertString: routeList,
},
]
);
// page.tsx
modifyFile(
path.join(directoryToOutputFiles, "page.tsx"),
path.join(path.join(outputDirectory, "app", "page.tsx")),
[
{
startComment: "{/* @nexquik routeList start */}",
endComment: "{/* @nexquik routeList stop */}",
insertString: routeList,
},
]
);

// Route sidebar
let routeSidebar = "";
for (const model of modelNames) {
const lowerCase = model.charAt(0).toLowerCase() + model.slice(1);
routeSidebar += `<li className="mt-4">
// Route sidebar
let routeSidebar = "";
for (const model of modelNames) {
const lowerCase = model.charAt(0).toLowerCase() + model.slice(1);
routeSidebar += `<li className="mt-4">
<a
href="/${lowerCase}"
Expand All @@ -484,19 +491,28 @@ export async function generate(
</li>
`;
}

// layout.tsx
await modifyFile(
path.join(path.join(__dirname, "templateRoot", "app", "layout.tsx")),
path.join(path.join(outputDirectory, "app", "layout.tsx")),
[
{
startComment: "{/* //@nexquik routeSidebar start */}",
endComment: "{/* //@nexquik routeSidebar stop */}",
insertString: routeSidebar,
},
]
);
}

// layout.tsx
await modifyFile(
path.join(path.join(__dirname, "templateRoot", "app", "layout.tsx")),
path.join(path.join(outputDirectory, "app", "layout.tsx")),
[
{
startComment: "{/* //@nexquik routeSidebar start */}",
endComment: "{/* //@nexquik routeSidebar stop */}",
insertString: routeSidebar,
},
]
await generateAppDirectoryFromModelTree(
modelTree,
directoryToOutputFiles,
enums,
maxAllowedDepth,
modelsOnly
);

return;
Expand Down Expand Up @@ -676,7 +692,8 @@ export async function generateAppDirectoryFromModelTree(
modelTreeArray: ModelTree[],
outputDirectory: string,
enums: Record<string, string[]>,
maxAllowedDepth: number
maxAllowedDepth: number,
modelsOnly: boolean
): Promise<RouteObject[]> {
const routes: RouteObject[] = [];
let fileCount = 0;
Expand Down Expand Up @@ -714,7 +731,7 @@ export async function generateAppDirectoryFromModelTree(

let route = parentRoute.name;

if (route === "/") {
if (modelsOnly === false && route === "/") {
// Copy over the files in the template app dir, skipping the model directory. (globals.css, layout.tsx, page.tsx)
copyDirectory(
path.join(__dirname, "templateRoot", "app"),
Expand Down

0 comments on commit e848086

Please sign in to comment.