hostfn 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1136 -0
  3. package/_conduct/specs/1.v0.spec.md +1041 -0
  4. package/examples/express-api/package.json +22 -0
  5. package/examples/express-api/src/index.ts +16 -0
  6. package/examples/express-api/tsconfig.json +11 -0
  7. package/examples/github-actions-deploy.yml +40 -0
  8. package/examples/monorepo-config.json +76 -0
  9. package/examples/monorepo-multi-server-config.json +74 -0
  10. package/package.json +39 -0
  11. package/packages/cli/package.json +40 -0
  12. package/packages/cli/src/__tests__/core/backup.test.ts +137 -0
  13. package/packages/cli/src/__tests__/core/health.test.ts +125 -0
  14. package/packages/cli/src/__tests__/core/lock.test.ts +173 -0
  15. package/packages/cli/src/__tests__/core/nginx-multi-domain.test.ts +176 -0
  16. package/packages/cli/src/__tests__/runtimes/pm2.test.ts +130 -0
  17. package/packages/cli/src/__tests__/utils/validation.test.ts +164 -0
  18. package/packages/cli/src/commands/deploy.ts +817 -0
  19. package/packages/cli/src/commands/env.ts +391 -0
  20. package/packages/cli/src/commands/expose.ts +438 -0
  21. package/packages/cli/src/commands/init.ts +192 -0
  22. package/packages/cli/src/commands/logs.ts +106 -0
  23. package/packages/cli/src/commands/rollback.ts +142 -0
  24. package/packages/cli/src/commands/server/info.ts +131 -0
  25. package/packages/cli/src/commands/server/setup.ts +200 -0
  26. package/packages/cli/src/commands/status.ts +149 -0
  27. package/packages/cli/src/config/loader.ts +66 -0
  28. package/packages/cli/src/config/schema.ts +140 -0
  29. package/packages/cli/src/core/backup.ts +128 -0
  30. package/packages/cli/src/core/health.ts +116 -0
  31. package/packages/cli/src/core/local.ts +67 -0
  32. package/packages/cli/src/core/lock.ts +108 -0
  33. package/packages/cli/src/core/nginx.ts +170 -0
  34. package/packages/cli/src/core/ssh.ts +335 -0
  35. package/packages/cli/src/core/sync.ts +138 -0
  36. package/packages/cli/src/core/workspace.ts +180 -0
  37. package/packages/cli/src/index.ts +240 -0
  38. package/packages/cli/src/runtimes/base.ts +144 -0
  39. package/packages/cli/src/runtimes/nodejs/detector.ts +157 -0
  40. package/packages/cli/src/runtimes/nodejs/index.ts +228 -0
  41. package/packages/cli/src/runtimes/nodejs/pm2.ts +71 -0
  42. package/packages/cli/src/runtimes/registry.ts +76 -0
  43. package/packages/cli/src/utils/logger.ts +86 -0
  44. package/packages/cli/src/utils/validation.ts +147 -0
  45. package/packages/cli/tsconfig.json +25 -0
  46. package/packages/cli/vitest.config.ts +19 -0
  47. package/turbo.json +24 -0
