Skip to content

Commit

Permalink
Read default-meta from package.json (by default!)
Browse files Browse the repository at this point in the history
but avoid evaluating it when copied with gen!
Add more command-line options
  • Loading branch information
tjme committed May 14, 2021
1 parent 2a1ae76 commit b7e3971
Show file tree
Hide file tree
Showing 16 changed files with 127 additions and 137 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ The `opinionate meta` sub-command can be used in a number of ways to help manage

You can generate an overlay file with default metadata using `opinionate meta --overlay-out models/overlayOut.json`, similarly you can generate a SQL script to create table and field comments containing metadata using `opinionate meta --comments-out models/comments.pgsql`. These assume there is a `models/schema.json` file describing the GraphQL schema (as produced by PostGraphile), otherwise you should use the `--schema` option to specify an alternative location.

You can define your own metadata structure (e.g. to extend the metadata available to the code generating templates), and this can include code to specify default values. This can simplify the templates, and has the added benefit of allowing some of those settings to be further customized (e.g. in the overlay file) before the final code is generated. Remember though, to add the metadata parameter to both commands, e.g. `opinionate meta --default-meta models/customMeta` and `opinionate gen --default-meta models/customMeta`. Note that the metadata file is NOT in standard JSON format.
You can define your own metadata structure (e.g. to extend the metadata available to the code generating templates), and this can include code to specify default values. This can simplify the templates, and has the added benefit of allowing some of those settings to be further customized (e.g. in the overlay file) before the final code is generated. Remember though, if you want to read the default metadata from an alternate location (rather than as config in your package.json file), add the metadata file parameter to both commands, e.g. `opinionate meta --default-meta models/customMeta` and `opinionate gen --default-meta models/customMeta`.

Note that you can use `opinionate meta -h` for more help, especially on defaults and options for the locations of files to read and write.
Note that you can use `opinionate meta -h` for more help.

## Writing your own Opinionate (ES6) template(s)

