Skip to content

feat: new argument to use a custom external generator #311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"build": "babel src -d lib --ignore '*.test.js'",
"watch": "babel --watch src -d lib --ignore '*.test.js'",
"test-gen": "rm -rf ./tmp && yarn build && ./lib/index.js https://demo.api-platform.com ./tmp/react -g react && ./lib/index.js https://demo.api-platform.com ./tmp/react-native -g react-native && ./lib/index.js https://demo.api-platform.com ./tmp/vue -g vue",
"test-gen-custom": "rm -rf ./tmp && yarn build && babel src/generators/ReactGenerator.js src/generators/BaseGenerator.js -d ./tmp/gens && cp -r ./templates/react ./templates/react-common ./templates/entrypoint.js ./tmp/gens && ./lib/index.js https://demo.api-platform.com ./tmp/react-custom -g \"$(pwd)/tmp/gens/ReactGenerator.js\" -t ./tmp/gens",
"test-gen-swagger": "rm -rf ./tmp && yarn build && ./lib/index.js https://demo.api-platform.com/docs.json ./tmp/react -f swagger && ./lib/index.js https://demo.api-platform.com/docs.json ./tmp/react-native -g react-native -f swagger && ./lib/index.js https://demo.api-platform.com/docs.json ./tmp/vue -g vue -f swagger",
"test-gen-env": "rm -rf ./tmp && yarn build && API_PLATFORM_CLIENT_GENERATOR_ENTRYPOINT=https://demo.api-platform.com API_PLATFORM_CLIENT_GENERATOR_OUTPUT=./tmp ./lib/index.js"
},
Expand Down
8 changes: 7 additions & 1 deletion src/generators.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from "fs";
import NextGenerator from "./generators/NextGenerator";
import NuxtGenerator from "./generators/NuxtGenerator";
import ReactGenerator from "./generators/ReactGenerator";
Expand All @@ -12,7 +13,12 @@ function wrap(cl) {
new cl({ hydraPrefix, templateDirectory });
}

