@orxataguy/tyr 1.0.17 → 1.0.19
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/package.json +1 -1
- package/src/core/Kernel.ts +8 -7
- package/src/core/sys/config.ts +49 -49
- package/src/core/sys/doc.ts +24 -24
- package/src/core/sys/gen.ts +12 -12
- package/src/core/sys/help.ts +20 -20
- package/src/core/sys/rem.ts +7 -7
- package/src/lib/GitManager.ts +6 -6
- package/src/lib/JiraManager.ts +5 -5
- package/src/lib/MongoManager.ts +38 -38
- package/src/lib/SQLManager.ts +10 -10
- package/src/lib/SetupManager.ts +18 -18
- package/src/lib/ShellManager.ts +9 -9
- package/src/lib/WorkspaceManager.ts +12 -12
package/package.json
CHANGED
package/src/core/Kernel.ts
CHANGED
|
@@ -104,14 +104,14 @@ export class Kernel {
|
|
|
104
104
|
const shell = this.container.get().shell;
|
|
105
105
|
const gitDir = path.join(this.userRoot, '.git');
|
|
106
106
|
if (!fs.existsSync(gitDir)) {
|
|
107
|
-
console.log('~/.tyr
|
|
108
|
-
console.log('
|
|
107
|
+
console.log('~/.tyr is not linked to any git repository.');
|
|
108
|
+
console.log('Run: tyr --config --repo <url> to link it.');
|
|
109
109
|
return;
|
|
110
110
|
}
|
|
111
|
-
console.log('
|
|
111
|
+
console.log('Updating ~/.tyr from repository...');
|
|
112
112
|
shell.cd(this.userRoot);
|
|
113
113
|
await shell.exec('git pull');
|
|
114
|
-
console.log('
|
|
114
|
+
console.log('Update complete.');
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -120,13 +120,14 @@ export class Kernel {
|
|
|
120
120
|
const pkgPath = path.resolve(this.frameworkRoot, 'package.json');
|
|
121
121
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
122
122
|
const shell = this.container.get().shell;
|
|
123
|
-
console.log(`
|
|
123
|
+
console.log(`Updating ${pkg.name}...`);
|
|
124
124
|
await shell.exec(`npm update -g ${pkg.name}`);
|
|
125
|
-
console.log(
|
|
125
|
+
console.log(`Current version:`)
|
|
126
|
+
await shell.exec(`tyr --version`)
|
|
126
127
|
return;
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
// --help / -h:
|
|
130
|
+
// --help / -h: lists all available commands with their documentation
|
|
130
131
|
if (commandName === '--help' || commandName === '-h') {
|
|
131
132
|
const helpContext = {
|
|
132
133
|
...this.container.get(),
|
package/src/core/sys/config.ts
CHANGED
|
@@ -64,7 +64,7 @@ const PACKAGE_JSON_TEMPLATE = `{
|
|
|
64
64
|
"version": "1.0.0",
|
|
65
65
|
"type": "module",
|
|
66
66
|
"private": true,
|
|
67
|
-
"description": "
|
|
67
|
+
"description": "Custom Tyr commands (~/.tyr/)",
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@orxataguy/tyr": "latest"
|
|
70
70
|
}
|
|
@@ -87,47 +87,47 @@ const TSCONFIG_TEMPLATE = `{
|
|
|
87
87
|
`;
|
|
88
88
|
|
|
89
89
|
const ENV_TEMPLATE = `# ~/.tyr/.env
|
|
90
|
-
#
|
|
90
|
+
# Environment variables for Tyr. This file must never be committed to git.
|
|
91
91
|
#
|
|
92
|
-
#
|
|
92
|
+
# SQL Server database
|
|
93
93
|
MSSQL_USER=
|
|
94
94
|
MSSQL_PASSWORD=
|
|
95
95
|
MSSQL_SERVER=
|
|
96
96
|
MSSQL_DATABASE=
|
|
97
|
-
#
|
|
97
|
+
# MongoDB database
|
|
98
98
|
MONGO_URI=
|
|
99
99
|
MONGO_DATABASE=
|
|
100
100
|
`;
|
|
101
101
|
|
|
102
102
|
const SH_ALIASES_TEMPLATE = `# ~/.tyr/aliases
|
|
103
|
-
#
|
|
104
|
-
#
|
|
103
|
+
# Add your custom aliases here.
|
|
104
|
+
# This file is loaded automatically by your shell.
|
|
105
105
|
#
|
|
106
|
-
#
|
|
106
|
+
# Examples:
|
|
107
107
|
# alias gs='git status'
|
|
108
108
|
# alias tyr-deploy='tyr deploy'
|
|
109
109
|
`;
|
|
110
110
|
|
|
111
111
|
const SH_PLUGINS_TEMPLATE = `# ~/.tyr/plugins
|
|
112
|
-
#
|
|
113
|
-
# Compatible
|
|
112
|
+
# Add your shell plugins here.
|
|
113
|
+
# Compatible with zsh, bash and other POSIX shells.
|
|
114
114
|
#
|
|
115
|
-
#
|
|
115
|
+
# Examples (zsh):
|
|
116
116
|
# source /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
|
|
117
117
|
`;
|
|
118
118
|
|
|
119
119
|
const PS_ALIASES_TEMPLATE = `# ~/.tyr/aliases.ps1
|
|
120
|
-
#
|
|
120
|
+
# Add your custom aliases for PowerShell here.
|
|
121
121
|
#
|
|
122
|
-
#
|
|
122
|
+
# Examples:
|
|
123
123
|
# Set-Alias gs git-status
|
|
124
124
|
# function tyr-deploy { tyr deploy @args }
|
|
125
125
|
`;
|
|
126
126
|
|
|
127
127
|
const PS_PLUGINS_TEMPLATE = `# ~/.tyr/plugins.ps1
|
|
128
|
-
#
|
|
128
|
+
# Add your PowerShell modules and plugins here.
|
|
129
129
|
#
|
|
130
|
-
#
|
|
130
|
+
# Examples:
|
|
131
131
|
# Import-Module posh-git
|
|
132
132
|
# Import-Module PSReadLine
|
|
133
133
|
`;
|
|
@@ -141,8 +141,8 @@ function makeTimestamp(): string {
|
|
|
141
141
|
async function configureUnixShell(tyrFs: any, logger: any, homeDir: string, aliasesPath: string, pluginsPath: string): Promise<void> {
|
|
142
142
|
const rcFile = detectShellRcFile(homeDir);
|
|
143
143
|
if (!rcFile) {
|
|
144
|
-
logger.warn('
|
|
145
|
-
logger.info(`
|
|
144
|
+
logger.warn('Could not detect shell configuration file.');
|
|
145
|
+
logger.info(`Add manually:\n source "${aliasesPath}"\n source "${pluginsPath}"`);
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
148
|
await tyrFs.ensureLine(rcFile, `source "${aliasesPath}"`);
|
|
@@ -156,14 +156,14 @@ async function configureWindowsShell(tyrFs: any, logger: any, aliasesPath: strin
|
|
|
156
156
|
? path.join(process.env.USERPROFILE, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1')
|
|
157
157
|
: null;
|
|
158
158
|
if (!psProfile) {
|
|
159
|
-
logger.warn('
|
|
160
|
-
logger.info(`
|
|
159
|
+
logger.warn('Could not detect PowerShell profile.');
|
|
160
|
+
logger.info(`Add manually:\n . "${aliasesPath}"\n . "${pluginsPath}"`);
|
|
161
161
|
return;
|
|
162
162
|
}
|
|
163
163
|
await tyrFs.ensureLine(psProfile, `. "${aliasesPath}"`);
|
|
164
164
|
await tyrFs.ensureLine(psProfile, `. "${pluginsPath}"`);
|
|
165
|
-
logger.success(`
|
|
166
|
-
logger.info('
|
|
165
|
+
logger.success(`PowerShell profile configured: ${psProfile}`);
|
|
166
|
+
logger.info('Restart PowerShell to apply the changes.');
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
export default function config({ logger, fs: tyrFs, frameworkRoot, shell }: TyrContext) {
|
|
@@ -178,8 +178,8 @@ export default function config({ logger, fs: tyrFs, frameworkRoot, shell }: TyrC
|
|
|
178
178
|
const repoUrl = repoIndex !== -1 ? (args[repoIndex + 1] ?? null) : null;
|
|
179
179
|
|
|
180
180
|
if (repoIndex !== -1 && (!repoUrl || repoUrl.startsWith('--'))) {
|
|
181
|
-
logger.error('
|
|
182
|
-
logger.info('
|
|
181
|
+
logger.error('Repository URL is missing.');
|
|
182
|
+
logger.info('Usage: tyr --config --repo <url>');
|
|
183
183
|
return;
|
|
184
184
|
}
|
|
185
185
|
|
|
@@ -187,7 +187,7 @@ export default function config({ logger, fs: tyrFs, frameworkRoot, shell }: TyrC
|
|
|
187
187
|
if (existsSync(userRoot)) {
|
|
188
188
|
backupPath = `${userRoot}.bak.${makeTimestamp()}`;
|
|
189
189
|
backupUserRoot(userRoot, backupPath);
|
|
190
|
-
logger.warn(`
|
|
190
|
+
logger.warn(`Previous configuration backed up at: ${backupPath}`);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
let repoHasContent = false;
|
|
@@ -197,7 +197,7 @@ export default function config({ logger, fs: tyrFs, frameworkRoot, shell }: TyrC
|
|
|
197
197
|
try { removeDirRecursive(tempDir); } catch {}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
logger.info(`\
|
|
200
|
+
logger.info(`\nCloning repository: ${repoUrl}`);
|
|
201
201
|
try {
|
|
202
202
|
await shell.exec(`git clone "${repoUrl}" "${tempDir}"`);
|
|
203
203
|
} catch (e) {
|
|
@@ -209,8 +209,8 @@ export default function config({ logger, fs: tyrFs, frameworkRoot, shell }: TyrC
|
|
|
209
209
|
|
|
210
210
|
repoHasContent = existsSync(path.join(tempDir, 'map.yml'));
|
|
211
211
|
logger.success(repoHasContent
|
|
212
|
-
? '
|
|
213
|
-
: '
|
|
212
|
+
? 'Repository cloned with existing configuration.'
|
|
213
|
+
: 'Empty repository — starting default configuration...');
|
|
214
214
|
|
|
215
215
|
clearDirExceptLogs(userRoot);
|
|
216
216
|
copyDirContents(tempDir, userRoot);
|
|
@@ -218,64 +218,64 @@ export default function config({ logger, fs: tyrFs, frameworkRoot, shell }: TyrC
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
if (!repoHasContent) {
|
|
221
|
-
logger.info('\
|
|
221
|
+
logger.info('\nInitializing ~/.tyr...\n');
|
|
222
222
|
|
|
223
223
|
await tyrFs.createDir(path.join(userRoot, 'commands'));
|
|
224
|
-
logger.success(`
|
|
224
|
+
logger.success(`Directory created: ${path.join(userRoot, 'commands')}`);
|
|
225
225
|
|
|
226
226
|
const aliasesPath = path.join(userRoot, `aliases${ext}`);
|
|
227
227
|
if (!tyrFs.exists(aliasesPath)) {
|
|
228
228
|
await tyrFs.write(aliasesPath, isWindows ? PS_ALIASES_TEMPLATE : SH_ALIASES_TEMPLATE);
|
|
229
|
-
logger.success(`
|
|
229
|
+
logger.success(`File created: ${aliasesPath}`);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
const pluginsPath = path.join(userRoot, `plugins${ext}`);
|
|
233
233
|
if (!tyrFs.exists(pluginsPath)) {
|
|
234
234
|
await tyrFs.write(pluginsPath, isWindows ? PS_PLUGINS_TEMPLATE : SH_PLUGINS_TEMPLATE);
|
|
235
|
-
logger.success(`
|
|
235
|
+
logger.success(`File created: ${pluginsPath}`);
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
const mapPath = path.join(userRoot, 'map.yml');
|
|
239
239
|
await tyrFs.write(mapPath, 'commands: {}\n');
|
|
240
|
-
logger.success(`
|
|
240
|
+
logger.success(`File created: ${mapPath}`);
|
|
241
241
|
|
|
242
242
|
const envPath = path.join(userRoot, '.env');
|
|
243
243
|
if (!tyrFs.exists(envPath)) {
|
|
244
244
|
await tyrFs.write(envPath, ENV_TEMPLATE);
|
|
245
|
-
logger.success(`
|
|
245
|
+
logger.success(`File created: ${envPath}`);
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
const packageJsonPath = path.join(userRoot, 'package.json');
|
|
249
249
|
if (!tyrFs.exists(packageJsonPath)) {
|
|
250
250
|
await tyrFs.write(packageJsonPath, PACKAGE_JSON_TEMPLATE);
|
|
251
|
-
logger.success(`
|
|
251
|
+
logger.success(`File created: ${packageJsonPath}`);
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
const tsconfigPath = path.join(userRoot, 'tsconfig.json');
|
|
255
255
|
if (!tyrFs.exists(tsconfigPath)) {
|
|
256
256
|
await tyrFs.write(tsconfigPath, TSCONFIG_TEMPLATE);
|
|
257
|
-
logger.success(`
|
|
257
|
+
logger.success(`File created: ${tsconfigPath}`);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
logger.info('\
|
|
260
|
+
logger.info('\nInstalling type dependencies in ~/.tyr...');
|
|
261
261
|
shell.cd(userRoot);
|
|
262
262
|
try {
|
|
263
263
|
await shell.exec('npm install');
|
|
264
|
-
logger.success('
|
|
264
|
+
logger.success('Dependencies installed successfully.');
|
|
265
265
|
} catch {
|
|
266
|
-
logger.warn('
|
|
266
|
+
logger.warn('Could not run npm install in ~/.tyr. Run it manually.');
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
if (repoUrl) {
|
|
270
|
-
logger.info('\
|
|
270
|
+
logger.info('\nPushing initial configuration to repository...');
|
|
271
271
|
shell.cd(userRoot);
|
|
272
272
|
try {
|
|
273
273
|
await shell.exec('git add .');
|
|
274
274
|
await shell.exec('git commit -m "Initial tyr configuration"');
|
|
275
275
|
await shell.exec('git push -u origin HEAD');
|
|
276
|
-
logger.success('
|
|
276
|
+
logger.success('Configuration pushed to repository.');
|
|
277
277
|
} catch (e) {
|
|
278
|
-
logger.warn('
|
|
278
|
+
logger.warn('Could not push automatically. Do it manually from ~/.tyr');
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
}
|
|
@@ -299,13 +299,13 @@ export default function config({ logger, fs: tyrFs, frameworkRoot, shell }: TyrC
|
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
if (needsInstall) {
|
|
302
|
-
logger.info('\
|
|
302
|
+
logger.info('\nInstalling type dependencies in ~/.tyr...');
|
|
303
303
|
shell.cd(userRoot);
|
|
304
304
|
try {
|
|
305
305
|
await shell.exec('npm install');
|
|
306
|
-
logger.success('
|
|
306
|
+
logger.success('Dependencies installed.');
|
|
307
307
|
} catch {
|
|
308
|
-
logger.warn('
|
|
308
|
+
logger.warn('Could not run npm install in ~/.tyr. Run it manually.');
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
|
|
@@ -321,11 +321,11 @@ export default function config({ logger, fs: tyrFs, frameworkRoot, shell }: TyrC
|
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
logger.success('\nTyr
|
|
325
|
-
logger.info(`
|
|
326
|
-
if (repoUrl) logger.info(`
|
|
327
|
-
logger.info('\
|
|
328
|
-
logger.info(' tyr gen <
|
|
329
|
-
logger.info(' tyr doc
|
|
324
|
+
logger.success('\nTyr configured successfully.');
|
|
325
|
+
logger.info(`Configuration directory: ${userRoot}`);
|
|
326
|
+
if (repoUrl) logger.info(`Linked repository: ${repoUrl}`);
|
|
327
|
+
logger.info('\nNext steps:');
|
|
328
|
+
logger.info(' tyr gen <name> <file> Create a new command');
|
|
329
|
+
logger.info(' tyr doc View API documentation');
|
|
330
330
|
};
|
|
331
331
|
}
|
package/src/core/sys/doc.ts
CHANGED
|
@@ -17,14 +17,14 @@ interface DocStructure {
|
|
|
17
17
|
|
|
18
18
|
export default function doc({ logger, frameworkRoot, run }: TyrContext) {
|
|
19
19
|
return async (args: string[]) => {
|
|
20
|
-
logger.info("📚
|
|
20
|
+
logger.info("📚 Generating system documentation (TS Mode)...");
|
|
21
21
|
|
|
22
22
|
const libPath = path.resolve(frameworkRoot, 'src/lib');
|
|
23
23
|
|
|
24
24
|
const parseJSDoc = (filename: string, content: string): DocStructure => {
|
|
25
25
|
const fileDoc: DocStructure = {
|
|
26
26
|
name: filename,
|
|
27
|
-
description: "
|
|
27
|
+
description: "No description.",
|
|
28
28
|
methods: []
|
|
29
29
|
};
|
|
30
30
|
|
|
@@ -81,7 +81,7 @@ export default function doc({ logger, frameworkRoot, run }: TyrContext) {
|
|
|
81
81
|
description = descMatch[1].trim();
|
|
82
82
|
} else {
|
|
83
83
|
const textLines = cleanComment.split('\n').filter(l => !l.startsWith('@'));
|
|
84
|
-
description = textLines.join(' ').trim() || "
|
|
84
|
+
description = textLines.join(' ').trim() || "No description";
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
let example = null;
|
|
@@ -104,14 +104,14 @@ export default function doc({ logger, frameworkRoot, run }: TyrContext) {
|
|
|
104
104
|
};
|
|
105
105
|
|
|
106
106
|
if (!fs.existsSync(libPath)) {
|
|
107
|
-
logger.error(`
|
|
107
|
+
logger.error(`Library folder not found: ${libPath}`);
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
const files = fs.readdirSync(libPath).filter(f => f.endsWith('.ts'));
|
|
112
112
|
|
|
113
113
|
if (files.length === 0) {
|
|
114
|
-
logger.warn("No
|
|
114
|
+
logger.warn("No .ts files found in /src/lib to document.");
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
const fileDocs = files.map(file => {
|
|
@@ -120,44 +120,44 @@ export default function doc({ logger, frameworkRoot, run }: TyrContext) {
|
|
|
120
120
|
|
|
121
121
|
const systemDocs: DocStructure = {
|
|
122
122
|
name: 'TyrContext (Kernel)',
|
|
123
|
-
description: '
|
|
123
|
+
description: 'Global utilities injected into every command. Accessible by destructuring the context.',
|
|
124
124
|
methods: [
|
|
125
125
|
{
|
|
126
126
|
name: 'run',
|
|
127
|
-
description: '
|
|
127
|
+
description: 'Programmatically runs another system command (command composition). Useful for a command to invoke others.',
|
|
128
128
|
example: `
|
|
129
|
-
//
|
|
129
|
+
// Calls the 'test' command passing extra arguments
|
|
130
130
|
const secret = "123";
|
|
131
131
|
args.push(secret);
|
|
132
132
|
await run('test', args);`.trim()
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
135
|
name: 'task',
|
|
136
|
-
description: 'Helper
|
|
136
|
+
description: 'Helper that wraps a critical operation. If it fails, the framework captures the error, adds context, and displays it cleanly in the console. Removes the need for manual try/catch.',
|
|
137
137
|
example: `
|
|
138
|
-
//
|
|
139
|
-
const buildId = await task('
|
|
138
|
+
// Example: async task that returns a value
|
|
139
|
+
const buildId = await task('Building project', async () => {
|
|
140
140
|
return await shell.exec('npm run build');
|
|
141
141
|
});
|
|
142
142
|
|
|
143
|
-
//
|
|
143
|
+
// If it fails, the log will say: "Task failed: Building project"`.trim()
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
name: 'fail',
|
|
147
|
-
description: '
|
|
147
|
+
description: 'Stops command execution immediately by throwing a controlled error. Allows adding a "suggestion" to help the user resolve the issue.',
|
|
148
148
|
example: `
|
|
149
|
-
//
|
|
149
|
+
// Use it for logic validations
|
|
150
150
|
if (!fs.existsSync('./package.json')) {
|
|
151
151
|
fail(
|
|
152
|
-
'
|
|
153
|
-
'
|
|
152
|
+
'npm package file not found',
|
|
153
|
+
'Run "npm init -y" to generate one.'
|
|
154
154
|
);
|
|
155
155
|
}`.trim()
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
name: 'logger',
|
|
159
|
-
description: '
|
|
160
|
-
example: `logger.info('
|
|
159
|
+
description: 'Standardised logging system with colours and formats.',
|
|
160
|
+
example: `logger.info('Starting...');\nlogger.success('Created');\nlogger.warn('Warning');`
|
|
161
161
|
}
|
|
162
162
|
]
|
|
163
163
|
};
|
|
@@ -192,7 +192,7 @@ if (!fs.existsSync('./package.json')) {
|
|
|
192
192
|
</head>
|
|
193
193
|
<body>
|
|
194
194
|
<nav>
|
|
195
|
-
<h3 style="color: #888; text-transform: uppercase; font-size: 0.8rem;">
|
|
195
|
+
<h3 style="color: #888; text-transform: uppercase; font-size: 0.8rem;">TS Modules</h3>
|
|
196
196
|
${docs.map(d => `<a href="#${d.name}">📦 ${d.name.replace('.ts', '')}</a>`).join('')}
|
|
197
197
|
</nav>
|
|
198
198
|
<main>
|
|
@@ -223,17 +223,17 @@ if (!fs.existsSync('./package.json')) {
|
|
|
223
223
|
const { name, prompt } = JSON.parse(body);
|
|
224
224
|
if (!name || !prompt) {
|
|
225
225
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
226
|
-
res.end(JSON.stringify({ success: false, message: '
|
|
226
|
+
res.end(JSON.stringify({ success: false, message: 'Missing required fields.' }));
|
|
227
227
|
return;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
await run('ai', [name, prompt]);
|
|
231
231
|
|
|
232
232
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
233
|
-
res.end(JSON.stringify({ success: true, message: `
|
|
233
|
+
res.end(JSON.stringify({ success: true, message: `Command '${name}' generated successfully at ~/.tyr/commands/${name}.tyr.ts` }));
|
|
234
234
|
} catch (e: any) {
|
|
235
235
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
236
|
-
res.end(JSON.stringify({ success: false, message: e.message || 'Error
|
|
236
|
+
res.end(JSON.stringify({ success: false, message: e.message || 'Error generating command.' }));
|
|
237
237
|
}
|
|
238
238
|
});
|
|
239
239
|
return;
|
|
@@ -244,8 +244,8 @@ if (!fs.existsSync('./package.json')) {
|
|
|
244
244
|
});
|
|
245
245
|
|
|
246
246
|
server.listen(PORT, () => {
|
|
247
|
-
logger.success(`
|
|
248
|
-
logger.info("
|
|
247
|
+
logger.success(`TS documentation ready at: http://localhost:${PORT}`);
|
|
248
|
+
logger.info("Press Ctrl+C to stop.");
|
|
249
249
|
});
|
|
250
250
|
};
|
|
251
251
|
};
|
package/src/core/sys/gen.ts
CHANGED
|
@@ -11,12 +11,12 @@ const template = `import type { TyrContext } from '@orxataguy/tyr';
|
|
|
11
11
|
|
|
12
12
|
export default ({ run, task, fail, logger, shell, fs }: TyrContext) => {
|
|
13
13
|
return async (args: string[]) => {
|
|
14
|
-
logger.info("
|
|
14
|
+
logger.info("Running command: %s");
|
|
15
15
|
|
|
16
|
-
//
|
|
17
|
-
//
|
|
16
|
+
// Your logic here...
|
|
17
|
+
// Run "tyr doc" to see the documentation for available managers
|
|
18
18
|
|
|
19
|
-
logger.success("
|
|
19
|
+
logger.success("Command %s finished!");
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
22
|
|
|
@@ -29,18 +29,18 @@ export default function gen({ logger, fs, userRoot }: TyrContext) {
|
|
|
29
29
|
const fileName = args[1];
|
|
30
30
|
|
|
31
31
|
if (!commandName || !fileName) {
|
|
32
|
-
logger.error('
|
|
33
|
-
logger.info('
|
|
32
|
+
logger.error('Incorrect usage.');
|
|
33
|
+
logger.info('Syntax: tyr gen [command-name] [file-name]');
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
logger.info(`
|
|
37
|
+
logger.info(`Creating new command: '${commandName}' -> '${fileName}.tyr.ts'`);
|
|
38
38
|
|
|
39
39
|
const commandsDir = path.join(userRoot, 'commands');
|
|
40
40
|
const filePath = path.join(commandsDir, `${fileName}.tyr.ts`);
|
|
41
41
|
|
|
42
42
|
if (fs.exists(filePath)) {
|
|
43
|
-
logger.error(`
|
|
43
|
+
logger.error(`File ${fileName}.tyr.ts already exists. Aborting.`);
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -56,7 +56,7 @@ export default function gen({ logger, fs, userRoot }: TyrContext) {
|
|
|
56
56
|
if (!config.commands) config.commands = {};
|
|
57
57
|
|
|
58
58
|
if (config.commands[commandName]) {
|
|
59
|
-
logger.warn(`
|
|
59
|
+
logger.warn(`Command '${commandName}' already existed. Updating path...`);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// Store path relative to userRoot so it remains portable
|
|
@@ -65,10 +65,10 @@ export default function gen({ logger, fs, userRoot }: TyrContext) {
|
|
|
65
65
|
const newYaml = yaml.dump(config, { indent: 2, lineWidth: -1 });
|
|
66
66
|
await fs.write(mapPath, newYaml);
|
|
67
67
|
|
|
68
|
-
logger.success(`
|
|
69
|
-
logger.success(`
|
|
68
|
+
logger.success(`Command '${commandName}' created at ${filePath}`);
|
|
69
|
+
logger.success(`Registered in ${mapPath}`);
|
|
70
70
|
} catch (e) {
|
|
71
|
-
logger.error('Error
|
|
71
|
+
logger.error('Error updating configuration.');
|
|
72
72
|
console.error(e);
|
|
73
73
|
}
|
|
74
74
|
};
|
package/src/core/sys/help.ts
CHANGED
|
@@ -9,8 +9,8 @@ interface CommandDoc {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Extracts the first JSDoc block from a .tyr.ts file and parses it
|
|
13
|
+
* into a description and usage examples.
|
|
14
14
|
*/
|
|
15
15
|
function parseCommandDoc(filePath: string): CommandDoc {
|
|
16
16
|
const fileName = path.basename(filePath, '.tyr.ts');
|
|
@@ -21,12 +21,12 @@ function parseCommandDoc(filePath: string): CommandDoc {
|
|
|
21
21
|
return { name: fileName, description: '', usage: '' };
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
//
|
|
24
|
+
// Clean each line: remove leading * and spaces
|
|
25
25
|
const lines = match[1]
|
|
26
26
|
.split('\n')
|
|
27
27
|
.map(line => line.replace(/^\s*\*\s?/, '').trimEnd());
|
|
28
28
|
|
|
29
|
-
//
|
|
29
|
+
// Split into description and "Usage:" block
|
|
30
30
|
const usoIndex = lines.findIndex(l => /^uso:/i.test(l.trim()));
|
|
31
31
|
|
|
32
32
|
let description = '';
|
|
@@ -70,19 +70,19 @@ export default function help({ userRoot }: TyrContext) {
|
|
|
70
70
|
const separator = `${gray} ${'─'.repeat(50)}${reset}`;
|
|
71
71
|
|
|
72
72
|
console.log('');
|
|
73
|
-
console.log(` ${bold}${cyan}tyr${reset} ${white}
|
|
73
|
+
console.log(` ${bold}${cyan}tyr${reset} ${white}Available commands${reset}`);
|
|
74
74
|
console.log(separator);
|
|
75
75
|
console.log('');
|
|
76
76
|
|
|
77
|
-
//
|
|
77
|
+
// Framework flags and built-in commands
|
|
78
78
|
const builtins = [
|
|
79
|
-
{ name: '--help', description: '
|
|
80
|
-
{ name: '--version', description: '
|
|
81
|
-
{ name: '--config', description: '
|
|
82
|
-
{ name: '--update', description: '
|
|
83
|
-
{ name: '--upgrade', description: '
|
|
84
|
-
{ name: 'gen', description: '
|
|
85
|
-
{ name: 'doc', description: '
|
|
79
|
+
{ name: '--help', description: 'Shows this command listing.', usage: 'tyr --help' },
|
|
80
|
+
{ name: '--version', description: 'Shows the installed version of tyr.', usage: 'tyr --version' },
|
|
81
|
+
{ name: '--config', description: 'Configures tyr for the first time.', usage: 'tyr --config' },
|
|
82
|
+
{ name: '--update', description: 'Updates ~/.tyr from the git repository.', usage: 'tyr --update' },
|
|
83
|
+
{ name: '--upgrade', description: 'Upgrades the tyr npm package.', usage: 'tyr --upgrade' },
|
|
84
|
+
{ name: 'gen', description: 'Generates a new command from a description using AI.', usage: 'tyr gen <name> "<description>"' },
|
|
85
|
+
{ name: 'doc', description: 'Opens the framework documentation in the browser.', usage: 'tyr doc' },
|
|
86
86
|
];
|
|
87
87
|
|
|
88
88
|
console.log(` ${bold}${yellow}Framework${reset}`);
|
|
@@ -94,10 +94,10 @@ export default function help({ userRoot }: TyrContext) {
|
|
|
94
94
|
console.log('');
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
//
|
|
97
|
+
// User commands in ~/.tyr/commands/
|
|
98
98
|
if (!fs.existsSync(commandsDir)) {
|
|
99
99
|
console.log(separator);
|
|
100
|
-
console.log(` ${yellow}
|
|
100
|
+
console.log(` ${yellow}Commands folder not found: ${commandsDir}${reset}`);
|
|
101
101
|
console.log('');
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
@@ -108,14 +108,14 @@ export default function help({ userRoot }: TyrContext) {
|
|
|
108
108
|
|
|
109
109
|
if (files.length === 0) {
|
|
110
110
|
console.log(separator);
|
|
111
|
-
console.log(` ${dim}No
|
|
111
|
+
console.log(` ${dim}No commands in ${commandsDir}${reset}`);
|
|
112
112
|
console.log('');
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
console.log(separator);
|
|
117
117
|
console.log('');
|
|
118
|
-
console.log(` ${bold}${yellow}
|
|
118
|
+
console.log(` ${bold}${yellow}User commands${reset} ${gray}(~/.tyr/commands/)${reset}`);
|
|
119
119
|
console.log('');
|
|
120
120
|
|
|
121
121
|
for (const file of files) {
|
|
@@ -128,12 +128,12 @@ export default function help({ userRoot }: TyrContext) {
|
|
|
128
128
|
console.log(` ${dim}${line}${reset}`);
|
|
129
129
|
}
|
|
130
130
|
} else {
|
|
131
|
-
console.log(` ${gray}
|
|
131
|
+
console.log(` ${gray}No description${reset}`);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
if (doc.usage) {
|
|
135
135
|
console.log('');
|
|
136
|
-
console.log(` ${gray}
|
|
136
|
+
console.log(` ${gray} Usage:${reset}`);
|
|
137
137
|
for (const line of doc.usage.split('\n')) {
|
|
138
138
|
console.log(` ${cyan} ${line}${reset}`);
|
|
139
139
|
}
|
|
@@ -143,7 +143,7 @@ export default function help({ userRoot }: TyrContext) {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
console.log(separator);
|
|
146
|
-
console.log(` ${dim}
|
|
146
|
+
console.log(` ${dim}Generate a new command with ${cyan}tyr gen <name> "<what it should do>"${reset}`);
|
|
147
147
|
console.log('');
|
|
148
148
|
};
|
|
149
149
|
}
|
package/src/core/sys/rem.ts
CHANGED
|
@@ -12,25 +12,25 @@ export default function rem({ logger, fs, userRoot }: TyrContext) {
|
|
|
12
12
|
const commandName = args[0];
|
|
13
13
|
|
|
14
14
|
if (!commandName) {
|
|
15
|
-
logger.error('
|
|
15
|
+
logger.error('Missing command name to remove.');
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
logger.info(`
|
|
19
|
+
logger.info(`Starting removal of command: '${commandName}'`);
|
|
20
20
|
|
|
21
21
|
const mapPath = path.join(userRoot, 'map.yml');
|
|
22
22
|
|
|
23
23
|
try {
|
|
24
24
|
const currentConfigRaw = await fs.read(mapPath);
|
|
25
25
|
if (!currentConfigRaw) {
|
|
26
|
-
logger.error(
|
|
26
|
+
logger.error(`${mapPath} not found. Run 'tyr --config' first.`);
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const config = yaml.load(currentConfigRaw) as TyrConfig;
|
|
31
31
|
|
|
32
32
|
if (!config.commands?.[commandName]) {
|
|
33
|
-
logger.error(`
|
|
33
|
+
logger.error(`Command '${commandName}' does not exist in ~/.tyr/map.yml.`);
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -44,7 +44,7 @@ export default function rem({ logger, fs, userRoot }: TyrContext) {
|
|
|
44
44
|
for (const [alias, target] of Object.entries(config.aliases)) {
|
|
45
45
|
if (target === commandName) {
|
|
46
46
|
delete config.aliases[alias];
|
|
47
|
-
logger.info(`Alias '${alias}'
|
|
47
|
+
logger.info(`Alias '${alias}' removed.`);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -52,9 +52,9 @@ export default function rem({ logger, fs, userRoot }: TyrContext) {
|
|
|
52
52
|
const newYaml = yaml.dump(config, { indent: 2, lineWidth: -1 });
|
|
53
53
|
await fs.write(mapPath, newYaml);
|
|
54
54
|
|
|
55
|
-
logger.success(`
|
|
55
|
+
logger.success(`Command '${commandName}' removed.`);
|
|
56
56
|
} catch (e) {
|
|
57
|
-
logger.error('
|
|
57
|
+
logger.error('Critical error during removal.');
|
|
58
58
|
console.error(e);
|
|
59
59
|
}
|
|
60
60
|
};
|
package/src/lib/GitManager.ts
CHANGED
|
@@ -74,16 +74,16 @@ export class GitManager {
|
|
|
74
74
|
* await git.cloneTo('git@github.com:org/repo.git', '/path/to/dest');
|
|
75
75
|
*/
|
|
76
76
|
public async cloneTo(repoUrl: string, destDir: string): Promise<void> {
|
|
77
|
-
this.logger.info(`
|
|
78
|
-
const loader = this.shell.showLoader('
|
|
77
|
+
this.logger.info(`Cloning ${repoUrl}...`);
|
|
78
|
+
const loader = this.shell.showLoader('Cloning repository...');
|
|
79
79
|
try {
|
|
80
80
|
await this.shell.exec(`git clone "${repoUrl}" "${destDir}"`);
|
|
81
81
|
await this.shell.exec(`git -C "${destDir}" config --add core.filemode false`);
|
|
82
82
|
loader.stop();
|
|
83
|
-
this.logger.success('
|
|
83
|
+
this.logger.success('Cloning complete.');
|
|
84
84
|
} catch (e) {
|
|
85
85
|
loader.stop();
|
|
86
|
-
throw new TyrError(`
|
|
86
|
+
throw new TyrError(`Could not clone repository: ${repoUrl}`, e, 'Check that the repository exists and that you have permission to clone it.');
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -118,9 +118,9 @@ export class GitManager {
|
|
|
118
118
|
await this.shell.exec(
|
|
119
119
|
`cd "${dir}" && rm -rf .git && git init -b master && git remote add origin "${remoteUrl}" && git config --add core.filemode false && echo 'node_modules' >> .gitignore`
|
|
120
120
|
);
|
|
121
|
-
this.logger.success(`
|
|
121
|
+
this.logger.success(`Git repository initialized at ${dir}`);
|
|
122
122
|
} catch (e) {
|
|
123
|
-
throw new TyrError(`
|
|
123
|
+
throw new TyrError(`Could not initialize git repository at ${dir}`, e);
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
}
|
package/src/lib/JiraManager.ts
CHANGED
|
@@ -74,18 +74,18 @@ export class JiraManager {
|
|
|
74
74
|
name: `${i.key} - ${i.summary} [${i.status}]`,
|
|
75
75
|
value: i.key,
|
|
76
76
|
})),
|
|
77
|
-
{ name: '
|
|
78
|
-
{ name: '
|
|
77
|
+
{ name: 'No ticket (enter branch manually)', value: '__manual__' },
|
|
78
|
+
{ name: 'Skip (do not create branch)', value: '__skip__' },
|
|
79
79
|
];
|
|
80
80
|
|
|
81
|
-
const selected = await this.shell.select(choices, '
|
|
81
|
+
const selected = await this.shell.select(choices, 'Select a Jira ticket:');
|
|
82
82
|
|
|
83
83
|
if (selected === '__skip__') return null;
|
|
84
84
|
if (selected === '__manual__') return this.askForBranch();
|
|
85
85
|
|
|
86
86
|
return selected;
|
|
87
87
|
} catch {
|
|
88
|
-
this.logger.warn('
|
|
88
|
+
this.logger.warn('Could not connect to Jira. Manual branch entry.');
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -93,7 +93,7 @@ export class JiraManager {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
private async askForBranch(): Promise<string | null> {
|
|
96
|
-
const raw = await this.shell.input('
|
|
96
|
+
const raw = await this.shell.input('Enter the new branch name (empty to skip):');
|
|
97
97
|
if (!raw.trim()) return null;
|
|
98
98
|
// Normalise: take the last segment separated by '/'
|
|
99
99
|
return raw.trim().split('/').pop()?.toUpperCase() ?? raw.trim();
|
package/src/lib/MongoManager.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { MongoClient, Db, Document, Filter, UpdateFilter, InsertOneResult, Inser
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @class MongoManager
|
|
5
|
-
* @description
|
|
5
|
+
* @description MongoDB connector that manages the connection lifecycle and exposes a generic CRUD interface.
|
|
6
6
|
*/
|
|
7
7
|
export class MongoManager {
|
|
8
8
|
private client!: MongoClient;
|
|
@@ -32,10 +32,10 @@ export class MongoManager {
|
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* @method insertOne
|
|
35
|
-
* @description
|
|
36
|
-
* @param {string} collection -
|
|
37
|
-
* @param {Document} document -
|
|
38
|
-
* @returns {Promise<InsertOneResult>}
|
|
35
|
+
* @description Inserts a document into the specified collection.
|
|
36
|
+
* @param {string} collection - Collection name.
|
|
37
|
+
* @param {Document} document - Document to insert.
|
|
38
|
+
* @returns {Promise<InsertOneResult>} Insertion result.
|
|
39
39
|
* @example
|
|
40
40
|
* const result = await mongo.insertOne('users', { name: 'Ana', age: 30 });
|
|
41
41
|
*/
|
|
@@ -48,10 +48,10 @@ export class MongoManager {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* @method insertMany
|
|
51
|
-
* @description
|
|
52
|
-
* @param {string} collection -
|
|
53
|
-
* @param {Document[]} documents - Array
|
|
54
|
-
* @returns {Promise<InsertManyResult>}
|
|
51
|
+
* @description Inserts multiple documents into the specified collection.
|
|
52
|
+
* @param {string} collection - Collection name.
|
|
53
|
+
* @param {Document[]} documents - Array of documents to insert.
|
|
54
|
+
* @returns {Promise<InsertManyResult>} Insertion result.
|
|
55
55
|
* @example
|
|
56
56
|
* const result = await mongo.insertMany('users', [{ name: 'Ana' }, { name: 'Luis' }]);
|
|
57
57
|
*/
|
|
@@ -64,11 +64,11 @@ export class MongoManager {
|
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* @method findOne
|
|
67
|
-
* @description
|
|
68
|
-
* @param {string} collection -
|
|
69
|
-
* @param {Filter<Document>} filter -
|
|
70
|
-
* @param {FindOptions} [options] -
|
|
71
|
-
* @returns {Promise<WithId<T> | null>}
|
|
67
|
+
* @description Finds the first document matching the filter.
|
|
68
|
+
* @param {string} collection - Collection name.
|
|
69
|
+
* @param {Filter<Document>} filter - Search filter.
|
|
70
|
+
* @param {FindOptions} [options] - Additional options (projection, etc.).
|
|
71
|
+
* @returns {Promise<WithId<T> | null>} The matching document or null.
|
|
72
72
|
* @example
|
|
73
73
|
* const user = await mongo.findOne('users', { name: 'Ana' });
|
|
74
74
|
*/
|
|
@@ -81,11 +81,11 @@ export class MongoManager {
|
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
* @method find
|
|
84
|
-
* @description
|
|
85
|
-
* @param {string} collection -
|
|
86
|
-
* @param {Filter<Document>} filter -
|
|
87
|
-
* @param {FindOptions} [options] -
|
|
88
|
-
* @returns {Promise<WithId<T>[]>} Array
|
|
84
|
+
* @description Finds all documents matching the filter.
|
|
85
|
+
* @param {string} collection - Collection name.
|
|
86
|
+
* @param {Filter<Document>} filter - Search filter. Use {} to return all documents.
|
|
87
|
+
* @param {FindOptions} [options] - Additional options (projection, limit, etc.).
|
|
88
|
+
* @returns {Promise<WithId<T>[]>} Array of matching documents.
|
|
89
89
|
* @example
|
|
90
90
|
* const users = await mongo.find('users', { age: { $gte: 18 } });
|
|
91
91
|
*/
|
|
@@ -98,11 +98,11 @@ export class MongoManager {
|
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
100
|
* @method updateOne
|
|
101
|
-
* @description
|
|
102
|
-
* @param {string} collection -
|
|
103
|
-
* @param {Filter<Document>} filter -
|
|
104
|
-
* @param {UpdateFilter<Document>} update -
|
|
105
|
-
* @returns {Promise<UpdateResult>}
|
|
101
|
+
* @description Updates the first document matching the filter.
|
|
102
|
+
* @param {string} collection - Collection name.
|
|
103
|
+
* @param {Filter<Document>} filter - Filter to identify the document.
|
|
104
|
+
* @param {UpdateFilter<Document>} update - Update operation (e.g. { $set: { field: value } }).
|
|
105
|
+
* @returns {Promise<UpdateResult>} Update result.
|
|
106
106
|
* @example
|
|
107
107
|
* const result = await mongo.updateOne('users', { name: 'Ana' }, { $set: { age: 31 } });
|
|
108
108
|
*/
|
|
@@ -115,11 +115,11 @@ export class MongoManager {
|
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
117
|
* @method updateMany
|
|
118
|
-
* @description
|
|
119
|
-
* @param {string} collection -
|
|
120
|
-
* @param {Filter<Document>} filter -
|
|
121
|
-
* @param {UpdateFilter<Document>} update -
|
|
122
|
-
* @returns {Promise<UpdateResult>}
|
|
118
|
+
* @description Updates all documents matching the filter.
|
|
119
|
+
* @param {string} collection - Collection name.
|
|
120
|
+
* @param {Filter<Document>} filter - Filter to identify the documents.
|
|
121
|
+
* @param {UpdateFilter<Document>} update - Update operation.
|
|
122
|
+
* @returns {Promise<UpdateResult>} Update result.
|
|
123
123
|
* @example
|
|
124
124
|
* const result = await mongo.updateMany('users', { active: false }, { $set: { active: true } });
|
|
125
125
|
*/
|
|
@@ -132,10 +132,10 @@ export class MongoManager {
|
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
134
|
* @method deleteOne
|
|
135
|
-
* @description
|
|
136
|
-
* @param {string} collection -
|
|
137
|
-
* @param {Filter<Document>} filter -
|
|
138
|
-
* @returns {Promise<DeleteResult>}
|
|
135
|
+
* @description Deletes the first document matching the filter.
|
|
136
|
+
* @param {string} collection - Collection name.
|
|
137
|
+
* @param {Filter<Document>} filter - Filter to identify the document.
|
|
138
|
+
* @returns {Promise<DeleteResult>} Deletion result.
|
|
139
139
|
* @example
|
|
140
140
|
* const result = await mongo.deleteOne('users', { name: 'Ana' });
|
|
141
141
|
*/
|
|
@@ -148,10 +148,10 @@ export class MongoManager {
|
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
150
|
* @method deleteMany
|
|
151
|
-
* @description
|
|
152
|
-
* @param {string} collection -
|
|
153
|
-
* @param {Filter<Document>} filter -
|
|
154
|
-
* @returns {Promise<DeleteResult>}
|
|
151
|
+
* @description Deletes all documents matching the filter.
|
|
152
|
+
* @param {string} collection - Collection name.
|
|
153
|
+
* @param {Filter<Document>} filter - Filter to identify the documents.
|
|
154
|
+
* @returns {Promise<DeleteResult>} Deletion result.
|
|
155
155
|
* @example
|
|
156
156
|
* const result = await mongo.deleteMany('users', { active: false });
|
|
157
157
|
*/
|
|
@@ -165,7 +165,7 @@ export class MongoManager {
|
|
|
165
165
|
|
|
166
166
|
/**
|
|
167
167
|
* @object MongoManagerTests
|
|
168
|
-
* @description
|
|
168
|
+
* @description Test parameters to validate MongoManager functionality.
|
|
169
169
|
*/
|
|
170
170
|
export const MongoManagerTests = {
|
|
171
171
|
// insertOne: { collection: 'test', document: { name: 'test_doc', value: 1 } },
|
package/src/lib/SQLManager.ts
CHANGED
|
@@ -3,7 +3,7 @@ import sql, { config as SQLConfig } from 'mssql';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @class SQLManager
|
|
6
|
-
* @description
|
|
6
|
+
* @description SQL Server database connector.
|
|
7
7
|
*/
|
|
8
8
|
export class SQLManager {
|
|
9
9
|
private pool!: sql.ConnectionPool;
|
|
@@ -33,12 +33,12 @@ export class SQLManager {
|
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* @method select
|
|
36
|
-
* @description
|
|
37
|
-
* @param {string} query -
|
|
38
|
-
* @returns {Promise<any[]>}
|
|
36
|
+
* @description Executes a SELECT command on SQL Server and returns the result as JSON.
|
|
37
|
+
* @param {string} query - The full SELECT command.
|
|
38
|
+
* @returns {Promise<any[]>} The result records.
|
|
39
39
|
* @example
|
|
40
40
|
* await dbManager.init();
|
|
41
|
-
* const data = await dbManager.select('SELECT * FROM
|
|
41
|
+
* const data = await dbManager.select('SELECT * FROM table');
|
|
42
42
|
*/
|
|
43
43
|
public async select(query: string): Promise<any[]> {
|
|
44
44
|
await this.init();
|
|
@@ -51,9 +51,9 @@ export class SQLManager {
|
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* @method searchBrokerOnDB
|
|
54
|
-
* @description
|
|
55
|
-
* @param {string | URL} url - URL
|
|
56
|
-
* @returns {Promise<string>}
|
|
54
|
+
* @description Looks up a broker by hostname using the encoded query.
|
|
55
|
+
* @param {string | URL} url - URL or string to extract the hostname from.
|
|
56
|
+
* @returns {Promise<string>} Broker name.
|
|
57
57
|
* @example
|
|
58
58
|
* const broker = await db.searchBrokerOnDB('https://www.foo.com');
|
|
59
59
|
*/
|
|
@@ -88,7 +88,7 @@ export class SQLManager {
|
|
|
88
88
|
await this.close();
|
|
89
89
|
|
|
90
90
|
if (!result.recordset[0] || !result.recordset[0].BROKER) {
|
|
91
|
-
throw new Error(`No
|
|
91
|
+
throw new Error(`No broker found for ${urlObj}`);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
return result.recordset[0].BROKER as string;
|
|
@@ -104,7 +104,7 @@ export class SQLManager {
|
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
106
|
* @object SQLManagerTests
|
|
107
|
-
* @description
|
|
107
|
+
* @description Test parameters to validate SQLManager functionality.
|
|
108
108
|
*/
|
|
109
109
|
export const SQLManagerTests = {
|
|
110
110
|
// init: {},
|
package/src/lib/SetupManager.ts
CHANGED
|
@@ -203,18 +203,18 @@ export class SetupManager {
|
|
|
203
203
|
|
|
204
204
|
if (await this.binExists('docker-compose')) return 'docker-compose';
|
|
205
205
|
|
|
206
|
-
this.logger.warn('docker-compose
|
|
206
|
+
this.logger.warn('docker-compose not found. Attempting to install...');
|
|
207
207
|
|
|
208
208
|
const rawArch = await this.shell.exec('uname -m').catch(() => 'x86_64');
|
|
209
209
|
const osName = await this.shell.exec("uname -s | tr '[:upper:]' '[:lower:]'").catch(() => 'linux');
|
|
210
210
|
const bin = '/usr/local/bin/docker-compose';
|
|
211
211
|
const url = `https://github.com/docker/compose/releases/latest/download/docker-compose-${osName.trim()}-${rawArch.trim()}`;
|
|
212
212
|
|
|
213
|
-
this.logger.info('
|
|
213
|
+
this.logger.info('Downloading docker-compose from GitHub...');
|
|
214
214
|
try {
|
|
215
215
|
await this.shell.exec(`curl -fsSL "${url}" -o "${bin}" && chmod +x "${bin}"`);
|
|
216
216
|
if (await this.shell.exec('docker-compose version 2>/dev/null').then(() => true).catch(() => false)) {
|
|
217
|
-
this.logger.success('docker-compose
|
|
217
|
+
this.logger.success('docker-compose installed successfully.');
|
|
218
218
|
return 'docker-compose';
|
|
219
219
|
}
|
|
220
220
|
} catch { /* fall through to pip */ }
|
|
@@ -226,14 +226,14 @@ export class SetupManager {
|
|
|
226
226
|
`${pip.trim()} install --quiet docker-compose --break-system-packages 2>/dev/null || ${pip.trim()} install --quiet docker-compose`
|
|
227
227
|
);
|
|
228
228
|
if (await this.binExists('docker-compose')) {
|
|
229
|
-
this.logger.success('docker-compose
|
|
229
|
+
this.logger.success('docker-compose installed via pip.');
|
|
230
230
|
return 'docker-compose';
|
|
231
231
|
}
|
|
232
232
|
} catch { /* fall through */ }
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
this.logger.warn('
|
|
236
|
-
this.logger.warn('
|
|
235
|
+
this.logger.warn('Could not install docker-compose automatically.');
|
|
236
|
+
this.logger.warn('Install it manually: https://docs.docker.com/compose/install/');
|
|
237
237
|
return null;
|
|
238
238
|
}
|
|
239
239
|
|
|
@@ -249,7 +249,7 @@ export class SetupManager {
|
|
|
249
249
|
.then(() => true).catch(() => false);
|
|
250
250
|
if (ok) return;
|
|
251
251
|
|
|
252
|
-
this.logger.info('
|
|
252
|
+
this.logger.info('Installing docker buildx plugin...');
|
|
253
253
|
const rawArch = await this.shell.exec('uname -m').catch(() => 'x86_64');
|
|
254
254
|
const arch = rawArch.trim().replace('x86_64', 'amd64').replace('aarch64', 'arm64');
|
|
255
255
|
const osName = await this.shell.exec("uname -s | tr '[:upper:]' '[:lower:]'").catch(() => 'linux');
|
|
@@ -261,16 +261,16 @@ export class SetupManager {
|
|
|
261
261
|
).catch(() => '');
|
|
262
262
|
|
|
263
263
|
if (!urlRaw.trim()) {
|
|
264
|
-
this.logger.warn('
|
|
264
|
+
this.logger.warn('Could not fetch the docker buildx URL. The build may show warnings.');
|
|
265
265
|
return;
|
|
266
266
|
}
|
|
267
267
|
try {
|
|
268
268
|
await this.shell.exec(
|
|
269
269
|
`mkdir -p "${pluginDir}" && wget -qO "${pluginDir}/docker-buildx" "${urlRaw.trim()}" && chmod +x "${pluginDir}/docker-buildx"`
|
|
270
270
|
);
|
|
271
|
-
this.logger.success('docker buildx
|
|
271
|
+
this.logger.success('docker buildx installed.');
|
|
272
272
|
} catch {
|
|
273
|
-
this.logger.warn('
|
|
273
|
+
this.logger.warn('Could not install docker buildx. The build may show warnings.');
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
|
|
@@ -292,13 +292,13 @@ export class SetupManager {
|
|
|
292
292
|
const invokePrefix = hasExisting ? 'make -f Makefile.dev' : 'make';
|
|
293
293
|
|
|
294
294
|
if (this.fs.exists(makefilePath)) {
|
|
295
|
-
this.logger.info(`${makefileName}
|
|
295
|
+
this.logger.info(`${makefileName} already exists. Skipping.`);
|
|
296
296
|
return;
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
const TAB = '\t';
|
|
300
300
|
const content = [
|
|
301
|
-
`# ${makefileName}
|
|
301
|
+
`# ${makefileName} generated by setup-dev`,
|
|
302
302
|
'',
|
|
303
303
|
'up:', TAB + 'docker-compose up -d', '',
|
|
304
304
|
'build:', TAB + 'docker-compose up -d --build', '',
|
|
@@ -309,12 +309,12 @@ export class SetupManager {
|
|
|
309
309
|
].join('\n');
|
|
310
310
|
|
|
311
311
|
await this.fs.write(makefilePath, content);
|
|
312
|
-
this.logger.success(`${makefileName}
|
|
313
|
-
this.logger.info(` ${invokePrefix} up ->
|
|
314
|
-
this.logger.info(` ${invokePrefix} build -> rebuild
|
|
315
|
-
this.logger.info(` ${invokePrefix} stop ->
|
|
316
|
-
this.logger.info(` ${invokePrefix} logs ->
|
|
317
|
-
this.logger.info(` ${invokePrefix} ps ->
|
|
312
|
+
this.logger.success(`${makefileName} generated at: ${makefilePath}`);
|
|
313
|
+
this.logger.info(` ${invokePrefix} up -> start containers`);
|
|
314
|
+
this.logger.info(` ${invokePrefix} build -> rebuild and start`);
|
|
315
|
+
this.logger.info(` ${invokePrefix} stop -> stop and remove (no orphans)`);
|
|
316
|
+
this.logger.info(` ${invokePrefix} logs -> watch logs in real time`);
|
|
317
|
+
this.logger.info(` ${invokePrefix} ps -> container status`);
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
|
package/src/lib/ShellManager.ts
CHANGED
|
@@ -123,7 +123,7 @@ export class ShellManager {
|
|
|
123
123
|
* @param {boolean} defaultValue - Default answer if user presses Enter (default: false).
|
|
124
124
|
* @returns {Promise<boolean>} True if the user confirmed.
|
|
125
125
|
* @example
|
|
126
|
-
* const ok = await shell.confirm('
|
|
126
|
+
* const ok = await shell.confirm('Continue?', false);
|
|
127
127
|
*/
|
|
128
128
|
public async confirm(question: string, defaultValue: boolean = false): Promise<boolean> {
|
|
129
129
|
try {
|
|
@@ -135,7 +135,7 @@ export class ShellManager {
|
|
|
135
135
|
}]);
|
|
136
136
|
return result.value;
|
|
137
137
|
} catch (e) {
|
|
138
|
-
throw new TyrError(`Error
|
|
138
|
+
throw new TyrError(`Error showing confirmation: ${question}`, e);
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
@@ -146,7 +146,7 @@ export class ShellManager {
|
|
|
146
146
|
* @param {string} question - The question to display.
|
|
147
147
|
* @returns {Promise<string>} The selected value.
|
|
148
148
|
* @example
|
|
149
|
-
* const branch = await shell.select([{ name: 'main', value: 'main' }], '
|
|
149
|
+
* const branch = await shell.select([{ name: 'main', value: 'main' }], 'Which branch?');
|
|
150
150
|
*/
|
|
151
151
|
public async select(choices: { name: string; value: string }[], question: string): Promise<string> {
|
|
152
152
|
try {
|
|
@@ -158,7 +158,7 @@ export class ShellManager {
|
|
|
158
158
|
}]);
|
|
159
159
|
return result.value;
|
|
160
160
|
} catch (e) {
|
|
161
|
-
throw new TyrError(`Error
|
|
161
|
+
throw new TyrError(`Error showing selection: ${question}`, e);
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -169,7 +169,7 @@ export class ShellManager {
|
|
|
169
169
|
* @param {string} question - The question to display.
|
|
170
170
|
* @returns {Promise<string[]>} The selected values.
|
|
171
171
|
* @example
|
|
172
|
-
* const widgets = await shell.checkbox(choices, '
|
|
172
|
+
* const widgets = await shell.checkbox(choices, 'Which widgets to include?');
|
|
173
173
|
*/
|
|
174
174
|
public async checkbox(choices: { name: string; value: string }[], question: string): Promise<string[]> {
|
|
175
175
|
try {
|
|
@@ -181,7 +181,7 @@ export class ShellManager {
|
|
|
181
181
|
}]);
|
|
182
182
|
return result.value;
|
|
183
183
|
} catch (e) {
|
|
184
|
-
throw new TyrError(`Error
|
|
184
|
+
throw new TyrError(`Error showing options: ${question}`, e);
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
}
|
|
@@ -195,7 +195,7 @@ export const ShellManagerTests = {
|
|
|
195
195
|
cd: { path: '/tmp' },
|
|
196
196
|
input: { question: 'Enter a test value:' },
|
|
197
197
|
showLoader: { message: 'Loading test...' },
|
|
198
|
-
confirm: { question: '
|
|
199
|
-
select: { choices: [{ name: '
|
|
200
|
-
checkbox: { choices: [{ name: 'Item 1', value: '1' }, { name: 'Item 2', value: '2' }], question: '
|
|
198
|
+
confirm: { question: 'Continue?', defaultValue: false },
|
|
199
|
+
select: { choices: [{ name: 'Option A', value: 'a' }, { name: 'Option B', value: 'b' }], question: 'Which one?' },
|
|
200
|
+
checkbox: { choices: [{ name: 'Item 1', value: '1' }, { name: 'Item 2', value: '2' }], question: 'Which ones?' },
|
|
201
201
|
};
|
|
@@ -25,29 +25,29 @@ export class WorkspaceManager {
|
|
|
25
25
|
* If it does, asks the user if they want to replace it (deleting it first).
|
|
26
26
|
* Returns true if the caller should proceed with creating the workspace.
|
|
27
27
|
* @param {string} dirPath - Absolute path to the workspace directory.
|
|
28
|
-
* @param {string} type - Human-readable type name shown in messages (e.g. '
|
|
28
|
+
* @param {string} type - Human-readable type name shown in messages (e.g. 'integration', 'web').
|
|
29
29
|
* @returns {Promise<boolean>} True if the workspace can be created/overwritten.
|
|
30
30
|
* @example
|
|
31
|
-
* const proceed = await workspace.checkExisting('/path/to/repo', '
|
|
31
|
+
* const proceed = await workspace.checkExisting('/path/to/repo', 'integration');
|
|
32
32
|
* if (!proceed) return;
|
|
33
33
|
*/
|
|
34
|
-
public async checkExisting(dirPath: string, type: string = '
|
|
34
|
+
public async checkExisting(dirPath: string, type: string = 'directory'): Promise<boolean> {
|
|
35
35
|
if (!this.fs.exists(dirPath)) return true;
|
|
36
36
|
|
|
37
|
-
this.logger.warn(`
|
|
38
|
-
const replace = await this.shell.confirm('
|
|
37
|
+
this.logger.warn(`This ${type} already exists: ${dirPath}`);
|
|
38
|
+
const replace = await this.shell.confirm('Do you want to replace it?', false);
|
|
39
39
|
|
|
40
40
|
if (!replace) {
|
|
41
|
-
this.logger.info('
|
|
41
|
+
this.logger.info('Operation cancelled.');
|
|
42
42
|
return false;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
try {
|
|
46
46
|
await this.shell.exec(`rm -rf "${dirPath}"`);
|
|
47
|
-
this.logger.info('
|
|
47
|
+
this.logger.info('Existing directory removed.');
|
|
48
48
|
return true;
|
|
49
49
|
} catch (e) {
|
|
50
|
-
throw new TyrError(`
|
|
50
|
+
throw new TyrError(`Could not remove existing directory: ${dirPath}`, e);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -65,9 +65,9 @@ export class WorkspaceManager {
|
|
|
65
65
|
if (branch) {
|
|
66
66
|
try {
|
|
67
67
|
await this.shell.exec(`git -C "${dir}" checkout -b ${branch}`);
|
|
68
|
-
this.logger.success(`
|
|
68
|
+
this.logger.success(`Branch '${branch}' created.`);
|
|
69
69
|
} catch (e) {
|
|
70
|
-
this.logger.warn(`
|
|
70
|
+
this.logger.warn(`Could not create branch '${branch}'. It may already exist.`);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -75,13 +75,13 @@ export class WorkspaceManager {
|
|
|
75
75
|
try {
|
|
76
76
|
await this.shell.exec(`code "${dir}"`);
|
|
77
77
|
} catch {
|
|
78
|
-
this.logger.warn('
|
|
78
|
+
this.logger.warn('Could not open VSCode. Make sure the "code" command is installed.');
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
export const WorkspaceManagerTests = {
|
|
85
|
-
checkExisting: { dirPath: '/tmp/tyr-workspace-test', type: '
|
|
85
|
+
checkExisting: { dirPath: '/tmp/tyr-workspace-test', type: 'integration' },
|
|
86
86
|
tagWorkspace: { dir: '/tmp/tyr-workspace-test', branch: null, openCode: false },
|
|
87
87
|
};
|