Expand Down
Empty file modified bin/opinionate
100644 → 100755
Empty file.
Empty file modified bin/opinionate-gen
100644 → 100755
Empty file.
Empty file modified bin/opinionate-meta
100644 → 100755
Empty file.
10 changes: 8 additions & 2 deletions dist/bin/opinionate-gen.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ class AppGen {
.option("-w, --target <dir>", "folder in which to write the generated code file(s)", "./")
.option("-s, --schema <file>", "JSON file to read (base) schema from", "models/schema.json")
.option("-o, --overlay <file>", "JSON file to read, defining additions to the schema.data.__schema.types (especially metadata)")
.option("-d, --default-meta <file>", "ES6 template file defining the default metadata (used for each type, in the absence of any other sources)")
.option("-d, --default-meta <file>", "ES6 template file defining the metadata structure and default values (used for each type, in the absence of any other sources)")
.option("-k, --default-meta-key <key>", "key of the metadata structure node in the above file")
.option("-e, --eval-exclude-files", " a regex to match any filenames to be excluded from eval (e.g. to exclude defaultMeta)")
.option("-b, --debug", "output extra debugging")
.parse(process.argv);
generate_1.generate(this.program.template, this.program.target, this.program.schema, this.program.overlay, this.program.defaultMeta);
const options = this.program.opts();
if (options.debug)
console.log(options);
generate_1.generate(this.program.template, this.program.target, this.program.schema, this.program.overlay, this.program.defaultMeta, this.program.defaultMetaKey);
process.exit();
}
}
Expand Down
12 changes: 9 additions & 3 deletions dist/bin/opinionate-meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,26 @@ class AppMetaMerge {
.option("-s, --schema <file>", "JSON file to read (base) schema from", "models/schema.json")
.option("-o, --overlay <file>", "JSON file to read, defining additions to the schema.data.__schema.types (especially metadata)")
.option("-d, --default-meta <file>", "ES6 template file defining the metadata structure and default values (used for each type, in the absence of any other sources)")
.option("-k, --default-meta-key <key>", "key of the metadata structure node in the above file")
.option("-m, --schema-out <file>", "JSON file to write, defining schema with merged in metadata")
.option("-v, --overlay-out <file>", "JSON file to write, defining additions to the schema (especially metadata, but including type IDs)")
.option("-t, --comments-out <file>", "PostgreSQL script file to write, defining (enhanced) table and field comments")
.option("-a, --allow-existing", "Allow the base schema to include metadata")
.option("-c, --clean-descriptions", "Remove metadata from the descriptions")
.option("-i, --ignore-comments", "Do not extract metadata from any comments in the base schema")
.option("-r, --relaxed-structure", "don't limit to just the structure specified in the default metadata")
.option("-q, --no-dequote", "don't try to remove quotes from values that shouldn't normally be quoted (e.g. null)")
.option("-n, --no-remove-null", "don't remove null metadata entries")
.option("-e, --dont-eval", "don't try to evaluate the metadata structure as an ES6 template in the above")
.option("-q, --dont-dequote", "don't try to remove quotes from values that shouldn't normally be quoted (e.g. null)")
.option("-n, --dont-remove-null", "don't remove null metadata entries")
.option("-z, --return-overlay", "return only the merged overlay, rather than the full merged schema")
.option("-b, --debug", "output extra debugging")
.parse(process.argv);
const options = this.program.opts();
if (options.debug)
console.log(options);
if (typeof this.program.schema !== 'string')
throw new Error(`No schema file supplied`);
generate_1.metaMerge(this.program.schema, this.program.overlay, this.program.defaultMeta, this.program.schemaOut, this.program.overlayOut, this.program.commentsOut, this.program.allowExisting, this.program.cleanDescriptions, this.program.ignoreComments, this.program.relaxedStructure, this.program.noDequote, this.program.noRemoveNull, this.program.returnOverlay);
generate_1.metaMerge(this.program.schema, this.program.overlay, this.program.defaultMeta, this.program.defaultMetaKey, this.program.schemaOut, this.program.overlayOut, this.program.commentsOut, this.program.allowExisting, this.program.cleanDescriptions, this.program.ignoreComments, this.program.relaxedStructure, this.program.dontEval, this.program.dontDequote, this.program.dontRemoveNull, this.program.returnOverlay);
process.exit();
}
}
Expand Down
4 changes: 4 additions & 0 deletions dist/bin/opinionate.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ class App {
.version(this.package.version)
.command('meta [options]', 'merge and/or convert schema metadata, from and to various sources and file formats')
.command('gen [options]', 'generate front-end components from schema intraspection and/or metadata', { isDefault: true })
.option("-b, --debug", "output extra debugging")
.parse(process.argv);
const options = this.program.opts();
if (options.debug)
console.log(options);
}
}
exports.App = App;
Expand Down
14 changes: 2 additions & 12 deletions dist/generate.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,2 @@
export declare function isObject(item: any): boolean;
export declare function get(obj: any, key: string): any;
export declare function merge(target: any, relaxed?: boolean, ...sources: any[]): any;
export declare function stringify(ob: any): string;
export declare function convert(ob: string): string;
export declare function toProperCase(txt: string): string;
export declare function isEntity(entity: any): boolean;
export declare function isField(field: any): boolean;
export declare function getType(field: any): string;
export declare function isType(field: any, type: string): boolean;
export declare function metaMerge(schemaInPath: string, overlayInPath?: string, defaultMeta?: string, schemaOutPath?: string, overlayOutPath?: string, commentsOutPath?: string, allowExisting?: boolean, cleanDescriptions?: boolean, ignoreComments?: boolean, relaxedStructure?: boolean, noDequote?: boolean, noRemoveNull?: boolean, returnOverlay?: boolean): any;
export declare function generate(templateDir: string, targetDir: string, schemaInPath: string, overlayInPath?: string, defaultMeta?: string): void;
export declare function metaMerge(schemaInPath: string, overlayInPath?: string, defaultMeta?: string, defaultMetaKey?: string, schemaOutPath?: string, overlayOutPath?: string, commentsOutPath?: string, allowExisting?: boolean, cleanDescriptions?: boolean, ignoreComments?: boolean, relaxedStructure?: boolean, dontEval?: boolean, dontDequote?: boolean, dontRemoveNull?: boolean, returnOverlay?: boolean): any;
export declare function generate(templateDir: string, targetDir: string, schemaInPath: string, overlayInPath?: string, defaultMeta?: string, defaultMetaKey?: string, evalExcludeFiles?: RegExp): void;
52 changes: 16 additions & 36 deletions dist/generate.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generate = exports.metaMerge = exports.isType = exports.getType = exports.isField = exports.isEntity = exports.toProperCase = exports.convert = exports.stringify = exports.merge = exports.get = exports.isObject = void 0;
exports.generate = exports.metaMerge = void 0;
const fs = require("fs");
const pluralize_1 = require("pluralize");
const metaProp = "meta", metaMarker = "@meta", separator = "\n", entityFilename = "_ENTITIES_";
function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
exports.isObject = isObject;
function get(obj, key) {
return key.split(".").reduce(function (o, x) {
return (typeof o == "undefined" || o === null) ? o : o[x];
}, obj);
}
exports.get = get;
function merge(target, relaxed = false, ...sources) {
if (!sources.length)
return target;
Expand All @@ -33,7 +31,6 @@ function merge(target, relaxed = false, ...sources) {
}
return merge(target, relaxed, ...sources);
}
exports.merge = merge;
function stringify(ob) {
if (typeof ob !== "object" || Array.isArray(ob)) {
return JSON.stringify(ob);
Expand All @@ -43,7 +40,6 @@ function stringify(ob) {
.map(key => `${key}:${stringify(ob[key])}`);
return `${props}`;
}
exports.stringify = stringify;
;
function convert(ob) {
return (ob.trim().startsWith("{") ? "" : "{") + ob
Expand All @@ -53,57 +49,41 @@ function convert(ob) {
.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?\s*:/g, '"$2": ')
.replace(/@colon@/g, ':') + (ob.trim().endsWith("}") ? "" : "}");
}
exports.convert = convert;
function toProperCase(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}
exports.toProperCase = toProperCase;
function isEntity(entity) {
return entity.kind == "OBJECT" && entity.interfaces.length > 0
&& entity.name !== "Query" && entity.name !== "Mutation" && entity.name !== "PageInfo" && !entity.name.startsWith("__")
&& !entity.name.endsWith("Connection") && !entity.name.endsWith("Edge") && !entity.name.endsWith("Payload");
}
exports.isEntity = isEntity;
function isField(field) {
return field.type && (field.type.kind == "SCALAR" || (field.type["ofType"] && field.type.ofType["kind"] == "SCALAR"));
}
exports.isField = isField;
function getType(field) {
return isField(field) && field.type && (field.type.name || (field.type.ofType && field.type.ofType.name));
}
exports.getType = getType;
function isType(field, type) { return (getType(field) === type); }
exports.isType = isType;
function metaMerge(schemaInPath, overlayInPath, defaultMeta, schemaOutPath, overlayOutPath, commentsOutPath, allowExisting = false, cleanDescriptions = false, ignoreComments = false, relaxedStructure = false, noDequote = false, noRemoveNull = false, returnOverlay = false) {
function metaMerge(schemaInPath, overlayInPath, defaultMeta = "./package.json", defaultMetaKey = "config.defaultMeta", schemaOutPath, overlayOutPath, commentsOutPath, allowExisting = false, cleanDescriptions = false, ignoreComments = false, relaxedStructure = false, dontEval = false, dontDequote = false, dontRemoveNull = false, returnOverlay = false) {
function plural(word) { return pluralize_1.plural(word); }
function singular(word) { return pluralize_1.singular(word); }
const es6MetaIn = defaultMeta && fs.readFileSync(defaultMeta).toString();
function mergeMeta(item, overlay, noDequote = false, parent) {
let es6Meta = "`" + (es6MetaIn ||
"label: \"${toProperCase(item.name)}\"," +
"menu: \"${!isEntity(item) ? 'null' : 'Entities'}\"," +
"primaryKey: \"${isEntity(item) ? item.fields[1].name : 'null'}\"," +
"format: \"${isEntity(item) ? 'null' : ['money','money!'].includes(getType(item)) ? 'currency' : ['Boolean','Boolean!'].includes(getType(item)) ? 'boolean' : ['Datetime','Datetime!'].includes(getType(item)) ? 'date' : !isField(item) || ['Int','Int!','BigInt','BigInt!','Float','Float!','BigFloat','BigFloat!'].includes(getType(item)) ? 'number' : 'string'}\"," +
"validation: \"${isEntity(item) ? 'null' : (item.type && item.type.kind=='NON_NULL' ? 'required|' : '')+(getType(item) && ['Datetime','Datetime!'].includes(getType(item)) ? 'datetime' : '')}\"," +
"align: \"${isEntity(item) ? 'null' : !isField(item) || ['money','money!','Datetime','Datetime!','Int','Int!','BigInt','BigInt!','Float','FLoat!','BigFloat','BigFLoat!'].includes(getType(item)) ? 'right' : ['Boolean','Boolean!'].includes(getType(item)) ? 'center' : 'left'}\"," +
"attributes: null," +
"readonly: \"${(isEntity(item) && item.fields[0].name!=='nodeId') || (!isEntity(item) && !isField(item)) || ['nodeId'].includes(item.name)}\"," +
"linkEntity: \"${isEntity(item) ? 'null' : getType(item)==false ? singular(item.name.toLowerCase().split('by')[0]) : !isField(item) || !parent.fields.find(link => getType(link)==null && link.name.toLowerCase().endsWith('by'+item.name)) ? 'null' : parent.fields.find(link => getType(link)==null && link.name.toLowerCase().endsWith('by'+item.name)).name.toLowerCase().split('by')[0]}\"," +
"linkFields: \"${isEntity(item) ? 'null' : getType(item)==false ? item.name.toLowerCase().split('by')[1] : 'null'}\"," +
"linkFieldsFrom: \"${isEntity(item) ? 'null' : getType(item)==false ? parent.fields[1].name : !isField(item) || !parent.fields.find(link => getType(link)==null && link.name.toLowerCase().endsWith('by'+item.name)) ? 'null' : parent.fields.find(link => getType(link)==null && link.name.toLowerCase().endsWith('by'+item.name)).name.toLowerCase().split('by')[1]}\"," +
"templates: [\"switchboard\",\"list\", \"crud\"]") + "`";
let tempMeta = JSON.parse(convert(eval(es6Meta)));
if (!noDequote) {
const es6MetaIn = JSON.stringify(get(JSON.parse(fs.readFileSync(defaultMeta).toString()), defaultMetaKey));
function mergeMeta(item, overlay, parent) {
let es6Meta = dontEval ? es6MetaIn : eval("`" + es6MetaIn + "`");
if (relaxedStructure)
es6Meta = convert(es6Meta);
let tempMeta = JSON.parse(es6Meta);
if (!dontDequote) {
tempMeta = Object.fromEntries(Object.entries(tempMeta).map(([key, val]) => [key,
val === "null" ? null :
val === "true" ? true :
val === "false" ? false :
val === "undefined" ? undefined :
val
]));
if (!noRemoveNull)
tempMeta = Object.fromEntries(Object.entries(tempMeta).filter(([key, val]) => val !== null));
}
if (!dontRemoveNull)
tempMeta = Object.fromEntries(Object.entries(tempMeta).filter(([key, val]) => val !== null));
item[metaProp] = tempMeta;
if (item.description) {
const [description, meta] = item.description.split(metaMarker);
Expand Down Expand Up @@ -140,12 +120,12 @@ function metaMerge(schemaInPath, overlayInPath, defaultMeta, schemaOutPath, over
.forEach((t) => {
if (!allowExisting && t.hasOwnProperty(metaProp))
throw new Error(`The schema already contains metadata (for table ${t.name})`);
mergeMeta(t, overlayIn, noDequote);
mergeMeta(t, overlayIn);
t.fields
.forEach((f) => {
if (!allowExisting && f.hasOwnProperty(metaProp))
throw new Error(`The schema already contains metadata (for field ${f.name})`);
mergeMeta(f, overlayIn && overlayIn.find((oi) => oi.name == t.name).fields, noDequote, t);
mergeMeta(f, overlayIn && overlayIn.find((oi) => oi.name == t.name).fields, t);
});
});
if (schemaOutPath)
Expand All @@ -166,10 +146,10 @@ function metaMerge(schemaInPath, overlayInPath, defaultMeta, schemaOutPath, over
return schema;
}
exports.metaMerge = metaMerge;
function generate(templateDir, targetDir, schemaInPath, overlayInPath, defaultMeta) {
function generate(templateDir, targetDir, schemaInPath, overlayInPath, defaultMeta = "./package.json", defaultMetaKey = "config.defaultMeta", evalExcludeFiles = /package.*\.json/) {
function plural(word) { return pluralize_1.plural(word); }
function singular(word) { return pluralize_1.singular(word); }
const schema = metaMerge(schemaInPath, overlayInPath, defaultMeta);
const schema = metaMerge(schemaInPath, overlayInPath, defaultMeta, defaultMetaKey);
const entities = schema.data.__schema.types.filter((f) => isEntity(f));
function genCore(templateDir, targetDir) {
fs.readdirSync(templateDir).forEach((targetName) => {
Expand All @@ -191,7 +171,7 @@ function generate(templateDir, targetDir, schemaInPath, overlayInPath, defaultMe
});
}
else
fs.writeFileSync(targetDir + "/" + targetName, eval(templateContent));
fs.writeFileSync(targetDir + "/" + targetName, evalExcludeFiles.test(targetName) ? templateContent : eval(templateContent));
}
});
}
Expand Down
Loading

0 comments on commit b7e3971

Please sign in to comment.