-
Notifications
You must be signed in to change notification settings - Fork 142
Expand file tree
/
Copy pathCompileTools.ts
More file actions
228 lines (195 loc) Β· 8.54 KB
/
CompileTools.ts
File metadata and controls
228 lines (195 loc) Β· 8.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
import IBMi from './IBMi';
import { SimpleQueue } from './queue';
import { Tools } from './Tools';
import { CommandResult, RemoteCommand, StandardIO } from './types';
import { Variables } from './variables';
export interface ILELibrarySettings {
currentLibrary: string;
libraryList: string[];
}
export namespace CompileTools {
export const NEWLINE = `\r\n`;
export const DID_NOT_RUN = -123;
const HIDE_MESSAGE_IDS = [`CPF3485`, `SQL0462`];
const ileQueue = new SimpleQueue();
let jobLogOrdinal = 0;
interface RunCommandEvents {
writeEvent?: (content: string) => void
commandConfirm?: (command: string) => Promise<string>
updateProgress?: (message: string) => void
}
/**
* Execute a command
*/
export async function runCommand(connection: IBMi, options: RemoteCommand, events: RunCommandEvents = {}): Promise<CommandResult> {
await updateDefaultUserLibraries(connection);
const config = connection.getConfig();
if (config && connection) {
const cwd = options.cwd;
const variables = new Variables(connection, options.env);
const ileSetup: ILELibrarySettings = {
currentLibrary: variables.get(`&CURLIB`) || config.currentLibrary,
libraryList: variables.get(`&LIBL`)?.split(` `) || config.libraryList,
};
// Remove any duplicates from the library list
ileSetup.libraryList = ileSetup.libraryList.filter(Tools.distinct);
const libraryList = buildLibraryList(ileSetup);
variables.set(`&LIBLS`, libraryList.join(` `));
let commandString = variables.expand(options.command);
if (events.commandConfirm) {
events.updateProgress?.(" - Prompting...");
commandString = await events.commandConfirm(commandString);
events.updateProgress?.("");
}
if (commandString) {
const commands = commandString.split(`\n`).filter(command => command.trim().length > 0);
if (events.writeEvent) {
if (options.environment === `ile` && !options.noLibList) {
events.writeEvent(`Current library: ` + ileSetup.currentLibrary + NEWLINE);
events.writeEvent(`Library list: ` + ileSetup.libraryList.join(` `) + NEWLINE);
}
if (options.cwd) {
events.writeEvent(`Working directory: ` + options.cwd + NEWLINE);
}
events.writeEvent(`Commands:\n${commands.map(command => `\t${command}\n`).join(``)}` + NEWLINE);
}
const callbacks: StandardIO = events.writeEvent ? {
onStdout: (data) => {
events.writeEvent!(data.toString().replaceAll(`\n`, NEWLINE));
},
onStderr: (data) => {
events.writeEvent!(data.toString().replaceAll(`\n`, NEWLINE));
}
} : {};
let commandResult: CommandResult;
switch (options.environment) {
case `pase`:
commandResult = await connection.sendCommand({
command: commands.join(` && `),
directory: cwd,
env: variables.toPaseVariables(),
...callbacks
});
break;
case `qsh`:
commandResult = await connection.sendQsh({
command: [
...options.noLibList ? [] : buildLiblistCommands(connection, ileSetup),
...commands,
].join(` && `),
directory: cwd,
...callbacks
});
break;
case `ile`:
default:
commandResult = {
code: 0,
stderr: ``,
stdout: ``,
command: commands.join(`, `),
};
await ileQueue.next(async () => {
const start = options.getSpooledFiles ? (await connection.runSQL('Values Current TimeStamp'))[0]["00001"] : undefined;
try {
await connection.runSQL([
...(cwd ? [`@CHGCURDIR DIR('${cwd}')`] : []),
...(options.noLibList ? [] : [`@CHGLIBL CURLIB(${ileSetup.currentLibrary}) LIBL(${ileSetup.libraryList.join(` `)})`]),
...commands.map(c => `@${c}`)
]);
} catch (e: any) {
commandResult.stdout = e.message;
commandResult.code = 1;
}
// Then fetch the job log
try {
// We only care about messages since the last run :)
const lastJobLog = await connection.runSQL(`select ORDINAL_POSITION, message_id, message_text from table(qsys2.joblog_info('*')) where ordinal_position > ${jobLogOrdinal}`);
if (lastJobLog?.length) {
commandResult.stderr = lastJobLog
.filter(r => !HIDE_MESSAGE_IDS.includes(r.MESSAGE_ID as string))
.map(r => `${r.MESSAGE_ID}: ${r.MESSAGE_TEXT}`).join(`\n`);
callbacks.onStderr?.(Buffer.from(commandResult.stderr));
jobLogOrdinal = Number(lastJobLog[lastJobLog.length - 1].ORDINAL_POSITION);
} else {
jobLogOrdinal = 0; // Reset if no job log
}
} catch (e) {
commandResult.code = 3;
}
// Fetch the spooled files if requested
if (start) {
try {
const spooledOutputs: string[] = [];
const spooledFiles = await connection.runSQL(`SELECT QUALIFIED_JOB_NAME, SPOOLED_FILE_NAME, SPOOLED_FILE_NUMBER FROM TABLE(QSYS2.SPOOLED_FILE_INFO(STARTING_TIMESTAMP => '${start}', USER_DATA => '${connection.splfUserData}', STATUS => '*HELD'))`);
for (const spooledFile of spooledFiles) {
spooledOutputs.push((await connection.runSQL(`SELECT spooled_data as LINE FROM TABLE(systools.spooled_file_data(
job_name => '${spooledFile.QUALIFIED_JOB_NAME}',
spooled_file_name => '${spooledFile.SPOOLED_FILE_NAME}',
spooled_file_number => ${spooledFile.SPOOLED_FILE_NUMBER})
)`)).map(row => String(row.LINE).trimEnd()).join("\n"));
}
commandResult.stdout = spooledOutputs.join("\n\n");
callbacks.onStdout?.(Buffer.from(commandResult.stdout));
} catch (e) {
commandResult.code = 2;
callbacks.onStderr?.(Buffer.from(`Failed to get spool output: ${JSON.stringify(e, undefined, 2)}`));
}
finally {
if (!connection.getConfig().keepActionSpooledFiles) {
await connection.runSQL(`@DLTSPLF FILE(*SELECT) SELECT(*CURRENT *ALL *ALL ${connection.splfUserData})`);
}
}
}
});
break;
}
commandResult.command = commandString;
return commandResult;
} else {
return {
code: DID_NOT_RUN,
command: options.command,
stdout: ``,
stderr: `Command execution failed. (No command)`,
};
}
}
else {
throw new Error("Please connect to an IBM i");
}
}
function buildLibraryList(config: ILELibrarySettings): string[] {
//We have to reverse it because `liblist -a` adds the next item to the top always
return config.libraryList.slice(0).reverse();
}
function buildLiblistCommands(connection: IBMi, config: ILELibrarySettings): string[] {
return [
`liblist -d ${IBMi.escapeForShell(Tools.sanitizeObjNamesForPase(connection.defaultUserLibraries).join(` `))}`,
`liblist -c ${IBMi.escapeForShell(Tools.sanitizeObjNamesForPase([config.currentLibrary])[0])}`,
`liblist -a ${IBMi.escapeForShell(Tools.sanitizeObjNamesForPase(buildLibraryList(config)).join(` `))}`
];
}
/**
* Update connection.defaultUserLibraries by fetching the current library list from the server
* @param connection IBMi connection
*/
async function updateDefaultUserLibraries(connection: IBMi): Promise<void> {
const liblResult = await connection.sendQsh({
command: `liblist`
});
if (liblResult.code === 0 && liblResult.stdout) {
const userLibraries: string[] = [];
const libraryList = liblResult.stdout.split(`\n`);
for (const line of libraryList) {
const lib = line.substring(0, 10).trim();
const type = line.substring(12).trim();
if (lib && type === `USR`) {
userLibraries.push(lib);
}
}
// Update connection.defaultUserLibraries with the libraries from the server
connection.defaultUserLibraries = userLibraries;
}
}
}