@alcedocore/cli 0.0.1-rc.1 → 0.0.1-rc.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/dist/commands/init.js +20 -6
- package/dist/commands/proxy.js +11 -9
- package/dist/commands/publish.js +1 -2
- package/package.json +1 -1
- package/src/commands/init.ts +31 -7
- package/src/commands/proxy.ts +17 -14
- package/src/commands/publish.ts +1 -2
- package/templates/plugin/Dockerfile.node.ejs +10 -8
- package/templates/plugin/README.md +31 -0
- package/templates/plugin/manifest.json.ejs +0 -1
- package/templates/plugin/package.json +23 -0
- package/templates/plugin/server.ts +33 -0
- package/templates/plugin/tsconfig.json +18 -0
- package/templates/plugin/README.md.ejs +0 -19
- package/templates/plugin/server.js.ejs +0 -27
- /package/templates/plugin/{server.py.ejs → server.py} +0 -0
package/dist/commands/init.js
CHANGED
|
@@ -11,6 +11,7 @@ const logger_1 = require("../utils/logger");
|
|
|
11
11
|
const ejs_renderer_1 = require("../utils/ejs-renderer");
|
|
12
12
|
const config_1 = require("../config");
|
|
13
13
|
const node_readline_1 = __importDefault(require("node:readline"));
|
|
14
|
+
const node_child_process_1 = require("node:child_process");
|
|
14
15
|
exports.initCommand = new commander_1.Command("init")
|
|
15
16
|
.argument("<name>", "Plugin project name (e.g., my-plugin)")
|
|
16
17
|
.option("-l, --language <language>", "Plugin language (python or node)")
|
|
@@ -36,6 +37,7 @@ exports.initCommand = new commander_1.Command("init")
|
|
|
36
37
|
description: `A new AlcedoCore plugin`,
|
|
37
38
|
language,
|
|
38
39
|
registryUrl,
|
|
40
|
+
startCMD: language == "node" ? "npm run dev" : "python server.py",
|
|
39
41
|
};
|
|
40
42
|
const spinner = (0, logger_1.createSpinner)(`Scaffolding plugin: ${name}`);
|
|
41
43
|
try {
|
|
@@ -50,21 +52,28 @@ exports.initCommand = new commander_1.Command("init")
|
|
|
50
52
|
: "Dockerfile.node.ejs";
|
|
51
53
|
(0, ejs_renderer_1.renderAndWrite)(node_path_1.default.join(templatesDir, "plugin", dockerTemplate), node_path_1.default.join(targetDir, "Dockerfile"), data);
|
|
52
54
|
// Server stub (language-specific)
|
|
53
|
-
const serverTemplate = language === "python" ? "server.py
|
|
54
|
-
(0, ejs_renderer_1.renderAndWrite)(node_path_1.default.join(templatesDir, "plugin", serverTemplate), node_path_1.default.join(targetDir, `server.${language === "python" ? "py" : "
|
|
55
|
+
const serverTemplate = language === "python" ? "server.py" : "server.ts";
|
|
56
|
+
(0, ejs_renderer_1.renderAndWrite)(node_path_1.default.join(templatesDir, "plugin", serverTemplate), node_path_1.default.join(targetDir, `server.${language === "python" ? "py" : "ts"}`), data);
|
|
55
57
|
// .gitignore
|
|
56
58
|
(0, ejs_renderer_1.renderAndWrite)(node_path_1.default.join(templatesDir, "plugin", "gitignore.ejs"), node_path_1.default.join(targetDir, ".gitignore"), data);
|
|
57
59
|
// README.md
|
|
58
|
-
(0, ejs_renderer_1.renderAndWrite)(node_path_1.default.join(templatesDir, "plugin", "README.md
|
|
60
|
+
(0, ejs_renderer_1.renderAndWrite)(node_path_1.default.join(templatesDir, "plugin", "README.md"), node_path_1.default.join(targetDir, "README.md"), data);
|
|
59
61
|
copyGitkeep(templatesDir, targetDir, "migrations");
|
|
60
62
|
copyGitkeep(templatesDir, targetDir, "pages");
|
|
61
63
|
copyGitkeep(templatesDir, targetDir, "public");
|
|
64
|
+
if (language == "node") {
|
|
65
|
+
copyFile(node_path_1.default.join(templatesDir, "plugin", "tsconfig.json"), node_path_1.default.join(targetDir, "tsconfig.json"));
|
|
66
|
+
copyFile(node_path_1.default.join(templatesDir, "plugin", "package.json"), node_path_1.default.join(targetDir, "package.json"));
|
|
67
|
+
spinner.text = "Installing NPM packages...";
|
|
68
|
+
(0, node_child_process_1.execSync)("npm install", { cwd: targetDir });
|
|
69
|
+
}
|
|
62
70
|
spinner.succeed();
|
|
63
71
|
(0, logger_1.success)(`Plugin scaffolded: ${targetDir}`);
|
|
64
72
|
(0, logger_1.info)(`Next steps:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
- cd ${name}
|
|
74
|
+
- You can now edit the server.${language === "python" ? "py" : "ts"} and manifest.json
|
|
75
|
+
- See the README.md for getting started!
|
|
76
|
+
`);
|
|
68
77
|
}
|
|
69
78
|
catch (err) {
|
|
70
79
|
spinner.fail();
|
|
@@ -113,6 +122,11 @@ function copyGitkeep(templateDir, targetDir, subDir) {
|
|
|
113
122
|
node_fs_1.default.writeFileSync(dest, "");
|
|
114
123
|
}
|
|
115
124
|
}
|
|
125
|
+
function copyFile(sourceDir, targetDir) {
|
|
126
|
+
if (node_fs_1.default.existsSync(sourceDir)) {
|
|
127
|
+
node_fs_1.default.copyFileSync(sourceDir, targetDir);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
116
130
|
function parseLanguage(lang) {
|
|
117
131
|
const v = lang.trim().toLowerCase();
|
|
118
132
|
if (v === "python" || v === "py")
|
package/dist/commands/proxy.js
CHANGED
|
@@ -38,14 +38,6 @@ exports.proxyCommand = new commander_1.Command("proxy")
|
|
|
38
38
|
validateSpinner.succeed();
|
|
39
39
|
const server = node_http_1.default.createServer(async (clientReq, clientRes) => {
|
|
40
40
|
const startTime = Date.now();
|
|
41
|
-
const requestId = await registerRequest(coreUrl, apiKey || undefined, slug);
|
|
42
|
-
if (!requestId) {
|
|
43
|
-
(0, logger_1.error)("Failed to register request ID with core");
|
|
44
|
-
clientRes.statusCode = 502;
|
|
45
|
-
clientRes.setHeader("Content-Type", "text/plain");
|
|
46
|
-
clientRes.end("Bad Gateway: core unavailable");
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
41
|
// Clone headers and inject X-Request-ID
|
|
50
42
|
const headers = {};
|
|
51
43
|
for (const [key, value] of Object.entries(clientReq.headers)) {
|
|
@@ -55,7 +47,17 @@ exports.proxyCommand = new commander_1.Command("proxy")
|
|
|
55
47
|
: value;
|
|
56
48
|
}
|
|
57
49
|
}
|
|
58
|
-
|
|
50
|
+
if (!clientReq.url || !clientReq.url.includes(".")) {
|
|
51
|
+
const requestId = await registerRequest(coreUrl, apiKey || undefined, slug);
|
|
52
|
+
if (!requestId) {
|
|
53
|
+
(0, logger_1.error)("Failed to register request ID with core");
|
|
54
|
+
clientRes.statusCode = 502;
|
|
55
|
+
clientRes.setHeader("Content-Type", "text/plain");
|
|
56
|
+
clientRes.end("Bad Gateway: core unavailable");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
headers["X-Request-ID"] = requestId;
|
|
60
|
+
}
|
|
59
61
|
headers["host"] = `${parsedTarget.hostname}:${parsedTarget.port}`;
|
|
60
62
|
const options = {
|
|
61
63
|
hostname: parsedTarget.hostname,
|
package/dist/commands/publish.js
CHANGED
|
@@ -42,8 +42,7 @@ exports.publishCommand = new commander_1.Command("publish")
|
|
|
42
42
|
try {
|
|
43
43
|
spinner.text = "Building Docker image...";
|
|
44
44
|
await buildDockerImage(pluginDir, config.registryUrl, manifest.name, manifest.version);
|
|
45
|
-
|
|
46
|
-
spinner.text = "Pushing Docker image...";
|
|
45
|
+
spinner.text = "Building complete, pushing Docker image...";
|
|
47
46
|
await pushDockerImage(config.registryUrl, manifest.name, manifest.version);
|
|
48
47
|
spinner.succeed();
|
|
49
48
|
(0, logger_1.success)(`Plugin has been build and pushed under ${manifest.name}:${manifest.version}`);
|
package/package.json
CHANGED
package/src/commands/init.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { renderAndWrite } from "../utils/ejs-renderer";
|
|
11
11
|
import { loadConfig } from "../config";
|
|
12
12
|
import readline from "node:readline";
|
|
13
|
+
import { exec, execSync } from "node:child_process";
|
|
13
14
|
|
|
14
15
|
export const initCommand = new Command("init")
|
|
15
16
|
.argument("<name>", "Plugin project name (e.g., my-plugin)")
|
|
@@ -41,6 +42,8 @@ export const initCommand = new Command("init")
|
|
|
41
42
|
description: `A new AlcedoCore plugin`,
|
|
42
43
|
language,
|
|
43
44
|
registryUrl,
|
|
45
|
+
startCMD:
|
|
46
|
+
language == "node" ? "npm run dev" : "python server.py",
|
|
44
47
|
};
|
|
45
48
|
|
|
46
49
|
const spinner = createSpinner(`Scaffolding plugin: ${name}`);
|
|
@@ -70,12 +73,12 @@ export const initCommand = new Command("init")
|
|
|
70
73
|
|
|
71
74
|
// Server stub (language-specific)
|
|
72
75
|
const serverTemplate =
|
|
73
|
-
language === "python" ? "server.py
|
|
76
|
+
language === "python" ? "server.py" : "server.ts";
|
|
74
77
|
renderAndWrite(
|
|
75
78
|
path.join(templatesDir, "plugin", serverTemplate),
|
|
76
79
|
path.join(
|
|
77
80
|
targetDir,
|
|
78
|
-
`server.${language === "python" ? "py" : "
|
|
81
|
+
`server.${language === "python" ? "py" : "ts"}`,
|
|
79
82
|
),
|
|
80
83
|
data,
|
|
81
84
|
);
|
|
@@ -89,7 +92,7 @@ export const initCommand = new Command("init")
|
|
|
89
92
|
|
|
90
93
|
// README.md
|
|
91
94
|
renderAndWrite(
|
|
92
|
-
path.join(templatesDir, "plugin", "README.md
|
|
95
|
+
path.join(templatesDir, "plugin", "README.md"),
|
|
93
96
|
path.join(targetDir, "README.md"),
|
|
94
97
|
data,
|
|
95
98
|
);
|
|
@@ -98,13 +101,28 @@ export const initCommand = new Command("init")
|
|
|
98
101
|
copyGitkeep(templatesDir, targetDir, "pages");
|
|
99
102
|
copyGitkeep(templatesDir, targetDir, "public");
|
|
100
103
|
|
|
101
|
-
|
|
104
|
+
if (language == "node") {
|
|
105
|
+
copyFile(
|
|
106
|
+
path.join(templatesDir, "plugin", "tsconfig.json"),
|
|
107
|
+
path.join(targetDir, "tsconfig.json"),
|
|
108
|
+
);
|
|
109
|
+
copyFile(
|
|
110
|
+
path.join(templatesDir, "plugin", "package.json"),
|
|
111
|
+
path.join(targetDir, "package.json"),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
spinner.text = "Installing NPM packages...";
|
|
115
|
+
execSync("npm install", { cwd: targetDir });
|
|
116
|
+
}
|
|
102
117
|
|
|
118
|
+
spinner.succeed();
|
|
103
119
|
success(`Plugin scaffolded: ${targetDir}`);
|
|
120
|
+
|
|
104
121
|
info(`Next steps:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
122
|
+
- cd ${name}
|
|
123
|
+
- You can now edit the server.${language === "python" ? "py" : "ts"} and manifest.json
|
|
124
|
+
- See the README.md for getting started!
|
|
125
|
+
`);
|
|
108
126
|
} catch (err: any) {
|
|
109
127
|
spinner.fail();
|
|
110
128
|
logError(`Failed to scaffold plugin: ${err.message}`);
|
|
@@ -162,6 +180,12 @@ function copyGitkeep(
|
|
|
162
180
|
}
|
|
163
181
|
}
|
|
164
182
|
|
|
183
|
+
function copyFile(sourceDir: string, targetDir: string) {
|
|
184
|
+
if (fs.existsSync(sourceDir)) {
|
|
185
|
+
fs.copyFileSync(sourceDir, targetDir);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
165
189
|
function parseLanguage(lang: string): "python" | "node" {
|
|
166
190
|
const v = lang.trim().toLowerCase();
|
|
167
191
|
if (v === "python" || v === "py") return "python";
|
package/src/commands/proxy.ts
CHANGED
|
@@ -63,19 +63,6 @@ export const proxyCommand = new Command("proxy")
|
|
|
63
63
|
const server = http.createServer(async (clientReq, clientRes) => {
|
|
64
64
|
const startTime = Date.now();
|
|
65
65
|
|
|
66
|
-
const requestId = await registerRequest(
|
|
67
|
-
coreUrl,
|
|
68
|
-
apiKey || undefined,
|
|
69
|
-
slug,
|
|
70
|
-
);
|
|
71
|
-
if (!requestId) {
|
|
72
|
-
logError("Failed to register request ID with core");
|
|
73
|
-
clientRes.statusCode = 502;
|
|
74
|
-
clientRes.setHeader("Content-Type", "text/plain");
|
|
75
|
-
clientRes.end("Bad Gateway: core unavailable");
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
66
|
// Clone headers and inject X-Request-ID
|
|
80
67
|
const headers: Record<string, string> = {};
|
|
81
68
|
for (const [key, value] of Object.entries(clientReq.headers)) {
|
|
@@ -85,7 +72,23 @@ export const proxyCommand = new Command("proxy")
|
|
|
85
72
|
: value;
|
|
86
73
|
}
|
|
87
74
|
}
|
|
88
|
-
|
|
75
|
+
|
|
76
|
+
if (!clientReq.url || !clientReq.url.includes(".")) {
|
|
77
|
+
const requestId = await registerRequest(
|
|
78
|
+
coreUrl,
|
|
79
|
+
apiKey || undefined,
|
|
80
|
+
slug,
|
|
81
|
+
);
|
|
82
|
+
if (!requestId) {
|
|
83
|
+
logError("Failed to register request ID with core");
|
|
84
|
+
clientRes.statusCode = 502;
|
|
85
|
+
clientRes.setHeader("Content-Type", "text/plain");
|
|
86
|
+
clientRes.end("Bad Gateway: core unavailable");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
headers["X-Request-ID"] = requestId;
|
|
91
|
+
}
|
|
89
92
|
headers["host"] = `${parsedTarget.hostname}:${parsedTarget.port}`;
|
|
90
93
|
|
|
91
94
|
const options: http.RequestOptions = {
|
package/src/commands/publish.ts
CHANGED
|
@@ -49,8 +49,7 @@ export const publishCommand = new Command("publish")
|
|
|
49
49
|
manifest.name,
|
|
50
50
|
manifest.version,
|
|
51
51
|
);
|
|
52
|
-
|
|
53
|
-
spinner.text = "Pushing Docker image...";
|
|
52
|
+
spinner.text = "Building complete, pushing Docker image...";
|
|
54
53
|
await pushDockerImage(
|
|
55
54
|
config.registryUrl,
|
|
56
55
|
manifest.name,
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
FROM node:
|
|
1
|
+
FROM node:24-slim AS builder
|
|
2
2
|
|
|
3
3
|
WORKDIR /app
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
COPY
|
|
7
|
-
|
|
8
|
-
COPY migrations/ ./migrations/
|
|
9
|
-
COPY pages/dist/ ./pages/dist/
|
|
10
|
-
COPY manifest.json .
|
|
5
|
+
# Copy the rest of the files
|
|
6
|
+
COPY . .
|
|
7
|
+
RUN npm ci
|
|
11
8
|
|
|
9
|
+
# Build the TypeScript project
|
|
10
|
+
RUN npm run build
|
|
11
|
+
|
|
12
|
+
# Expose the port your app runs on
|
|
12
13
|
EXPOSE 8080
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
# Run the compiled JavaScript file
|
|
16
|
+
CMD ["node", "server.js"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# <%= name %>
|
|
2
|
+
|
|
3
|
+
A plugin for AlcedoCore.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
Run locally during development:
|
|
8
|
+
|
|
9
|
+
### Connect
|
|
10
|
+
|
|
11
|
+
Run `alcedo connect` to connect to your AlcedoCore instance.
|
|
12
|
+
|
|
13
|
+
### Development
|
|
14
|
+
|
|
15
|
+
In one terminal:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
alcedo dev proxy
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
In another terminal:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
<%= startCMD %>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Build and deploy
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
alcedo publish
|
|
31
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "test-node",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A plugin for Alcedo.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "npx tsx server.ts",
|
|
8
|
+
"build": "tsc"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [],
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"typescript": "^7.0.2"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@alcedocore/sdk": "^0.0.1-rc.0",
|
|
19
|
+
"@types/express": "^5.0.6",
|
|
20
|
+
"@types/node": "^26.4.1",
|
|
21
|
+
"express": "^5.2.1"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { createClient } from "@alcedocore/sdk";
|
|
3
|
+
|
|
4
|
+
const app = express();
|
|
5
|
+
const port = process.env.PORT || 3000;
|
|
6
|
+
const CORE_URL = process.env.CORE_URL || "http://localhost:8080";
|
|
7
|
+
const PLUGIN_SLUG = process.env.PLUGIN_SLUG || "<%= slug %>";
|
|
8
|
+
|
|
9
|
+
const client = createClient(CORE_URL, {
|
|
10
|
+
timeout: 30_000,
|
|
11
|
+
retry: { limit: 2 },
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
app.get("/", (req, res) => {
|
|
15
|
+
res.send("Hello World!");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
app.get("/settings", async (_req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
const settings = await client.settings.get(PLUGIN_SLUG, {
|
|
21
|
+
headers: {
|
|
22
|
+
["x-request-id"]: _req.headers["x-request-id"],
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
res.json({ slug: PLUGIN_SLUG, settings });
|
|
26
|
+
} catch (err) {
|
|
27
|
+
res.status(500).json({ slug: PLUGIN_SLUG, error: err.message });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
app.listen(port, () => {
|
|
32
|
+
console.log(`Example app listening on port ${port}`);
|
|
33
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "nodenext",
|
|
4
|
+
"target": "es2022",
|
|
5
|
+
"lib": ["esnext"],
|
|
6
|
+
"types": ["node"],
|
|
7
|
+
"sourceMap": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true,
|
|
10
|
+
"noUncheckedIndexedAccess": true,
|
|
11
|
+
"exactOptionalPropertyTypes": true,
|
|
12
|
+
"verbatimModuleSyntax": false,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noUncheckedSideEffectImports": true,
|
|
15
|
+
"moduleDetection": "force",
|
|
16
|
+
"skipLibCheck": true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# <%= name %>
|
|
2
|
-
|
|
3
|
-
A plugin for Alcedo.
|
|
4
|
-
|
|
5
|
-
## Getting Started
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# Build and deploy
|
|
9
|
-
docker build -t localhost:5000/<%= slug %>:1.0.0 .
|
|
10
|
-
docker push localhost:5000/<%= slug %>:1.0.0
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Development
|
|
14
|
-
|
|
15
|
-
Run locally during development:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
python server.py
|
|
19
|
-
```
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
const http = require("http");
|
|
2
|
-
const url = require("url");
|
|
3
|
-
|
|
4
|
-
const server = http.createServer((req, res) => {
|
|
5
|
-
const parsed = url.parse(req.url);
|
|
6
|
-
const path = parsed.pathname;
|
|
7
|
-
|
|
8
|
-
const sendJson = (data, status = 200) => {
|
|
9
|
-
const body = JSON.stringify(data);
|
|
10
|
-
res.writeHead(status, { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) });
|
|
11
|
-
res.end(body);
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
if (path === "/health") {
|
|
15
|
-
sendJson({ status: "healthy" });
|
|
16
|
-
} else if (req.method === "GET") {
|
|
17
|
-
sendJson({ status: "ok", path });
|
|
18
|
-
} else if (req.method === "POST") {
|
|
19
|
-
sendJson({ status: "ok", method: "POST" });
|
|
20
|
-
} else {
|
|
21
|
-
sendJson({ error: "Method not allowed" }, 405);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
server.listen(8080, "0.0.0.0", () => {
|
|
26
|
-
console.log("Plugin running on port 8080");
|
|
27
|
-
});
|
|
File without changes
|