@oussema_mili/test-pkg-123 1.1.22
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.
Potentially problematic release.
This version of @oussema_mili/test-pkg-123 might be problematic. Click here for more details.
- package/LICENSE +29 -0
- package/README.md +220 -0
- package/auth-callback.html +97 -0
- package/auth.js +276 -0
- package/cli-commands.js +1923 -0
- package/containerManager.js +304 -0
- package/daemon/agentRunner.js +429 -0
- package/daemon/daemonEntry.js +64 -0
- package/daemon/daemonManager.js +271 -0
- package/daemon/logManager.js +227 -0
- package/dist/styles.css +504 -0
- package/docker-actions/apps.js +3938 -0
- package/docker-actions/config-transformer.js +380 -0
- package/docker-actions/containers.js +355 -0
- package/docker-actions/general.js +171 -0
- package/docker-actions/images.js +1128 -0
- package/docker-actions/logs.js +224 -0
- package/docker-actions/metrics.js +270 -0
- package/docker-actions/registry.js +1100 -0
- package/docker-actions/setup-tasks.js +859 -0
- package/docker-actions/terminal.js +247 -0
- package/docker-actions/volumes.js +696 -0
- package/helper-functions.js +193 -0
- package/index.html +83 -0
- package/index.js +341 -0
- package/package.json +82 -0
- package/postcss.config.mjs +5 -0
- package/scripts/release.sh +212 -0
- package/setup/setupWizard.js +403 -0
- package/store/agentSessionStore.js +51 -0
- package/store/agentStore.js +113 -0
- package/store/configStore.js +171 -0
- package/store/daemonStore.js +217 -0
- package/store/deviceCredentialStore.js +107 -0
- package/store/npmTokenStore.js +65 -0
- package/store/registryStore.js +329 -0
- package/store/setupState.js +147 -0
- package/styles.css +1 -0
- package/utils/appLogger.js +223 -0
- package/utils/deviceInfo.js +98 -0
- package/utils/ecrAuth.js +225 -0
- package/utils/encryption.js +112 -0
- package/utils/envSetup.js +44 -0
- package/utils/errorHandler.js +327 -0
- package/utils/portUtils.js +59 -0
- package/utils/prerequisites.js +323 -0
- package/utils/prompts.js +318 -0
- package/utils/ssl-certificates.js +256 -0
- package/websocket-server.js +415 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
|
|
3
|
+
// Format container uptime in a human-readable format
|
|
4
|
+
const formatUptime = (ms) => {
|
|
5
|
+
const seconds = Math.floor(ms / 1000);
|
|
6
|
+
const minutes = Math.floor(seconds / 60);
|
|
7
|
+
const hours = Math.floor(minutes / 60);
|
|
8
|
+
const days = Math.floor(hours / 24);
|
|
9
|
+
|
|
10
|
+
if (days > 0) return `${days} day${days > 1 ? 's' : ''}`;
|
|
11
|
+
if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''}`;
|
|
12
|
+
if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''}`;
|
|
13
|
+
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Format image size to human-readable format
|
|
17
|
+
const formatSize = (size) => {
|
|
18
|
+
const kb = size / 1000;
|
|
19
|
+
if (kb < 1000) return `${Math.round(kb)} KB`;
|
|
20
|
+
|
|
21
|
+
const mb = size / 1_000_000;
|
|
22
|
+
if (mb < 1000) return `${Math.round(mb)} MB`;
|
|
23
|
+
|
|
24
|
+
const gb = size / 1_000_000_000;
|
|
25
|
+
return `${gb.toFixed(2)} GB`;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Format image creation time to relative time
|
|
29
|
+
const formatCreatedTime = (created) => {
|
|
30
|
+
const now = Date.now() / 1000;
|
|
31
|
+
const diff = now - created;
|
|
32
|
+
|
|
33
|
+
const minutes = Math.floor(diff / 60);
|
|
34
|
+
const hours = Math.floor(minutes / 60);
|
|
35
|
+
const days = Math.floor(hours / 24);
|
|
36
|
+
const months = Math.floor(days / 30);
|
|
37
|
+
const years = Math.floor(months / 12);
|
|
38
|
+
|
|
39
|
+
if (years > 0) return `${years} year${years > 1 ? 's' : ''} ago`;
|
|
40
|
+
if (months > 0) return `${months} month${months > 1 ? 's' : ''} ago`;
|
|
41
|
+
if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`;
|
|
42
|
+
if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
|
|
43
|
+
if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
|
|
44
|
+
return 'Just now';
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Parse log line to extract timestamp, level, and message
|
|
48
|
+
function parseLogLine(line, containerId) {
|
|
49
|
+
let rawLine = line.toString();
|
|
50
|
+
|
|
51
|
+
// Docker logs can contain binary headers (8 bytes) that need to be stripped
|
|
52
|
+
// The first byte indicates the stream type (1=stdout, 2=stderr)
|
|
53
|
+
// Bytes 1-3 are padding, bytes 4-7 contain the length
|
|
54
|
+
if (rawLine.length > 8) {
|
|
55
|
+
const firstByte = rawLine.charCodeAt(0);
|
|
56
|
+
// Check if this looks like a Docker log header
|
|
57
|
+
if (firstByte === 1 || firstByte === 2) {
|
|
58
|
+
// Skip the 8-byte header
|
|
59
|
+
rawLine = rawLine.substring(8);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let message = rawLine;
|
|
64
|
+
let timestamp = new Date().toISOString();
|
|
65
|
+
let level = 'INFO';
|
|
66
|
+
const service = 'container';
|
|
67
|
+
let isEmptyLine = false;
|
|
68
|
+
|
|
69
|
+
// Check if this is an empty line (only whitespace or completely empty)
|
|
70
|
+
if (rawLine.trim() === '') {
|
|
71
|
+
isEmptyLine = true;
|
|
72
|
+
message = ''; // Keep as empty for empty lines
|
|
73
|
+
} else {
|
|
74
|
+
// Extract Docker timestamp if present (format: 2025-09-12T14:44:10.598127086Z)
|
|
75
|
+
const timestampMatch = message.match(
|
|
76
|
+
/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z)\s*(.*)$/
|
|
77
|
+
);
|
|
78
|
+
if (timestampMatch) {
|
|
79
|
+
timestamp = timestampMatch[1];
|
|
80
|
+
message = timestampMatch[2] || ''; // This is the actual log content after the Docker timestamp
|
|
81
|
+
|
|
82
|
+
// If after extracting timestamp, message is empty, mark as empty line
|
|
83
|
+
if (message.trim() === '') {
|
|
84
|
+
isEmptyLine = true;
|
|
85
|
+
message = '';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Clean up any remaining malformed timestamp artifacts
|
|
90
|
+
message = message.replace(
|
|
91
|
+
/^-?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z\s*/,
|
|
92
|
+
''
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Try to detect log level from content (only for non-empty lines)
|
|
96
|
+
if (!isEmptyLine) {
|
|
97
|
+
const lowerMessage = message.toLowerCase();
|
|
98
|
+
if (
|
|
99
|
+
lowerMessage.includes('"s":"e"') ||
|
|
100
|
+
lowerMessage.includes('error') ||
|
|
101
|
+
lowerMessage.includes('err') ||
|
|
102
|
+
lowerMessage.includes('fatal')
|
|
103
|
+
) {
|
|
104
|
+
level = 'ERROR';
|
|
105
|
+
} else if (
|
|
106
|
+
lowerMessage.includes('"s":"w"') ||
|
|
107
|
+
lowerMessage.includes('warn')
|
|
108
|
+
) {
|
|
109
|
+
level = 'WARN';
|
|
110
|
+
} else if (
|
|
111
|
+
lowerMessage.includes('"s":"d"') ||
|
|
112
|
+
lowerMessage.includes('debug')
|
|
113
|
+
) {
|
|
114
|
+
level = 'DEBUG';
|
|
115
|
+
} else if (
|
|
116
|
+
lowerMessage.includes('"s":"i"') ||
|
|
117
|
+
lowerMessage.includes('info') ||
|
|
118
|
+
lowerMessage.includes('log:')
|
|
119
|
+
) {
|
|
120
|
+
level = 'INFO';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
id: `log-${containerId}-${Date.now()}-${Math.random()
|
|
127
|
+
.toString(36)
|
|
128
|
+
.substring(2, 9)}`,
|
|
129
|
+
timestamp,
|
|
130
|
+
level,
|
|
131
|
+
service,
|
|
132
|
+
message,
|
|
133
|
+
isEmptyLine, // Add flag to identify empty lines
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Parse size strings like "10 GB" to bytes
|
|
138
|
+
function parseSize(sizeStr) {
|
|
139
|
+
if (!sizeStr || typeof sizeStr !== 'string') return 0;
|
|
140
|
+
|
|
141
|
+
const match = sizeStr.match(/^(\d+(\.\d+)?)\s*([KMGT]?B)$/);
|
|
142
|
+
if (!match) return 0;
|
|
143
|
+
|
|
144
|
+
const size = Number.parseFloat(match[1]);
|
|
145
|
+
const unit = match[3];
|
|
146
|
+
|
|
147
|
+
switch (unit) {
|
|
148
|
+
case 'B':
|
|
149
|
+
return size;
|
|
150
|
+
case 'KB':
|
|
151
|
+
return size * 1024;
|
|
152
|
+
case 'MB':
|
|
153
|
+
return size * 1024 * 1024;
|
|
154
|
+
case 'GB':
|
|
155
|
+
return size * 1024 * 1024 * 1024;
|
|
156
|
+
case 'TB':
|
|
157
|
+
return size * 1024 * 1024 * 1024 * 1024;
|
|
158
|
+
default:
|
|
159
|
+
return size;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Simple CPU usage estimation without native modules
|
|
164
|
+
function estimateCpuUsage() {
|
|
165
|
+
try {
|
|
166
|
+
const cpus = os.cpus();
|
|
167
|
+
let idle = 0;
|
|
168
|
+
let total = 0;
|
|
169
|
+
|
|
170
|
+
for (const cpu of cpus) {
|
|
171
|
+
for (const type in cpu.times) {
|
|
172
|
+
total += cpu.times[type];
|
|
173
|
+
}
|
|
174
|
+
idle += cpu.times.idle;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Calculate CPU usage as percentage of non-idle time
|
|
178
|
+
const usage = 100 - (idle / total) * 100;
|
|
179
|
+
return Math.round(usage * 100) / 100;
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error('Error estimating CPU usage:', error.message);
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export {
|
|
187
|
+
formatUptime,
|
|
188
|
+
formatSize,
|
|
189
|
+
formatCreatedTime,
|
|
190
|
+
parseLogLine,
|
|
191
|
+
parseSize,
|
|
192
|
+
estimateCpuUsage,
|
|
193
|
+
};
|
package/index.html
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Authorization Successful</title>
|
|
7
|
+
<link
|
|
8
|
+
rel="icon"
|
|
9
|
+
href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><circle cx='50' cy='50' r='45' fill='%2322c55e'/><path d='M30 50l15 15 25-30' stroke='white' stroke-width='8' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>"
|
|
10
|
+
/>
|
|
11
|
+
<link href="/dist/styles.css" rel="stylesheet" />
|
|
12
|
+
<script>
|
|
13
|
+
// Store WebSocket token in localStorage for App Builder to use
|
|
14
|
+
// These values are injected by the agent server
|
|
15
|
+
const wsToken = new URLSearchParams(window.location.search).get(
|
|
16
|
+
"wsToken",
|
|
17
|
+
);
|
|
18
|
+
const wsPort = new URLSearchParams(window.location.search).get("wsPort");
|
|
19
|
+
const containerPort = new URLSearchParams(window.location.search).get(
|
|
20
|
+
"containerPort",
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if (wsToken) {
|
|
24
|
+
localStorage.setItem("fenwave-ws-token", wsToken);
|
|
25
|
+
localStorage.setItem("fenwave-ws-port", wsPort || "3001");
|
|
26
|
+
localStorage.setItem("fenwave-container-port", containerPort || "3006");
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
<style>
|
|
30
|
+
@keyframes fadeIn {
|
|
31
|
+
from {
|
|
32
|
+
opacity: 0;
|
|
33
|
+
}
|
|
34
|
+
to {
|
|
35
|
+
opacity: 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
.fade-in {
|
|
39
|
+
animation: fadeIn 0.5s ease-in-out;
|
|
40
|
+
}
|
|
41
|
+
</style>
|
|
42
|
+
</head>
|
|
43
|
+
<body class="bg-gray-50 min-h-screen">
|
|
44
|
+
<div class="flex justify-center items-center min-h-[80vh]">
|
|
45
|
+
<div
|
|
46
|
+
class="fade-in max-w-sm mx-auto mt-8 p-8 text-center bg-white rounded-lg shadow-lg"
|
|
47
|
+
>
|
|
48
|
+
<!-- Success Icon Circle -->
|
|
49
|
+
<div
|
|
50
|
+
class="w-16 h-16 mx-auto mb-6 flex items-center justify-center rounded-full bg-green-500 text-white"
|
|
51
|
+
>
|
|
52
|
+
<svg
|
|
53
|
+
class="w-8 h-8"
|
|
54
|
+
fill="currentColor"
|
|
55
|
+
viewBox="0 0 20 20"
|
|
56
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
57
|
+
>
|
|
58
|
+
<path
|
|
59
|
+
fill-rule="evenodd"
|
|
60
|
+
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
|
61
|
+
clip-rule="evenodd"
|
|
62
|
+
></path>
|
|
63
|
+
</svg>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<!-- Main Heading -->
|
|
67
|
+
<h2 class="text-2xl font-medium text-gray-900 mb-4">
|
|
68
|
+
Authorization Successful
|
|
69
|
+
</h2>
|
|
70
|
+
|
|
71
|
+
<!-- Description Text -->
|
|
72
|
+
<p class="text-base text-gray-600 mb-8">
|
|
73
|
+
Your agent has been successfully authorized.
|
|
74
|
+
</p>
|
|
75
|
+
|
|
76
|
+
<!-- Footer Text -->
|
|
77
|
+
<p class="text-sm text-gray-500">
|
|
78
|
+
You may now close this window and return to your CLI.
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</body>
|
|
83
|
+
</html>
|
package/index.js
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { program } from "commander";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { dirname } from "path";
|
|
8
|
+
import dotenv from "dotenv";
|
|
9
|
+
|
|
10
|
+
// ES module helpers
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
|
|
14
|
+
// Ensure environment files exist
|
|
15
|
+
import { ensureEnvironmentFiles } from "./utils/envSetup.js";
|
|
16
|
+
const agentEnvPath = ensureEnvironmentFiles(__dirname);
|
|
17
|
+
|
|
18
|
+
// Load environment variables from .env.agent
|
|
19
|
+
dotenv.config({ path: agentEnvPath });
|
|
20
|
+
|
|
21
|
+
// Load environment variables
|
|
22
|
+
dotenv.config();
|
|
23
|
+
|
|
24
|
+
// Import ES modules
|
|
25
|
+
import { setupCLICommands } from "./cli-commands.js";
|
|
26
|
+
|
|
27
|
+
// Version from package.json
|
|
28
|
+
const packageJson = JSON.parse(
|
|
29
|
+
fs.readFileSync(new URL("./package.json", import.meta.url), "utf8")
|
|
30
|
+
);
|
|
31
|
+
const { version } = packageJson;
|
|
32
|
+
|
|
33
|
+
// Initialize the CLI
|
|
34
|
+
program
|
|
35
|
+
.name("fenwave")
|
|
36
|
+
.description("Fenwave - Developer Platform CLI")
|
|
37
|
+
.version(version)
|
|
38
|
+
.option('--debug', 'Enable debug mode for verbose logging')
|
|
39
|
+
.hook('preAction', (thisCommand) => {
|
|
40
|
+
const opts = thisCommand.opts();
|
|
41
|
+
if (opts.debug) {
|
|
42
|
+
process.env.DEBUG = 'true';
|
|
43
|
+
console.log(chalk.yellow('Debug mode enabled'));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Setup CLI commands (startServer function is no longer needed - using agentRunner instead)
|
|
48
|
+
setupCLICommands(program, null);
|
|
49
|
+
|
|
50
|
+
// Custom unknown command handler
|
|
51
|
+
program.on("command:*", ([cmd]) => {
|
|
52
|
+
console.error(`error: unknown command '${cmd}'`);
|
|
53
|
+
|
|
54
|
+
const available = program.commands.map((c) => c.name());
|
|
55
|
+
|
|
56
|
+
// First, try prefix matches
|
|
57
|
+
let matches = available.filter((c) => c.startsWith(cmd));
|
|
58
|
+
|
|
59
|
+
// If no prefix matches, try bidirectional substring matches
|
|
60
|
+
if (matches.length === 0) {
|
|
61
|
+
matches = available.filter((c) => cmd.includes(c) || c.includes(cmd));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (matches.length > 0) {
|
|
65
|
+
if (matches.length === 1) {
|
|
66
|
+
console.error(`(Did you mean '${matches[0]}'?)`);
|
|
67
|
+
} else {
|
|
68
|
+
console.error(`(Did you mean one of ${matches.join(", ")}?)`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Pre-process arguments to handle incomplete options before Commander.js
|
|
76
|
+
function preprocessArguments() {
|
|
77
|
+
const args = process.argv.slice(2);
|
|
78
|
+
|
|
79
|
+
if (args.length >= 2) {
|
|
80
|
+
const commandName = args[0];
|
|
81
|
+
const lastArg = args[args.length - 1];
|
|
82
|
+
|
|
83
|
+
// Check if user typed incomplete option (just - or --)
|
|
84
|
+
if (lastArg === "-" || lastArg === "--") {
|
|
85
|
+
// Find the command
|
|
86
|
+
const command = program.commands.find(
|
|
87
|
+
(cmd) =>
|
|
88
|
+
cmd.name() === commandName || cmd.aliases().includes(commandName)
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (command) {
|
|
92
|
+
console.error(`error: unknown option '${lastArg}'`);
|
|
93
|
+
|
|
94
|
+
// Get all options for this command
|
|
95
|
+
const options = command.options || [];
|
|
96
|
+
const availableOptions = [];
|
|
97
|
+
|
|
98
|
+
// Add command-specific options
|
|
99
|
+
options.forEach((option) => {
|
|
100
|
+
if (option.short) availableOptions.push(option.short);
|
|
101
|
+
if (option.long) availableOptions.push(option.long);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Always add help option
|
|
105
|
+
if (!availableOptions.includes("-h")) availableOptions.push("-h");
|
|
106
|
+
if (!availableOptions.includes("--help"))
|
|
107
|
+
availableOptions.push("--help");
|
|
108
|
+
|
|
109
|
+
let suggestions = [];
|
|
110
|
+
|
|
111
|
+
if (lastArg === "--") {
|
|
112
|
+
// User typed just '--', suggest all long options
|
|
113
|
+
suggestions = availableOptions.filter((opt) => opt.startsWith("--"));
|
|
114
|
+
} else if (lastArg === "-") {
|
|
115
|
+
// User typed just '-', suggest all short options with their long equivalents
|
|
116
|
+
const shortOpts = availableOptions.filter(
|
|
117
|
+
(opt) => opt.startsWith("-") && !opt.startsWith("--")
|
|
118
|
+
);
|
|
119
|
+
const longOpts = availableOptions.filter((opt) =>
|
|
120
|
+
opt.startsWith("--")
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Pair short and long options
|
|
124
|
+
suggestions = shortOpts.map((shortOpt) => {
|
|
125
|
+
const longEquivalent = longOpts.find(
|
|
126
|
+
(longOpt) =>
|
|
127
|
+
longOpt.substring(2) === shortOpt.substring(1) ||
|
|
128
|
+
(shortOpt === "-h" && longOpt === "--help") ||
|
|
129
|
+
(shortOpt === "-a" && longOpt === "--all") ||
|
|
130
|
+
(shortOpt === "-f" &&
|
|
131
|
+
(longOpt === "--force" || longOpt === "--follow")) ||
|
|
132
|
+
(shortOpt === "-d" && longOpt === "--driver") ||
|
|
133
|
+
(shortOpt === "-p" && longOpt === "--port") ||
|
|
134
|
+
(shortOpt === "-t" && longOpt === "--tail")
|
|
135
|
+
);
|
|
136
|
+
return longEquivalent ? `${shortOpt}, ${longEquivalent}` : shortOpt;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (suggestions.length > 0) {
|
|
141
|
+
const formattedSuggestions = suggestions.map((suggestion) => {
|
|
142
|
+
// Add argument placeholder for options that require arguments
|
|
143
|
+
let displaySuggestion = suggestion;
|
|
144
|
+
|
|
145
|
+
// Find the matching option object to check if it requires an argument
|
|
146
|
+
const option = options.find(
|
|
147
|
+
(opt) =>
|
|
148
|
+
opt.long === suggestion ||
|
|
149
|
+
opt.short === suggestion ||
|
|
150
|
+
(suggestion.includes(opt.long) && opt.long) ||
|
|
151
|
+
(suggestion.includes(opt.short) && opt.short)
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (option && option.required) {
|
|
155
|
+
// Use a more descriptive argument name based on the option's own description
|
|
156
|
+
let argName = "<value>";
|
|
157
|
+
if (option.argChoices) {
|
|
158
|
+
argName = `<${option.argChoices.join("|")}>`;
|
|
159
|
+
} else if (option.long) {
|
|
160
|
+
// Map common option names to better argument descriptions
|
|
161
|
+
const optName = option.long.replace("--", "");
|
|
162
|
+
const argNameMap = {
|
|
163
|
+
tail: "lines",
|
|
164
|
+
port: "port",
|
|
165
|
+
driver: "driver",
|
|
166
|
+
"backend-url": "url",
|
|
167
|
+
"frontend-url": "url",
|
|
168
|
+
token: "token",
|
|
169
|
+
"aws-region": "region",
|
|
170
|
+
"aws-account-id": "id",
|
|
171
|
+
};
|
|
172
|
+
argName = `<${argNameMap[optName] || optName}>`;
|
|
173
|
+
}
|
|
174
|
+
displaySuggestion = `${suggestion} ${argName}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return `'${displaySuggestion}'`;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
console.error(`(Did you mean ${formattedSuggestions.join(", ")}?)`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Run preprocessing before parsing
|
|
190
|
+
preprocessArguments();
|
|
191
|
+
|
|
192
|
+
// Handle unknown options
|
|
193
|
+
program.exitOverride((err) => {
|
|
194
|
+
if (err.code === "commander.unknownOption") {
|
|
195
|
+
const args = process.argv.slice(2);
|
|
196
|
+
const commandName = args[0];
|
|
197
|
+
const invalidOption = err.message.match(/unknown option '([^']+)'/)?.[1];
|
|
198
|
+
|
|
199
|
+
if (invalidOption) {
|
|
200
|
+
// Find the command
|
|
201
|
+
const command = program.commands.find(
|
|
202
|
+
(cmd) =>
|
|
203
|
+
cmd.name() === commandName || cmd.aliases().includes(commandName)
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
if (command) {
|
|
207
|
+
console.error(`error: unknown option '${invalidOption}'`);
|
|
208
|
+
|
|
209
|
+
// Get all options for this command
|
|
210
|
+
const options = command.options || [];
|
|
211
|
+
const availableOptions = [];
|
|
212
|
+
|
|
213
|
+
// Add command-specific options
|
|
214
|
+
options.forEach((option) => {
|
|
215
|
+
if (option.short) availableOptions.push(option.short);
|
|
216
|
+
if (option.long) availableOptions.push(option.long);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Always add help option
|
|
220
|
+
if (!availableOptions.includes("-h")) availableOptions.push("-h");
|
|
221
|
+
if (!availableOptions.includes("--help"))
|
|
222
|
+
availableOptions.push("--help");
|
|
223
|
+
|
|
224
|
+
// Find suggestions for partial/incorrect options
|
|
225
|
+
const suggestions = availableOptions.filter(
|
|
226
|
+
(opt) =>
|
|
227
|
+
opt.startsWith(invalidOption) ||
|
|
228
|
+
opt.includes(invalidOption.replace(/^-+/, "")) ||
|
|
229
|
+
invalidOption.includes(opt.replace(/^-+/, ""))
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (suggestions.length > 0) {
|
|
233
|
+
// Group short and long options together
|
|
234
|
+
const pairedSuggestions = [];
|
|
235
|
+
const processed = new Set();
|
|
236
|
+
|
|
237
|
+
suggestions.forEach((suggestion) => {
|
|
238
|
+
if (processed.has(suggestion)) return;
|
|
239
|
+
|
|
240
|
+
const option = options.find(
|
|
241
|
+
(opt) => opt.long === suggestion || opt.short === suggestion
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
if (option) {
|
|
245
|
+
let displaySuggestion = "";
|
|
246
|
+
|
|
247
|
+
// Check if both short and long forms are in suggestions
|
|
248
|
+
const hasShort =
|
|
249
|
+
option.short && suggestions.includes(option.short);
|
|
250
|
+
const hasLong = option.long && suggestions.includes(option.long);
|
|
251
|
+
|
|
252
|
+
if (hasShort && hasLong) {
|
|
253
|
+
displaySuggestion = `${option.short}, ${option.long}`;
|
|
254
|
+
processed.add(option.short);
|
|
255
|
+
processed.add(option.long);
|
|
256
|
+
} else if (hasShort) {
|
|
257
|
+
// If only short form matched, still pair it with long form if available
|
|
258
|
+
displaySuggestion = option.long
|
|
259
|
+
? `${option.short}, ${option.long}`
|
|
260
|
+
: option.short;
|
|
261
|
+
processed.add(option.short);
|
|
262
|
+
if (option.long) processed.add(option.long);
|
|
263
|
+
} else if (hasLong) {
|
|
264
|
+
// If only long form matched, still pair it with short form if available
|
|
265
|
+
displaySuggestion = option.short
|
|
266
|
+
? `${option.short}, ${option.long}`
|
|
267
|
+
: option.long;
|
|
268
|
+
if (option.short) processed.add(option.short);
|
|
269
|
+
processed.add(option.long);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Add argument placeholder for options that require arguments
|
|
273
|
+
if (option.required) {
|
|
274
|
+
let argName = "<value>";
|
|
275
|
+
if (option.argChoices) {
|
|
276
|
+
argName = `<${option.argChoices.join("|")}>`;
|
|
277
|
+
} else if (option.long) {
|
|
278
|
+
const optName = option.long.replace("--", "");
|
|
279
|
+
const argNameMap = {
|
|
280
|
+
tail: "lines",
|
|
281
|
+
port: "port",
|
|
282
|
+
driver: "driver",
|
|
283
|
+
"backend-url": "url",
|
|
284
|
+
"frontend-url": "url",
|
|
285
|
+
token: "token",
|
|
286
|
+
"aws-region": "region",
|
|
287
|
+
"aws-account-id": "id",
|
|
288
|
+
};
|
|
289
|
+
argName = `<${argNameMap[optName] || optName}>`;
|
|
290
|
+
}
|
|
291
|
+
displaySuggestion = `${displaySuggestion} ${argName}`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
pairedSuggestions.push(`'${displaySuggestion}'`);
|
|
295
|
+
} else {
|
|
296
|
+
// Fallback for options without Option object (like help)
|
|
297
|
+
processed.add(suggestion);
|
|
298
|
+
|
|
299
|
+
// Pair -h with --help
|
|
300
|
+
if (suggestion === "-h" && suggestions.includes("--help")) {
|
|
301
|
+
pairedSuggestions.push(`'${suggestion}, --help'`);
|
|
302
|
+
processed.add("--help");
|
|
303
|
+
} else if (
|
|
304
|
+
suggestion === "--help" &&
|
|
305
|
+
suggestions.includes("-h")
|
|
306
|
+
) {
|
|
307
|
+
// Skip, already handled
|
|
308
|
+
} else {
|
|
309
|
+
pairedSuggestions.push(`'${suggestion}'`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
console.error(`(Did you mean ${pairedSuggestions.join(", ")}?)`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// For other errors (like help), exit normally with the error's exit code
|
|
323
|
+
process.exit(err.exitCode || 1);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Parse command line arguments
|
|
327
|
+
program.parse(process.argv);
|
|
328
|
+
|
|
329
|
+
// If no arguments, show help
|
|
330
|
+
if (process.argv.length === 2) {
|
|
331
|
+
program.help();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Process error handling
|
|
335
|
+
process.on("uncaughtException", (error) => {
|
|
336
|
+
console.error("Uncaught exception:", error);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
340
|
+
console.error("Unhandled rejection at:", promise, "reason:", reason);
|
|
341
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oussema_mili/test-pkg-123",
|
|
3
|
+
"version": "1.1.22",
|
|
4
|
+
"description": "Fenwave Docker Agent and CLI",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"fenwave",
|
|
7
|
+
"fenleap",
|
|
8
|
+
"fenwave-idp"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://github.com/fenleap/fenwave-agent#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/fenleap/fenwave-agent/issues"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/fenleap/fenwave-agent.git"
|
|
17
|
+
},
|
|
18
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
19
|
+
"author": "Fenleap",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "index.js",
|
|
22
|
+
"bin": {
|
|
23
|
+
"fenwave": "index.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"index.js",
|
|
27
|
+
"index.html",
|
|
28
|
+
"auth-callback.html",
|
|
29
|
+
"dist/",
|
|
30
|
+
"styles.css",
|
|
31
|
+
"postcss.config.mjs",
|
|
32
|
+
"cli-commands.js",
|
|
33
|
+
"auth.js",
|
|
34
|
+
"containerManager.js",
|
|
35
|
+
"helper-functions.js",
|
|
36
|
+
"websocket-server.js",
|
|
37
|
+
"daemon/",
|
|
38
|
+
"docker-actions/",
|
|
39
|
+
"scripts/",
|
|
40
|
+
"setup/",
|
|
41
|
+
"store/",
|
|
42
|
+
"utils/",
|
|
43
|
+
"completion/"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"start": "node index.js server",
|
|
47
|
+
"agent": "node index.js",
|
|
48
|
+
"build:css": "postcss styles.css -o dist/styles.css",
|
|
49
|
+
"watch:css": "postcss styles.css -o dist/styles.css --watch",
|
|
50
|
+
"prepare": "npm run build:css",
|
|
51
|
+
"release": "bash scripts/release.sh",
|
|
52
|
+
"release:patch": "bash scripts/release.sh patch",
|
|
53
|
+
"release:minor": "bash scripts/release.sh minor",
|
|
54
|
+
"release:major": "bash scripts/release.sh major"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@aws-sdk/client-ecr": "^3.879.0",
|
|
58
|
+
"@aws-sdk/client-sts": "^3.879.0",
|
|
59
|
+
"@tailwindcss/postcss": "^4.1.18",
|
|
60
|
+
"axios": "^1.11.0",
|
|
61
|
+
"chalk": "^4.1.2",
|
|
62
|
+
"cli-table3": "^0.6.3",
|
|
63
|
+
"commander": "^11.0.0",
|
|
64
|
+
"dockerode": "^4.0.0",
|
|
65
|
+
"dotenv": "^16.0.3",
|
|
66
|
+
"google-auth-library": "^9.0.0",
|
|
67
|
+
"inquirer": "^8.2.5",
|
|
68
|
+
"js-yaml": "^4.1.0",
|
|
69
|
+
"open": "^11.0.0",
|
|
70
|
+
"ora": "^5.4.1",
|
|
71
|
+
"postcss": "^8.5.6",
|
|
72
|
+
"tailwindcss": "^4.1.18",
|
|
73
|
+
"uuid": "^9.0.1",
|
|
74
|
+
"ws": "^8.15.1"
|
|
75
|
+
},
|
|
76
|
+
"engines": {
|
|
77
|
+
"node": ">=14.0.0"
|
|
78
|
+
},
|
|
79
|
+
"devDependencies": {
|
|
80
|
+
"postcss-cli": "^11.0.1"
|
|
81
|
+
}
|
|
82
|
+
}
|