Add function exec-url command
This commit is contained in:
@@ -30,6 +30,7 @@ this will check the health, and if not setup, it will prompt you to set up the C
|
||||
- `shsf delete trigger <functionId> <triggerId>`: Deletes a specific trigger from a function.
|
||||
|
||||
- `shsf get function <id>`: Get details of a specific function by its ID.
|
||||
- `shsf get exec-url [--id <id>]`: Get the execution URL for a function. Falls back to `.shsf.json` for the function ID and also prints the alias URL when the function has an `executionAlias`.
|
||||
- `shsf get namespace <id>`: Get details of a specific namespace by its ID
|
||||
- `shsf get trigger <functionId> <triggerId>`: Get details of a specific trigger from a function.
|
||||
|
||||
@@ -81,6 +82,12 @@ You can also create a `.shsf.json` mapping file in the repository root so you do
|
||||
|
||||
Command-line flags take precedence over values in `.shsf.json`.
|
||||
|
||||
This mapping can also be used with `shsf get exec-url`, so from a mapped function directory you can run:
|
||||
|
||||
```bash
|
||||
shsf get exec-url
|
||||
```
|
||||
|
||||
## Instructions
|
||||
Use these commands for when you need to interact with shsf from the command line. Its faster than using the ui for almost all ops.
|
||||
|
||||
@@ -96,6 +103,18 @@ After creating a function and receiving an ID (for example, 81), you can share t
|
||||
[UI_URL]/functions/[ID]
|
||||
```
|
||||
|
||||
To get the function execution URL from the CLI:
|
||||
|
||||
```bash
|
||||
shsf get exec-url --id 81
|
||||
```
|
||||
|
||||
If the function has an execution alias configured, the CLI also prints the alias form:
|
||||
|
||||
```text
|
||||
[API_URL]/exec/[executionAlias]
|
||||
```
|
||||
|
||||
## Update
|
||||
Update with your package manager of choice. ALWAYS pnpm:
|
||||
```bash
|
||||
|
||||
@@ -46,6 +46,22 @@ Quickly see if the system is up and running:
|
||||
shsf health
|
||||
```
|
||||
|
||||
### 🔗 Execution URL
|
||||
|
||||
Get a function's execution URL by ID:
|
||||
|
||||
```bash
|
||||
shsf get exec-url --id <id>
|
||||
```
|
||||
|
||||
If your repo has a `.shsf.json` mapping with an `id`, you can omit `--id`:
|
||||
|
||||
```bash
|
||||
shsf get exec-url
|
||||
```
|
||||
|
||||
The command prints the standard execution URL and, when available, the alias URL based on the function's `executionAlias`.
|
||||
|
||||
### 🏗️ Local Development
|
||||
|
||||
If you're contributing or running from source:
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildFunctionExecUrls } from "../utils/function_exec_url.js";
|
||||
|
||||
describe("buildFunctionExecUrls", () => {
|
||||
it("builds the ID-based execution URL and trims trailing slashes", () => {
|
||||
expect(
|
||||
buildFunctionExecUrls("https://example.com/", {
|
||||
namespaceId: 4,
|
||||
executionId: "abc-123",
|
||||
}),
|
||||
).toEqual({
|
||||
executionUrl: "https://example.com/api/exec/4/abc-123",
|
||||
});
|
||||
});
|
||||
|
||||
it("includes the alias URL when the function has an execution alias", () => {
|
||||
expect(
|
||||
buildFunctionExecUrls("https://example.com", {
|
||||
namespaceId: 7,
|
||||
executionId: "uuid-456",
|
||||
executionAlias: "friendly-name",
|
||||
}),
|
||||
).toEqual({
|
||||
executionUrl: "https://example.com/api/exec/7/uuid-456",
|
||||
aliasUrl: "https://example.com/exec/friendly-name",
|
||||
});
|
||||
});
|
||||
|
||||
it("throws when required execution URL fields are missing", () => {
|
||||
expect(() =>
|
||||
buildFunctionExecUrls("https://example.com", {
|
||||
executionAlias: "friendly-name",
|
||||
}),
|
||||
).toThrow("Function response is missing execution URL fields.");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import chalk from "chalk";
|
||||
import { getApiClient } from "../../api.js";
|
||||
import { loadConfig } from "../../config.js";
|
||||
import { resolveFunctionId } from "../../utils/function_commands.js";
|
||||
import { buildFunctionExecUrls } from "../../utils/function_exec_url.js";
|
||||
|
||||
export const getExecUrlDefinition = {
|
||||
name: "exec-url",
|
||||
description: "Get a function execution URL by its ID.",
|
||||
options: [{ name: "--id <id>", description: "Function ID (falls back to .shsf.json default id)" }],
|
||||
action: async (options: { id?: string }) => {
|
||||
const functionId = resolveFunctionId(options);
|
||||
if (!functionId) return;
|
||||
|
||||
await getExecUrl(functionId);
|
||||
},
|
||||
};
|
||||
|
||||
async function getExecUrl(functionId: string): Promise<void> {
|
||||
const client = await getApiClient();
|
||||
const config = await loadConfig();
|
||||
|
||||
try {
|
||||
const response = await client.get(`/api/function/${functionId}`);
|
||||
const functionData = response.data?.data;
|
||||
|
||||
if (!functionData) {
|
||||
console.error(`${chalk.red("✗")} Unexpected response from server.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!functionData.allow_http) {
|
||||
console.error(`${chalk.red("✗")} HTTP execution is not allowed for function ${chalk.yellow(functionId)}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const urls = buildFunctionExecUrls(config.SHSF_INSTANCE, functionData);
|
||||
|
||||
console.log(`${chalk.blue("Execution URL")} for function ${chalk.cyan(functionId)}:`);
|
||||
console.log(chalk.bgGreen.black(` ${urls.executionUrl} `));
|
||||
|
||||
if (urls.aliasUrl) {
|
||||
console.log(`${chalk.blue("Alias URL")} for function ${chalk.cyan(functionId)}:`);
|
||||
console.log(chalk.bgGreen.black(` ${urls.aliasUrl} `));
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
if (error.response.status === 404) {
|
||||
console.error(`${chalk.red("✗")} Function with ID ${chalk.yellow(functionId)} not found.`);
|
||||
} else {
|
||||
console.error(`${chalk.red("✗")} Failed to fetch function details.`);
|
||||
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: ${chalk.yellow(error.message)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,9 @@
|
||||
import chalk from "chalk";
|
||||
import { getApiClient } from "../api.js";
|
||||
import { readMappingFile } from "./push_helpers.js";
|
||||
import { parseCorsOriginsOption } from "./cors.js";
|
||||
import { resolveFunctionId } from "./function_commands.js";
|
||||
|
||||
export function resolveFunctionId(options: { id?: string }): string | null {
|
||||
if (options.id) {
|
||||
return options.id;
|
||||
}
|
||||
|
||||
const mapping = readMappingFile();
|
||||
if (mapping?.id) {
|
||||
console.log(chalk.blue(`Using mapped id ${mapping.id} from .shsf.json`));
|
||||
return mapping.id;
|
||||
}
|
||||
|
||||
console.error(
|
||||
`${chalk.red("✗")} Function ID is required. Use ${chalk.cyan("--id <id>")} or provide an ${chalk.cyan(".shsf.json")} mapping with an ${chalk.cyan("id")}.`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
export { resolveFunctionId } from "./function_commands.js";
|
||||
|
||||
export function validateSingleOrigin(origin: string): { origin?: string; error?: string } {
|
||||
const parsed = parseCorsOriginsOption(origin);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import chalk from "chalk";
|
||||
import { readMappingFile } from "./push_helpers.js";
|
||||
|
||||
export function resolveFunctionId(options: { id?: string }): string | null {
|
||||
if (options.id) {
|
||||
return options.id;
|
||||
}
|
||||
|
||||
const mapping = readMappingFile();
|
||||
if (mapping?.id) {
|
||||
console.log(chalk.blue(`Using mapped id ${mapping.id} from .shsf.json`));
|
||||
return mapping.id;
|
||||
}
|
||||
|
||||
console.error(
|
||||
`${chalk.red("✗")} Function ID is required. Use ${chalk.cyan("--id <id>")} or provide an ${chalk.cyan(".shsf.json")} mapping with an ${chalk.cyan("id")}.`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
export interface FunctionExecUrlInput {
|
||||
namespaceId?: number | string;
|
||||
executionId?: string;
|
||||
executionAlias?: string | null;
|
||||
}
|
||||
|
||||
export interface FunctionExecUrls {
|
||||
executionUrl: string;
|
||||
aliasUrl?: string;
|
||||
}
|
||||
|
||||
function normalizeBaseUrl(instanceUrl: string): string {
|
||||
return instanceUrl.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
export function buildFunctionExecUrls(
|
||||
instanceUrl: string,
|
||||
functionData: FunctionExecUrlInput,
|
||||
): FunctionExecUrls {
|
||||
const baseUrl = normalizeBaseUrl(instanceUrl);
|
||||
|
||||
if (functionData.namespaceId === undefined || !functionData.executionId) {
|
||||
throw new Error("Function response is missing execution URL fields.");
|
||||
}
|
||||
|
||||
const urls: FunctionExecUrls = {
|
||||
executionUrl: `${baseUrl}/api/exec/${functionData.namespaceId}/${functionData.executionId}`,
|
||||
};
|
||||
|
||||
if (functionData.executionAlias) {
|
||||
urls.aliasUrl = `${baseUrl}/exec/${functionData.executionAlias}`;
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
Reference in New Issue
Block a user