Skip to content

Commit aa0998a

Browse files
committed
feat: add next generator
1 parent e900635 commit aa0998a

File tree

15 files changed

+671
-17
lines changed

15 files changed

+671
-17
lines changed

src/generators.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import AdminOnRestGenerator from "./generators/AdminOnRestGenerator";
2+
import NextGenerator from "./generators/NextGenerator";
23
import ReactGenerator from "./generators/ReactGenerator";
34
import ReactNativeGenerator from "./generators/ReactNativeGenerator";
45
import TypescriptInterfaceGenerator from "./generators/TypescriptInterfaceGenerator";
@@ -13,6 +14,8 @@ export default function generators(generator = "react") {
1314
switch (generator) {
1415
case "admin-on-rest":
1516
return wrap(AdminOnRestGenerator);
17+
case "next":
18+
return wrap(NextGenerator);
1619
case "react":
1720
return wrap(ReactGenerator);
1821
case "react-native":

src/generators/BaseGenerator.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import chalk from "chalk";
12
import fs from "fs";
23
import handlebars from "handlebars";
34
import mkdirp from "mkdirp";
@@ -30,7 +31,9 @@ export default class {
3031
return;
3132
}
3233

33-
if (warn) console.log(`The directory "${dir}" already exists`);
34+
if (warn) {
35+
console.log(chalk.yellow(`The directory "${dir}" already exists`));
36+
}
3437
}
3538

3639
createFileFromPattern(pattern, dir, lc, context) {
@@ -55,6 +58,34 @@ export default class {
5558
this.createFile("entrypoint.js", dest, { entrypoint }, false);
5659
}
5760

61+
// eslint-disable-next-line no-unused-vars
62+
checkDependencies(dir) {}
63+
64+
getTargetDependencies(dir) {
65+
const packageFilePath = `${dir}/package.json`;
66+
let packageFile;
67+
let dependencies = [];
68+
try {
69+
if (!fs.existsSync(packageFilePath)) {
70+
throw new Error();
71+
}
72+
packageFile = fs.readFileSync(packageFilePath);
73+
const configuration = JSON.parse(packageFile.toString());
74+
dependencies = Object.keys({
75+
...configuration.dependencies,
76+
...configuration.devDependencies
77+
});
78+
} catch (e) {
79+
console.log(
80+
chalk.yellow(
81+
"There's no readable package file in the target directory. Generator can't check dependencies."
82+
)
83+
);
84+
}
85+
86+
return dependencies;
87+
}
88+
5889
getHtmlInputTypeFromField(field) {
5990
switch (field.id) {
6091
case "http://schema.org/email":

src/generators/NextGenerator.js

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import chalk from "chalk";
2+
import fs from "fs";
3+
import BaseGenerator from "./BaseGenerator";
4+
5+
export default class NextGenerator extends BaseGenerator {
6+
constructor(params) {
7+
super(params);
8+
9+
this.registerTemplates(`next/`, [
10+
// components
11+
"components/common/ReferenceLinks.tsx",
12+
"components/foo/List.tsx",
13+
"components/foo/ListItem.tsx",
14+
"components/foo/Show.tsx",
15+
16+
// interfaces
17+
"error/SubmissionError.ts",
18+
19+
// interfaces
20+
"interfaces/Collection.ts",
21+
"interfaces/foo.ts",
22+
23+
// pages
24+
"pages/foo.tsx",
25+
"pages/foos.tsx",
26+
27+
// utils
28+
"utils/dataAccess.ts"
29+
]);
30+
}
31+
32+
checkDependencies(dir) {
33+
const dependencies = this.getTargetDependencies(dir);
34+
35+
if (dependencies.length) {
36+
if (!dependencies.includes("@zeit/next-typescript")) {
37+
console.log(
38+
chalk.yellow(
39+
"It seems next-typescript is not installed but generator needs typescript to work efficiently."
40+
)
41+
);
42+
}
43+
44+
if (!dependencies.includes("express")) {
45+
console.log(
46+
chalk.yellow(
47+
"It seems express is not installed but generator needs a custom express server to work efficiently."
48+
)
49+
);
50+
}
51+
}
52+
}
53+
54+
checkImports(directory, imports, extension = ".ts") {
55+
imports.forEach(({ file }) => {
56+
if (!fs.existsSync(directory + file + extension)) {
57+
console.log(
58+
chalk.yellow(
59+
'An import for the file "%s" has been generated but the file doesn\'t exists.'
60+
),
61+
file
62+
);
63+
}
64+
});
65+
}
66+
67+
help(resource, dir) {
68+
console.log(
69+
chalk.green('Code for the "%s" resource type has been generated!'),
70+
resource.title
71+
);
72+
73+
// missing import
74+
const { imports } = this.parseFields(resource);
75+
this.checkImports(`${dir}/interfaces/`, imports);
76+
77+
// server route configuration
78+
const lc = resource.title.toLowerCase();
79+
console.log("Paste the following route to your server configuration file:");
80+
console.log(
81+
chalk.green(`
82+
server.get('/${lc}/:hash', (req, res) => {
83+
return app.render(req, res, '/${lc}', { hash: req.params.hash })
84+
});
85+
`)
86+
);
87+
}
88+
89+
generate(api, resource, dir) {
90+
const lc = resource.title.toLowerCase();
91+
const ucf = this.ucFirst(resource.title);
92+
const { fields, imports } = this.parseFields(resource);
93+
94+
const context = {
95+
name: resource.name,
96+
lc,
97+
uc: resource.title.toUpperCase(),
98+
ucf,
99+
fields,
100+
formFields: this.buildFields(resource.writableFields),
101+
imports,
102+
hydraPrefix: this.hydraPrefix,
103+
title: resource.title
104+
};
105+
106+
// Create directories
107+
// These directories may already exist
108+
[
109+
`${dir}/components/common`,
110+
`${dir}/config`,
111+
`${dir}/error`,
112+
`${dir}/interfaces`,
113+
`${dir}/pages`,
114+
`${dir}/utils`
115+
].forEach(dir => this.createDir(dir, false));
116+
117+
// copy with patterned name
118+
this.createDir(`${dir}/components/${context.lc}`);
119+
[
120+
// components
121+
"components/%s/List.tsx",
122+
"components/%s/ListItem.tsx",
123+
"components/%s/Show.tsx",
124+
125+
// pages
126+
"pages/%s.tsx",
127+
"pages/%ss.tsx"
128+
].forEach(pattern =>
129+
this.createFileFromPattern(pattern, dir, context.lc, context)
130+
);
131+
132+
// interface pattern should be camel cased
133+
this.createFile(
134+
"interfaces/foo.ts",
135+
`${dir}/interfaces/${context.ucf}.ts`,
136+
context
137+
);
138+
139+
// copy with regular name
140+
[
141+
// components
142+
"components/common/ReferenceLinks.tsx",
143+
144+
// error
145+
"error/SubmissionError.ts",
146+
147+
// interfaces
148+
"interfaces/Collection.ts",
149+
150+
// utils
151+
"utils/dataAccess.ts"
152+
].forEach(file => this.createFile(file, `${dir}/${file}`, context, false));
153+
154+
// API config
155+
this.createEntrypoint(api.entrypoint, `${dir}/config/entrypoint.ts`);
156+
}
157+
158+
getType(field) {
159+
if (field.reference) {
160+
return field.reference.title;
161+
}
162+
163+
switch (field.range) {
164+
case "http://www.w3.org/2001/XMLSchema#integer":
165+
case "http://www.w3.org/2001/XMLSchema#decimal":
166+
return "number";
167+
case "http://www.w3.org/2001/XMLSchema#boolean":
168+
return "boolean";
169+
case "http://www.w3.org/2001/XMLSchema#date":
170+
case "http://www.w3.org/2001/XMLSchema#dateTime":
171+
case "http://www.w3.org/2001/XMLSchema#time":
172+
return "Date";
173+
case "http://www.w3.org/2001/XMLSchema#string":
174+
return "string";
175+
}
176+
177+
return "any";
178+
}
179+
180+
getDescription(field) {
181+
return field.description ? field.description.replace(/"/g, "'") : "";
182+
}
183+
184+
parseFields(resource) {
185+
const fields = {};
186+
187+
for (let field of resource.writableFields) {
188+
fields[field.name] = {
189+
notrequired: !field.required,
190+
name: field.name,
191+
type: this.getType(field),
192+
description: this.getDescription(field),
193+
readonly: false,
194+
reference: field.reference
195+
};
196+
}
197+
198+
for (let field of resource.readableFields) {
199+
if (fields[field.name] !== undefined) {
200+
continue;
201+
}
202+
203+
fields[field.name] = {
204+
notrequired: !field.required,
205+
name: field.name,
206+
type: this.getType(field),
207+
description: this.getDescription(field),
208+
readonly: true,
209+
reference: field.reference
210+
};
211+
}
212+
213+
// Parse fields to add relevant imports, required for Typescript
214+
const fieldsArray = Object.values(fields);
215+
const imports = {};
216+
217+
for (const field of fieldsArray) {
218+
if (field.reference) {
219+
imports[field.type] = {
220+
type: field.type,
221+
file: "./" + field.type
222+
};
223+
}
224+
}
225+
226+
return { fields: fieldsArray, imports: Object.values(imports) };
227+
}
228+
229+
ucFirst(target) {
230+
return target.charAt(0).toUpperCase() + target.slice(1);
231+
}
232+
}

0 commit comments

Comments
 (0)