@@ -0,0 +1,157 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import { resolve } from 'path';
3
+ import { RuntimeDetectionResult } from '../base.js';
4
+
5
+ interface PackageJson {
6
+ name?: string;
7
+ version?: string;
8
+ dependencies?: Record<string, string>;
9
+ devDependencies?: Record<string, string>;
10
+ scripts?: Record<string, string>;
11
+ engines?: {
12
+ node?: string;
13
+ };
14
+ }
15
+
16
+ export class NodeJSDetector {
17
+ /**
18
+ * Detect Node.js project
19
+ */
20
+ static async detect(cwd: string): Promise<RuntimeDetectionResult> {
21
+ const packageJsonPath = resolve(cwd, 'package.json');
22
+
23
+ if (!existsSync(packageJsonPath)) {
24
+ return {
25
+ detected: false,
26
+ confidence: 0,
27
+ };
28
+ }
29
+
30
+ try {
31
+ const content = readFileSync(packageJsonPath, 'utf-8');
32
+ const pkg: PackageJson = JSON.parse(content);
33
+
34
+ // Determine framework
35
+ const framework = this.detectFramework(pkg);
36
+ const packageManager = this.detectPackageManager(cwd);
37
+ const version = this.detectNodeVersion(pkg);
38
+
39
+ return {
40
+ detected: true,
41
+ confidence: 100, // package.json exists = 100% Node.js
42
+ version,
43
+ framework,
44
+ packageManager,
45
+ metadata: {
46
+ hasTypeScript: this.hasTypeScript(pkg),
47
+ hasScripts: Object.keys(pkg.scripts || {}).length > 0,
48
+ dependencies: Object.keys(pkg.dependencies || {}).length,
49
+ },
50
+ };
51
+ } catch (error) {
52
+ return {
53
+ detected: false,
54
+ confidence: 0,
55
+ };
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Detect framework from dependencies
61
+ */
62
+ private static detectFramework(pkg: PackageJson): string | undefined {
63
+ const allDeps = {
64
+ ...pkg.dependencies,
65
+ ...pkg.devDependencies,
66
+ };
67
+
68
+ // Check for common frameworks (order matters - more specific first)
69
+ if (allDeps['next']) return 'next';
70
+ if (allDeps['@nestjs/core']) return 'nestjs';
71
+ if (allDeps['hono']) return 'hono';
72
+ if (allDeps['fastify']) return 'fastify';
73
+ if (allDeps['express']) return 'express';
74
+ if (allDeps['koa']) return 'koa';
75
+ if (allDeps['@hapi/hapi']) return 'hapi';
76
+
77
+ return 'generic';
78
+ }
79
+
80
+ /**
81
+ * Detect package manager
82
+ */
83
+ private static detectPackageManager(cwd: string): string {
84
+ if (existsSync(resolve(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
85
+ if (existsSync(resolve(cwd, 'yarn.lock'))) return 'yarn';
86
+ if (existsSync(resolve(cwd, 'package-lock.json'))) return 'npm';
87
+ return 'npm'; // default
88
+ }
89
+
90
+ /**
91
+ * Detect Node.js version from engines
92
+ */
93
+ private static detectNodeVersion(pkg: PackageJson): string | undefined {
94
+ if (!pkg.engines?.node) return undefined;
95
+
96
+ // Parse version constraint (>=18.0.0 -> 18)
97
+ const match = pkg.engines.node.match(/(\d+)/);
98
+ return match ? match[1] : undefined;
99
+ }
100
+
101
+ /**
102
+ * Check if project uses TypeScript
103
+ */
104
+ private static hasTypeScript(pkg: PackageJson): boolean {
105
+ const allDeps = {
106
+ ...pkg.dependencies,
107
+ ...pkg.devDependencies,
108
+ };
109
+ return 'typescript' in allDeps;
110
+ }
111
+
112
+ /**
113
+ * Extract build command from package.json
114
+ */
115
+ static getBuildCommand(pkg: PackageJson): string | undefined {
116
+ const scripts = pkg.scripts || {};
117
+
118
+ // Common build script names
119
+ if (scripts.build) return 'npm run build';
120
+ if (scripts.compile) return 'npm run compile';
121
+ if (scripts['build:prod']) return 'npm run build:prod';
122
+
123
+ return undefined;
124
+ }
125
+
126
+ /**
127
+ * Extract start command from package.json
128
+ */
129
+ static getStartCommand(pkg: PackageJson): string {
130
+ const scripts = pkg.scripts || {};
131
+
132
+ // Common start script names
133
+ if (scripts.start) return 'npm start';
134
+ if (scripts['start:prod']) return 'npm run start:prod';
135
+
136
+ // Fallback: node entry point
137
+ return 'node dist/index.js';
138
+ }
139
+
140
+ /**
141
+ * Read package.json
142
+ */
143
+ static readPackageJson(cwd: string): PackageJson | null {
144
+ const packageJsonPath = resolve(cwd, 'package.json');
145
+
146
+ if (!existsSync(packageJsonPath)) {
147
+ return null;
148
+ }
149
+
150
+ try {
151
+ const content = readFileSync(packageJsonPath, 'utf-8');
152
+ return JSON.parse(content);
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ }
@@ -0,0 +1,228 @@
1
+ import {
2
+ BaseRuntimeAdapter,
3
+ RuntimeDetectionResult,
4
+ RuntimeConfig,
5
+ SetupOptions,
6
+ ProcessManager,
7
+ } from '../base.js';
8
+ import { Runtime } from '../../config/schema.js';
9
+ import { NodeJSDetector } from './detector.js';
10
+ import { PM2Manager } from './pm2.js';
11
+
12
+ /**
13
+ * Node.js runtime adapter
14
+ */
15
+ export class NodeJSAdapter extends BaseRuntimeAdapter {
16
+ readonly name: Runtime = 'nodejs';
17
+ private pm2Manager = new PM2Manager();
18
+
19
+ async detect(cwd: string): Promise<RuntimeDetectionResult> {
20
+ return NodeJSDetector.detect(cwd);
21
+ }
22
+
23
+ async getDefaultConfig(cwd: string): Promise<Partial<RuntimeConfig>> {
24
+ const pkg = NodeJSDetector.readPackageJson(cwd);
25
+
26
+ if (!pkg) {
27
+ throw new Error('package.json not found');
28
+ }
29
+
30
+ const buildCommand = NodeJSDetector.getBuildCommand(pkg);
31
+ const startCommand = NodeJSDetector.getStartCommand(pkg);
32
+
33
+ return {
34
+ name: pkg.name || 'my-app',
35
+ runtime: 'nodejs',
36
+ version: pkg.engines?.node?.match(/\d+/)?.[0] || '18',
37
+ build: buildCommand ? {
38
+ command: buildCommand,
39
+ directory: 'dist',
40
+ } : undefined,
41
+ start: {
42
+ command: startCommand,
43
+ entry: 'dist/index.js',
44
+ },
45
+ };
46
+ }
47
+
48
+ generateSetupScript(version: string, options: SetupOptions = {}): string {
49
+ const port = options.port || 3000;
50
+ const env = options.environment || 'production';
51
+ const installRedis = options.installRedis ?? false;
52
+
53
+ return `#!/bin/bash
54
+ set -e
55
+
56
+ # Log everything to file
57
+ LOG_FILE="/tmp/hostfn-setup.log"
58
+ exec > >(tee -a "$LOG_FILE") 2>&1
59
+ echo "[$(date)] Starting hostfn setup..."
60
+
61
+ echo "=========================================="
62
+ echo "Node.js Server Setup (hostfn)"
63
+ echo "Environment: ${env}"
64
+ echo "=========================================="
65
+
66
+ # Detect OS
67
+ if [ -f /etc/os-release ]; then
68
+ . /etc/os-release
69
+ OS=$NAME
70
+ VER=$VERSION_ID
71
+ echo "Detected OS: $OS $VER"
72
+ fi
73
+
74
+ # 1. Install Node.js via nvm
75
+ echo ""
76
+ echo "[1/7] Installing Node.js v${version} via nvm..."
77
+ if [ ! -d "$HOME/.nvm" ]; then
78
+ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
79
+ export NVM_DIR="$HOME/.nvm"
80
+ [ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"
81
+
82
+ nvm install ${version}
83
+ nvm use ${version}
84
+ nvm alias default ${version}
85
+ echo "Node.js $(node --version) installed"
86
+ else
87
+ echo "nvm already installed"
88
+ source "$HOME/.nvm/nvm.sh"
89
+ nvm install ${version}
90
+ nvm use ${version}
91
+ fi
92
+
93
+ # 2. Install PM2
94
+ echo ""
95
+ echo "[2/7] Installing PM2 process manager..."
96
+ npm install -g pm2
97
+ pm2 --version
98
+
99
+ # 3. Install system dependencies
100
+ echo ""
101
+ echo "[3/7] Installing system dependencies..."
102
+ if command -v apt-get &> /dev/null; then
103
+ sudo apt-get update
104
+ sudo apt-get install -y nginx certbot python3-certbot-nginx rsync curl git build-essential
105
+ elif command -v yum &> /dev/null; then
106
+ sudo yum install -y nginx certbot python3-certbot-nginx rsync git gcc-c++ make
107
+ elif command -v dnf &> /dev/null; then
108
+ sudo dnf install -y nginx certbot python3-certbot-nginx rsync curl git gcc-c++ make
109
+ fi
110
+
111
+ # 4. Configure Nginx
112
+ echo ""
113
+ echo "[4/7] Configuring Nginx..."
114
+ if [ -d "/etc/nginx/sites-available" ]; then
115
+ NGINX_CONFIG="/etc/nginx/sites-available/hostfn-${env}"
116
+ sudo tee $NGINX_CONFIG > /dev/null <<'EOF'
117
+ server {
118
+ listen 80;
119
+ server_name _;
120
+
121
+ location / {
122
+ proxy_pass http://localhost:${port};
123
+ proxy_http_version 1.1;
124
+ proxy_set_header Upgrade $http_upgrade;
125
+ proxy_set_header Connection 'upgrade';
126
+ proxy_set_header Host $host;
127
+ proxy_set_header X-Real-IP $remote_addr;
128
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
129
+ proxy_set_header X-Forwarded-Proto $scheme;
130
+ proxy_cache_bypass $http_upgrade;
131
+ proxy_read_timeout 60s;
132
+ proxy_connect_timeout 60s;
133
+ }
134
+ }
135
+ EOF
136
+ sudo ln -sf $NGINX_CONFIG /etc/nginx/sites-enabled/hostfn-${env}
137
+ elif [ -d "/etc/nginx/conf.d" ]; then
138
+ NGINX_CONFIG="/etc/nginx/conf.d/hostfn-${env}.conf"
139
+ sudo tee $NGINX_CONFIG > /dev/null <<'EOF'
140
+ server {
141
+ listen 80 default_server;
142
+ server_name _;
143
+
144
+ location / {
145
+ proxy_pass http://localhost:${port};
146
+ proxy_http_version 1.1;
147
+ proxy_set_header Upgrade $http_upgrade;
148
+ proxy_set_header Connection 'upgrade';
149
+ proxy_set_header Host $host;
150
+ proxy_set_header X-Real-IP $remote_addr;
151
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
152
+ proxy_set_header X-Forwarded-Proto $scheme;
153
+ proxy_cache_bypass $http_upgrade;
154
+ }
155
+ }
156
+ EOF
157
+ fi
158
+
159
+ sudo nginx -t
160
+ sudo systemctl restart nginx
161
+ sudo systemctl enable nginx
162
+
163
+ ${installRedis ? `
164
+ # 5. Install Redis (optional)
165
+ echo ""
166
+ echo "[5/7] Installing Redis..."
167
+ if command -v apt-get &> /dev/null; then
168
+ sudo apt-get install -y redis-server
169
+ sudo systemctl enable redis-server
170
+ sudo systemctl start redis-server
171
+ elif command -v yum &> /dev/null; then
172
+ sudo yum install -y redis
173
+ sudo systemctl enable redis
174
+ sudo systemctl start redis
175
+ fi
176
+ ` : `
177
+ # 5. Skip Redis installation
178
+ echo ""
179
+ echo "[5/7] Skipping Redis installation..."
180
+ `}
181
+
182
+ # 6. Create deployment directories
183
+ echo ""
184
+ echo "[6/7] Creating deployment directories..."
185
+ sudo mkdir -p /var/www
186
+ sudo chown -R $USER:$USER /var/www
187
+ mkdir -p /var/log/pm2
188
+
189
+ # 7. Configure PM2 startup
190
+ echo ""
191
+ echo "[7/7] Configuring PM2 startup..."
192
+ PM2_STARTUP_CMD=$(pm2 startup | grep 'sudo' | tail -n 1)
193
+ if [ -n "$PM2_STARTUP_CMD" ]; then
194
+ eval "$PM2_STARTUP_CMD" || echo "PM2 startup configured"
195
+ else
196
+ echo "PM2 startup already configured or not needed"
197
+ fi
198
+ pm2 save
199
+
200
+ # 8. Firewall setup
201
+ echo ""
202
+ echo "[8/7] Configuring firewall..."
203
+ if command -v ufw &> /dev/null; then
204
+ sudo ufw allow 22/tcp
205
+ sudo ufw allow 80/tcp
206
+ sudo ufw allow 443/tcp
207
+ sudo ufw --force enable
208
+ fi
209
+
210
+ echo ""
211
+ echo "=========================================="
212
+ echo "✅ Server setup complete!"
213
+ echo "=========================================="
214
+ echo "Node.js: $(node --version)"
215
+ echo "npm: $(npm --version)"
216
+ echo "PM2: $(pm2 --version)"
217
+ echo "Nginx: Running"
218
+ echo "Port: ${port}"
219
+ echo "=========================================="
220
+ echo "[$(date)] Setup completed successfully"
221
+ echo "Log saved to: $LOG_FILE"
222
+ `;
223
+ }
224
+
225
+ getProcessManager(): ProcessManager {
226
+ return this.pm2Manager;
227
+ }
228
+ }
@@ -0,0 +1,71 @@
1
+ import { ProcessManager, RuntimeConfig } from '../base.js';
2
+
3
+ /**
4
+ * PM2 process manager for Node.js
5
+ */
6
+ export class PM2Manager implements ProcessManager {
7
+ readonly name = 'pm2';
8
+
9
+ generateStartCommand(config: RuntimeConfig, environment: string): string {
10
+ const serviceName = `${config.name}-${environment}`;
11
+ const entry = config.start.entry || 'dist/index.js';
12
+
13
+ return `pm2 start ${entry} --name ${serviceName} -i max --env ${environment}`;
14
+ }
15
+
16
+ generateReloadCommand(serviceName: string): string {
17
+ return `pm2 reload ${serviceName} --update-env`;
18
+ }
19
+
20
+ generateStopCommand(serviceName: string): string {
21
+ return `pm2 stop ${serviceName}`;
22
+ }
23
+
24
+ generateStatusCommand(serviceName: string): string {
25
+ return `pm2 list | grep ${serviceName}`;
26
+ }
27
+
28
+ generateLogsCommand(serviceName: string, lines: number = 100): string {
29
+ return `pm2 logs ${serviceName} --lines ${lines}`;
30
+ }
31
+
32
+ /**
33
+ * Generate PM2 ecosystem config file content
34
+ */
35
+ generateEcosystemConfig(config: RuntimeConfig, environment: string, envVars: Record<string, string> = {}): string {
36
+ const serviceName = `${config.name}-${environment}`;
37
+ const entry = config.start.entry || 'dist/index.js';
38
+
39
+ // Merge base env with provided env vars
40
+ const allEnv = {
41
+ NODE_ENV: environment,
42
+ PORT: config.port || 3000,
43
+ ...envVars
44
+ };
45
+
46
+ // Generate env object string
47
+ const envString = Object.entries(allEnv)
48
+ .map(([key, value]) => ` ${key}: ${JSON.stringify(value)}`)
49
+ .join(',\n');
50
+
51
+ return `module.exports = {
52
+ apps: [{
53
+ name: '${serviceName}',
54
+ script: '${entry}',
55
+ instances: 'max',
56
+ exec_mode: 'cluster',
57
+ env: {
58
+ ${envString}
59
+ },
60
+ error_file: './logs/err.log',
61
+ out_file: './logs/out.log',
62
+ time: true,
63
+ autorestart: true,
64
+ max_restarts: 10,
65
+ min_uptime: '10s',
66
+ max_memory_restart: '1G'
67
+ }]
68
+ };
69
+ `;
70
+ }
71
+ }
@@ -0,0 +1,76 @@
1
+ import { Runtime } from '../config/schema.js';
2
+ import { RuntimeAdapter } from './base.js';
3
+
4
+ /**
5
+ * Runtime adapter registry
6
+ * Manages all available runtime adapters
7
+ */
8
+ export class RuntimeRegistry {
9
+ private static adapters = new Map<Runtime, RuntimeAdapter>();
10
+
11
+ /**
12
+ * Register a runtime adapter
13
+ */
14
+ static register(adapter: RuntimeAdapter): void {
15
+ this.adapters.set(adapter.name, adapter);
16
+ }
17
+
18
+ /**
19
+ * Get runtime adapter by name
20
+ */
21
+ static get(runtime: Runtime): RuntimeAdapter {
22
+ const adapter = this.adapters.get(runtime);
23
+ if (!adapter) {
24
+ throw new Error(
25
+ `Runtime adapter not found: ${runtime}\n` +
26
+ `Available runtimes: ${Array.from(this.adapters.keys()).join(', ')}`
27
+ );
28
+ }
29
+ return adapter;
30
+ }
31
+
32
+ /**
33
+ * Get all registered adapters
34
+ */
35
+ static getAll(): RuntimeAdapter[] {
36
+ return Array.from(this.adapters.values());
37
+ }
38
+
39
+ /**
40
+ * Check if runtime is supported
41
+ */
42
+ static has(runtime: Runtime): boolean {
43
+ return this.adapters.has(runtime);
44
+ }
45
+
46
+ /**
47
+ * Auto-detect runtime in project directory
48
+ */
49
+ static async detect(cwd: string): Promise<{
50
+ runtime: Runtime;
51
+ adapter: RuntimeAdapter;
52
+ confidence: number;
53
+ } | null> {
54
+ const results = await Promise.all(
55
+ Array.from(this.adapters.values()).map(async (adapter) => {
56
+ const result = await adapter.detect(cwd);
57
+ return {
58
+ runtime: adapter.name,
59
+ adapter,
60
+ ...result,
61
+ };
62
+ })
63
+ );
64
+
65
+ // Sort by confidence and get the best match
66
+ const sorted = results
67
+ .filter(r => r.detected)
68
+ .sort((a, b) => b.confidence - a.confidence);
69
+
70
+ if (sorted.length === 0) {
71
+ return null;
72
+ }
73
+
74
+ return sorted[0];
75
+ }
76
+ }
@@ -0,0 +1,86 @@
1
+ import chalk from 'chalk';
2
+
3
+ export class Logger {
4
+ /**
5
+ * Log info message
6
+ */
7
+ static info(message: string): void {
8
+ console.log(chalk.blue('ℹ'), message);
9
+ }
10
+
11
+ /**
12
+ * Log success message
13
+ */
14
+ static success(message: string): void {
15
+ console.log(chalk.green('✓'), message);
16
+ }
17
+
18
+ /**
19
+ * Log warning message
20
+ */
21
+ static warn(message: string): void {
22
+ console.log(chalk.yellow('⚠'), message);
23
+ }
24
+
25
+ /**
26
+ * Log error message
27
+ */
28
+ static error(message: string): void {
29
+ console.log(chalk.red('✗'), message);
30
+ }
31
+
32
+ /**
33
+ * Log step message (with emoji)
34
+ */
35
+ static step(emoji: string, message: string): void {
36
+ console.log(emoji, chalk.bold(message));
37
+ }
38
+
39
+ /**
40
+ * Log header/title
41
+ */
42
+ static header(message: string): void {
43
+ console.log();
44
+ console.log(chalk.bold.cyan('='.repeat(50)));
45
+ console.log(chalk.bold.cyan(message));
46
+ console.log(chalk.bold.cyan('='.repeat(50)));
47
+ console.log();
48
+ }
49
+
50
+ /**
51
+ * Log section
52
+ */
53
+ static section(message: string): void {
54
+ console.log();
55
+ console.log(chalk.bold(message));
56
+ console.log(chalk.gray('-'.repeat(message.length)));
57
+ }
58
+
59
+ /**
60
+ * Log plain message
61
+ */
62
+ static log(message: string): void {
63
+ console.log(message);
64
+ }
65
+
66
+ /**
67
+ * Log empty line
68
+ */
69
+ static br(): void {
70
+ console.log();
71
+ }
72
+
73
+ /**
74
+ * Log key-value pair
75
+ */
76
+ static kv(key: string, value: string): void {
77
+ console.log(` ${chalk.gray(key + ':')} ${value}`);
78
+ }
79
+
80
+ /**
81
+ * Log command to run
82
+ */
83
+ static command(cmd: string): void {
84
+ console.log(chalk.gray(' $ ') + chalk.cyan(cmd));
85
+ }
86
+ }