@alcedocore/cli 0.0.1-rc.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/LICENSE.md +102 -0
- package/dist/commands/add-endpoint.js +104 -0
- package/dist/commands/add-migration.js +80 -0
- package/dist/commands/add-nav-item.js +72 -0
- package/dist/commands/add-page.js +64 -0
- package/dist/commands/build-frontend.js +126 -0
- package/dist/commands/compile-pages.js +120 -0
- package/dist/commands/connect.js +122 -0
- package/dist/commands/deploy.js +74 -0
- package/dist/commands/dev.js +13 -0
- package/dist/commands/init-core.js +900 -0
- package/dist/commands/init.js +124 -0
- package/dist/commands/init.test.js +84 -0
- package/dist/commands/migrate.js +22 -0
- package/dist/commands/proxy.js +157 -0
- package/dist/commands/publish.js +81 -0
- package/dist/commands/replay.js +142 -0
- package/dist/commands/serve-frontend.js +92 -0
- package/dist/config.js +110 -0
- package/dist/config.test.js +48 -0
- package/dist/index.js +81 -0
- package/dist/integration/cli-api.test.js +116 -0
- package/dist/utils/ejs-renderer.js +46 -0
- package/dist/utils/ejs-renderer.test.js +93 -0
- package/dist/utils/formatting.js +65 -0
- package/dist/utils/generateTimestamp.js +17 -0
- package/dist/utils/logger.js +33 -0
- package/dist/utils/validation.js +15 -0
- package/package.json +41 -0
- package/src/commands/add-endpoint.ts +157 -0
- package/src/commands/add-migration.ts +102 -0
- package/src/commands/add-nav-item.ts +106 -0
- package/src/commands/add-page.ts +100 -0
- package/src/commands/build-frontend.ts +148 -0
- package/src/commands/connect.ts +98 -0
- package/src/commands/deploy.ts +85 -0
- package/src/commands/dev.ts +12 -0
- package/src/commands/init-core.ts +1019 -0
- package/src/commands/init.test.ts +92 -0
- package/src/commands/init.ts +171 -0
- package/src/commands/migrate.ts +20 -0
- package/src/commands/proxy.ts +206 -0
- package/src/commands/publish.ts +106 -0
- package/src/commands/serve-frontend.ts +103 -0
- package/src/config.test.ts +50 -0
- package/src/config.ts +125 -0
- package/src/index.ts +100 -0
- package/src/integration/cli-api.test.ts +143 -0
- package/src/utils/ejs-renderer.ts +55 -0
- package/src/utils/formatting.ts +62 -0
- package/src/utils/generateTimestamp.ts +16 -0
- package/src/utils/logger.ts +27 -0
- package/src/utils/validation.ts +13 -0
- package/templates/endpoint/handler.js.ejs +23 -0
- package/templates/endpoint/handler.py.ejs +23 -0
- package/templates/migration/down.sql.ejs +6 -0
- package/templates/migration/up.sql.ejs +11 -0
- package/templates/page/page.vue.ejs +63 -0
- package/templates/plugin/Dockerfile.ejs +13 -0
- package/templates/plugin/Dockerfile.node.ejs +14 -0
- package/templates/plugin/README.md.ejs +19 -0
- package/templates/plugin/gitignore.ejs +6 -0
- package/templates/plugin/manifest.json.ejs +18 -0
- package/templates/plugin/migrations/.gitkeep +0 -0
- package/templates/plugin/pages/.gitkeep +0 -0
- package/templates/plugin/public/.gitkeep +0 -0
- package/templates/plugin/server.js.ejs +27 -0
- package/templates/plugin/server.py.ejs +32 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.serveFrontendCommand = void 0;
|
|
7
|
+
exports.buildInMemory = buildInMemory;
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const express_1 = __importDefault(require("express"));
|
|
10
|
+
const node_events_1 = __importDefault(require("node:events"));
|
|
11
|
+
const build_frontend_1 = require("./build-frontend");
|
|
12
|
+
const chokidar_1 = __importDefault(require("chokidar"));
|
|
13
|
+
exports.serveFrontendCommand = new commander_1.Command("serve-frontend")
|
|
14
|
+
.description("Compile all pages into a dist directory")
|
|
15
|
+
.action(async (opts, cmd) => {
|
|
16
|
+
const app = (0, express_1.default)();
|
|
17
|
+
app.use((req, res, next) => {
|
|
18
|
+
res.header(`Access-Control-Allow-Origin`, `*`);
|
|
19
|
+
res.header(`Access-Control-Allow-Methods`, `GET`);
|
|
20
|
+
res.header(`Access-Control-Allow-Headers`, `Content-Type`);
|
|
21
|
+
next();
|
|
22
|
+
});
|
|
23
|
+
app.get("/dev/css", async (req, res) => {
|
|
24
|
+
res.end(lastCSS);
|
|
25
|
+
});
|
|
26
|
+
app.get("/dev/js", async (req, res) => {
|
|
27
|
+
res.json({
|
|
28
|
+
code: lastJS,
|
|
29
|
+
currentImportsGlobalVue: [...build_frontend_1.currentImportsGlobalVue].map((e) => e.trim()),
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
const updateEvents = new node_events_1.default();
|
|
33
|
+
const watchUpdates = chokidar_1.default.watch("./pages");
|
|
34
|
+
app.get("/streaming", (req, res) => {
|
|
35
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
36
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
37
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
38
|
+
res.setHeader("Connection", "keep-alive");
|
|
39
|
+
res.flushHeaders(); // flush the headers to establish SSE with client
|
|
40
|
+
updateEvents.on("jsUpdate", () => {
|
|
41
|
+
res.write(`id:0\nevent:reloadJS\ndata:js\n\n`);
|
|
42
|
+
});
|
|
43
|
+
updateEvents.on("cssUpdate", () => {
|
|
44
|
+
res.write(`id:0\nevent:reloadCSS\ndata:css\n\n`);
|
|
45
|
+
});
|
|
46
|
+
// If client closes connection, stop sending events
|
|
47
|
+
res.on("close", () => {
|
|
48
|
+
// watcher.
|
|
49
|
+
res.end();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
buildInMemory().then(() => {
|
|
53
|
+
app.listen(3003, () => {
|
|
54
|
+
console.log("Dev server is running");
|
|
55
|
+
watchUpdates.on("all", (event, path) => {
|
|
56
|
+
let beforeTSJS = tsJS;
|
|
57
|
+
let beforeTSCSS = tsCSS;
|
|
58
|
+
buildInMemory().then(() => {
|
|
59
|
+
if (beforeTSCSS != tsCSS) {
|
|
60
|
+
updateEvents.emit("cssUpdate");
|
|
61
|
+
}
|
|
62
|
+
if (beforeTSJS != tsJS) {
|
|
63
|
+
updateEvents.emit("jsUpdate");
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
let lastCSS = "", lastJS = "", tsJS = new Date().getTime(), tsCSS = new Date().getTime();
|
|
71
|
+
async function buildInMemory() {
|
|
72
|
+
console.log("Building...");
|
|
73
|
+
const result = await (0, build_frontend_1.buildFrontendFiles)(false);
|
|
74
|
+
const js = result
|
|
75
|
+
.outputFiles.filter((e) => e.path.endsWith(".js"))
|
|
76
|
+
.map((e) => e.text)
|
|
77
|
+
.join("\n");
|
|
78
|
+
const css = `${result
|
|
79
|
+
.outputFiles.filter((e) => e.path.endsWith(".css"))
|
|
80
|
+
.map((e) => e.text)
|
|
81
|
+
.join("\n")}\`;`;
|
|
82
|
+
if (js != lastJS) {
|
|
83
|
+
tsJS = new Date().getTime();
|
|
84
|
+
lastJS = js;
|
|
85
|
+
}
|
|
86
|
+
if (css != lastCSS) {
|
|
87
|
+
tsCSS = new Date().getTime();
|
|
88
|
+
lastCSS = css;
|
|
89
|
+
}
|
|
90
|
+
console.log("Build done!");
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=serve-frontend.js.map
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadConfig = loadConfig;
|
|
7
|
+
exports.findAlcedorc = findAlcedorc;
|
|
8
|
+
const cosmiconfig_1 = require("cosmiconfig");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const dotenv_1 = require("dotenv");
|
|
12
|
+
const DEFAULTS = {
|
|
13
|
+
registryUrl: "localhost:5000",
|
|
14
|
+
coreUrl: "http://localhost:8080",
|
|
15
|
+
pluginDir: process.cwd(),
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Load config with priority: CLI flags highest → env vars → .alcedorc → defaults lowest.
|
|
19
|
+
* `cliFlags` is the parsed Commander.js flags object (already processed by Commander).
|
|
20
|
+
*/
|
|
21
|
+
function loadConfig(cliFlags = {}) {
|
|
22
|
+
// Start with defaults
|
|
23
|
+
const config = { ...DEFAULTS };
|
|
24
|
+
// Override with .alcedorc (lowest priority override)
|
|
25
|
+
const rcConfig = loadAlcedorc();
|
|
26
|
+
if (rcConfig) {
|
|
27
|
+
Object.assign(config, rcConfig);
|
|
28
|
+
}
|
|
29
|
+
// Override with .alcedocore.dev.env (set by `alcedocore connect`)
|
|
30
|
+
const devEnvConfig = loadDevEnv();
|
|
31
|
+
Object.assign(config, devEnvConfig);
|
|
32
|
+
// Override with env vars (medium priority)
|
|
33
|
+
const envConfig = loadEnvConfig();
|
|
34
|
+
Object.assign(config, envConfig);
|
|
35
|
+
// Override with CLI flags (highest priority)
|
|
36
|
+
Object.assign(config, cliFlags);
|
|
37
|
+
return config;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Walk up directory tree from startDir (default: cwd) to find .alcedorc JSON file.
|
|
41
|
+
* Returns the parsed config or null if no file found.
|
|
42
|
+
*/
|
|
43
|
+
function loadAlcedorc() {
|
|
44
|
+
const explorer = (0, cosmiconfig_1.cosmiconfigSync)("alcedo", {
|
|
45
|
+
searchPlaces: [".alcedorc", ".alcedorc.json"],
|
|
46
|
+
stopDir: node_path_1.default.parse(process.cwd()).root,
|
|
47
|
+
});
|
|
48
|
+
// cosmiconfig's search walks up automatically
|
|
49
|
+
const result = explorer.search();
|
|
50
|
+
if (result && !result.isEmpty) {
|
|
51
|
+
return result.config;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Read .alcedocore.dev.env from the current directory (set by `alcedocore connect`).
|
|
57
|
+
*/
|
|
58
|
+
function loadDevEnv() {
|
|
59
|
+
const config = {};
|
|
60
|
+
const envPath = node_path_1.default.join(process.cwd(), ".alcedocore.dev.env");
|
|
61
|
+
if (!node_fs_1.default.existsSync(envPath))
|
|
62
|
+
return config;
|
|
63
|
+
const envConfig = (0, dotenv_1.parse)(node_fs_1.default.readFileSync(envPath));
|
|
64
|
+
if (envConfig.CORE_URL)
|
|
65
|
+
config.coreUrl = envConfig.CORE_URL;
|
|
66
|
+
if (envConfig.API_KEY)
|
|
67
|
+
config.apiKey = envConfig.API_KEY;
|
|
68
|
+
return config;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Read environment variables with ALCEDO_ prefix.
|
|
72
|
+
* e.g., ALCEDO_REGISTRY_URL → registryUrl, ALCEDO_CORE_URL → coreUrl
|
|
73
|
+
*/
|
|
74
|
+
function loadEnvConfig() {
|
|
75
|
+
const config = {};
|
|
76
|
+
const envMapping = {
|
|
77
|
+
ALCEDO_REGISTRY_URL: "registryUrl",
|
|
78
|
+
ALCEDO_CORE_URL: "coreUrl",
|
|
79
|
+
ALCEDO_PLUGIN_DIR: "pluginDir",
|
|
80
|
+
ALCEDO_API_KEY: "apiKey",
|
|
81
|
+
};
|
|
82
|
+
for (const [envKey, configKey] of Object.entries(envMapping)) {
|
|
83
|
+
const value = process.env[envKey];
|
|
84
|
+
if (value) {
|
|
85
|
+
config[configKey] = value;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return config;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Explicit directory tree walk for .alcedorc discovery (CLI-06).
|
|
92
|
+
* Used when cosmiconfig's auto-search isn't desired and we need
|
|
93
|
+
* manual control over the walk.
|
|
94
|
+
*/
|
|
95
|
+
function findAlcedorc(startDir = process.cwd()) {
|
|
96
|
+
let currentDir = node_path_1.default.resolve(startDir);
|
|
97
|
+
while (true) {
|
|
98
|
+
const candidate = node_path_1.default.join(currentDir, ".alcedorc");
|
|
99
|
+
if (node_fs_1.default.existsSync(candidate)) {
|
|
100
|
+
return candidate;
|
|
101
|
+
}
|
|
102
|
+
const parent = node_path_1.default.dirname(currentDir);
|
|
103
|
+
if (parent === currentDir) {
|
|
104
|
+
// Reached filesystem root
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
currentDir = parent;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const vitest_1 = require("vitest");
|
|
7
|
+
const config_1 = require("./config");
|
|
8
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
+
(0, vitest_1.describe)("loadConfig", () => {
|
|
10
|
+
const originalEnv = { ...process.env };
|
|
11
|
+
(0, vitest_1.afterEach)(() => {
|
|
12
|
+
process.env = { ...originalEnv };
|
|
13
|
+
});
|
|
14
|
+
(0, vitest_1.it)("returns defaults when no config sources are present", () => {
|
|
15
|
+
const config = (0, config_1.loadConfig)({});
|
|
16
|
+
(0, vitest_1.expect)(config.registryUrl).toBe("localhost:5000");
|
|
17
|
+
(0, vitest_1.expect)(config.coreUrl).toBe("http://localhost:8080");
|
|
18
|
+
(0, vitest_1.expect)(config.pluginDir).toBe(process.cwd());
|
|
19
|
+
});
|
|
20
|
+
(0, vitest_1.it)("prefers CLI flags over env vars", () => {
|
|
21
|
+
process.env.ALCEDO_CORE_URL = "http://env-url:8080";
|
|
22
|
+
const config = (0, config_1.loadConfig)({ coreUrl: "http://cli-url:8080" });
|
|
23
|
+
(0, vitest_1.expect)(config.coreUrl).toBe("http://cli-url:8080");
|
|
24
|
+
});
|
|
25
|
+
(0, vitest_1.it)("prefers env vars over defaults", () => {
|
|
26
|
+
process.env.ALCEDO_CORE_URL = "http://env-url:8080";
|
|
27
|
+
const config = (0, config_1.loadConfig)({});
|
|
28
|
+
(0, vitest_1.expect)(config.coreUrl).toBe("http://env-url:8080");
|
|
29
|
+
});
|
|
30
|
+
(0, vitest_1.it)("reads ALCEDO_REGISTRY_URL from env", () => {
|
|
31
|
+
process.env.ALCEDO_REGISTRY_URL = "my-registry:5000";
|
|
32
|
+
const config = (0, config_1.loadConfig)({});
|
|
33
|
+
(0, vitest_1.expect)(config.registryUrl).toBe("my-registry:5000");
|
|
34
|
+
});
|
|
35
|
+
(0, vitest_1.it)("reads ALCEDO_PLUGIN_DIR from env", () => {
|
|
36
|
+
process.env.ALCEDO_PLUGIN_DIR = "/custom/plugin/path";
|
|
37
|
+
const config = (0, config_1.loadConfig)({});
|
|
38
|
+
(0, vitest_1.expect)(config.pluginDir).toBe("/custom/plugin/path");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
(0, vitest_1.describe)("findAlcedorc", () => {
|
|
42
|
+
(0, vitest_1.it)("returns null when no .alcedorc file exists", () => {
|
|
43
|
+
// Use system temp dir where no .alcedorc should exist
|
|
44
|
+
const result = (0, config_1.findAlcedorc)(node_os_1.default.tmpdir());
|
|
45
|
+
(0, vitest_1.expect)(result).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=config.test.js.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const config_1 = require("./config");
|
|
9
|
+
const logger_1 = require("./utils/logger");
|
|
10
|
+
const init_core_1 = require("./commands/init-core");
|
|
11
|
+
const init_1 = require("./commands/init");
|
|
12
|
+
const deploy_1 = require("./commands/deploy");
|
|
13
|
+
const add_migration_1 = require("./commands/add-migration");
|
|
14
|
+
const migrate_1 = require("./commands/migrate");
|
|
15
|
+
const add_endpoint_1 = require("./commands/add-endpoint");
|
|
16
|
+
const add_page_1 = require("./commands/add-page");
|
|
17
|
+
const add_nav_item_1 = require("./commands/add-nav-item");
|
|
18
|
+
const connect_1 = require("./commands/connect");
|
|
19
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
20
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
21
|
+
const dev_1 = require("./commands/dev");
|
|
22
|
+
const publish_1 = require("./commands/publish");
|
|
23
|
+
// Read version from package.json
|
|
24
|
+
function getVersion() {
|
|
25
|
+
const pkgPath = node_path_1.default.resolve(__dirname, "../package.json");
|
|
26
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, "utf-8"));
|
|
27
|
+
return pkg.version;
|
|
28
|
+
}
|
|
29
|
+
const program = new commander_1.Command();
|
|
30
|
+
program
|
|
31
|
+
.name("alcedo")
|
|
32
|
+
.version(getVersion(), "-V, --version", "Output the version number")
|
|
33
|
+
.description("Alcedo plugin development CLI - scaffold, develop, and test plugins")
|
|
34
|
+
.hook("preAction", (thisCommand) => {
|
|
35
|
+
// Parse config before any action runs
|
|
36
|
+
// Walk parent chain to collect all options (including global flags)
|
|
37
|
+
const opts = {};
|
|
38
|
+
let cmd = thisCommand;
|
|
39
|
+
while (cmd) {
|
|
40
|
+
Object.assign(opts, cmd.opts());
|
|
41
|
+
cmd = cmd.parent;
|
|
42
|
+
}
|
|
43
|
+
const config = (0, config_1.loadConfig)(opts);
|
|
44
|
+
// Store config on the command for subcommands to access
|
|
45
|
+
thisCommand._alcedoConfig = config;
|
|
46
|
+
});
|
|
47
|
+
// Global options
|
|
48
|
+
program.option("-r, --registry-url <url>", "Docker registry URL (default: localhost:5000)");
|
|
49
|
+
program.option("-c, --core-url <url>", "core API URL (default: http://localhost:8080)");
|
|
50
|
+
program.option("-d, --plugin-dir <path>", "Plugin project directory (default: current directory)");
|
|
51
|
+
program.addCommand(init_1.initCommand);
|
|
52
|
+
program.addCommand(init_core_1.initCoreCommand);
|
|
53
|
+
program.addCommand(deploy_1.deployCommand);
|
|
54
|
+
program.addCommand(publish_1.publishCommand);
|
|
55
|
+
const addCommand = new commander_1.Command("add").description("Generate plugin components (migrations, endpoints, pages, nav-items)");
|
|
56
|
+
addCommand.addCommand(add_migration_1.addMigrationCommand);
|
|
57
|
+
addCommand.addCommand(add_endpoint_1.addEndpointCommand);
|
|
58
|
+
addCommand.addCommand(add_page_1.addPageCommand);
|
|
59
|
+
addCommand.addCommand(add_nav_item_1.addNavItemCommand);
|
|
60
|
+
program.addCommand(addCommand);
|
|
61
|
+
program.addCommand(migrate_1.migrateCommand);
|
|
62
|
+
program.addCommand(connect_1.connectCommand);
|
|
63
|
+
program.addCommand(dev_1.devCommand);
|
|
64
|
+
// Error handling: non-zero exit on errors (CLI-04)
|
|
65
|
+
// Catch unhandled promise rejections and exceptions
|
|
66
|
+
process.on("unhandledRejection", (reason) => {
|
|
67
|
+
(0, logger_1.error)(`Unhandled error: ${reason}`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
});
|
|
70
|
+
process.on("uncaughtException", (err) => {
|
|
71
|
+
(0, logger_1.error)(`Fatal error: ${err.message}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
74
|
+
async function main() {
|
|
75
|
+
await program.parseAsync(process.argv);
|
|
76
|
+
}
|
|
77
|
+
main().catch((err) => {
|
|
78
|
+
(0, logger_1.error)(`Fatal error: ${err.message}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const CORE_URL = process.env.CORE_URL || "http://localhost:8080";
|
|
5
|
+
const TEST_SLUG = process.env.TEST_PLUGIN_SLUG || "hello-world";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Health check — skip all tests if AlcedoCore is not reachable
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
let coreReachable = false;
|
|
10
|
+
(0, vitest_1.beforeAll)(async () => {
|
|
11
|
+
try {
|
|
12
|
+
const res = await fetch(`${CORE_URL}/health`, {
|
|
13
|
+
signal: AbortSignal.timeout(3000),
|
|
14
|
+
});
|
|
15
|
+
coreReachable = res.ok;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
coreReachable = false;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Test helpers — replicate CLI patterns without Commander overhead
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
async function apiCall(apiPath, options) {
|
|
25
|
+
const url = `${CORE_URL.replace(/\/$/, "")}${apiPath}`;
|
|
26
|
+
const res = await fetch(url, {
|
|
27
|
+
method: options?.method || "GET",
|
|
28
|
+
headers: { "Content-Type": "application/json" },
|
|
29
|
+
body: options?.body ? JSON.stringify(options.body) : undefined,
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const errBody = (await res
|
|
33
|
+
.json()
|
|
34
|
+
.catch(() => ({ error: res.statusText })));
|
|
35
|
+
throw new Error(errBody.error || `HTTP ${res.status}: ${res.statusText}`);
|
|
36
|
+
}
|
|
37
|
+
return res.json();
|
|
38
|
+
}
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Migration API tests (used by `alcedo migrate` command)
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
(0, vitest_1.describe)("Migration API", () => {
|
|
43
|
+
(0, vitest_1.it)("lists migrations for a plugin via GET /api/plugins/:slug/migrations", async () => {
|
|
44
|
+
if (!coreReachable)
|
|
45
|
+
return;
|
|
46
|
+
const migrations = await apiCall(`/api/plugins/${TEST_SLUG}/migrations`);
|
|
47
|
+
(0, vitest_1.expect)(Array.isArray(migrations)).toBe(true);
|
|
48
|
+
// Migration list should always return an array (possibly empty)
|
|
49
|
+
for (const m of migrations) {
|
|
50
|
+
(0, vitest_1.expect)(m.version).toBeTruthy();
|
|
51
|
+
(0, vitest_1.expect)(m.name).toBeTruthy();
|
|
52
|
+
(0, vitest_1.expect)(["applied", "pending"]).toContain(m.status);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Request log detail API tests (used by `alcedo dev replay` command)
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
(0, vitest_1.describe)("Request log detail API", () => {
|
|
60
|
+
(0, vitest_1.it)("returns 404 for non-existent request ID", async () => {
|
|
61
|
+
if (!coreReachable)
|
|
62
|
+
return;
|
|
63
|
+
const url = `${CORE_URL.replace(/\/$/, "")}/api/plugins/${TEST_SLUG}/logs/nonexistent-id/detail`;
|
|
64
|
+
const res = await fetch(url);
|
|
65
|
+
(0, vitest_1.expect)(res.status).toBe(404);
|
|
66
|
+
const body = (await res.json());
|
|
67
|
+
(0, vitest_1.expect)(body.error).toBeTruthy();
|
|
68
|
+
});
|
|
69
|
+
(0, vitest_1.it)("returns log entries listing via GET /api/plugins/:slug/logs", async () => {
|
|
70
|
+
if (!coreReachable)
|
|
71
|
+
return;
|
|
72
|
+
// The API wraps logs under { data: { logs: [...] } }
|
|
73
|
+
const res = await apiCall(`/api/plugins/${TEST_SLUG}/logs`);
|
|
74
|
+
(0, vitest_1.expect)(res.data).toBeDefined();
|
|
75
|
+
(0, vitest_1.expect)(Array.isArray(res.data.logs)).toBe(true);
|
|
76
|
+
if (res.data.logs.length > 0) {
|
|
77
|
+
const entry = res.data.logs[0];
|
|
78
|
+
(0, vitest_1.expect)(entry.request_uuid).toBeTruthy();
|
|
79
|
+
(0, vitest_1.expect)(entry.method).toBeTruthy();
|
|
80
|
+
(0, vitest_1.expect)(entry.path).toBeDefined();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Error handling tests
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
(0, vitest_1.describe)("Error handling", () => {
|
|
88
|
+
(0, vitest_1.it)("returns 404 for non-existent plugin slug", async () => {
|
|
89
|
+
if (!coreReachable)
|
|
90
|
+
return;
|
|
91
|
+
const url = `${CORE_URL.replace(/\/$/, "")}/api/plugins/nonexistent-plugin/logs`;
|
|
92
|
+
const res = await fetch(url);
|
|
93
|
+
(0, vitest_1.expect)(res.status).toBe(404);
|
|
94
|
+
});
|
|
95
|
+
(0, vitest_1.it)("handles network errors gracefully", async () => {
|
|
96
|
+
// Use a non-routable address to simulate network failure
|
|
97
|
+
const badUrl = "http://192.0.2.1:9999/api/dev/start";
|
|
98
|
+
try {
|
|
99
|
+
await fetch(badUrl, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: { "Content-Type": "application/json" },
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
slug: "test",
|
|
104
|
+
url: "http://localhost:3000",
|
|
105
|
+
}),
|
|
106
|
+
signal: AbortSignal.timeout(1000),
|
|
107
|
+
});
|
|
108
|
+
// Should not reach here
|
|
109
|
+
(0, vitest_1.expect)(true).toBe(false);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
(0, vitest_1.expect)(err).toBeTruthy();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
//# sourceMappingURL=cli-api.test.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.renderTemplate = renderTemplate;
|
|
7
|
+
exports.renderAndWrite = renderAndWrite;
|
|
8
|
+
exports.renderToString = renderToString;
|
|
9
|
+
const ejs_1 = __importDefault(require("ejs"));
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
/**
|
|
13
|
+
* Render an EJS template string with the given data.
|
|
14
|
+
* Returns the rendered string.
|
|
15
|
+
*/
|
|
16
|
+
function renderTemplate(templateContent, data) {
|
|
17
|
+
return ejs_1.default.render(templateContent, data);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Read an EJS template file, render it with data, and write to the target path.
|
|
21
|
+
* Creates parent directories if they don't exist.
|
|
22
|
+
* ONLY creates new files — never overwrites (GEN-05 compliance).
|
|
23
|
+
* Returns the target path on success, throws on error.
|
|
24
|
+
*/
|
|
25
|
+
function renderAndWrite(templatePath, targetPath, data) {
|
|
26
|
+
if (node_fs_1.default.existsSync(targetPath)) {
|
|
27
|
+
throw new Error(`Target file already exists: ${targetPath}. ` +
|
|
28
|
+
`Refusing to overwrite existing code (GEN-05).`);
|
|
29
|
+
}
|
|
30
|
+
const templateContent = node_fs_1.default.readFileSync(templatePath, "utf-8");
|
|
31
|
+
const rendered = renderTemplate(templateContent, data);
|
|
32
|
+
const dir = node_path_1.default.dirname(targetPath);
|
|
33
|
+
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
34
|
+
node_fs_1.default.writeFileSync(targetPath, rendered, "utf-8");
|
|
35
|
+
return targetPath;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Read an EJS template file and return the rendered content as a string
|
|
39
|
+
* (without writing to disk). Used for content that gets appended to existing files
|
|
40
|
+
* like manifest.json updates.
|
|
41
|
+
*/
|
|
42
|
+
function renderToString(templatePath, data) {
|
|
43
|
+
const templateContent = node_fs_1.default.readFileSync(templatePath, "utf-8");
|
|
44
|
+
return renderTemplate(templateContent, data);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=ejs-renderer.js.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const vitest_1 = require("vitest");
|
|
7
|
+
const ejs_renderer_js_1 = require("./ejs-renderer.js");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
11
|
+
(0, vitest_1.describe)("renderTemplate", () => {
|
|
12
|
+
(0, vitest_1.it)("renders a template string with variables", () => {
|
|
13
|
+
const template = "<h1><%= name %></h1>";
|
|
14
|
+
const result = (0, ejs_renderer_js_1.renderTemplate)(template, { name: "Test Plugin" });
|
|
15
|
+
(0, vitest_1.expect)(result).toBe("<h1>Test Plugin</h1>");
|
|
16
|
+
});
|
|
17
|
+
(0, vitest_1.it)("renders with conditionals", () => {
|
|
18
|
+
const template = "<% if (enabled) { %>enabled<% } else { %>disabled<% } %>";
|
|
19
|
+
(0, vitest_1.expect)((0, ejs_renderer_js_1.renderTemplate)(template, { enabled: true })).toBe("enabled");
|
|
20
|
+
(0, vitest_1.expect)((0, ejs_renderer_js_1.renderTemplate)(template, { enabled: false })).toBe("disabled");
|
|
21
|
+
});
|
|
22
|
+
(0, vitest_1.it)("renders with loops", () => {
|
|
23
|
+
const template = "<% items.forEach(function(item) { %><li><%= item %></li><% }) %>";
|
|
24
|
+
const result = (0, ejs_renderer_js_1.renderTemplate)(template, { items: ["a", "b", "c"] });
|
|
25
|
+
(0, vitest_1.expect)(result).toBe("<li>a</li><li>b</li><li>c</li>");
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)("renders empty string when variable value is empty string", () => {
|
|
28
|
+
const template = "<%= name %>";
|
|
29
|
+
(0, vitest_1.expect)((0, ejs_renderer_js_1.renderTemplate)(template, { name: "" })).toBe("");
|
|
30
|
+
});
|
|
31
|
+
(0, vitest_1.it)("renders JSON output correctly using unescaped output", () => {
|
|
32
|
+
// NOTE: Use <%- (unescaped) for JSON output because <%= HTML-escapes quotes
|
|
33
|
+
const template = '<%- JSON.stringify(data) %>';
|
|
34
|
+
const result = (0, ejs_renderer_js_1.renderTemplate)(template, { data: { slug: "test", version: "1.0" } });
|
|
35
|
+
(0, vitest_1.expect)(JSON.parse(result)).toEqual({ slug: "test", version: "1.0" });
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.describe)("renderAndWrite", () => {
|
|
39
|
+
const testDir = node_path_1.default.join(node_os_1.default.tmpdir(), `alcedo-test-ejs-${Date.now()}`);
|
|
40
|
+
const templatesDir = node_path_1.default.join(testDir, "templates");
|
|
41
|
+
(0, vitest_1.beforeAll)(() => {
|
|
42
|
+
node_fs_1.default.mkdirSync(templatesDir, { recursive: true });
|
|
43
|
+
});
|
|
44
|
+
(0, vitest_1.afterAll)(() => {
|
|
45
|
+
node_fs_1.default.rmSync(testDir, { recursive: true, force: true });
|
|
46
|
+
});
|
|
47
|
+
(0, vitest_1.it)("writes rendered output to target file", () => {
|
|
48
|
+
const templatePath = node_path_1.default.join(templatesDir, "greeting.ejs");
|
|
49
|
+
node_fs_1.default.writeFileSync(templatePath, "Hello, <%= name %>!", "utf-8");
|
|
50
|
+
const targetPath = node_path_1.default.join(testDir, "output.txt");
|
|
51
|
+
const result = (0, ejs_renderer_js_1.renderAndWrite)(templatePath, targetPath, { name: "World" });
|
|
52
|
+
(0, vitest_1.expect)(result).toBe(targetPath);
|
|
53
|
+
(0, vitest_1.expect)(node_fs_1.default.readFileSync(targetPath, "utf-8")).toBe("Hello, World!");
|
|
54
|
+
});
|
|
55
|
+
(0, vitest_1.it)("creates parent directories when needed", () => {
|
|
56
|
+
const templatePath = node_path_1.default.join(templatesDir, "greeting2.ejs");
|
|
57
|
+
node_fs_1.default.writeFileSync(templatePath, "<%= greeting %>", "utf-8");
|
|
58
|
+
const targetPath = node_path_1.default.join(testDir, "subdir", "nested", "output.txt");
|
|
59
|
+
const result = (0, ejs_renderer_js_1.renderAndWrite)(templatePath, targetPath, { greeting: "hi" });
|
|
60
|
+
(0, vitest_1.expect)(result).toBe(targetPath);
|
|
61
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(targetPath)).toBe(true);
|
|
62
|
+
(0, vitest_1.expect)(node_fs_1.default.readFileSync(targetPath, "utf-8")).toBe("hi");
|
|
63
|
+
});
|
|
64
|
+
(0, vitest_1.it)("throws when target file already exists (GEN-05)", () => {
|
|
65
|
+
const templatePath = node_path_1.default.join(templatesDir, "exists.ejs");
|
|
66
|
+
node_fs_1.default.writeFileSync(templatePath, "content", "utf-8");
|
|
67
|
+
const targetPath = node_path_1.default.join(testDir, "already-exists.txt");
|
|
68
|
+
node_fs_1.default.writeFileSync(targetPath, "original", "utf-8");
|
|
69
|
+
(0, vitest_1.expect)(() => (0, ejs_renderer_js_1.renderAndWrite)(templatePath, targetPath, {})).toThrow(/already exists/i);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
(0, vitest_1.describe)("renderToString", () => {
|
|
73
|
+
const testDir = node_path_1.default.join(node_os_1.default.tmpdir(), `alcedo-test-renderToString-${Date.now()}`);
|
|
74
|
+
(0, vitest_1.beforeAll)(() => {
|
|
75
|
+
node_fs_1.default.mkdirSync(testDir, { recursive: true });
|
|
76
|
+
});
|
|
77
|
+
(0, vitest_1.afterAll)(() => {
|
|
78
|
+
node_fs_1.default.rmSync(testDir, { recursive: true, force: true });
|
|
79
|
+
});
|
|
80
|
+
(0, vitest_1.it)("reads template file and returns rendered string", () => {
|
|
81
|
+
const templatePath = node_path_1.default.join(testDir, "template.ejs");
|
|
82
|
+
node_fs_1.default.writeFileSync(templatePath, "Hello, <%= name %>!", "utf-8");
|
|
83
|
+
const result = (0, ejs_renderer_js_1.renderToString)(templatePath, { name: "EJS" });
|
|
84
|
+
(0, vitest_1.expect)(result).toBe("Hello, EJS!");
|
|
85
|
+
});
|
|
86
|
+
(0, vitest_1.it)("renders JSON output from file template", () => {
|
|
87
|
+
const templatePath = node_path_1.default.join(testDir, "json.ejs");
|
|
88
|
+
node_fs_1.default.writeFileSync(templatePath, '<%- JSON.stringify(data) %>', "utf-8");
|
|
89
|
+
const result = (0, ejs_renderer_js_1.renderToString)(templatePath, { data: { key: "value" } });
|
|
90
|
+
(0, vitest_1.expect)(JSON.parse(result)).toEqual({ key: "value" });
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=ejs-renderer.test.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared naming/convention utilities used by CLI generator commands.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.toPascalCase = toPascalCase;
|
|
7
|
+
exports.toCamelCase = toCamelCase;
|
|
8
|
+
exports.derivePath = derivePath;
|
|
9
|
+
exports.deriveMethod = deriveMethod;
|
|
10
|
+
/**
|
|
11
|
+
* Convert a name to PascalCase for display labels and class names.
|
|
12
|
+
* "dashboard" -> "Dashboard"
|
|
13
|
+
* "user-management" -> "UserManagement"
|
|
14
|
+
* "get_users" -> "GetUsers"
|
|
15
|
+
*/
|
|
16
|
+
function toPascalCase(name) {
|
|
17
|
+
return name
|
|
18
|
+
.split(/[_-]/)
|
|
19
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
20
|
+
.join("");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Convert a name to camelCase for function/variable names.
|
|
24
|
+
* "get_users" -> "getUsers"
|
|
25
|
+
*/
|
|
26
|
+
function toCamelCase(name) {
|
|
27
|
+
const pascal = toPascalCase(name);
|
|
28
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Derive a URL path from an endpoint name.
|
|
32
|
+
* "get_users" -> "/api/get_users"
|
|
33
|
+
* "health" -> "/health"
|
|
34
|
+
*/
|
|
35
|
+
function derivePath(name) {
|
|
36
|
+
const normalized = name.replace(/[_-]/g, "_");
|
|
37
|
+
if (normalized.startsWith("get_") || normalized.startsWith("post_") ||
|
|
38
|
+
normalized.startsWith("put_") || normalized.startsWith("delete_") ||
|
|
39
|
+
normalized.startsWith("patch_")) {
|
|
40
|
+
return "/" + normalized.replace(/_/g, "/");
|
|
41
|
+
}
|
|
42
|
+
return "/api/" + normalized.replace(/_/g, "/");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Derive HTTP method from endpoint name.
|
|
46
|
+
* "get_users" -> "GET"
|
|
47
|
+
* "create_user" -> "POST"
|
|
48
|
+
* "health" -> "GET" (default)
|
|
49
|
+
*/
|
|
50
|
+
function deriveMethod(name) {
|
|
51
|
+
const prefix = name.split("_")[0].toLowerCase();
|
|
52
|
+
const methodMap = {
|
|
53
|
+
get: "GET",
|
|
54
|
+
post: "POST",
|
|
55
|
+
create: "POST",
|
|
56
|
+
put: "PUT",
|
|
57
|
+
update: "PUT",
|
|
58
|
+
delete: "DELETE",
|
|
59
|
+
remove: "DELETE",
|
|
60
|
+
patch: "PATCH",
|
|
61
|
+
list: "GET",
|
|
62
|
+
};
|
|
63
|
+
return methodMap[prefix] || "GET";
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=formatting.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateTimestamp = generateTimestamp;
|
|
4
|
+
/**
|
|
5
|
+
* Generate a timestamp string (same format as migration command).
|
|
6
|
+
*/
|
|
7
|
+
function generateTimestamp() {
|
|
8
|
+
const now = new Date();
|
|
9
|
+
const pad = (n, d = 2) => String(n).padStart(d, "0");
|
|
10
|
+
return (`${now.getFullYear()}` +
|
|
11
|
+
`${pad(now.getMonth() + 1)}` +
|
|
12
|
+
`${pad(now.getDate())}` +
|
|
13
|
+
`${pad(now.getHours())}` +
|
|
14
|
+
`${pad(now.getMinutes())}` +
|
|
15
|
+
`${pad(now.getSeconds())}`);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=generateTimestamp.js.map
|