@fireberry/cli 0.0.3 → 0.0.5-beta.0

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.
@@ -0,0 +1,69 @@
1
+ name: Publish Fireberry CLI to npm
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ type:
7
+ description: "Publish type"
8
+ required: true
9
+ type: choice
10
+ options:
11
+ - beta
12
+ - production
13
+ version:
14
+ description: "Version bump (for production only)"
15
+ required: false
16
+ type: choice
17
+ options:
18
+ - patch
19
+ - minor
20
+ - major
21
+ default: patch
22
+
23
+ jobs:
24
+ publish:
25
+ runs-on: ubuntu-latest
26
+ permissions:
27
+ contents: read
28
+ id-token: write
29
+
30
+ steps:
31
+ - name: Checkout repository
32
+ uses: actions/checkout@v5
33
+
34
+ - name: Setup Node.js
35
+ uses: actions/setup-node@v4
36
+ with:
37
+ node-version: 22
38
+ registry-url: "https://registry.npmjs.org"
39
+ cache: "npm"
40
+ cache-dependency-path: ${{ github.workspace }}/package-lock.json
41
+
42
+ - name: Install dependencies
43
+ run: npm ci
44
+
45
+ - name: Update version (Beta)
46
+ if: github.event.inputs.type == 'beta'
47
+ run: npm version prerelease --preid=beta --no-git-tag-version
48
+
49
+ - name: Update version (Production)
50
+ if: github.event.inputs.type == 'production'
51
+ run: npm version ${{ github.event.inputs.version }} --no-git-tag-version
52
+
53
+ - name: Build package
54
+ run: npm run build --if-present
55
+
56
+ - name: Run tests
57
+ run: npm test
58
+
59
+ - name: Publish to npm (Beta)
60
+ if: github.event.inputs.type == 'beta'
61
+ run: npm publish --tag beta --provenance --access public
62
+ env:
63
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
64
+
65
+ - name: Publish to npm (Production)
66
+ if: github.event.inputs.type == 'production'
67
+ run: npm publish --tag latest --provenance --access public
68
+ env:
69
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,12 @@
1
+ import "../config/env.js";
2
+ import { AxiosInstance, AxiosRequestConfig } from "axios";
3
+ declare const fbApi: AxiosInstance;
4
+ export declare function sendApiRequest<T = any>(config: AxiosRequestConfig): Promise<T>;
5
+ export declare const api: {
6
+ get: <T = any>(url: string, config?: AxiosRequestConfig) => Promise<T>;
7
+ post: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) => Promise<T>;
8
+ put: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) => Promise<T>;
9
+ patch: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) => Promise<T>;
10
+ delete: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) => Promise<T>;
11
+ };
12
+ export default fbApi;
@@ -0,0 +1,60 @@
1
+ import "../config/env.js";
2
+ import axios from "axios";
3
+ import { getApiToken } from "./config.js";
4
+ import packageJson from "../../package.json" with { type: "json" };
5
+ // Determine default API URL based on version
6
+ const getDefaultApiUrl = () => {
7
+ if (process.env.FIREBERRY_API_URL) {
8
+ return process.env.FIREBERRY_API_URL;
9
+ }
10
+ const isBeta = packageJson.version.includes("beta");
11
+ if (isBeta) {
12
+ return process.env.FIREBERRY_STAGING_URL || "https://dev.fireberry.com/api/v3";
13
+ }
14
+ return "https://api.fireberry.com/api/v3";
15
+ };
16
+ const BASE_URL = getDefaultApiUrl();
17
+ const fbApi = axios.create({
18
+ baseURL: BASE_URL,
19
+ timeout: 30000,
20
+ headers: {
21
+ "Content-Type": "application/json",
22
+ "User-Agent": `@fireberry/cli@${packageJson.version}`,
23
+ },
24
+ });
25
+ fbApi.interceptors.request.use(async (config) => {
26
+ try {
27
+ const token = await getApiToken();
28
+ if (token) {
29
+ config.headers.tokenid = token;
30
+ }
31
+ }
32
+ catch (error) {
33
+ console.warn("Failed to get API token:", error);
34
+ }
35
+ return config;
36
+ }, (error) => {
37
+ return Promise.reject(error);
38
+ });
39
+ export async function sendApiRequest(config) {
40
+ try {
41
+ const response = await fbApi.request(config);
42
+ return response.data;
43
+ }
44
+ catch (error) {
45
+ if (axios.isAxiosError(error)) {
46
+ const message = error.response?.data?.message || error.message;
47
+ const status = error.response?.status;
48
+ throw new Error(`API Error${status ? ` (${status})` : ""}: ${message}`);
49
+ }
50
+ throw error;
51
+ }
52
+ }
53
+ export const api = {
54
+ get: (url, config) => sendApiRequest({ ...config, method: "GET", url }),
55
+ post: (url, data, config) => sendApiRequest({ ...config, method: "POST", url, data }),
56
+ put: (url, data, config) => sendApiRequest({ ...config, method: "PUT", url, data }),
57
+ patch: (url, data, config) => sendApiRequest({ ...config, method: "PATCH", url, data }),
58
+ delete: (url, data, config) => sendApiRequest({ ...config, method: "DELETE", url, data }),
59
+ };
60
+ export default fbApi;
@@ -0,0 +1 @@
1
+ export declare function getApiToken(): Promise<string | null>;
@@ -0,0 +1,18 @@
1
+ import envPaths from "env-paths";
2
+ import path from "node:path";
3
+ import fs from "fs-extra";
4
+ export async function getApiToken() {
5
+ try {
6
+ const paths = envPaths("Fireberry CLI", { suffix: "" });
7
+ const configFile = path.join(paths.config, "config.json");
8
+ if (!(await fs.pathExists(configFile))) {
9
+ return null;
10
+ }
11
+ const config = await fs.readJson(configFile);
12
+ return config.apiToken || null;
13
+ }
14
+ catch (error) {
15
+ console.warn("Failed to read config file:", error);
16
+ return null;
17
+ }
18
+ }
@@ -0,0 +1,3 @@
1
+ import "../config/env.js";
2
+ import type { CreateAppRequest } from "./types.js";
3
+ export declare const createApp: (data: CreateAppRequest) => Promise<void>;
@@ -0,0 +1,11 @@
1
+ import "../config/env.js";
2
+ import { api } from "./axios.js";
3
+ export const createApp = async (data) => {
4
+ const url = "/services/developer/create";
5
+ try {
6
+ await api.post(url, data);
7
+ }
8
+ catch (error) {
9
+ throw new Error(error instanceof Error ? error.message : "Unknown error");
10
+ }
11
+ };
@@ -0,0 +1,13 @@
1
+ export interface CreateAppRequest {
2
+ appId: string;
3
+ }
4
+ export interface ApiError {
5
+ message: string;
6
+ code?: string;
7
+ details?: any;
8
+ }
9
+ export interface ApiResponse<T> {
10
+ data: T;
11
+ success: boolean;
12
+ message?: string;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import chalk from "chalk";
4
+ import { runInit } from "../commands/init.js";
5
+ import { runCreate } from "../commands/create.js";
6
+ import packageJson from "../../package.json" with { type: "json" };
7
+ const program = new Command();
8
+ program
9
+ .name("fireberry")
10
+ .description("Fireberry developer CLI")
11
+ .version(packageJson.version);
12
+ program
13
+ .command("init")
14
+ .argument("[tokenid]", "Fireberry token id")
15
+ .description("Initiates credentials and stores token in local config")
16
+ .action(async (tokenid) => {
17
+ await runInit({ tokenid });
18
+ });
19
+ program
20
+ .command("create")
21
+ .argument("[name]", "App name")
22
+ .description("Create a new Fireberry app")
23
+ .action(async (name) => {
24
+ await runCreate({ name });
25
+ });
26
+ program.parseAsync(process.argv).catch((err) => {
27
+ const errorMessage = err instanceof Error
28
+ ? err.message
29
+ : typeof err === 'string'
30
+ ? err
31
+ : 'Unexpected error';
32
+ console.error(chalk.red(errorMessage));
33
+ process.exit(1);
34
+ });
@@ -0,0 +1,5 @@
1
+ interface CreateOptions {
2
+ name?: string;
3
+ }
4
+ export declare function runCreate({ name }: CreateOptions): Promise<void>;
5
+ export {};
@@ -0,0 +1,63 @@
1
+ import inquirer from "inquirer";
2
+ import path from "node:path";
3
+ import fs from "fs-extra";
4
+ import { v4 as uuidv4 } from "uuid";
5
+ import { fileURLToPath } from "node:url";
6
+ import ora from "ora";
7
+ import chalk from "chalk";
8
+ import { createApp } from "../api/requests.js";
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ function slugifyName(name) {
12
+ const validPattern = /^[a-zA-Z0-9_-]+$/;
13
+ if (!validPattern.test(name)) {
14
+ throw new Error(`Invalid app name: "${name}". Only alphanumeric characters, underscores, and hyphens are allowed.`);
15
+ }
16
+ return name
17
+ .toLowerCase()
18
+ .replace(/[^a-z0-9]+/g, "-")
19
+ .replace(/-+/g, "-")
20
+ .replace(/^-|-$/g, "");
21
+ }
22
+ export async function runCreate({ name }) {
23
+ let appName = name;
24
+ if (!appName) {
25
+ const answers = await inquirer.prompt([
26
+ { type: "input", name: "name", message: "App name" },
27
+ ]);
28
+ appName = (answers.name || "").trim();
29
+ }
30
+ if (!appName) {
31
+ throw new Error("App name is required.");
32
+ }
33
+ const slug = slugifyName(appName);
34
+ const appId = uuidv4();
35
+ const appDir = path.resolve(process.cwd(), slug);
36
+ if (await fs.pathExists(appDir)) {
37
+ throw new Error(`Directory already exists: ${chalk.yellow(appDir)}`);
38
+ }
39
+ const spinner = ora(`Creating app "${chalk.cyan(appName)}"...`).start();
40
+ try {
41
+ await createApp({ appId });
42
+ await fs.ensureDir(appDir);
43
+ const templatesDir = path.join(__dirname, "..", "..", "src", "templates");
44
+ const manifestTemplate = await fs.readFile(path.join(templatesDir, "manifest.yml"), "utf-8");
45
+ const htmlTemplate = await fs.readFile(path.join(templatesDir, "index.html"), "utf-8");
46
+ const manifestContent = manifestTemplate
47
+ .replace(/{{appName}}/g, appName)
48
+ .replace(/{ { appId } }/g, appId);
49
+ const htmlContent = htmlTemplate.replace(/{{appName}}/g, appName);
50
+ await fs.writeFile(path.join(appDir, "manifest.yml"), manifestContent);
51
+ await fs.writeFile(path.join(appDir, "index.html"), htmlContent);
52
+ spinner.succeed(`Successfully created "${chalk.cyan(appName)}" app!`);
53
+ console.log(chalk.gray(`šŸ“ Location: ${appDir}`));
54
+ console.log(chalk.gray(`App ID: ${appId}`));
55
+ console.log(chalk.green("\nšŸŽ‰ Your app is ready! Next steps:"));
56
+ console.log(chalk.white(` cd ${slug}`));
57
+ console.log(chalk.white(" # Start developing your Fireberry app"));
58
+ }
59
+ catch (error) {
60
+ spinner.fail(`Failed to create app "${chalk.cyan(appName)}"`);
61
+ throw error;
62
+ }
63
+ }
@@ -0,0 +1,9 @@
1
+ interface InitOptions {
2
+ tokenid?: string;
3
+ }
4
+ export interface Config {
5
+ apiToken: string;
6
+ createdAt: string;
7
+ }
8
+ export declare function runInit({ tokenid }?: InitOptions): Promise<void>;
9
+ export {};
@@ -0,0 +1,41 @@
1
+ import inquirer from "inquirer";
2
+ import envPaths from "env-paths";
3
+ import path from "node:path";
4
+ import fs from "fs-extra";
5
+ import ora from "ora";
6
+ import chalk from "chalk";
7
+ export async function runInit({ tokenid } = {}) {
8
+ let token = tokenid;
9
+ if (!token) {
10
+ const answers = await inquirer.prompt([
11
+ {
12
+ type: "password",
13
+ name: "token",
14
+ message: "Enter Fireberry token id",
15
+ mask: "*",
16
+ },
17
+ ]);
18
+ token = answers.token?.trim();
19
+ }
20
+ if (!token) {
21
+ throw new Error("An access token must be provided.");
22
+ }
23
+ const paths = envPaths("Fireberry CLI", { suffix: "" });
24
+ const configDir = paths.config;
25
+ const configFile = path.join(configDir, "config.json");
26
+ const spinner = ora("Saving API Token to local config").start();
27
+ try {
28
+ await fs.ensureDir(configDir);
29
+ const config = {
30
+ apiToken: token,
31
+ createdAt: new Date().toISOString(),
32
+ };
33
+ await fs.writeJson(configFile, config, { spaces: 2 });
34
+ spinner.succeed("Initialized. Token stored locally.");
35
+ console.log(chalk.gray(`Config: ${configFile}`));
36
+ }
37
+ catch (err) {
38
+ spinner.fail("Failed to save token.");
39
+ throw err;
40
+ }
41
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import { config } from "dotenv";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
6
+ const projectRoot = path.resolve(__dirname, "..", "..");
7
+ const envPath = path.join(projectRoot, ".env");
8
+ config({ path: envPath, quiet: true });
package/package.json CHANGED
@@ -1,18 +1,27 @@
1
1
  {
2
2
  "name": "@fireberry/cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.5-beta.0",
4
4
  "description": "Fireberry CLI tool",
5
5
  "type": "module",
6
6
  "author": "",
7
7
  "license": "MIT",
8
8
  "bin": {
9
- "fireberry": "bin/fireberry.js"
9
+ "fireberry": "dist/bin/fireberry.js"
10
10
  },
11
11
  "engines": {
12
12
  "node": ">=18"
13
13
  },
14
14
  "scripts": {
15
- "start": "node bin/fireberry.js"
15
+ "start": "node dist/bin/fireberry.js",
16
+ "build": "tsc",
17
+ "dev": "tsc --watch",
18
+ "clean": "rm -rf dist",
19
+ "prebuild": "npm run clean",
20
+ "prepare": "npm run build",
21
+ "test": "node test/smoke.test.js",
22
+ "version:beta": "npm version prerelease --preid=beta",
23
+ "publish:beta": "npm run version:beta && npm publish --tag beta",
24
+ "publish:prod": "npm publish --tag latest"
16
25
  },
17
26
  "keywords": [
18
27
  "cli",
@@ -31,11 +40,21 @@
31
40
  "access": "public"
32
41
  },
33
42
  "dependencies": {
43
+ "axios": "^1.12.2",
34
44
  "chalk": "^5.3.0",
35
45
  "commander": "^12.1.0",
46
+ "dotenv": "^17.2.3",
36
47
  "env-paths": "^3.0.0",
37
48
  "fs-extra": "^11.2.0",
38
49
  "inquirer": "^9.2.12",
39
- "ora": "^8.0.1"
50
+ "ora": "^8.0.1",
51
+ "uuid": "^13.0.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/fs-extra": "^11.0.4",
55
+ "@types/inquirer": "^9.0.7",
56
+ "@types/node": "^20.11.24",
57
+ "@types/uuid": "^10.0.0",
58
+ "typescript": "^5.3.3"
40
59
  }
41
60
  }
@@ -0,0 +1,83 @@
1
+ import "../config/env.js";
2
+ import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
3
+ import { getApiToken } from "./config.js";
4
+ import packageJson from "../../package.json" with { type: "json" };
5
+
6
+ // Determine default API URL based on version
7
+ const getDefaultApiUrl = () => {
8
+ if (process.env.FIREBERRY_API_URL) {
9
+ return process.env.FIREBERRY_API_URL;
10
+ }
11
+
12
+ const isBeta = packageJson.version.includes("beta");
13
+
14
+ if (isBeta) {
15
+ return process.env.FIREBERRY_STAGING_URL || "https://dev.fireberry.com/api/v3";
16
+ }
17
+
18
+ return "https://api.fireberry.com/api/v3";
19
+ };
20
+
21
+ const BASE_URL = getDefaultApiUrl();
22
+
23
+ const fbApi: AxiosInstance = axios.create({
24
+ baseURL: BASE_URL,
25
+ timeout: 30000,
26
+ headers: {
27
+ "Content-Type": "application/json",
28
+ "User-Agent": `@fireberry/cli@${packageJson.version}`,
29
+ },
30
+ });
31
+
32
+ fbApi.interceptors.request.use(
33
+ async (config) => {
34
+ try {
35
+ const token = await getApiToken();
36
+ if (token) {
37
+ config.headers.tokenid = token;
38
+ }
39
+ } catch (error) {
40
+ console.warn("Failed to get API token:", error);
41
+ }
42
+ return config;
43
+ },
44
+ (error) => {
45
+ return Promise.reject(error);
46
+ }
47
+ );
48
+
49
+ export async function sendApiRequest<T = any>(
50
+ config: AxiosRequestConfig
51
+ ): Promise<T> {
52
+ try {
53
+ const response = await fbApi.request<T>(config);
54
+
55
+ return response.data;
56
+ } catch (error) {
57
+ if (axios.isAxiosError(error)) {
58
+ const message = error.response?.data?.message || error.message;
59
+ const status = error.response?.status;
60
+ throw new Error(`API Error${status ? ` (${status})` : ""}: ${message}`);
61
+ }
62
+ throw error;
63
+ }
64
+ }
65
+
66
+ export const api = {
67
+ get: <T = any>(url: string, config?: AxiosRequestConfig) =>
68
+ sendApiRequest<T>({ ...config, method: "GET", url }),
69
+
70
+ post: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) =>
71
+ sendApiRequest<T>({ ...config, method: "POST", url, data }),
72
+
73
+ put: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) =>
74
+ sendApiRequest<T>({ ...config, method: "PUT", url, data }),
75
+
76
+ patch: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) =>
77
+ sendApiRequest<T>({ ...config, method: "PATCH", url, data }),
78
+
79
+ delete: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) =>
80
+ sendApiRequest<T>({ ...config, method: "DELETE", url, data }),
81
+ };
82
+
83
+ export default fbApi;
@@ -0,0 +1,21 @@
1
+ import envPaths from "env-paths";
2
+ import path from "node:path";
3
+ import fs from "fs-extra";
4
+ import type { Config } from "../commands/init.js";
5
+
6
+ export async function getApiToken(): Promise<string | null> {
7
+ try {
8
+ const paths = envPaths("Fireberry CLI", { suffix: "" });
9
+ const configFile = path.join(paths.config, "config.json");
10
+
11
+ if (!(await fs.pathExists(configFile))) {
12
+ return null;
13
+ }
14
+
15
+ const config: Config = await fs.readJson(configFile);
16
+ return config.apiToken || null;
17
+ } catch (error) {
18
+ console.warn("Failed to read config file:", error);
19
+ return null;
20
+ }
21
+ }
@@ -0,0 +1,12 @@
1
+ import "../config/env.js";
2
+ import { api } from "./axios.js";
3
+ import type { CreateAppRequest } from "./types.js";
4
+
5
+ export const createApp = async (data: CreateAppRequest): Promise<void> => {
6
+ const url = "/services/developer/create";
7
+ try {
8
+ await api.post(url, data);
9
+ } catch (error) {
10
+ throw new Error(error instanceof Error ? error.message : "Unknown error");
11
+ }
12
+ };
@@ -0,0 +1,15 @@
1
+ export interface CreateAppRequest {
2
+ appId: string;
3
+ }
4
+
5
+ export interface ApiError {
6
+ message: string;
7
+ code?: string;
8
+ details?: any;
9
+ }
10
+
11
+ export interface ApiResponse<T> {
12
+ data: T;
13
+ success: boolean;
14
+ message?: string;
15
+ }
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import chalk from "chalk";
4
+ import { runInit } from "../commands/init.js";
5
+ import { runCreate } from "../commands/create.js";
6
+ import packageJson from "../../package.json" with { type: "json" };
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .name("fireberry")
12
+ .description("Fireberry developer CLI")
13
+ .version(packageJson.version);
14
+
15
+ program
16
+ .command("init")
17
+ .argument("[tokenid]", "Fireberry token id")
18
+ .description("Initiates credentials and stores token in local config")
19
+ .action(async (tokenid?: string) => {
20
+ await runInit({ tokenid });
21
+ });
22
+
23
+ program
24
+ .command("create")
25
+ .argument("[name]", "App name")
26
+ .description("Create a new Fireberry app")
27
+ .action(async (name?: string) => {
28
+ await runCreate({ name });
29
+ });
30
+
31
+ program.parseAsync(process.argv).catch((err: unknown) => {
32
+ const errorMessage = err instanceof Error
33
+ ? err.message
34
+ : typeof err === 'string'
35
+ ? err
36
+ : 'Unexpected error';
37
+ console.error(chalk.red(errorMessage));
38
+ process.exit(1);
39
+ });
@@ -0,0 +1,88 @@
1
+ import inquirer from "inquirer";
2
+ import path from "node:path";
3
+ import fs from "fs-extra";
4
+ import { v4 as uuidv4 } from "uuid";
5
+ import { fileURLToPath } from "node:url";
6
+ import ora from "ora";
7
+ import chalk from "chalk";
8
+ import { createApp } from "../api/requests.js";
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ interface CreateOptions {
14
+ name?: string;
15
+ }
16
+
17
+ function slugifyName(name: string) {
18
+ const validPattern = /^[a-zA-Z0-9_-]+$/;
19
+ if (!validPattern.test(name)) {
20
+ throw new Error(
21
+ `Invalid app name: "${name}". Only alphanumeric characters, underscores, and hyphens are allowed.`
22
+ );
23
+ }
24
+
25
+ return name
26
+ .toLowerCase()
27
+ .replace(/[^a-z0-9]+/g, "-")
28
+ .replace(/-+/g, "-")
29
+ .replace(/^-|-$/g, "");
30
+ }
31
+
32
+ export async function runCreate({ name }: CreateOptions): Promise<void> {
33
+ let appName = name;
34
+ if (!appName) {
35
+ const answers = await inquirer.prompt([
36
+ { type: "input", name: "name", message: "App name" },
37
+ ]);
38
+ appName = (answers.name || "").trim();
39
+ }
40
+ if (!appName) {
41
+ throw new Error("App name is required.");
42
+ }
43
+
44
+ const slug = slugifyName(appName);
45
+ const appId = uuidv4();
46
+ const appDir = path.resolve(process.cwd(), slug);
47
+
48
+ if (await fs.pathExists(appDir)) {
49
+ throw new Error(`Directory already exists: ${chalk.yellow(appDir)}`);
50
+ }
51
+
52
+ const spinner = ora(`Creating app "${chalk.cyan(appName)}"...`).start();
53
+
54
+ try {
55
+ await createApp({ appId });
56
+
57
+ await fs.ensureDir(appDir);
58
+
59
+ const templatesDir = path.join(__dirname, "..", "..", "src", "templates");
60
+ const manifestTemplate = await fs.readFile(
61
+ path.join(templatesDir, "manifest.yml"),
62
+ "utf-8"
63
+ );
64
+ const htmlTemplate = await fs.readFile(
65
+ path.join(templatesDir, "index.html"),
66
+ "utf-8"
67
+ );
68
+
69
+ const manifestContent = manifestTemplate
70
+ .replace(/{{appName}}/g, appName)
71
+ .replace(/{ { appId } }/g, appId);
72
+
73
+ const htmlContent = htmlTemplate.replace(/{{appName}}/g, appName);
74
+
75
+ await fs.writeFile(path.join(appDir, "manifest.yml"), manifestContent);
76
+ await fs.writeFile(path.join(appDir, "index.html"), htmlContent);
77
+
78
+ spinner.succeed(`Successfully created "${chalk.cyan(appName)}" app!`);
79
+ console.log(chalk.gray(`šŸ“ Location: ${appDir}`));
80
+ console.log(chalk.gray(`App ID: ${appId}`));
81
+ console.log(chalk.green("\nšŸŽ‰ Your app is ready! Next steps:"));
82
+ console.log(chalk.white(` cd ${slug}`));
83
+ console.log(chalk.white(" # Start developing your Fireberry app"));
84
+ } catch (error) {
85
+ spinner.fail(`Failed to create app "${chalk.cyan(appName)}"`);
86
+ throw error;
87
+ }
88
+ }
@@ -5,7 +5,16 @@ import fs from "fs-extra";
5
5
  import ora from "ora";
