@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
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
|