@fireberry/cli 0.0.5-beta.16 → 0.0.5-beta.18
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 +42 -5
- package/dist/api/requests.js +1 -1
- package/dist/api/types.d.ts +5 -1
- package/dist/constants/component-types.d.ts +1 -0
- package/dist/constants/component-types.js +1 -0
- package/dist/utils/components.utils.js +20 -0
- package/package.json +1 -1
- package/src/api/requests.ts +1 -1
- package/src/api/types.ts +7 -1
- package/src/constants/component-types.ts +3 -1
- package/src/utils/components.utils.ts +40 -0
package/CLAUDE.md
CHANGED
|
@@ -9,6 +9,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
9
9
|
## Development Commands
|
|
10
10
|
|
|
11
11
|
### Build and Development
|
|
12
|
+
|
|
12
13
|
```bash
|
|
13
14
|
npm run build # Compile TypeScript to dist/
|
|
14
15
|
npm run build:dev # Clean, build, link globally, and make executable
|
|
@@ -17,17 +18,20 @@ npm run clean # Remove dist/ directory
|
|
|
17
18
|
```
|
|
18
19
|
|
|
19
20
|
### Testing
|
|
21
|
+
|
|
20
22
|
```bash
|
|
21
23
|
npm test # Run smoke test (verifies CLI --help works)
|
|
22
24
|
```
|
|
23
25
|
|
|
24
26
|
### Local Development
|
|
27
|
+
|
|
25
28
|
```bash
|
|
26
29
|
npm run build:dev # Build and link CLI locally
|
|
27
30
|
fireberry --help # Test the linked CLI
|
|
28
31
|
```
|
|
29
32
|
|
|
30
33
|
### Publishing
|
|
34
|
+
|
|
31
35
|
```bash
|
|
32
36
|
npm run publish:beta # Version bump (beta) and publish to npm with beta tag
|
|
33
37
|
npm run publish:prod # Publish to npm with latest tag
|
|
@@ -36,84 +40,117 @@ npm run publish:prod # Publish to npm with latest tag
|
|
|
36
40
|
## Architecture
|
|
37
41
|
|
|
38
42
|
### Module System
|
|
43
|
+
|
|
39
44
|
- **Type**: ESM (ES Modules) with `"type": "module"` in package.json
|
|
40
45
|
- **Module Resolution**: NodeNext
|
|
41
46
|
- **Import Extensions**: All local imports must use `.js` extension (e.g., `import { foo } from "./bar.js"`)
|
|
42
47
|
- **JSON Imports**: Use `with { type: "json" }` syntax (e.g., `import packageJson from "../../package.json" with { type: "json" }`)
|
|
43
48
|
|
|
44
49
|
### CLI Entry Point
|
|
50
|
+
|
|
45
51
|
- **Binary**: [dist/bin/fireberry.js](dist/bin/fireberry.js) (generated from [src/bin/fireberry.ts](src/bin/fireberry.ts))
|
|
46
52
|
- **Framework**: Commander.js for command parsing and routing
|
|
47
53
|
- **Error Handling**: Global error handler in [src/bin/fireberry.ts](src/bin/fireberry.ts) catches and formats errors
|
|
48
54
|
|
|
49
55
|
### Command Structure
|
|
56
|
+
|
|
50
57
|
Each CLI command is in [src/commands/](src/commands/):
|
|
58
|
+
|
|
51
59
|
- **init**: Stores Fireberry API token in local config using `env-paths`
|
|
52
60
|
- **create**: Creates new Fireberry app from templates with generated UUIDs
|
|
53
61
|
- **push**: Validates manifest, zips components, uploads to Fireberry API
|
|
54
62
|
- **install**: Installs app on user's Fireberry account
|
|
63
|
+
- **delete**: Deletes app from Fireberry platform (requires confirmation)
|
|
55
64
|
|
|
56
65
|
### Configuration System
|
|
66
|
+
|
|
57
67
|
- **User Config**: Stored via `env-paths` ("Fireberry CLI") in OS-specific config directory
|
|
58
68
|
- **Config File**: `config.json` with `{ apiToken, createdAt }`
|
|
59
69
|
- **Environment**: [src/config/env.ts](src/config/env.ts) loads `.env` file from project root using dotenv
|
|
60
70
|
|
|
61
71
|
### API Layer ([src/api/](src/api/))
|
|
72
|
+
|
|
62
73
|
- **axios.ts**: Axios instance with automatic token injection via interceptors
|
|
63
74
|
- **API URL Logic**:
|
|
64
75
|
- Beta versions (version contains "beta") → Uses `FIREBERRY_STAGING_URL` environment variable
|
|
65
76
|
- Production versions → `FIREBERRY_API_URL` or `https://api.fireberry.com/api/v3`
|
|
66
77
|
- **Authentication**: Token passed via `tokenId` header (read from config in interceptor)
|
|
67
|
-
- **requests.ts**: API endpoints (`createApp`, `pushComponents`, `installApp`)
|
|
78
|
+
- **requests.ts**: API endpoints (`createApp`, `pushComponents`, `installApp`, `deleteApp`)
|
|
68
79
|
- **types.ts**: TypeScript interfaces for API requests/responses
|
|
69
80
|
|
|
70
81
|
### Component System
|
|
82
|
+
|
|
71
83
|
Component utilities in [src/utils/components.utils.ts](src/utils/components.utils.ts):
|
|
84
|
+
|
|
72
85
|
- **Manifest Parsing**: YAML manifest loaded from `manifest.yml` in current directory
|
|
73
|
-
- **Component Validation**: Checks paths exist, components are unique
|
|
86
|
+
- **Component Validation**: Checks paths exist, components are unique, validates required settings per component type
|
|
74
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")
|
|
75
92
|
- **Manifest Structure**:
|
|
76
93
|
```yaml
|
|
77
94
|
app:
|
|
78
95
|
id: "uuid"
|
|
79
96
|
name: "App Name"
|
|
80
97
|
components:
|
|
81
|
-
- type: record
|
|
98
|
+
- type: record | global-menu | side-menu
|
|
82
99
|
title: component-name
|
|
83
100
|
id: "uuid"
|
|
84
101
|
path: relative/path/to/build
|
|
85
|
-
settings:
|
|
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"
|
|
86
112
|
```
|
|
87
113
|
|
|
88
114
|
### Templates ([src/templates/](src/templates/))
|
|
115
|
+
|
|
89
116
|
Templates use mustache-style placeholders (`{{appName}}`, `{{appId}}`, `{{componentId}}`):
|
|
117
|
+
|
|
90
118
|
- **manifest.yml**: Default manifest with single component
|
|
91
119
|
- **index.html**: Basic HTML template
|
|
92
120
|
|
|
93
121
|
## Key Patterns
|
|
94
122
|
|
|
95
123
|
### Error Handling
|
|
124
|
+
|
|
96
125
|
- Commands throw errors which are caught by the global handler in [src/bin/fireberry.ts:47](src/bin/fireberry.ts#L47)
|
|
97
126
|
- Errors are formatted with chalk.red and prefixed with "Error:" if not already present
|
|
98
127
|
- API errors map HTTP status codes to user-friendly messages
|
|
99
128
|
|
|
100
129
|
### User Feedback
|
|
130
|
+
|
|
101
131
|
- Use `ora` spinners for long-running operations (start → succeed/fail)
|
|
102
132
|
- Use `chalk` for colored output (cyan for highlights, gray for details, yellow for warnings)
|
|
103
133
|
- Use `inquirer` for interactive prompts when arguments are missing
|
|
104
134
|
|
|
105
135
|
### File Operations
|
|
136
|
+
|
|
106
137
|
- Use `fs-extra` for all file operations (promisified, ensures directories exist)
|
|
107
138
|
- Check `fs.pathExists()` before operations
|
|
108
139
|
- Use `process.cwd()` as base for relative paths in user projects
|
|
109
140
|
|
|
110
141
|
### Component Path Resolution
|
|
142
|
+
|
|
111
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"`.
|
|
112
144
|
|
|
113
145
|
## Important Notes
|
|
114
146
|
|
|
115
|
-
- **Manifest Required**: `push` and `
|
|
147
|
+
- **Manifest Required**: `push`, `install`, and `delete` commands must be run from a directory containing `manifest.yml`
|
|
116
148
|
- **Token Required**: Most commands require prior `init` to store API token
|
|
117
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)
|
|
118
154
|
- **Build Zipping**: Single files are wrapped in a directory before tar.gz creation
|
|
119
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/dist/api/requests.js
CHANGED
|
@@ -30,7 +30,7 @@ export const installApp = async (manifest) => {
|
|
|
30
30
|
export const deleteApp = async (manifest) => {
|
|
31
31
|
const url = `/services/developer/delete`;
|
|
32
32
|
try {
|
|
33
|
-
await api.delete(url, manifest);
|
|
33
|
+
await api.delete(url, { manifest });
|
|
34
34
|
}
|
|
35
35
|
catch (error) {
|
|
36
36
|
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
package/dist/api/types.d.ts
CHANGED
|
@@ -25,7 +25,11 @@ export interface RecordComponentSettings {
|
|
|
25
25
|
export interface GlobalMenuComponentSettings {
|
|
26
26
|
displayName: string;
|
|
27
27
|
}
|
|
28
|
-
export
|
|
28
|
+
export interface SideMenuComponentSettings {
|
|
29
|
+
icon: string;
|
|
30
|
+
width: "S" | "M" | "L";
|
|
31
|
+
}
|
|
32
|
+
export type ComponentSettings = RecordComponentSettings | GlobalMenuComponentSettings | SideMenuComponentSettings;
|
|
29
33
|
export interface BaseManifestComponent {
|
|
30
34
|
title: string;
|
|
31
35
|
id: string;
|
|
@@ -58,6 +58,23 @@ const validateGlobalMenuComponentSettings = (comp) => {
|
|
|
58
58
|
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.GLOBAL_MENU}) setting "displayName" must be a string`);
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
|
+
const validateSideMenuComponentSettings = (comp) => {
|
|
62
|
+
const settings = comp.settings;
|
|
63
|
+
if (!settings) {
|
|
64
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.SIDE_MENU}) is missing required settings`);
|
|
65
|
+
}
|
|
66
|
+
if (!settings.icon) {
|
|
67
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.SIDE_MENU}) setting "icon" must be a string`);
|
|
68
|
+
}
|
|
69
|
+
if (!settings.width) {
|
|
70
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.SIDE_MENU}) setting "width" must be a S | M | L`);
|
|
71
|
+
}
|
|
72
|
+
if (settings.width !== "S" &&
|
|
73
|
+
settings.width !== "M" &&
|
|
74
|
+
settings.width !== "L") {
|
|
75
|
+
throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.SIDE_MENU}) setting "width" must be a S | M | L`);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
61
78
|
const validateComponentSettings = (comp) => {
|
|
62
79
|
switch (comp.type) {
|
|
63
80
|
case COMPONENT_TYPE.RECORD:
|
|
@@ -66,6 +83,9 @@ const validateComponentSettings = (comp) => {
|
|
|
66
83
|
case COMPONENT_TYPE.GLOBAL_MENU:
|
|
67
84
|
validateGlobalMenuComponentSettings(comp);
|
|
68
85
|
break;
|
|
86
|
+
case COMPONENT_TYPE.SIDE_MENU:
|
|
87
|
+
validateSideMenuComponentSettings(comp);
|
|
88
|
+
break;
|
|
69
89
|
default:
|
|
70
90
|
throw new Error(`Component "${comp.title}" has unsupported type: ${comp.type}. Supported types: ${COMPONENT_TYPE.RECORD}, ${COMPONENT_TYPE.GLOBAL_MENU}`);
|
|
71
91
|
}
|
package/package.json
CHANGED
package/src/api/requests.ts
CHANGED
|
@@ -35,7 +35,7 @@ export const installApp = async (manifest: Manifest): Promise<void> => {
|
|
|
35
35
|
export const deleteApp = async (manifest: Manifest): Promise<void> => {
|
|
36
36
|
const url = `/services/developer/delete`;
|
|
37
37
|
try {
|
|
38
|
-
await api.delete<void>(url, manifest);
|
|
38
|
+
await api.delete<void>(url, { manifest });
|
|
39
39
|
} catch (error) {
|
|
40
40
|
throw new Error(error instanceof Error ? error.message : "Unknown error");
|
|
41
41
|
}
|
package/src/api/types.ts
CHANGED
|
@@ -32,9 +32,15 @@ export interface GlobalMenuComponentSettings {
|
|
|
32
32
|
displayName: string;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export interface SideMenuComponentSettings {
|
|
36
|
+
icon: string;
|
|
37
|
+
width: "S" | "M" | "L";
|
|
38
|
+
}
|
|
39
|
+
|
|
35
40
|
export type ComponentSettings =
|
|
36
41
|
| RecordComponentSettings
|
|
37
|
-
| GlobalMenuComponentSettings
|
|
42
|
+
| GlobalMenuComponentSettings
|
|
43
|
+
| SideMenuComponentSettings;
|
|
38
44
|
|
|
39
45
|
export interface BaseManifestComponent {
|
|
40
46
|
title: string;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export const COMPONENT_TYPE = {
|
|
2
2
|
RECORD: "record",
|
|
3
3
|
GLOBAL_MENU: "global-menu",
|
|
4
|
+
SIDE_MENU: "side-menu",
|
|
4
5
|
} as const;
|
|
5
6
|
|
|
6
|
-
export type ComponentType =
|
|
7
|
+
export type ComponentType =
|
|
8
|
+
(typeof COMPONENT_TYPE)[keyof typeof COMPONENT_TYPE];
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
UntypedManifestComponent,
|
|
11
11
|
RecordComponentSettings,
|
|
12
12
|
GlobalMenuComponentSettings,
|
|
13
|
+
SideMenuComponentSettings,
|
|
13
14
|
} from "../api/types.js";
|
|
14
15
|
import { COMPONENT_TYPE } from "../constants/component-types.js";
|
|
15
16
|
|
|
@@ -112,6 +113,42 @@ const validateGlobalMenuComponentSettings = (
|
|
|
112
113
|
}
|
|
113
114
|
};
|
|
114
115
|
|
|
116
|
+
const validateSideMenuComponentSettings = (
|
|
117
|
+
comp: UntypedManifestComponent
|
|
118
|
+
): void => {
|
|
119
|
+
const settings = comp.settings as
|
|
120
|
+
| Partial<SideMenuComponentSettings>
|
|
121
|
+
| undefined;
|
|
122
|
+
|
|
123
|
+
if (!settings) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Component "${comp.title}" (type: ${COMPONENT_TYPE.SIDE_MENU}) is missing required settings`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!settings.icon) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Component "${comp.title}" (type: ${COMPONENT_TYPE.SIDE_MENU}) setting "icon" must be a string`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!settings.width) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Component "${comp.title}" (type: ${COMPONENT_TYPE.SIDE_MENU}) setting "width" must be a S | M | L`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (
|
|
142
|
+
settings.width !== "S" &&
|
|
143
|
+
settings.width !== "M" &&
|
|
144
|
+
settings.width !== "L"
|
|
145
|
+
) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Component "${comp.title}" (type: ${COMPONENT_TYPE.SIDE_MENU}) setting "width" must be a S | M | L`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
115
152
|
const validateComponentSettings = (comp: UntypedManifestComponent): void => {
|
|
116
153
|
switch (comp.type) {
|
|
117
154
|
case COMPONENT_TYPE.RECORD:
|
|
@@ -120,6 +157,9 @@ const validateComponentSettings = (comp: UntypedManifestComponent): void => {
|
|
|
120
157
|
case COMPONENT_TYPE.GLOBAL_MENU:
|
|
121
158
|
validateGlobalMenuComponentSettings(comp);
|
|
122
159
|
break;
|
|
160
|
+
case COMPONENT_TYPE.SIDE_MENU:
|
|
161
|
+
validateSideMenuComponentSettings(comp);
|
|
162
|
+
break;
|
|
123
163
|
default:
|
|
124
164
|
throw new Error(
|
|
125
165
|
`Component "${comp.title}" has unsupported type: ${comp.type}. Supported types: ${COMPONENT_TYPE.RECORD}, ${COMPONENT_TYPE.GLOBAL_MENU}`
|