@alcedocore/cli 0.0.1-rc.1 → 0.0.1-rc.2

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.
@@ -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.ejs" : "server.js.ejs";
54
- (0, ejs_renderer_1.renderAndWrite)(node_path_1.default.join(templatesDir, "plugin", serverTemplate), node_path_1.default.join(targetDir, `server.${language === "python" ? "py" : "js"}`), data);
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.ejs"), node_path_1.default.join(targetDir, "README.md"), data);
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
- cd ${name}
66
- # Edit server.${language === "python" ? "py" : "js"} and manifest.json
67
- # Build with: docker build -t ${registryUrl}/${slug}:1.0.0 .`);
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")
@@ -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
- headers["X-Request-ID"] = requestId;
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,
@@ -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
- (0, logger_1.success)(`Plugin has been build`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alcedocore/cli",
3
- "version": "0.0.1-rc.1",
3
+ "version": "0.0.1-rc.2",
4
4
  "description": "Alcedo plugin development CLI - scaffold, develop, and test plugins",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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.ejs" : "server.js.ejs";
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" : "js"}`,
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.ejs"),
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
- spinner.succeed();
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
- cd ${name}
106
- # Edit server.${language === "python" ? "py" : "js"} and manifest.json
107
- # Build with: docker build -t ${registryUrl}/${slug}:1.0.0 .`);
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";
@@ -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
- headers["X-Request-ID"] = requestId;
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 = {
@@ -49,8 +49,7 @@ export const publishCommand = new Command("publish")
49
49
  manifest.name,
50
50
  manifest.version,
51
51
  );
52
- success(`Plugin has been build`);
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,13 +1,9 @@
1
- FROM node:20-slim
1
+ FROM node:24-slim
2
2
 
3
3
  WORKDIR /app
4
4
 
5
- COPY server.js .
6
- COPY endpoints/ ./endpoints/
7
- COPY public/ ./public/
8
- COPY migrations/ ./migrations/
9
- COPY pages/dist/ ./pages/dist/
10
- COPY manifest.json .
5
+ COPY . .
6
+ RUN npm ci
11
7
 
12
8
  EXPOSE 8080
13
9
 
@@ -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
+ ```
@@ -2,7 +2,6 @@
2
2
  "name": "<%= name %>",
3
3
  "version": "<%= version || '1.0.0' %>",
4
4
  "plugin_type": "docker",
5
- "system_plugin": false,
6
5
  "image": "<%= registryUrl %>/<%= slug %>:1.0.0",
7
6
  "env": {},
8
7
  "resources": {},
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "<%= slug %>",
3
+ "version": "1.0.0",
4
+ "description": "A plugin for Alcedo.",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "dev": "npx tsx server.ts"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "type": "module",
13
+ "devDependencies": {
14
+ "typescript": "^7.0.2"
15
+ },
16
+ "dependencies": {
17
+ "@alcedocore/sdk": "^0.0.1-rc.0"
18
+ }
19
+ }
@@ -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