@researchdatabox/redbox-dev-tools 0.0.1 → 5.0.0-RC2
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/README.md +259 -14
- package/bin/cli.js +3 -0
- package/bin/run-hook-mocha.js +3 -0
- package/bin/run-hook-tsc.js +3 -0
- package/bin/wrapper-webpack.js +168 -0
- package/config/tsconfig.base.json +16 -0
- package/dist/bin/run-hook-mocha.d.ts +1 -0
- package/dist/bin/run-hook-mocha.js +49 -0
- package/dist/bin/run-hook-tsc.d.ts +1 -0
- package/dist/bin/run-hook-tsc.js +19 -0
- package/dist/cli-parsers.d.ts +9 -0
- package/dist/cli-parsers.js +131 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +495 -0
- package/dist/commands/form-config.d.ts +6 -0
- package/dist/commands/form-config.js +401 -0
- package/dist/commands/sync-translation.d.ts +23 -0
- package/dist/commands/sync-translation.js +348 -0
- package/dist/completion/index.d.ts +1 -0
- package/dist/completion/index.js +218 -0
- package/dist/generators/add-method.d.ts +27 -0
- package/dist/generators/add-method.js +235 -0
- package/dist/generators/angular-app.d.ts +24 -0
- package/dist/generators/angular-app.js +342 -0
- package/dist/generators/angular-service.d.ts +21 -0
- package/dist/generators/angular-service.js +154 -0
- package/dist/generators/controller.d.ts +40 -0
- package/dist/generators/controller.js +195 -0
- package/dist/generators/form-component.d.ts +46 -0
- package/dist/generators/form-component.js +906 -0
- package/dist/generators/form-field.d.ts +15 -0
- package/dist/generators/form-field.js +160 -0
- package/dist/generators/migration.d.ts +15 -0
- package/dist/generators/migration.js +100 -0
- package/dist/generators/model.d.ts +42 -0
- package/dist/generators/model.js +206 -0
- package/dist/generators/service.d.ts +16 -0
- package/dist/generators/service.js +111 -0
- package/dist/runtime-resolver.d.ts +3 -0
- package/dist/runtime-resolver.js +27 -0
- package/dist/templates/hook-archetype.d.ts +12 -0
- package/dist/templates/hook-archetype.js +187 -0
- package/dist/testing.d.ts +7 -0
- package/dist/testing.js +53 -0
- package/dist/utils/config-helper.d.ts +71 -0
- package/dist/utils/config-helper.js +574 -0
- package/dist/utils/generator.d.ts +10 -0
- package/dist/utils/generator.js +68 -0
- package/dist/utils/hook-tsconfig.d.ts +9 -0
- package/dist/utils/hook-tsconfig.js +104 -0
- package/dist/utils/paths.d.ts +10 -0
- package/dist/utils/paths.js +60 -0
- package/index.js +4 -0
- package/package.json +76 -19
- package/runtime-resolver.d.ts +1 -0
- package/runtime-resolver.js +3 -0
- package/skills/redbox-angular-apps/SKILL.md +42 -0
- package/skills/redbox-angular-services/SKILL.md +29 -0
- package/skills/redbox-controllers/SKILL.md +65 -0
- package/skills/redbox-feature-design-planner/SKILL.md +169 -0
- package/skills/redbox-feature-implementation-review/SKILL.md +117 -0
- package/skills/redbox-form-components/SKILL.md +114 -0
- package/skills/redbox-form-config/SKILL.md +34 -0
- package/skills/redbox-hook-development/SKILL.md +66 -0
- package/skills/redbox-services/SKILL.md +54 -0
- package/skills/redbox-test-verification/SKILL.md +38 -0
- package/skills/redbox-testing/SKILL.md +64 -0
- package/skills/redbox-web-verification/SKILL.md +71 -0
- package/skills/redbox-web-verification/redbox-angular-apps/SKILL.md +48 -0
- package/skills/redbox-web-verification/redbox-angular-services/SKILL.md +30 -0
- package/skills/redbox-web-verification/redbox-controllers/SKILL.md +56 -0
- package/skills/redbox-web-verification/redbox-form-config/SKILL.md +29 -0
- package/skills/redbox-web-verification/redbox-hook-development/SKILL.md +61 -0
- package/skills/redbox-web-verification/redbox-services/SKILL.md +45 -0
- package/skills/redbox-web-verification/redbox-testing/SKILL.md +53 -0
- package/skills/redbox-web-verification/redbox-web-verification/SKILL.md +47 -0
- package/templates/hook-archetype/standard/.gitignore.hbs +5 -0
- package/templates/hook-archetype/standard/.npmignore.hbs +4 -0
- package/templates/hook-archetype/standard/.npmrc.hbs +6 -0
- package/templates/hook-archetype/standard/.nvmrc.hbs +1 -0
- package/templates/hook-archetype/standard/AGENTS.md.hbs +51 -0
- package/templates/hook-archetype/standard/Dockerfile.hbs +29 -0
- package/templates/hook-archetype/standard/README.md.hbs +57 -0
- package/templates/hook-archetype/standard/assets/default/.gitkeep +1 -0
- package/templates/hook-archetype/standard/assets/fonts/.gitkeep +1 -0
- package/templates/hook-archetype/standard/assets/images/.gitkeep +1 -0
- package/templates/hook-archetype/standard/assets/styles/.gitkeep +1 -0
- package/templates/hook-archetype/standard/language-defaults/.gitkeep +1 -0
- package/templates/hook-archetype/standard/language-defaults/README.md.hbs +10 -0
- package/templates/hook-archetype/standard/language-defaults/en/translation.json.hbs +3 -0
- package/templates/hook-archetype/standard/language-defaults/meta.json.hbs +6 -0
- package/templates/hook-archetype/standard/package.json.hbs +44 -0
- package/templates/hook-archetype/standard/src/api/controllers/ExampleController.ts.hbs +21 -0
- package/templates/hook-archetype/standard/src/api/controllers/index.ts.hbs +23 -0
- package/templates/hook-archetype/standard/src/api/services/ExampleService.ts.hbs +22 -0
- package/templates/hook-archetype/standard/src/api/services/index.ts.hbs +23 -0
- package/templates/hook-archetype/standard/src/config/brandingConfigurationDefaults.ts.hbs +9 -0
- package/templates/hook-archetype/standard/src/config/reusableFormDefinitions.ts.hbs +6 -0
- package/templates/hook-archetype/standard/src/form-config/default-1.0-draft.ts.hbs +20 -0
- package/templates/hook-archetype/standard/src/form-config/index.ts.hbs +8 -0
- package/templates/hook-archetype/standard/src/index.ts.hbs +55 -0
- package/templates/hook-archetype/standard/src/migrations/index.ts.hbs +19 -0
- package/templates/hook-archetype/standard/support/bootstrap-data/.gitkeep +1 -0
- package/templates/hook-archetype/standard/support/bootstrap-data/namedqueries/.gitkeep +1 -0
- package/templates/hook-archetype/standard/support/bootstrap-data/records/.gitkeep +1 -0
- package/templates/hook-archetype/standard/support/bootstrap-data/reports/.gitkeep +1 -0
- package/templates/hook-archetype/standard/support/bootstrap-data/vocabularies/.gitkeep +1 -0
- package/templates/hook-archetype/standard/support/development/core-mount.env.hbs +3 -0
- package/templates/hook-archetype/standard/support/development/docker-compose.core-mount.yml.hbs +37 -0
- package/templates/hook-archetype/standard/support/development/docker-compose.yml.hbs +108 -0
- package/templates/hook-archetype/standard/support/development/prepare-core-mount.sh.hbs +61 -0
- package/templates/hook-archetype/standard/support/integration-testing/docker-compose.mocha.yml.hbs +118 -0
- package/templates/hook-archetype/standard/support/integration-testing/run-mocha-hook.sh.hbs +14 -0
- package/templates/hook-archetype/standard/support/integration-testing/run-mocha-redbox.sh.hbs +100 -0
- package/templates/hook-archetype/standard/support/prepare-minio.sh.hbs +74 -0
- package/templates/hook-archetype/standard/test/integration/hook-registration.test.ts.hbs +15 -0
- package/templates/hook-archetype/standard/test/support/globals.ts.hbs +40 -0
- package/templates/hook-archetype/standard/test/tsconfig.json.hbs +8 -0
- package/templates/hook-archetype/standard/test/unit/.mocharc.cjs.hbs +9 -0
- package/templates/hook-archetype/standard/test/unit/index.test.ts.hbs +46 -0
- package/templates/hook-archetype/standard/tsconfig.json.hbs +15 -0
- package/templates/hook-archetype/standard/views/.gitkeep +1 -0
- package/templates/hook-archetype/standard/views/default/.gitkeep +1 -0
- package/testing.d.ts +1 -0
- package/testing.js +3 -0
- package/.travis.yml +0 -23
- package/commands/initProject.js +0 -129
- package/redbox-dev-tools +0 -70
- package/yarn.lock +0 -1061
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Generator, GeneratorOptions } from '../utils/generator';
|
|
2
|
+
export interface FormFieldGeneratorOptions extends GeneratorOptions {
|
|
3
|
+
name: string;
|
|
4
|
+
type: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class FormFieldGenerator extends Generator {
|
|
7
|
+
private name;
|
|
8
|
+
private type;
|
|
9
|
+
constructor(options: FormFieldGeneratorOptions);
|
|
10
|
+
generate(): Promise<void>;
|
|
11
|
+
private escapeJsString;
|
|
12
|
+
private escapeForComment;
|
|
13
|
+
private generateFormContent;
|
|
14
|
+
private getSafeFileName;
|
|
15
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FormFieldGenerator = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const generator_1 = require("../utils/generator");
|
|
39
|
+
class FormFieldGenerator extends generator_1.Generator {
|
|
40
|
+
name;
|
|
41
|
+
type;
|
|
42
|
+
constructor(options) {
|
|
43
|
+
super(options);
|
|
44
|
+
this.name = options.name;
|
|
45
|
+
this.type = options.type;
|
|
46
|
+
}
|
|
47
|
+
async generate() {
|
|
48
|
+
const safeName = this.getSafeFileName(this.name);
|
|
49
|
+
const fileName = safeName.endsWith('.js') ? safeName : `${safeName}.js`;
|
|
50
|
+
const formConfigDir = path.join(this.root, 'form-config');
|
|
51
|
+
const filePath = path.resolve(formConfigDir, fileName);
|
|
52
|
+
if (!filePath.startsWith(path.resolve(formConfigDir) + path.sep)) {
|
|
53
|
+
throw new Error(`Form field path resolves outside form-config: ${fileName}`);
|
|
54
|
+
}
|
|
55
|
+
const content = this.generateFormContent();
|
|
56
|
+
await this.writeFile(filePath, content);
|
|
57
|
+
}
|
|
58
|
+
escapeJsString(value) {
|
|
59
|
+
return value
|
|
60
|
+
.replace(/\\/g, '\\\\')
|
|
61
|
+
.replace(/'/g, "\\'")
|
|
62
|
+
.replace(/\r/g, '\\r')
|
|
63
|
+
.replace(/\n/g, '\\n')
|
|
64
|
+
.replace(/\t/g, '\\t')
|
|
65
|
+
.replace(/\u2028/g, '\\u2028')
|
|
66
|
+
.replace(/\u2029/g, '\\u2029');
|
|
67
|
+
}
|
|
68
|
+
escapeForComment(value) {
|
|
69
|
+
return value
|
|
70
|
+
.replace(/\*\//g, '*\\/')
|
|
71
|
+
.replace(/\/\*/g, '/\\*');
|
|
72
|
+
}
|
|
73
|
+
generateFormContent() {
|
|
74
|
+
const escapedName = this.escapeJsString(this.name);
|
|
75
|
+
const commentName = this.escapeForComment(escapedName);
|
|
76
|
+
const escapedType = this.escapeJsString(this.type);
|
|
77
|
+
return `/**
|
|
78
|
+
* ${commentName} form configuration
|
|
79
|
+
*/
|
|
80
|
+
module.exports = {
|
|
81
|
+
name: '${escapedName}',
|
|
82
|
+
type: '${escapedType}',
|
|
83
|
+
skipValidationOnSave: false,
|
|
84
|
+
editCssClasses: 'row col-md-12',
|
|
85
|
+
viewCssClasses: 'row col-md-offset-1 col-md-10',
|
|
86
|
+
messages: {
|
|
87
|
+
"saving": ["@dmpt-form-saving"],
|
|
88
|
+
"validationFail": ["@dmpt-form-validation-fail-prefix", "@dmpt-form-validation-fail-suffix"],
|
|
89
|
+
"saveSuccess": ["@dmpt-form-save-success"],
|
|
90
|
+
"saveError": ["@dmpt-form-save-error"]
|
|
91
|
+
},
|
|
92
|
+
fields: [
|
|
93
|
+
{
|
|
94
|
+
class: 'Container',
|
|
95
|
+
compClass: 'TextBlockComponent',
|
|
96
|
+
viewOnly: true,
|
|
97
|
+
definition: {
|
|
98
|
+
name: 'titleBlock',
|
|
99
|
+
type: 'h1',
|
|
100
|
+
value: '\${title}'
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
class: 'TextField',
|
|
105
|
+
definition: {
|
|
106
|
+
name: 'title',
|
|
107
|
+
label: 'Title',
|
|
108
|
+
type: 'text',
|
|
109
|
+
required: true
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
class: 'TextArea',
|
|
114
|
+
definition: {
|
|
115
|
+
name: 'description',
|
|
116
|
+
label: 'Description',
|
|
117
|
+
type: 'text'
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
class: "ButtonBarContainer",
|
|
122
|
+
compClass: "ButtonBarContainerComponent",
|
|
123
|
+
definition: {
|
|
124
|
+
fields: [
|
|
125
|
+
{
|
|
126
|
+
class: "SaveButton",
|
|
127
|
+
definition: {
|
|
128
|
+
label: '@save-button',
|
|
129
|
+
cssClasses: 'btn-success'
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
class: "CancelButton",
|
|
134
|
+
definition: {
|
|
135
|
+
label: '@close-button',
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
};
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
getSafeFileName(name) {
|
|
146
|
+
const trimmed = name.trim();
|
|
147
|
+
if (!trimmed) {
|
|
148
|
+
throw new Error('Form field name cannot be empty.');
|
|
149
|
+
}
|
|
150
|
+
const baseName = path.basename(trimmed);
|
|
151
|
+
if (baseName !== trimmed) {
|
|
152
|
+
throw new Error(`Form field name contains path separators: ${name}`);
|
|
153
|
+
}
|
|
154
|
+
if (!/^[A-Za-z0-9._-]+$/.test(baseName)) {
|
|
155
|
+
throw new Error(`Form field name contains invalid characters: ${name}`);
|
|
156
|
+
}
|
|
157
|
+
return baseName;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
exports.FormFieldGenerator = FormFieldGenerator;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Generator, GeneratorOptions } from '../utils/generator';
|
|
2
|
+
export interface MigrationGeneratorOptions extends GeneratorOptions {
|
|
3
|
+
description: string;
|
|
4
|
+
/** Override the clock, used by tests for deterministic file names. */
|
|
5
|
+
now?: Date;
|
|
6
|
+
}
|
|
7
|
+
export declare class MigrationGenerator extends Generator {
|
|
8
|
+
private description;
|
|
9
|
+
private now;
|
|
10
|
+
migrationName: string;
|
|
11
|
+
migrationPath: string;
|
|
12
|
+
constructor(options: MigrationGeneratorOptions);
|
|
13
|
+
generate(): Promise<void>;
|
|
14
|
+
private generateMigrationContent;
|
|
15
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MigrationGenerator = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const generator_1 = require("../utils/generator");
|
|
39
|
+
function slugify(description) {
|
|
40
|
+
return description
|
|
41
|
+
.trim()
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
44
|
+
.replace(/^-+|-+$/g, '');
|
|
45
|
+
}
|
|
46
|
+
function formatTimestamp(now) {
|
|
47
|
+
const pad = (value) => String(value).padStart(2, '0');
|
|
48
|
+
return (`${now.getUTCFullYear()}${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}` +
|
|
49
|
+
`T${pad(now.getUTCHours())}${pad(now.getUTCMinutes())}${pad(now.getUTCSeconds())}`);
|
|
50
|
+
}
|
|
51
|
+
class MigrationGenerator extends generator_1.Generator {
|
|
52
|
+
description;
|
|
53
|
+
now;
|
|
54
|
+
migrationName = '';
|
|
55
|
+
migrationPath = '';
|
|
56
|
+
constructor(options) {
|
|
57
|
+
super(options);
|
|
58
|
+
this.description = options.description;
|
|
59
|
+
this.now = options.now ?? new Date();
|
|
60
|
+
}
|
|
61
|
+
async generate() {
|
|
62
|
+
const slug = slugify(this.description);
|
|
63
|
+
if (!slug) {
|
|
64
|
+
throw new Error(`Cannot derive a migration name from description "${this.description}". Use letters, numbers, and hyphens.`);
|
|
65
|
+
}
|
|
66
|
+
this.migrationName = `${formatTimestamp(this.now)}-${slug}`;
|
|
67
|
+
this.migrationPath = path.join(this.root, 'api', 'migrations', `${this.migrationName}.js`);
|
|
68
|
+
this.writeFile(this.migrationPath, this.generateMigrationContent(slug));
|
|
69
|
+
}
|
|
70
|
+
generateMigrationContent(slug) {
|
|
71
|
+
return `'use strict';
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Data migration: ${slug}
|
|
75
|
+
*
|
|
76
|
+
* Runs once during application lift, before coreBootstrap().
|
|
77
|
+
* Migrations MUST be idempotent: if the process crashes after up() completes
|
|
78
|
+
* but before the run is logged, the migration runs again on the next lift.
|
|
79
|
+
* See the Data Migrations wiki page for the full contract.
|
|
80
|
+
*/
|
|
81
|
+
module.exports = {
|
|
82
|
+
name: '${this.migrationName}',
|
|
83
|
+
|
|
84
|
+
up: async ({ context: sails } = {}) => {
|
|
85
|
+
// TODO: implement the forward data transformation.
|
|
86
|
+
// Example:
|
|
87
|
+
// await sails.models.appconfig.update({ key: 'oldKey' }, { key: 'newKey' });
|
|
88
|
+
throw new Error('Migration ${this.migrationName} is not implemented yet');
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// Optional manual rollback - only ever invoked by operator-driven Umzug
|
|
92
|
+
// usage, never by a normal lift. Omit it entirely if a safe reverse is
|
|
93
|
+
// not possible.
|
|
94
|
+
// down: async ({ context: sails } = {}) => {
|
|
95
|
+
// },
|
|
96
|
+
};
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
exports.MigrationGenerator = MigrationGenerator;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Generator, GeneratorOptions } from '../utils/generator';
|
|
2
|
+
import { RedboxPaths } from '../utils/paths';
|
|
3
|
+
export interface ModelAttribute {
|
|
4
|
+
name: string;
|
|
5
|
+
type: 'string' | 'number' | 'boolean' | 'json' | 'ref';
|
|
6
|
+
required?: boolean;
|
|
7
|
+
unique?: boolean;
|
|
8
|
+
columnType?: string;
|
|
9
|
+
defaultsTo?: string | number | boolean | Record<string, unknown> | unknown[] | null;
|
|
10
|
+
}
|
|
11
|
+
export interface ModelAssociation {
|
|
12
|
+
name: string;
|
|
13
|
+
type: 'belongsTo' | 'hasMany';
|
|
14
|
+
model: string;
|
|
15
|
+
via?: string;
|
|
16
|
+
dominant?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface ModelGeneratorOptions extends GeneratorOptions {
|
|
19
|
+
name: string;
|
|
20
|
+
attributes?: ModelAttribute[];
|
|
21
|
+
associations?: ModelAssociation[];
|
|
22
|
+
identity?: string;
|
|
23
|
+
paths: RedboxPaths;
|
|
24
|
+
}
|
|
25
|
+
export declare class ModelGenerator extends Generator {
|
|
26
|
+
private name;
|
|
27
|
+
private attributes;
|
|
28
|
+
private associations;
|
|
29
|
+
private identity;
|
|
30
|
+
private paths;
|
|
31
|
+
private project;
|
|
32
|
+
constructor(options: ModelGeneratorOptions);
|
|
33
|
+
generate(): Promise<void>;
|
|
34
|
+
private generateModelContent;
|
|
35
|
+
private buildImports;
|
|
36
|
+
private buildDecorators;
|
|
37
|
+
private buildProperties;
|
|
38
|
+
private buildAttributeOptions;
|
|
39
|
+
private formatDefaultValue;
|
|
40
|
+
private tsType;
|
|
41
|
+
private buildAttributesInterface;
|
|
42
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ModelGenerator = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const ts_morph_1 = require("ts-morph");
|
|
39
|
+
const generator_1 = require("../utils/generator");
|
|
40
|
+
const config_helper_1 = require("../utils/config-helper");
|
|
41
|
+
class ModelGenerator extends generator_1.Generator {
|
|
42
|
+
name;
|
|
43
|
+
attributes;
|
|
44
|
+
associations;
|
|
45
|
+
identity;
|
|
46
|
+
paths;
|
|
47
|
+
project;
|
|
48
|
+
constructor(options) {
|
|
49
|
+
super(options);
|
|
50
|
+
this.name = options.name;
|
|
51
|
+
this.attributes = options.attributes || [];
|
|
52
|
+
this.associations = options.associations || [];
|
|
53
|
+
// Identity is the Waterline table/collection name (lowercase)
|
|
54
|
+
this.identity = options.identity || options.name.toLowerCase().replace(/class$/i, '');
|
|
55
|
+
this.paths = options.paths;
|
|
56
|
+
this.project = new ts_morph_1.Project({
|
|
57
|
+
manipulationSettings: {
|
|
58
|
+
quoteKind: ts_morph_1.QuoteKind.Single,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async generate() {
|
|
63
|
+
// Ensure class name ends with 'Class' (convention for Waterline decorator models)
|
|
64
|
+
const className = this.name.endsWith('Class') ? this.name : `${this.name}Class`;
|
|
65
|
+
const baseName = className.replace(/Class$/, '');
|
|
66
|
+
const modelDir = path.join(this.paths.coreTypes, 'src', 'waterline-models');
|
|
67
|
+
const modelPath = path.join(modelDir, `${baseName}.ts`);
|
|
68
|
+
const content = this.generateModelContent(baseName, className);
|
|
69
|
+
this.writeFile(modelPath, content);
|
|
70
|
+
if (!this.dryRun) {
|
|
71
|
+
await (0, config_helper_1.updateModelIndex)({
|
|
72
|
+
project: this.project,
|
|
73
|
+
paths: this.paths,
|
|
74
|
+
root: this.root,
|
|
75
|
+
modelName: baseName,
|
|
76
|
+
dryRun: this.dryRun
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
generateModelContent(baseName, className) {
|
|
81
|
+
const imports = this.buildImports();
|
|
82
|
+
const decorators = this.buildDecorators();
|
|
83
|
+
const properties = this.buildProperties();
|
|
84
|
+
const wlDefExport = `${baseName}WLDef`;
|
|
85
|
+
const attributesInterface = `${baseName}Attributes`;
|
|
86
|
+
const waterlineModelInterface = `${baseName}WaterlineModel`;
|
|
87
|
+
return `/// <reference path="../sails.ts" />
|
|
88
|
+
import { JsonMap } from './types';
|
|
89
|
+
${imports}
|
|
90
|
+
|
|
91
|
+
@Entity('${this.identity}')
|
|
92
|
+
export class ${className} {
|
|
93
|
+
${decorators}${properties}}
|
|
94
|
+
|
|
95
|
+
// Export the Waterline model definition for runtime use
|
|
96
|
+
export const ${wlDefExport} = toWaterlineModelDef(${className});
|
|
97
|
+
|
|
98
|
+
// Type interface for backwards compatibility
|
|
99
|
+
export interface ${attributesInterface} extends Sails.WaterlineAttributes {
|
|
100
|
+
${this.buildAttributesInterface()}}
|
|
101
|
+
|
|
102
|
+
export interface ${waterlineModelInterface} extends Sails.Model<${attributesInterface}> {
|
|
103
|
+
attributes: ${attributesInterface};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
declare global {
|
|
107
|
+
var ${baseName}: ${waterlineModelInterface};
|
|
108
|
+
}
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
buildImports() {
|
|
112
|
+
const decoratorImports = ['Entity', 'Attr', 'toWaterlineModelDef'];
|
|
113
|
+
const hasBelongsTo = this.associations.some(a => a.type === 'belongsTo');
|
|
114
|
+
const hasHasMany = this.associations.some(a => a.type === 'hasMany');
|
|
115
|
+
if (hasBelongsTo)
|
|
116
|
+
decoratorImports.push('BelongsTo');
|
|
117
|
+
if (hasHasMany)
|
|
118
|
+
decoratorImports.push('HasMany');
|
|
119
|
+
return `import { ${decoratorImports.join(', ')} } from '../decorators';`;
|
|
120
|
+
}
|
|
121
|
+
buildDecorators() {
|
|
122
|
+
const lines = [];
|
|
123
|
+
// Build attribute decorators
|
|
124
|
+
for (const attr of this.attributes) {
|
|
125
|
+
const opts = this.buildAttributeOptions(attr);
|
|
126
|
+
lines.push(` @Attr(${opts})`);
|
|
127
|
+
lines.push(` public ${attr.name}${attr.required ? '!' : '?'}: ${this.tsType(attr.type)};`);
|
|
128
|
+
lines.push('');
|
|
129
|
+
}
|
|
130
|
+
// Build association decorators
|
|
131
|
+
for (const assoc of this.associations) {
|
|
132
|
+
if (assoc.type === 'belongsTo') {
|
|
133
|
+
lines.push(` @BelongsTo('${assoc.model}')`);
|
|
134
|
+
lines.push(` public ${assoc.name}?: string | number;`);
|
|
135
|
+
}
|
|
136
|
+
else if (assoc.type === 'hasMany') {
|
|
137
|
+
if (!assoc.via) {
|
|
138
|
+
throw new Error(`Missing 'via' for hasMany association '${assoc.name}'.`);
|
|
139
|
+
}
|
|
140
|
+
const opts = assoc.dominant ? `, { dominant: ${assoc.dominant} }` : '';
|
|
141
|
+
lines.push(` @HasMany('${assoc.model}', '${assoc.via}'${opts})`);
|
|
142
|
+
lines.push(` public ${assoc.name}?: unknown[];`);
|
|
143
|
+
}
|
|
144
|
+
lines.push('');
|
|
145
|
+
}
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
148
|
+
buildProperties() {
|
|
149
|
+
// For now, decorators include properties; this can be extended later
|
|
150
|
+
return '';
|
|
151
|
+
}
|
|
152
|
+
buildAttributeOptions(attr) {
|
|
153
|
+
const opts = [];
|
|
154
|
+
opts.push(`type: '${attr.type === 'ref' ? 'ref' : attr.type}'`);
|
|
155
|
+
if (attr.required)
|
|
156
|
+
opts.push('required: true');
|
|
157
|
+
if (attr.unique)
|
|
158
|
+
opts.push('unique: true');
|
|
159
|
+
if (attr.columnType)
|
|
160
|
+
opts.push(`columnType: '${attr.columnType}'`);
|
|
161
|
+
if (attr.defaultsTo !== undefined) {
|
|
162
|
+
const val = this.formatDefaultValue(attr.defaultsTo);
|
|
163
|
+
opts.push(`defaultsTo: ${val}`);
|
|
164
|
+
}
|
|
165
|
+
return `{ ${opts.join(', ')} }`;
|
|
166
|
+
}
|
|
167
|
+
formatDefaultValue(value) {
|
|
168
|
+
if (value === null) {
|
|
169
|
+
return 'null';
|
|
170
|
+
}
|
|
171
|
+
if (typeof value === 'string') {
|
|
172
|
+
return `'${value}'`;
|
|
173
|
+
}
|
|
174
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
175
|
+
return String(value);
|
|
176
|
+
}
|
|
177
|
+
return JSON.stringify(value);
|
|
178
|
+
}
|
|
179
|
+
tsType(waterlineType) {
|
|
180
|
+
switch (waterlineType) {
|
|
181
|
+
case 'string': return 'string';
|
|
182
|
+
case 'number': return 'number';
|
|
183
|
+
case 'boolean': return 'boolean';
|
|
184
|
+
case 'json': return 'Record<string, unknown>';
|
|
185
|
+
case 'ref': return 'unknown';
|
|
186
|
+
default: return 'unknown';
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
buildAttributesInterface() {
|
|
190
|
+
const lines = [];
|
|
191
|
+
for (const attr of this.attributes) {
|
|
192
|
+
const optional = attr.required ? '' : '?';
|
|
193
|
+
lines.push(` ${attr.name}${optional}: ${this.tsType(attr.type)};`);
|
|
194
|
+
}
|
|
195
|
+
for (const assoc of this.associations) {
|
|
196
|
+
if (assoc.type === 'belongsTo') {
|
|
197
|
+
lines.push(` ${assoc.name}?: string | number;`);
|
|
198
|
+
}
|
|
199
|
+
else if (assoc.type === 'hasMany') {
|
|
200
|
+
lines.push(` ${assoc.name}?: unknown[];`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return lines.join('\n');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
exports.ModelGenerator = ModelGenerator;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Generator, GeneratorOptions } from '../utils/generator';
|
|
2
|
+
import { RedboxPaths } from '../utils/paths';
|
|
3
|
+
export interface ServiceGeneratorOptions extends GeneratorOptions {
|
|
4
|
+
name: string;
|
|
5
|
+
methods?: string[];
|
|
6
|
+
paths: RedboxPaths;
|
|
7
|
+
}
|
|
8
|
+
export declare class ServiceGenerator extends Generator {
|
|
9
|
+
private name;
|
|
10
|
+
private methods;
|
|
11
|
+
private paths;
|
|
12
|
+
private project;
|
|
13
|
+
constructor(options: ServiceGeneratorOptions);
|
|
14
|
+
generate(): Promise<void>;
|
|
15
|
+
private generateServiceContent;
|
|
16
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ServiceGenerator = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const ts_morph_1 = require("ts-morph");
|
|
39
|
+
const generator_1 = require("../utils/generator");
|
|
40
|
+
const config_helper_1 = require("../utils/config-helper");
|
|
41
|
+
class ServiceGenerator extends generator_1.Generator {
|
|
42
|
+
name;
|
|
43
|
+
methods;
|
|
44
|
+
paths;
|
|
45
|
+
project;
|
|
46
|
+
constructor(options) {
|
|
47
|
+
super(options);
|
|
48
|
+
this.name = options.name;
|
|
49
|
+
this.methods = options.methods || [];
|
|
50
|
+
this.paths = options.paths;
|
|
51
|
+
this.project = new ts_morph_1.Project({
|
|
52
|
+
manipulationSettings: {
|
|
53
|
+
quoteKind: ts_morph_1.QuoteKind.Single,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async generate() {
|
|
58
|
+
const serviceName = this.name.endsWith('Service') ? this.name : `${this.name}Service`;
|
|
59
|
+
const className = this.name.endsWith('Service')
|
|
60
|
+
? this.name.replace(/Service$/, '')
|
|
61
|
+
: this.name;
|
|
62
|
+
const serviceDir = path.join(this.paths.coreTypes, 'src', 'services');
|
|
63
|
+
const servicePath = path.join(serviceDir, `${serviceName}.ts`);
|
|
64
|
+
const content = this.generateServiceContent(className, serviceName);
|
|
65
|
+
this.writeFile(servicePath, content);
|
|
66
|
+
if (!this.dryRun) {
|
|
67
|
+
await (0, config_helper_1.updateServiceIndex)({
|
|
68
|
+
project: this.project,
|
|
69
|
+
paths: this.paths,
|
|
70
|
+
root: this.root,
|
|
71
|
+
serviceName: serviceName,
|
|
72
|
+
className: className,
|
|
73
|
+
dryRun: this.dryRun
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
generateServiceContent(className, serviceName) {
|
|
78
|
+
const methodsCode = this.methods.map(method => `
|
|
79
|
+
public async ${method}(..._args: unknown[]): Promise<Record<string, unknown>> {
|
|
80
|
+
sails.log.verbose(\`\${this.logHeader} ${method} called\`);
|
|
81
|
+
return { message: 'Method ${method} in ${serviceName}' };
|
|
82
|
+
}`).join('\n');
|
|
83
|
+
const exportedMethods = ['init', ...this.methods].map(m => `'${m}'`).join(',\n ');
|
|
84
|
+
return `import { Services as services } from '../CoreService';
|
|
85
|
+
|
|
86
|
+
export namespace Services {
|
|
87
|
+
export class ${className}Service extends services.Core.Service {
|
|
88
|
+
|
|
89
|
+
constructor() {
|
|
90
|
+
super();
|
|
91
|
+
this.logHeader = "${serviceName}::";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
protected override _exportedMethods: string[] = [
|
|
95
|
+
${exportedMethods}
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
public init(): void {
|
|
99
|
+
// Initialize service here
|
|
100
|
+
}
|
|
101
|
+
${methodsCode}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
declare global {
|
|
106
|
+
let ${className}Service: Services.${className}Service;
|
|
107
|
+
}
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.ServiceGenerator = ServiceGenerator;
|