6
6
  import chalk from "chalk";
7
7
 
8
- export async function runInit({ tokenid } = {}) {
8
+ interface InitOptions {
9
+ tokenid?: string;
10
+ }
11
+
12
+ export interface Config {
13
+ apiToken: string;
14
+ createdAt: string;
15
+ }
16
+
17
+ export async function runInit({ tokenid }: InitOptions = {}): Promise<void> {
9
18
  let token = tokenid;
10
19
  if (!token) {
11
20
  const answers = await inquirer.prompt([
@@ -26,15 +35,15 @@ export async function runInit({ tokenid } = {}) {
26
35
  const configDir = paths.config;
27
36
  const configFile = path.join(configDir, "config.json");
28
37
 
29
- const spinner = ora("Saving token to local config (demo)").start();
38
+ const spinner = ora("Saving API Token to local config").start();
30
39
  try {
31
40
  await fs.ensureDir(configDir);
32
- const config = {
33
- token,
41
+ const config: Config = {
42
+ apiToken: token,
34
43
  createdAt: new Date().toISOString(),
35
44
  };
36
45
  await fs.writeJson(configFile, config, { spaces: 2 });
37
- spinner.succeed("Initialized. Token stored locally for demo purposes.");
46
+ spinner.succeed("Initialized. Token stored locally.");
38
47
  console.log(chalk.gray(`Config: ${configFile}`));
39
48
  } catch (err) {
40
49
  spinner.fail("Failed to save token.");
@@ -0,0 +1,10 @@
1
+ import { config } from "dotenv";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const projectRoot = path.resolve(__dirname, "..", "..");
8
+ const envPath = path.join(projectRoot, ".env");
9
+
10
+ config({ path: envPath, quiet: true });
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>{{appName}}</title>
8
+ </head>
9
+
10
+ <body>
11
+ <h1>Hello World</h1>
12
+ <p>Welcome to {{appName}}!</p>
13
+ </body>
14
+
15
+ </html>
@@ -0,0 +1,10 @@
1
+ app:
2
+ id: "{{appId}}"
3
+ name: "{{appName}}"
4
+ description: ""
5
+ components:
6
+ - type: record
7
+ title: my-first-component
8
+ key: comp
9
+ path: static/comp/build
10
+ settings: {}
@@ -0,0 +1,8 @@
1
+ import { execSync } from "child_process";
2
+
3
+ try {
4
+ execSync("node ./dist/bin/fireberry.js --help", { stdio: "pipe" });
5
+ process.exit(0);
6
+ } catch (error) {
7
+ process.exit(1);
8
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "resolveJsonModule": true
14
+ },
15
+ "include": ["src/**/*", "bin/**/*", "package.json"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }
package/bin/fireberry.js DELETED
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Command } from "commander";
3
- import chalk from "chalk";
4
- import { runInit } from "../src/commands/init.js";
5
-
6
- const program = new Command();
7
-
8
- program
9
- .name("fireberry")
10
- .description("Fireberry developer CLI (demo)")
11
- .version("0.0.1");
12
-
13
- program
14
- .command("init")
15
- .argument("[tokenid]", "Fireberry token id")
16
- .description("Initiates credentials and stores token in local config (demo)")
17
- .action(async (tokenid) => {
18
- await runInit({ tokenid });
19
- });
20
-
21
- program.parseAsync(process.argv).catch((err) => {
22
- console.error(chalk.red(err?.message || "Unexpected error"));
23
- process.exit(1);
24
- });