Skip to content
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

feat: configuration for seamless cli use in a typescript project #1439

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
5 changes: 3 additions & 2 deletions src/assets/migrations/create-table.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use strict';
<%= isTypescriptProject ? `import { QueryInterface, DataTypes } from 'sequelize';` : '' %>

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
async up (queryInterface<%= isTypescriptProject ? ': QueryInterface' : '' %>, Sequelize<%= isTypescriptProject ? ': typeof DataTypes' : '' %>) {
await queryInterface.createTable('<%= tableName %>', {
id: {
allowNull: false,
Expand All @@ -29,7 +30,7 @@ module.exports = {
});
},

async down (queryInterface, Sequelize) {
async down (queryInterface<%= isTypescriptProject ? ': QueryInterface' : '' %>, Sequelize<%= isTypescriptProject ? ': typeof DataTypes' : '' %>) {
await queryInterface.dropTable('<%= tableName %>');
}
};
5 changes: 3 additions & 2 deletions src/assets/migrations/skeleton.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use strict';
<%= isTypescriptProject ? `import { QueryInterface, DataTypes } from 'sequelize';` : '' %>

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
async up (queryInterface<%= isTypescriptProject ? ': QueryInterface' : '' %>, Sequelize<%= isTypescriptProject ? ': typeof DataTypes' : '' %>) {
/**
* Add altering commands here.
*
Expand All @@ -11,7 +12,7 @@ module.exports = {
*/
},

async down (queryInterface, Sequelize) {
async down (queryInterface<%= isTypescriptProject ? ': QueryInterface' : '' %>, Sequelize<%= isTypescriptProject ? ': typeof DataTypes' : '' %>) {
/**
* Add reverting commands here.
*
Expand Down
10 changes: 10 additions & 0 deletions src/assets/models/connection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict';
import { Sequelize } from 'sequelize';
const env = process.env.NODE_ENV || 'development';
const config = require(<%= configFile %>)[env];

const sequelizeConnection<%= isTypescriptProject ? ': Sequelize' : '' %> = config.config.use_env_variable
? new Sequelize( process.env[config.config.use_env_variable], config)
: new Sequelize(config.database, config.username, config.password, config);

module.exports = sequelizeConnection;
43 changes: 0 additions & 43 deletions src/assets/models/index.js

This file was deleted.

49 changes: 34 additions & 15 deletions src/assets/models/model.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
'use strict';

const { Model } = require('sequelize');
import { Model, DataTypes } from 'sequelize';
<% if (isTypescriptProject) { %>
import sequelize from './connection';
<% }else{ %>
const sequelize = require('./connection');
<% } %>

module.exports = (sequelize, DataTypes) => {
class <%= name %> extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate (models) {
// define association here
}
}
<% if (isTypescriptProject) { %>
export interface <%= name %>Attributes {
<% attributes.forEach(function(attribute, index) { %>
<%= attribute.fieldName %>: <%= attribute.tsType %>;
<% }) %>
}
<% } %>

<%= name %>.init({
class <%= name %> extends Model<%= isTypescriptProject ? `<${name}Attributes> implements ${name}Attributes` : '' %> {
<% if (isTypescriptProject) { %>
<% attributes.forEach(function(attribute, index) { %>
<%= attribute.fieldName %><%=isTypescriptProject ? `!: ${attribute.tsType}` : '' %>;
<% }) %>
<% } %>
}

<%= name %>.init({
<% attributes.forEach(function(attribute, index) { %>
<%= attribute.fieldName %>: DataTypes.<%= attribute.dataFunction ? `${attribute.dataFunction.toUpperCase()}(DataTypes.${attribute.dataType.toUpperCase()})` : attribute.dataValues ? `${attribute.dataType.toUpperCase()}(${attribute.dataValues})` : attribute.dataType.toUpperCase() %>
<%= (Object.keys(attributes).length - 1) > index ? ',' : '' %>
Expand All @@ -25,5 +34,15 @@ module.exports = (sequelize, DataTypes) => {
<%= underscored ? 'underscored: true,' : '' %>
});

return <%= name %>;
};
// Associations
// <%= name %>.belongsTo(TargetModel, {
// as: 'custom_name',
// foreignKey: {
// name: 'foreign_key_column_name',
// allowNull: false,
// },
// onDelete: "RESTRICT",
// foreignKeyConstraint: true,
// });

export default <%= name %>;
5 changes: 3 additions & 2 deletions src/assets/seeders/skeleton.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use strict';
<%= isTypescriptProject ? `import { QueryInterface, DataTypes } from 'sequelize';` : '' %>

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
async up (queryInterface<%= isTypescriptProject ? ': QueryInterface' : '' %>, Sequelize<%= isTypescriptProject ? ': typeof DataTypes' : '' %>) {
/**
* Add seed commands here.
*
Expand All @@ -14,7 +15,7 @@ module.exports = {
*/
},

async down (queryInterface, Sequelize) {
async down (queryInterface<%= isTypescriptProject ? ': QueryInterface' : '' %>, Sequelize<%= isTypescriptProject ? ': typeof DataTypes' : '' %>) {
/**
* Add commands to revert seed here.
*
Expand Down
2 changes: 1 addition & 1 deletion src/commands/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function initConfig(args) {

function initModels(args) {
helpers.init.createModelsFolder(!!args.force);
helpers.init.createModelsIndexFile(!!args.force);
helpers.init.createConnectionFile(!!args.force);
}

function initMigrations(args) {
Expand Down
15 changes: 15 additions & 0 deletions src/helpers/import-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ async function supportsDynamicImport() {
}
}

function isPackageInstalled(packageName) {
try {
// Try to require the package
require.resolve(packageName);
return true;
} catch (error) {
// If require.resolve throws an error, the package is not installed
return false;
}
}

const isTypescriptProject = isPackageInstalled('typescript');

/**
* Imports a JSON, CommonJS or ESM module
* based on feature detection.
Expand Down Expand Up @@ -39,4 +52,6 @@ async function importModule(modulePath) {
module.exports = {
supportsDynamicImport,
importModule,
isPackageInstalled,
isTypescriptProject,
};
6 changes: 3 additions & 3 deletions src/helpers/init-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ const init = {
createFolder('models', helpers.path.getModelsPath(), force);
},

createModelsIndexFile: (force) => {
createConnectionFile: (force) => {
const modelsPath = helpers.path.getModelsPath();
const indexPath = path.resolve(
modelsPath,
helpers.path.addFileExtension('index')
helpers.path.addFileExtension('connection')
);

if (!helpers.path.existsSync(modelsPath)) {
Expand All @@ -71,7 +71,7 @@ const init = {
helpers.asset.write(
indexPath,
helpers.template.render(
'models/index.js',
'models/connection.js',
{
configFile:
"__dirname + '/" + relativeConfigPath.replace(/\\/g, '/') + "'",
Expand Down
50 changes: 44 additions & 6 deletions src/helpers/model-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

const Sequelize = helpers.generic.getSequelize();
const validAttributeFunctionType = ['array', 'enum'];
const typescriptTypesForDbFieldTypes = {
string: 'string',
text: 'string',
uuid: 'string',
CHAR: 'string',
number: 'number',
float: 'number',
integer: 'number',
bigint: 'number',
mediumint: 'number',
tinyint: 'number',
smallint: 'number',
double: 'number',
'double precision': 'number',
real: 'number',
decimal: 'number',
date: 'data',
now: 'data',
dateonly: 'data',
boolean: 'boolean',
};

/**
* Check the given dataType actual exists.
Expand All @@ -15,6 +36,17 @@
return dataType;
}

function getTsTypeForDbColumnType(db_type, attribute_func, values) {
db_type = db_type.toLowerCase();
if (attribute_func === 'array') {
return `${typescriptTypesForDbFieldTypes[db_type]}[]`;
} else if (attribute_func === 'enum') {
return values.join(' | ');
}

return typescriptTypesForDbFieldTypes[db_type] || 'any';
}

function formatAttributes(attribute) {
let result;
const split = attribute.split(':');
Expand All @@ -24,10 +56,13 @@
fieldName: split[0],
dataType: split[1],
dataFunction: null,
tsType: getTsTypeForDbColumnType(split[1]),
dataValues: null,
};
} else if (split.length === 3) {
const validValues = /^\{(,? ?[A-z0-9 ]+)+\}$/;
const validValues =
/^\{((('[A-z0-9 ]+')|("[A-z0-9 ]+")|([A-z0-9 ]+)))(, ?(('[A-z0-9 ]+')|("[A-z0-9 ]+")|([A-z0-9 ]+)))*\}$/;

Check failure

Code scanning / CodeQL

Inefficient regular expression High

This part of the regular expression may cause exponential backtracking on strings starting with '{ ,' and containing many repetitions of ' ,'.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a false positive, @WikiRik can you please dismiss this alert?

Check warning

Code scanning / CodeQL

Overly permissive regular expression range Medium

Suspicious character range that is equivalent to [A-Z[]^_`a-z].

Check warning

Code scanning / CodeQL

Overly permissive regular expression range Medium

Suspicious character range that is equivalent to [A-Z[]^_`a-z].

Check warning

Code scanning / CodeQL

Overly permissive regular expression range Medium

Suspicious character range that is equivalent to [A-Z[]^_`a-z].

Check warning

Code scanning / CodeQL

Overly permissive regular expression range Medium

Suspicious character range that is equivalent to [A-Z[]^_`a-z].

Check warning

Code scanning / CodeQL

Overly permissive regular expression range Medium

Suspicious character range that is equivalent to [A-Z[]^_`a-z].

Check warning

Code scanning / CodeQL

Overly permissive regular expression range Medium

Suspicious character range that is equivalent to [A-Z[]^_`a-z].

const isValidFunction =
validAttributeFunctionType.indexOf(split[1].toLowerCase()) !== -1;
const isValidValue =
Expand All @@ -40,20 +75,23 @@
fieldName: split[0],
dataType: split[2],
dataFunction: split[1],
tsType: getTsTypeForDbColumnType(split[2], split[1]),
dataValues: null,
};
}

if (isValidFunction && !isValidValue && isValidValues) {
const values = split[2]
.replace(/(^\{|\}$)/g, '')
.split(/\s*,\s*/)
.map((s) => (s.startsWith('"') || s.startsWith("'") ? s : `'${s}'`));

result = {
fieldName: split[0],
dataType: split[1],
tsType: getTsTypeForDbColumnType(split[2], split[1], values),
dataFunction: null,
dataValues: split[2]
.replace(/(^\{|\}$)/g, '')
.split(/\s*,\s*/)
.map((s) => `'${s}'`)
.join(', '),
dataValues: values.join(', '),
};
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/helpers/path-helper.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from 'path';
import fs from 'fs';
import process from 'process';
import { isTypescriptProject } from './import-helper';

const resolve = require('resolve').sync;
import getYArgs from '../core/yargs';
Expand Down Expand Up @@ -45,7 +46,7 @@ module.exports = {
},

getFileExtension() {
return 'js';
return isTypescriptProject ? 'ts' : 'js';
},

addFileExtension(basename, options) {
Expand Down
4 changes: 4 additions & 0 deletions src/helpers/template-helper.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import _ from 'lodash';
import beautify from 'js-beautify';
import helpers from './index';
import { isTypescriptProject } from './import-helper';

module.exports = {
render(path, locals, options) {
Expand All @@ -14,6 +15,9 @@ module.exports = {
);

const template = helpers.asset.read(path);
locals = locals || {};
locals['isTypescriptProject'] = isTypescriptProject;

let content = _.template(template)(locals || {});

if (options.beautify) {
Expand Down
4 changes: 4 additions & 0 deletions src/sequelize.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#!/usr/bin/env node

import getYArgs from './core/yargs';
import { isTypescriptProject } from './helpers/import-helper';

// enable typescript compatibility if project is based on typescript
if (isTypescriptProject) require('ts-node/register');

const yargs = getYArgs();

Expand Down
Loading