export default function generators(generator = "react") {
export default async function generators(generator = "react") {
if (fs.existsSync(generator)) {
const gen = await import(generator);
return wrap(gen.default);
}

switch (generator) {
case "next":
return wrap(NextGenerator);
Expand Down
234 changes: 120 additions & 114 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,129 +8,135 @@ import parseOpenApi3Documentation from "@api-platform/api-doc-parser/lib/openapi
import { version } from "../package.json";
import generators from "./generators";

program
.version(version)
.description(
"Generate apps built with Next, Nuxt, Quasar, React, React Native, Vue or Vuetify for any API documented using Hydra or OpenAPI"
)
.usage("entrypoint outputDirectory")
.option(
"-r, --resource [resourceName]",
"Generate CRUD for the given resource"
)
.option(
"-p, --hydra-prefix [hydraPrefix]",
"The hydra prefix used by the API",
"hydra:"
)
.option("--username [username]", "Username for basic auth (Hydra only)")
.option("--password [password]", "Password for basic auth (Hydra only)")
.option("--bearer [bearer]", "Token for bearer auth (Hydra only)")
.option(
"-g, --generator [generator]",
'The generator to use, one of "next", "nuxt", "quasar", "react", "react-native", "typescript", "vue", "vuetify"',
"next"
)
.option(
"-t, --template-directory [templateDirectory]",
"The templates directory base to use. Final directory will be ${templateDirectory}/${generator}",
`${__dirname}/../templates/`
)
.option(
"-f, --format [hydra|openapi3|openapi2]",
'"hydra", "openapi3" or "openapi2"',
"hydra"
)
.option(
"-s, --server-path [serverPath]",
"Path to express server file to allow route dynamic addition (Next.js generator only)"
)
.parse(process.argv);
async function main() {
program
.version(version)
.description(
"Generate apps built with Next, Nuxt, Quasar, React, React Native, Vue or Vuetify for any API documented using Hydra or OpenAPI"
)
.usage("entrypoint outputDirectory")
.option(
"-r, --resource [resourceName]",
"Generate CRUD for the given resource"
)
.option(
"-p, --hydra-prefix [hydraPrefix]",
"The hydra prefix used by the API",
"hydra:"
)
.option("--username [username]", "Username for basic auth (Hydra only)")
.option("--password [password]", "Password for basic auth (Hydra only)")
.option("--bearer [bearer]", "Token for bearer auth (Hydra only)")
.option(
"-g, --generator [generator]",
'The generator to use, one of "next", "nuxt", "quasar", "react", "react-native", "typescript", "vue", "vuetify" or a path to a custom generator of your choice',
"next"
)
.option(
"-t, --template-directory [templateDirectory]",
"The templates directory base to use. Final directory will be ${templateDirectory}/${generator}",
`${__dirname}/../templates/`
)
.option(
"-f, --format [hydra|openapi3|openapi2]",
'"hydra", "openapi3" or "openapi2"',
"hydra"
)
.option(
"-s, --server-path [serverPath]",
"Path to express server file to allow route dynamic addition (Next.js generator only)"
)
.parse(process.argv);

if (
2 !== program.args.length &&
(!process.env.API_PLATFORM_CLIENT_GENERATOR_ENTRYPOINT ||
!process.env.API_PLATFORM_CLIENT_GENERATOR_OUTPUT)
) {
program.help();
}
if (
2 !== program.args.length &&
(!process.env.API_PLATFORM_CLIENT_GENERATOR_ENTRYPOINT ||
!process.env.API_PLATFORM_CLIENT_GENERATOR_OUTPUT)
) {
program.help();
}

const options = program.opts();
const options = program.opts();

const entrypoint =
program.args[0] || process.env.API_PLATFORM_CLIENT_GENERATOR_ENTRYPOINT;
const outputDirectory =
program.args[1] || process.env.API_PLATFORM_CLIENT_GENERATOR_OUTPUT;
const entrypoint =
program.args[0] || process.env.API_PLATFORM_CLIENT_GENERATOR_ENTRYPOINT;
const outputDirectory =
program.args[1] || process.env.API_PLATFORM_CLIENT_GENERATOR_OUTPUT;

const entrypointWithSlash = entrypoint.endsWith("/")
? entrypoint
: entrypoint + "/";
const entrypointWithSlash = entrypoint.endsWith("/")
? entrypoint
: entrypoint + "/";

const generator = generators(options.generator)({
hydraPrefix: options.hydraPrefix,
templateDirectory: options.templateDirectory,
});
const resourceToGenerate = options.resource
? options.resource.toLowerCase()
: null;
const serverPath = options.serverPath ? options.serverPath.toLowerCase() : null;
const generator = (await generators(options.generator))({
hydraPrefix: options.hydraPrefix,
templateDirectory: options.templateDirectory,
});
const resourceToGenerate = options.resource
? options.resource.toLowerCase()
: null;
const serverPath = options.serverPath
? options.serverPath.toLowerCase()
: null;

const parser = (entrypointWithSlash) => {
const options = {};
if (options.username && options.password) {
const encoded = Buffer.from(
`${options.username}:${options.password}`
).toString("base64");
options.headers = new Headers();
options.headers.set("Authorization", `Basic ${encoded}`);
}
if (options.bearer) {
options.headers = new Headers();
options.headers.set("Authorization", `Bearer ${options.bearer}`);
}
switch (options.format) {
case "swagger": // deprecated
case "openapi2":
return parseSwaggerDocumentation(entrypointWithSlash);
case "openapi3":
return parseOpenApi3Documentation(entrypointWithSlash);
default:
return parseHydraDocumentation(entrypointWithSlash, options);
}
};
const parser = (entrypointWithSlash) => {
const options = {};
if (options.username && options.password) {
const encoded = Buffer.from(
`${options.username}:${options.password}`
).toString("base64");
options.headers = new Headers();
options.headers.set("Authorization", `Basic ${encoded}`);
}
if (options.bearer) {
options.headers = new Headers();
options.headers.set("Authorization", `Bearer ${options.bearer}`);
}
switch (options.format) {
case "swagger": // deprecated
case "openapi2":
return parseSwaggerDocumentation(entrypointWithSlash);
case "openapi3":
return parseOpenApi3Documentation(entrypointWithSlash);
default:
return parseHydraDocumentation(entrypointWithSlash, options);
}
};

// check generator dependencies
generator.checkDependencies(outputDirectory, serverPath);
// check generator dependencies
generator.checkDependencies(outputDirectory, serverPath);

parser(entrypointWithSlash)
.then((ret) => {
ret.api.resources
.filter(({ deprecated }) => !deprecated)
.filter((resource) => {
const nameLc = resource.name.toLowerCase();
const titleLc = resource.title.toLowerCase();
parser(entrypointWithSlash)
.then((ret) => {
ret.api.resources
.filter(({ deprecated }) => !deprecated)
.filter((resource) => {
const nameLc = resource.name.toLowerCase();
const titleLc = resource.title.toLowerCase();

return (
null === resourceToGenerate ||
nameLc === resourceToGenerate ||
titleLc === resourceToGenerate
);
})
.map((resource) => {
const filterDeprecated = (list) =>
list.filter(({ deprecated }) => !deprecated);
return (
null === resourceToGenerate ||
nameLc === resourceToGenerate ||
titleLc === resourceToGenerate
);
})
.map((resource) => {
const filterDeprecated = (list) =>
list.filter(({ deprecated }) => !deprecated);

resource.fields = filterDeprecated(resource.fields);
resource.readableFields = filterDeprecated(resource.readableFields);
resource.writableFields = filterDeprecated(resource.writableFields);
resource.fields = filterDeprecated(resource.fields);
resource.readableFields = filterDeprecated(resource.readableFields);
resource.writableFields = filterDeprecated(resource.writableFields);

generator.generate(ret.api, resource, outputDirectory, serverPath);
generator.generate(ret.api, resource, outputDirectory, serverPath);

return resource;
})
// display helps after all resources have been generated to check relation dependency for example
.forEach((resource) => generator.help(resource, outputDirectory));
})
.catch((e) => {
console.log(e);
});
return resource;
})
// display helps after all resources have been generated to check relation dependency for example
.forEach((resource) => generator.help(resource, outputDirectory));
})
.catch((e) => {
console.log(e);
});
}

main();