@researchdatabox/redbox-dev-tools 0.0.1-beta.10173

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.
Files changed (106) hide show
  1. package/README.md +255 -0
  2. package/bin/cli.js +3 -0
  3. package/bin/run-hook-mocha.js +3 -0
  4. package/bin/run-hook-tsc.js +3 -0
  5. package/bin/wrapper-webpack.js +168 -0
  6. package/config/tsconfig.base.json +16 -0
  7. package/dist/bin/run-hook-mocha.d.ts +1 -0
  8. package/dist/bin/run-hook-mocha.js +49 -0
  9. package/dist/bin/run-hook-tsc.d.ts +1 -0
  10. package/dist/bin/run-hook-tsc.js +19 -0
  11. package/dist/cli-parsers.d.ts +9 -0
  12. package/dist/cli-parsers.js +131 -0
  13. package/dist/cli.d.ts +1 -0
  14. package/dist/cli.js +491 -0
  15. package/dist/commands/form-config.d.ts +6 -0
  16. package/dist/commands/form-config.js +404 -0
  17. package/dist/completion/index.d.ts +1 -0
  18. package/dist/completion/index.js +218 -0
  19. package/dist/generators/add-method.d.ts +27 -0
  20. package/dist/generators/add-method.js +235 -0
  21. package/dist/generators/angular-app.d.ts +24 -0
  22. package/dist/generators/angular-app.js +342 -0
  23. package/dist/generators/angular-service.d.ts +21 -0
  24. package/dist/generators/angular-service.js +154 -0
  25. package/dist/generators/controller.d.ts +40 -0
  26. package/dist/generators/controller.js +195 -0
  27. package/dist/generators/form-component.d.ts +46 -0
  28. package/dist/generators/form-component.js +906 -0
  29. package/dist/generators/form-field.d.ts +15 -0
  30. package/dist/generators/form-field.js +160 -0
  31. package/dist/generators/migration.d.ts +15 -0
  32. package/dist/generators/migration.js +100 -0
  33. package/dist/generators/model.d.ts +42 -0
  34. package/dist/generators/model.js +206 -0
  35. package/dist/generators/service.d.ts +16 -0
  36. package/dist/generators/service.js +111 -0
  37. package/dist/runtime-resolver.d.ts +3 -0
  38. package/dist/runtime-resolver.js +27 -0
  39. package/dist/templates/hook-archetype.d.ts +12 -0
  40. package/dist/templates/hook-archetype.js +186 -0
  41. package/dist/testing.d.ts +7 -0
  42. package/dist/testing.js +53 -0
  43. package/dist/utils/config-helper.d.ts +71 -0
  44. package/dist/utils/config-helper.js +574 -0
  45. package/dist/utils/generator.d.ts +10 -0
  46. package/dist/utils/generator.js +68 -0
  47. package/dist/utils/hook-tsconfig.d.ts +9 -0
  48. package/dist/utils/hook-tsconfig.js +104 -0
  49. package/dist/utils/paths.d.ts +10 -0
  50. package/dist/utils/paths.js +60 -0
  51. package/index.js +4 -0
  52. package/package.json +85 -0
  53. package/runtime-resolver.d.ts +1 -0
  54. package/runtime-resolver.js +3 -0
  55. package/skills/redbox-angular-apps/SKILL.md +42 -0
  56. package/skills/redbox-angular-services/SKILL.md +29 -0
  57. package/skills/redbox-controllers/SKILL.md +65 -0
  58. package/skills/redbox-feature-design-planner/SKILL.md +169 -0
  59. package/skills/redbox-feature-implementation-review/SKILL.md +117 -0
  60. package/skills/redbox-form-components/SKILL.md +114 -0
  61. package/skills/redbox-form-config/SKILL.md +34 -0
  62. package/skills/redbox-hook-development/SKILL.md +66 -0
  63. package/skills/redbox-services/SKILL.md +54 -0
  64. package/skills/redbox-test-verification/SKILL.md +38 -0
  65. package/skills/redbox-testing/SKILL.md +64 -0
  66. package/skills/redbox-web-verification/SKILL.md +71 -0
  67. package/skills/redbox-web-verification/redbox-angular-apps/SKILL.md +48 -0
  68. package/skills/redbox-web-verification/redbox-angular-services/SKILL.md +30 -0
  69. package/skills/redbox-web-verification/redbox-controllers/SKILL.md +56 -0
  70. package/skills/redbox-web-verification/redbox-form-config/SKILL.md +29 -0
  71. package/skills/redbox-web-verification/redbox-hook-development/SKILL.md +61 -0
  72. package/skills/redbox-web-verification/redbox-services/SKILL.md +45 -0
  73. package/skills/redbox-web-verification/redbox-testing/SKILL.md +53 -0
  74. package/skills/redbox-web-verification/redbox-web-verification/SKILL.md +47 -0
  75. package/templates/hook-archetype/standard/.gitignore.hbs +4 -0
  76. package/templates/hook-archetype/standard/.npmignore.hbs +3 -0
  77. package/templates/hook-archetype/standard/.npmrc.hbs +6 -0
  78. package/templates/hook-archetype/standard/.nvmrc.hbs +1 -0
  79. package/templates/hook-archetype/standard/AGENTS.md.hbs +34 -0
  80. package/templates/hook-archetype/standard/Dockerfile.hbs +28 -0
  81. package/templates/hook-archetype/standard/README.md.hbs +39 -0
  82. package/templates/hook-archetype/standard/assets/fonts/.gitkeep +1 -0
  83. package/templates/hook-archetype/standard/language-defaults/.gitkeep +1 -0
  84. package/templates/hook-archetype/standard/package.json.hbs +40 -0
  85. package/templates/hook-archetype/standard/src/api/controllers/ExampleController.ts.hbs +21 -0
  86. package/templates/hook-archetype/standard/src/api/controllers/index.ts.hbs +23 -0
  87. package/templates/hook-archetype/standard/src/api/services/ExampleService.ts.hbs +22 -0
  88. package/templates/hook-archetype/standard/src/api/services/index.ts.hbs +23 -0
  89. package/templates/hook-archetype/standard/src/config/brandingConfigurationDefaults.ts.hbs +9 -0
  90. package/templates/hook-archetype/standard/src/config/reusableFormDefinitions.ts.hbs +6 -0
  91. package/templates/hook-archetype/standard/src/form-config/default-1.0-draft.ts.hbs +20 -0
  92. package/templates/hook-archetype/standard/src/form-config/index.ts.hbs +8 -0
  93. package/templates/hook-archetype/standard/src/index.ts.hbs +55 -0
  94. package/templates/hook-archetype/standard/support/bootstrap-data/.gitkeep +1 -0
  95. package/templates/hook-archetype/standard/support/development/docker-compose.yml.hbs +75 -0
  96. package/templates/hook-archetype/standard/support/integration-testing/docker-compose.mocha.yml.hbs +84 -0
  97. package/templates/hook-archetype/standard/support/integration-testing/run-mocha-hook.sh.hbs +9 -0
  98. package/templates/hook-archetype/standard/test/integration/hook-registration.test.ts.hbs +13 -0
  99. package/templates/hook-archetype/standard/test/support/globals.ts.hbs +40 -0
  100. package/templates/hook-archetype/standard/test/tsconfig.json.hbs +8 -0
  101. package/templates/hook-archetype/standard/test/unit/.mocharc.cjs.hbs +9 -0
  102. package/templates/hook-archetype/standard/test/unit/index.test.ts.hbs +40 -0
  103. package/templates/hook-archetype/standard/tsconfig.json.hbs +15 -0
  104. package/templates/hook-archetype/standard/views/.gitkeep +1 -0
  105. package/testing.d.ts +1 -0
  106. package/testing.js +3 -0
