ns-gm 1.0.3
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/LICENSE +21 -0
- package/README.md +127 -0
- package/ns_gm_restlet.js +452 -0
- package/package.json +53 -0
- package/server/app.js +154 -0
- package/server/auth.js +216 -0
- package/src/cli.js +78 -0
- package/src/commands/env.js +12 -0
- package/src/commands/help.js +274 -0
- package/src/commands/init.js +107 -0
- package/src/commands/logs.js +118 -0
- package/src/commands/run.js +118 -0
- package/src/commands/setup.js +179 -0
- package/src/commands/stop.js +98 -0
- package/src/utils/config.js +39 -0
- package/src/utils/exitCodes.js +37 -0
- package/src/utils/profileStore.js +107 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
const { EXIT_CODES } = require('../utils/exitCodes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Help data structure with all commands
|
|
5
|
+
*/
|
|
6
|
+
const helpData = {
|
|
7
|
+
commands: {
|
|
8
|
+
init: {
|
|
9
|
+
brief: "Start the local NetSuite proxy server",
|
|
10
|
+
syntax: "ns-gm init",
|
|
11
|
+
description: "Starts the Express proxy server used by ns-gm commands. The server runs on port 9292 (configurable in config.json) as a detached background process.",
|
|
12
|
+
options: [],
|
|
13
|
+
examples: ["ns-gm init"],
|
|
14
|
+
clarifications: [
|
|
15
|
+
"Server runs as detached process (continues after CLI exits)",
|
|
16
|
+
"Auto-stops after 15 minutes of inactivity (configurable)",
|
|
17
|
+
"Only one instance can run at a time",
|
|
18
|
+
"Check server status with: curl http://localhost:9292/health"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
run: {
|
|
22
|
+
brief: "Execute SuiteScript 2.1 code against NetSuite",
|
|
23
|
+
syntax: "ns-gm run --code <code> | --file <path>",
|
|
24
|
+
description: "Executes arbitrary SuiteScript 2.1 code against your NetSuite account via the runner RESTlet. Code runs in NetSuite's SuiteScript runtime with access to all N/* modules.",
|
|
25
|
+
options: [
|
|
26
|
+
{ flag: "--code <code>", description: "Inline SuiteScript code to execute (for short snippets)" },
|
|
27
|
+
{ flag: "--file <path>", description: "Path to file containing SuiteScript code (for longer scripts)" }
|
|
28
|
+
],
|
|
29
|
+
examples: [
|
|
30
|
+
'ns-gm run --code "return 2 + 2"',
|
|
31
|
+
'ns-gm run --code "const rec = record.load({type: \'customer\', id: 123}); return rec.getValue({fieldId: \'entityid\'});"',
|
|
32
|
+
"ns-gm run --file script.js"
|
|
33
|
+
],
|
|
34
|
+
clarifications: [
|
|
35
|
+
"Output is the return value of the executed code (what you return from the function)",
|
|
36
|
+
"Errors include stack trace, line numbers, and error type",
|
|
37
|
+
"Requires proxy running (run 'ns-gm init' first)",
|
|
38
|
+
"Governance usage is tracked and returned in response",
|
|
39
|
+
"Use log.debug(), log.audit(), log.error() for logging - retrieve with 'ns-gm logs'",
|
|
40
|
+
"Supports all N/* modules (search, record, query, runtime, format, etc.)"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
env: {
|
|
44
|
+
brief: "Get the current NetSuite environment type",
|
|
45
|
+
syntax: "ns-gm env",
|
|
46
|
+
description: "Runs the equivalent of: ns-gm run --code \"return runtime.envType\".",
|
|
47
|
+
options: [],
|
|
48
|
+
examples: [
|
|
49
|
+
"ns-gm env"
|
|
50
|
+
],
|
|
51
|
+
clarifications: [
|
|
52
|
+
"Requires proxy running (run 'ns-gm init' first)",
|
|
53
|
+
"Requires an active profile (run 'ns-gm setup' first)"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
logs: {
|
|
57
|
+
brief: "Retrieve script execution logs from NetSuite",
|
|
58
|
+
syntax: "ns-gm logs [--script-id <scriptId>] [options]",
|
|
59
|
+
description: "Retrieves execution logs from NetSuite's Script Execution Log for any deployed script. Useful for debugging code executed via 'ns-gm run' or any other script.",
|
|
60
|
+
options: [
|
|
61
|
+
{ flag: "--script-id <scriptId>", description: "Script ID to retrieve logs for (e.g., customscript_my_script)" },
|
|
62
|
+
{ flag: "--date-from <date>", description: "Start date in YYYY-MM-DD format" },
|
|
63
|
+
{ flag: "--date-to <date>", description: "End date in YYYY-MM-DD format" },
|
|
64
|
+
{ flag: "--type <type>", description: "Log type: DEBUG, AUDIT, ERROR, EMERGENCY" },
|
|
65
|
+
{ flag: "--page <number>", description: "Page number for pagination (default: 0)" },
|
|
66
|
+
{ flag: "--page-size <number>", description: "Number of logs per page (default: 20)" }
|
|
67
|
+
],
|
|
68
|
+
examples: [
|
|
69
|
+
"ns-gm logs --script-id customscript_ns_gm_",
|
|
70
|
+
"ns-gm logs --script-id customscript_sync_handler --type ERROR",
|
|
71
|
+
"ns-gm logs --script-id customscript_my_script --date-from 2025-12-01 --date-to 2025-12-15"
|
|
72
|
+
],
|
|
73
|
+
clarifications: [
|
|
74
|
+
"Script ID is converted to internal ID automatically",
|
|
75
|
+
"Returns logs in reverse chronological order (newest first)",
|
|
76
|
+
"Default date range is last 7 days if not specified",
|
|
77
|
+
"Pagination starts at page 0",
|
|
78
|
+
"Governance cost: ~10 units per query (SuiteQL search)",
|
|
79
|
+
"Use this to see logs from code executed via 'ns-gm run'"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
stop: {
|
|
83
|
+
brief: "Stop the local NetSuite proxy server",
|
|
84
|
+
syntax: "ns-gm stop",
|
|
85
|
+
description: "Gracefully stops the local Express proxy server by finding the process using port 9292 and terminating it.",
|
|
86
|
+
options: [],
|
|
87
|
+
examples: ["ns-gm stop"],
|
|
88
|
+
clarifications: [
|
|
89
|
+
"Finds process by port number (9292 by default)",
|
|
90
|
+
"Sends termination signal to the process",
|
|
91
|
+
"Verifies server stopped via health check",
|
|
92
|
+
"Safe to run even if server not running (no error)"
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
setup: {
|
|
96
|
+
brief: "Interactive credential configuration",
|
|
97
|
+
syntax: "ns-gm setup [options]",
|
|
98
|
+
description: "Interactive wizard for profile alias setup. Lets you select an existing alias or create a new one, then stores credentials in ~/.ns-gm/credentials.json.",
|
|
99
|
+
options: [
|
|
100
|
+
{ flag: "--show", description: "Display current configuration with secrets masked" }
|
|
101
|
+
],
|
|
102
|
+
examples: [
|
|
103
|
+
"ns-gm setup",
|
|
104
|
+
"ns-gm setup --show"
|
|
105
|
+
],
|
|
106
|
+
clarifications: [
|
|
107
|
+
"Prompts show existing aliases plus 'new'",
|
|
108
|
+
"Choosing existing alias only switches active profile",
|
|
109
|
+
"Private key path is validated during setup",
|
|
110
|
+
"Press Ctrl+C to cancel without saving",
|
|
111
|
+
"Credentials are stored in ~/.ns-gm/credentials.json",
|
|
112
|
+
"Private key material is not stored in the credentials file"
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
help: {
|
|
116
|
+
brief: "Show help information for commands",
|
|
117
|
+
syntax: "ns-gm help [command] [options]",
|
|
118
|
+
description: "Displays help information for all commands or a specific command. Output format can be JSON (default, for AI agents) or plain text (for humans).",
|
|
119
|
+
options: [
|
|
120
|
+
{ flag: "[command]", description: "Optional command name to get detailed help for (init, run, env, logs, stop, setup)" },
|
|
121
|
+
{ flag: "--format <format>", description: "Output format: json (default) or text" }
|
|
122
|
+
],
|
|
123
|
+
examples: [
|
|
124
|
+
"ns-gm help",
|
|
125
|
+
"ns-gm help run",
|
|
126
|
+
"ns-gm help --format text",
|
|
127
|
+
"ns-gm help logs --format json"
|
|
128
|
+
],
|
|
129
|
+
clarifications: [
|
|
130
|
+
"JSON format is default for AI agent consumption",
|
|
131
|
+
"Use --format text for human-readable output",
|
|
132
|
+
"Without [command] argument, shows all commands with brief descriptions",
|
|
133
|
+
"With [command] argument, shows detailed help for that command"
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
exitCodes: {
|
|
138
|
+
"0": "SUCCESS - Command completed successfully",
|
|
139
|
+
"1": "GENERAL_ERROR - Unknown or general error",
|
|
140
|
+
"2": "CONFIG_ERROR - Configuration error (port conflict, missing profile/config)",
|
|
141
|
+
"3": "NETWORK_ERROR - Network error (proxy unreachable, connection failed)",
|
|
142
|
+
"4": "VALIDATION_ERROR - Validation error (missing parameters, file not found)",
|
|
143
|
+
"5": "EXECUTION_ERROR - Code execution failed in NetSuite",
|
|
144
|
+
"6": "AUTH_ERROR - OAuth authentication failed"
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Format help output as JSON
|
|
150
|
+
*/
|
|
151
|
+
function formatJSON(data) {
|
|
152
|
+
return JSON.stringify(data, null, 2);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Format help output as plain text
|
|
157
|
+
*/
|
|
158
|
+
function formatText(data) {
|
|
159
|
+
let output = '';
|
|
160
|
+
|
|
161
|
+
if (data.command) {
|
|
162
|
+
// Detailed help for specific command
|
|
163
|
+
output += `Command: ${data.command}\n`;
|
|
164
|
+
output += `Brief: ${data.brief}\n`;
|
|
165
|
+
output += `Syntax: ${data.syntax}\n\n`;
|
|
166
|
+
|
|
167
|
+
if (data.description) {
|
|
168
|
+
output += `Description:\n${data.description}\n\n`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (data.options && data.options.length > 0) {
|
|
172
|
+
output += `Options:\n`;
|
|
173
|
+
data.options.forEach(opt => {
|
|
174
|
+
output += ` ${opt.flag.padEnd(30)} ${opt.description}\n`;
|
|
175
|
+
});
|
|
176
|
+
output += '\n';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (data.examples && data.examples.length > 0) {
|
|
180
|
+
output += `Examples:\n`;
|
|
181
|
+
data.examples.forEach(ex => {
|
|
182
|
+
output += ` ${ex}\n`;
|
|
183
|
+
});
|
|
184
|
+
output += '\n';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (data.clarifications && data.clarifications.length > 0) {
|
|
188
|
+
output += `Clarifications:\n`;
|
|
189
|
+
data.clarifications.forEach(cl => {
|
|
190
|
+
output += ` - ${cl}\n`;
|
|
191
|
+
});
|
|
192
|
+
output += '\n';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (data.exitCodes) {
|
|
196
|
+
output += `Exit Codes:\n`;
|
|
197
|
+
Object.keys(data.exitCodes).forEach(code => {
|
|
198
|
+
output += ` ${code}: ${data.exitCodes[code]}\n`;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
// Overview of all commands
|
|
203
|
+
output += `NetSuite GM - Help\n`;
|
|
204
|
+
output += `${'='.repeat(50)}\n\n`;
|
|
205
|
+
output += `Available Commands:\n\n`;
|
|
206
|
+
|
|
207
|
+
Object.keys(data.commands).forEach(cmd => {
|
|
208
|
+
const cmdData = data.commands[cmd];
|
|
209
|
+
output += ` ${cmd.padEnd(12)} ${cmdData.brief}\n`;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
output += `\n\nExit Codes:\n`;
|
|
213
|
+
Object.keys(data.exitCodes).forEach(code => {
|
|
214
|
+
output += ` ${code}: ${data.exitCodes[code]}\n`;
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
output += `\n\nFor detailed help: ns-gm help <command>\n`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return output;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Help command implementation
|
|
225
|
+
*/
|
|
226
|
+
async function helpCommand(commandName, options) {
|
|
227
|
+
try {
|
|
228
|
+
const format = options.format || 'json';
|
|
229
|
+
|
|
230
|
+
if (!commandName) {
|
|
231
|
+
// Show overview of all commands
|
|
232
|
+
const data = {
|
|
233
|
+
commands: {},
|
|
234
|
+
exitCodes: helpData.exitCodes
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Include brief descriptions only
|
|
238
|
+
Object.keys(helpData.commands).forEach(cmd => {
|
|
239
|
+
data.commands[cmd] = {
|
|
240
|
+
brief: helpData.commands[cmd].brief,
|
|
241
|
+
syntax: helpData.commands[cmd].syntax
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const output = format === 'text' ? formatText(data) : formatJSON(data);
|
|
246
|
+
console.log(output);
|
|
247
|
+
} else {
|
|
248
|
+
// Show detailed help for specific command
|
|
249
|
+
const cmdData = helpData.commands[commandName];
|
|
250
|
+
|
|
251
|
+
if (!cmdData) {
|
|
252
|
+
console.error(`Unknown command: ${commandName}`);
|
|
253
|
+
console.error(`Available commands: ${Object.keys(helpData.commands).join(', ')}`);
|
|
254
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const data = {
|
|
258
|
+
command: commandName,
|
|
259
|
+
...cmdData,
|
|
260
|
+
exitCodes: helpData.exitCodes
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const output = format === 'text' ? formatText(data) : formatJSON(data);
|
|
264
|
+
console.log(output);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error(`Help error: ${error.message}`);
|
|
270
|
+
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
module.exports = helpCommand;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const axios = require('axios');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const net = require('net');
|
|
5
|
+
const { EXIT_CODES, exitWithCode } = require('../utils/exitCodes');
|
|
6
|
+
const config = require('../utils/config');
|
|
7
|
+
const { loadConfigWithWarning } = require('../utils/config');
|
|
8
|
+
|
|
9
|
+
const PORT = config.proxyPort;
|
|
10
|
+
const PROXY_URL = `http://localhost:${PORT}`;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if a port is in use
|
|
14
|
+
*/
|
|
15
|
+
function isPortInUse(port) {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
const server = net.createServer();
|
|
18
|
+
|
|
19
|
+
server.once('error', (err) => {
|
|
20
|
+
if (err.code === 'EADDRINUSE') {
|
|
21
|
+
resolve(true); // Port is in use
|
|
22
|
+
} else {
|
|
23
|
+
resolve(false);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
server.once('listening', () => {
|
|
28
|
+
server.close();
|
|
29
|
+
resolve(false); // Port is available
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
server.listen(port);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Wait for proxy server to become healthy
|
|
38
|
+
*/
|
|
39
|
+
async function waitForHealth(maxAttempts = 10, delayMs = 500) {
|
|
40
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
41
|
+
try {
|
|
42
|
+
const response = await axios.get(`${PROXY_URL}/health`, { timeout: 1000 });
|
|
43
|
+
if (response.data.status === 'ok') {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
// Server not ready yet, wait and retry
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Start the proxy server
|
|
58
|
+
*/
|
|
59
|
+
async function initCommand() {
|
|
60
|
+
try {
|
|
61
|
+
// Load config with warning (shows warning if config.json not found)
|
|
62
|
+
loadConfigWithWarning();
|
|
63
|
+
|
|
64
|
+
// Check if proxy is already running
|
|
65
|
+
const portInUse = await isPortInUse(PORT);
|
|
66
|
+
|
|
67
|
+
if (portInUse) {
|
|
68
|
+
// Verify it's our proxy by checking /health
|
|
69
|
+
try {
|
|
70
|
+
const response = await axios.get(`${PROXY_URL}/health`, { timeout: 2000 });
|
|
71
|
+
if (response.data.status === 'ok') {
|
|
72
|
+
console.log(`Proxy server is already running on port ${PORT}`);
|
|
73
|
+
console.log(`Health check: ${PROXY_URL}/health`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
exitWithCode(EXIT_CODES.CONFIG_ERROR, `Port ${PORT} is in use by another process. Please stop it or choose a different port.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Start proxy server as detached process
|
|
82
|
+
const serverPath = path.join(__dirname, '../../server/app.js');
|
|
83
|
+
const child = spawn('node', [serverPath], {
|
|
84
|
+
detached: true,
|
|
85
|
+
stdio: 'ignore'
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
child.unref(); // Allow parent to exit independently
|
|
89
|
+
|
|
90
|
+
console.log('Starting proxy server...');
|
|
91
|
+
|
|
92
|
+
// Wait for server to become healthy
|
|
93
|
+
const healthy = await waitForHealth();
|
|
94
|
+
|
|
95
|
+
if (healthy) {
|
|
96
|
+
console.log(`✓ Proxy server started successfully on port ${PORT}`);
|
|
97
|
+
console.log(`Health check: ${PROXY_URL}/health`);
|
|
98
|
+
} else {
|
|
99
|
+
exitWithCode(EXIT_CODES.CONFIG_ERROR, 'Failed to start proxy server. Check your ns-gm configuration.');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
} catch (error) {
|
|
103
|
+
exitWithCode(EXIT_CODES.GENERAL_ERROR, `Error starting proxy server: ${error.message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = initCommand;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const { EXIT_CODES, exitWithCode } = require('../utils/exitCodes');
|
|
3
|
+
const config = require('../utils/config');
|
|
4
|
+
|
|
5
|
+
const PORT = config.proxyPort;
|
|
6
|
+
const PROXY_URL = `http://localhost:${PORT}`;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Format and display logs
|
|
10
|
+
*/
|
|
11
|
+
function displayLogs(response) {
|
|
12
|
+
const { success, logs, governance, error } = response;
|
|
13
|
+
|
|
14
|
+
// Handle case where query succeeded but no logs found (success: false, error: null)
|
|
15
|
+
if (!success && !error) {
|
|
16
|
+
console.log('\nNo logs found for the specified criteria.');
|
|
17
|
+
if (governance) {
|
|
18
|
+
console.log(`Governance used: ${governance.initial - governance.remaining} units`);
|
|
19
|
+
}
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!success) {
|
|
24
|
+
console.error('\n✗ Failed to retrieve logs\n');
|
|
25
|
+
console.error('Error:', error || 'Unknown error');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!logs || logs.length === 0) {
|
|
30
|
+
console.log('\nNo logs found for the specified criteria.');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(`\nFound ${logs.length} log entries:\n`);
|
|
35
|
+
console.log('─'.repeat(80));
|
|
36
|
+
|
|
37
|
+
logs.forEach((log, index) => {
|
|
38
|
+
console.log(`\n[${index + 1}] ${log.date || 'Unknown Date'}`);
|
|
39
|
+
console.log(` Type: ${log.type || 'N/A'}`);
|
|
40
|
+
console.log(` Title: ${log.title || 'N/A'}`);
|
|
41
|
+
if (log.details) {
|
|
42
|
+
console.log(` Details: ${log.details}`);
|
|
43
|
+
}
|
|
44
|
+
if (log.scriptType) {
|
|
45
|
+
console.log(` Script Type: ${log.scriptType}`);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
console.log('\n' + '─'.repeat(80));
|
|
50
|
+
|
|
51
|
+
if (governance) {
|
|
52
|
+
console.log(`\nGovernance used: ${governance.initial - governance.remaining} units`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check if proxy server is running
|
|
58
|
+
*/
|
|
59
|
+
async function checkProxyHealth() {
|
|
60
|
+
try {
|
|
61
|
+
const response = await axios.get(`${PROXY_URL}/health`, { timeout: 2000 });
|
|
62
|
+
return response.data.status === 'ok';
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Retrieve logs command
|
|
70
|
+
*/
|
|
71
|
+
async function logsCommand(options) {
|
|
72
|
+
try {
|
|
73
|
+
const { scriptId, dateFrom, dateTo, type, page, pageSize } = options;
|
|
74
|
+
|
|
75
|
+
// Check if proxy is running
|
|
76
|
+
const proxyHealthy = await checkProxyHealth();
|
|
77
|
+
if (!proxyHealthy) {
|
|
78
|
+
console.error('Error: Proxy server is not running.');
|
|
79
|
+
exitWithCode(EXIT_CODES.NETWORK_ERROR, 'Start it with: ns-gm init');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Build request payload
|
|
83
|
+
const payload = {};
|
|
84
|
+
|
|
85
|
+
if (scriptId) payload.scriptId = scriptId;
|
|
86
|
+
if (dateFrom) payload.dateFrom = dateFrom;
|
|
87
|
+
if (dateTo) payload.dateTo = dateTo;
|
|
88
|
+
if (type) payload.type = type.toUpperCase();
|
|
89
|
+
if (page) payload.pageIndex = parseInt(page);
|
|
90
|
+
if (pageSize) payload.pageSize = parseInt(pageSize);
|
|
91
|
+
|
|
92
|
+
// Retrieve logs
|
|
93
|
+
const targetScript = scriptId ? scriptId : 'RESTlet runner';
|
|
94
|
+
console.log(`Retrieving logs for ${targetScript}...`);
|
|
95
|
+
const response = await axios.post(`${PROXY_URL}/logs`, payload, {
|
|
96
|
+
timeout: 30000 // 30 second timeout
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Display logs
|
|
100
|
+
displayLogs(response.data);
|
|
101
|
+
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error.response) {
|
|
104
|
+
// Server responded with error
|
|
105
|
+
console.error('\n✗ Failed to retrieve logs\n');
|
|
106
|
+
const errorMsg = error.response.data.error || error.message;
|
|
107
|
+
console.error('Error:', errorMsg);
|
|
108
|
+
exitWithCode(EXIT_CODES.EXECUTION_ERROR);
|
|
109
|
+
} else if (error.code === 'ECONNREFUSED') {
|
|
110
|
+
console.error('Error: Cannot connect to proxy server.');
|
|
111
|
+
exitWithCode(EXIT_CODES.NETWORK_ERROR, 'Start it with: ns-gm init');
|
|
112
|
+
} else {
|
|
113
|
+
exitWithCode(EXIT_CODES.GENERAL_ERROR, `Error: ${error.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = logsCommand;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { EXIT_CODES, exitWithCode } = require('../utils/exitCodes');
|
|
5
|
+
const config = require('../utils/config');
|
|
6
|
+
|
|
7
|
+
const PORT = config.proxyPort;
|
|
8
|
+
const PROXY_URL = `http://localhost:${PORT}`;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Format and display execution results
|
|
12
|
+
*/
|
|
13
|
+
function displayResults(response) {
|
|
14
|
+
const { success, result, executionTime, governance, logs, error } = response;
|
|
15
|
+
|
|
16
|
+
if (success) {
|
|
17
|
+
console.log('\n✓ Execution successful\n');
|
|
18
|
+
console.log('Result:', result);
|
|
19
|
+
console.log(`\nExecution time: ${executionTime}ms`);
|
|
20
|
+
|
|
21
|
+
if (governance) {
|
|
22
|
+
console.log('\nGovernance:');
|
|
23
|
+
console.log(` Initial: ${governance.initial}`);
|
|
24
|
+
console.log(` Remaining: ${governance.remaining}`);
|
|
25
|
+
console.log(` Used: ${governance.used}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (logs && logs.length > 0) {
|
|
29
|
+
console.log('\nLogs:');
|
|
30
|
+
logs.forEach(log => console.log(` ${log}`));
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
console.error('\n✗ Execution failed\n');
|
|
34
|
+
console.error('Error:', error || 'Unknown error');
|
|
35
|
+
|
|
36
|
+
if (logs && logs.length > 0) {
|
|
37
|
+
console.log('\nLogs:');
|
|
38
|
+
logs.forEach(log => console.log(` ${log}`));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if proxy server is running
|
|
45
|
+
*/
|
|
46
|
+
async function checkProxyHealth() {
|
|
47
|
+
try {
|
|
48
|
+
const response = await axios.get(`${PROXY_URL}/health`, { timeout: 2000 });
|
|
49
|
+
return response.data.status === 'ok';
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Execute code command
|
|
57
|
+
*/
|
|
58
|
+
async function runCommand(options) {
|
|
59
|
+
try {
|
|
60
|
+
const { code, file } = options;
|
|
61
|
+
|
|
62
|
+
// Validate input
|
|
63
|
+
if (!code && !file) {
|
|
64
|
+
console.error('Error: Must provide either --code or --file');
|
|
65
|
+
console.error('Usage: ns-gm run --code "return 2+2"');
|
|
66
|
+
console.error(' or: ns-gm run --file script.js');
|
|
67
|
+
exitWithCode(EXIT_CODES.VALIDATION_ERROR);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (code && file) {
|
|
71
|
+
exitWithCode(EXIT_CODES.VALIDATION_ERROR, 'Error: Cannot use both --code and --file. Choose one.');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Get code to execute
|
|
75
|
+
let codeToExecute = code;
|
|
76
|
+
if (file) {
|
|
77
|
+
const filePath = path.resolve(file);
|
|
78
|
+
if (!fs.existsSync(filePath)) {
|
|
79
|
+
exitWithCode(EXIT_CODES.VALIDATION_ERROR, `Error: File not found: ${filePath}`);
|
|
80
|
+
}
|
|
81
|
+
codeToExecute = fs.readFileSync(filePath, 'utf8');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check if proxy is running
|
|
85
|
+
const proxyHealthy = await checkProxyHealth();
|
|
86
|
+
if (!proxyHealthy) {
|
|
87
|
+
console.error('Error: Proxy server is not running.');
|
|
88
|
+
exitWithCode(EXIT_CODES.NETWORK_ERROR, 'Start it with: ns-gm init');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Execute code
|
|
92
|
+
console.log('Executing code...');
|
|
93
|
+
const response = await axios.post(`${PROXY_URL}/run`, {
|
|
94
|
+
code: codeToExecute
|
|
95
|
+
}, {
|
|
96
|
+
timeout: 30000 // 30 second timeout
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Display results
|
|
100
|
+
displayResults(response.data);
|
|
101
|
+
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error.response) {
|
|
104
|
+
// Server responded with error
|
|
105
|
+
console.error('\n✗ Execution failed\n');
|
|
106
|
+
const errorMsg = error.response.data.error || error.message;
|
|
107
|
+
console.error('Error:', errorMsg);
|
|
108
|
+
exitWithCode(EXIT_CODES.EXECUTION_ERROR);
|
|
109
|
+
} else if (error.code === 'ECONNREFUSED') {
|
|
110
|
+
console.error('Error: Cannot connect to proxy server.');
|
|
111
|
+
exitWithCode(EXIT_CODES.NETWORK_ERROR, 'Start it with: ns-gm init');
|
|
112
|
+
} else {
|
|
113
|
+
exitWithCode(EXIT_CODES.GENERAL_ERROR, `Error: ${error.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = runCommand;
|