@fireberry/cli 0.0.5-beta.13 → 0.0.5-beta.16

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 ADDED
@@ -0,0 +1,119 @@
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
+ ```bash
13
+ npm run build # Compile TypeScript to dist/
14
+ npm run build:dev # Clean, build, link globally, and make executable
15
+ npm run dev # Watch mode compilation
16
+ npm run clean # Remove dist/ directory
17
+ ```
18
+
19
+ ### Testing
20
+ ```bash
21
+ npm test # Run smoke test (verifies CLI --help works)
22
+ ```
23
+
24
+ ### Local Development
25
+ ```bash
26
+ npm run build:dev # Build and link CLI locally
27
+ fireberry --help # Test the linked CLI
28
+ ```
29
+
30
+ ### Publishing
31
+ ```bash
32
+ npm run publish:beta # Version bump (beta) and publish to npm with beta tag
33
+ npm run publish:prod # Publish to npm with latest tag
34
+ ```
35
+
36
+ ## Architecture
37
+
38
+ ### Module System
39
+ - **Type**: ESM (ES Modules) with `"type": "module"` in package.json
40
+ - **Module Resolution**: NodeNext
41
+ - **Import Extensions**: All local imports must use `.js` extension (e.g., `import { foo } from "./bar.js"`)
42
+ - **JSON Imports**: Use `with { type: "json" }` syntax (e.g., `import packageJson from "../../package.json" with { type: "json" }`)
43
+
44
+ ### CLI Entry Point
45
+ - **Binary**: [dist/bin/fireberry.js](dist/bin/fireberry.js) (generated from [src/bin/fireberry.ts](src/bin/fireberry.ts))
46
+ - **Framework**: Commander.js for command parsing and routing
47
+ - **Error Handling**: Global error handler in [src/bin/fireberry.ts](src/bin/fireberry.ts) catches and formats errors
48
+
49
+ ### Command Structure
50
+ Each CLI command is in [src/commands/](src/commands/):
51
+ - **init**: Stores Fireberry API token in local config using `env-paths`
52
+ - **create**: Creates new Fireberry app from templates with generated UUIDs
53
+ - **push**: Validates manifest, zips components, uploads to Fireberry API
54
+ - **install**: Installs app on user's Fireberry account
55
+
56
+ ### Configuration System
57
+ - **User Config**: Stored via `env-paths` ("Fireberry CLI") in OS-specific config directory
58
+ - **Config File**: `config.json` with `{ apiToken, createdAt }`
59
+ - **Environment**: [src/config/env.ts](src/config/env.ts) loads `.env` file from project root using dotenv
60
+
61
+ ### API Layer ([src/api/](src/api/))
62
+ - **axios.ts**: Axios instance with automatic token injection via interceptors
63
+ - **API URL Logic**:
64
+ - Beta versions (version contains "beta") → Uses `FIREBERRY_STAGING_URL` environment variable
65
+ - Production versions → `FIREBERRY_API_URL` or `https://api.fireberry.com/api/v3`
66
+ - **Authentication**: Token passed via `tokenId` header (read from config in interceptor)
67
+ - **requests.ts**: API endpoints (`createApp`, `pushComponents`, `installApp`)
68
+ - **types.ts**: TypeScript interfaces for API requests/responses
69
+
70
+ ### Component System
71
+ Component utilities in [src/utils/components.utils.ts](src/utils/components.utils.ts):
72
+ - **Manifest Parsing**: YAML manifest loaded from `manifest.yml` in current directory
73
+ - **Component Validation**: Checks paths exist, components are unique
74
+ - **Component Zipping**: Creates tar.gz archives of component builds (directories or single files)
75
+ - **Manifest Structure**:
76
+ ```yaml
77
+ app:
78
+ id: "uuid"
79
+ name: "App Name"
80
+ components:
81
+ - type: record
82
+ title: component-name
83
+ id: "uuid"
84
+ path: relative/path/to/build
85
+ settings: {...}
86
+ ```
87
+
88
+ ### Templates ([src/templates/](src/templates/))
89
+ Templates use mustache-style placeholders (`{{appName}}`, `{{appId}}`, `{{componentId}}`):
90
+ - **manifest.yml**: Default manifest with single component
91
+ - **index.html**: Basic HTML template
92
+
93
+ ## Key Patterns
94
+
95
+ ### Error Handling
96
+ - Commands throw errors which are caught by the global handler in [src/bin/fireberry.ts:47](src/bin/fireberry.ts#L47)
97
+ - Errors are formatted with chalk.red and prefixed with "Error:" if not already present
98
+ - API errors map HTTP status codes to user-friendly messages
99
+
100
+ ### User Feedback
101
+ - Use `ora` spinners for long-running operations (start → succeed/fail)
102
+ - Use `chalk` for colored output (cyan for highlights, gray for details, yellow for warnings)
103
+ - Use `inquirer` for interactive prompts when arguments are missing
104
+
105
+ ### File Operations
106
+ - Use `fs-extra` for all file operations (promisified, ensures directories exist)
107
+ - Check `fs.pathExists()` before operations
108
+ - Use `process.cwd()` as base for relative paths in user projects
109
+
110
+ ### Component Path Resolution
111
+ 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
+
113
+ ## Important Notes
114
+
115
+ - **Manifest Required**: `push` and `install` commands must be run from a directory containing `manifest.yml`
116
+ - **Token Required**: Most commands require prior `init` to store API token
117
+ - **Component IDs**: Must be unique UUIDs within a manifest
118
+ - **Build Zipping**: Single files are wrapped in a directory before tar.gz creation
119
+ - **Template Location**: Templates are resolved from `src/templates/` at compile time, copied to `dist/templates/`
@@ -3,3 +3,4 @@ import type { CreateAppRequest, Manifest, ZippedComponent } from "./types.js";
3
3
  export declare const createApp: (data: CreateAppRequest) => Promise<void>;
4
4
  export declare const pushComponents: (appId: string, components: ZippedComponent[]) => Promise<void>;
5
5
  export declare const installApp: (manifest: Manifest) => Promise<void>;
6
+ export declare const deleteApp: (manifest: Manifest) => Promise<void>;
@@ -27,3 +27,12 @@ export const installApp = async (manifest) => {
27
27
  throw new Error(error instanceof Error ? error.message : "Unknown error");
28
28
  }
29
29
  };
