mythix 2.5.4 → 2.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,149 @@
1
+ 'use strict';
2
+
3
+ const Path = require('path');
4
+ const Nife = require('nife');
5
+ const { Logger } = require('../../logger');
6
+
7
+ const {
8
+ defineCommand,
9
+ getCommandFiles,
10
+ getInternalCommandsPath,
11
+ } = require('../cli-utils');
12
+
13
+ function loadGeneratorCommands(application, operationName) {
14
+ const generatorFilterFunc = (fullFileName, fileName, stats) => {
15
+ if (stats.isDirectory())
16
+ return true;
17
+
18
+ if ((/[^_][\w-]+-generator\.\w+$/).test(fileName))
19
+ return true;
20
+
21
+ return false;
22
+ };
23
+
24
+ let applicationOptions = application.getOptions();
25
+ let generatorFiles = [].concat(
26
+ getCommandFiles(
27
+ getInternalCommandsPath(),
28
+ generatorFilterFunc,
29
+ ),
30
+ (Nife.isEmpty(applicationOptions.commandsPath)) ? getCommandFiles(
31
+ getInternalCommandsPath(),
32
+ generatorFilterFunc,
33
+ ) : null,
34
+ ).filter(Boolean);
35
+
36
+ generatorFiles = Nife.uniq(generatorFiles);
37
+
38
+ let commands = {};
39
+
40
+ for (let i = 0, il = generatorFiles.length; i < il; i++) {
41
+ let fullFileName = generatorFiles[i];
42
+ let fileName = Path.basename(fullFileName);
43
+ let commandName = fileName.replace(/^([\w-]+)-generator\.\w+$/, '$1');
44
+ let commandArgs = {};
45
+ let Klass;
46
+
47
+ try {
48
+ Klass = require(fullFileName);
49
+ if (Klass.__esModule)
50
+ Klass = Klass['default'];
51
+
52
+ if (typeof Klass.commandArguments === 'function') {
53
+ commandArgs = Klass.commandArguments(application, operationName);
54
+
55
+ if (commandArgs && commandArgs.help)
56
+ commandArgs.help['@see'] = `See: 'mythix-cli generate ${commandName} --help' for more help`;
57
+ }
58
+ } catch (error) {
59
+ if (operationName === 'help')
60
+ console.error(`Error while attempting to load generator "${fullFileName}": `, error);
61
+
62
+ continue;
63
+ }
64
+
65
+ commands[commandName.toLocaleLowerCase()] = {
66
+ CommandKlass: Klass,
67
+ ...commandArgs,
68
+ };
69
+ }
70
+
71
+ return commands;
72
+ }
73
+
74
+ module.exports = defineCommand('generate', ({ Parent }) => {
75
+ return class GenerateCommand extends Parent {
76
+ static applicationConfig = () => {
77
+ return {
78
+ httpServer: false,
79
+ autoReload: false,
80
+ runTasks: false,
81
+ logger: {
82
+ level: Logger.LEVEL_ERROR,
83
+ },
84
+ };
85
+ };
86
+
87
+ static commandArguments(application, operationName) {
88
+ let generatorCommands = loadGeneratorCommands(application, operationName);
89
+
90
+ let help = {
91
+ '@usage': 'mythix-cli generate {generator}',
92
+ '@title': 'Generate new content using the specified generator',
93
+ };
94
+
95
+ Nife.iterate(generatorCommands, ({ value, key }) => {
96
+ if (value.help)
97
+ help[key] = value.help;
98
+ });
99
+
100
+ return {
101
+ help: help,
102
+ runner: ({ $, fetch, showHelp, scope, store }) => {
103
+ let subCommandResult = $(
104
+ /[\w-]+/,
105
+ ({ store }, parsedResult) => {
106
+ store({ generateSubCommand: parsedResult.value.toLowerCase() });
107
+ return true;
108
+ },
109
+ { formatParsedResult: (result) => ({ value: result[0] }) },
110
+ );
111
+
112
+ if (!subCommandResult)
113
+ return false;
114
+
115
+ let subCommandName = fetch('generateSubCommand');
116
+ let command = generatorCommands[subCommandName];
117
+
118
+ if (!command) {
119
+ console.error(`Error: unknown generator "${subCommandName}"\n`);
120
+ return false;
121
+ }
122
+
123
+ let subCommandRunnerResult = true;
124
+ if (command.runner)
125
+ subCommandRunnerResult = scope(subCommandName, command.runner);
126
+
127
+ if (!subCommandRunnerResult || fetch('help', false)) {
128
+ showHelp(null, help[subCommandName] || help);
129
+ return false;
130
+ }
131
+
132
+ store('generatorCommands', generatorCommands);
133
+
134
+ return true;
135
+ },
136
+ };
137
+ }
138
+
139
+ async execute(args, fullArgs) {
140
+ let application = this.getApplication();
141
+ let subCommandName = args.generateSubCommand;
142
+ let generatorCommands = args.generatorCommands;
143
+ let command = generatorCommands[subCommandName];
144
+
145
+ let commandInstance = new command.CommandKlass(application, this.getOptions());
146
+ return await commandInstance.execute.call(commandInstance, args[subCommandName], fullArgs);
147
+ }
148
+ };
149
+ });
@@ -0,0 +1,209 @@
1
+ /* eslint-disable key-spacing */
2
+
3
+ 'use strict';
4
+
5
+ const Path = require('path');
6
+ const FileSystem = require('fs');
7
+ const Nife = require('nife');
8
+ const { Utils } = require('mythix-orm');
9
+ const { CommandBase } = require('../cli-utils');
10
+
11
+ class ValidationError extends Error {}
12
+
13
+ function generateMigration(migrationID, upCode, downCode) {
14
+ let template =
15
+ `
16
+ const MIGRATION_ID = '${migrationID}';
17
+
18
+ module.exports = {
19
+ MIGRATION_ID,
20
+ up: async function(connection) {
21
+ ${upCode}
22
+ },
23
+ down: async function(connection) {
24
+ ${downCode}
25
+ },
26
+ };
27
+ `;
28
+
29
+ return template;
30
+ }
31
+
32
+ class GenerateMigrationCommand extends CommandBase {
33
+ static commandArguments() {
34
+ return {
35
+ help: {
36
+ '@usage': 'mythix-cli generate migration {operation} {operation args} [options]',
37
+ '@title': 'Generate a migration file',
38
+ '@examples': [
39
+ 'mythix-cli generate migration add model User',
40
+ 'mythix-cli generate migration rename model User AdminUser',
41
+ 'mythix-cli generate migration remove model User',
42
+ 'mythix-cli generate migration add field User:age',
43
+ 'mythix-cli generate migration rename field User:age yearsLived',
44
+ 'mythix-cli generate migration remove field User:age',
45
+ ],
46
+ '-n={name} | -n {name} | --name={name} | --name {name}': 'Specify the name of the generated migration file',
47
+ '-o={path} | -o {path} | --output={path} | --output {path}': 'Specify the name of the generated migration file',
48
+ },
49
+ runner: ({ $, Types, fetch, store }) => {
50
+ const pathFormatter = (value) => Path.resolve(value);
51
+
52
+ let application = fetch('mythixApplication');
53
+ let applicationOptions = application.getOptions();
54
+
55
+ $('--name', Types.STRING(), { name: 'name' })
56
+ || $('-n', Types.STRING(), { name: 'name' });
57
+
58
+ $('--output', Types.STRING(), { name: 'outputPath', format: pathFormatter })
59
+ || $('-o', Types.STRING(), { name: 'outputPath', format: pathFormatter })
60
+ || store('outputPath', Path.resolve(applicationOptions.migrationsPath));
61
+
62
+ let result = $(
63
+ /[\w-]+/,
64
+ ({ store }, parsedResult) => {
65
+ store({ operation: parsedResult.value });
66
+ return true;
67
+ },
68
+ { formatParsedResult: (result) => ({ value: result[0] }) },
69
+ );
70
+
71
+ if (!result)
72
+ return false;
73
+
74
+ result = $(
75
+ /[\w-]+/,
76
+ ({ store }, parsedResult) => {
77
+ store({ entity: parsedResult.value });
78
+ return true;
79
+ },
80
+ { formatParsedResult: (result) => ({ value: result[0] }) },
81
+ );
82
+
83
+ if (!result && fetch('operation') !== 'blank')
84
+ return false;
85
+
86
+ if (fetch('operation') === 'blank' && Nife.isEmpty(fetch('name'))) {
87
+ console.log('Error: Blank templates require a name. Use the "--name {name}" argument to specify a name.\n');
88
+ return false;
89
+ }
90
+
91
+ return true;
92
+ },
93
+ };
94
+ }
95
+
96
+ getRevisionNumber() {
97
+ let date = new Date();
98
+ return date.toISOString().replace(/\.[^.]+$/, '').replace(/\D/g, '');
99
+ }
100
+
101
+ formatMigrationName(name) {
102
+ return name
103
+ .replace(/\.\w+$/, '')
104
+ .replace(/[^a-zA-Z0-9-]+/g, '-')
105
+ .replace(/^[^a-zA-Z0-9]+/, '')
106
+ .replace(/[^a-zA-Z0-9]+$/, '')
107
+ .toLowerCase();
108
+ }
109
+
110
+ getOrGenerateMigrationName(args) {
111
+ if (Nife.isNotEmpty(args.name))
112
+ return `${args.version}-${this.formatMigrationName(args.name)}.js`;
113
+
114
+ let name = [
115
+ args.operation,
116
+ args.entity,
117
+ ...args.remaining,
118
+ ];
119
+
120
+ return `${args.version}-${this.formatMigrationName(name.join('-'))}.js`;
121
+ }
122
+
123
+ async execute(args, fullArgs) {
124
+ args.version = this.getRevisionNumber();
125
+ args.remaining = fullArgs._remaining;
126
+
127
+ // Ensure the output path exists
128
+ try {
129
+ let stats = FileSystem.statSync(args.outputPath);
130
+ if (!stats.isDirectory())
131
+ throw new Error(`The path specified is a file, not a directory: "${args.outputPath}"`);
132
+ } catch (error) {
133
+ if (error.code === 'ENOENT')
134
+ FileSystem.mkdirSync(args.outputPath, { recursive: true });
135
+ else
136
+ throw error;
137
+ }
138
+
139
+ // Generate the migration name
140
+ let migrationName = this.getOrGenerateMigrationName(args);
141
+ let methodName = `operation${Nife.capitalize(args.operation)}${(args.entity) ? Nife.capitalize(args.entity) : ''}`;
142
+ if (typeof this[methodName] !== 'function') {
143
+ console.error(`Error: unknown operation or entity specified: "${args.operation}${(args.entity) ? `:${args.entity}` : ''}"`);
144
+ return 1;
145
+ }
146
+
147
+ try {
148
+ let content = await this[methodName](args);
149
+ let finalPath = Path.join(args.outputPath, migrationName);
150
+
151
+ console.log(`Would write content:\n${content}`);
152
+
153
+ // FileSystem.writeFileSync(migrationWritePath, migrationSource, 'utf8');
154
+
155
+ console.log(`New migration to revision ${args.version} has been written to file "${finalPath}"`);
156
+ } catch (error) {
157
+ if (error instanceof ValidationError) {
158
+ console.error(error.message + '\n');
159
+ return 1;
160
+ }
161
+
162
+ throw error;
163
+ }
164
+ }
165
+
166
+ tabIn(str, amount = 1) {
167
+ let parts = new Array(amount);
168
+ for (let i = 0; i < amount; i++)
169
+ parts.push(' ');
170
+
171
+ let ws = parts.join('');
172
+ return str.trim().replace(/^/gm, ws);
173
+ }
174
+
175
+ operationAddModels(args) {
176
+ if (Nife.isEmpty(args.remaining))
177
+ throw new ValidationError('No model name(s) provided. Try this instead: \n\nmythix-cli generate migration add models \'{model name}\' ...');
178
+
179
+ let application = this.getApplication();
180
+ let connection = application.getDBConnection();
181
+ let queryGenerator = connection.getQueryGenerator();
182
+ let statements = [];
183
+ let reverseStatements = [];
184
+
185
+ let modelNames = Utils.sortModelNamesByCreationOrder(connection, Nife.uniq(args.remaining));
186
+ for (let i = 0, il = modelNames.length; i < il; i++) {
187
+ let modelName = modelNames[i];
188
+ let Model = connection.getModel(modelName);
189
+
190
+ let createTable = queryGenerator.generateCreateTableStatement(Model);
191
+ statements.push(` // Create ${modelName} table\n await connection.query(\`\n${this.tabIn(createTable, 3)}\`,\n { logger: console },\n );`);
192
+
193
+ let dropTable = queryGenerator.generateDropTableStatement(Model, { cascade: true });
194
+ reverseStatements.push(` // Drop ${modelName} table\n await connection.query(\n \`${dropTable.trim()}\`,\n { logger: console },\n );`);
195
+ }
196
+
197
+ return generateMigration(
198
+ args.version,
199
+ statements.join('\n\n'),
200
+ reverseStatements.reverse().join('\n\n'),
201
+ );
202
+ }
203
+
204
+ operationAddModel(args) {
205
+ return this.operationAddModels(args);
206
+ }
207
+ }
208
+
209
+ module.exports = GenerateMigrationCommand;
@@ -2,8 +2,8 @@
2
2
 
