Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# dependencies (bun install)
node_modules

# test folders
src/modules

# output
out
dist
Expand Down
2 changes: 1 addition & 1 deletion build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ build({
cjsInterop: false,
clean: true,
bundle: false,
external: ['michi', 'gaman', 'gaman/types', 'gaman/responder', 'gaman/compose', 'gaman/utils', 'gaman/formdata', 'gaman/header', 'gaman/enums'],
external: ['node:path', 'node:readline', 'michi', 'gaman', 'gaman/types', 'gaman/responder', 'gaman/compose', 'gaman/utils', 'gaman/formdata', 'gaman/header', 'gaman/enums'],
esbuildPlugins: [
fixImportsPlugin()
],
Expand Down
161 changes: 159 additions & 2 deletions bun.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
"framework"
],
"scripts": {
"release": "bun build.ts && bun test && npm publish --access public"
"release": "bun build.ts && bun test && npm publish --access public",
"kame": "bun packages/kame/src/index.ts"
},
"devDependencies": {
"@types/bun": "latest",
Expand Down
4 changes: 4 additions & 0 deletions packages/kame/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
src
test
node_modules
log
21 changes: 21 additions & 0 deletions packages/kame/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# @gaman/static
**Secure, Lightweight & High-Performance Static File Server for GamanJS**. Built for Bun, optimized for speed with non-blocking I/O, built-in compression, and ETag caching.

## Installation
```bash
bun add @gaman/static
```

## Quick Used
By default, this middleware will serve files from the `public/` folder in the root of your project.
```ts
import { defineBootstrap } from "gaman";
import { StaticServe } from "@gaman/static";

defineBootstrap((app) => {
// Mount the static server
app.mount(StaticServe());

app.mountServer(...)
});
```
41 changes: 41 additions & 0 deletions packages/kame/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@gaman/kame",
"version": "0.1.0",
"author": "angga7togk",
"license": "MIT",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
"./compose": {
"types": "./dist/compose/index.d.ts",
"require": "./dist/compose/index.js",
"import": "./dist/compose/index.mjs"
}
},
"keywords": [
"gamanjs",
"gaman",
"gaman cli",
"kame",
"cli",
"repl",
"bun"
],
"repository": {
"url": "git+https://github.com/7TogkID/gaman.git",
"directory": "packages/kame"
},
"bugs": {
"url": "https://github.com/7TogkID/gaman/issues"
},
"homepage": "https://gaman.7togk.id",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
},
"gitHead": "8d1bf3bbac4b72847e9157e2c95d75b5e90c89cf"
}
37 changes: 37 additions & 0 deletions packages/kame/src/commands/buntest-cmd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { $ } from 'bun';
import { Logger } from 'gaman/utils';
import { registerCommand } from './registry';

const handler = async (args: string[]): Promise<void> => {
Logger.info("Starting Bun test suite...");

try {
const result = await $`bun test ${args}`.quiet().text();

result.split('\n').forEach((line) => {
if (line.trim()) {
Logger.info(line);
}
});

Logger.info("All tests passed successfully.");
} catch (err: any) {
const errorOutput = err.stderr?.toString() || err.stdout?.toString() || "";

errorOutput.split('\n').forEach((line: string) => {
if (line.trim()) {
Logger.error(line);
}
});

Logger.error("Test suite failed.");
}
};

registerCommand({
name: 'test',
description: 'Run project tests.',
usage: 'test [filter]',
aliases: ['t'],
handler,
});
77 changes: 77 additions & 0 deletions packages/kame/src/commands/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Logger } from 'gaman/utils';
import { registerCommand } from './registry';

const handler = async (args: string[]): Promise<void> => {
const method = args[0]?.toUpperCase();
const url = args[1];

if (!method || !url) {
Logger.error("Usage: fetch <METHOD> <URL> [-h Header] [-b Body]");
return;
}

// Parsing manual untuk -h dan -b
const headerIndex = args.indexOf("-h");
const bodyIndex = args.indexOf("-b");

const headers: Record<string, string> = {
"Content-Type": "application/json",
};

// Ambil value setelah -h (Format "Key:Value")
if (headerIndex !== -1 && args[headerIndex + 1]) {
const headerRaw = args[headerIndex + 1];
const splitIndex = headerRaw?.indexOf(":") || -1;
if (splitIndex !== -1) {
const key = headerRaw?.slice(0, splitIndex).trim() || '';
const value = headerRaw?.slice(splitIndex + 1).trim() || '';
headers[key] = value;
}
}

// Ambil value setelah -b
let body = null;
if (bodyIndex !== -1 && args[bodyIndex + 1]) {
body = args[bodyIndex + 1];
}

Logger.info(`Sending ${method} request to: ${url}`);

try {
const response = await fetch(url, {
method,
headers,
body: ["GET", "HEAD"].includes(method) ? null : body,
});

const resText = await response.text();
let resData;

try {
resData = JSON.parse(resText);
} catch {
resData = resText;
}

if (response.ok) {
Logger.info(`Status: ${response.status}`);
console.log(typeof resData === "object"
? JSON.stringify(resData, null, 2)
: resData
);
} else {
Logger.error(`Status: ${response.status}`);
console.log(resData);
}
} catch (err: any) {
Logger.error(`Fetch failed: ${err.message}`);
}
};

registerCommand({
name: 'fetch',
description: 'Request internal/external API via CLI',
usage: 'fetch <METHOD> <URL> [-h "Key: Value"] [-b "Body"]',
aliases: ['req'],
handler,
});
34 changes: 34 additions & 0 deletions packages/kame/src/commands/gen-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Logger } from 'gaman/utils';
import { join, relative } from 'node:path';
import { registerCommand } from './registry';
import { controllerTemplate } from '../templates/module';
import { capitalize } from '../utils';

