@researchdatabox/redbox-dev-tools 0.0.1-beta.8936
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 +255 -0
- 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 +145 -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 +464 -0
- package/dist/commands/form-config.d.ts +5 -0
- package/dist/commands/form-config.js +194 -0
- package/dist/completion/index.d.ts +1 -0
- package/dist/completion/index.js +217 -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/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 +143 -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 +85 -0
- 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 +4 -0
- package/templates/hook-archetype/standard/.npmignore.hbs +3 -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 +34 -0
- package/templates/hook-archetype/standard/Dockerfile.hbs +28 -0
- package/templates/hook-archetype/standard/README.md.hbs +39 -0
- package/templates/hook-archetype/standard/assets/fonts/.gitkeep +1 -0
- package/templates/hook-archetype/standard/language-defaults/.gitkeep +1 -0
- package/templates/hook-archetype/standard/package.json.hbs +40 -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/support/bootstrap-data/.gitkeep +1 -0
- package/templates/hook-archetype/standard/support/development/docker-compose.yml.hbs +75 -0
- package/templates/hook-archetype/standard/support/integration-testing/docker-compose.mocha.yml.hbs +84 -0
- package/templates/hook-archetype/standard/support/integration-testing/run-mocha-hook.sh.hbs +9 -0
- package/templates/hook-archetype/standard/test/integration/hook-registration.test.ts.hbs +13 -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 +40 -0
- package/templates/hook-archetype/standard/tsconfig.json.hbs +15 -0
- package/templates/hook-archetype/standard/views/.gitkeep +1 -0
- package/testing.d.ts +1 -0
- package/testing.js +3 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
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.registerClientFormConfigCommand)(program);
|
|
132
|
+
(0, form_config_1.registerQuestionTreeDiagramCommand)(program);
|
|
133
|
+
program
|
|
134
|
+
.command('init [name]')
|
|
135
|
+
.description('Initialize a new ReDBox hook project from the standard archetype')
|
|
136
|
+
.option('--template <template>', 'Archetype template name', 'standard')
|
|
137
|
+
.option('--description <description>', 'Project description')
|
|
138
|
+
.option('--force', 'Overwrite existing archetype files', false)
|
|
139
|
+
.action((name, options) => {
|
|
140
|
+
if (options.template !== 'standard') {
|
|
141
|
+
console.error(`â Unsupported template: ${options.template}. Supported templates: standard`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
const cwd = process.cwd();
|
|
145
|
+
const packageName = name || path.basename(cwd);
|
|
146
|
+
console.log(`đ Initializing ReDBox hook archetype for ${packageName}...\n`);
|
|
147
|
+
const result = (0, hook_archetype_1.generateHookArchetype)({
|
|
148
|
+
cwd,
|
|
149
|
+
packageName,
|
|
150
|
+
description: options.description,
|
|
151
|
+
force: options.force,
|
|
152
|
+
templateName: options.template,
|
|
153
|
+
});
|
|
154
|
+
for (const createdPath of result.created) {
|
|
155
|
+
console.log(`â
Created ${createdPath}`);
|
|
156
|
+
}
|
|
157
|
+
for (const skippedPath of result.skipped) {
|
|
158
|
+
console.log(`âšď¸ Skipped existing ${skippedPath}`);
|
|
159
|
+
}
|
|
160
|
+
console.log('\n⨠Archetype ready!\n');
|
|
161
|
+
console.log('Next steps:');
|
|
162
|
+
console.log(' 1. Install dependencies: npm install');
|
|
163
|
+
console.log(' 2. Compile TypeScript: npm run compile');
|
|
164
|
+
console.log(' 3. Run unit tests: npm run test:unit');
|
|
165
|
+
console.log(' 4. Start tailoring the hook in src/, test/, and support/\n');
|
|
166
|
+
});
|
|
167
|
+
program
|
|
168
|
+
.command('check')
|
|
169
|
+
.description('Validate that a hook uses the shared Redbox dependency contract')
|
|
170
|
+
.action(() => {
|
|
171
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
172
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
173
|
+
console.error('â package.json not found in the current directory.');
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
const pkg = readPackageJson(packageJsonPath);
|
|
177
|
+
const findings = findForbiddenHookDeps(pkg);
|
|
178
|
+
if (findings.length === 0) {
|
|
179
|
+
console.log('â
Hook dependency contract looks good.');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
console.error('â Hook dependency contract violations found:');
|
|
183
|
+
for (const finding of findings) {
|
|
184
|
+
console.error(` - ${finding}`);
|
|
185
|
+
}
|
|
186
|
+
console.error('Shared runtime dependencies should come from @researchdatabox/redbox-core, and shared toolchain dependencies should come from @researchdatabox/redbox-dev-tools.');
|
|
187
|
+
process.exit(1);
|
|
188
|
+
});
|
|
189
|
+
program
|
|
190
|
+
.command('migrate-hook-dependencies')
|
|
191
|
+
.description('Rewrite the current hook package.json to the minimal shared dependency contract')
|
|
192
|
+
.action(() => {
|
|
193
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
194
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
195
|
+
console.error('â package.json not found in the current directory.');
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
const pkg = readPackageJson(packageJsonPath);
|
|
199
|
+
const migratedPkg = migrateHookDependencyContract(pkg);
|
|
200
|
+
writePackageJson(packageJsonPath, migratedPkg);
|
|
201
|
+
console.log('â
Updated package.json to use the minimal shared hook dependency contract.');
|
|
202
|
+
});
|
|
203
|
+
const generate = program.command('generate').alias('g').description('Generate code components');
|
|
204
|
+
generate
|
|
205
|
+
.command('controller <name>')
|
|
206
|
+
.description('Generate a new ReDBox controller')
|
|
207
|
+
.option('--actions <actions>', 'Comma-separated list of actions', val => val.split(','))
|
|
208
|
+
.option('--webservice', 'Generate as a webservice controller', false)
|
|
209
|
+
.option('--class-name <className>', 'Override controller class name (defaults to name without Controller suffix)')
|
|
210
|
+
.option('--route <route>', 'Single route (deprecated, use --routes)')
|
|
211
|
+
.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)
|
|
212
|
+
.option('--nav <nav>', 'Navigation mappings: action:type[:containerId]:labelKey[:itemId],... (e.g., list:menu:plan:create-rdmp:plan-create)', cli_parsers_1.parseNavMappings)
|
|
213
|
+
.option('--lang <lang>', 'Language defaults: key=value[:lang],... (e.g., menu-custom=Custom menu:en)', cli_parsers_1.parseLanguageDefaults)
|
|
214
|
+
.option('--auth <roles>', 'Default roles for auth rules (can be overridden per-route)', val => val.split(','))
|
|
215
|
+
.action(async (name, options) => {
|
|
216
|
+
try {
|
|
217
|
+
const globalOptions = program.opts();
|
|
218
|
+
const paths = (0, paths_1.resolvePaths)(globalOptions);
|
|
219
|
+
const { ControllerGenerator } = require('./generators/controller');
|
|
220
|
+
const generator = new ControllerGenerator({
|
|
221
|
+
name,
|
|
222
|
+
actions: options.actions,
|
|
223
|
+
webservice: options.webservice,
|
|
224
|
+
route: options.route,
|
|
225
|
+
routes: options.routes,
|
|
226
|
+
auth: options.auth,
|
|
227
|
+
navigation: options.nav,
|
|
228
|
+
languageDefaults: options.lang,
|
|
229
|
+
className: options.className,
|
|
230
|
+
dryRun: globalOptions.dryRun,
|
|
231
|
+
root: paths.root,
|
|
232
|
+
paths,
|
|
233
|
+
});
|
|
234
|
+
console.log(`\nđ ď¸ Generating controller: ${name}...\n`);
|
|
235
|
+
await generator.generate();
|
|
236
|
+
console.log('\nâ
Done!\n');
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
console.error(`\nâ Error: ${error.message}\n`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
generate
|
|
244
|
+
.command('service <name>')
|
|
245
|
+
.description('Generate a new ReDBox service')
|
|
246
|
+
.option('--methods <methods>', 'Comma-separated list of methods', val => val.split(','))
|
|
247
|
+
.action(async (name, options) => {
|
|
248
|
+
try {
|
|
249
|
+
const globalOptions = program.opts();
|
|
250
|
+
const paths = (0, paths_1.resolvePaths)(globalOptions);
|
|
251
|
+
const { ServiceGenerator } = require('./generators/service');
|
|
252
|
+
const generator = new ServiceGenerator({
|
|
253
|
+
name,
|
|
254
|
+
methods: options.methods,
|
|
255
|
+
dryRun: globalOptions.dryRun,
|
|
256
|
+
root: paths.root,
|
|
257
|
+
paths,
|
|
258
|
+
});
|
|
259
|
+
console.log(`\nđ ď¸ Generating service: ${name}...\n`);
|
|
260
|
+
await generator.generate();
|
|
261
|
+
console.log('\nâ
Done!\n');
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
console.error(`\nâ Error: ${error.message}\n`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
generate
|
|
269
|
+
.command('method')
|
|
270
|
+
.description('Add a method to an existing controller or service')
|
|
271
|
+
.requiredOption('--file <path>', 'Path to existing controller or service file')
|
|
272
|
+
.requiredOption('--method <name>', 'Method name to add')
|
|
273
|
+
.option('--route <route>', 'Route path (for controllers)')
|
|
274
|
+
.option('--http <verb>', 'HTTP verb (GET, POST, etc.)')
|
|
275
|
+
.option('--auth <roles>', 'Comma-separated list of roles', val => val.split(','))
|
|
276
|
+
.option('--nav <nav>', 'Navigation mappings: type[:containerId]:labelKey[:itemId],... (e.g., menu:plan:create-rdmp:plan-create)', cli_parsers_1.parseNavMappings)
|
|
277
|
+
.option('--lang <lang>', 'Language defaults: key=value[:lang],... (e.g., menu-custom=Custom menu:en)', cli_parsers_1.parseLanguageDefaults)
|
|
278
|
+
.action(async (options) => {
|
|
279
|
+
try {
|
|
280
|
+
const globalOptions = program.opts();
|
|
281
|
+
const paths = (0, paths_1.resolvePaths)(globalOptions);
|
|
282
|
+
const { AddMethodGenerator } = require('./generators/add-method');
|
|
283
|
+
const generator = new AddMethodGenerator({
|
|
284
|
+
file: options.file,
|
|
285
|
+
method: options.method,
|
|
286
|
+
route: options.route,
|
|
287
|
+
http: options.http,
|
|
288
|
+
auth: options.auth,
|
|
289
|
+
navigation: options.nav,
|
|
290
|
+
languageDefaults: options.lang,
|
|
291
|
+
dryRun: globalOptions.dryRun,
|
|
292
|
+
root: paths.root,
|
|
293
|
+
paths,
|
|
294
|
+
});
|
|
295
|
+
console.log(`\nđ ď¸ Adding method ${options.method} to ${options.file}...\n`);
|
|
296
|
+
await generator.generate();
|
|
297
|
+
console.log('\nâ
Done!\n');
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
console.error(`\nâ Error: ${error.message}\n`);
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
generate
|
|
305
|
+
.command('angular-app <name>')
|
|
306
|
+
.description('Scaffold a new Angular app for embedded EJS usage')
|
|
307
|
+
.requiredOption('--ejs-view <view>', 'EJS view name')
|
|
308
|
+
.option('--auth <roles>', 'Comma-separated list of roles', val => val.split(','))
|
|
309
|
+
.action(async (name, options) => {
|
|
310
|
+
try {
|
|
311
|
+
const globalOptions = program.opts();
|
|
312
|
+
const paths = (0, paths_1.resolvePaths)(globalOptions);
|
|
313
|
+
const { AngularAppGenerator } = require('./generators/angular-app');
|
|
314
|
+
const generator = new AngularAppGenerator({
|
|
315
|
+
name,
|
|
316
|
+
ejsView: options.ejsView,
|
|
317
|
+
auth: options.auth,
|
|
318
|
+
dryRun: globalOptions.dryRun,
|
|
319
|
+
root: paths.root,
|
|
320
|
+
paths,
|
|
321
|
+
});
|
|
322
|
+
console.log(`\nđ ď¸ Generating Angular app: ${name}...\n`);
|
|
323
|
+
await generator.generate();
|
|
324
|
+
console.log('\nâ
Done!\n');
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
console.error(`\nâ Error: ${error.message}\n`);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
generate
|
|
332
|
+
.command('angular-service <name>')
|
|
333
|
+
.description('Generate a new Angular service extending HttpClientService')
|
|
334
|
+
.requiredOption('--app <app>', 'Target Angular app name')
|
|
335
|
+
.option('--methods <methods>', 'Comma-separated list of methods', val => val.split(','))
|
|
336
|
+
.action(async (name, options) => {
|
|
337
|
+
try {
|
|
338
|
+
const globalOptions = program.opts();
|
|
339
|
+
const paths = (0, paths_1.resolvePaths)(globalOptions);
|
|
340
|
+
const { AngularServiceGenerator } = require('./generators/angular-service');
|
|
341
|
+
const generator = new AngularServiceGenerator({
|
|
342
|
+
name,
|
|
343
|
+
app: options.app,
|
|
344
|
+
methods: options.methods,
|
|
345
|
+
dryRun: globalOptions.dryRun,
|
|
346
|
+
root: paths.root,
|
|
347
|
+
paths,
|
|
348
|
+
});
|
|
349
|
+
console.log(`\nđ ď¸ Generating Angular service: ${name} for app ${options.app}...\n`);
|
|
350
|
+
await generator.generate();
|
|
351
|
+
console.log('\nâ
Done!\n');
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
console.error(`\nâ Error: ${error.message}\n`);
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
generate
|
|
359
|
+
.command('form-component <name>')
|
|
360
|
+
.description('Generate scaffold for a new form component across Angular and sails-ng-common')
|
|
361
|
+
.option('--app <app>', 'Target Angular app name', 'form')
|
|
362
|
+
.option('--with-service', 'Also generate a companion Angular HttpClient service', false)
|
|
363
|
+
.action(async (name, options) => {
|
|
364
|
+
try {
|
|
365
|
+
const globalOptions = program.opts();
|
|
366
|
+
const paths = (0, paths_1.resolvePaths)(globalOptions);
|
|
367
|
+
const { FormComponentGenerator } = require('./generators/form-component');
|
|
368
|
+
const generator = new FormComponentGenerator({
|
|
369
|
+
name,
|
|
370
|
+
app: options.app,
|
|
371
|
+
withService: options.withService,
|
|
372
|
+
dryRun: globalOptions.dryRun,
|
|
373
|
+
root: paths.root,
|
|
374
|
+
paths,
|
|
375
|
+
});
|
|
376
|
+
console.log(`\nđ ď¸ Generating form component scaffold: ${name}...\n`);
|
|
377
|
+
await generator.generate();
|
|
378
|
+
console.log('\nâ
Done!\n');
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
console.error(`\nâ Error: ${error.message}\n`);
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
generate
|
|
386
|
+
.command('form-field <name>')
|
|
387
|
+
.description('Generate a new form field DSL configuration')
|
|
388
|
+
.requiredOption('--type <type>', 'Record type')
|
|
389
|
+
.action(async (name, options) => {
|
|
390
|
+
try {
|
|
391
|
+
const globalOptions = program.opts();
|
|
392
|
+
const paths = (0, paths_1.resolvePaths)(globalOptions);
|
|
393
|
+
const { FormFieldGenerator } = require('./generators/form-field');
|
|
394
|
+
const generator = new FormFieldGenerator({
|
|
395
|
+
name,
|
|
396
|
+
type: options.type,
|
|
397
|
+
dryRun: globalOptions.dryRun,
|
|
398
|
+
root: paths.root,
|
|
399
|
+
});
|
|
400
|
+
console.log(`\nđ ď¸ Generating form field config: ${name}...\n`);
|
|
401
|
+
await generator.generate();
|
|
402
|
+
console.log('\nâ
Done!\n');
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
console.error(`\nâ Error: ${error.message}\n`);
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
program
|
|
410
|
+
.command('install-skills')
|
|
411
|
+
.alias('skills')
|
|
412
|
+
.description('Install bundled agent skills using the vercel-labs skills CLI')
|
|
413
|
+
.allowUnknownOption(true)
|
|
414
|
+
.allowExcessArguments(true)
|
|
415
|
+
.action(() => {
|
|
416
|
+
const skillsDir = resolveSkillsDir();
|
|
417
|
+
if (!fs.existsSync(skillsDir)) {
|
|
418
|
+
console.error(`Skills directory not found: ${skillsDir}`);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
const rawArgs = process.argv.slice(2);
|
|
422
|
+
const cmdIndex = rawArgs.findIndex(arg => arg === 'install-skills' || arg === 'skills');
|
|
423
|
+
const forwardedArgs = cmdIndex === -1 ? [] : rawArgs.slice(cmdIndex + 1);
|
|
424
|
+
const result = (0, child_process_1.spawnSync)('npx', ['skills', 'add', skillsDir, ...forwardedArgs], {
|
|
425
|
+
stdio: 'inherit',
|
|
426
|
+
shell: process.platform === 'win32',
|
|
427
|
+
});
|
|
428
|
+
if (result.error) {
|
|
429
|
+
console.error('Failed to run npx skills:', result.error.message);
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
433
|
+
});
|
|
434
|
+
generate
|
|
435
|
+
.command('model <name>')
|
|
436
|
+
.description('Generate a new Waterline model with decorator-based definition')
|
|
437
|
+
.option('--identity <identity>', 'Waterline identity (table name), defaults to lowercase name')
|
|
438
|
+
.option('--attrs <attrs>', 'Comma-separated list of attributes in format name:type[:required][:unique]', cli_parsers_1.parseAttributes)
|
|
439
|
+
.option('--belongs-to <associations>', 'Comma-separated belongsTo associations in format name:model', cli_parsers_1.parseBelongsTo)
|
|
440
|
+
.option('--has-many <associations>', 'Comma-separated hasMany associations in format name:model:via[:dominant]', cli_parsers_1.parseHasMany)
|
|
441
|
+
.action(async (name, options) => {
|
|
442
|
+
try {
|
|
443
|
+
const globalOptions = program.opts();
|
|
444
|
+
const paths = (0, paths_1.resolvePaths)(globalOptions);
|
|
445
|
+
const { ModelGenerator } = require('./generators/model');
|
|
446
|
+
const generator = new ModelGenerator({
|
|
447
|
+
name,
|
|
448
|
+
identity: options.identity,
|
|
449
|
+
attributes: options.attrs,
|
|
450
|
+
associations: [...(options.belongsTo || []), ...(options.hasMany || [])],
|
|
451
|
+
dryRun: globalOptions.dryRun,
|
|
452
|
+
root: paths.root,
|
|
453
|
+
paths,
|
|
454
|
+
});
|
|
455
|
+
console.log(`\nđ ď¸ Generating Waterline model: ${name}...\n`);
|
|
456
|
+
await generator.generate();
|
|
457
|
+
console.log('\nâ
Done!\n');
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
console.error(`\nâ Error: ${error.message}\n`);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
program.parse();
|
|
@@ -0,0 +1,5 @@
|
|
|
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;
|
|
@@ -0,0 +1,194 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.registerMigrateFormConfigCommand = registerMigrateFormConfigCommand;
|
|
40
|
+
exports.registerMigrateDataClassificationCommand = registerMigrateDataClassificationCommand;
|
|
41
|
+
exports.registerClientFormConfigCommand = registerClientFormConfigCommand;
|
|
42
|
+
exports.registerQuestionTreeDiagramCommand = registerQuestionTreeDiagramCommand;
|
|
43
|
+
const fs_1 = __importDefault(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const redbox_core_1 = require("@researchdatabox/redbox-core");
|
|
46
|
+
const migrationLogger = {
|
|
47
|
+
silly: (...args) => console.debug(...args),
|
|
48
|
+
verbose: (...args) => console.debug(...args),
|
|
49
|
+
trace: (...args) => console.debug(...args),
|
|
50
|
+
debug: (...args) => console.debug(...args),
|
|
51
|
+
log: (...args) => console.log(...args),
|
|
52
|
+
info: (...args) => console.info(...args),
|
|
53
|
+
warn: (...args) => console.warn(...args),
|
|
54
|
+
error: (...args) => console.error(...args),
|
|
55
|
+
crit: (...args) => console.error(...args),
|
|
56
|
+
fatal: (...args) => console.error(...args),
|
|
57
|
+
silent: () => undefined,
|
|
58
|
+
blank: () => undefined,
|
|
59
|
+
};
|
|
60
|
+
function registerMigrateFormConfigCommand(program) {
|
|
61
|
+
program
|
|
62
|
+
.command('migrate-form-config')
|
|
63
|
+
.description('Migrate a legacy v4 JS form config file to the v5 TS form framework format')
|
|
64
|
+
.requiredOption('-i, --input <path>', 'Path to the legacy v4 form config JS file')
|
|
65
|
+
.requiredOption('-o, --output <path>', 'Path to write the migrated v5 TypeScript config file')
|
|
66
|
+
.option('--format <name>', "The output format, either 'esm' or 'cjs'")
|
|
67
|
+
.action(async (options) => {
|
|
68
|
+
try {
|
|
69
|
+
const globalOptions = program.opts();
|
|
70
|
+
const inputPath = path.resolve(options.input);
|
|
71
|
+
const outputPath = path.resolve(options.output);
|
|
72
|
+
const outputFormat = options.format || "esm";
|
|
73
|
+
console.log(`\nđ ď¸ Migrating form config to ${outputFormat} format: ${inputPath} -> ${outputPath}\n`);
|
|
74
|
+
const migrateVisitor = new redbox_core_1.MigrationV4ToV5FormConfigVisitor(migrationLogger);
|
|
75
|
+
const migrated = await (0, redbox_core_1.migrateFormConfigFile)(migrateVisitor, inputPath, outputFormat);
|
|
76
|
+
if (globalOptions.dryRun) {
|
|
77
|
+
console.log('[dry-run] Migration completed; no file written.');
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
fs_1.default.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
81
|
+
fs_1.default.writeFileSync(outputPath, migrated.tsContent, 'utf8');
|
|
82
|
+
console.log(`â
Wrote migrated form config: ${outputPath}`);
|
|
83
|
+
}
|
|
84
|
+
await (0, redbox_core_1.migrateFormConfigVerify)(migrated.migrated, migrationLogger);
|
|
85
|
+
console.log('\nâ
Done!\n');
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error(`\nâ Error: `, error);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function registerMigrateDataClassificationCommand(program) {
|
|
94
|
+
program
|
|
95
|
+
.command('migrate-data-classification')
|
|
96
|
+
.description('Migrate a legacy v4 JS data classification definition file to the v5 TS form framework format')
|
|
97
|
+
.requiredOption('-i, --input <path>', 'Path to the legacy v4 JS data classification definition file')
|
|
98
|
+
.requiredOption('-o, --output <path>', 'Path to write the migrated v5 TypeScript question tree config file')
|
|
99
|
+
.option('--format <name>', "The output format, either 'esm' or 'cjs'")
|
|
100
|
+
.action(async (options) => {
|
|
101
|
+
try {
|
|
102
|
+
const globalOptions = program.opts();
|
|
103
|
+
const inputPath = path.resolve(options.input);
|
|
104
|
+
const outputPath = path.resolve(options.output);
|
|
105
|
+
const outputFormat = options.format || 'esm';
|
|
106
|
+
console.log(`\nđ ď¸ Migrating data classification to question tree to ${outputFormat} format: ${inputPath} -> ${outputPath}\n`);
|
|
107
|
+
const migrateVisitor = new redbox_core_1.MigrationV4ToV5FormConfigVisitor(migrationLogger);
|
|
108
|
+
const migrated = (0, redbox_core_1.migrateDataClassification)(migrateVisitor, inputPath, outputFormat);
|
|
109
|
+
if (globalOptions.dryRun) {
|
|
110
|
+
console.log('[dry-run] Migration completed; no file written.');
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
fs_1.default.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
114
|
+
fs_1.default.writeFileSync(outputPath, migrated.tsContent, 'utf8');
|
|
115
|
+
console.log(`â
Wrote migrated question tree config: ${outputPath}`);
|
|
116
|
+
}
|
|
117
|
+
await (0, redbox_core_1.migrateFormConfigVerify)(migrated.formConfig, migrationLogger);
|
|
118
|
+
console.log('\nâ
Done!\n');
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error(`\nâ Error: `, error);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function registerClientFormConfigCommand(program) {
|
|
127
|
+
program
|
|
128
|
+
.command('client-form-config')
|
|
129
|
+
.description('Process a form config to produce the client form config')
|
|
130
|
+
.requiredOption('-i, --input <path>', 'Path to read the server-side form config TypeScript file')
|
|
131
|
+
.requiredOption('-o, --output <path>', 'Path to write the client-side form config TypeScript file')
|
|
132
|
+
.option('--formMode <name>', "The form mode, either 'edit' or 'view'")
|
|
133
|
+
.option('--userRoles <name...>', "The user roles, zero or more of 'Admin', 'Librarians', 'Researcher', 'Guest'")
|
|
134
|
+
.option('-r, --record <path>', "Path to read the data record file")
|
|
135
|
+
.action(async (options) => {
|
|
136
|
+
try {
|
|
137
|
+
const globalOptions = program.opts();
|
|
138
|
+
const inputPath = path.resolve(options.input);
|
|
139
|
+
const outputPath = path.resolve(options.output);
|
|
140
|
+
const formMode = options.formMode ?? null;
|
|
141
|
+
const userRoles = options.userRoles ?? null;
|
|
142
|
+
const record = options.record ? require(path.resolve(options.record)) : null;
|
|
143
|
+
console.log(`\nđ ď¸ Building client form config from server form config: ${inputPath} -> ${outputPath}\n`);
|
|
144
|
+
const serverFormConfig = require(inputPath).default;
|
|
145
|
+
const clientFormConfig = await (0, redbox_core_1.createClientFormConfig)(serverFormConfig, migrationLogger, formMode, userRoles, undefined, record);
|
|
146
|
+
const tsContent = `import { FormConfigFrame } from '@researchdatabox/sails-ng-common';
|
|
147
|
+
const clientFormConfig: FormConfigFrame = ${JSON.stringify(clientFormConfig, null, 2)};
|
|
148
|
+
export default clientFormConfig;`;
|
|
149
|
+
if (globalOptions.dryRun) {
|
|
150
|
+
console.log('[dry-run] Client form config built from server form config; no file written.');
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
fs_1.default.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
154
|
+
fs_1.default.writeFileSync(outputPath, tsContent, 'utf8');
|
|
155
|
+
console.log(`â
Wrote client form config: ${outputPath}`);
|
|
156
|
+
}
|
|
157
|
+
await (0, redbox_core_1.migrateFormConfigVerify)(clientFormConfig, migrationLogger);
|
|
158
|
+
console.log('\nâ
Done!\n');
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
console.error(`\nâ Error: `, error);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function registerQuestionTreeDiagramCommand(program) {
|
|
167
|
+
program
|
|
168
|
+
.command('question-tree-diagram')
|
|
169
|
+
.description('Convert a question tree config to a mermaid diagram text file')
|
|
170
|
+
.requiredOption('-i, --input <path>', 'Path to read the question tree form config TypeScript file')
|
|
171
|
+
.requiredOption('-o, --output <path>', 'Path to write the diagram text file')
|
|
172
|
+
.action(async (options) => {
|
|
173
|
+
try {
|
|
174
|
+
const globalOptions = program.opts();
|
|
175
|
+
const inputPath = path.resolve(options.input);
|
|
176
|
+
const outputPath = path.resolve(options.output);
|
|
177
|
+
console.log(`\nđ ď¸ Creating diagram from form config: ${inputPath} -> ${outputPath}\n`);
|
|
178
|
+
const componentConfig = require(inputPath);
|
|
179
|
+
const diagram = await (0, redbox_core_1.createQuestionTreeDiagram)(componentConfig, migrationLogger);
|
|
180
|
+
if (globalOptions.dryRun) {
|
|
181
|
+
console.log('[dry-run] Built diagram from form config; no file written.');
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
fs_1.default.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
185
|
+
fs_1.default.writeFileSync(outputPath, diagram, 'utf8');
|
|
186
|
+
console.log(`â
Wrote diagram text file: ${outputPath}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
console.error(`\nâ Error: `, error);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateCompletionScript(shell: string): string;
|