@fireberry/cli 0.0.5-beta.9 → 0.2.1
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/CLAUDE.md +156 -0
- package/LICENSE +21 -0
- package/dist/api/axios.js +1 -1
- package/dist/api/requests.d.ts +7 -3
- package/dist/api/requests.js +43 -6
- package/dist/api/types.d.ts +34 -3
- package/dist/bin/fireberry.js +23 -0
- package/dist/commands/create.js +6 -2
- package/dist/commands/debug.d.ts +3 -0
- package/dist/commands/debug.js +54 -0
- package/dist/commands/delete.d.ts +1 -0
- package/dist/commands/delete.js +31 -0
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +17 -0
- package/dist/commands/push.js +2 -2
- package/dist/constants/component-types.d.ts +7 -0
- package/dist/constants/component-types.js +6 -0
- package/dist/constants/height-options.d.ts +2 -0
- package/dist/constants/height-options.js +1 -0
- package/dist/utils/components.utils.d.ts +4 -3
- package/dist/utils/components.utils.js +98 -11
- package/package.json +1 -1
- package/src/api/axios.ts +1 -1
- package/src/api/requests.ts +54 -7
- package/src/api/types.ts +46 -3
- package/src/bin/fireberry.ts +26 -0
- package/src/commands/create.ts +7 -3
- package/src/commands/debug.ts +84 -0
- package/src/commands/delete.ts +40 -0
- package/src/commands/install.ts +23 -0
- package/src/commands/push.ts +2 -2
- package/src/constants/component-types.ts +10 -0
- package/src/constants/height-options.ts +3 -0
- package/src/templates/manifest.yml +6 -2
- package/src/utils/components.utils.ts +179 -17
|
@@ -4,10 +4,13 @@ import yaml from "js-yaml";
|
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import * as tar from "tar";
|
|
6
6
|
import os from "node:os";
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { COMPONENT_TYPE } from "../constants/component-types.js";
|
|
8
|
+
import { HEIGHT_OPTIONS } from "../constants/height-options.js";
|
|
9
|
+
export const getManifest = async (basePath) => {
|
|
10
|
+
const manifestPath = path.join(basePath || process.cwd(), "manifest.yml");
|
|
11
|
+
const searchDir = basePath || process.cwd();
|
|
9
12
|
if (!(await fs.pathExists(manifestPath))) {
|
|
10
|
-
throw new Error(`No manifest.yml found at ${chalk.yellow(
|
|
13
|
+
throw new Error(`No manifest.yml found at ${chalk.yellow(searchDir)}.\n` +
|
|
11
14
|
`Please run this command from your Fireberry app directory.`);
|
|
12
15
|
}
|
|
13
16
|
const manifestContent = await fs.readFile(manifestPath, "utf-8");
|
|
@@ -20,6 +23,82 @@ export const getManifest = async () => {
|
|
|
20
23
|
}
|
|
21
24
|
return manifest;
|
|
22
25
|
};
|
|
26
|
+
const validateRecordComponentSettings = (comp) => {
|
|
27
|
+
const settings = comp.settings;
|
|
28
|
+
if (!settings) {
|
|
29
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) is missing required settings`);
|
|
30
|
+
}
|
|
31
|
+
const requiredFields = [
|
|
32
|
+
"iconName",
|
|
33
|
+
"iconColor",
|
|
34
|
+
"objectType",
|
|
35
|
+
"height",
|
|
36
|
+
];
|
|
37
|
+
for (const fieldName of requiredFields) {
|
|
38
|
+
if (settings[fieldName] === undefined || settings[fieldName] === null) {
|
|
39
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) is missing required setting: ${fieldName}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (typeof settings.iconName !== "string") {
|
|
43
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) setting "iconName" must be a string`);
|
|
44
|
+
}
|
|
45
|
+
if (typeof settings.iconColor !== "string") {
|
|
46
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) setting "iconColor" must be a string`);
|
|
47
|
+
}
|
|
48
|
+
if (typeof settings.objectType !== "number") {
|
|
49
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) setting "objectType" must be a number`);
|
|
50
|
+
}
|
|
51
|
+
if (!settings.height) {
|
|
52
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) setting "height" must be one of: ${HEIGHT_OPTIONS.join(" | ")}`);
|
|
53
|
+
}
|
|
54
|
+
if (!HEIGHT_OPTIONS.includes(settings.height)) {
|
|
55
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) setting "height" must be one of: ${HEIGHT_OPTIONS.join(" | ")}`);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const validateGlobalMenuComponentSettings = (comp) => {
|
|
59
|
+
const settings = comp.settings;
|
|
60
|
+
if (!settings) {
|
|
61
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.GLOBAL_MENU}) is missing required settings`);
|
|
62
|
+
}
|
|
63
|
+
if (!settings.displayName) {
|
|
64
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.GLOBAL_MENU}) is missing required setting: displayName`);
|
|
65
|
+
}
|
|
66
|
+
if (typeof settings.displayName !== "string") {
|
|
67
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.GLOBAL_MENU}) setting "displayName" must be a string`);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const validateSideMenuComponentSettings = (comp) => {
|
|
71
|
+
const settings = comp.settings;
|
|
72
|
+
if (!settings) {
|
|
73
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.SIDE_MENU}) is missing required settings`);
|
|
74
|
+
}
|
|
75
|
+
if (!settings.icon) {
|
|
76
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.SIDE_MENU}) setting "icon" must be a string`);
|
|
77
|
+
}
|
|
78
|
+
if (!settings.width) {
|
|
79
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.SIDE_MENU}) setting "width" must be a S | M | L`);
|
|
80
|
+
}
|
|
81
|
+
if (settings.width !== "S" &&
|
|
82
|
+
settings.width !== "M" &&
|
|
83
|
+
settings.width !== "L") {
|
|
84
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.SIDE_MENU}) setting "width" must be a S | M | L`);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const validateComponentSettings = (comp) => {
|
|
88
|
+
switch (comp.type) {
|
|
89
|
+
case COMPONENT_TYPE.RECORD:
|
|
90
|
+
validateRecordComponentSettings(comp);
|
|
91
|
+
break;
|
|
92
|
+
case COMPONENT_TYPE.GLOBAL_MENU:
|
|
93
|
+
validateGlobalMenuComponentSettings(comp);
|
|
94
|
+
break;
|
|
95
|
+
case COMPONENT_TYPE.SIDE_MENU:
|
|
96
|
+
validateSideMenuComponentSettings(comp);
|
|
97
|
+
break;
|
|
98
|
+
default:
|
|
99
|
+
throw new Error(`Component "${comp.title}" has unsupported type: ${comp.type}. Supported types: ${COMPONENT_TYPE.RECORD}, ${COMPONENT_TYPE.GLOBAL_MENU}`);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
23
102
|
export const validateComponentBuild = async (componentPath, comp) => {
|
|
24
103
|
if (!(await fs.pathExists(componentPath))) {
|
|
25
104
|
throw new Error(`Component "${comp.title}" path does not exist: ${chalk.yellow(componentPath)}\n` + `Make sure the path in manifest.yml is correct.`);
|
|
@@ -28,9 +107,10 @@ export const validateComponentBuild = async (componentPath, comp) => {
|
|
|
28
107
|
if (stats.isDirectory()) {
|
|
29
108
|
const files = await fs.readdir(componentPath);
|
|
30
109
|
if (files.length === 0) {
|
|
31
|
-
throw new Error(`Component <${comp.
|
|
110
|
+
throw new Error(`Component <${comp.id}> at: /${comp.path} not found`);
|
|
32
111
|
}
|
|
33
112
|
}
|
|
113
|
+
validateComponentSettings(comp);
|
|
34
114
|
};
|
|
35
115
|
export const zipComponentBuild = async (componentPath, title) => {
|
|
36
116
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "fireberry-"));
|
|
@@ -64,23 +144,30 @@ export const zipComponentBuild = async (componentPath, title) => {
|
|
|
64
144
|
throw error;
|
|
65
145
|
}
|
|
66
146
|
};
|
|
67
|
-
export const
|
|
147
|
+
export const validateManifestComponents = async (manifest) => {
|
|
68
148
|
const components = manifest.components;
|
|
69
149
|
if (!components || components.length === 0) {
|
|
70
|
-
|
|
150
|
+
throw new Error("No components found in manifest");
|
|
71
151
|
}
|
|
72
|
-
const
|
|
73
|
-
if (new Set(
|
|
74
|
-
throw new Error("All component
|
|
152
|
+
const ids = components.map((comp) => comp.id);
|
|
153
|
+
if (new Set(ids).size !== ids.length) {
|
|
154
|
+
throw new Error("All component ids must be unique");
|
|
75
155
|
}
|
|
76
|
-
const zippedComponents = [];
|
|
77
156
|
for (const comp of components) {
|
|
78
157
|
const componentPath = path.join(process.cwd(), comp.path);
|
|
79
158
|
await validateComponentBuild(componentPath, comp);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
export const handleComponents = async (manifest) => {
|
|
162
|
+
await validateManifestComponents(manifest);
|
|
163
|
+
const components = manifest.components;
|
|
164
|
+
const zippedComponents = [];
|
|
165
|
+
for (const comp of components) {
|
|
166
|
+
const componentPath = path.join(process.cwd(), comp.path);
|
|
80
167
|
const buildBuffer = await zipComponentBuild(componentPath, comp.title);
|
|
81
168
|
zippedComponents.push({
|
|
82
169
|
title: comp.title,
|
|
83
|
-
|
|
170
|
+
id: comp.id,
|
|
84
171
|
build: buildBuffer,
|
|
85
172
|
});
|
|
86
173
|
}
|
package/package.json
CHANGED
package/src/api/axios.ts
CHANGED
|
@@ -59,7 +59,7 @@ export async function sendApiRequest<T = any>(
|
|
|
59
59
|
|
|
60
60
|
switch (status) {
|
|
61
61
|
case 401:
|
|
62
|
-
console.log(error.response?.data?.message || error.message);
|
|
62
|
+
console.log(error.response?.data?.message || error.message); // TODO: remove this
|
|
63
63
|
errorMessage = "Unauthorized user.";
|
|
64
64
|
break;
|
|
65
65
|
case 500:
|
package/src/api/requests.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import "../config/env.js";
|
|
2
|
+
import { BASE_SERVICE_URL } from "../constants/component-types.js";
|
|
2
3
|
import { api } from "./axios.js";
|
|
3
|
-
import type {
|
|
4
|
+
import type { Manifest, ZippedComponent } from "./types.js";
|
|
4
5
|
|
|
5
|
-
export const createApp = async (
|
|
6
|
-
const url =
|
|
6
|
+
export const createApp = async (manifest: Manifest): Promise<void> => {
|
|
7
|
+
const url = `${BASE_SERVICE_URL}/create`;
|
|
7
8
|
try {
|
|
8
|
-
await api.post<void>(url,
|
|
9
|
+
await api.post<void>(url, { manifest });
|
|
9
10
|
} catch (error) {
|
|
10
11
|
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
|
11
12
|
}
|
|
@@ -13,11 +14,57 @@ export const createApp = async (data: CreateAppRequest): Promise<void> => {
|
|
|
13
14
|
|
|
14
15
|
export const pushComponents = async (
|
|
15
16
|
appId: string,
|
|
16
|
-
components: ZippedComponent[]
|
|
17
|
+
components: ZippedComponent[],
|
|
18
|
+
manifest: Manifest
|
|
17
19
|
): Promise<void> => {
|
|
18
|
-
const url =
|
|
20
|
+
const url = `${BASE_SERVICE_URL}/push`;
|
|
19
21
|
try {
|
|
20
|
-
await api.post<void>(url, { appId, components });
|
|
22
|
+
await api.post<void>(url, { appId, components, manifest });
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const installApp = async (manifest: Manifest): Promise<void> => {
|
|
29
|
+
const url = `${BASE_SERVICE_URL}/install`;
|
|
30
|
+
try {
|
|
31
|
+
await api.post<void>(url, { manifest }, { timeout: 300000 }); // 5 minutes
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const deleteApp = async (manifest: Manifest): Promise<void> => {
|
|
38
|
+
const url = `${BASE_SERVICE_URL}/delete`;
|
|
39
|
+
try {
|
|
40
|
+
await api.delete<void>(url, { manifest });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const startDebug = async (
|
|
47
|
+
appId: string,
|
|
48
|
+
componentId: string,
|
|
49
|
+
debugUrl: string,
|
|
50
|
+
manifest: Manifest
|
|
51
|
+
): Promise<void> => {
|
|
52
|
+
const url = `${BASE_SERVICE_URL}/debug`;
|
|
53
|
+
try {
|
|
54
|
+
await api.post<void>(url, { appId, componentId, debugUrl, manifest });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const stopDebug = async (
|
|
61
|
+
appId: string,
|
|
62
|
+
componentId: string,
|
|
63
|
+
manifest: Manifest
|
|
64
|
+
): Promise<void> => {
|
|
65
|
+
const url = `${BASE_SERVICE_URL}/debug`;
|
|
66
|
+
try {
|
|
67
|
+
await api.delete<void>(url, { appId, componentId, manifest });
|
|
21
68
|
} catch (error) {
|
|
22
69
|
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
|
23
70
|
}
|
package/src/api/types.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { COMPONENT_TYPE } from "../constants/component-types.js";
|
|
2
|
+
import { HeightOption } from "../constants/height-options.js";
|
|
3
|
+
|
|
1
4
|
export interface CreateAppRequest {
|
|
2
5
|
appId: string;
|
|
6
|
+
componentId: string;
|
|
3
7
|
}
|
|
4
8
|
|
|
5
9
|
export interface ApiError {
|
|
@@ -20,10 +24,49 @@ interface ManifestApp {
|
|
|
20
24
|
description?: string;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
export interface
|
|
27
|
+
export interface RecordComponentSettings {
|
|
28
|
+
iconName: string;
|
|
29
|
+
iconColor: string;
|
|
30
|
+
objectType: number;
|
|
31
|
+
height: HeightOption;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface GlobalMenuComponentSettings {
|
|
35
|
+
displayName: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SideMenuComponentSettings {
|
|
39
|
+
icon: string;
|
|
40
|
+
width: "S" | "M" | "L";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type ComponentSettings =
|
|
44
|
+
| RecordComponentSettings
|
|
45
|
+
| GlobalMenuComponentSettings
|
|
46
|
+
| SideMenuComponentSettings;
|
|
47
|
+
|
|
48
|
+
export interface BaseManifestComponent {
|
|
49
|
+
title: string;
|
|
50
|
+
id: string;
|
|
51
|
+
path: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface RecordComponent extends BaseManifestComponent {
|
|
55
|
+
type: typeof COMPONENT_TYPE.RECORD;
|
|
56
|
+
settings: RecordComponentSettings;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface GlobalMenuComponent extends BaseManifestComponent {
|
|
60
|
+
type: typeof COMPONENT_TYPE.GLOBAL_MENU;
|
|
61
|
+
settings: GlobalMenuComponentSettings;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type ManifestComponent = RecordComponent | GlobalMenuComponent;
|
|
65
|
+
|
|
66
|
+
export interface UntypedManifestComponent {
|
|
24
67
|
type: string;
|
|
25
68
|
title: string;
|
|
26
|
-
|
|
69
|
+
id: string;
|
|
27
70
|
path: string;
|
|
28
71
|
settings?: Record<string, unknown>;
|
|
29
72
|
}
|
|
@@ -35,6 +78,6 @@ export interface Manifest {
|
|
|
35
78
|
|
|
36
79
|
export interface ZippedComponent {
|
|
37
80
|
title: string;
|
|
38
|
-
|
|
81
|
+
id: string;
|
|
39
82
|
build: Buffer;
|
|
40
83
|
}
|
package/src/bin/fireberry.ts
CHANGED
|
@@ -5,6 +5,9 @@ import { runInit } from "../commands/init.js";
|
|
|
5
5
|
import { runCreate } from "../commands/create.js";
|
|
6
6
|
import packageJson from "../../package.json" with { type: "json" };
|
|
7
7
|
import { runPush } from "../commands/push.js";
|
|
8
|
+
import { runInstall } from "../commands/install.js";
|
|
9
|
+
import { runDelete } from "../commands/delete.js";
|
|
10
|
+
import { runDebug } from "../commands/debug.js";
|
|
8
11
|
|
|
9
12
|
const program = new Command();
|
|
10
13
|
|
|
@@ -37,6 +40,29 @@ program
|
|
|
37
40
|
await runPush();
|
|
38
41
|
});
|
|
39
42
|
|
|
43
|
+
program.command("install")
|
|
44
|
+
.description("Install app on your Fireberry account")
|
|
45
|
+
.action(async () => {
|
|
46
|
+
await runInstall();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
program
|
|
50
|
+
.command("delete")
|
|
51
|
+
.description("Delete a Fireberry app")
|
|
52
|
+
.action(async () => {
|
|
53
|
+
await runDelete();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
program
|
|
57
|
+
.command("debug")
|
|
58
|
+
.argument("<component-id>", "Component ID to debug")
|
|
59
|
+
.argument("[url]", "Debug URL in format localhost:[port]")
|
|
60
|
+
.option("--stop", "Stop debugging the component")
|
|
61
|
+
.description("Start or stop debugging a component")
|
|
62
|
+
.action(async (componentId: string, url?: string, options?: { stop?: boolean }) => {
|
|
63
|
+
await runDebug(componentId, url, options);
|
|
64
|
+
});
|
|
65
|
+
|
|
40
66
|
program.parseAsync(process.argv).catch((err: unknown) => {
|
|
41
67
|
const errorMessage = err instanceof Error
|
|
42
68
|
? err.message
|
package/src/commands/create.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import ora from "ora";
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
import { createApp } from "../api/requests.js";
|
|
9
|
+
import { getManifest } from "../utils/components.utils.js";
|
|
9
10
|
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
@@ -40,6 +41,7 @@ export async function runCreate({ name }: CreateOptions): Promise<void> {
|
|
|
40
41
|
|
|
41
42
|
const slug = slugifyName(appName);
|
|
42
43
|
const appId = uuidv4();
|
|
44
|
+
const componentId = uuidv4();
|
|
43
45
|
const appDir = path.resolve(process.cwd(), slug);
|
|
44
46
|
|
|
45
47
|
if (await fs.pathExists(appDir)) {
|
|
@@ -49,8 +51,6 @@ export async function runCreate({ name }: CreateOptions): Promise<void> {
|
|
|
49
51
|
const spinner = ora(`Creating app "${chalk.cyan(appName)}"...`).start();
|
|
50
52
|
|
|
51
53
|
try {
|
|
52
|
-
await createApp({ appId });
|
|
53
|
-
|
|
54
54
|
await fs.ensureDir(appDir);
|
|
55
55
|
|
|
56
56
|
const templatesDir = path.join(__dirname, "..", "..", "src", "templates");
|
|
@@ -65,12 +65,16 @@ export async function runCreate({ name }: CreateOptions): Promise<void> {
|
|
|
65
65
|
|
|
66
66
|
const manifestContent = manifestTemplate
|
|
67
67
|
.replace(/{{appName}}/g, appName)
|
|
68
|
-
.replace(/{{appId}}/g, appId)
|
|
68
|
+
.replace(/{{appId}}/g, appId)
|
|
69
|
+
.replace(/{{componentId}}/g, componentId);
|
|
69
70
|
|
|
70
71
|
const htmlContent = htmlTemplate.replace(/{{appName}}/g, appName);
|
|
71
72
|
|
|
72
73
|
await fs.writeFile(path.join(appDir, "manifest.yml"), manifestContent);
|
|
73
74
|
await fs.writeFile(path.join(appDir, "index.html"), htmlContent);
|
|
75
|
+
const manifest = await getManifest(appDir);
|
|
76
|
+
|
|
77
|
+
await createApp(manifest);
|
|
74
78
|
|
|
75
79
|
spinner.succeed(`Successfully created "${chalk.cyan(appName)}" app!`);
|
|
76
80
|
console.log(chalk.gray(`📁 Location: ${appDir}`));
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { startDebug, stopDebug } from "../api/requests.js";
|
|
4
|
+
import { getManifest } from "../utils/components.utils.js";
|
|
5
|
+
|
|
6
|
+
function validateDebugUrl(url: string): void {
|
|
7
|
+
const localhostPattern = /^localhost:\d+$/;
|
|
8
|
+
|
|
9
|
+
if (!localhostPattern.test(url)) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
"Invalid URL format. URL must be in format: localhost:[port] (e.g., localhost:3000)\n" +
|
|
12
|
+
"Do not include http:// or https://"
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function validateComponentExists(manifest: any, componentId: string): void {
|
|
18
|
+
const component = manifest.components?.find(
|
|
19
|
+
(comp: any) => comp.id === componentId
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (!component) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Component with ID "${componentId}" not found in manifest.\n` +
|
|
25
|
+
`Available components:\n` +
|
|
26
|
+
manifest.components
|
|
27
|
+
?.map((comp: any) => ` - ${comp.title} (${comp.id})`)
|
|
28
|
+
.join("\n")
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function runDebug(
|
|
34
|
+
componentId: string,
|
|
35
|
+
url?: string,
|
|
36
|
+
options?: { stop?: boolean }
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
const spinner = ora("Loading manifest...").start();
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const manifest = await getManifest();
|
|
42
|
+
spinner.succeed("Manifest loaded");
|
|
43
|
+
|
|
44
|
+
// Validate component exists
|
|
45
|
+
validateComponentExists(manifest, componentId);
|
|
46
|
+
|
|
47
|
+
if (options?.stop) {
|
|
48
|
+
// Stop debugging
|
|
49
|
+
spinner.start("Stopping debug mode...");
|
|
50
|
+
await stopDebug(manifest.app.id, componentId, manifest);
|
|
51
|
+
spinner.succeed(
|
|
52
|
+
chalk.green(`Debug mode stopped for component: ${componentId}`)
|
|
53
|
+
);
|
|
54
|
+
} else {
|
|
55
|
+
// Start debugging
|
|
56
|
+
if (!url) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"URL is required when starting debug mode.\n" +
|
|
59
|
+
`Usage: fireberry debug ${componentId} localhost:[port]`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
validateDebugUrl(url);
|
|
64
|
+
|
|
65
|
+
spinner.start(`Starting debug mode for component ${componentId}...`);
|
|
66
|
+
await startDebug(manifest.app.id, componentId, url, manifest);
|
|
67
|
+
spinner.succeed(
|
|
68
|
+
chalk.green(
|
|
69
|
+
`Debug mode started!\n` +
|
|
70
|
+
` Component: ${componentId}\n` +
|
|
71
|
+
` URL: ${url}\n\n` +
|
|
72
|
+
`To stop debugging, run: ${chalk.cyan(
|
|
73
|
+
`fireberry debug ${componentId} --stop`
|
|
74
|
+
)}`
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
spinner.fail(
|
|
80
|
+
options?.stop ? "Failed to stop debug mode" : "Failed to start debug mode"
|
|
81
|
+
);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import inquirer from "inquirer";
|
|
4
|
+
import { deleteApp } from "../api/requests.js";
|
|
5
|
+
import { getManifest } from "../utils/components.utils.js";
|
|
6
|
+
|
|
7
|
+
export async function runDelete(): Promise<void> {
|
|
8
|
+
const spinner = ora("Loading manifest...").start();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const manifest = await getManifest();
|
|
12
|
+
spinner.stop();
|
|
13
|
+
|
|
14
|
+
const confirmAnswer = await inquirer.prompt([
|
|
15
|
+
{
|
|
16
|
+
type: "confirm",
|
|
17
|
+
name: "confirm",
|
|
18
|
+
message: `Are you sure you want to delete app ${chalk.yellow(
|
|
19
|
+
manifest.app.name
|
|
20
|
+
)} (${chalk.gray(manifest.app.id)})? This action cannot be undone.`,
|
|
21
|
+
default: false,
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
if (!confirmAnswer.confirm) {
|
|
26
|
+
console.log(chalk.gray("Delete operation cancelled."));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
spinner.start(`Deleting app "${chalk.cyan(manifest.app.name)}"...`);
|
|
31
|
+
|
|
32
|
+
await deleteApp(manifest);
|
|
33
|
+
spinner.succeed(
|
|
34
|
+
`Successfully deleted app "${chalk.cyan(manifest.app.name)}"`
|
|
35
|
+
);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
spinner.fail("Failed to delete app");
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
import { installApp } from "../api/requests.js";
|
|
3
|
+
import {
|
|
4
|
+
getManifest,
|
|
5
|
+
validateManifestComponents,
|
|
6
|
+
} from "../utils/components.utils.js";
|
|
7
|
+
|
|
8
|
+
export async function runInstall(): Promise<void> {
|
|
9
|
+
const spinner = ora("Loading manifest...").start();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const manifest = await getManifest();
|
|
13
|
+
|
|
14
|
+
await validateManifestComponents(manifest);
|
|
15
|
+
|
|
16
|
+
spinner.start("Installing app on Fireberry...");
|
|
17
|
+
await installApp(manifest);
|
|
18
|
+
spinner.succeed("App installed successfully");
|
|
19
|
+
} catch (error) {
|
|
20
|
+
spinner.fail("Installation failed");
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/commands/push.ts
CHANGED
|
@@ -24,13 +24,13 @@ export async function runPush(): Promise<void> {
|
|
|
24
24
|
zippedComponents.forEach((comp, idx) => {
|
|
25
25
|
const sizeKB = (comp.build.length / 1024).toFixed(2);
|
|
26
26
|
console.log(
|
|
27
|
-
chalk.gray(` ${idx + 1}. ${comp.title} (${comp.
|
|
27
|
+
chalk.gray(` ${idx + 1}. ${comp.title} (${comp.id}) - ${sizeKB} KB`)
|
|
28
28
|
);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
spinner.start("Uploading to Fireberry...");
|
|
32
32
|
|
|
33
|
-
await pushComponents(manifest.app.id, zippedComponents);
|
|
33
|
+
await pushComponents(manifest.app.id, zippedComponents, manifest);
|
|
34
34
|
spinner.succeed("Components pushed successfully");
|
|
35
35
|
} else {
|
|
36
36
|
spinner.succeed("No components to push");
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const COMPONENT_TYPE = {
|
|
2
|
+
RECORD: "record",
|
|
3
|
+
GLOBAL_MENU: "global-menu",
|
|
4
|
+
SIDE_MENU: "side-menu",
|
|
5
|
+
} as const;
|
|
6
|
+
|
|
7
|
+
export type ComponentType =
|
|
8
|
+
(typeof COMPONENT_TYPE)[keyof typeof COMPONENT_TYPE];
|
|
9
|
+
|
|
10
|
+
export const BASE_SERVICE_URL = "/services/developer/app";
|