const handler = async (args: string[]): Promise<void> => {
const [name, module = 'app'] = args;
if (!name || !module) {
Logger.error("Usage: gen:controller <name> <module: 'app'>");
return;
}

const nameCapitalized = capitalize(name);
const cwd = process.cwd();
// Support nested paths like "v2/user"
const controllerDir = join(cwd, 'src', 'modules', module, 'controllers');
const filePath = join(controllerDir, `${nameCapitalized}Controller.ts`);

await Bun.$`mkdir -p ${controllerDir}`.quiet();
await Bun.write(filePath, controllerTemplate(name) + '\n');
Logger.info(`created ${relative(cwd, filePath)}`);
Logger.info(
`Controller "${nameCapitalized}Controller" generated successfully.`,
);
};

registerCommand({
name: 'gen:controller',
description: 'Generate a new Controller inside an existing module',
usage: "gen:controller <name> <module: 'app'>",
aliases: ['gen:co'],
handler,
});
37 changes: 37 additions & 0 deletions packages/kame/src/commands/gen-exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Logger, TextFormat } from 'gaman/utils';
import { join, relative } from 'node:path';
import { registerCommand } from './registry';
import { exceptionTemplate } from '../templates/module';
import { capitalize } from '../utils';

const handler = async (args: string[]): Promise<void> => {
const [name, module = 'app'] = args;
if (!name || !module) {
Logger.error("Usage: gen:exception <name> <module: 'app'>");
return;
}

const nameCapitalized = capitalize(name);
const cwd = process.cwd();

// Support nested paths like "v2/user"
const exceptionDir = join(cwd, 'src', 'modules', module, 'exceptions');
const filePath = join(exceptionDir, `${nameCapitalized}Exception.ts`);

await Bun.$`mkdir -p ${exceptionDir}`.quiet();
await Bun.write(filePath, exceptionTemplate() + '\n');
Logger.info(
`created ${TextFormat.UNDERLINE}${relative(cwd, filePath)}${TextFormat.RESET}`,
);
Logger.info(
`Exception "${nameCapitalized}Exception" generated successfully.`,
);
};

registerCommand({
name: 'gen:exception',
description: 'Generate a new Exception inside an existing module',
usage: "gen:exception <name> <module: 'app'>",
aliases: ['gen:ex'],
handler,
});
36 changes: 36 additions & 0 deletions packages/kame/src/commands/gen-middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Logger, TextFormat } from 'gaman/utils';
import { join, relative } from 'node:path';
import { registerCommand } from './registry';
import { middlewareTemplate } from '../templates/module';
import { capitalize } from '../utils';

const handler = async (args: string[]): Promise<void> => {
const [name, module = 'app'] = args;
if (!name || !module) {
Logger.error("Usage: gen:middleware <name> <module: 'app'>");
return;
}

const nameCapitalized = capitalize(name);
const cwd = process.cwd();
// Support nested paths like "v2/user"
const middlewareDir = join(cwd, 'src', 'modules', module, 'middlewares');
const filePath = join(middlewareDir, `${nameCapitalized}Middleware.ts`);

await Bun.$`mkdir -p ${middlewareDir}`.quiet();
await Bun.write(filePath, middlewareTemplate() + '\n');
Logger.info(
`created ${TextFormat.UNDERLINE}${relative(cwd, filePath)}${TextFormat.RESET}`,
);
Logger.info(
`Middleware "${nameCapitalized}Middleware" generated successfully.`,
);
};

registerCommand({
name: 'gen:middleware',
description: 'Generate a new Middleware inside an existing module',
usage: "gen:middleware <name> <module: 'app'>",
aliases: ['gen:mi'],
handler,
});
59 changes: 59 additions & 0 deletions packages/kame/src/commands/gen-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Logger } from 'gaman/utils';
import { join, basename, relative, dirname } from 'node:path';
import { registerCommand } from './registry';
import {
controllerTemplate,
routerTemplate,
serviceTemplate,
} from '../templates/module';
import { capitalize } from '../utils';

const handler = async (args: string[]): Promise<void> => {
const modulePath = args[0];
if (!modulePath) {
Logger.error('Usage: gen:module <name>');
return;
}

// Support nested paths like "v2/user" — use last segment for file naming
const name = basename(modulePath);
const nameCapitalized = capitalize(name);
const cwd = process.cwd();
const moduleDir = join(cwd, 'src', 'modules', modulePath);

const files: { filePath: string; content: string }[] = [
{
filePath: join(
moduleDir,
'controllers',
`${nameCapitalized}Controller.ts`,
),
content: controllerTemplate(name),
},
{
filePath: join(moduleDir, 'services', `${nameCapitalized}Service.ts`),
content: serviceTemplate(name),
},
{
filePath: join(moduleDir, `${nameCapitalized}Router.ts`),
content: routerTemplate(name),
},
];

for (const { filePath, content } of files) {
const dir = dirname(filePath);
await Bun.$`mkdir -p ${dir}`.quiet();
await Bun.write(filePath, content + '\n');
Logger.info(`created ${relative(cwd, filePath)}`);
}

Logger.info(`Module "${nameCapitalized}" generated successfully.`);
};

registerCommand({
name: 'gen:module',
description: 'Generate a new module with Controller, Service, and Router',
usage: 'gen:module <name>',
aliases: ['gen:mo'],
handler,
});
Loading
Loading