diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 571db49..19569b3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -74,12 +74,18 @@ jobs: with: name: dist-artifact + - name: Get version from package.json + id: package_version + run: | + pkg_version=$(node -p "require('./package.json').version") + echo "VERSION=$pkg_version" >> $GITHUB_OUTPUT + - name: Create Release uses: softprops/action-gh-release@v2 with: files: dist.zip - tag_name: v${{ github.run_number }} - name: Release v${{ github.run_number }} + tag_name: v${{ steps.package_version.outputs.VERSION }} + name: Release v${{ steps.package_version.outputs.VERSION }} draft: false prerelease: false env: diff --git a/package.json b/package.json index 839f99d..9754990 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shsf-cli", - "version": "2.0.1", + "version": "2.0.2", "description": "", "type": "module", "files": [ diff --git a/src/commands.ts b/src/commands.ts index 40be99b..5e8f8d8 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -55,10 +55,21 @@ export async function resolveCommands() { const definition = module.default || Object.values(module).find((val: any) => val && val.name && val.action); if (definition && definition.name && definition.action) { - currentParent + const command = currentParent .command(definition.name) - .description(definition.description || '') - .action(definition.action); + .description(definition.description || ''); + + if (definition.options) { + definition.options.forEach((opt: any) => { + if (opt.required) { + command.requiredOption(opt.name, opt.description); + } else { + command.option(opt.name, opt.description); + } + }); + } + + command.action(definition.action); } } } diff --git a/src/commands/count/functions.ts b/src/commands/count/functions.ts new file mode 100644 index 0000000..4c1c975 --- /dev/null +++ b/src/commands/count/functions.ts @@ -0,0 +1,59 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; + +export const functionsCountDefinition = { + name: "functions", + description: "Counts the amount of functions the current user has.", + action: async () => { + await countFunctions(); + }, +}; + +async function countFunctions() { + const client = await getApiClient(); + + try { + const response = await client.get("/api/functions"); + + if (response.status === 200 && response.data.data) { + const functions = response.data.data; + const count = functions.length; + + if (count > 0) { + console.log(chalk.blue("Functions List:")); + functions.forEach((f: any) => { + const namespaceInfo = f.namespace ? chalk.gray(` [NS: ${f.namespace.id}]`) : ""; + console.log(`- ${chalk.cyan(f.name)} ${chalk.gray(`(${f.id})`)}${namespaceInfo}`); + if (f.description) { + console.log(` ${chalk.italic.gray(f.description)}`); + } + }); + console.log(""); + } + + console.log( + `${chalk.green("✓")} You have ${chalk.bgGreen.black(` ${count} `)} functions.`, + ); + } else { + console.log( + `${chalk.yellow("!")} Unexpected response from server.`, + ); + console.log(`Status Code: ${chalk.blue(response.status)}`); + } + } catch (error: any) { + if (error.response) { + console.error( + `${chalk.red("✗")} Failed to fetch function count.`, + ); + console.error(`Status Code: ${chalk.red(error.response.status)}`); + console.error( + `Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`, + ); + } else { + console.error( + `${chalk.red("✗")} Local error occurred.`, + ); + console.error(`Error: ${chalk.yellow(error.message)}`); + } + } +} diff --git a/src/commands/count/namespaces.ts b/src/commands/count/namespaces.ts new file mode 100644 index 0000000..cda9a44 --- /dev/null +++ b/src/commands/count/namespaces.ts @@ -0,0 +1,54 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; + +export const namespacesCountDefinition = { + name: "namespaces", + description: "Counts the amount of namespaces the current user has.", + action: async () => { + await countNamespaces(); + }, +}; + +async function countNamespaces() { + const client = await getApiClient(); + + try { + const response = await client.get("/api/namespaces"); + + if (response.status === 200 && response.data.data) { + const namespaces = response.data.data; + const count = namespaces.length; + + if (count > 0) { + console.log(chalk.blue("Namespaces List:")); + namespaces.forEach((ns: any) => { + console.log(`- ${chalk.cyan(ns.name)} ${chalk.gray(`(${ns.id})`)}`); + }); + console.log(""); + } + + console.log( + `${chalk.green("✓")} You have ${chalk.bgGreen.black(` ${count} `)} namespaces.`, + ); + } else { + console.log( + `${chalk.yellow("!")} Unexpected response from server.`, + ); + console.log(`Status Code: ${chalk.blue(response.status)}`); + } + } catch (error: any) { + if (error.response) { + console.error( + `${chalk.red("✗")} Failed to fetch namespace count.`, + ); + console.error(`Status Code: ${chalk.red(error.response.status)}`); + console.error( + `Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`, + ); + } else if (error.request) { + console.error(`${chalk.red("✗")} No response received from server.`); + } else { + console.error(`${chalk.red("✗")} Error:`, error.message); + } + } +} diff --git a/src/commands/count/storages.ts b/src/commands/count/storages.ts new file mode 100644 index 0000000..9a83883 --- /dev/null +++ b/src/commands/count/storages.ts @@ -0,0 +1,55 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; + +export const storagesCountDefinition = { + name: "storages", + description: "Counts the amount of storages the current user has.", + action: async () => { + await countStorages(); + }, +}; + +async function countStorages() { + const client = await getApiClient(); + + try { + const response = await client.get("/api/storage"); + + if (response.status === 200 && response.data.data) { + const storages = response.data.data; + const count = storages.length; + + if (count > 0) { + console.log(chalk.blue("Storages List:")); + storages.forEach((s: any) => { + const purposeInfo = s.purpose ? chalk.gray(` - ${s.purpose}`) : ""; + console.log(`- ${chalk.cyan(s.name)}${purposeInfo}`); + }); + console.log(""); + } + + console.log( + `${chalk.green("✓")} You have ${chalk.bgGreen.black(` ${count} `)} storages.`, + ); + } else { + console.log( + `${chalk.yellow("!")} Unexpected response from server.`, + ); + console.log(`Status Code: ${chalk.blue(response.status)}`); + } + } catch (error: any) { + if (error.response) { + console.error( + `${chalk.red("✗")} Failed to fetch storage count.`, + ); + console.error(`Status Code: ${chalk.red(error.response.status)}`); + console.error( + `Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`, + ); + } else if (error.request) { + console.error(`${chalk.red("✗")} No response received from server.`); + } else { + console.error(`${chalk.red("✗")} Error:`, error.message); + } + } +} diff --git a/src/commands/count/triggers.ts b/src/commands/count/triggers.ts new file mode 100644 index 0000000..889f3dc --- /dev/null +++ b/src/commands/count/triggers.ts @@ -0,0 +1,59 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; + +export const triggersCountDefinition = { + name: "triggers", + description: "Counts the amount of triggers the current user has.", + action: async () => { + await countTriggers(); + }, +}; + +async function countTriggers() { + const client = await getApiClient(); + + try { + const response = await client.get("/api/triggers"); + + if (response.status === 200 && response.data.data) { + const triggers = response.data.data; + const count = triggers.length; + + if (count > 0) { + console.log(chalk.blue("Triggers List:")); + triggers.forEach((t: any) => { + const functionInfo = t.function ? chalk.gray(` [Func: ${t.function.name}]`) : ""; + const cronInfo = t.cron ? chalk.yellow(` (Cron: ${t.cron})`) : ""; + console.log(`- ${chalk.cyan(t.name)} ${chalk.gray(`(${t.id})`)}${functionInfo}${cronInfo}`); + if (t.description) { + console.log(` ${chalk.italic.gray(t.description)}`); + } + }); + console.log(""); + } + + console.log( + `${chalk.green("✓")} You have ${chalk.bgGreen.black(` ${count} `)} triggers.`, + ); + } else { + console.log( + `${chalk.yellow("!")} Unexpected response from server.`, + ); + console.log(`Status Code: ${chalk.blue(response.status)}`); + } + } catch (error: any) { + if (error.response) { + console.error( + `${chalk.red("✗")} Failed to fetch trigger count.`, + ); + console.error(`Status Code: ${chalk.red(error.response.status)}`); + console.error( + `Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`, + ); + } else if (error.request) { + console.error(`${chalk.red("✗")} No response received from server.`); + } else { + console.error(`${chalk.red("✗")} Error:`, error.message); + } + } +} diff --git a/src/commands/create/function.ts b/src/commands/create/function.ts new file mode 100644 index 0000000..f20b83f --- /dev/null +++ b/src/commands/create/function.ts @@ -0,0 +1,55 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; + +export const createFunctionDefinition = { + name: "function", + description: "Create a new serverless function.", + options: [ + { name: "--name ", description: "Function name", required: true }, + { name: "--description ", description: "Function description", required: true }, + { name: "--image ", description: "Docker image tag", required: true }, + { name: "--startup-file ", description: "Startup file name", required: true }, + { name: "--namespace-id ", description: "Namespace ID", required: true }, + { name: "--execution-alias ", description: "Custom execution alias" }, + { name: "--docker-mount", description: "Enable Docker mount", type: "boolean" }, + { name: "--ffmpeg-install", description: "Install ffmpeg in container", type: "boolean" }, + { name: "--imported", description: "Marks the function as imported", type: "boolean" }, + ], + action: async (options: any) => { + const data = { + name: options.name, + description: options.description, + image: options.image, + startup_file: options.startupFile, + namespaceId: parseInt(options.namespaceId), + executionAlias: options.executionAlias, + docker_mount: !!options.dockerMount, + ffmpeg_install: !!options.ffmpegInstall, + imported: !!options.imported, + }; + + const client = await getApiClient(); + + try { + const response = await client.post("/api/function", data); + + if (response.status === 200) { + console.log( + `${chalk.green("✓")} Function ${chalk.cyan(data.name)} created successfully! ID: ${chalk.bgGreen.black(` ${response.data.data.id} `)}`, + ); + } else { + console.log( + `${chalk.yellow("!")} Unexpected response from server: ${response.status}`, + ); + } + } catch (error: any) { + if (error.response) { + console.error( + `${chalk.red("✗")} Failed to create function: ${chalk.yellow(error.response.data.message || "Unknown error")}`, + ); + } else { + console.error(`${chalk.red("✗")} Error: ${error.message}`); + } + } + }, +};