@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.
Files changed (70) hide show
  1. package/LICENSE.md +102 -0
  2. package/dist/commands/add-endpoint.js +104 -0
  3. package/dist/commands/add-migration.js +80 -0
  4. package/dist/commands/add-nav-item.js +72 -0
  5. package/dist/commands/add-page.js +64 -0
  6. package/dist/commands/build-frontend.js +126 -0
  7. package/dist/commands/compile-pages.js +120 -0
  8. package/dist/commands/connect.js +122 -0
  9. package/dist/commands/deploy.js +74 -0
  10. package/dist/commands/dev.js +13 -0
  11. package/dist/commands/init-core.js +900 -0
  12. package/dist/commands/init.js +124 -0
  13. package/dist/commands/init.test.js +84 -0
  14. package/dist/commands/migrate.js +22 -0
  15. package/dist/commands/proxy.js +157 -0
  16. package/dist/commands/publish.js +81 -0
  17. package/dist/commands/replay.js +142 -0
  18. package/dist/commands/serve-frontend.js +92 -0
  19. package/dist/config.js +110 -0
  20. package/dist/config.test.js +48 -0
  21. package/dist/index.js +81 -0
  22. package/dist/integration/cli-api.test.js +116 -0
  23. package/dist/utils/ejs-renderer.js +46 -0
  24. package/dist/utils/ejs-renderer.test.js +93 -0
  25. package/dist/utils/formatting.js +65 -0
  26. package/dist/utils/generateTimestamp.js +17 -0
  27. package/dist/utils/logger.js +33 -0
  28. package/dist/utils/validation.js +15 -0
  29. package/package.json +41 -0
  30. package/src/commands/add-endpoint.ts +157 -0
  31. package/src/commands/add-migration.ts +102 -0
  32. package/src/commands/add-nav-item.ts +106 -0
  33. package/src/commands/add-page.ts +100 -0
  34. package/src/commands/build-frontend.ts +148 -0
  35. package/src/commands/connect.ts +98 -0
  36. package/src/commands/deploy.ts +85 -0
  37. package/src/commands/dev.ts +12 -0
  38. package/src/commands/init-core.ts +1019 -0
  39. package/src/commands/init.test.ts +92 -0
  40. package/src/commands/init.ts +171 -0
  41. package/src/commands/migrate.ts +20 -0
  42. package/src/commands/proxy.ts +206 -0
  43. package/src/commands/publish.ts +106 -0
  44. package/src/commands/serve-frontend.ts +103 -0
  45. package/src/config.test.ts +50 -0
  46. package/src/config.ts +125 -0
  47. package/src/index.ts +100 -0
  48. package/src/integration/cli-api.test.ts +143 -0
  49. package/src/utils/ejs-renderer.ts +55 -0
  50. package/src/utils/formatting.ts +62 -0
  51. package/src/utils/generateTimestamp.ts +16 -0
  52. package/src/utils/logger.ts +27 -0
  53. package/src/utils/validation.ts +13 -0
  54. package/templates/endpoint/handler.js.ejs +23 -0
  55. package/templates/endpoint/handler.py.ejs +23 -0
  56. package/templates/migration/down.sql.ejs +6 -0
  57. package/templates/migration/up.sql.ejs +11 -0
  58. package/templates/page/page.vue.ejs +63 -0
  59. package/templates/plugin/Dockerfile.ejs +13 -0
  60. package/templates/plugin/Dockerfile.node.ejs +14 -0
  61. package/templates/plugin/README.md.ejs +19 -0
  62. package/templates/plugin/gitignore.ejs +6 -0
  63. package/templates/plugin/manifest.json.ejs +18 -0
  64. package/templates/plugin/migrations/.gitkeep +0 -0
  65. package/templates/plugin/pages/.gitkeep +0 -0
  66. package/templates/plugin/public/.gitkeep +0 -0
  67. package/templates/plugin/server.js.ejs +27 -0
  68. package/templates/plugin/server.py.ejs +32 -0
  69. package/tsconfig.json +16 -0
  70. package/vitest.config.ts +14 -0
