@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 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 `install` commands must be run from a directory containing `manifest.yml`
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)
@@ -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");
@@ -25,7 +25,11 @@ export interface RecordComponentSettings {
25
25
  export interface GlobalMenuComponentSettings {
26
26
  displayName: string;
27
27
  }
28
- export type ComponentSettings = RecordComponentSettings | GlobalMenuComponentSettings;
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;
@@ -1,5 +1,6 @@
1
1
  export declare const COMPONENT_TYPE: {
2
2
  readonly RECORD: "record";
3
3
  readonly GLOBAL_MENU: "global-menu";
4
+ readonly SIDE_MENU: "side-menu";
4
5
  };
5
6
  export type ComponentType = (typeof COMPONENT_TYPE)[keyof typeof COMPONENT_TYPE];
@@ -1,4 +1,5 @@
1
1
  export const COMPONENT_TYPE = {
2
2
  RECORD: "record",
3
3
  GLOBAL_MENU: "global-menu",
4
+ SIDE_MENU: "side-menu",
4
5
  };
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fireberry/cli",
3
- "version": "0.0.5-beta.16",
3
+ "version": "0.0.5-beta.18",
4
4
  "description": "Fireberry CLI tool",
5
5
  "type": "module",
6
6
  "author": "",
@@ -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 = (typeof COMPONENT_TYPE)[keyof typeof COMPONENT_TYPE];
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}`