5 Commits

Author SHA1 Message Date
Space-Banane 6ad74bb991 feat: add testing using vitest 2026-03-20 14:56:19 +01:00
Space-Banane 1b3851d0af docs: emphasize the use of pnpm for package updates 2026-03-20 09:00:39 +01:00
luna 9887ca3dcc chore: bump version to 2.1.5 and add versioning policy 2026-03-20 08:58:03 +01:00
luna ae82ba221b fix: improve error handling for pipinstall 2026-03-20 08:55:10 +01:00
luna 8428ed56b5 feat: add pipinstall command 2026-03-20 08:51:29 +01:00
8 changed files with 143 additions and 4 deletions
+3
View File
@@ -24,3 +24,6 @@
- **New Commands**: To add a command, create a new file in `src/commands/` (or a subfolder). It must export a definition object (default or named) with `name`, `description`, and `action`. - **New Commands**: To add a command, create a new file in `src/commands/` (or a subfolder). It must export a definition object (default or named) with `name`, `description`, and `action`.
- **Error Handling**: Follow the pattern in [src/commands/health.ts](src/commands/health.ts) for handling Axios errors (check for `error.response`, `error.request`, etc.). - **Error Handling**: Follow the pattern in [src/commands/health.ts](src/commands/health.ts) for handling Axios errors (check for `error.response`, `error.request`, etc.).
- **No Global Scope**: Keep command logic within the `action` function or extracted to utility modules to maintain testability. - **No Global Scope**: Keep command logic within the `action` function or extracted to utility modules to maintain testability.
## Versioning
- **Versioning**: Always increment the version number in package.json whenever a change is merged into main. The version format is major.minor.patch.
+1
View File
@@ -28,6 +28,7 @@ jobs:
node-version: '24' node-version: '24'
- run: pnpm install --no-frozen-lockfile - run: pnpm install --no-frozen-lockfile
- run: pnpm test
- run: pnpm build - run: pnpm build
- name: Zip dist directory - name: Zip dist directory
+1 -1
View File
@@ -94,7 +94,7 @@ After creating a function and receiving an ID (for example, 81), you can share t
``` ```
## Update ## Update
Update with your package manager of choice. Preferably pnpm: Update with your package manager of choice. ALWAYS pnpm:
```bash ```bash
pnpm add -g shsf-cli pnpm add -g shsf-cli
``` ```
+7 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "shsf-cli", "name": "shsf-cli",
"version": "2.1.0", "version": "2.1.6",
"description": "", "description": "",
"type": "module", "type": "module",
"files": [ "files": [
@@ -12,7 +12,9 @@
"scripts": { "scripts": {
"build": "rimraf dist && tsc", "build": "rimraf dist && tsc",
"start": "node dist/index.js", "start": "node dist/index.js",
"test": "echo \"Error: no test specified\" && exit 1" "test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@@ -21,9 +23,11 @@
"devDependencies": { "devDependencies": {
"@types/inquirer": "^9.0.9", "@types/inquirer": "^9.0.9",
"@types/node": "^25.5.0", "@types/node": "^25.5.0",
"@vitest/coverage-v8": "^4.1.0",
"rimraf": "^6.1.3", "rimraf": "^6.1.3",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.9.3" "typescript": "^5.9.3",
"vitest": "^4.1.0"
}, },
"dependencies": { "dependencies": {
"axios": "^1.13.6", "axios": "^1.13.6",
+52
View File
@@ -0,0 +1,52 @@
import { describe, it, expect, vi } from 'vitest';
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
describe('Command Loading', () => {
it('all command files should export a valid definition', async () => {
const commandsDir = path.resolve(__dirname, '../commands');
function getCommandFiles(dir: string): string[] {
const files = fs.readdirSync(dir);
let commandFiles: string[] = [];
for (const file of files) {
const fullPath = path.join(dir, file);
if (fs.statSync(fullPath).isDirectory()) {
commandFiles = commandFiles.concat(getCommandFiles(fullPath));
} else if (file.endsWith('.ts') || file.endsWith('.js')) {
commandFiles.push(fullPath);
}
}
return commandFiles;
}
const files = getCommandFiles(commandsDir);
expect(files.length).toBeGreaterThan(0);
for (const file of files) {
// Use dynamic import to check the module
// We convert absolute path to file:// URL for ESM import on Linux/Windows
const module = await import(`file://${file}`);
const definition = module.default || Object.values(module).find((val: any) =>
val && typeof val === 'object' && val.name && typeof val.action === 'function'
);
if (!definition) {
throw new Error(`Command file ${file} does not export a valid command definition (needs name and action function)`);
}
expect(definition).toBeDefined();
expect(typeof definition.name).toBe('string');
expect(typeof definition.action).toBe('function');
console.log(`✓ Validated command: ${definition.name} (${path.relative(commandsDir, file)})`);
}
});
});
+11
View File
@@ -0,0 +1,11 @@
import { describe, it, expect, vi } from 'vitest';
import path from 'path';
import os from 'os';
import { getConfigPath } from '../config.js';
describe('config', () => {
it('should return the correct config path', () => {
const expectedPath = path.join(os.homedir(), '.shsf_config');
expect(getConfigPath()).toBe(expectedPath);
});
});
+56
View File
@@ -0,0 +1,56 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const pipInstallDefinition = {
name: "pipinstall",
description: "Install Python dependencies for a function using requirements.txt",
options: [
{
name: "--id <id>",
description: "The ID of the function.",
required: true,
},
],
action: async (options: { id: string }) => {
await pipInstallFunction(options.id);
},
};
async function pipInstallFunction(id: string) {
const client = await getApiClient();
try {
const response = await client.post(`/api/function/${id}/pip-install`);
if (response.status === 200) {
console.log(`${chalk.green("✓")} Dependencies installed successfully for function ${chalk.yellow(id)}.`);
if (response.data && response.data.status) {
console.log(`${chalk.gray("Status:")} ${response.data.status}`);
}
} 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 install dependencies.`,
);
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("✗")} Failed to install dependencies.`,
);
console.error(
`${chalk.yellow("Could not connect to the SHSF instance. Check your connection.")}`,
);
} else {
console.error(`${chalk.red("✗")} Error:`, error.message);
}
}
}
+12
View File
@@ -0,0 +1,12 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
});