@fireberry/cli 0.0.4 ā 0.0.5-beta.3
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/.github/workflows/main.yml +25 -5
- package/dist/api/axios.d.ts +12 -0
- package/dist/api/axios.js +60 -0
- package/dist/api/config.d.ts +1 -0
- package/dist/api/config.js +18 -0
- package/dist/api/requests.d.ts +3 -0
- package/dist/api/requests.js +11 -0
- package/dist/api/types.d.ts +13 -0
- package/dist/api/types.js +1 -0
- package/dist/bin/fireberry.js +8 -0
- package/dist/commands/create.d.ts +5 -0
- package/dist/commands/create.js +63 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/config/env.d.ts +1 -0
- package/dist/config/env.js +8 -0
- package/package.json +10 -3
- package/src/api/axios.ts +83 -0
- package/src/api/config.ts +21 -0
- package/src/api/requests.ts +12 -0
- package/src/api/types.ts +15 -0
- package/src/bin/fireberry.ts +9 -0
- package/src/commands/create.ts +88 -0
- package/src/commands/init.ts +1 -1
- package/src/config/env.ts +10 -0
- package/src/templates/index.html +15 -0
- package/src/templates/manifest.yml +10 -0
- package/bin/fireberry.js +0 -24
|
@@ -3,14 +3,22 @@ name: Publish Fireberry CLI to npm
|
|
|
3
3
|
on:
|
|
4
4
|
workflow_dispatch:
|
|
5
5
|
inputs:
|
|
6
|
-
|
|
7
|
-
description: "
|
|
6
|
+
type:
|
|
7
|
+
description: "Publish type"
|
|
8
8
|
required: true
|
|
9
9
|
type: choice
|
|
10
|
+
options:
|
|
11
|
+
- beta
|
|
12
|
+
- production
|
|
13
|
+
version:
|
|
14
|
+
description: "Version bump (for production only)"
|
|
15
|
+
required: false
|
|
16
|
+
type: choice
|
|
10
17
|
options:
|
|
11
18
|
- patch
|
|
12
19
|
- minor
|
|
13
20
|
- major
|
|
21
|
+
default: patch
|
|
14
22
|
|
|
15
23
|
jobs:
|
|
16
24
|
publish:
|
|
@@ -34,7 +42,12 @@ jobs:
|
|
|
34
42
|
- name: Install dependencies
|
|
35
43
|
run: npm ci
|
|
36
44
|
|
|
37
|
-
- name: Update version
|
|
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'
|
|
38
51
|
run: npm version ${{ github.event.inputs.version }} --no-git-tag-version
|
|
39
52
|
|
|
40
53
|
- name: Build package
|
|
@@ -43,7 +56,14 @@ jobs:
|
|
|
43
56
|
- name: Run tests
|
|
44
57
|
run: npm test
|
|
45
58
|
|
|
46
|
-
- name: Publish to npm
|
|
47
|
-
|
|
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
|
|
48
68
|
env:
|
|
49
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,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 @@
|
|
|
1
|
+
export {};
|
package/dist/bin/fireberry.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { runInit } from "../commands/init.js";
|
|
5
|
+
import { runCreate } from "../commands/create.js";
|
|
5
6
|
import packageJson from "../../package.json" with { type: "json" };
|
|
6
7
|
const program = new Command();
|
|
7
8
|
program
|
|
@@ -15,6 +16,13 @@ program
|
|
|
15
16
|
.action(async (tokenid) => {
|
|
16
17
|
await runInit({ tokenid });
|
|
17
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
|
+
});
|
|
18
26
|
program.parseAsync(process.argv).catch((err) => {
|
|
19
27
|
const errorMessage = err instanceof Error
|
|
20
28
|
? err.message
|
|
@@ -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
|
+
}
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fireberry/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5-beta.3",
|
|
4
4
|
"description": "Fireberry CLI tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
"clean": "rm -rf dist",
|
|
19
19
|
"prebuild": "npm run clean",
|
|
20
20
|
"prepare": "npm run build",
|
|
21
|
-
"test": "node test/smoke.test.js"
|
|
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"
|
|
22
25
|
},
|
|
23
26
|
"keywords": [
|
|
24
27
|
"cli",
|
|
@@ -37,17 +40,21 @@
|
|
|
37
40
|
"access": "public"
|
|
38
41
|
},
|
|
39
42
|
"dependencies": {
|
|
43
|
+
"axios": "^1.12.2",
|
|
40
44
|
"chalk": "^5.3.0",
|
|
41
45
|
"commander": "^12.1.0",
|
|
46
|
+
"dotenv": "^17.2.3",
|
|
42
47
|
"env-paths": "^3.0.0",
|
|
43
48
|
"fs-extra": "^11.2.0",
|
|
44
49
|
"inquirer": "^9.2.12",
|
|
45
|
-
"ora": "^8.0.1"
|
|
50
|
+
"ora": "^8.0.1",
|
|
51
|
+
"uuid": "^13.0.0"
|
|
46
52
|
},
|
|
47
53
|
"devDependencies": {
|
|
48
54
|
"@types/fs-extra": "^11.0.4",
|
|
49
55
|
"@types/inquirer": "^9.0.7",
|
|
50
56
|
"@types/node": "^20.11.24",
|
|
57
|
+
"@types/uuid": "^10.0.0",
|
|
51
58
|
"typescript": "^5.3.3"
|
|
52
59
|
}
|
|
53
60
|
}
|
package/src/api/axios.ts
ADDED
|
@@ -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
|
+
};
|
package/src/api/types.ts
ADDED
package/src/bin/fireberry.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { runInit } from "../commands/init.js";
|
|
5
|
+
import { runCreate } from "../commands/create.js";
|
|
5
6
|
import packageJson from "../../package.json" with { type: "json" };
|
|
6
7
|
|
|
7
8
|
const program = new Command();
|
|
@@ -19,6 +20,14 @@ program
|
|
|
19
20
|
await runInit({ tokenid });
|
|
20
21
|
});
|
|
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
|
+
|
|
22
31
|
program.parseAsync(process.argv).catch((err: unknown) => {
|
|
23
32
|
const errorMessage = err instanceof Error
|
|
24
33
|
? err.message
|
|
@@ -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
|
+
}
|
package/src/commands/init.ts
CHANGED
|
@@ -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>
|
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")
|
|
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")
|
|
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
|
-
});
|