package/LICENSE.md ADDED
@@ -0,0 +1,102 @@
1
+ Licensor: AlcedoCore
2
+ Licensed Work: AlcedoCore © 2026 Bart Don
3
+ Additional Use Grant: You may use the Licensed Work provided that your
4
+ Total Finances do not exceed EUR 100,000 for the most
5
+ recent 12-month period.
6
+
7
+ "Total Finances" means the largest of your gross revenues
8
+ and/or funding received, regardless of source.
9
+
10
+ "You" and "your" include (without limitation) any individual
11
+ or entity agreeing to these terms and any affiliates of such
12
+ individual or entity.
13
+
14
+ AlcedoCore is not liable to you in any way arising out of
15
+ your use under this Additional Use Grant, including for any
16
+ general, special, incidental, or consequential damages.
17
+
18
+ Change Date: The fourth anniversary of the date on which the applicable
19
+ version of the Licensed Work was first publicly distributed.
20
+ Change License: GNU General Public License, version 3 or later (GPL-3.0-or-later)
21
+
22
+ For information about licensing arrangements, please visit:
23
+ https://alcedocore.nl/pricing
24
+
25
+ --------------------------------------------------------------------------------
26
+
27
+ Business Source License 1.1
28
+
29
+ Terms
30
+
31
+ The Licensor hereby grants you the right to copy, modify, create derivative
32
+ works, redistribute, and make non-production use of the Licensed Work. The
33
+ Licensor may make an Additional Use Grant, above, permitting limited production
34
+ use.
35
+
36
+ Effective on the Change Date, or the fourth anniversary of the first publicly
37
+ available distribution of a specific version of the Licensed Work under this
38
+ License, whichever comes first, the Licensor hereby grants you rights under the
39
+ terms of the Change License, and the rights granted in the paragraph above
40
+ terminate.
41
+
42
+ If your use of the Licensed Work does not comply with the requirements currently
43
+ in effect as described in this License, you must purchase a commercial license
44
+ from the Licensor, its affiliated entities, or authorized resellers, or you must
45
+ refrain from using the Licensed Work.
46
+
47
+ All copies of the original and modified Licensed Work, and derivative works of
48
+ the Licensed Work, are subject to this License. This License applies separately
49
+ for each version of the Licensed Work and the Change Date may vary for each
50
+ version of the Licensed Work released by Licensor.
51
+
52
+ You must conspicuously display this License on each original or modified copy of
53
+ the Licensed Work. If you receive the Licensed Work in original or modified form
54
+ from a third party, the terms and conditions set forth in this License apply to
55
+ your use of that work.
56
+
57
+ Any use of the Licensed Work in violation of this License will automatically
58
+ terminate your rights under this License for the current and all other versions
59
+ of the Licensed Work.
60
+
61
+ This License does not grant you any right in any trademark or logo of Licensor
62
+ or its affiliates (provided that you may use a trademark or logo of Licensor as
63
+ expressly required by this License).
64
+
65
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN
66
+ “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS
67
+ OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY,
68
+ FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE.
69
+
70
+ MariaDB hereby grants you permission to use this License’s text to license your
71
+ works, and to refer to it using the trademark “Business Source License”, as long
72
+ as you comply with the Covenants of Licensor below.
73
+
74
+ Covenants of Licensor
75
+
76
+ In consideration of the right to use this License’s text and the “Business
77
+ Source License” name and trademark, Licensor covenants to MariaDB, and to all
78
+ other recipients of the licensed work to be provided by Licensor:
79
+
80
+ 1. To specify as the Change License the GPL Version 2.0 or any later version,
81
+ or a license that is compatible with GPL Version 2.0 or a later version,
82
+ where “compatible” means that software provided under the Change License can
83
+ be included in a program with software provided under GPL Version 2.0 or a
84
+ later version. Licensor may specify additional Change Licenses without
85
+ limitation.
86
+
87
+ 2. To either: (a) specify an additional grant of rights to use that does not
88
+ impose any additional restriction on the right granted in this License, as
89
+ the Additional Use Grant; or (b) insert the text “None”.
90
+
91
+ 3. To specify a Change Date.
92
+
93
+ 4. Not to modify this License in any other way.
94
+
95
+ Notice
96
+
97
+ The Business Source License (this document, or the "License") is not an Open
98
+ Source license. However, the Licensed Work will eventually be made available
99
+ under an Open Source License, as stated in this License.
100
+
101
+ License text copyright © 2023 MariaDB plc, All Rights Reserved.
102
+ “Business Source License” is a trademark of MariaDB plc.
@@ -0,0 +1,104 @@
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.addEndpointCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const logger_1 = require("../utils/logger");
11
+ const ejs_renderer_1 = require("../utils/ejs-renderer");
12
+ const config_1 = require("../config");
13
+ const validation_1 = require("../utils/validation");
14
+ const formatting_1 = require("../utils/formatting");
15
+ const generateTimestamp_1 = require("../utils/generateTimestamp");
16
+ exports.addEndpointCommand = new commander_1.Command("endpoint")
17
+ .argument("<name>", "Endpoint name (e.g., get_users)")
18
+ .option("-m, --method <method>", "HTTP method (GET, POST, PUT, DELETE, PATCH)")
19
+ .option("-p, --path <path>", "URL path (e.g., /api/users)")
20
+ .description("Generate a handler stub and update manifest.json endpoints")
21
+ .action(async (name, options, cmd) => {
22
+ const config = (0, config_1.loadConfig)(cmd.optsWithGlobals());
23
+ (0, validation_1.assertSafeName)(name, "endpoint name");
24
+ const pluginDir = config.pluginDir || process.cwd();
25
+ const manifestPath = node_path_1.default.resolve(pluginDir, "manifest.json");
26
+ // Derive endpoint metadata
27
+ const VALID_METHODS = [
28
+ "GET",
29
+ "POST",
30
+ "PUT",
31
+ "DELETE",
32
+ "PATCH",
33
+ "HEAD",
34
+ "OPTIONS",
35
+ ];
36
+ const method = (options.method || (0, formatting_1.deriveMethod)(name)).toUpperCase();
37
+ if (!VALID_METHODS.includes(method)) {
38
+ throw new Error(`Invalid HTTP method "${method}". Valid methods: ${VALID_METHODS.join(", ")}`);
39
+ }
40
+ const urlPath = options.path || (0, formatting_1.derivePath)(name);
41
+ const className = (0, formatting_1.toPascalCase)(name);
42
+ const camelName = (0, formatting_1.toCamelCase)(name);
43
+ const timestamp = (0, generateTimestamp_1.generateTimestamp)();
44
+ // Detect language from existing server files
45
+ let language = "python";
46
+ if (node_fs_1.default.existsSync(node_path_1.default.join(pluginDir, "server.js"))) {
47
+ language = "node";
48
+ }
49
+ const templatesDir = node_path_1.default.resolve(__dirname, "../../templates");
50
+ const endpointsDir = node_path_1.default.resolve(pluginDir, "endpoints");
51
+ const spinner = (0, logger_1.createSpinner)(`Generating endpoint: ${name}`);
52
+ try {
53
+ // Create endpoints directory
54
+ node_fs_1.default.mkdirSync(endpointsDir, { recursive: true });
55
+ // Generate handler stub file
56
+ const handlerTemplate = language === "python" ? "handler.py.ejs" : "handler.js.ejs";
57
+ const handlerFilename = `${name}.${language === "python" ? "py" : "js"}`;
58
+ (0, ejs_renderer_1.renderAndWrite)(node_path_1.default.join(templatesDir, "endpoint", handlerTemplate), node_path_1.default.join(endpointsDir, handlerFilename), {
59
+ name,
60
+ className,
61
+ camelName,
62
+ method,
63
+ path: urlPath,
64
+ timestamp,
65
+ language,
66
+ });
67
+ // Update manifest.json endpoints array
68
+ if (!node_fs_1.default.existsSync(manifestPath)) {
69
+ throw new Error(`No manifest.json found at ${manifestPath} — skipping manifest update.`);
70
+ }
71
+ const manifest = JSON.parse(node_fs_1.default.readFileSync(manifestPath, "utf-8"));
72
+ // Initialize endpoints array if it doesn't exist
73
+ if (!manifest.endpoints) {
74
+ manifest.endpoints = [];
75
+ }
76
+ // Append new endpoint entry (never modify existing entries — GEN-05)
77
+ const newEntry = {
78
+ method,
79
+ path: urlPath,
80
+ description: `${camelName} endpoint`,
81
+ group: "api",
82
+ };
83
+ // Check for duplicate before appending (GEN-05 "never modify existing entries"
84
+ // also implies no duplicate entries for the same method + path)
85
+ const isDuplicate = manifest.endpoints.some((e) => e.method === method && e.path === urlPath);
86
+ if (isDuplicate) {
87
+ throw new Error(`Endpoint ${method} ${urlPath} already exists in manifest.json — skipping.`);
88
+ }
89
+ manifest.endpoints.push(newEntry);
90
+ // Write updated manifest.json back (preserving existing data)
91
+ node_fs_1.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
92
+ spinner.succeed();
93
+ (0, logger_1.success)(`Endpoint created:`);
94
+ (0, logger_1.info)(` endpoints/${handlerFilename}`);
95
+ (0, logger_1.info)(` manifest.json ← ${method} ${urlPath}`);
96
+ (0, logger_1.info)(`Import the handler in your main server file and add the route.`);
97
+ }
98
+ catch (err) {
99
+ spinner.fail();
100
+ (0, logger_1.error)(`Failed to generate endpoint: ${err.message}`);
101
+ process.exit(1);
102
+ }
103
+ });
104
+ //# sourceMappingURL=add-endpoint.js.map
@@ -0,0 +1,80 @@
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.addMigrationCommand = void 0;
7
+ exports.addMigrationAction = addMigrationAction;
8
+ const commander_1 = require("commander");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const logger_1 = require("../utils/logger");
12
+ const ejs_renderer_1 = require("../utils/ejs-renderer");
13
+ const config_1 = require("../config");
14
+ const validation_1 = require("../utils/validation");
15
+ const generateTimestamp_1 = require("../utils/generateTimestamp");
16
+ /**
17
+ * Derive a table name from migration name.
18
+ * Converts "create_users_table" → "users", "add_category_to_items" → "category"
19
+ * Falls back to a safe default if pattern doesn't match.
20
+ */
21
+ function deriveTableName(migrationName) {
22
+ // Pattern: create_{table_name}_table -> table_name
23
+ const createMatch = migrationName.match(/^create_(.+)_table$/);
24
+ if (createMatch)
25
+ return createMatch[1];
26
+ // Pattern: add_{column}_to_{table} -> table
27
+ const addMatch = migrationName.match(/^add_\w+_to_(.+)$/);
28
+ if (addMatch)
29
+ return addMatch[1];
30
+ // Fallback: use the migration name itself (already validated as safe by assertSafeName)
31
+ return migrationName;
32
+ }
33
+ /**
34
+ * Shared migration generation action. Creates a versioned SQL migration file pair
35
+ * (.up.sql + .down.sql) in the plugin's migrations/ directory.
36
+ * Can be used by both `alcedo add migration` and `alcedo migrate generate`.
37
+ */
38
+ async function addMigrationAction(name) {
39
+ const config = (0, config_1.loadConfig)();
40
+ (0, validation_1.assertSafeName)(name, "migration name");
41
+ const pluginDir = config.pluginDir || process.cwd();
42
+ const migrationsDir = node_path_1.default.resolve(pluginDir, "migrations");
43
+ if (!node_fs_1.default.existsSync(migrationsDir)) {
44
+ throw new Error(`No migrations/ directory found in ${pluginDir}. Run \`alcedo init\` first or create the directory.`);
45
+ }
46
+ const timestamp = (0, generateTimestamp_1.generateTimestamp)();
47
+ const tableName = deriveTableName(name);
48
+ const templatesDir = node_path_1.default.resolve(__dirname, "../../templates");
49
+ const data = {
50
+ name,
51
+ timestamp,
52
+ tableName,
53
+ };
54
+ const upFilename = `${timestamp}_${name}.up.sql`;
55
+ const downFilename = `${timestamp}_${name}.down.sql`;
56
+ const spinner = (0, logger_1.createSpinner)(`Generating migration: ${name}`);
57
+ try {
58
+ // Generate up migration
59
+ (0, ejs_renderer_1.renderAndWrite)(node_path_1.default.join(templatesDir, "migration", "up.sql.ejs"), node_path_1.default.join(migrationsDir, upFilename), data);
60
+ // Generate down migration
61
+ (0, ejs_renderer_1.renderAndWrite)(node_path_1.default.join(templatesDir, "migration", "down.sql.ejs"), node_path_1.default.join(migrationsDir, downFilename), data);
62
+ spinner.succeed();
63
+ (0, logger_1.success)(`Migration created:`);
64
+ (0, logger_1.info)(` ${upFilename}`);
65
+ (0, logger_1.info)(` ${downFilename}`);
66
+ (0, logger_1.info)(`Edit these files to add your SQL logic.`);
67
+ }
68
+ catch (err) {
69
+ spinner.fail();
70
+ (0, logger_1.error)(`Failed to generate migration: ${err.message}`);
71
+ process.exit(1);
72
+ }
73
+ }
74
+ exports.addMigrationCommand = new commander_1.Command("migration")
75
+ .argument("<name>", "Migration name (e.g., create_users_table)")
76
+ .description("Generate a versioned SQL migration file pair (.up.sql + .down.sql)")
77
+ .action(async (name) => {
78
+ await addMigrationAction(name);
79
+ });
80
+ //# sourceMappingURL=add-migration.js.map
@@ -0,0 +1,72 @@
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.addNavItemCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const logger_1 = require("../utils/logger");
11
+ const config_1 = require("../config");
12
+ const validation_1 = require("../utils/validation");
13
+ const formatting_1 = require("../utils/formatting");
14
+ exports.addNavItemCommand = new commander_1.Command("nav-item")
15
+ .argument("<name>", "Nav item name (e.g., settings, user-management)")
16
+ .option("-p, --path <path>", "Destination URL path (default: /{name})")
17
+ .option("-i, --icon <icon>", "Material Symbol icon name (default: page)")
18
+ .option("--parent <parent>", "Parent nav-item path for nesting")
19
+ .option("-b, --badge <badge>", "Badge text for the nav item")
20
+ .description("Add a sidebar navigation entry to manifest.json (no file created)")
21
+ .action(async (name, options, cmd) => {
22
+ const config = (0, config_1.loadConfig)(cmd.optsWithGlobals());
23
+ (0, validation_1.assertSafeName)(name, "nav-item name");
24
+ const pluginDir = config.pluginDir || process.cwd();
25
+ const manifestPath = node_path_1.default.resolve(pluginDir, "manifest.json");
26
+ // Derive nav-item metadata
27
+ const label = (0, formatting_1.toPascalCase)(name);
28
+ const targetPath = options.path || "/" + name;
29
+ const icon = options.icon || "page";
30
+ const parent = options.parent;
31
+ const badge = options.badge;
32
+ const spinner = (0, logger_1.createSpinner)(`Adding nav-item: ${name}`);
33
+ try {
34
+ // Nav-item is manifest-only — no file created
35
+ if (!node_fs_1.default.existsSync(manifestPath)) {
36
+ throw new Error(`No manifest.json found at ${manifestPath} — skipping.`);
37
+ }
38
+ const manifest = JSON.parse(node_fs_1.default.readFileSync(manifestPath, "utf-8"));
39
+ // Initialize pages array if it doesn't exist
40
+ if (!manifest.pages) {
41
+ manifest.pages = [];
42
+ }
43
+ // Check for duplicate before appending
44
+ const isDuplicate = manifest.pages.some((e) => e.path === targetPath);
45
+ if (isDuplicate) {
46
+ throw new Error(`Nav-item at ${targetPath} already exists in manifest.json — skipping.`);
47
+ }
48
+ // Construct nav-entry as a plain JS object (avoids JSON injection via EJS)
49
+ const navEntry = {
50
+ label,
51
+ path: targetPath,
52
+ icon,
53
+ sidebar: true,
54
+ };
55
+ if (parent)
56
+ navEntry.parent = parent;
57
+ if (badge)
58
+ navEntry.badge = badge;
59
+ manifest.pages.push(navEntry);
60
+ // Write updated manifest.json back (preserving existing data)
61
+ node_fs_1.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
62
+ spinner.succeed();
63
+ (0, logger_1.success)(`Nav-item added:`);
64
+ (0, logger_1.info)(` manifest.json ← ${label} (${targetPath})`);
65
+ }
66
+ catch (err) {
67
+ spinner.fail();
68
+ (0, logger_1.error)(`Failed to add nav-item: ${err.message}`);
69
+ process.exit(1);
70
+ }
71
+ });
72
+ //# sourceMappingURL=add-nav-item.js.map
@@ -0,0 +1,64 @@
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.addPageCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const logger_1 = require("../utils/logger");
11
+ const ejs_renderer_1 = require("../utils/ejs-renderer");
12
+ const config_1 = require("../config");
13
+ const validation_1 = require("../utils/validation");
14
+ const formatting_1 = require("../utils/formatting");
15
+ exports.addPageCommand = new commander_1.Command("page")
16
+ .argument("<name>", "Page name (e.g., dashboard)")
17
+ .option("-r, --route <path>", "URL path (e.g., /my-page)")
18
+ .option("-i, --icon <icon>", "Material Symbol icon name")
19
+ .description("Generate a Vue 3 page component and update manifest.json")
20
+ .action(async (name, options, cmd) => {
21
+ const config = (0, config_1.loadConfig)(cmd.optsWithGlobals());
22
+ (0, validation_1.assertSafeName)(name, "page name");
23
+ const pluginDir = config.pluginDir || process.cwd();
24
+ const manifestPath = node_path_1.default.resolve(pluginDir, "manifest.json");
25
+ // Derive page metadata
26
+ const label = (0, formatting_1.toPascalCase)(name);
27
+ const route = options.route || "/" + name;
28
+ const icon = options.icon || "page";
29
+ const templatesDir = node_path_1.default.resolve(__dirname, "../../templates");
30
+ const pagesDir = node_path_1.default.resolve(pluginDir, "pages");
31
+ const spinner = (0, logger_1.createSpinner)(`Generating page: ${name}`);
32
+ try {
33
+ if (!node_fs_1.default.existsSync(manifestPath)) {
34
+ throw new Error(`No manifest.json found at ${manifestPath} — skipping manifest update.`);
35
+ }
36
+ (0, ejs_renderer_1.renderAndWrite)(node_path_1.default.join(templatesDir, "page", "page.vue.ejs"), node_path_1.default.join(pagesDir, `${name}.vue`), { name, label, route, icon });
37
+ const manifest = JSON.parse(node_fs_1.default.readFileSync(manifestPath, "utf-8"));
38
+ // Initialize pages array if it doesn't exist
39
+ if (!manifest.pages) {
40
+ manifest.pages = [];
41
+ }
42
+ // Check for duplicate before appending (GEN-05)
43
+ const isDuplicate = manifest.pages.some((e) => e.path === route);
44
+ if (isDuplicate) {
45
+ spinner.succeed();
46
+ (0, logger_1.info)(`Page ${route} already exists in manifest.json — skipping.`);
47
+ return;
48
+ }
49
+ manifest.pages.push({ label, path: route, sidebar: true });
50
+ // Write updated manifest.json back (preserving existing data)
51
+ node_fs_1.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
52
+ spinner.succeed();
53
+ (0, logger_1.success)(`Page created:`);
54
+ (0, logger_1.info)(` pages/${name}.vue`);
55
+ (0, logger_1.info)(` manifest.json ← ${label} (${route})`);
56
+ (0, logger_1.info)(`Register this page in pages/main.ts by importing and adding to the pages array.`);
57
+ }
58
+ catch (err) {
59
+ spinner.fail();
60
+ (0, logger_1.error)(`Failed to generate page: ${err.message}`);
61
+ process.exit(1);
62
+ }
63
+ });
64
+ //# sourceMappingURL=add-page.js.map
@@ -0,0 +1,126 @@
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.currentImportsGlobalVue = exports.buildFrontendCommand = void 0;
7
+ exports.buildFrontendFiles = buildFrontendFiles;
8
+ const esbuild_1 = __importDefault(require("esbuild"));
9
+ // @ts-ignore - unplugin-vue exports ./esbuild but types are not fully resolved
10
+ const esbuild_2 = __importDefault(require("unplugin-vue/esbuild"));
11
+ const esbuild_plugin_postcss_1 = __importDefault(require("@chialab/esbuild-plugin-postcss"));
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const commander_1 = require("commander");
15
+ exports.buildFrontendCommand = new commander_1.Command("build-frontend")
16
+ .description("Compile the frontend code into a dist directory")
17
+ .action(async (opts, cmd) => {
18
+ buildProdFiles().catch((err) => {
19
+ console.error(`[page-compiler] Failed:`, err);
20
+ process.exit(1);
21
+ });
22
+ });
23
+ const compilerDir = path_1.default.dirname(process.cwd());
24
+ const compilerRootDir = path_1.default.join(compilerDir, "..");
25
+ const compilerNodeModules = path_1.default.join(compilerRootDir, "node_modules");
26
+ function parseArgs() {
27
+ let plugin = "./pages";
28
+ let output = "./pages/dist";
29
+ for (let i = 0; i < process.argv.length; i++) {
30
+ const arg = process.argv[i];
31
+ if (arg === "--plugin" && i + 1 < process.argv.length) {
32
+ plugin = process.argv[i + 1];
33
+ }
34
+ else if (arg.startsWith("--plugin=")) {
35
+ plugin = arg.split("=")[1];
36
+ }
37
+ if (arg === "--output" && i + 1 < process.argv.length) {
38
+ output = process.argv[i + 1];
39
+ }
40
+ else if (arg.startsWith("--output=")) {
41
+ output = arg.split("=")[1];
42
+ }
43
+ }
44
+ return { plugin, output };
45
+ }
46
+ exports.currentImportsGlobalVue = new Set();
47
+ let importingModules = "createTextVNode,defineComponent,toDisplayString,createElementVNode,openBlock,createElementBlock,Fragment,renderSlot,createCommentVNode,useModel,vModelText,vModelSelect,withDirectives,withKeys,normalizeClass,renderList,computed,ref,onMounted,normalizeStyle,createComment,resolveComponent,resolveDirective,withCtx,toHandlers,mergeProps,createSlots,withScopeId,createStaticVNode,createBlock,popScopeId,pushScopeId,isRef,unref,isReactive,toRef,toRefs,isProxy,isReadonly,shallowRef,triggerRef,customRef,markRaw,toRaw,reactive,readonly,watch,watchEffect,watchPostEffect,watchSyncEffect,provide,inject,getCurrentInstance,h,nextTick,onBeforeMount,onBeforeUpdate,onBeforeUnmount,onUpdated,onUnmounted,onActivated,onDeactivated,onErrorCaptured,onRenderTracked,onRenderTriggered,isVNode,cloneVNode,createVNode,Transition,TransitionGroup,Teleport,Suspense,KeepAlive,defineAsyncComponent,defineEmits,defineExpose,defineProps,withDefaults,useAttrs,useSlots,useCssModule,useCssVars,EffectScope,effectScope,getCurrentScope,onScopeDispose,useId,resolveDynamicComponent,normalizeProps,withModifiers,vShow";
48
+ exports.currentImportsGlobalVue = new Set(importingModules.split(","));
49
+ const vueGlobalPlugin = {
50
+ name: "vue-global",
51
+ setup(build) {
52
+ build.onResolve({ filter: /^vue$/ }, async (args) => {
53
+ let source = await node_fs_1.default.promises.readFile(args.importer, "utf8");
54
+ const matchingImports = source.match(/import\s*{\s*([^}]*)\s*}\s*from\s*["']vue["']/) ||
55
+ [];
56
+ // console.log(matchingImports);
57
+ if (matchingImports[1]) {
58
+ importingModules = matchingImports[1] + "," + importingModules;
59
+ for (const imp of importingModules.split(",")) {
60
+ exports.currentImportsGlobalVue.add(imp.trim());
61
+ }
62
+ }
63
+ return {
64
+ path: args.path,
65
+ namespace: "vue-global",
66
+ };
67
+ });
68
+ build.onLoad({ filter: /.*/, namespace: "vue-global" }, () => {
69
+ return {
70
+ contents: `
71
+ export const { ${[...exports.currentImportsGlobalVue].join(",")} } = window.vue;
72
+ export default window.vue;
73
+ `,
74
+ loader: "js",
75
+ };
76
+ });
77
+ },
78
+ };
79
+ async function buildProdFiles() {
80
+ const args = parseArgs();
81
+ const outputDir = path_1.default.resolve(process.cwd(), args.output);
82
+ const pluginPath = path_1.default.resolve(process.cwd(), args.plugin);
83
+ const mainPath = path_1.default.join(pluginPath, "main.ts");
84
+ await buildFrontendFiles(outputDir);
85
+ console.log(`[page-compiler] Compiling pages from: ${pluginPath}`);
86
+ console.log(`[page-compiler] Output directory: ${outputDir}`);
87
+ if (!node_fs_1.default.existsSync(mainPath)) {
88
+ console.error(`[page-compiler] Entry point not found: ${mainPath}`);
89
+ process.exit(1);
90
+ }
91
+ await node_fs_1.default.promises.mkdir(outputDir, { recursive: true });
92
+ const mainJsPath = path_1.default.join(outputDir, "main.js");
93
+ const pluginPagesJsPath = path_1.default.join(outputDir, "plugin-pages.js");
94
+ if (node_fs_1.default.existsSync(mainJsPath)) {
95
+ await node_fs_1.default.promises.rename(mainJsPath, pluginPagesJsPath);
96
+ console.log(`[page-compiler] Renamed main.js -> plugin-pages.js`);
97
+ }
98
+ console.log(`[page-compiler] Build complete`);
99
+ }
100
+ async function buildFrontendFiles(outputDir) {
101
+ const args = parseArgs();
102
+ const pluginPath = path_1.default.resolve(process.cwd(), args.plugin);
103
+ const mainPath = path_1.default.join(pluginPath, "main.ts");
104
+ const buildOptions = {
105
+ entryPoints: [mainPath],
106
+ bundle: true,
107
+ format: "esm",
108
+ target: "es2020",
109
+ platform: "browser",
110
+ sourcemap: outputDir == false ? undefined : false,
111
+ minify: outputDir == false ? false : true,
112
+ write: outputDir == false ? false : undefined,
113
+ outfile: outputDir == false ? ".temp/bundle.js" : undefined,
114
+ outdir: typeof outputDir == "string" ? outputDir : undefined,
115
+ plugins: [(0, esbuild_plugin_postcss_1.default)(), vueGlobalPlugin, (0, esbuild_2.default)({ sourceMap: false })],
116
+ alias: {
117
+ vue: path_1.default.join(compilerNodeModules, "vue", "dist", "vue.esm-bundler.js"),
118
+ },
119
+ loader: {
120
+ ".css": "css",
121
+ },
122
+ external: ["vue"],
123
+ };
124
+ return await esbuild_1.default.build(buildOptions);
125
+ }
126
+ //# sourceMappingURL=build-frontend.js.map