mcp-for-i 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.
- package/README.md +53 -0
- package/dist/config/store.js +365 -0
- package/dist/controlplane/autostart.js +93 -0
- package/dist/controlplane/connectionService.js +79 -0
- package/dist/controlplane/index.js +120 -0
- package/dist/controlplane/runtimeService.js +263 -0
- package/dist/controlplane/server.js +180 -0
- package/dist/controlplane/ui.js +741 -0
- package/dist/ibmi/CompileTools.js +91 -0
- package/dist/ibmi/IBMiContent.js +386 -0
- package/dist/ibmi/LocalLanguageActions.js +183 -0
- package/dist/ibmi/Search.js +135 -0
- package/dist/ibmi/Tools.js +188 -0
- package/dist/ibmi/client.js +213 -0
- package/dist/ibmi/sourceDates.js +60 -0
- package/dist/ibmi/types.js +1 -0
- package/dist/ibmi/variables.js +53 -0
- package/dist/index.js +59 -0
- package/dist/mcp/context.js +235 -0
- package/dist/mcp/logger.js +73 -0
- package/dist/mcp/tools.js +1335 -0
- package/dist/mcp/validation.js +74 -0
- package/dist/security/credentialStore.js +42 -0
- package/docs/configuration.md +57 -0
- package/docs/examples.md +73 -0
- package/docs/installation.md +61 -0
- package/docs/overview.md +14 -0
- package/docs/security.md +8 -0
- package/docs/tools.md +105 -0
- package/package.json +49 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { IBMiClient } from "./client.js";
|
|
2
|
+
import { Tools } from "./Tools.js";
|
|
3
|
+
import { Variables } from "./variables.js";
|
|
4
|
+
export var CompileTools;
|
|
5
|
+
(function (CompileTools) {
|
|
6
|
+
CompileTools.NEWLINE = `\r\n`;
|
|
7
|
+
CompileTools.DID_NOT_RUN = -123;
|
|
8
|
+
async function runCommand(connection, options, events = {}) {
|
|
9
|
+
const config = connection.getConfig();
|
|
10
|
+
const cwd = options.cwd;
|
|
11
|
+
const variables = new Variables(connection, options.env);
|
|
12
|
+
const ileSetup = {
|
|
13
|
+
currentLibrary: variables.get(`&CURLIB`) || config.currentLibrary || "",
|
|
14
|
+
libraryList: variables.get(`&LIBL`)?.split(` `) || config.libraryList || []
|
|
15
|
+
};
|
|
16
|
+
ileSetup.libraryList = ileSetup.libraryList.filter(Tools.distinct);
|
|
17
|
+
const libraryList = buildLibraryList(ileSetup);
|
|
18
|
+
variables.set(`&LIBLS`, libraryList.join(` `));
|
|
19
|
+
let commandString = variables.expand(options.command);
|
|
20
|
+
if (events.commandConfirm) {
|
|
21
|
+
events.updateProgress?.(" - Prompting...");
|
|
22
|
+
commandString = await events.commandConfirm(commandString);
|
|
23
|
+
events.updateProgress?.("");
|
|
24
|
+
}
|
|
25
|
+
if (commandString) {
|
|
26
|
+
const commands = commandString.split(`\n`).filter(cmd => cmd.trim().length > 0);
|
|
27
|
+
if (events.writeEvent) {
|
|
28
|
+
if ((options.environment || "ile") === "ile" && !options.noLibList) {
|
|
29
|
+
events.writeEvent(`Current library: ` + ileSetup.currentLibrary + CompileTools.NEWLINE);
|
|
30
|
+
events.writeEvent(`Library list: ` + ileSetup.libraryList.join(` `) + CompileTools.NEWLINE);
|
|
31
|
+
}
|
|
32
|
+
if (options.cwd) {
|
|
33
|
+
events.writeEvent(`Working directory: ` + options.cwd + CompileTools.NEWLINE);
|
|
34
|
+
}
|
|
35
|
+
events.writeEvent(`Commands:\n${commands.map(cmd => `\t${cmd}\n`).join(``)}` + CompileTools.NEWLINE);
|
|
36
|
+
}
|
|
37
|
+
let commandResult;
|
|
38
|
+
const env = options.environment || "ile";
|
|
39
|
+
switch (env) {
|
|
40
|
+
case "pase":
|
|
41
|
+
commandResult = await connection.sendCommand({
|
|
42
|
+
command: commands.join(` && `),
|
|
43
|
+
directory: cwd,
|
|
44
|
+
env: variables.toPaseVariables()
|
|
45
|
+
});
|
|
46
|
+
break;
|
|
47
|
+
case "qsh":
|
|
48
|
+
commandResult = await connection.sendQsh({
|
|
49
|
+
command: [
|
|
50
|
+
...(options.noLibList ? [] : buildLiblistCommands(connection, ileSetup)),
|
|
51
|
+
...commands
|
|
52
|
+
].join(` && `),
|
|
53
|
+
directory: cwd
|
|
54
|
+
});
|
|
55
|
+
break;
|
|
56
|
+
case "ile":
|
|
57
|
+
default:
|
|
58
|
+
commandResult = await connection.sendQsh({
|
|
59
|
+
command: [
|
|
60
|
+
...(options.noLibList ? [] : buildLiblistCommands(connection, ileSetup)),
|
|
61
|
+
...commands.map(cmd => `system \"${IBMiClient.escapeForShell(cmd)}\"`)
|
|
62
|
+
].join(` && `),
|
|
63
|
+
directory: cwd
|
|
64
|
+
});
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
commandResult.command = commandString;
|
|
68
|
+
return commandResult;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
code: CompileTools.DID_NOT_RUN,
|
|
72
|
+
command: options.command,
|
|
73
|
+
stdout: "",
|
|
74
|
+
stderr: "Command execution failed. (No command)"
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
CompileTools.runCommand = runCommand;
|
|
78
|
+
function buildLibraryList(config) {
|
|
79
|
+
return config.libraryList.slice(0).reverse();
|
|
80
|
+
}
|
|
81
|
+
function buildLiblistCommands(connection, config) {
|
|
82
|
+
const defaultLibs = Tools.sanitizeObjNamesForPase((connection.defaultUserLibraries || []).filter(l => l && l !== "QSYS"));
|
|
83
|
+
const currentLib = Tools.sanitizeObjNamesForPase([config.currentLibrary || "QGPL"])[0];
|
|
84
|
+
const userLibs = Tools.sanitizeObjNamesForPase(buildLibraryList(config));
|
|
85
|
+
return [
|
|
86
|
+
`liblist -d ${IBMiClient.escapeForShell(defaultLibs.join(` `))}`,
|
|
87
|
+
`liblist -c ${IBMiClient.escapeForShell(currentLib)}`,
|
|
88
|
+
`liblist -a ${IBMiClient.escapeForShell(userLibs.join(` `))}`
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
})(CompileTools || (CompileTools = {}));
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { Tools } from "./Tools.js";
|
|
5
|
+
import { IBMiClient } from "./client.js";
|
|
6
|
+
import { SourceDateStore, makeAliasName } from "./sourceDates.js";
|
|
7
|
+
export class IBMiContent {
|
|
8
|
+
ibmi;
|
|
9
|
+
sourceDates = new SourceDateStore();
|
|
10
|
+
constructor(ibmi) {
|
|
11
|
+
this.ibmi = ibmi;
|
|
12
|
+
}
|
|
13
|
+
get config() {
|
|
14
|
+
return this.ibmi.getConfig();
|
|
15
|
+
}
|
|
16
|
+
async getTempLibrary() {
|
|
17
|
+
const desired = this.ibmi.upperCaseName(this.config.tempLibrary || "ILEDITOR");
|
|
18
|
+
if (desired === "QTEMP")
|
|
19
|
+
return "QTEMP";
|
|
20
|
+
try {
|
|
21
|
+
const rows = await this.ibmi.runSQL(`select SYSTEM_SCHEMA_NAME from QSYS2.SYSSCHEMAS where SYSTEM_SCHEMA_NAME=${Tools.sqlString(desired)}`);
|
|
22
|
+
if (rows.length > 0)
|
|
23
|
+
return desired;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// ignore and fall back to QTEMP
|
|
27
|
+
}
|
|
28
|
+
if (!this.config.readOnlyMode) {
|
|
29
|
+
try {
|
|
30
|
+
await this.createLibrary(desired);
|
|
31
|
+
return desired;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// ignore
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
async downloadStreamfileRaw(remotePath) {
|
|
40
|
+
const tmpFile = await this.createTempFile();
|
|
41
|
+
await this.ibmi.client.getFile(tmpFile, remotePath);
|
|
42
|
+
return fs.readFile(tmpFile);
|
|
43
|
+
}
|
|
44
|
+
async writeStreamfileRaw(remotePath, content) {
|
|
45
|
+
this.assertWritable();
|
|
46
|
+
const tmpFile = await this.createTempFile();
|
|
47
|
+
await fs.writeFile(tmpFile, content);
|
|
48
|
+
return this.ibmi.client.putFile(tmpFile, remotePath);
|
|
49
|
+
}
|
|
50
|
+
async createStreamFile(remotePath) {
|
|
51
|
+
this.assertWritable();
|
|
52
|
+
return this.ibmi.sendCommand({ command: `touch ${Tools.escapePath(remotePath)}` });
|
|
53
|
+
}
|
|
54
|
+
async testStreamFile(remotePath, mode) {
|
|
55
|
+
const flag = mode === "d" ? "-d" : mode === "w" ? "-w" : "-e";
|
|
56
|
+
const res = await this.ibmi.sendCommand({ command: `test ${flag} ${Tools.escapePath(remotePath)} && echo 1 || echo 0` });
|
|
57
|
+
return res.stdout.trim() === "1";
|
|
58
|
+
}
|
|
59
|
+
async getFileList(remotePath) {
|
|
60
|
+
const ls = this.ibmi.remoteFeatures.ls || "ls";
|
|
61
|
+
const res = await this.ibmi.sendCommand({ command: `${ls} -la ${Tools.escapePath(remotePath)}` });
|
|
62
|
+
const lines = res.stdout.split("\n").filter(l => l.trim().length > 0);
|
|
63
|
+
const files = [];
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
if (line.startsWith("total"))
|
|
66
|
+
continue;
|
|
67
|
+
const parts = line.split(/\s+/);
|
|
68
|
+
if (parts.length < 9)
|
|
69
|
+
continue;
|
|
70
|
+
const typeChar = parts[0][0];
|
|
71
|
+
const name = parts.slice(8).join(" ");
|
|
72
|
+
if (name === "." || name === "..")
|
|
73
|
+
continue;
|
|
74
|
+
files.push({
|
|
75
|
+
type: typeChar === "d" ? "directory" : "streamfile",
|
|
76
|
+
name,
|
|
77
|
+
path: path.posix.join(remotePath, name),
|
|
78
|
+
size: Number(parts[4])
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return files;
|
|
82
|
+
}
|
|
83
|
+
async downloadMemberContent(library, sourceFile, member) {
|
|
84
|
+
if (this.config.enableSourceDates) {
|
|
85
|
+
const withDates = await this.downloadMemberContentWithDates(library, sourceFile, member);
|
|
86
|
+
if (withDates !== undefined)
|
|
87
|
+
return withDates;
|
|
88
|
+
}
|
|
89
|
+
const lib = this.ibmi.upperCaseName(library);
|
|
90
|
+
const file = this.ibmi.upperCaseName(sourceFile);
|
|
91
|
+
const mbr = this.ibmi.upperCaseName(member);
|
|
92
|
+
const tempRemote = this.ibmi.getTempRemote("mbr");
|
|
93
|
+
const ccsid = this.config.sourceFileCCSID || "*FILE";
|
|
94
|
+
const cmd = `QSYS/CPYTOSTMF FROMMBR('${Tools.qualifyPath(lib, file, mbr, undefined, true)}') ` +
|
|
95
|
+
`TOSTMF('${tempRemote}') STMFOPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${ccsid})`;
|
|
96
|
+
const result = await this.ibmi.sendQsh({ command: `system \"${IBMiClient.escapeForShell(cmd)}\"` });
|
|
97
|
+
if (result.code !== 0)
|
|
98
|
+
throw new Error(result.stderr || "CPYTOSTMF failed");
|
|
99
|
+
const buffer = await this.downloadStreamfileRaw(tempRemote);
|
|
100
|
+
await this.ibmi.sendCommand({ command: `rm -f ${Tools.escapePath(tempRemote)}` });
|
|
101
|
+
return Buffer.from(buffer).toString("utf8");
|
|
102
|
+
}
|
|
103
|
+
async uploadMemberContent(library, sourceFile, member, content) {
|
|
104
|
+
if (this.config.enableSourceDates) {
|
|
105
|
+
try {
|
|
106
|
+
await this.uploadMemberContentWithDates(library, sourceFile, member, content.toString());
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Fall back to normal upload without source dates
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
await this.uploadMemberContentRaw(library, sourceFile, member, content);
|
|
114
|
+
}
|
|
115
|
+
async uploadMemberContentRaw(library, sourceFile, member, content) {
|
|
116
|
+
this.assertWritable();
|
|
117
|
+
const lib = this.ibmi.upperCaseName(library);
|
|
118
|
+
const file = this.ibmi.upperCaseName(sourceFile);
|
|
119
|
+
const mbr = this.ibmi.upperCaseName(member);
|
|
120
|
+
const tempRemote = this.ibmi.getTempRemote("mbr");
|
|
121
|
+
await this.writeStreamfileRaw(tempRemote, content);
|
|
122
|
+
const ccsid = this.config.sourceFileCCSID || "*FILE";
|
|
123
|
+
const cmd = `QSYS/CPYFRMSTMF FROMSTMF('${tempRemote}') ` +
|
|
124
|
+
`TOMBR('${Tools.qualifyPath(lib, file, mbr, undefined, true)}') MBROPT(*REPLACE) ` +
|
|
125
|
+
`STMFCCSID(1208) DBFCCSID(${ccsid})`;
|
|
126
|
+
const result = await this.ibmi.sendQsh({ command: `system \"${IBMiClient.escapeForShell(cmd)}\"` });
|
|
127
|
+
if (result.code !== 0)
|
|
128
|
+
throw new Error(result.stderr || "CPYFRMSTMF failed");
|
|
129
|
+
await this.ibmi.sendCommand({ command: `rm -f ${Tools.escapePath(tempRemote)}` });
|
|
130
|
+
}
|
|
131
|
+
async getLibraries(filter = "*") {
|
|
132
|
+
const libFilter = filter === "*" ? "*ALL" : filter;
|
|
133
|
+
const rows = await this.ibmi.runSQL(`select OBJNAME, OBJTEXT from table(QSYS2.OBJECT_STATISTICS(${Tools.sqlString(libFilter)}, 'LIB', '*ALLSIMPLE'))`);
|
|
134
|
+
return rows.map(r => ({
|
|
135
|
+
library: String(r.OBJNAME),
|
|
136
|
+
name: String(r.OBJNAME),
|
|
137
|
+
type: "*LIB",
|
|
138
|
+
text: String(r.OBJTEXT || "")
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
async getLibraryList(libraries) {
|
|
142
|
+
const libs = libraries.map(l => this.ibmi.upperCaseName(l));
|
|
143
|
+
if (libs.length === 0)
|
|
144
|
+
return [];
|
|
145
|
+
const inClause = libs.map(l => Tools.sqlString(l)).join(", ");
|
|
146
|
+
const rows = await this.ibmi.runSQL(`select SYSTEM_SCHEMA_NAME, SCHEMA_TEXT from QSYS2.SYSSCHEMAS where SYSTEM_SCHEMA_NAME in (${inClause})`);
|
|
147
|
+
const found = rows.map(r => ({
|
|
148
|
+
library: "QSYS",
|
|
149
|
+
name: String(r.SYSTEM_SCHEMA_NAME),
|
|
150
|
+
type: "*LIB",
|
|
151
|
+
text: String(r.SCHEMA_TEXT || "")
|
|
152
|
+
}));
|
|
153
|
+
return libs.map(library => {
|
|
154
|
+
return found.find(f => f.name === library) || {
|
|
155
|
+
library: "QSYS",
|
|
156
|
+
name: library,
|
|
157
|
+
type: "*LIB",
|
|
158
|
+
text: "*** NOT FOUND ***"
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
async validateLibraryList(newLibl) {
|
|
163
|
+
let badLibs = [];
|
|
164
|
+
const libs = newLibl
|
|
165
|
+
.map(l => this.ibmi.upperCaseName(l))
|
|
166
|
+
.filter(l => {
|
|
167
|
+
const ok = this.ibmi.validQsysName(l);
|
|
168
|
+
if (!ok)
|
|
169
|
+
badLibs.push(l);
|
|
170
|
+
return ok;
|
|
171
|
+
});
|
|
172
|
+
if (libs.length === 0)
|
|
173
|
+
return badLibs;
|
|
174
|
+
const inClause = libs.map(l => Tools.sqlString(l)).join(", ");
|
|
175
|
+
const rows = await this.ibmi.runSQL(`select SYSTEM_SCHEMA_NAME from QSYS2.SYSSCHEMAS where SYSTEM_SCHEMA_NAME in (${inClause})`);
|
|
176
|
+
const existing = rows.map(r => String(r.SYSTEM_SCHEMA_NAME));
|
|
177
|
+
libs.forEach(l => {
|
|
178
|
+
if (!existing.includes(l))
|
|
179
|
+
badLibs.push(l);
|
|
180
|
+
});
|
|
181
|
+
return badLibs;
|
|
182
|
+
}
|
|
183
|
+
async getObjectList(library, types = ["*ALL"]) {
|
|
184
|
+
const lib = this.ibmi.upperCaseName(library);
|
|
185
|
+
const safeTypes = types.map(t => this.ibmi.upperCaseName(t));
|
|
186
|
+
const hasInvalidType = safeTypes.some(t => !/^[*A-Z0-9]+$/.test(t));
|
|
187
|
+
if (hasInvalidType)
|
|
188
|
+
throw new Error("Invalid object type filter");
|
|
189
|
+
const typesList = safeTypes.join(" ");
|
|
190
|
+
const rows = await this.ibmi.runSQL(`select OBJNAME, OBJTYPE, OBJTEXT, OBJATTRIBUTE from table(QSYS2.OBJECT_STATISTICS(${Tools.sqlString(lib)}, ${Tools.sqlString(typesList)}, '*ALLSIMPLE'))`);
|
|
191
|
+
return rows.map(r => ({
|
|
192
|
+
library: lib,
|
|
193
|
+
name: String(r.OBJNAME),
|
|
194
|
+
type: String(r.OBJTYPE),
|
|
195
|
+
text: String(r.OBJTEXT || ""),
|
|
196
|
+
attribute: String(r.OBJATTRIBUTE || "")
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
async getMemberList(options) {
|
|
200
|
+
const lib = this.ibmi.upperCaseName(options.library);
|
|
201
|
+
const file = this.ibmi.upperCaseName(options.sourceFile);
|
|
202
|
+
const rows = await this.ibmi.runSQL(`select SYSTEM_TABLE_MEMBER, SOURCE_TYPE, PARTITION_TEXT from QSYS2.SYSPARTITIONSTAT where SYSTEM_TABLE_SCHEMA=${Tools.sqlString(lib)} and SYSTEM_TABLE_NAME=${Tools.sqlString(file)}`);
|
|
203
|
+
return rows.map(r => ({
|
|
204
|
+
library: lib,
|
|
205
|
+
file,
|
|
206
|
+
name: String(r.SYSTEM_TABLE_MEMBER),
|
|
207
|
+
extension: String(r.SOURCE_TYPE || ""),
|
|
208
|
+
text: String(r.PARTITION_TEXT || "")
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
async getMemberInfo(library, sourceFile, member) {
|
|
212
|
+
const lib = this.ibmi.upperCaseName(library);
|
|
213
|
+
const file = this.ibmi.upperCaseName(sourceFile);
|
|
214
|
+
const mbr = this.ibmi.upperCaseName(member);
|
|
215
|
+
const rows = await this.ibmi.runSQL(`select SYSTEM_TABLE_MEMBER, SOURCE_TYPE, PARTITION_TEXT from QSYS2.SYSPARTITIONSTAT where SYSTEM_TABLE_SCHEMA=${Tools.sqlString(lib)} and SYSTEM_TABLE_NAME=${Tools.sqlString(file)} and SYSTEM_TABLE_MEMBER=${Tools.sqlString(mbr)}`);
|
|
216
|
+
const row = rows[0];
|
|
217
|
+
if (!row)
|
|
218
|
+
return undefined;
|
|
219
|
+
return {
|
|
220
|
+
library: lib,
|
|
221
|
+
file,
|
|
222
|
+
name: String(row.SYSTEM_TABLE_MEMBER),
|
|
223
|
+
extension: String(row.SOURCE_TYPE || ""),
|
|
224
|
+
text: String(row.PARTITION_TEXT || "")
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
async downloadMemberContentWithDates(library, sourceFile, member) {
|
|
228
|
+
const lib = this.ibmi.upperCaseName(library);
|
|
229
|
+
const file = this.ibmi.upperCaseName(sourceFile);
|
|
230
|
+
const mbr = this.ibmi.upperCaseName(member);
|
|
231
|
+
const tempLib = await this.getTempLibrary();
|
|
232
|
+
if (!tempLib)
|
|
233
|
+
return undefined;
|
|
234
|
+
const alias = makeAliasName(`${lib}/${file}/${mbr}`);
|
|
235
|
+
const aliasPath = `${tempLib}.${alias}`;
|
|
236
|
+
try {
|
|
237
|
+
await this.ibmi.runSQL(`CREATE OR REPLACE ALIAS ${aliasPath} for \"${lib}\".\"${file}\"(\"${mbr}\")`);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// ignore, alias might already exist
|
|
241
|
+
}
|
|
242
|
+
const recordLength = await this.getRecordLength(aliasPath, lib, file);
|
|
243
|
+
const rows = await this.ibmi.runSQL(`select case when locate('40',hex(srcdat)) > 0 then 0 else srcdat end as SRCDAT, srcseq, srcdta from ${aliasPath}`);
|
|
244
|
+
if (!rows || rows.length === 0) {
|
|
245
|
+
return "";
|
|
246
|
+
}
|
|
247
|
+
const sourceDates = rows.map(row => String(row.SRCDAT).padStart(6, `0`));
|
|
248
|
+
const body = rows.map(row => String(row.SRCDTA ?? "")).join(`\n`);
|
|
249
|
+
this.sourceDates.setBase(`${lib}/${file}/${mbr}`, body, sourceDates, recordLength);
|
|
250
|
+
return body;
|
|
251
|
+
}
|
|
252
|
+
async uploadMemberContentWithDates(library, sourceFile, member, body) {
|
|
253
|
+
this.assertWritable();
|
|
254
|
+
const lib = this.ibmi.upperCaseName(library);
|
|
255
|
+
const file = this.ibmi.upperCaseName(sourceFile);
|
|
256
|
+
const mbr = this.ibmi.upperCaseName(member);
|
|
257
|
+
const tempLib = await this.getTempLibrary();
|
|
258
|
+
if (!tempLib) {
|
|
259
|
+
throw new Error(`Temp library is not available for source-date updates.`);
|
|
260
|
+
}
|
|
261
|
+
const alias = makeAliasName(`${lib}/${file}/${mbr}`);
|
|
262
|
+
const aliasPath = `${tempLib}.${alias}`;
|
|
263
|
+
const baseKey = `${lib}/${file}/${mbr}`;
|
|
264
|
+
if (!this.sourceDates.getBaseSource(baseKey)) {
|
|
265
|
+
await this.downloadMemberContentWithDates(lib, file, mbr);
|
|
266
|
+
}
|
|
267
|
+
const sourceDates = this.sourceDates.calcNewSourceDates(baseKey, body);
|
|
268
|
+
const recordLength = this.sourceDates.getRecordLength(baseKey) || await this.getRecordLength(aliasPath, lib, file);
|
|
269
|
+
const sourceData = body.split(`\n`);
|
|
270
|
+
const decimalSequence = sourceData.length >= 10000;
|
|
271
|
+
const rows = [];
|
|
272
|
+
for (let i = 0; i < sourceData.length; i++) {
|
|
273
|
+
const seq = decimalSequence ? ((i + 1) / 100) : (i + 1);
|
|
274
|
+
let line = sourceData[i].trimEnd();
|
|
275
|
+
if (line.length > recordLength) {
|
|
276
|
+
line = line.substring(0, recordLength);
|
|
277
|
+
}
|
|
278
|
+
const date = sourceDates[i] ? sourceDates[i].padEnd(6, `0`) : `0`;
|
|
279
|
+
rows.push(`(${seq}, ${date}, '${escapeString(line)}')`);
|
|
280
|
+
}
|
|
281
|
+
const tempTable = `QTEMP.NEWMEMBER`;
|
|
282
|
+
const statements = [
|
|
283
|
+
`CREATE TABLE ${tempTable} LIKE \"${lib}\".\"${file}\";`
|
|
284
|
+
];
|
|
285
|
+
const rowLength = recordLength + 55;
|
|
286
|
+
const perInsert = Math.max(1, Math.floor(400000 / rowLength));
|
|
287
|
+
for (let i = 0; i < rows.length; i += perInsert) {
|
|
288
|
+
statements.push(`insert into ${tempTable} values ${rows.slice(i, i + perInsert).join(`,`)};`);
|
|
289
|
+
}
|
|
290
|
+
statements.push(`CALL QSYS2.QCMDEXC('CLRPFM FILE(${lib}/${file}) MBR(${mbr})');`, `insert into ${aliasPath} (select * from ${tempTable});`);
|
|
291
|
+
const tempRmt = this.ibmi.getTempRemote(`${lib}${file}${mbr}`);
|
|
292
|
+
await this.writeStreamfileRaw(tempRmt, statements.join(`\n`));
|
|
293
|
+
const setccsid = this.ibmi.remoteFeatures.setccsid;
|
|
294
|
+
if (setccsid) {
|
|
295
|
+
await this.ibmi.sendCommand({ command: `${setccsid} 1208 ${tempRmt}` });
|
|
296
|
+
}
|
|
297
|
+
const result = await this.ibmi.sendQsh({
|
|
298
|
+
command: `system \"RUNSQLSTM SRCSTMF('${tempRmt}') COMMIT(*NONE) NAMING(*SQL)\"`
|
|
299
|
+
});
|
|
300
|
+
if (result.code !== 0) {
|
|
301
|
+
throw new Error(`Failed to save member with source dates: ${result.stderr || result.stdout}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async getRecordLength(aliasPath, lib, file) {
|
|
305
|
+
let recordLength = 80;
|
|
306
|
+
try {
|
|
307
|
+
const res = await this.ibmi.runSQL(`select length(SRCDTA) as LENGTH from ${aliasPath} fetch first 1 rows only`);
|
|
308
|
+
if (res.length > 0 && res[0].LENGTH) {
|
|
309
|
+
recordLength = Number(res[0].LENGTH);
|
|
310
|
+
return recordLength;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch { }
|
|
314
|
+
try {
|
|
315
|
+
const res = await this.ibmi.runSQL(`select row_length-12 as LENGTH from QSYS2.SYSTABLES where SYSTEM_TABLE_SCHEMA = ${Tools.sqlString(lib)} and SYSTEM_TABLE_NAME = ${Tools.sqlString(file)} fetch first 1 rows only`);
|
|
316
|
+
if (res.length > 0 && res[0].LENGTH) {
|
|
317
|
+
recordLength = Number(res[0].LENGTH);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch { }
|
|
321
|
+
return recordLength;
|
|
322
|
+
}
|
|
323
|
+
async createLibrary(library) {
|
|
324
|
+
this.assertWritable();
|
|
325
|
+
const lib = this.ibmi.upperCaseName(library);
|
|
326
|
+
return this.ibmi.sendQsh({ command: `system \"CRTLIB LIB(${lib})\"` });
|
|
327
|
+
}
|
|
328
|
+
async createSourceFile(library, file, rcdlen = 112) {
|
|
329
|
+
this.assertWritable();
|
|
330
|
+
const lib = this.ibmi.upperCaseName(library);
|
|
331
|
+
const src = this.ibmi.upperCaseName(file);
|
|
332
|
+
return this.ibmi.sendQsh({ command: `system \"CRTSRCPF FILE(${lib}/${src}) RCDLEN(${rcdlen})\"` });
|
|
333
|
+
}
|
|
334
|
+
async createMember(library, file, member, srctype) {
|
|
335
|
+
this.assertWritable();
|
|
336
|
+
const lib = this.ibmi.upperCaseName(library);
|
|
337
|
+
const src = this.ibmi.upperCaseName(file);
|
|
338
|
+
const mbr = this.ibmi.upperCaseName(member);
|
|
339
|
+
const type = srctype ? this.ibmi.upperCaseName(srctype) : "*NONE";
|
|
340
|
+
return this.ibmi.sendQsh({ command: `system \"ADDPFM FILE(${lib}/${src}) MBR(${mbr}) SRCTYPE(${type})\"` });
|
|
341
|
+
}
|
|
342
|
+
async renameMember(library, file, member, newMember) {
|
|
343
|
+
this.assertWritable();
|
|
344
|
+
const lib = this.ibmi.upperCaseName(library);
|
|
345
|
+
const src = this.ibmi.upperCaseName(file);
|
|
346
|
+
const mbr = this.ibmi.upperCaseName(member);
|
|
347
|
+
const newMbr = this.ibmi.upperCaseName(newMember);
|
|
348
|
+
return this.ibmi.sendQsh({ command: `system \"RNMM FILE(${lib}/${src}) MBR(${mbr}) NEWMBR(${newMbr})\"` });
|
|
349
|
+
}
|
|
350
|
+
async deleteMember(library, file, member) {
|
|
351
|
+
this.assertWritable();
|
|
352
|
+
const lib = this.ibmi.upperCaseName(library);
|
|
353
|
+
const src = this.ibmi.upperCaseName(file);
|
|
354
|
+
const mbr = this.ibmi.upperCaseName(member);
|
|
355
|
+
return this.ibmi.sendQsh({ command: `system \"RMVM FILE(${lib}/${src}) MBR(${mbr})\"` });
|
|
356
|
+
}
|
|
357
|
+
async createTempFile() {
|
|
358
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "mcpfori-"));
|
|
359
|
+
return path.join(dir, "tmp");
|
|
360
|
+
}
|
|
361
|
+
assertWritable() {
|
|
362
|
+
if (this.config.readOnlyMode) {
|
|
363
|
+
throw new Error("Connection is in read-only mode");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function escapeString(val) {
|
|
368
|
+
return val.replace(/[\0\n\r\b\t'\x1a]/g, function (s) {
|
|
369
|
+
switch (s) {
|
|
370
|
+
case `\0`:
|
|
371
|
+
return `\\0`;
|
|
372
|
+
case `\n`:
|
|
373
|
+
return `\\n`;
|
|
374
|
+
case `\r`:
|
|
375
|
+
return ``;
|
|
376
|
+
case `\b`:
|
|
377
|
+
return `\\b`;
|
|
378
|
+
case `\x1a`:
|
|
379
|
+
return `\\Z`;
|
|
380
|
+
case `'`:
|
|
381
|
+
return `''`;
|
|
382
|
+
default:
|
|
383
|
+
return `\\` + s;
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
export const LocalLanguageActions = {
|
|
2
|
+
RPGLE: [
|
|
3
|
+
{
|
|
4
|
+
name: `Create RPGLE Program`,
|
|
5
|
+
command: `CRTBNDRPG PGM(&CURLIB/&NAME) SRCSTMF('&RELATIVEPATH') OPTION(*EVENTF) DBGVIEW(*SOURCE) TGTCCSID(*JOB)`,
|
|
6
|
+
deployFirst: true,
|
|
7
|
+
environment: `ile`,
|
|
8
|
+
extensions: [`RPGLE`]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: `Create RPGLE Module`,
|
|
12
|
+
command: `CRTRPGMOD MODULE(&CURLIB/&NAME) SRCSTMF('&RELATIVEPATH') OPTION(*EVENTF) DBGVIEW(*SOURCE) TGTCCSID(*JOB)`,
|
|
13
|
+
deployFirst: true,
|
|
14
|
+
environment: `ile`,
|
|
15
|
+
extensions: [`RPGLE`]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: `Create SQLRPGLE Program`,
|
|
19
|
+
command: `CRTSQLRPGI OBJ(&CURLIB/&NAME) SRCSTMF('&RELATIVEPATH') OPTION(*EVENTF) DBGVIEW(*SOURCE) CLOSQLCSR(*ENDMOD) CVTCCSID(*JOB) COMPILEOPT('TGTCCSID(*JOB)') RPGPPOPT(*LVL2)`,
|
|
20
|
+
deployFirst: true,
|
|
21
|
+
environment: `ile`,
|
|
22
|
+
extensions: [`SQLRPGLE`]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: `Create SQLRPGLE Module`,
|
|
26
|
+
command: `CRTSQLRPGI OBJ(&CURLIB/&NAME) SRCSTMF('&RELATIVEPATH') OBJTYPE(*MODULE) OPTION(*EVENTF) DBGVIEW(*SOURCE) CLOSQLCSR(*ENDMOD) CVTCCSID(*JOB) COMPILEOPT('TGTCCSID(*JOB)') RPGPPOPT(*LVL2)`,
|
|
27
|
+
deployFirst: true,
|
|
28
|
+
environment: `ile`,
|
|
29
|
+
extensions: [`SQLRPGLE`]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
COBOL: [
|
|
33
|
+
{
|
|
34
|
+
name: `Create COBOL Program (SQL)`,
|
|
35
|
+
command: `CRTSQLCBLI OBJ(&CURLIB/&NAME) SRCSTMF('&RELATIVEPATH') OPTION(*EVENTF) DBGVIEW(*SOURCE) CLOSQLCSR(*ENDMOD) CVTCCSID(*JOB) TOSRCFILE(&CURLIB/QSQLTEMP)`,
|
|
36
|
+
deployFirst: true,
|
|
37
|
+
environment: `ile`,
|
|
38
|
+
extensions: [`SQLCBL`, `SQLCBBLE`, `SQLCBLLE`, `COB`, `CBLLE`, `CBL`, `CBBLE`]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: `Create COBOL Module (SQL)`,
|
|
42
|
+
command: `CRTSQLCBLI OBJ(&CURLIB/&NAME) SRCSTMF('&RELATIVEPATH') OBJTYPE(*MODULE) OPTION(*EVENTF) DBGVIEW(*SOURCE) CLOSQLCSR(*ENDMOD) CVTCCSID(*JOB) TOSRCFILE(&CURLIB/QSQLTEMP)`,
|
|
43
|
+
deployFirst: true,
|
|
44
|
+
environment: `ile`,
|
|
45
|
+
extensions: [`SQLCBL`, `SQLCBBLE`, `SQLCBLLE`, `COB`, `CBLLE`, `CBL`, `CBBLE`]
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
C: [
|
|
49
|
+
{
|
|
50
|
+
name: `Create C Program`,
|
|
51
|
+
command: `CRTBNDC PGM(&CURLIB/&NAME) SRCSTMF('&RELATIVEPATH') OPTION(*EVENTF) DBGVIEW(*SOURCE) TGTCCSID(*JOB)`,
|
|
52
|
+
deployFirst: true,
|
|
53
|
+
environment: `ile`,
|
|
54
|
+
extensions: [`C`]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: `Create C Module`,
|
|
58
|
+
command: `CRTCMOD MODULE(&CURLIB/&NAME) SRCSTMF('&RELATIVEPATH') OPTION(*EVENTF) DBGVIEW(*SOURCE) TGTCCSID(*JOB)`,
|
|
59
|
+
deployFirst: true,
|
|
60
|
+
environment: `ile`,
|
|
61
|
+
extensions: [`C`]
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
CPP: [
|
|
65
|
+
{
|
|
66
|
+
name: `Create CPP Program`,
|
|
67
|
+
command: `CRTBNDCPP PGM(&CURLIB/&NAME) SRCSTMF('&RELATIVEPATH') OPTION(*EVENTF) DBGVIEW(*SOURCE) TGTCCSID(*JOB)`,
|
|
68
|
+
deployFirst: true,
|
|
69
|
+
environment: `ile`,
|
|
70
|
+
extensions: [`CPP`]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: `Create CPP Module`,
|
|
74
|
+
command: `CRTCPPMOD MODULE(&CURLIB/&NAME) SRCSTMF('&RELATIVEPATH') OPTION(*EVENTF) DBGVIEW(*SOURCE) TGTCCSID(*JOB)`,
|
|
75
|
+
deployFirst: true,
|
|
76
|
+
environment: `ile`,
|
|
77
|
+
extensions: [`CPP`]
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
CLLE: [
|
|
81
|
+
{
|
|
82
|
+
extensions: [`CLP`, `CLLE`],
|
|
83
|
+
name: `Create Bound CL Program`,
|
|
84
|
+
command: `CRTBNDCL PGM(&CURLIB/&NAME) SRCSTMF('&RELATIVEPATH') OPTION(*EVENTF) DBGVIEW(*SOURCE)`,
|
|
85
|
+
deployFirst: true,
|
|
86
|
+
environment: `ile`
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
CMD: [
|
|
90
|
+
{
|
|
91
|
+
extensions: [`cmd`],
|
|
92
|
+
name: `Create Command`,
|
|
93
|
+
command: `CRTCMD CMD(&CURLIB/&NAME) PGM(&CURLIB/&NAME) SRCSTMF('&RELATIVEPATH') OPTION(*EVENTF)`,
|
|
94
|
+
deployFirst: true,
|
|
95
|
+
environment: `ile`
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
SQL: [
|
|
99
|
+
{
|
|
100
|
+
extensions: [`SQL`, `TABLE`, `VIEW`, `SQLPRC`, `SQLUDF`, `SQLUDT`, `SQLTRG`, `SQLALIAS`, `SQLSEQ`],
|
|
101
|
+
name: `Run SQL Statements (RUNSQLSTM)`,
|
|
102
|
+
command: `RUNSQLSTM SRCSTMF('&FULLPATH') COMMIT(*NONE) NAMING(*SQL)`,
|
|
103
|
+
deployFirst: true,
|
|
104
|
+
environment: `ile`
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
DSPF: [
|
|
108
|
+
{
|
|
109
|
+
name: "Create DSPF",
|
|
110
|
+
command: "CRTDSPF FILE(&CURLIB/&NAME) SRCFILE(&SRCFILE) RSTDSP(*NO) OPTION(*EVENTF)",
|
|
111
|
+
environment: "ile",
|
|
112
|
+
deployFirst: true,
|
|
113
|
+
extensions: ["dspf"]
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
"Service Programs": [
|
|
117
|
+
{
|
|
118
|
+
extensions: [`GLOBAL`],
|
|
119
|
+
name: `Create Service Program (CRTSRVPGM EXPORT(*ALL))`,
|
|
120
|
+
command: `CRTSRVPGM SRVPGM(&CURLIB/&NAME) EXPORT(*ALL) BNDSRVPGM(*NONE) BNDDIR(*NONE) ACTGRP(*CALLER)`,
|
|
121
|
+
environment: `ile`
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
extensions: ["BND", "BINDER"],
|
|
125
|
+
deployFirst: true,
|
|
126
|
+
name: "Create Service Program (CRTSRVPGM with source)",
|
|
127
|
+
command: "CRTSRVPGM SRVPGM(&CURLIB/&NAME) SRCSTMF('&RELATIVEPATH') BNDSRVPGM(*NONE) BNDDIR(*NONE) ACTGRP(*CALLER)",
|
|
128
|
+
environment: "ile"
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
"GNU Make": [
|
|
132
|
+
{
|
|
133
|
+
extensions: [`GLOBAL`],
|
|
134
|
+
name: `Build all with gmake`,
|
|
135
|
+
command: `/QOpenSys/pkgs/bin/gmake BUILDLIB=&CURLIB ERR=*EVENTF`,
|
|
136
|
+
environment: `pase`,
|
|
137
|
+
deployFirst: true
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
extensions: [`GLOBAL`],
|
|
141
|
+
name: `Build current with gmake`,
|
|
142
|
+
command: `/QOpenSys/pkgs/bin/gmake &BASENAME BUILDLIB=&CURLIB ERR=*EVENTF`,
|
|
143
|
+
environment: `pase`,
|
|
144
|
+
deployFirst: true
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
"The Object Builder for i (TOBi)": [
|
|
148
|
+
{
|
|
149
|
+
extensions: [`GLOBAL`],
|
|
150
|
+
name: `Build all with TOBi`,
|
|
151
|
+
command: `OPT=*EVENTF BUILDLIB=&CURLIB /QOpenSys/pkgs/bin/makei build`,
|
|
152
|
+
environment: `pase`,
|
|
153
|
+
deployFirst: true,
|
|
154
|
+
postDownload: [".logs", ".evfevent"]
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
extensions: [`GLOBAL`],
|
|
158
|
+
name: `Build current with TOBi`,
|
|
159
|
+
command: `OPT=*EVENTF BUILDLIB=&CURLIB /QOpenSys/pkgs/bin/makei compile -f &BASENAME`,
|
|
160
|
+
environment: `pase`,
|
|
161
|
+
deployFirst: true,
|
|
162
|
+
postDownload: [".logs", ".evfevent"]
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
"Source Orbit": [
|
|
166
|
+
{
|
|
167
|
+
name: "Build current with Source Orbit",
|
|
168
|
+
command: "so -bf make -s &RELATIVEPATH && /QOpenSys/pkgs/bin/gmake LIBL='&LIBLS' BIN_LIB=&CURLIB OPT=*EVENTF",
|
|
169
|
+
environment: "pase",
|
|
170
|
+
deployFirst: true,
|
|
171
|
+
extensions: ["GLOBAL"],
|
|
172
|
+
postDownload: [".evfevent/"]
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "Build entire project with Source Orbit",
|
|
176
|
+
command: "so -bf make && /QOpenSys/pkgs/bin/gmake LIBL='&LIBLS' BIN_LIB=&CURLIB OPT=*EVENTF",
|
|
177
|
+
environment: "pase",
|
|
178
|
+
deployFirst: true,
|
|
179
|
+
extensions: ["GLOBAL"],
|
|
180
|
+
postDownload: [".evfevent/"]
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
};
|