package/dist/cli.js ADDED
@@ -0,0 +1,491 @@
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
+ const child_process_1 = require("child_process");
37
+ const commander_1 = require("commander");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const paths_1 = require("./utils/paths");
41
+ const hook_archetype_1 = require("./templates/hook-archetype");
42
+ const form_config_1 = require("./commands/form-config");
43
+ const completion_1 = require("./completion");
44
+ const cli_parsers_1 = require("./cli-parsers");
45
+ const program = new commander_1.Command();
46
+ // List of dependencies that are common in our legacy hooks but should not be in modern hook packages as they are supplied by redbox-core
47
+ const forbiddenHookDeps = ['axios', 'rxjs', 'lodash', 'mocha', 'chai', 'ts-node', 'typescript'];
48
+ const allowedSharedHookDeps = {
49
+ dependencies: new Set(),
50
+ devDependencies: new Set(['@researchdatabox/redbox-dev-tools']),
51
+ peerDependencies: new Set(['@researchdatabox/redbox-core']),
52
+ };
53
+ function readPackageJson(pkgPath) {
54
+ return JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
55
+ }
56
+ function writePackageJson(pkgPath, pkg) {
57
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
58
+ }
59
+ function isForbiddenSharedHookDep(section, depName) {
60
+ if (!depName.startsWith('@researchdatabox/')) {
61
+ return false;
62
+ }
63
+ return !allowedSharedHookDeps[section].has(depName);
64
+ }
65
+ function migrateHookDependencyContract(pkg) {
66
+ const nextPkg = { ...pkg };
67
+ nextPkg.dependencies = { ...(pkg.dependencies ?? {}) };
68
+ nextPkg.devDependencies = { ...(pkg.devDependencies ?? {}) };
69
+ nextPkg.peerDependencies = { ...(pkg.peerDependencies ?? {}) };
70
+ for (const section of Object.keys(allowedSharedHookDeps)) {
71
+ for (const depName of Object.keys(nextPkg[section] ?? {})) {
72
+ if (isForbiddenSharedHookDep(section, depName)) {
73
+ delete nextPkg[section][depName];
74
+ }
75
+ }
76
+ }
77
+ for (const depName of forbiddenHookDeps) {
78
+ delete nextPkg.dependencies[depName];
79
+ delete nextPkg.devDependencies[depName];
80
+ delete nextPkg.peerDependencies[depName];
81
+ }
82
+ nextPkg.peerDependencies['@researchdatabox/redbox-core'] = nextPkg.peerDependencies['@researchdatabox/redbox-core'] ?? '*';
83
+ nextPkg.devDependencies['@researchdatabox/redbox-dev-tools'] = nextPkg.devDependencies['@researchdatabox/redbox-dev-tools'] ?? '*';
84
+ return nextPkg;
85
+ }
86
+ function findForbiddenHookDeps(pkg) {
87
+ const sections = ['dependencies', 'devDependencies', 'peerDependencies'];
88
+ const findings = [];
89
+ for (const section of sections) {
90
+ const deps = pkg[section] ?? {};
91
+ for (const depName of Object.keys(deps)) {
92
+ if (isForbiddenSharedHookDep(section, depName)) {
93
+ findings.push(`${section}.${depName}`);
94
+ }
95
+ }
96
+ for (const depName of forbiddenHookDeps) {
97
+ if (deps[depName] != null) {
98
+ findings.push(`${section}.${depName}`);
99
+ }
100
+ }
101
+ }
102
+ return findings;
103
+ }
104
+ program
105
+ .name('redbox-dev-tools')
106
+ .description('CLI for ReDBox hook development and code generation')
107
+ .version('1.0.0')
108
+ .option('--root <path>', 'ReDBox repo root directory (required for generators)')
109
+ .option('--core-types-root <path>', 'Override core-types package root')
110
+ .option('--angular-root <path>', 'Override angular projects root')
111
+ .option('--dry-run', 'Print intended changes without writing files', false);
112
+ function resolveSkillsDir() {
113
+ return path.resolve(__dirname, '..', 'skills');
114
+ }
115
+ program
116
+ .command('completion <shell>')
117
+ .description('Output shell completion script for bash, zsh, fish, or powershell')
118
+ .action((shell) => {
119
+ const normalized = shell.toLowerCase();
120
+ try {
121
+ const script = (0, completion_1.generateCompletionScript)(normalized);
122
+ process.stdout.write(script);
123
+ }
124
+ catch (error) {
125
+ console.error(`\n❌ Error: ${error.message}\n`);
126
+ process.exit(1);
127
+ }
128
+ });
129
+ (0, form_config_1.registerMigrateFormConfigCommand)(program);
130
+ (0, form_config_1.registerMigrateDataClassificationCommand)(program);
131
+ (0, form_config_1.registerMigrateFigshareConfigCommand)(program);
132
+ (0, form_config_1.registerClientFormConfigCommand)(program);
133
+ (0, form_config_1.registerQuestionTreeDiagramCommand)(program);
134
+ program
135
+ .command('init [name]')
136
+ .description('Initialize a new ReDBox hook project from the standard archetype')
137
+ .option('--template <template>', 'Archetype template name', 'standard')
138
+ .option('--description <description>', 'Project description')
139
+ .option('--force', 'Overwrite existing archetype files', false)
140
+ .action((name, options) => {
141
+ if (options.template !== 'standard') {
142
+ console.error(`❌ Unsupported template: ${options.template}. Supported templates: standard`);
143
+ process.exit(1);
144
+ }
145
+ const cwd = process.cwd();
146
+ const packageName = name || path.basename(cwd);
147
+ console.log(`🚀 Initializing ReDBox hook archetype for ${packageName}...\n`);
148
+ const result = (0, hook_archetype_1.generateHookArchetype)({
149
+ cwd,
150
+ packageName,
151
+ description: options.description,
152
+ force: options.force,
153
+ templateName: options.template,
154
+ });
155
+ for (const createdPath of result.created) {
156
+ console.log(`✅ Created ${createdPath}`);
157
+ }
158
+ for (const skippedPath of result.skipped) {
159
+ console.log(`ℹ️ Skipped existing ${skippedPath}`);
160
+ }
161
+ console.log('\n✨ Archetype ready!\n');
162
+ console.log('Next steps:');
163
+ console.log(' 1. Install dependencies: npm install');
164
+ console.log(' 2. Compile TypeScript: npm run compile');
165
+ console.log(' 3. Run unit tests: npm run test:unit');
166
+ console.log(' 4. Start tailoring the hook in src/, test/, and support/\n');
167
+ });
168
+ program
169
+ .command('check')
170
+ .description('Validate that a hook uses the shared Redbox dependency contract')
171
+ .action(() => {
172
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
173
+ if (!fs.existsSync(packageJsonPath)) {
174
+ console.error('❌ package.json not found in the current directory.');
175
+ process.exit(1);
176
+ }
177
+ const pkg = readPackageJson(packageJsonPath);
178
+ const findings = findForbiddenHookDeps(pkg);
179
+ if (findings.length === 0) {
180
+ console.log('✅ Hook dependency contract looks good.');
181
+ return;
182
+ }
183
+ console.error('❌ Hook dependency contract violations found:');
184
+ for (const finding of findings) {
185
+ console.error(` - ${finding}`);
186
+ }
187
+ console.error('Shared runtime dependencies should come from @researchdatabox/redbox-core, and shared toolchain dependencies should come from @researchdatabox/redbox-dev-tools.');
188
+ process.exit(1);
189
+ });
190
+ program
191
+ .command('migrate-hook-dependencies')
192
+ .description('Rewrite the current hook package.json to the minimal shared dependency contract')
193
+ .action(() => {
194
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
195
+ if (!fs.existsSync(packageJsonPath)) {
196
+ console.error('❌ package.json not found in the current directory.');
197
+ process.exit(1);
198
+ }
199
+ const pkg = readPackageJson(packageJsonPath);
200
+ const migratedPkg = migrateHookDependencyContract(pkg);
201
+ writePackageJson(packageJsonPath, migratedPkg);
202
+ console.log('✅ Updated package.json to use the minimal shared hook dependency contract.');
203
+ });
204
+ const generate = program.command('generate').alias('g').description('Generate code components');
205
+ generate
206
+ .command('controller <name>')
207
+ .description('Generate a new ReDBox controller')
208
+ .option('--actions <actions>', 'Comma-separated list of actions', val => val.split(','))
209
+ .option('--webservice', 'Generate as a webservice controller', false)
210
+ .option('--class-name <className>', 'Override controller class name (defaults to name without Controller suffix)')
211
+ .option('--route <route>', 'Single route (deprecated, use --routes)')
212
+ .option('--routes <routes>', 'Action-to-route mappings: action:verb:path[:role1:role2],... (e.g., list:get:/api/items:Admin:Researcher,delete:delete:/api/items/:id:Admin)', cli_parsers_1.parseRoutes)
213
+ .option('--nav <nav>', 'Navigation mappings: action:type[:containerId]:labelKey[:itemId],... (e.g., list:menu:plan:create-rdmp:plan-create)', cli_parsers_1.parseNavMappings)
214
+ .option('--lang <lang>', 'Language defaults: key=value[:lang],... (e.g., menu-custom=Custom menu:en)', cli_parsers_1.parseLanguageDefaults)
215
+ .option('--auth <roles>', 'Default roles for auth rules (can be overridden per-route)', val => val.split(','))
216
+ .action(async (name, options) => {
217
+ try {
218
+ const globalOptions = program.opts();
219
+ const paths = (0, paths_1.resolvePaths)(globalOptions);
220
+ const { ControllerGenerator } = require('./generators/controller');
221
+ const generator = new ControllerGenerator({
222
+ name,
223
+ actions: options.actions,
224
+ webservice: options.webservice,
225
+ route: options.route,
226
+ routes: options.routes,
227
+ auth: options.auth,
228
+ navigation: options.nav,
229
+ languageDefaults: options.lang,
230
+ className: options.className,
231
+ dryRun: globalOptions.dryRun,
232
+ root: paths.root,
233
+ paths,
234
+ });
235
+ console.log(`\n🛠️ Generating controller: ${name}...\n`);
236
+ await generator.generate();
237
+ console.log('\n✅ Done!\n');
238
+ }
239
+ catch (error) {
240
+ console.error(`\n❌ Error: ${error.message}\n`);
241
+ process.exit(1);
242
+ }
243
+ });
244
+ generate
245
+ .command('service <name>')
246
+ .description('Generate a new ReDBox service')
247
+ .option('--methods <methods>', 'Comma-separated list of methods', val => val.split(','))
248
+ .action(async (name, options) => {
249
+ try {
250
+ const globalOptions = program.opts();
251
+ const paths = (0, paths_1.resolvePaths)(globalOptions);
252
+ const { ServiceGenerator } = require('./generators/service');
253
+ const generator = new ServiceGenerator({
254
+ name,
255
+ methods: options.methods,
256
+ dryRun: globalOptions.dryRun,
257
+ root: paths.root,
258
+ paths,
259
+ });
260
+ console.log(`\n🛠️ Generating service: ${name}...\n`);
261
+ await generator.generate();
262
+ console.log('\n✅ Done!\n');
263
+ }
264
+ catch (error) {
265
+ console.error(`\n❌ Error: ${error.message}\n`);
266
+ process.exit(1);
267
+ }
268
+ });
269
+ generate
270
+ .command('method')
271
+ .description('Add a method to an existing controller or service')
272
+ .requiredOption('--file <path>', 'Path to existing controller or service file')
273
+ .requiredOption('--method <name>', 'Method name to add')
274
+ .option('--route <route>', 'Route path (for controllers)')
275
+ .option('--http <verb>', 'HTTP verb (GET, POST, etc.)')
276
+ .option('--auth <roles>', 'Comma-separated list of roles', val => val.split(','))
277
+ .option('--nav <nav>', 'Navigation mappings: type[:containerId]:labelKey[:itemId],... (e.g., menu:plan:create-rdmp:plan-create)', cli_parsers_1.parseNavMappings)
278
+ .option('--lang <lang>', 'Language defaults: key=value[:lang],... (e.g., menu-custom=Custom menu:en)', cli_parsers_1.parseLanguageDefaults)
279
+ .action(async (options) => {
280
+ try {
281
+ const globalOptions = program.opts();
282
+ const paths = (0, paths_1.resolvePaths)(globalOptions);
283
+ const { AddMethodGenerator } = require('./generators/add-method');
284
+ const generator = new AddMethodGenerator({
285
+ file: options.file,
286
+ method: options.method,
287
+ route: options.route,
288
+ http: options.http,
289
+ auth: options.auth,
290
+ navigation: options.nav,
291
+ languageDefaults: options.lang,
292
+ dryRun: globalOptions.dryRun,
293
+ root: paths.root,
294
+ paths,
295
+ });
296
+ console.log(`\n🛠️ Adding method ${options.method} to ${options.file}...\n`);
297
+ await generator.generate();
298
+ console.log('\n✅ Done!\n');
299
+ }
300
+ catch (error) {
301
+ console.error(`\n❌ Error: ${error.message}\n`);
302
+ process.exit(1);
303
+ }
304
+ });
305
+ generate
306
+ .command('angular-app <name>')
307
+ .description('Scaffold a new Angular app for embedded EJS usage')
308
+ .requiredOption('--ejs-view <view>', 'EJS view name')
309
+ .option('--auth <roles>', 'Comma-separated list of roles', val => val.split(','))
310
+ .action(async (name, options) => {
311
+ try {
312
+ const globalOptions = program.opts();
313
+ const paths = (0, paths_1.resolvePaths)(globalOptions);
314
+ const { AngularAppGenerator } = require('./generators/angular-app');
315
+ const generator = new AngularAppGenerator({
316
+ name,
317
+ ejsView: options.ejsView,
318
+ auth: options.auth,
319
+ dryRun: globalOptions.dryRun,
320
+ root: paths.root,
321
+ paths,
322
+ });
323
+ console.log(`\n🛠️ Generating Angular app: ${name}...\n`);
324
+ await generator.generate();
325
+ console.log('\n✅ Done!\n');
326
+ }
327
+ catch (error) {
328
+ console.error(`\n❌ Error: ${error.message}\n`);
329
+ process.exit(1);
330
+ }
331
+ });
332
+ generate
333
+ .command('angular-service <name>')
334
+ .description('Generate a new Angular service extending HttpClientService')
335
+ .requiredOption('--app <app>', 'Target Angular app name')
336
+ .option('--methods <methods>', 'Comma-separated list of methods', val => val.split(','))
337
+ .action(async (name, options) => {
338
+ try {
339
+ const globalOptions = program.opts();
340
+ const paths = (0, paths_1.resolvePaths)(globalOptions);
341
+ const { AngularServiceGenerator } = require('./generators/angular-service');
342
+ const generator = new AngularServiceGenerator({
343
+ name,
344
+ app: options.app,
345
+ methods: options.methods,
346
+ dryRun: globalOptions.dryRun,
347
+ root: paths.root,
348
+ paths,
349
+ });
350
+ console.log(`\n🛠️ Generating Angular service: ${name} for app ${options.app}...\n`);
351
+ await generator.generate();
352
+ console.log('\n✅ Done!\n');
353
+ }
354
+ catch (error) {
355
+ console.error(`\n❌ Error: ${error.message}\n`);
356
+ process.exit(1);
357
+ }
358
+ });
359
+ generate
360
+ .command('form-component <name>')
361
+ .description('Generate scaffold for a new form component across Angular and sails-ng-common')
362
+ .option('--app <app>', 'Target Angular app name', 'form')
363
+ .option('--with-service', 'Also generate a companion Angular HttpClient service', false)
364
+ .action(async (name, options) => {
365
+ try {
366
+ const globalOptions = program.opts();
367
+ const paths = (0, paths_1.resolvePaths)(globalOptions);
368
+ const { FormComponentGenerator } = require('./generators/form-component');
369
+ const generator = new FormComponentGenerator({
370
+ name,
371
+ app: options.app,
372
+ withService: options.withService,
373
+ dryRun: globalOptions.dryRun,
374
+ root: paths.root,
375
+ paths,
376
+ });
377
+ console.log(`\n🛠️ Generating form component scaffold: ${name}...\n`);
378
+ await generator.generate();
379
+ console.log('\n✅ Done!\n');
380
+ }
381
+ catch (error) {
382
+ console.error(`\n❌ Error: ${error.message}\n`);
383
+ process.exit(1);
384
+ }
385
+ });
386
+ generate
387
+ .command('migration <description>')
388
+ .description('Generate an app-local data migration in api/migrations/ (runs at lift, before coreBootstrap)')
389
+ .action(async (description) => {
390
+ try {
391
+ const globalOptions = program.opts();
392
+ const paths = (0, paths_1.resolvePaths)(globalOptions);
393
+ const { MigrationGenerator } = require('./generators/migration');
394
+ const generator = new MigrationGenerator({
395
+ description,
396
+ dryRun: globalOptions.dryRun,
397
+ root: paths.root,
398
+ });
399
+ console.log(`\n🛠️ Generating data migration: ${description}...\n`);
400
+ await generator.generate();
401
+ console.log(`\n✅ Done! Created migration '${generator.migrationName}'.\n`);
402
+ console.log('Next steps:');
403
+ console.log(` 1. Implement up() in ${path.relative(paths.root, generator.migrationPath)} (remove the placeholder throw)`);
404
+ console.log(' 2. Make sure up() is idempotent - it can run more than once');
405
+ console.log(' 3. Test by lifting the app, then verify the Migration table contains the new name\n');
406
+ }
407
+ catch (error) {
408
+ console.error(`\n❌ Error: ${error.message}\n`);
409
+ process.exit(1);
410
+ }
411
+ });
412
+ generate
413
+ .command('form-field <name>')
414
+ .description('Generate a new form field DSL configuration')
415
+ .requiredOption('--type <type>', 'Record type')
416
+ .action(async (name, options) => {
417
+ try {
418
+ const globalOptions = program.opts();
419
+ const paths = (0, paths_1.resolvePaths)(globalOptions);
420
+ const { FormFieldGenerator } = require('./generators/form-field');
421
+ const generator = new FormFieldGenerator({
422
+ name,
423
+ type: options.type,
424
+ dryRun: globalOptions.dryRun,
425
+ root: paths.root,
426
+ });
427
+ console.log(`\n🛠️ Generating form field config: ${name}...\n`);
428
+ await generator.generate();
429
+ console.log('\n✅ Done!\n');
430
+ }
431
+ catch (error) {
432
+ console.error(`\n❌ Error: ${error.message}\n`);
433
+ process.exit(1);
434
+ }
435
+ });
436
+ program
437
+ .command('install-skills')
438
+ .alias('skills')
439
+ .description('Install bundled agent skills using the vercel-labs skills CLI')
440
+ .allowUnknownOption(true)
441
+ .allowExcessArguments(true)
442
+ .action(() => {
443
+ const skillsDir = resolveSkillsDir();
444
+ if (!fs.existsSync(skillsDir)) {
445
+ console.error(`Skills directory not found: ${skillsDir}`);
446
+ process.exit(1);
447
+ }
448
+ const rawArgs = process.argv.slice(2);
449
+ const cmdIndex = rawArgs.findIndex(arg => arg === 'install-skills' || arg === 'skills');
450
+ const forwardedArgs = cmdIndex === -1 ? [] : rawArgs.slice(cmdIndex + 1);
451
+ const result = (0, child_process_1.spawnSync)('npx', ['skills', 'add', skillsDir, ...forwardedArgs], {
452
+ stdio: 'inherit',
453
+ shell: process.platform === 'win32',
454
+ });
455
+ if (result.error) {
456
+ console.error('Failed to run npx skills:', result.error.message);
457
+ process.exit(1);
458
+ }
459
+ process.exit(result.status === null ? 1 : result.status);
460
+ });
461
+ generate
462
+ .command('model <name>')
463
+ .description('Generate a new Waterline model with decorator-based definition')
464
+ .option('--identity <identity>', 'Waterline identity (table name), defaults to lowercase name')
465
+ .option('--attrs <attrs>', 'Comma-separated list of attributes in format name:type[:required][:unique]', cli_parsers_1.parseAttributes)
466
+ .option('--belongs-to <associations>', 'Comma-separated belongsTo associations in format name:model', cli_parsers_1.parseBelongsTo)
467
+ .option('--has-many <associations>', 'Comma-separated hasMany associations in format name:model:via[:dominant]', cli_parsers_1.parseHasMany)
468
+ .action(async (name, options) => {
469
+ try {
470
+ const globalOptions = program.opts();
471
+ const paths = (0, paths_1.resolvePaths)(globalOptions);
472
+ const { ModelGenerator } = require('./generators/model');
473
+ const generator = new ModelGenerator({
474
+ name,
475
+ identity: options.identity,
476
+ attributes: options.attrs,
477
+ associations: [...(options.belongsTo || []), ...(options.hasMany || [])],
478
+ dryRun: globalOptions.dryRun,
479
+ root: paths.root,
480
+ paths,
481
+ });
482
+ console.log(`\n🛠️ Generating Waterline model: ${name}...\n`);
483
+ await generator.generate();
484
+ console.log('\n✅ Done!\n');
485
+ }
486
+ catch (error) {
487
+ console.error(`\n❌ Error: ${error.message}\n`);
488
+ process.exit(1);
489
+ }
490
+ });
491
+ program.parse();
@@ -0,0 +1,6 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerMigrateFormConfigCommand(program: Command): void;
3
+ export declare function registerMigrateDataClassificationCommand(program: Command): void;
4
+ export declare function registerClientFormConfigCommand(program: Command): void;
5
+ export declare function registerQuestionTreeDiagramCommand(program: Command): void;
6
+ export declare function registerMigrateFigshareConfigCommand(program: Command): void;