Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0434376b83 | |||
| 681a3cd6df |
@@ -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 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 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 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.
|
- `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`.
|
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
|
## 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.
|
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]
|
[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
|
||||||
Update with your package manager of choice. ALWAYS pnpm:
|
Update with your package manager of choice. ALWAYS pnpm:
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -46,6 +46,22 @@ Quickly see if the system is up and running:
|
|||||||
shsf health
|
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
|
### 🏗️ Local Development
|
||||||
|
|
||||||
If you're contributing or running from source:
|
If you're contributing or running from source:
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "shsf-cli",
|
"name": "shsf-cli",
|
||||||
"version": "2.3.0",
|
"version": "2.3.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -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 chalk from "chalk";
|
||||||
import { getApiClient } from "../api.js";
|
import { getApiClient } from "../api.js";
|
||||||
import { readMappingFile } from "./push_helpers.js";
|
|
||||||
import { parseCorsOriginsOption } from "./cors.js";
|
import { parseCorsOriginsOption } from "./cors.js";
|
||||||
|
import { resolveFunctionId } from "./function_commands.js";
|
||||||
|
|
||||||
export function resolveFunctionId(options: { id?: string }): string | null {
|
export { resolveFunctionId } from "./function_commands.js";
|
||||||
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 function validateSingleOrigin(origin: string): { origin?: string; error?: string } {
|
export function validateSingleOrigin(origin: string): { origin?: string; error?: string } {
|
||||||
const parsed = parseCorsOriginsOption(origin);
|
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