3
3
  const Path = require('path');
4
4
  const Nife = require('nife');
5
- const { defineCommand } = require('../cli-utils');
6
- const { walkDir } = require('../../utils/file-utils');
5
+ const { defineCommand } = require('./cli-utils');
6
+ const { walkDir } = require('../utils/file-utils');
7
7
 
8
8
  const TIMESTAMP_LENGTH = 14;
9
9
  const MILLISECONDS_PER_SECOND = 1000.0;
@@ -9,7 +9,7 @@ function _setupModel(modelName, _Model, { application, connection }) {
9
9
  let tablePrefix = application.getDBTablePrefix();
10
10
 
11
11
  if (tablePrefix)
12
- tableName = (`${tablePrefix}${tableName}`);
12
+ tableName = `${tablePrefix}${tableName}`.replace(/_+/g, '_');
13
13
 
14
14
  Model.getTableName = () => tableName;
15
15
  Model.getModelName = () => modelName;
@@ -89,7 +89,7 @@ class DatabaseModule extends BaseModule {
89
89
  if (userSpecifiedPrefix != null)
90
90
  return ('' + userSpecifiedPrefix);
91
91
 
92
- return `${this.getApplication().getApplicationName()}_`.replace(/[^A-Za-z0-9_]+/g, '');
92
+ return `${this.getApplication().getApplicationName()}_`.replace(/[^A-Za-z0-9_]+/g, '_').replace(/_+/g, '_');
93
93
  }
94
94
 
95
95
  getConnection() {
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const Nife = require('nife');
4
- const TaskBase = require('./task-base');
3
+ const Nife = require('nife');
4
+ const { TaskBase } = require('./task-base');
5
5
 
6
6
  const SECONDS_PER_MINUTE = 60;
7
7
  const MINUTES_PER_HOUR = 60;
@@ -14,7 +14,16 @@ function walkDir(rootPath, _options, _callback, _allFiles, _depth) {
14
14
  for (let i = 0, il = fileNames.length; i < il; i++) {
15
15
  let fileName = fileNames[i];
16
16
  let fullFileName = Path.join(rootPath, fileName);
17
- let stats = FileSystem.statSync(fullFileName);
17
+ let stats;
18
+
19
+ try {
20
+ stats = FileSystem.statSync(fullFileName);
21
+ } catch (error) {
22
+ if (error.code === 'ENOENT')
23
+ continue;
24
+
25
+ throw error;
26
+ }
18
27
 
19
28
  if (typeof filterFunc === 'function' && !filterFunc(fullFileName, fileName, stats, rootPath, depth))
20
29
  continue;
@@ -54,8 +54,6 @@ class TestHTTPServerModule extends HTTPServerModule {
54
54
 
55
55
  function createTestApplication(ApplicationClass) {
56
56
  const Klass = class TestApplication extends ApplicationClass {
57
- static APP_NAME = `${(Nife.isNotEmpty(ApplicationClass.APP_NAME)) ? ApplicationClass.APP_NAME : 'mythix'}_test`;
58
-
59
57
  // Swap out modules for test config
60
58
  static getDefaultModules() {
61
59
  let defaultModules = ApplicationClass.getDefaultModules();
@@ -72,6 +70,7 @@ function createTestApplication(ApplicationClass) {
72
70
  autoReload: false,
73
71
  runTasks: false,
74
72
  testMode: true,
73
+ appName: `${(Nife.isNotEmpty(ApplicationClass.APP_NAME)) ? ApplicationClass.APP_NAME : 'mythix'}_test`,
75
74
  }, _opts || {});
76
75
 
77
76
  super(opts);