@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
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@fireberry/cli` is a command-line tool for managing Fireberry applications. It allows developers to initialize credentials, create new apps from templates, push components to the Fireberry platform, and install apps on Fireberry accounts.
|
|
8
|
+
|
|
9
|
+
## Development Commands
|
|
10
|
+
|
|
11
|
+
### Build and Development
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm run build # Compile TypeScript to dist/
|
|
15
|
+
npm run build:dev # Clean, build, link globally, and make executable
|
|
16
|
+
npm run dev # Watch mode compilation
|
|
17
|
+
npm run clean # Remove dist/ directory
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Testing
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm test # Run smoke test (verifies CLI --help works)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Local Development
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm run build:dev # Build and link CLI locally
|
|
30
|
+
fireberry --help # Test the linked CLI
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Publishing
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm run publish:beta # Version bump (beta) and publish to npm with beta tag
|
|
37
|
+
npm run publish:prod # Publish to npm with latest tag
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
### Module System
|
|
43
|
+
|
|
44
|
+
- **Type**: ESM (ES Modules) with `"type": "module"` in package.json
|
|
45
|
+
- **Module Resolution**: NodeNext
|
|
46
|
+
- **Import Extensions**: All local imports must use `.js` extension (e.g., `import { foo } from "./bar.js"`)
|
|
47
|
+
- **JSON Imports**: Use `with { type: "json" }` syntax (e.g., `import packageJson from "../../package.json" with { type: "json" }`)
|
|
48
|
+
|
|
49
|
+
### CLI Entry Point
|
|
50
|
+
|
|
51
|
+
- **Binary**: [dist/bin/fireberry.js](dist/bin/fireberry.js) (generated from [src/bin/fireberry.ts](src/bin/fireberry.ts))
|
|
52
|
+
- **Framework**: Commander.js for command parsing and routing
|
|
53
|
+
- **Error Handling**: Global error handler in [src/bin/fireberry.ts](src/bin/fireberry.ts) catches and formats errors
|
|
54
|
+
|
|
55
|
+
### Command Structure
|
|
56
|
+
|
|
57
|
+
Each CLI command is in [src/commands/](src/commands/):
|
|
58
|
+
|
|
59
|
+
- **init**: Stores Fireberry API token in local config using `env-paths`
|
|
60
|
+
- **create**: Creates new Fireberry app from templates with generated UUIDs
|
|
61
|
+
- **push**: Validates manifest, zips components, uploads to Fireberry API
|
|
62
|
+
- **install**: Installs app on user's Fireberry account
|
|
63
|
+
- **delete**: Deletes app from Fireberry platform (requires confirmation)
|
|
64
|
+
|
|
65
|
+
### Configuration System
|
|
66
|
+
|
|
67
|
+
- **User Config**: Stored via `env-paths` ("Fireberry CLI") in OS-specific config directory
|
|
68
|
+
- **Config File**: `config.json` with `{ apiToken, createdAt }`
|
|
69
|
+
- **Environment**: [src/config/env.ts](src/config/env.ts) loads `.env` file from project root using dotenv
|
|
70
|
+
|
|
71
|
+
### API Layer ([src/api/](src/api/))
|
|
72
|
+
|
|
73
|
+
- **axios.ts**: Axios instance with automatic token injection via interceptors
|
|
74
|
+
- **API URL Logic**:
|
|
75
|
+
- Beta versions (version contains "beta") → Uses `FIREBERRY_STAGING_URL` environment variable
|
|
76
|
+
- Production versions → `FIREBERRY_API_URL` or `https://api.fireberry.com/api/v3`
|
|
77
|
+
- **Authentication**: Token passed via `tokenId` header (read from config in interceptor)
|
|
78
|
+
- **requests.ts**: API endpoints (`createApp`, `pushComponents`, `installApp`, `deleteApp`)
|
|
79
|
+
- **types.ts**: TypeScript interfaces for API requests/responses
|
|
80
|
+
|
|
81
|
+
### Component System
|
|
82
|
+
|
|
83
|
+
Component utilities in [src/utils/components.utils.ts](src/utils/components.utils.ts):
|
|
84
|
+
|
|
85
|
+
- **Manifest Parsing**: YAML manifest loaded from `manifest.yml` in current directory
|
|
86
|
+
- **Component Validation**: Checks paths exist, components are unique, validates required settings per component type
|
|
87
|
+
- **Component Zipping**: Creates tar.gz archives of component builds (directories or single files)
|
|
88
|
+
- **Component Types** ([src/constants/component-types.ts](src/constants/component-types.ts)):
|
|
89
|
+
- **record**: Record page component with required settings: `iconName` (string), `iconColor` (string), `objectType` (number)
|
|
90
|
+
- **global-menu**: Global menu component with required setting: `displayName` (string)
|
|
91
|
+
- **side-menu**: Side menu component with required settings: `icon` (string), `width` (string: "S" | "M" | "L")
|
|
92
|
+
- **Manifest Structure**:
|
|
93
|
+
```yaml
|
|
94
|
+
app:
|
|
95
|
+
id: "uuid"
|
|
96
|
+
name: "App Name"
|
|
97
|
+
components:
|
|
98
|
+
- type: record | global-menu | side-menu
|
|
99
|
+
title: component-name
|
|
100
|
+
id: "uuid"
|
|
101
|
+
path: relative/path/to/build
|
|
102
|
+
settings:
|
|
103
|
+
# For record type:
|
|
104
|
+
iconName: "icon-name"
|
|
105
|
+
iconColor: "#hexcolor"
|
|
106
|
+
objectType: 1
|
|
107
|
+
# For global-menu type:
|
|
108
|
+
displayName: "Menu Name"
|
|
109
|
+
# For side-menu type:
|
|
110
|
+
icon: "icon-name"
|
|
111
|
+
width: "S" | "M" | "L"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Templates ([src/templates/](src/templates/))
|
|
115
|
+
|
|
116
|
+
Templates use mustache-style placeholders (`{{appName}}`, `{{appId}}`, `{{componentId}}`):
|
|
117
|
+
|
|
118
|
+
- **manifest.yml**: Default manifest with single component
|
|
119
|
+
- **index.html**: Basic HTML template
|
|
120
|
+
|
|
121
|
+
## Key Patterns
|
|
122
|
+
|
|
123
|
+
### Error Handling
|
|
124
|
+
|
|
125
|
+
- Commands throw errors which are caught by the global handler in [src/bin/fireberry.ts:47](src/bin/fireberry.ts#L47)
|
|
126
|
+
- Errors are formatted with chalk.red and prefixed with "Error:" if not already present
|
|
127
|
+
- API errors map HTTP status codes to user-friendly messages
|
|
128
|
+
|
|
129
|
+
### User Feedback
|
|
130
|
+
|
|
131
|
+
- Use `ora` spinners for long-running operations (start → succeed/fail)
|
|
132
|
+
- Use `chalk` for colored output (cyan for highlights, gray for details, yellow for warnings)
|
|
133
|
+
- Use `inquirer` for interactive prompts when arguments are missing
|
|
134
|
+
|
|
135
|
+
### File Operations
|
|
136
|
+
|
|
137
|
+
- Use `fs-extra` for all file operations (promisified, ensures directories exist)
|
|
138
|
+
- Check `fs.pathExists()` before operations
|
|
139
|
+
- Use `process.cwd()` as base for relative paths in user projects
|
|
140
|
+
|
|
141
|
+
### Component Path Resolution
|
|
142
|
+
|
|
143
|
+
Component paths in manifest.yml are relative to the current working directory, not the CLI installation directory. Example: `path: static/comp/build` resolves to `process.cwd() + "/static/comp/build"`.
|
|
144
|
+
|
|
145
|
+
## Important Notes
|
|
146
|
+
|
|
147
|
+
- **Manifest Required**: `push`, `install`, and `delete` commands must be run from a directory containing `manifest.yml`
|
|
148
|
+
- **Token Required**: Most commands require prior `init` to store API token
|
|
149
|
+
- **Component IDs**: Must be unique UUIDs within a manifest
|
|
150
|
+
- **Component Settings Validation**: Each component type has required settings that are validated during `push`:
|
|
151
|
+
- `record`: Must have `iconName`, `iconColor`, and `objectType`
|
|
152
|
+
- `global-menu`: Must have `displayName`
|
|
153
|
+
- `side-menu`: Must have `icon` and `width` (S/M/L)
|
|
154
|
+
- **Build Zipping**: Single files are wrapped in a directory before tar.gz creation
|
|
155
|
+
- **Template Location**: Templates are resolved from `src/templates/` at compile time, copied to `dist/templates/`
|
|
156
|
+
- **Delete Safety**: Delete command requires user confirmation before executing (cannot be undone)
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Fireberry LTD
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/api/axios.js
CHANGED
|
@@ -46,7 +46,7 @@ export async function sendApiRequest(config) {
|
|
|
46
46
|
let errorMessage;
|
|
47
47
|
switch (status) {
|
|
48
48
|
case 401:
|
|
49
|
-
console.log(error.response?.data?.message || error.message);
|
|
49
|
+
console.log(error.response?.data?.message || error.message); // TODO: remove this
|
|
50
50
|
errorMessage = "Unauthorized user.";
|
|
51
51
|
break;
|
|
52
52
|
case 500:
|
package/dist/api/requests.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import "../config/env.js";
|
|
2
|
-
import type {
|
|
3
|
-
export declare const createApp: (
|
|
4
|
-
export declare const pushComponents: (appId: string, components: ZippedComponent[]) => Promise<void>;
|
|
2
|
+
import type { Manifest, ZippedComponent } from "./types.js";
|
|
3
|
+
export declare const createApp: (manifest: Manifest) => Promise<void>;
|
|
4
|
+
export declare const pushComponents: (appId: string, components: ZippedComponent[], manifest: Manifest) => Promise<void>;
|
|
5
|
+
export declare const installApp: (manifest: Manifest) => Promise<void>;
|
|
6
|
+
export declare const deleteApp: (manifest: Manifest) => Promise<void>;
|
|
7
|
+
export declare const startDebug: (appId: string, componentId: string, debugUrl: string, manifest: Manifest) => Promise<void>;
|
|
8
|
+
export declare const stopDebug: (appId: string, componentId: string, manifest: Manifest) => Promise<void>;
|
package/dist/api/requests.js
CHANGED
|
@@ -1,18 +1,55 @@
|
|
|
1
1
|
import "../config/env.js";
|
|
2
|
+
import { BASE_SERVICE_URL } from "../constants/component-types.js";
|
|
2
3
|
import { api } from "./axios.js";
|
|
3
|
-
export const createApp = async (
|
|
4
|
-
const url =
|
|
4
|
+
export const createApp = async (manifest) => {
|
|
5
|
+
const url = `${BASE_SERVICE_URL}/create`;
|
|
5
6
|
try {
|
|
6
|
-
await api.post(url,
|
|
7
|
+
await api.post(url, { manifest });
|
|
7
8
|
}
|
|
8
9
|
catch (error) {
|
|
9
10
|
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
|
10
11
|
}
|
|
11
12
|
};
|
|
12
|
-
export const pushComponents = async (appId, components) => {
|
|
13
|
-
const url =
|
|
13
|
+
export const pushComponents = async (appId, components, manifest) => {
|
|
14
|
+
const url = `${BASE_SERVICE_URL}/push`;
|
|
14
15
|
try {
|
|
15
|
-
await api.post(url, { appId, components });
|
|
16
|
+
await api.post(url, { appId, components, manifest });
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
export const installApp = async (manifest) => {
|
|
23
|
+
const url = `${BASE_SERVICE_URL}/install`;
|
|
24
|
+
try {
|
|
25
|
+
await api.post(url, { manifest }, { timeout: 300000 }); // 5 minutes
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
export const deleteApp = async (manifest) => {
|
|
32
|
+
const url = `${BASE_SERVICE_URL}/delete`;
|
|
33
|
+
try {
|
|
34
|
+
await api.delete(url, { manifest });
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
export const startDebug = async (appId, componentId, debugUrl, manifest) => {
|
|
41
|
+
const url = `${BASE_SERVICE_URL}/debug`;
|
|
42
|
+
try {
|
|
43
|
+
await api.post(url, { appId, componentId, debugUrl, manifest });
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
export const stopDebug = async (appId, componentId, manifest) => {
|
|
50
|
+
const url = `${BASE_SERVICE_URL}/debug`;
|
|
51
|
+
try {
|
|
52
|
+
await api.delete(url, { appId, componentId, manifest });
|
|
16
53
|
}
|
|
17
54
|
catch (error) {
|
|
18
55
|
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
package/dist/api/types.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { COMPONENT_TYPE } from "../constants/component-types.js";
|
|
2
|
+
import { HeightOption } from "../constants/height-options.js";
|
|
1
3
|
export interface CreateAppRequest {
|
|
2
4
|
appId: string;
|
|
5
|
+
componentId: string;
|
|
3
6
|
}
|
|
4
7
|
export interface ApiError {
|
|
5
8
|
message: string;
|
|
@@ -16,10 +19,38 @@ interface ManifestApp {
|
|
|
16
19
|
name: string;
|
|
17
20
|
description?: string;
|
|
18
21
|
}
|
|
19
|
-
export interface
|
|
22
|
+
export interface RecordComponentSettings {
|
|
23
|
+
iconName: string;
|
|
24
|
+
iconColor: string;
|
|
25
|
+
objectType: number;
|
|
26
|
+
height: HeightOption;
|
|
27
|
+
}
|
|
28
|
+
export interface GlobalMenuComponentSettings {
|
|
29
|
+
displayName: string;
|
|
30
|
+
}
|
|
31
|
+
export interface SideMenuComponentSettings {
|
|
32
|
+
icon: string;
|
|
33
|
+
width: "S" | "M" | "L";
|
|
34
|
+
}
|
|
35
|
+
export type ComponentSettings = RecordComponentSettings | GlobalMenuComponentSettings | SideMenuComponentSettings;
|
|
36
|
+
export interface BaseManifestComponent {
|
|
37
|
+
title: string;
|
|
38
|
+
id: string;
|
|
39
|
+
path: string;
|
|
40
|
+
}
|
|
41
|
+
export interface RecordComponent extends BaseManifestComponent {
|
|
42
|
+
type: typeof COMPONENT_TYPE.RECORD;
|
|
43
|
+
settings: RecordComponentSettings;
|
|
44
|
+
}
|
|
45
|
+
export interface GlobalMenuComponent extends BaseManifestComponent {
|
|
46
|
+
type: typeof COMPONENT_TYPE.GLOBAL_MENU;
|
|
47
|
+
settings: GlobalMenuComponentSettings;
|
|
48
|
+
}
|
|
49
|
+
export type ManifestComponent = RecordComponent | GlobalMenuComponent;
|
|
50
|
+
export interface UntypedManifestComponent {
|
|
20
51
|
type: string;
|
|
21
52
|
title: string;
|
|
22
|
-
|
|
53
|
+
id: string;
|
|
23
54
|
path: string;
|
|
24
55
|
settings?: Record<string, unknown>;
|
|
25
56
|
}
|
|
@@ -29,7 +60,7 @@ export interface Manifest {
|
|
|
29
60
|
}
|
|
30
61
|
export interface ZippedComponent {
|
|
31
62
|
title: string;
|
|
32
|
-
|
|
63
|
+
id: string;
|
|
33
64
|
build: Buffer;
|
|
34
65
|
}
|
|
35
66
|
export {};
|
package/dist/bin/fireberry.js
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
|
const program = new Command();
|
|
9
12
|
program
|
|
10
13
|
.name("fireberry")
|
|
@@ -31,6 +34,26 @@ program
|
|
|
31
34
|
.action(async () => {
|
|
32
35
|
await runPush();
|
|
33
36
|
});
|
|
37
|
+
program.command("install")
|
|
38
|
+
.description("Install app on your Fireberry account")
|
|
39
|
+
.action(async () => {
|
|
40
|
+
await runInstall();
|
|
41
|
+
});
|
|
42
|
+
program
|
|
43
|
+
.command("delete")
|
|
44
|
+
.description("Delete a Fireberry app")
|
|
45
|
+
.action(async () => {
|
|
46
|
+
await runDelete();
|
|
47
|
+
});
|
|
48
|
+
program
|
|
49
|
+
.command("debug")
|
|
50
|
+
.argument("<component-id>", "Component ID to debug")
|
|
51
|
+
.argument("[url]", "Debug URL in format localhost:[port]")
|
|
52
|
+
.option("--stop", "Stop debugging the component")
|
|
53
|
+
.description("Start or stop debugging a component")
|
|
54
|
+
.action(async (componentId, url, options) => {
|
|
55
|
+
await runDebug(componentId, url, options);
|
|
56
|
+
});
|
|
34
57
|
program.parseAsync(process.argv).catch((err) => {
|
|
35
58
|
const errorMessage = err instanceof Error
|
|
36
59
|
? err.message
|
package/dist/commands/create.js
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
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = path.dirname(__filename);
|
|
11
12
|
function slugifyName(name) {
|
|
@@ -28,23 +29,26 @@ export async function runCreate({ name }) {
|
|
|
28
29
|
}
|
|
29
30
|
const slug = slugifyName(appName);
|
|
30
31
|
const appId = uuidv4();
|
|
32
|
+
const componentId = uuidv4();
|
|
31
33
|
const appDir = path.resolve(process.cwd(), slug);
|
|
32
34
|
if (await fs.pathExists(appDir)) {
|
|
33
35
|
throw new Error(`Already exists. ${chalk.yellow(slug)}`);
|
|
34
36
|
}
|
|
35
37
|
const spinner = ora(`Creating app "${chalk.cyan(appName)}"...`).start();
|
|
36
38
|
try {
|
|
37
|
-
await createApp({ appId });
|
|
38
39
|
await fs.ensureDir(appDir);
|
|
39
40
|
const templatesDir = path.join(__dirname, "..", "..", "src", "templates");
|
|
40
41
|
const manifestTemplate = await fs.readFile(path.join(templatesDir, "manifest.yml"), "utf-8");
|
|
41
42
|
const htmlTemplate = await fs.readFile(path.join(templatesDir, "index.html"), "utf-8");
|
|
42
43
|
const manifestContent = manifestTemplate
|
|
43
44
|
.replace(/{{appName}}/g, appName)
|
|
44
|
-
.replace(/{{appId}}/g, appId)
|
|
45
|
+
.replace(/{{appId}}/g, appId)
|
|
46
|
+
.replace(/{{componentId}}/g, componentId);
|
|
45
47
|
const htmlContent = htmlTemplate.replace(/{{appName}}/g, appName);
|
|
46
48
|
await fs.writeFile(path.join(appDir, "manifest.yml"), manifestContent);
|
|
47
49
|
await fs.writeFile(path.join(appDir, "index.html"), htmlContent);
|
|
50
|
+
const manifest = await getManifest(appDir);
|
|
51
|
+
await createApp(manifest);
|
|
48
52
|
spinner.succeed(`Successfully created "${chalk.cyan(appName)}" app!`);
|
|
49
53
|
console.log(chalk.gray(`📁 Location: ${appDir}`));
|
|
50
54
|
console.log(chalk.gray(`App ID: ${appId}`));
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
function validateDebugUrl(url) {
|
|
6
|
+
const localhostPattern = /^localhost:\d+$/;
|
|
7
|
+
if (!localhostPattern.test(url)) {
|
|
8
|
+
throw new Error("Invalid URL format. URL must be in format: localhost:[port] (e.g., localhost:3000)\n" +
|
|
9
|
+
"Do not include http:// or https://");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function validateComponentExists(manifest, componentId) {
|
|
13
|
+
const component = manifest.components?.find((comp) => comp.id === componentId);
|
|
14
|
+
if (!component) {
|
|
15
|
+
throw new Error(`Component with ID "${componentId}" not found in manifest.\n` +
|
|
16
|
+
`Available components:\n` +
|
|
17
|
+
manifest.components
|
|
18
|
+
?.map((comp) => ` - ${comp.title} (${comp.id})`)
|
|
19
|
+
.join("\n"));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function runDebug(componentId, url, options) {
|
|
23
|
+
const spinner = ora("Loading manifest...").start();
|
|
24
|
+
try {
|
|
25
|
+
const manifest = await getManifest();
|
|
26
|
+
spinner.succeed("Manifest loaded");
|
|
27
|
+
// Validate component exists
|
|
28
|
+
validateComponentExists(manifest, componentId);
|
|
29
|
+
if (options?.stop) {
|
|
30
|
+
// Stop debugging
|
|
31
|
+
spinner.start("Stopping debug mode...");
|
|
32
|
+
await stopDebug(manifest.app.id, componentId, manifest);
|
|
33
|
+
spinner.succeed(chalk.green(`Debug mode stopped for component: ${componentId}`));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Start debugging
|
|
37
|
+
if (!url) {
|
|
38
|
+
throw new Error("URL is required when starting debug mode.\n" +
|
|
39
|
+
`Usage: fireberry debug ${componentId} localhost:[port]`);
|
|
40
|
+
}
|
|
41
|
+
validateDebugUrl(url);
|
|
42
|
+
spinner.start(`Starting debug mode for component ${componentId}...`);
|
|
43
|
+
await startDebug(manifest.app.id, componentId, url, manifest);
|
|
44
|
+
spinner.succeed(chalk.green(`Debug mode started!\n` +
|
|
45
|
+
` Component: ${componentId}\n` +
|
|
46
|
+
` URL: ${url}\n\n` +
|
|
47
|
+
`To stop debugging, run: ${chalk.cyan(`fireberry debug ${componentId} --stop`)}`));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
spinner.fail(options?.stop ? "Failed to stop debug mode" : "Failed to start debug mode");
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDelete(): Promise<void>;
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
export async function runDelete() {
|
|
7
|
+
const spinner = ora("Loading manifest...").start();
|
|
8
|
+
try {
|
|
9
|
+
const manifest = await getManifest();
|
|
10
|
+
spinner.stop();
|
|
11
|
+
const confirmAnswer = await inquirer.prompt([
|
|
12
|
+
{
|
|
13
|
+
type: "confirm",
|
|
14
|
+
name: "confirm",
|
|
15
|
+
message: `Are you sure you want to delete app ${chalk.yellow(manifest.app.name)} (${chalk.gray(manifest.app.id)})? This action cannot be undone.`,
|
|
16
|
+
default: false,
|
|
17
|
+
},
|
|
18
|
+
]);
|
|
19
|
+
if (!confirmAnswer.confirm) {
|
|
20
|
+
console.log(chalk.gray("Delete operation cancelled."));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
spinner.start(`Deleting app "${chalk.cyan(manifest.app.name)}"...`);
|
|
24
|
+
await deleteApp(manifest);
|
|
25
|
+
spinner.succeed(`Successfully deleted app "${chalk.cyan(manifest.app.name)}"`);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
spinner.fail("Failed to delete app");
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runInstall(): Promise<void>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
import { installApp } from "../api/requests.js";
|
|
3
|
+
import { getManifest, validateManifestComponents, } from "../utils/components.utils.js";
|
|
4
|
+
export async function runInstall() {
|
|
5
|
+
const spinner = ora("Loading manifest...").start();
|
|
6
|
+
try {
|
|
7
|
+
const manifest = await getManifest();
|
|
8
|
+
await validateManifestComponents(manifest);
|
|
9
|
+
spinner.start("Installing app on Fireberry...");
|
|
10
|
+
await installApp(manifest);
|
|
11
|
+
spinner.succeed("App installed successfully");
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
spinner.fail("Installation failed");
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
}
|
package/dist/commands/push.js
CHANGED
|
@@ -15,10 +15,10 @@ export async function runPush() {
|
|
|
15
15
|
console.log(chalk.cyan("\nComponents ready to push:"));
|
|
16
16
|
zippedComponents.forEach((comp, idx) => {
|
|
17
17
|
const sizeKB = (comp.build.length / 1024).toFixed(2);
|
|
18
|
-
console.log(chalk.gray(` ${idx + 1}. ${comp.title} (${comp.
|
|
18
|
+
console.log(chalk.gray(` ${idx + 1}. ${comp.title} (${comp.id}) - ${sizeKB} KB`));
|
|
19
19
|
});
|
|
20
20
|
spinner.start("Uploading to Fireberry...");
|
|
21
|
-
await pushComponents(manifest.app.id, zippedComponents);
|
|
21
|
+
await pushComponents(manifest.app.id, zippedComponents, manifest);
|
|
22
22
|
spinner.succeed("Components pushed successfully");
|
|
23
23
|
}
|
|
24
24
|
else {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const COMPONENT_TYPE: {
|
|
2
|
+
readonly RECORD: "record";
|
|
3
|
+
readonly GLOBAL_MENU: "global-menu";
|
|
4
|
+
readonly SIDE_MENU: "side-menu";
|
|
5
|
+
};
|
|
6
|
+
export type ComponentType = (typeof COMPONENT_TYPE)[keyof typeof COMPONENT_TYPE];
|
|
7
|
+
export declare const BASE_SERVICE_URL = "/services/developer/app";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const HEIGHT_OPTIONS = ["S", "M", "L", "XL"];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Manifest,
|
|
2
|
-
export declare const getManifest: () => Promise<Manifest>;
|
|
3
|
-
export declare const validateComponentBuild: (componentPath: string, comp:
|
|
1
|
+
import { Manifest, ZippedComponent, UntypedManifestComponent } from "../api/types.js";
|
|
2
|
+
export declare const getManifest: (basePath?: string) => Promise<Manifest>;
|
|
3
|
+
export declare const validateComponentBuild: (componentPath: string, comp: UntypedManifestComponent) => Promise<void>;
|
|
4
4
|
export declare const zipComponentBuild: (componentPath: string, title: string) => Promise<Buffer>;
|
|
5
|
+
export declare const validateManifestComponents: (manifest: Manifest) => Promise<void>;
|
|
5
6
|
export declare const handleComponents: (manifest: Manifest) => Promise<ZippedComponent[]>;
|