Skip to content

Flutter CLI #82

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

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
update for generator
  • Loading branch information
lohanidamodar committed Jul 17, 2023
commit aa7a27065297ed1e765f1998eec42a0c57ad45af
File renamed without changes.
11 changes: 11 additions & 0 deletions generator/template_ts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
%IMPORTS%

export type %NAME% {
$id: string;
$collectionId: string;
$databaseId: string;
$createdAt: string;
$updatedAt: string;
$permissions: string[];
%ATTRIBUTES%
}
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const { storage } = require("./lib/commands/storage");
const { teams } = require("./lib/commands/teams");
const { users } = require("./lib/commands/users");
const { flutter } = require("./lib/commands/flutter");
const { generate } = require("./lib/commands/generate");

program
.description(commandDescriptions['main'])
Expand Down Expand Up @@ -61,6 +62,7 @@ program
.addCommand(users)
.addCommand(client)
.addCommand(flutter)
.addCommand(generate)
.parse(process.argv);

process.stdout.columns = oldWidth;
2 changes: 1 addition & 1 deletion lib/commands/flutter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const inquirer = require("inquirer");
const { teamsCreate } = require("./teams");
const { projectsCreate, projectsCreatePlatform, projectsListPlatforms } = require("./projects");
const { sdkForConsole } = require("../sdks");
const { questionsFlutterConfigure, questionsFlutterSelectPlatforms, questionsFlutterChooseDatabase, questionsFlutterChooseProject } = require("../questions");
const { questionsFlutterConfigure, questionsFlutterSelectPlatforms, questionsGeneratorChooseDatabase: questionsFlutterChooseDatabase, questionsGeneratorChooseProject: questionsFlutterChooseProject } = require("../questions");
const { success, log, actionRunner, error } = require("../parser");
const { Command } = require("commander");
const { globalConfig, localConfig } = require("../config");
Expand Down
244 changes: 244 additions & 0 deletions lib/commands/generate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
const fs = require("fs");
const inquirer = require("inquirer");
const { questionsGeneratorChooseDatabase: questionsFlutterChooseDatabase, questionsGeneratorChooseProject: questionsFlutterChooseProject } = require("../questions");
const { success, actionRunner, error } = require("../parser");
const { Command } = require("commander");
const { localConfig } = require("../config");
const { databasesListCollections } = require("./databases");
const { sdkForProject } = require("../sdks");
const { toSnakeCase, toUpperCamelCase } = require("../utils");

const generate = new Command('generate');

generate
.argument('[language]', 'Language to generate models. Currently only `dart` and `ts` is supported.')
.description("Generate model classes")
.option('--modelPath <modelPath>', 'Path where the generated models are saved. By default it\'s saved to lib/models folder.')
.option('--projectId <projectId>', 'Project ID to use to generate models for database. If not provided you will be requested to select.')
.option('--databaseIds <databaseIds>', 'Comma separated database IDs to generate models for. If not provided you will be requested to choose.')
.configureHelp({
helpWidth: process.stdout.columns || 80
})
.action(actionRunner(async (language, options) => {
if (language === 'dart' || language === 'ts') {
generateModels({ ...options, language })
return;
}
generate.help();
}));

const generateModels = async (options) => {
let modelPath = options.modelPath ?? './lib/models/';
if (!modelPath.endsWith('/')) {
modelPath += '/';
}
let projectId = options.projectId ?? localConfig.getProject().projectId;
let databaseIds = options.databaseIds?.split(',');

if (!projectId) {
let answer = await inquirer.prompt(questionsFlutterChooseProject);
if (!answer.project) {
error('You must select a project.');
return;
}
projectId = answer.project.id;
localConfig.setProject(projectId, answer.project.name);
}

if (!databaseIds) {
answer = await inquirer.prompt(questionsFlutterChooseDatabase);

if (!answer.databases.length) {
error('Please select at least one database');
return;
}
databaseIds = answer.databases.map(database => database.id);
}

const sdk = await sdkForProject();
for (let index in databaseIds) {
let id = databaseIds[index];
let response = await databasesListCollections({
databaseId: id,
sdk,
parseOutput: false
});

let collections = response.collections;

for (let index in collections) {
let extension = '.dart';
const collection = collections[index];
const className = toUpperCamelCase(collection.$id);

let template = fs.readFileSync(`${__dirname}/../../generator/template_dart.dart`, 'utf8');

let data = '';
switch (options.language) {
case 'dart':
extension = '.dart';
data = generateDartClass(className, collection.attributes, template);
break;
case 'ts':
extension = '.ts';
template = fs.readFileSync(`${__dirname}/../../generator/template_ts.ts`, 'utf8');
data = generateTSClass(className, collection.attributes, template);
break;
}

const filename = toSnakeCase(collection.$id) + extension;
if (!fs.existsSync(modelPath)) {
fs.mkdirSync(modelPath, { recursive: true });
}
fs.writeFileSync(modelPath + filename, data);
success(`Generated ${className} class and saved to ${modelPath + filename}`);
}
}
}

