forge-fsql 1.0.4 ā 1.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 +13 -29
- package/bin/setup.js +468 -0
- package/dist/cjs/index.js +3 -3
- package/dist/index.js +3 -3
- package/package.json +7 -4
- package/templates/execute-sql.cjs +64 -0
- package/templates/execute-sql.js +64 -0
- package/templates/execute-sql.ts +75 -0
- package/bin/setup +0 -193
- /package/bin/{fsql ā fsql.js} +0 -0
package/README.md
CHANGED
|
@@ -4,49 +4,33 @@ Interactive command-line interface for querying Atlassian Forge SQL databases vi
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- šØ
|
|
8
|
-
- š Multi-line SQL support
|
|
9
|
-
- āØļø Command history (ā/ā arrows)
|
|
7
|
+
- šØ Table formatting with colors
|
|
10
8
|
- ā” Special commands (.tables, .describe, .schema)
|
|
11
|
-
-
|
|
9
|
+
- āØļø Command history (ā/ā arrows)
|
|
12
10
|
- š¾ Persistent history across sessions
|
|
11
|
+
- ā±ļø Query timing
|
|
12
|
+
- š Multi-line SQL support
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
16
|
### In Your Forge Project
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
npm install -
|
|
19
|
+
npm install -g forge-fsql
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
node_modules/.bin/fsql-setup
|
|
23
|
-
|
|
24
|
-
# deploy with the webtrigger
|
|
25
|
-
forge deploy
|
|
26
|
-
|
|
27
|
-
# get trigger url:
|
|
28
|
-
forge webtrigger create --product Confluence --site <site>.atlassian.net --functionKey execute-sql
|
|
21
|
+
fsql-setup
|
|
29
22
|
```
|
|
30
23
|
|
|
31
|
-
|
|
24
|
+
Notes:
|
|
32
25
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
```
|
|
26
|
+
- creates a webtrigger in your manifest.yml
|
|
27
|
+
- creates a module at src/fsql.ts for the webtrigger function
|
|
28
|
+
- deploys the project with the new manifest
|
|
29
|
+
- creates the webtrigger with `forge webtrigger create`
|
|
30
|
+
- adds the webtrigger URL to a FORGE_SQL_WEBTRIGGER environment variable in .env
|
|
40
31
|
|
|
41
32
|
## Run
|
|
42
33
|
|
|
43
34
|
```sh
|
|
44
|
-
|
|
45
|
-
export FORGE_SQL_URL=https://your-trigger-url.com
|
|
46
|
-
|
|
47
|
-
# run fsql!
|
|
48
|
-
npm run fsql
|
|
49
|
-
|
|
50
|
-
# or
|
|
51
|
-
npm run fsql --url https://your-trigger-url.com
|
|
35
|
+
fsql
|
|
52
36
|
```
|
package/bin/setup.js
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import ora from "ora";
|
|
10
|
+
import prompts from "prompts";
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
const pkgRoot = path.join(__dirname, "..");
|
|
15
|
+
const templatesDir = path.join(pkgRoot, "templates");
|
|
16
|
+
|
|
17
|
+
// Support both ESM and CJS imports for js-yaml
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
const YAML = require("yaml");
|
|
20
|
+
|
|
21
|
+
const projectRoot = process.cwd();
|
|
22
|
+
|
|
23
|
+
async function main() {
|
|
24
|
+
console.log(chalk.bold.blue("\nš Forge SQL CLI Setup\n"));
|
|
25
|
+
|
|
26
|
+
// Detect consumer project type
|
|
27
|
+
let isEsm = false;
|
|
28
|
+
let isTypeScript = fs.existsSync(path.join(projectRoot, "tsconfig.json"));
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const pkgPath = path.join(projectRoot, "package.json");
|
|
32
|
+
if (fs.existsSync(pkgPath)) {
|
|
33
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
34
|
+
if (pkg.type === "module") {
|
|
35
|
+
isEsm = true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check for @forge/sql dependency
|
|
39
|
+
const hasForgeSql =
|
|
40
|
+
(pkg.dependencies && pkg.dependencies["@forge/sql"]) ||
|
|
41
|
+
(pkg.devDependencies && pkg.devDependencies["@forge/sql"]);
|
|
42
|
+
|
|
43
|
+
if (!hasForgeSql) {
|
|
44
|
+
console.error(chalk.red("\nā Error: @forge/sql is not installed."));
|
|
45
|
+
console.log(
|
|
46
|
+
chalk.yellow(
|
|
47
|
+
"This tool requires @forge/sql to be present in your project.",
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
console.log("Please install it by running:");
|
|
51
|
+
console.log(chalk.blue("\n npm install @forge/sql"));
|
|
52
|
+
console.log(" # or");
|
|
53
|
+
console.log(chalk.blue(" yarn add @forge/sql\n"));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
console.error(chalk.red("\nā Error: package.json not found."));
|
|
58
|
+
console.log(
|
|
59
|
+
chalk.yellow(
|
|
60
|
+
"This tool requires a valid Node.js project with @forge/sql installed.",
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(chalk.yellow("Warning: Could not read package.json:"), error);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 1. Detect manifest.yaml or manifest.yml
|
|
70
|
+
let manifestPath = path.join(projectRoot, "manifest.yml");
|
|
71
|
+
if (!fs.existsSync(manifestPath)) {
|
|
72
|
+
manifestPath = path.join(projectRoot, "manifest.yaml");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!fs.existsSync(manifestPath)) {
|
|
76
|
+
console.error(
|
|
77
|
+
chalk.red(
|
|
78
|
+
"Error: Could not find manifest.yml or manifest.yaml in the current directory.",
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 2. Prompt for fsql execution function path
|
|
85
|
+
const extension = isTypeScript ? "ts" : isEsm ? "js" : "js";
|
|
86
|
+
const defaultPath = `src/fsql.${extension}`;
|
|
87
|
+
|
|
88
|
+
const response = await prompts({
|
|
89
|
+
type: "text",
|
|
90
|
+
name: "fsqlPath",
|
|
91
|
+
message: "Where should the SQL execution function be created?",
|
|
92
|
+
initial: defaultPath,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (!response.fsqlPath) {
|
|
96
|
+
console.log(chalk.yellow("Setup cancelled."));
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const fsqlRelPath = response.fsqlPath;
|
|
101
|
+
const fsqlAbsPath = path.resolve(projectRoot, fsqlRelPath);
|
|
102
|
+
const srcSameDir = path.dirname(fsqlAbsPath);
|
|
103
|
+
|
|
104
|
+
// Spinner for file creation
|
|
105
|
+
const spinner = ora("Creating function file...").start();
|
|
106
|
+
|
|
107
|
+
if (!fs.existsSync(srcSameDir)) {
|
|
108
|
+
fs.mkdirSync(srcSameDir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let templateFile = "";
|
|
112
|
+
if (isTypeScript) {
|
|
113
|
+
templateFile = "execute-sql.ts";
|
|
114
|
+
} else if (isEsm) {
|
|
115
|
+
templateFile = "execute-sql.js";
|
|
116
|
+
} else {
|
|
117
|
+
templateFile = "execute-sql.cjs";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const fsqlContent = fs.readFileSync(
|
|
121
|
+
path.join(templatesDir, templateFile),
|
|
122
|
+
"utf8",
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
fs.writeFileSync(fsqlAbsPath, fsqlContent);
|
|
126
|
+
spinner.succeed(`Created ${fsqlRelPath}`);
|
|
127
|
+
|
|
128
|
+
// 3. Update manifest
|
|
129
|
+
spinner.start("Updating manifest.yml...");
|
|
130
|
+
|
|
131
|
+
let doc;
|
|
132
|
+
try {
|
|
133
|
+
const fileContents = fs.readFileSync(manifestPath, "utf8");
|
|
134
|
+
doc = YAML.parseDocument(fileContents);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
spinner.fail("Error reading manifest");
|
|
137
|
+
console.error(e);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!doc.contents) {
|
|
142
|
+
doc.contents = doc.createNode({});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!doc.has("modules")) {
|
|
146
|
+
doc.set("modules", doc.createNode({}));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const modules = doc.get("modules");
|
|
150
|
+
|
|
151
|
+
const functionKey = "executeSql";
|
|
152
|
+
if (!modules.has("function")) {
|
|
153
|
+
modules.set("function", doc.createNode([]));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let functions = modules.get("function");
|
|
157
|
+
|
|
158
|
+
if (!YAML.isSeq(functions)) {
|
|
159
|
+
const obj = functions.toJSON();
|
|
160
|
+
functions = doc.createNode(
|
|
161
|
+
Object.entries(obj).map(([key, val]) => ({ key, ...val })),
|
|
162
|
+
);
|
|
163
|
+
modules.set("function", functions);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Determine handler path
|
|
167
|
+
// Standard Forge pattern: filename without extension + .functionName
|
|
168
|
+
// We need to be careful with paths.
|
|
169
|
+
// If user chooses src/foo/bar.ts, handler is src/foo/bar.executeSql
|
|
170
|
+
// If user chooses modules/bar.js, handler is modules/bar.executeSql
|
|
171
|
+
|
|
172
|
+
// 145. Determine handler path
|
|
173
|
+
let handlerPath = fsqlRelPath.replace(/\.(ts|js|cjs|mjs)$/, "");
|
|
174
|
+
// Ensure we use forward slashes for handler paths
|
|
175
|
+
handlerPath = handlerPath.split(path.sep).join("/");
|
|
176
|
+
|
|
177
|
+
// Logic to detect if we should strip 'src/' from the handler path
|
|
178
|
+
// This happens if the project uses 'src' as the root for handlers (common in Forge)
|
|
179
|
+
// We check if 'src/index.ts' exists but 'index.ts' does not, AND if there is a handler 'index.handler'
|
|
180
|
+
// Or simply, if the user created the file in 'src/' but 'src/' path is redundant for handlers.
|
|
181
|
+
|
|
182
|
+
// Heuristic: Check existing function handlers
|
|
183
|
+
let srcIsRoot = false;
|
|
184
|
+
try {
|
|
185
|
+
if (functions && functions.items && functions.items.length > 0) {
|
|
186
|
+
for (const f of functions.items) {
|
|
187
|
+
const fJson = f.toJSON();
|
|
188
|
+
if (fJson && fJson.handler) {
|
|
189
|
+
const h = fJson.handler.split(".")[0]; // e.g. "index" or "consumers/ingestion-consumer"
|
|
190
|
+
const possibleSrcPath = path.join(projectRoot, "src", h + ".ts"); // simple check for .ts
|
|
191
|
+
const possibleRootPath = path.join(projectRoot, h + ".ts");
|
|
192
|
+
|
|
193
|
+
if (
|
|
194
|
+
fs.existsSync(possibleSrcPath) &&
|
|
195
|
+
!fs.existsSync(possibleRootPath)
|
|
196
|
+
) {
|
|
197
|
+
srcIsRoot = true;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
// Fallback: if index.ts is in src but not root
|
|
204
|
+
if (
|
|
205
|
+
fs.existsSync(path.join(projectRoot, "src", "index.ts")) &&
|
|
206
|
+
!fs.existsSync(path.join(projectRoot, "index.ts"))
|
|
207
|
+
) {
|
|
208
|
+
srcIsRoot = true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
// ignore
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (srcIsRoot && handlerPath.startsWith("src/")) {
|
|
216
|
+
handlerPath = handlerPath.substring(4); // remove "src/"
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const handlerName = `${handlerPath}.executeSql`;
|
|
220
|
+
|
|
221
|
+
let functionExists = functions.items.find((f) => {
|
|
222
|
+
const js = f.toJSON();
|
|
223
|
+
return js && js.key === functionKey;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (!functionExists) {
|
|
227
|
+
functions.add(
|
|
228
|
+
doc.createNode({
|
|
229
|
+
key: functionKey,
|
|
230
|
+
handler: handlerName,
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
233
|
+
} else {
|
|
234
|
+
if (YAML.isMap(functionExists)) {
|
|
235
|
+
functionExists.set("handler", handlerName);
|
|
236
|
+
} else {
|
|
237
|
+
const idx = functions.items.indexOf(functionExists);
|
|
238
|
+
functions.set(
|
|
239
|
+
idx,
|
|
240
|
+
doc.createNode({ key: functionKey, handler: handlerName }),
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const webtriggerKey = "execute-sql";
|
|
246
|
+
if (!modules.has("webtrigger")) {
|
|
247
|
+
modules.set("webtrigger", doc.createNode([]));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let webtriggers = modules.get("webtrigger");
|
|
251
|
+
if (!YAML.isSeq(webtriggers)) {
|
|
252
|
+
const obj = webtriggers.toJSON();
|
|
253
|
+
webtriggers = doc.createNode(
|
|
254
|
+
Object.entries(obj).map(([key, val]) => ({ key, ...val })),
|
|
255
|
+
);
|
|
256
|
+
modules.set("webtrigger", webtriggers);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
let webtriggerExists = webtriggers.items.find((w) => {
|
|
260
|
+
const js = w.toJSON();
|
|
261
|
+
return js && js.key === webtriggerKey;
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
if (!webtriggerExists) {
|
|
265
|
+
webtriggers.add(
|
|
266
|
+
doc.createNode({
|
|
267
|
+
key: webtriggerKey,
|
|
268
|
+
function: functionKey,
|
|
269
|
+
}),
|
|
270
|
+
);
|
|
271
|
+
} else {
|
|
272
|
+
if (YAML.isMap(webtriggerExists)) {
|
|
273
|
+
webtriggerExists.set("function", functionKey);
|
|
274
|
+
} else {
|
|
275
|
+
const idx = webtriggers.items.indexOf(webtriggerExists);
|
|
276
|
+
webtriggers.set(
|
|
277
|
+
idx,
|
|
278
|
+
doc.createNode({ key: webtriggerKey, function: functionKey }),
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
fs.writeFileSync(manifestPath, doc.toString());
|
|
285
|
+
spinner.succeed("Updated manifest file");
|
|
286
|
+
} catch (e) {
|
|
287
|
+
spinner.fail("Error writing manifest");
|
|
288
|
+
console.error(e);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 4. Prompt for deployment
|
|
293
|
+
console.log(); // Add newline for better UX
|
|
294
|
+
const deployResponse = await prompts({
|
|
295
|
+
type: "text",
|
|
296
|
+
name: "cmd",
|
|
297
|
+
message:
|
|
298
|
+
"Command to deploy your app (required before creating webtrigger):",
|
|
299
|
+
initial: "forge deploy",
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
if (!deployResponse.cmd) {
|
|
303
|
+
console.log(chalk.yellow("Deployment cancelled."));
|
|
304
|
+
process.exit(0);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const deployCmd = deployResponse.cmd;
|
|
308
|
+
|
|
309
|
+
console.log(chalk.bold.blue(`\nRunning ${deployCmd}...\n`));
|
|
310
|
+
|
|
311
|
+
await new Promise((resolve) => {
|
|
312
|
+
const child = spawn(deployCmd, {
|
|
313
|
+
stdio: "inherit",
|
|
314
|
+
shell: true,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
child.on("close", (code) => {
|
|
318
|
+
if (code !== 0) {
|
|
319
|
+
console.error(chalk.red(`\nDeploy failed with exit code ${code}.`));
|
|
320
|
+
process.exit(code);
|
|
321
|
+
}
|
|
322
|
+
resolve();
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// 5. Run forge webtrigger create
|
|
327
|
+
let webtriggerArgs = ["webtrigger", "create", "--functionKey", "execute-sql"];
|
|
328
|
+
|
|
329
|
+
// Try to detect existing installation
|
|
330
|
+
try {
|
|
331
|
+
const listProc = spawn("forge", ["install", "list", "--json"], {
|
|
332
|
+
shell: true,
|
|
333
|
+
});
|
|
334
|
+
let listOutput = "";
|
|
335
|
+
await new Promise((resolve) => {
|
|
336
|
+
listProc.stdout.on("data", (d) => (listOutput += d.toString()));
|
|
337
|
+
listProc.on("close", resolve);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Attempt to extract JSON
|
|
341
|
+
const jsonStart = listOutput.indexOf("[");
|
|
342
|
+
const jsonEnd = listOutput.lastIndexOf("]");
|
|
343
|
+
if (jsonStart !== -1 && jsonEnd !== -1) {
|
|
344
|
+
const installations = JSON.parse(
|
|
345
|
+
listOutput.substring(jsonStart, jsonEnd + 1),
|
|
346
|
+
);
|
|
347
|
+
const devInst = installations.find(
|
|
348
|
+
(i) => i.environment === "development",
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
if (devInst) {
|
|
352
|
+
console.log();
|
|
353
|
+
const useInst = await prompts({
|
|
354
|
+
type: "confirm",
|
|
355
|
+
name: "value",
|
|
356
|
+
message: `Found existing installation for ${chalk.cyan(devInst.product)} at ${chalk.cyan(devInst.site)}. Use this?`,
|
|
357
|
+
initial: true,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
if (useInst.value) {
|
|
361
|
+
webtriggerArgs.push("--site", devInst.site);
|
|
362
|
+
webtriggerArgs.push("--product", devInst.product);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
} catch {
|
|
367
|
+
// Ignore errors in auto-detection
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
console.log(
|
|
371
|
+
chalk.bold.blue(
|
|
372
|
+
"\nCreating webtrigger... (Please follow prompts if asked)\n",
|
|
373
|
+
),
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
const separator = chalk.gray(
|
|
377
|
+
"==================================================",
|
|
378
|
+
);
|
|
379
|
+
console.log(separator);
|
|
380
|
+
|
|
381
|
+
const forgeCmd = spawn("forge", webtriggerArgs, {
|
|
382
|
+
stdio: ["inherit", "pipe", "inherit"],
|
|
383
|
+
shell: true,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
let stdoutData = "";
|
|
387
|
+
|
|
388
|
+
forgeCmd.stdout.on("data", (data) => {
|
|
389
|
+
process.stdout.write(data);
|
|
390
|
+
stdoutData += data.toString();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
forgeCmd.on("close", (code) => {
|
|
394
|
+
console.log(separator);
|
|
395
|
+
|
|
396
|
+
if (code !== 0) {
|
|
397
|
+
console.log(
|
|
398
|
+
chalk.yellow(
|
|
399
|
+
`\nForge command exited with code ${code}. If you cancelled or it failed, you may need to run it manually.`,
|
|
400
|
+
),
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Capture URL
|
|
405
|
+
const urlRegex =
|
|
406
|
+
/https:\/\/[a-zA-Z0-9-.]+\.atlassian-dev\.net\/[a-zA-Z0-9/-]+/;
|
|
407
|
+
const matches = stdoutData.match(urlRegex);
|
|
408
|
+
|
|
409
|
+
if (matches) {
|
|
410
|
+
const url = matches[0];
|
|
411
|
+
console.log(chalk.green(`\nFound Webtrigger URL: ${url}`));
|
|
412
|
+
updateEnv(url);
|
|
413
|
+
} else {
|
|
414
|
+
console.log(
|
|
415
|
+
chalk.yellow(
|
|
416
|
+
"\nCould not automatically find Webtrigger URL in output.",
|
|
417
|
+
),
|
|
418
|
+
);
|
|
419
|
+
console.log(
|
|
420
|
+
"If created, please add the URL to your .env file as FORGE_SQL_WEBTRIGGER=<url>",
|
|
421
|
+
);
|
|
422
|
+
finish();
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function updateEnv(url) {
|
|
428
|
+
console.log();
|
|
429
|
+
const spinner = ora("Updating .env file...").start();
|
|
430
|
+
const envPath = path.join(projectRoot, ".env");
|
|
431
|
+
const envVar = `FORGE_SQL_WEBTRIGGER=${url}`;
|
|
432
|
+
|
|
433
|
+
let envContent = "";
|
|
434
|
+
if (fs.existsSync(envPath)) {
|
|
435
|
+
envContent = fs.readFileSync(envPath, "utf8");
|
|
436
|
+
if (!envContent.endsWith("\n") && envContent.length > 0) {
|
|
437
|
+
envContent += "\n";
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Check if already exists
|
|
442
|
+
if (envContent.includes("FORGE_SQL_WEBTRIGGER=")) {
|
|
443
|
+
envContent = envContent.replace(
|
|
444
|
+
/FORGE_SQL_WEBTRIGGER=.*(\n|$)/,
|
|
445
|
+
`${envVar}\n`,
|
|
446
|
+
);
|
|
447
|
+
} else {
|
|
448
|
+
envContent += `${envVar}\n`;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
fs.writeFileSync(envPath, envContent);
|
|
452
|
+
spinner.succeed("Updated .env with Webtrigger URL");
|
|
453
|
+
finish();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function finish() {
|
|
457
|
+
const msg = " ā Setup completed successfully! ";
|
|
458
|
+
const line = "ā".repeat(msg.length + 1);
|
|
459
|
+
console.log(chalk.bold.green(`\nā${line}ā`));
|
|
460
|
+
console.log(chalk.bold.green(`ā${msg}ā`));
|
|
461
|
+
console.log(chalk.bold.green(`ā${line}ā\n`));
|
|
462
|
+
console.log(`Run ${chalk.cyan("'fsql'")} to start.\n`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
main().catch((err) => {
|
|
466
|
+
console.error(chalk.red("Unexpected error:"), err);
|
|
467
|
+
process.exit(1);
|
|
468
|
+
});
|
package/dist/cjs/index.js
CHANGED
|
@@ -53,9 +53,9 @@ class ForgeSqlCli {
|
|
|
53
53
|
constructor(config) {
|
|
54
54
|
this.multilineBuffer = "";
|
|
55
55
|
this.isMultiline = false;
|
|
56
|
-
const url = config.url || process.env.
|
|
56
|
+
const url = config.url || process.env.FORGE_SQL_WEBTRIGGER;
|
|
57
57
|
if (!url) {
|
|
58
|
-
console.error(chalk_1.default.red("Error:
|
|
58
|
+
console.error(chalk_1.default.red("Error: FORGE_SQL_WEBTRIGGER not configured"));
|
|
59
59
|
console.error(chalk_1.default.yellow("Set it via environment variable or .env file"));
|
|
60
60
|
process.exit(1);
|
|
61
61
|
}
|
|
@@ -167,7 +167,7 @@ class ForgeSqlCli {
|
|
|
167
167
|
}
|
|
168
168
|
else {
|
|
169
169
|
console.log(chalk_1.default.red("ā Connection failed"));
|
|
170
|
-
console.log(chalk_1.default.yellow("Check your
|
|
170
|
+
console.log(chalk_1.default.yellow("Check your FORGE_SQL_WEBTRIGGER configuration"));
|
|
171
171
|
}
|
|
172
172
|
console.log("");
|
|
173
173
|
this.rl.prompt();
|
package/dist/index.js
CHANGED
|
@@ -12,9 +12,9 @@ export class ForgeSqlCli {
|
|
|
12
12
|
constructor(config) {
|
|
13
13
|
this.multilineBuffer = "";
|
|
14
14
|
this.isMultiline = false;
|
|
15
|
-
const url = config.url || process.env.
|
|
15
|
+
const url = config.url || process.env.FORGE_SQL_WEBTRIGGER;
|
|
16
16
|
if (!url) {
|
|
17
|
-
console.error(chalk.red("Error:
|
|
17
|
+
console.error(chalk.red("Error: FORGE_SQL_WEBTRIGGER not configured"));
|
|
18
18
|
console.error(chalk.yellow("Set it via environment variable or .env file"));
|
|
19
19
|
process.exit(1);
|
|
20
20
|
}
|
|
@@ -126,7 +126,7 @@ export class ForgeSqlCli {
|
|
|
126
126
|
}
|
|
127
127
|
else {
|
|
128
128
|
console.log(chalk.red("ā Connection failed"));
|
|
129
|
-
console.log(chalk.yellow("Check your
|
|
129
|
+
console.log(chalk.yellow("Check your FORGE_SQL_WEBTRIGGER configuration"));
|
|
130
130
|
}
|
|
131
131
|
console.log("");
|
|
132
132
|
this.rl.prompt();
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forge-fsql",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Interactive SQL CLI for Atlassian Forge SQL via web triggers",
|
|
6
6
|
"main": "dist/cjs/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
9
|
"bin": {
|
|
10
|
-
"fsql": "bin/fsql",
|
|
11
|
-
"fsql-setup": "bin/setup"
|
|
10
|
+
"fsql": "bin/fsql.js",
|
|
11
|
+
"fsql-setup": "bin/setup.js"
|
|
12
12
|
},
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"dist",
|
|
27
27
|
"bin",
|
|
28
|
+
"templates",
|
|
28
29
|
"README.md"
|
|
29
30
|
],
|
|
30
31
|
"lint-staged": {
|
|
@@ -46,6 +47,8 @@
|
|
|
46
47
|
"chalk": "^5.4.1",
|
|
47
48
|
"cli-table3": "^0.6.3",
|
|
48
49
|
"dotenv": "^16.0.3",
|
|
50
|
+
"ora": "^9.0.0",
|
|
51
|
+
"prompts": "^2.4.2",
|
|
49
52
|
"yaml": "^2.8.2"
|
|
50
53
|
},
|
|
51
54
|
"devDependencies": {
|
|
@@ -69,7 +72,7 @@
|
|
|
69
72
|
"build": "tsc && tsc -p tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json",
|
|
70
73
|
"dev": "ts-node src/index.ts",
|
|
71
74
|
"start": "node dist/index.js",
|
|
72
|
-
"fsql": "node ./bin/fsql",
|
|
75
|
+
"fsql": "node ./bin/fsql.js",
|
|
73
76
|
"lint": "pnpm lint:eslint && pnpm lint:prettier",
|
|
74
77
|
"lint:eslint": "eslint '**/*.{ts,tsx,js,jsx}' --ignore-pattern '**/eslint.config.js'",
|
|
75
78
|
"lint:prettier": "prettier '**/*.{ts,tsx,js,jsx,json,md,yaml,yml}' --check || echo 'ā ļø Warning: Prettier formatting issues found. Run \"pnpm fixstyle\" to fix them.'",
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const { sql } = require("@forge/sql");
|
|
2
|
+
|
|
3
|
+
const executeSql = async (req) => {
|
|
4
|
+
console.log("\n=== Executing Custom SQL Query ===");
|
|
5
|
+
|
|
6
|
+
const payload = req.body;
|
|
7
|
+
let sqlRequest = null;
|
|
8
|
+
let query;
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
sqlRequest = JSON.parse(payload);
|
|
12
|
+
query = sqlRequest?.query;
|
|
13
|
+
|
|
14
|
+
if (!query) {
|
|
15
|
+
return getHttpResponse(400, {
|
|
16
|
+
success: false,
|
|
17
|
+
error: "No SQL query provided",
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log("Executing query:", query);
|
|
22
|
+
|
|
23
|
+
const result = await sql.executeRaw(query);
|
|
24
|
+
|
|
25
|
+
console.log("Query result:", result);
|
|
26
|
+
|
|
27
|
+
return getHttpResponse(200, {
|
|
28
|
+
success: true,
|
|
29
|
+
rows: result.rows || [],
|
|
30
|
+
rowCount: result.rows?.length || 0,
|
|
31
|
+
query,
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(error);
|
|
35
|
+
console.error("Error while executing sql", { error });
|
|
36
|
+
|
|
37
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
38
|
+
|
|
39
|
+
return getHttpResponse(500, {
|
|
40
|
+
success: false,
|
|
41
|
+
error: errorMessage,
|
|
42
|
+
...(query && { query }),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function getHttpResponse(statusCode, body) {
|
|
48
|
+
const statusTexts = {
|
|
49
|
+
200: "OK",
|
|
50
|
+
400: "Bad Request",
|
|
51
|
+
404: "Not Found",
|
|
52
|
+
500: "Internal Server Error",
|
|
53
|
+
};
|
|
54
|
+
const statusText = statusTexts[statusCode] || "Bad Request";
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
headers: { "Content-Type": ["application/json"] },
|
|
58
|
+
statusCode,
|
|
59
|
+
statusText,
|
|
60
|
+
body: JSON.stringify(body),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { executeSql };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { sql } from "@forge/sql";
|
|
2
|
+
|
|
3
|
+
const executeSql = async (req) => {
|
|
4
|
+
console.log("\n=== Executing Custom SQL Query ===");
|
|
5
|
+
|
|
6
|
+
const payload = req.body;
|
|
7
|
+
let sqlRequest = null;
|
|
8
|
+
let query;
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
sqlRequest = JSON.parse(payload);
|
|
12
|
+
query = sqlRequest?.query;
|
|
13
|
+
|
|
14
|
+
if (!query) {
|
|
15
|
+
return getHttpResponse(400, {
|
|
16
|
+
success: false,
|
|
17
|
+
error: "No SQL query provided",
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log("Executing query:", query);
|
|
22
|
+
|
|
23
|
+
const result = await sql.executeRaw(query);
|
|
24
|
+
|
|
25
|
+
console.log("Query result:", result);
|
|
26
|
+
|
|
27
|
+
return getHttpResponse(200, {
|
|
28
|
+
success: true,
|
|
29
|
+
rows: result.rows || [],
|
|
30
|
+
rowCount: result.rows?.length || 0,
|
|
31
|
+
query,
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(error);
|
|
35
|
+
console.error("Error while executing sql", { error });
|
|
36
|
+
|
|
37
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
38
|
+
|
|
39
|
+
return getHttpResponse(500, {
|
|
40
|
+
success: false,
|
|
41
|
+
error: errorMessage,
|
|
42
|
+
...(query && { query }),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function getHttpResponse(statusCode, body) {
|
|
48
|
+
const statusTexts = {
|
|
49
|
+
200: "OK",
|
|
50
|
+
400: "Bad Request",
|
|
51
|
+
404: "Not Found",
|
|
52
|
+
500: "Internal Server Error",
|
|
53
|
+
};
|
|
54
|
+
const statusText = statusTexts[statusCode] || "Bad Request";
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
headers: { "Content-Type": ["application/json"] },
|
|
58
|
+
statusCode,
|
|
59
|
+
statusText,
|
|
60
|
+
body: JSON.stringify(body),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { executeSql };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { sql } from "@forge/sql";
|
|
2
|
+
|
|
3
|
+
const executeSql = async (req: {
|
|
4
|
+
body: string;
|
|
5
|
+
}): Promise<ReturnType<typeof getHttpResponse>> => {
|
|
6
|
+
console.log("\n=== Executing Custom SQL Query ===");
|
|
7
|
+
|
|
8
|
+
const payload = req.body;
|
|
9
|
+
let sqlRequest: { query?: string } | null = null;
|
|
10
|
+
let query: string | undefined;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
sqlRequest = JSON.parse(payload);
|
|
14
|
+
query = sqlRequest?.query;
|
|
15
|
+
|
|
16
|
+
if (!query) {
|
|
17
|
+
return getHttpResponse(400, {
|
|
18
|
+
success: false,
|
|
19
|
+
error: "No SQL query provided",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log("Executing query:", query);
|
|
24
|
+
|
|
25
|
+
// Import sql directly for custom queries
|
|
26
|
+
const result = await sql.executeRaw(query);
|
|
27
|
+
|
|
28
|
+
console.log("Query result:", result);
|
|
29
|
+
|
|
30
|
+
return getHttpResponse(200, {
|
|
31
|
+
success: true,
|
|
32
|
+
rows: result.rows || [],
|
|
33
|
+
rowCount: result.rows?.length || 0,
|
|
34
|
+
query,
|
|
35
|
+
});
|
|
36
|
+
} catch (error: unknown) {
|
|
37
|
+
console.error(error);
|
|
38
|
+
console.error("Error while executing sql", { error });
|
|
39
|
+
|
|
40
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
41
|
+
|
|
42
|
+
return getHttpResponse(500, {
|
|
43
|
+
success: false,
|
|
44
|
+
error: errorMessage,
|
|
45
|
+
...(query && { query }),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function getHttpResponse(
|
|
51
|
+
statusCode: number,
|
|
52
|
+
body: Record<string, unknown>,
|
|
53
|
+
): {
|
|
54
|
+
headers: { "Content-Type": string[] };
|
|
55
|
+
statusCode: number;
|
|
56
|
+
statusText: string;
|
|
57
|
+
body: string;
|
|
58
|
+
} {
|
|
59
|
+
const statusTexts: Record<number, string> = {
|
|
60
|
+
200: "OK",
|
|
61
|
+
400: "Bad Request",
|
|
62
|
+
404: "Not Found",
|
|
63
|
+
500: "Internal Server Error",
|
|
64
|
+
};
|
|
65
|
+
const statusText = statusTexts[statusCode] || "Bad Request";
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
headers: { "Content-Type": ["application/json"] },
|
|
69
|
+
statusCode,
|
|
70
|
+
statusText,
|
|
71
|
+
body: JSON.stringify(body),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { executeSql };
|
package/bin/setup
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import { createRequire } from "module";
|
|
6
|
-
|
|
7
|
-
// Support both ESM and CJS imports for js-yaml
|
|
8
|
-
const require = createRequire(import.meta.url);
|
|
9
|
-
const YAML = require("yaml");
|
|
10
|
-
|
|
11
|
-
const projectRoot = process.cwd();
|
|
12
|
-
|
|
13
|
-
console.log("Setting up Forge SQL CLI in this project...");
|
|
14
|
-
|
|
15
|
-
// Detect consumer project type
|
|
16
|
-
let isEsm = false;
|
|
17
|
-
let isTypeScript = fs.existsSync(path.join(projectRoot, "tsconfig.json"));
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
const pkgPath = path.join(projectRoot, "package.json");
|
|
21
|
-
if (fs.existsSync(pkgPath)) {
|
|
22
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
23
|
-
if (pkg.type === "module") {
|
|
24
|
-
isEsm = true;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
} catch (e) {
|
|
28
|
-
// Ignore errors reading package.json
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// 1. Create src/fsql file
|
|
32
|
-
const srcDir = path.join(projectRoot, "src");
|
|
33
|
-
if (!fs.existsSync(srcDir)) {
|
|
34
|
-
fs.mkdirSync(srcDir, { recursive: true });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const extension = isTypeScript ? "ts" : isEsm ? "js" : "js";
|
|
38
|
-
// If it's CJS, we still use .js but different content
|
|
39
|
-
const fsqlPath = path.join(srcDir, `fsql.${extension}`);
|
|
40
|
-
|
|
41
|
-
let fsqlContent = "";
|
|
42
|
-
if (isEsm || isTypeScript) {
|
|
43
|
-
fsqlContent = `import { executeSql } from 'forge-fsql';
|
|
44
|
-
|
|
45
|
-
export { executeSql };
|
|
46
|
-
`;
|
|
47
|
-
} else {
|
|
48
|
-
fsqlContent = `const { executeSql } = require('forge-fsql');
|
|
49
|
-
|
|
50
|
-
module.exports = { executeSql };
|
|
51
|
-
`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
fs.writeFileSync(fsqlPath, fsqlContent);
|
|
55
|
-
console.log(`ā Created src/fsql.${extension}`);
|
|
56
|
-
|
|
57
|
-
// 2. Update manifest.yaml or manifest.yml
|
|
58
|
-
let manifestPath = path.join(projectRoot, "manifest.yml");
|
|
59
|
-
if (!fs.existsSync(manifestPath)) {
|
|
60
|
-
manifestPath = path.join(projectRoot, "manifest.yaml");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!fs.existsSync(manifestPath)) {
|
|
64
|
-
console.error(
|
|
65
|
-
"Error: Could not find manifest.yml or manifest.yaml in the current directory.",
|
|
66
|
-
);
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
let doc;
|
|
71
|
-
try {
|
|
72
|
-
const fileContents = fs.readFileSync(manifestPath, "utf8");
|
|
73
|
-
doc = YAML.parseDocument(fileContents);
|
|
74
|
-
} catch (e) {
|
|
75
|
-
console.error("Error reading manifest:", e);
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!doc.contents) {
|
|
80
|
-
doc.contents = doc.createNode({});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (!doc.has("modules")) {
|
|
84
|
-
doc.set("modules", doc.createNode({}));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const modules = doc.get("modules");
|
|
88
|
-
|
|
89
|
-
// Ensure function is an array
|
|
90
|
-
const functionKey = "executeSql";
|
|
91
|
-
if (!modules.has("function")) {
|
|
92
|
-
modules.set("function", doc.createNode([]));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
let functions = modules.get("function");
|
|
96
|
-
|
|
97
|
-
// If it's somehow not a sequence, convert it (though unlikely in Forge)
|
|
98
|
-
if (!YAML.isSeq(functions)) {
|
|
99
|
-
const obj = functions.toJSON();
|
|
100
|
-
functions = doc.createNode(
|
|
101
|
-
Object.entries(obj).map(([key, val]) => ({ key, ...val })),
|
|
102
|
-
);
|
|
103
|
-
modules.set("function", functions);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const handlerName = "fsql.executeSql";
|
|
107
|
-
let functionExists = functions.items.find((f) => {
|
|
108
|
-
const js = f.toJSON();
|
|
109
|
-
return js && js.key === functionKey;
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
if (!functionExists) {
|
|
113
|
-
functions.add(
|
|
114
|
-
doc.createNode({
|
|
115
|
-
key: functionKey,
|
|
116
|
-
handler: handlerName,
|
|
117
|
-
}),
|
|
118
|
-
);
|
|
119
|
-
console.log(
|
|
120
|
-
`ā Added function:${functionKey} with handler ${handlerName} to manifest`,
|
|
121
|
-
);
|
|
122
|
-
} else {
|
|
123
|
-
// If it's a Pair (map item) or just a Map in the sequence
|
|
124
|
-
if (YAML.isMap(functionExists)) {
|
|
125
|
-
functionExists.set("handler", handlerName);
|
|
126
|
-
} else {
|
|
127
|
-
// Should not happen with doc.createNode above, but for safety:
|
|
128
|
-
const idx = functions.items.indexOf(functionExists);
|
|
129
|
-
functions.set(
|
|
130
|
-
idx,
|
|
131
|
-
doc.createNode({ key: functionKey, handler: handlerName }),
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
console.log(
|
|
135
|
-
`ā Updated function:${functionKey} handler to ${handlerName} in manifest`,
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Ensure webtrigger is an array
|
|
140
|
-
const webtriggerKey = "execute-sql";
|
|
141
|
-
if (!modules.has("webtrigger")) {
|
|
142
|
-
modules.set("webtrigger", doc.createNode([]));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
let webtriggers = modules.get("webtrigger");
|
|
146
|
-
if (!YAML.isSeq(webtriggers)) {
|
|
147
|
-
const obj = webtriggers.toJSON();
|
|
148
|
-
webtriggers = doc.createNode(
|
|
149
|
-
Object.entries(obj).map(([key, val]) => ({ key, ...val })),
|
|
150
|
-
);
|
|
151
|
-
modules.set("webtrigger", webtriggers);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
let webtriggerExists = webtriggers.items.find((w) => {
|
|
155
|
-
const js = w.toJSON();
|
|
156
|
-
return js && js.key === webtriggerKey;
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
if (!webtriggerExists) {
|
|
160
|
-
webtriggers.add(
|
|
161
|
-
doc.createNode({
|
|
162
|
-
key: webtriggerKey,
|
|
163
|
-
function: functionKey,
|
|
164
|
-
}),
|
|
165
|
-
);
|
|
166
|
-
console.log(`ā Added webtrigger:${webtriggerKey} to manifest`);
|
|
167
|
-
} else {
|
|
168
|
-
if (YAML.isMap(webtriggerExists)) {
|
|
169
|
-
webtriggerExists.set("function", functionKey);
|
|
170
|
-
} else {
|
|
171
|
-
const idx = webtriggers.items.indexOf(webtriggerExists);
|
|
172
|
-
webtriggers.set(
|
|
173
|
-
idx,
|
|
174
|
-
doc.createNode({ key: webtriggerKey, function: functionKey }),
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
console.log(
|
|
178
|
-
`ā Ensured webtrigger:${webtriggerKey} points to function ${functionKey}`,
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
fs.writeFileSync(manifestPath, doc.toString());
|
|
184
|
-
console.log("ā Updated manifest file successfully");
|
|
185
|
-
} catch (e) {
|
|
186
|
-
console.error("Error writing manifest:", e);
|
|
187
|
-
process.exit(1);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
console.log("\nSetup completed successfully!");
|
|
191
|
-
console.log(
|
|
192
|
-
"You can now use the forge-fsql CLI to interact with your Forge SQL database.",
|
|
193
|
-
);
|
/package/bin/{fsql ā fsql.js}
RENAMED
|
File without changes
|