30
+ export const deleteApp = async (manifest) => {
31
+ const url = `/services/developer/delete`;
32
+ try {
33
+ await api.delete(url, manifest);
34
+ }
35
+ catch (error) {
36
+ throw new Error(error instanceof Error ? error.message : "Unknown error");
37
+ }
38
+ };
@@ -1,3 +1,4 @@
1
+ import { COMPONENT_TYPE } from "../constants/component-types.js";
1
2
  export interface CreateAppRequest {
2
3
  appId: string;
3
4
  }
@@ -16,7 +17,30 @@ interface ManifestApp {
16
17
  name: string;
17
18
  description?: string;
18
19
  }
19
- export interface ManifestComponent {
20
+ export interface RecordComponentSettings {
21
+ iconName: string;
22
+ iconColor: string;
23
+ objectType: number;
24
+ }
25
+ export interface GlobalMenuComponentSettings {
26
+ displayName: string;
27
+ }
28
+ export type ComponentSettings = RecordComponentSettings | GlobalMenuComponentSettings;
29
+ export interface BaseManifestComponent {
30
+ title: string;
31
+ id: string;
32
+ path: string;
33
+ }
34
+ export interface RecordComponent extends BaseManifestComponent {
35
+ type: typeof COMPONENT_TYPE.RECORD;
36
+ settings: RecordComponentSettings;
37
+ }
38
+ export interface GlobalMenuComponent extends BaseManifestComponent {
39
+ type: typeof COMPONENT_TYPE.GLOBAL_MENU;
40
+ settings: GlobalMenuComponentSettings;
41
+ }
42
+ export type ManifestComponent = RecordComponent | GlobalMenuComponent;
43
+ export interface UntypedManifestComponent {
20
44
  type: string;
21
45
  title: string;
22
46
  id: string;
@@ -6,6 +6,7 @@ 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
8
  import { runInstall } from "../commands/install.js";
9
+ import { runDelete } from "../commands/delete.js";
9
10
  const program = new Command();
10
11
  program
11
12
  .name("fireberry")
@@ -37,6 +38,12 @@ program.command("install")
37
38
  .action(async () => {
38
39
  await runInstall();
39
40
  });
41
+ program
42
+ .command("delete")
43
+ .description("Delete a Fireberry app")
44
+ .action(async () => {
45
+ await runDelete();
46
+ });
40
47
  program.parseAsync(process.argv).catch((err) => {
41
48
  const errorMessage = err instanceof Error
42
49
  ? err.message
@@ -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,5 @@
1
+ export declare const COMPONENT_TYPE: {
2
+ readonly RECORD: "record";
3
+ readonly GLOBAL_MENU: "global-menu";
4
+ };
5
+ export type ComponentType = (typeof COMPONENT_TYPE)[keyof typeof COMPONENT_TYPE];
@@ -0,0 +1,4 @@
1
+ export const COMPONENT_TYPE = {
2
+ RECORD: "record",
3
+ GLOBAL_MENU: "global-menu",
4
+ };
@@ -1,6 +1,6 @@
1
- import { Manifest, ManifestComponent, ZippedComponent } from "../api/types.js";
1
+ import { Manifest, ZippedComponent, UntypedManifestComponent } from "../api/types.js";
2
2
  export declare const getManifest: () => Promise<Manifest>;
3
- export declare const validateComponentBuild: (componentPath: string, comp: ManifestComponent) => Promise<void>;
3
+ export declare const validateComponentBuild: (componentPath: string, comp: UntypedManifestComponent) => Promise<void>;
4
4
  export declare const zipComponentBuild: (componentPath: string, title: string) => Promise<Buffer>;
5
5
  export declare const validateManifestComponents: (manifest: Manifest) => Promise<void>;
6
6
  export declare const handleComponents: (manifest: Manifest) => Promise<ZippedComponent[]>;
@@ -4,6 +4,7 @@ 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
+ import { COMPONENT_TYPE } from "../constants/component-types.js";
7
8
  export const getManifest = async () => {
8
9
  const manifestPath = path.join(process.cwd(), "manifest.yml");
9
10
  if (!(await fs.pathExists(manifestPath))) {
@@ -20,6 +21,55 @@ export const getManifest = async () => {
20
21
  }
21
22
  return manifest;
22
23
  };
24
+ const validateRecordComponentSettings = (comp) => {
25
+ const settings = comp.settings;
26
+ if (!settings) {
27
+ throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) is missing required settings`);
28
+ }
29
+ const requiredFields = [
30
+ "iconName",
31
+ "iconColor",
32
+ "objectType",
33
+ ];
34
+ for (const fieldName of requiredFields) {
35
+ if (settings[fieldName] === undefined || settings[fieldName] === null) {
36
+ throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) is missing required setting: ${fieldName}`);
37
+ }
38
+ }
39
+ if (typeof settings.iconName !== "string") {
40
+ throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) setting "iconName" must be a string`);
41
+ }
42
+ if (typeof settings.iconColor !== "string") {
43
+ throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) setting "iconColor" must be a string`);
44
+ }
45
+ if (typeof settings.objectType !== "number") {
46
+ throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) setting "objectType" must be a number`);
47
+ }
48
+ };
49
+ const validateGlobalMenuComponentSettings = (comp) => {
50
+ const settings = comp.settings;
51
+ if (!settings) {
52
+ throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.GLOBAL_MENU}) is missing required settings`);
53
+ }
54
+ if (!settings.displayName) {
55
+ throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.GLOBAL_MENU}) is missing required setting: displayName`);
56
+ }
57
+ if (typeof settings.displayName !== "string") {
58
+ throw new Error(`Component "${comp.title}" (type: ${COMPONENT_TYPE.GLOBAL_MENU}) setting "displayName" must be a string`);
59
+ }
60
+ };
61
+ const validateComponentSettings = (comp) => {
62
+ switch (comp.type) {
63
+ case COMPONENT_TYPE.RECORD:
64
+ validateRecordComponentSettings(comp);
65
+ break;
66
+ case COMPONENT_TYPE.GLOBAL_MENU:
67
+ validateGlobalMenuComponentSettings(comp);
68
+ break;
69
+ default:
70
+ throw new Error(`Component "${comp.title}" has unsupported type: ${comp.type}. Supported types: ${COMPONENT_TYPE.RECORD}, ${COMPONENT_TYPE.GLOBAL_MENU}`);
71
+ }
72
+ };
23
73
  export const validateComponentBuild = async (componentPath, comp) => {
24
74
  if (!(await fs.pathExists(componentPath))) {
25
75
  throw new Error(`Component "${comp.title}" path does not exist: ${chalk.yellow(componentPath)}\n` + `Make sure the path in manifest.yml is correct.`);
@@ -31,6 +81,7 @@ export const validateComponentBuild = async (componentPath, comp) => {
31
81
  throw new Error(`Component <${comp.id}> at: /${comp.path} not found`);
32
82
  }
33
83
  }
84
+ validateComponentSettings(comp);
34
85
  };
35
86
  export const zipComponentBuild = async (componentPath, title) => {
36
87
  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "fireberry-"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fireberry/cli",
3
- "version": "0.0.5-beta.13",
3
+ "version": "0.0.5-beta.16",
4
4
  "description": "Fireberry CLI tool",
5
5
  "type": "module",
6
6
  "author": "",
@@ -31,3 +31,12 @@ export const installApp = async (manifest: Manifest): Promise<void> => {
31
31
  throw new Error(error instanceof Error ? error.message : "Unknown error");
32
32
  }
33
33
  };
34
+
35
+ export const deleteApp = async (manifest: Manifest): Promise<void> => {
36
+ const url = `/services/developer/delete`;
37
+ try {
38
+ await api.delete<void>(url, manifest);
39
+ } catch (error) {
40
+ throw new Error(error instanceof Error ? error.message : "Unknown error");
41
+ }
42
+ };
package/src/api/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { COMPONENT_TYPE } from "../constants/component-types.js";
2
+
1
3
  export interface CreateAppRequest {
2
4
  appId: string;
3
5
  }
@@ -20,7 +22,39 @@ interface ManifestApp {
20
22
  description?: string;
21
23
  }
22
24
 
23
- export interface ManifestComponent {
25
+ export interface RecordComponentSettings {
26
+ iconName: string;
27
+ iconColor: string;
28
+ objectType: number;
29
+ }
30
+
31
+ export interface GlobalMenuComponentSettings {
32
+ displayName: string;
33
+ }
34
+
35
+ export type ComponentSettings =
36
+ | RecordComponentSettings
37
+ | GlobalMenuComponentSettings;
38
+
39
+ export interface BaseManifestComponent {
40
+ title: string;
41
+ id: string;
42
+ path: string;
43
+ }
44
+
45
+ export interface RecordComponent extends BaseManifestComponent {
46
+ type: typeof COMPONENT_TYPE.RECORD;
47
+ settings: RecordComponentSettings;
48
+ }
49
+
50
+ export interface GlobalMenuComponent extends BaseManifestComponent {
51
+ type: typeof COMPONENT_TYPE.GLOBAL_MENU;
52
+ settings: GlobalMenuComponentSettings;
53
+ }
54
+
55
+ export type ManifestComponent = RecordComponent | GlobalMenuComponent;
56
+
57
+ export interface UntypedManifestComponent {
24
58
  type: string;
25
59
  title: string;
26
60
  id: string;
@@ -6,6 +6,7 @@ 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
8
  import { runInstall } from "../commands/install.js";
9
+ import { runDelete } from "../commands/delete.js";
9
10
 
10
11
  const program = new Command();
11
12
 
@@ -38,12 +39,19 @@ program
38
39
  await runPush();
39
40
  });
40
41
 
41
- program.command("install")
42
+ program.command("install")
42
43
  .description("Install app on your Fireberry account")
43
44
  .action(async () => {
44
45
  await runInstall();
45
46
  });
46
47
 
48
+ program
49
+ .command("delete")
50
+ .description("Delete a Fireberry app")
51
+ .action(async () => {
52
+ await runDelete();
53
+ });
54
+
47
55
  program.parseAsync(process.argv).catch((err: unknown) => {
48
56
  const errorMessage = err instanceof Error
49
57
  ? err.message
@@ -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,6 @@
1
+ export const COMPONENT_TYPE = {
2
+ RECORD: "record",
3
+ GLOBAL_MENU: "global-menu",
4
+ } as const;
5
+
6
+ export type ComponentType = (typeof COMPONENT_TYPE)[keyof typeof COMPONENT_TYPE];
@@ -4,7 +4,14 @@ 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
- import { Manifest, ManifestComponent, ZippedComponent } from "../api/types.js";
7
+ import {
8
+ Manifest,
9
+ ZippedComponent,
10
+ UntypedManifestComponent,
11
+ RecordComponentSettings,
12
+ GlobalMenuComponentSettings,
13
+ } from "../api/types.js";
14
+ import { COMPONENT_TYPE } from "../constants/component-types.js";
8
15
 
9
16
  export const getManifest = async (): Promise<Manifest> => {
10
17
  const manifestPath = path.join(process.cwd(), "manifest.yml");
@@ -33,9 +40,96 @@ export const getManifest = async (): Promise<Manifest> => {
33
40
  return manifest;
34
41
  };
35
42
 
43
+ const validateRecordComponentSettings = (
44
+ comp: UntypedManifestComponent
45
+ ): void => {
46
+ const settings = comp.settings as
47
+ | Partial<RecordComponentSettings>
48
+ | undefined;
49
+
50
+ if (!settings) {
51
+ throw new Error(
52
+ `Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) is missing required settings`
53
+ );
54
+ }
55
+
56
+ const requiredFields: (keyof RecordComponentSettings)[] = [
57
+ "iconName",
58
+ "iconColor",
59
+ "objectType",
60
+ ];
61
+
62
+ for (const fieldName of requiredFields) {
63
+ if (settings[fieldName] === undefined || settings[fieldName] === null) {
64
+ throw new Error(
65
+ `Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) is missing required setting: ${fieldName}`
66
+ );
67
+ }
68
+ }
69
+
70
+ if (typeof settings.iconName !== "string") {
71
+ throw new Error(
72
+ `Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) setting "iconName" must be a string`
73
+ );
74
+ }
75
+
76
+ if (typeof settings.iconColor !== "string") {
77
+ throw new Error(
78
+ `Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) setting "iconColor" must be a string`
79
+ );
80
+ }
81
+
82
+ if (typeof settings.objectType !== "number") {
83
+ throw new Error(
84
+ `Component "${comp.title}" (type: ${COMPONENT_TYPE.RECORD}) setting "objectType" must be a number`
85
+ );
86
+ }
87
+ };
88
+
89
+ const validateGlobalMenuComponentSettings = (
90
+ comp: UntypedManifestComponent
91
+ ): void => {
92
+ const settings = comp.settings as
93
+ | Partial<GlobalMenuComponentSettings>
94
+ | undefined;
95
+
96
+ if (!settings) {
97
+ throw new Error(
98
+ `Component "${comp.title}" (type: ${COMPONENT_TYPE.GLOBAL_MENU}) is missing required settings`
99
+ );
100
+ }
101
+
102
+ if (!settings.displayName) {
103
+ throw new Error(
104
+ `Component "${comp.title}" (type: ${COMPONENT_TYPE.GLOBAL_MENU}) is missing required setting: displayName`
105
+ );
106
+ }
107
+
108
+ if (typeof settings.displayName !== "string") {
109
+ throw new Error(
110
+ `Component "${comp.title}" (type: ${COMPONENT_TYPE.GLOBAL_MENU}) setting "displayName" must be a string`
111
+ );
112
+ }
113
+ };
114
+
115
+ const validateComponentSettings = (comp: UntypedManifestComponent): void => {
116
+ switch (comp.type) {
117
+ case COMPONENT_TYPE.RECORD:
118
+ validateRecordComponentSettings(comp);
119
+ break;
120
+ case COMPONENT_TYPE.GLOBAL_MENU:
121
+ validateGlobalMenuComponentSettings(comp);
122
+ break;
123
+ default:
124
+ throw new Error(
125
+ `Component "${comp.title}" has unsupported type: ${comp.type}. Supported types: ${COMPONENT_TYPE.RECORD}, ${COMPONENT_TYPE.GLOBAL_MENU}`
126
+ );
127
+ }
128
+ };
129
+
36
130
  export const validateComponentBuild = async (
37
131
  componentPath: string,
38
- comp: ManifestComponent
132
+ comp: UntypedManifestComponent
39
133
  ) => {
40
134
  if (!(await fs.pathExists(componentPath))) {
41
135
  throw new Error(
@@ -54,6 +148,8 @@ export const validateComponentBuild = async (
54
148
  throw new Error(`Component <${comp.id}> at: /${comp.path} not found`);
55
149
  }
56
150
  }
151
+
152
+ validateComponentSettings(comp);
57
153
  };
58
154
 
59
155
  export const zipComponentBuild = async (
@@ -104,7 +200,9 @@ export const zipComponentBuild = async (
104
200
  };
105
201
 
106
202
  export const validateManifestComponents = async (manifest: Manifest) => {
107
- const components = manifest.components;
203
+ const components = manifest.components as unknown as
204
+ | UntypedManifestComponent[]
205
+ | undefined;
108
206
  if (!components || components.length === 0) {
109
207
  throw new Error("No components found in manifest");
110
208
  }
@@ -124,7 +222,8 @@ export const handleComponents = async (
124
222
  manifest: Manifest
125
223
  ): Promise<ZippedComponent[]> => {
126
224
  await validateManifestComponents(manifest);
127
- const components = manifest.components!;
225
+ const components =
226
+ manifest.components as unknown as UntypedManifestComponent[];
128
227
 
129
228
  const zippedComponents: ZippedComponent[] = [];
130
229