function generateTSClass(name, attributes, template) {
let imports = '';

const getType = (attribute) => {
switch (attribute.type) {
case 'string':
case 'email':
case 'url':
case 'enum':
case 'datetime':
return attribute.array ? 'string[]' : 'string';
case 'boolean':
return attribute.array ? 'bool[]' : 'bool';
case 'integer':
case 'double':

case 'relationship':
if (imports.indexOf(toSnakeCase(attribute.relatedCollection)) === -1) {
imports += `import './${toSnakeCase(attribute.relatedCollection)}.ts';\n`;
}

if ((attribute.relationType === 'oneToMany' && attribute.side === 'parent') || (attribute.relationType === 'manyToOne' && attribute.side === 'child') || attribute.relationType === 'manyToMany') {

return `${toUpperCamelCase(attribute.relatedCollection)}[]`;
}
return toUpperCamelCase(attribute.relatedCollection);
}
}

const properties = attributes.map(attr => {
let property = `${attr.key}${(!attr.required) ? '?' : ''}: ${getType(attr)}`;
property += ';';
return property;
}).join('\n ');

const replaceMaps = {
"%NAME%": name,
"%IMPORTS%": imports,
"%ATTRIBUTES%": properties,
}

for (let key in replaceMaps) {
template = template.replaceAll(key, replaceMaps[key]);
}

return template;
}

function generateDartClass(name, attributes, template) {
let imports = '';

const getType = (attribute) => {
switch (attribute.type) {
case 'string':
case 'email':
case 'url':
case 'enum':
case 'datetime':
return attribute.array ? 'List<String>' : 'String';
case 'boolean':
return attribute.array ? 'List<bool>' : 'bool';
case 'integer':
return attribute.array ? 'List<int>' : 'int';
case 'double':
return attribute.array ? 'List<double>' : 'double';
case 'relationship':
if (imports.indexOf(toSnakeCase(attribute.relatedCollection)) === -1) {
imports += `import './${toSnakeCase(attribute.relatedCollection)}.dart';\n`;
}

if ((attribute.relationType === 'oneToMany' && attribute.side === 'parent') || (attribute.relationType === 'manyToOne' && attribute.side === 'child') || attribute.relationType === 'manyToMany') {

return `List<${toUpperCamelCase(attribute.relatedCollection)}>`;
}
return toUpperCamelCase(attribute.relatedCollection);
}
}

const getFromMap = (attr) => {
if (attr.type === 'relationship') {
if ((attr.relationType === 'oneToMany' && attr.side === 'parent') || (attr.relationType === 'manyToOne' && attr.side === 'child') || attr.relationType === 'manyToMany') {
return `${getType(attr)}.from((map['${attr.key}'] ?? []).map((p) => ${toUpperCamelCase(attr.relatedCollection)}.fromMap(p)))`;
}
return `map['${attr.key}'] != null ? ${getType(attr)}.fromMap(map['${attr.key}']) : null`;
}
if (attr.array) {
return `${getType(attr)}.from(map['${attr.key}'])`;
}
return `map['${attr.key}']`;
}
const properties = attributes.map(attr => {
let property = `final ${getType(attr)}${(!attr.required) ? '?' : ''} ${attr.key}`;
property += ';';
return property;
}).join('\n ');

const constructorParams = attributes.map(attr => {
let out = '';
if (attr.required) {
out += 'required ';
}
out += `this.${attr.key}`;
if (attr.default && attr.default !== null) {
out += ` = ${JSON.stringify(attr.default)}`;
}
return out;
}).join(',\n ');

const constructorArgs = attributes.map(attr => {
return `${attr.key}: ${getFromMap(attr)}`;
}).join(',\n ');

const mapFields = attributes.map(attr => {
let out = `'${attr.key}': `;
if (attr.type === 'relationship') {
if ((attr.relationType === 'oneToMany' && attr.side === 'parent') || (attr.relationType === 'manyToOne' && attr.side === 'child') || attr.relationType === 'manyToMany') {
return `${out}${attr.key}?.map((p) => p.toMap())`;
}
return `${out}${attr.key}?.toMap()`;
}
return `${out}${attr.key}`;
}).join(',\n ');

const replaceMaps = {
"%NAME%": name,
"%IMPORTS%": imports,
"%ATTRIBUTES%": properties,
"%CONSTRUCTOR_PARAMETERS%": constructorParams,
"%CONSTRUCTOR_ARGUMENTS%": constructorArgs,
"%MAP_FIELDS%": mapFields,
}

for (let key in replaceMaps) {
template = template.replaceAll(key, replaceMaps[key]);
}

return template;
}

function generateJSType(name, attributes, template) {

}

module.exports = {
generate,
}
8 changes: 4 additions & 4 deletions lib/questions.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ const questionsFlutterSelectPlatforms = [
}
];

const questionsFlutterChooseProject = [
const questionsGeneratorChooseProject = [
{
type: "list",
name: "project",
Expand Down Expand Up @@ -468,7 +468,7 @@ const questionsFlutterChooseProject = [
},
]

const questionsFlutterChooseDatabase = [
const questionsGeneratorChooseDatabase = [
{
type: "checkbox",
name: "databases",
Expand Down Expand Up @@ -509,6 +509,6 @@ module.exports = {
questionsGetEntrypoint,
questionsFlutterConfigure,
questionsFlutterSelectPlatforms,
questionsFlutterChooseDatabase,
questionsFlutterChooseProject,
questionsGeneratorChooseDatabase,
questionsGeneratorChooseProject,
};