pinstripe 0.32.0 → 0.33.0

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.
package/cli.js CHANGED
@@ -1,46 +1,36 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { spawn } from 'child_process';
4
-
5
3
  import { Project } from './lib/project.js';
6
4
  import { Command } from './lib/command.js';
7
5
  import { importAll } from './lib/import_all.js';
8
6
  import { Workspace } from './lib/workspace.js';
9
7
 
10
8
  (async () => {
11
- const { entryPath, localPinstripePath, exists } = await Project.instance;
12
- const { argv, env, execPath } = process;
13
- const args = argv.slice(2);
9
+ const { entryPath, exists } = await Project.instance;
10
+ const { argv } = process;
11
+ const [name, ...args ] = argv.slice(2);
14
12
 
15
- if (env.IS_LOCAL_PINSTRIPE != 'true' && localPinstripePath) {
16
- spawn(execPath, [localPinstripePath, ...args], {
17
- env: { ...env, IS_LOCAL_PINSTRIPE: 'true' },
18
- stdio: 'inherit'
19
- });
20
- } else {
21
- if(entryPath){
22
- import(entryPath);
23
- }
13
+ if(entryPath){
14
+ import(entryPath);
15
+ }
24
16
 
25
- await importAll();
17
+ await importAll();
26
18
 
27
- if(exists){
28
- Command.unregister('generate-project');
29
- } else {
30
- const allowedCommands = ['generate-project', 'list-commands'];
31
- Command.names.forEach(commandName => {
32
- if(!allowedCommands.includes(commandName)){
33
- Command.unregister(commandName);
34
- }
35
- });
36
- }
37
-
38
- try {
39
- await Workspace.run(async function(){
40
- await this.runCommand(...args);
41
- });
42
- } catch(e) {
43
- console.error(e);
44
- }
19
+ if(exists){
20
+ Command.names.forEach(commandName => {
21
+ if(!Command.for(commandName).internal) Command.unregister(commandName);
22
+ });
23
+ } else {
24
+ Command.names.forEach(commandName => {
25
+ if(!Command.for(commandName).external) Command.unregister(commandName);
26
+ });
27
+ }
28
+
29
+ try {
30
+ await Workspace.run(async function(){
31
+ await this.runCommand(name, args);
32
+ });
33
+ } catch(e) {
34
+ console.error(e);
45
35
  }
46
36
  })();
package/lib/command.js CHANGED
@@ -4,9 +4,15 @@ import { inflector } from './inflector.js';
4
4
  import { Registry } from './registry.js';
5
5
  import { ServiceConsumer } from './service_consumer.js';
6
6
 
7
+ const optionPattern = /^-([a-z]|-[a-z\-]+)$/;
8
+
7
9
  export const Command = Class.extend().include({
8
10
  meta(){
9
- this.assignProps({ name: 'Command' });
11
+ this.assignProps({
12
+ name: 'Command',
13
+ internal: true,
14
+ external: false
15
+ });
10
16
 
11
17
  this.include(Registry);
12
18
  this.include(ServiceConsumer);
@@ -16,12 +22,43 @@ export const Command = Class.extend().include({
16
22
  return inflector.dasherize(name);
17
23
  },
18
24
 
19
- async run(context, name = 'list-commands', ...args){
25
+ async run(context, name = 'list-commands', params = {}){
20
26
  await context.fork().run(async context => {
21
- context.args = [ ...args ];
27
+ context.params = Array.isArray(params) ? this.extractParams(params) : params;
22
28
  await this.create(name, context).run();
23
29
  });
24
30
  },
31
+
32
+ extractParams(_args = []){
33
+ const args = [ ..._args ];
34
+ const out = {};
35
+ let currentName;
36
+ while(args.length){
37
+ const arg = args.shift();
38
+ const matches = arg.match(optionPattern);
39
+ if(matches){
40
+ currentName = inflector.camelize(matches[1]);
41
+ if(out[currentName] === undefined){
42
+ out[currentName] = [];
43
+ }
44
+ } else {
45
+ if(currentName === undefined){
46
+ currentName = 'args';
47
+ out[currentName] = [];
48
+ }
49
+ out[currentName].push(arg);
50
+ }
51
+ }
52
+ Object.keys(out).forEach(name => {
53
+ const value = out[name];
54
+ if(!value.length){
55
+ out[name] = true;
56
+ } else {
57
+ out[name] = value.join(' ');
58
+ }
59
+ });
60
+ return out;
61
+ }
25
62
  });
26
63
  },
27
64
 
@@ -0,0 +1,32 @@
1
+ import { Command } from "./command.js";
2
+
3
+ [
4
+ { args: [], expectedParams: {} },
5
+ { args: ["-a"], expectedParams: { a: true } },
6
+ { args: ["--apple"], expectedParams: { apple: true } },
7
+ { args: ["--apple", "--pear"], expectedParams: { apple: true, pear: true } },
8
+ { args: ["Hello world!"], expectedParams: { args: "Hello world!" } },
9
+ { args: ["Hello", "world!"], expectedParams: { args: "Hello world!" } },
10
+ {
11
+ args: [
12
+ "Hello", "world!",
13
+ "--apple",
14
+ "--pear",
15
+ "--plum-color", "purple",
16
+ "--other-fruit", "peach", "orange",
17
+ ],
18
+ expectedParams: {
19
+ args: "Hello world!",
20
+ apple: true,
21
+ pear: true,
22
+ plumColor: "purple",
23
+ otherFruit: "peach orange",
24
+ },
25
+ },
26
+ ].forEach(({ args, expectedParams }, i) => {
27
+ test(`Command.extractParams (${i}})`, () => {
28
+ expect(Command.extractParams(args)).toStrictEqual(expectedParams);
29
+ });
30
+ });
31
+
32
+
@@ -2,10 +2,10 @@
2
2
 
3
3
  export default {
4
4
  async run(){
5
- const [ name = '' ] = this.args;
5
+ const { name = '' } = this.params;
6
6
  const normalizedName = this.inflector.snakeify(name);
7
7
  if(normalizedName == ''){
8
- console.error('An app name must be given.');
8
+ console.error('An app --name must be given.');
9
9
  process.exit();
10
10
  }
11
11
 
@@ -35,7 +35,9 @@ export default {
35
35
 
36
36
  });
37
37
 
38
- await this.runCommand('generate-view', `${normalizedName}/index`);
38
+ await this.runCommand('generate-view', {
39
+ name: `${normalizedName}/index`
40
+ });
39
41
  }
40
42
  }
41
43
 
@@ -2,10 +2,10 @@
2
2
 
3
3
  export default {
4
4
  async run(){
5
- const [ name = '' ] = this.args;
5
+ const { name = '' } = this.params;
6
6
  const normalizedName = this.inflector.snakeify(name);
7
7
  if(normalizedName == ''){
8
- console.error('A background_job name must be given.');
8
+ console.error('A background job --name must be given.');
9
9
  process.exit();
10
10
  }
11
11
 
@@ -2,10 +2,10 @@
2
2
 
3
3
  export default {
4
4
  async run(){
5
- const [ name = '' ] = this.args;
5
+ const { name = '' } = this.params;
6
6
  const normalizedName = this.inflector.snakeify(name);
7
7
  if(normalizedName == ''){
8
- console.error('A command name must be given.');
8
+ console.error('A command --name must be given.');
9
9
  process.exit();
10
10
  }
11
11
 
@@ -1,10 +1,9 @@
1
1
 
2
2
  export default {
3
3
  async run(){
4
- const { extractArg } = this.cliUtils;
5
- const name = this.inflector.snakeify(extractArg(''));
4
+ const name = this.inflector.snakeify(this.params.name || '');
6
5
  if(name == ''){
7
- console.error('A component name must be given.');
6
+ console.error('A component --name must be given.');
8
7
  process.exit();
9
8
  }
10
9
 
@@ -1,18 +1,18 @@
1
1
 
2
2
  export default {
3
3
  async run(){
4
- const { extractArg, extractFields, extractOptions } = this.cliUtils;
5
4
 
6
- const suffix = this.inflector.snakeify(extractArg('migration'));
7
- const fields = extractFields();
8
- const { table } = extractOptions({
9
- table: (() => {
10
- const matches = suffix.match(/_to_(.+)$/);
11
- if(matches){
12
- return matches[1];
13
- }
14
- })()
15
- });
5
+ const suffix = this.inflector.snakeify(this.params.suffix || 'migration');
6
+
7
+ const { fields = '' } = this.params;
8
+ const normalizedFields = this.cliUtils.normalizeFields(fields);
9
+
10
+ const table = this.params.table || (() => {
11
+ const matches = suffix.match(/_to_(.+)$/);
12
+ if(matches){
13
+ return matches[1];
14
+ }
15
+ })();
16
16
 
17
17
  const unixTime = Math.floor(new Date().getTime() / 1000);
18
18
  const name = `${unixTime}_${suffix}`;
@@ -33,10 +33,10 @@ export default {
33
33
  indent(() => {
34
34
  line(`async migrate(){`);
35
35
  indent(() => {
36
- if(table && fields.length){
36
+ if(table && normalizedFields.length){
37
37
  line(`await this.database.table('${table}', async ${table} => {`);
38
38
  indent(() => {
39
- fields.forEach(({ name, type }) => {
39
+ normalizedFields.forEach(({ name, type }) => {
40
40
  line(`await ${table}.addColumn('${name}', '${type}');`);
41
41
  });
42
42
  })
@@ -2,20 +2,17 @@
2
2
  export default {
3
3
  async run(){
4
4
 
5
- const { extractArg, extractFields } = this.cliUtils;
6
- const name = this.inflector.snakeify(extractArg(''));
5
+ const name = this.inflector.snakeify(this.params.name || '');
7
6
  if(name == ''){
8
- console.error('A model name must be given.');
7
+ console.error('A model --name must be given.');
9
8
  process.exit();
10
9
  }
11
- const fields = extractFields();
12
-
10
+
11
+ let { fields = '' } = this.params;
12
+
13
13
  const collectionName = this.inflector.camelize(this.inflector.pluralize(name));
14
14
  if(!await this.database[collectionName]){
15
- const denormalizedFields = fields.map(({ mandatory, name, type }) => {
16
- return `${ mandatory ? '^' : '' }${name}:${type}`
17
- });
18
- await this.runCommand('generate-migration', `create_${name}`, ...denormalizedFields, '--table', collectionName)
15
+ await this.runCommand('generate-migration', { name: `create_${name}`, fields, table: collectionName});
19
16
  }
20
17
 
21
18
  const { inProjectRootDir, generateFile, line, indent } = this.fsBuilder;
@@ -7,18 +7,23 @@ const defaultDependencies = [
7
7
  ];
8
8
 
9
9
  export default {
10
- async run(){
10
+ meta(){
11
+ this.assignProps({
12
+ external: true,
13
+ internal: false
14
+ });
15
+ },
11
16
 
12
- const { extractArg, extractOptions } = this.cliUtils;
13
- const name = extractArg('');
17
+ async run(){
18
+ const name = this.params.name || '';
14
19
  if(name == ''){
15
- console.error('A project name must be given.');
20
+ console.error('A project --name must be given.');
16
21
  process.exit();
17
22
  }
18
- const { with: dependencies, core } = extractOptions({
19
- with: [],
20
- core: false
21
- });
23
+
24
+ let { with: dependencies = '', core = false } = this.params;
25
+
26
+ dependencies = dependencies.split(/\s+/).map(dependency => dependency.trim());
22
27
 
23
28
  if(!core) defaultDependencies.forEach(dependency => {
24
29
  if(!dependencies.includes(dependency)) dependencies.unshift(dependency);
@@ -1,9 +1,9 @@
1
1
 
2
2
  export default {
3
3
  async run(){
4
- const [ name = '' ] = this.args;
4
+ const { name = '' } = this.params;
5
5
  if(name == ''){
6
- console.error('A service name must be given.');
6
+ console.error('A service --name must be given.');
7
7
  process.exit();
8
8
  }
9
9
 
@@ -6,9 +6,7 @@ import { App, View } from 'pinstripe';
6
6
  export default {
7
7
  async run(){
8
8
 
9
- const { extractOptions } = this.cliUtils;
10
-
11
- const { app = 'main' } = extractOptions();
9
+ const { app = 'main' } = this.params;
12
10
 
13
11
  const { viewNames } = View.mapperFor(App.create(app, this.context).compose());
14
12
 
@@ -4,10 +4,10 @@ import { readFile } from 'fs/promises';
4
4
 
5
5
  export default {
6
6
  async run(){
7
- const [ name = '' ] = this.args;
7
+ const { name = '' } = this.params;
8
8
  let normalizedName = name.replace(/^\//, '');
9
9
  if(name == ''){
10
- console.error('A view name must be given.');
10
+ console.error('A view --name must be given.');
11
11
  process.exit();
12
12
  }
13
13
 
@@ -3,6 +3,12 @@ import chalk from 'chalk';
3
3
  import { Command } from 'pinstripe';
4
4
 
5
5
  export default {
6
+ meta(){
7
+ this.assignProps({
8
+ external: true,
9
+ });
10
+ },
11
+
6
12
  run(){
7
13
  console.log('');
8
14
  console.log('The following commands are available:');
@@ -4,9 +4,7 @@ import { App, View } from 'pinstripe';
4
4
 
5
5
  export default {
6
6
  run(){
7
- const { extractOptions } = this.cliUtils;
8
-
9
- const { app = 'main' } = extractOptions();
7
+ const { app = 'main' } = this.params;
10
8
 
11
9
  const { viewNames } = View.mapperFor(App.create(app, this.context).compose());
12
10
  console.log('');
@@ -1,9 +1,9 @@
1
1
 
2
2
  export default {
3
3
  async run(){
4
- const [ name = '' ] = this.args;
4
+ const { name = '' } = this.params;
5
5
  if(name == ''){
6
- console.error('A background job name must be given.');
6
+ console.error('A background job --name must be given.');
7
7
  process.exit();
8
8
  }
9
9
  this.runBackgroundJob(name);
@@ -1,13 +1,10 @@
1
1
 
2
2
  export default {
3
3
  run(){
4
- const { extractOptions } = this.cliUtils;
5
-
6
- const { app, withoutBot } = extractOptions({
7
- app: `main:${process.env.HOST || '127.0.0.1'}:${parseInt(process.env.PORT || '3000')}`,
8
- withoutBot: false
9
- });
10
-
4
+ const {
5
+ app = `main:${process.env.HOST || '127.0.0.1'}:${parseInt(process.env.PORT || '3000')}`,
6
+ withoutBot = false
7
+ } = this.params;
11
8
 
12
9
  const apps = [];
13
10
  let currentPort = 3000;
package/lib/project.js CHANGED
@@ -40,8 +40,6 @@ export const Project = Class.extend().include({
40
40
  }
41
41
 
42
42
  this.entryPath = this.exports['.'] || this.main;
43
-
44
- this.localPinstripePath = (await findInPath('node_modules/.bin/pinstripe', process.cwd())).shift();
45
43
 
46
44
  this.nodePaths = await findInPath('node_modules/', process.cwd());
47
45
  },
package/lib/registry.js CHANGED
@@ -52,6 +52,7 @@ export const Registry = {
52
52
  unregister(name){
53
53
  const normalizedName = this.normalizeName(name);
54
54
  delete this.mixins[normalizedName];
55
+ this.clearCache();
55
56
  },
56
57
 
57
58
  clearCache(){
@@ -1,77 +1,25 @@
1
-
2
- const optionPattern = /^-([a-z]|-[a-z\-]+)$/;
3
-
4
1
  export default {
5
2
  create(){
6
- if(this.context.cliUtils) return this.context.cliUtils;
7
-
8
- let args = [...this.args];
9
-
10
- const extractArg = (_default) => {
11
- if(args[0] && !args[0].match(optionPattern)){
12
- return args.shift();
3
+ return {
4
+ normalizeFields: fields => {
5
+ let out = fields;
6
+ if(typeof out == 'string') out = out.split(/\s+/).map(field => field.trim()).filter(field => field).map(arg => {
7
+ const matches = arg.match(/^(\^|)([^:]*)(:|)(.*)$/);
8
+ const mandatory = matches[1] == '^';
9
+ const name = this.inflector.camelize(matches[2]);
10
+ const type = matches[4];
11
+ return {
12
+ mandatory,
13
+ name,
14
+ type
15
+ };
16
+ });
17
+ out.forEach(field => {
18
+ field.mandatory ??= false;
19
+ field.type ||= 'string';
20
+ });
21
+ return out;
13
22
  }
14
- return _default;
15
23
  };
16
-
17
- const extractArgs = () => {
18
- const out = [];
19
- while(args[0] && !args[0].match(optionPattern)){
20
- out.push(args.shift())
21
- }
22
- return out;
23
- };
24
-
25
- const extractFields = () => extractArgs().map(arg => {
26
- const matches = arg.match(/^(\^|)([^:]*)(:|)(.*)$/);
27
- const mandatory = matches[1] == '^';
28
- const name = this.inflector.camelize(matches[2]);
29
- const type = matches[4] || 'string';
30
- return {
31
- mandatory,
32
- name,
33
- type
34
- };
35
- });
36
-
37
- const extractOptions = (_default = {}) => {
38
- const out = {};
39
- let currentName;
40
- while(args.length){
41
- const arg = args.shift();
42
- const matches = arg.match(optionPattern);
43
- if(matches){
44
- currentName = this.inflector.camelize(matches[1]);
45
- if(out[currentName] === undefined){
46
- out[currentName] = [];
47
- }
48
- } else if(currentName){
49
- out[currentName].push(arg);
50
- }
51
- }
52
- Object.keys({ ...out, ..._default }).forEach(name => {
53
- const value = out[name];
54
- if(value === undefined){
55
- out[name] = _default[name];
56
- } else if(!value.length){
57
- out[name] = true;
58
- } else if(!Array.isArray(_default[name])){
59
- out[name] = value.join(' ');
60
- }
61
- });
62
- return out;
63
- };
64
-
65
- const resetArgs = () => args = [..._args];
66
-
67
- this.context.cliUtils = {
68
- extractArg,
69
- extractArgs,
70
- extractFields,
71
- extractOptions,
72
- resetArgs
73
- };
74
-
75
- return this.context.cliUtils;
76
24
  }
77
25
  };
@@ -70,7 +70,7 @@ export default {
70
70
  parseBody(request){
71
71
  return new Promise((resolve) => {
72
72
  const out = {};
73
- const busboy = new Busboy({ headers: request.headers });
73
+ const busboy = Busboy({ headers: request.headers });
74
74
 
75
75
  busboy.on('file', function(fieldname, file, filename, encoding, mimeType) {
76
76
  const chunks = [];
@@ -10,6 +10,7 @@ export const styles = `
10
10
  border-radius: 0.4rem;
11
11
  box-shadow: none;
12
12
  font-size: 1.6rem;
13
+ font-weight: 600;
13
14
  height: 2.5em;
14
15
  line-height: 1.5;
15
16
  position: relative;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "name": "pinstripe",
4
4
  "description": "A slick web framework for Node.js.",
5
- "version": "0.32.0",
5
+ "version": "0.33.0",
6
6
  "author": "Jody Salt",
7
7
  "license": "MIT",
8
8
  "exports": {
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "scripts": {
18
18
  "clean": "rm -rf ./node_modules",
19
- "start": "IS_LOCAL_PINSTRIPE=true pinstripe start-server",
19
+ "start": "pinstripe start-server",
20
20
  "test": "npm run test:unit",
21
21
  "test:unit": "jest lib"
22
22
  },
@@ -24,13 +24,13 @@
24
24
  "@babel/core": "^7.9.0",
25
25
  "@babel/plugin-transform-modules-commonjs": "^7.18.6",
26
26
  "@babel/preset-env": "^7.9.0",
27
- "babel-jest": "^25.1.0",
27
+ "babel-jest": "^29.7.0",
28
28
  "jest": "^28.0.1",
29
29
  "jest-environment-jsdom": "^29.1.2"
30
30
  },
31
31
  "dependencies": {
32
32
  "bcrypt": "^5.0.1",
33
- "busboy": "^0.3.1",
33
+ "busboy": "^1.6.0",
34
34
  "chalk": "^4.1.0",
35
35
  "cron-parser": "^4.3.0",
36
36
  "css": "^3.0.0",
@@ -41,10 +41,11 @@
41
41
  "markdown-it": "^12.2.0",
42
42
  "memfs": "^3.2.2",
43
43
  "mime-types": "^2.1.28",
44
- "mysql2": "^2.3.3",
45
- "nodemailer": "^6.7.2",
44
+ "mysql2": "^3.10.1",
45
+ "nodemailer": "^6.9.14",
46
46
  "punycode": "^2.3.0",
47
- "sqlite3": "^5.0.2",
47
+ "sqlite3": "^5.1.7",
48
+ "start-server-and-test": "2.0.4",
48
49
  "tmp": "^0.2.1",
49
50
  "unionfs": "^4.4.0"
50
51
  },
@@ -1,9 +0,0 @@
1
-
2
- export default {
3
- create(){
4
- if(!this.context.hasOwnProperty('args')){
5
- this.context.args = [];
6
- }
7
- return this.context.args;
8
- }
9
- };