tambahannya

This commit is contained in:
bipproduction
2025-09-24 12:23:17 +08:00
parent e39e8196b8
commit a8a700f09a
9 changed files with 1218 additions and 19 deletions

View File

@@ -1,7 +1,16 @@
#!/usr/bin/env bun
import minimist from "minimist";
import path from "path";
import { generateEnvTypes } from "../generate/env.generate.js";
import { generateEnvTypes } from "../generate/env.generate";
import checkPort from "./src/port";
import route from "./src/route";
import not3 from "./src/not3";
interface CheckPortResult {
port: number;
open: boolean;
}
const args = minimist(process.argv.slice(2));
@@ -9,14 +18,20 @@ const help = `
g3n [command] [options]
Commands:
env Generate env.d.ts from .env file
env Generate env.d.ts from .env file
scan-port Scan port range (default 3000-4000)
route Generate routes.ts from AppRoutes.tsx
Options:
--env Path ke file .env (default: .env)
--out Path file output (default: types/env.d.ts)
--start Port awal scan (default: 3000)
--end Port akhir scan (default: 4000)
--host Host/IP target (default: 127.0.0.1)
Examples:
g3n env --env .env.local --out src/types/env.d.ts
g3n scan-port --start 7700 --end 7800 --host 127.0.0.1
g3n route
`;
(async () => {
@@ -31,5 +46,41 @@ Examples:
return;
}
if (cmd === "scan-port") {
const start: number = args.start ? parseInt(args.start, 10) : 3000;
const end: number = args.end ? parseInt(args.end, 10) : 4000;
const host: string = args.host || "localhost";
console.log(`🔍 Scan port ${start}-${end} di host ${host} ...`);
const ports: number[] = Array.from(
{ length: end - start + 1 },
(_, i) => start + i
);
const results: CheckPortResult[] = await Promise.all(
ports.map((p) => checkPort(p, host))
);
results.filter((r) => r.open).forEach((r) => {
console.log(`✅ Port ${r.port} sedang digunakan`);
});
console.log("✅ Selesai");
return;
}
if(cmd === "route") {
route();
return;
}
if(cmd === "note") {
not3()
return;
}
console.error(help);
})();

275
bin/not3.ts Executable file
View File

@@ -0,0 +1,275 @@
#!/usr/bin/env bun
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { execSync } from 'child_process';
const CONFIG_FILE = path.join(os.homedir(), '.note.conf');
interface Config {
TOKEN?: string;
REPO?: string;
URL?: string;
}
// --- Check dependencies ---
function checkDependency(cmd: string): boolean {
try {
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
function checkDependencies(): void {
const deps = ['curl', 'jq'];
for (const cmd of deps) {
if (!checkDependency(cmd)) {
console.error(`❌ Missing dependency: ${cmd} (please install it)`);
process.exit(1);
}
}
}
// --- Config management ---
function createDefaultConfig(): void {
const defaultConfig = `TOKEN=
REPO=
URL=https://cld-dkr-makuro-seafile.wibudev.com/api2
`;
fs.writeFileSync(CONFIG_FILE, defaultConfig);
}
function editConfig(): void {
if (!fs.existsSync(CONFIG_FILE)) {
createDefaultConfig();
}
const editor = process.env.EDITOR || 'vim';
try {
execSync(`${editor} "${CONFIG_FILE}"`, { stdio: 'inherit' });
} catch (error) {
console.error('❌ Failed to open editor');
process.exit(1);
}
}
function loadConfig(): Config {
if (!fs.existsSync(CONFIG_FILE)) {
console.error(`⚠️ Config file not found at ${CONFIG_FILE}`);
console.error('Run: node note.ts config to create/edit it.');
process.exit(1);
}
const configContent = fs.readFileSync(CONFIG_FILE, 'utf8');
const config: Config = {};
for (const line of configContent.split('\n')) {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith('#')) {
const [key, ...valueParts] = trimmed.split('=');
if (key && valueParts.length > 0) {
const value = valueParts.join('=');
config[key as keyof Config] = value;
}
}
}
if (!config.TOKEN || !config.REPO) {
console.error(`❌ Config invalid. Please set TOKEN=... and REPO=... inside ${CONFIG_FILE}`);
process.exit(1);
}
// Set default URL if not provided
if (!config.URL) {
console.error(`❌ Config invalid. Please set URL=... inside ${CONFIG_FILE}`);
process.exit(1);
}
return config;
}
// --- HTTP helpers ---
function curlCommand(config: Config, options: string): string {
try {
return execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" ${options}`,
{ encoding: 'utf8' });
} catch (error) {
console.error('❌ Request failed');
process.exit(1);
}
}
function curlCommandSilent(config: Config, options: string): void {
try {
execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" ${options} >/dev/null`,
{ stdio: 'ignore' });
} catch (error) {
console.error('❌ Request failed');
process.exit(1);
}
}
// --- Commands ---
function listFiles(config: Config): void {
const response = curlCommand(config, `"${config.URL}/${config.REPO}/dir/?p=/"`);
try {
const files = JSON.parse(response);
for (const file of files) {
console.log(file.name);
}
} catch (error) {
console.error('❌ Failed to parse response');
process.exit(1);
}
}
function catFile(config: Config, fileName: string): void {
const downloadUrlResponse = curlCommand(config, `"${config.URL}/${config.REPO}/file/?p=/${fileName}"`);
const downloadUrl = downloadUrlResponse.replace(/"/g, '');
const content = curlCommand(config, `"${downloadUrl}"`);
console.log(content);
}
function uploadFile(config: Config, localFile: string, remoteFile?: string): void {
if (!fs.existsSync(localFile)) {
console.error(`❌ File not found: ${localFile}`);
process.exit(1);
}
const remoteName = remoteFile || path.basename(localFile);
const uploadUrlResponse = curlCommand(config, `"${config.URL}/${config.REPO}/upload-link/?p=/"`);
const uploadUrl = uploadUrlResponse.replace(/"/g, '');
try {
execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" \
-F "file=@${localFile}" \
-F "filename=${remoteName}" \
-F "parent_dir=/" \
"${uploadUrl}" >/dev/null`, { stdio: 'ignore' });
console.log(`✅ Uploaded ${localFile}${remoteName}`);
} catch (error) {
console.error('❌ Upload failed');
process.exit(1);
}
}
function removeFile(config: Config, fileName: string): void {
curlCommandSilent(config, `-X DELETE "${config.URL}/${config.REPO}/file/?p=/${fileName}"`);
console.log(`🗑️ Removed ${fileName}`);
}
function moveFile(config: Config, oldName: string, newName: string): void {
try {
execSync(`curl -s -X POST -H "Authorization: Token ${config.TOKEN}" \
-d "operation=rename" \
-d "newname=${newName}" \
"${config.URL}/${config.REPO}/file/?p=/${oldName}" >/dev/null`, { stdio: 'ignore' });
console.log(`✏️ Renamed ${oldName}${newName}`);
} catch (error) {
console.error('❌ Rename failed');
process.exit(1);
}
}
function downloadFile(config: Config, remoteFile: string, localFile?: string): void {
const localName = localFile || remoteFile;
const downloadUrlResponse = curlCommand(config, `"${config.URL}/${config.REPO}/file/?p=/${remoteFile}"`);
const downloadUrl = downloadUrlResponse.replace(/"/g, '');
try {
execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" "${downloadUrl}" -o "${localName}"`,
{ stdio: 'ignore' });
console.log(`⬇️ Downloaded ${remoteFile}${localName}`);
} catch (error) {
console.error('❌ Download failed');
process.exit(1);
}
}
function showHelp(): void {
console.log(`note - simple CLI for Seafile notes
Usage:
node note.ts ls List files
node note.ts cat <file> Show file content
node note.ts cp <local> [remote] Upload file
node note.ts rm <remote> Remove file
node note.ts mv <old> <new> Rename/move file
node note.ts get <remote> [local] Download file
node note.ts config Edit config (~/.note.conf)
Config (~/.note.conf):
TOKEN=your_seafile_token
REPO=repos/<repo-id>
URL=your_seafile_url/api2`);
}
// --- Main ---
function not3(): void {
const args = process.argv.slice(2);
const cmd = args[0] || 'help';
// Handle config command without dependency check
if (cmd === 'config') {
editConfig();
return;
}
// Check dependencies for other commands
checkDependencies();
// Load config for other commands
const config = loadConfig();
switch (cmd) {
case 'ls':
listFiles(config);
break;
case 'cat':
if (!args[1]) {
console.error('Usage: node note.ts cat <file>');
process.exit(1);
}
catFile(config, args[1]);
break;
case 'cp':
if (!args[1]) {
console.error('Usage: node note.ts cp <local_file> [remote_file]');
process.exit(1);
}
uploadFile(config, args[1], args[2]);
break;
case 'rm':
if (!args[1]) {
console.error('Usage: node note.ts rm <remote_file>');
process.exit(1);
}
removeFile(config, args[1]);
break;
case 'mv':
if (!args[1] || !args[2]) {
console.error('Usage: node note.ts mv <old_name> <new_name>');
process.exit(1);
}
moveFile(config, args[1], args[2]);
break;
case 'get':
if (!args[1]) {
console.error('Usage: node note.ts get <remote_file> [local_file]');
process.exit(1);
}
downloadFile(config, args[1], args[2]);
break;
case 'help':
default:
showHelp();
break;
}
}

277
bin/src/not3.ts Normal file
View File

@@ -0,0 +1,277 @@
#!/usr/bin/env bun
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { execSync } from 'child_process';
const CONFIG_FILE = path.join(os.homedir(), '.note.conf');
interface Config {
TOKEN?: string;
REPO?: string;
URL?: string;
}
// --- Check dependencies ---
function checkDependency(cmd: string): boolean {
try {
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
function checkDependencies(): void {
const deps = ['curl', 'jq'];
for (const cmd of deps) {
if (!checkDependency(cmd)) {
console.error(`❌ Missing dependency: ${cmd} (please install it)`);
process.exit(1);
}
}
}
// --- Config management ---
function createDefaultConfig(): void {
const defaultConfig = `TOKEN=
REPO=
URL=https://cld-dkr-makuro-seafile.wibudev.com/api2
`;
fs.writeFileSync(CONFIG_FILE, defaultConfig);
}
function editConfig(): void {
if (!fs.existsSync(CONFIG_FILE)) {
createDefaultConfig();
}
const editor = process.env.EDITOR || 'vim';
try {
execSync(`${editor} "${CONFIG_FILE}"`, { stdio: 'inherit' });
} catch (error) {
console.error('❌ Failed to open editor');
process.exit(1);
}
}
function loadConfig(): Config {
if (!fs.existsSync(CONFIG_FILE)) {
console.error(`⚠️ Config file not found at ${CONFIG_FILE}`);
console.error('Run: node note.ts config to create/edit it.');
process.exit(1);
}
const configContent = fs.readFileSync(CONFIG_FILE, 'utf8');
const config: Config = {};
for (const line of configContent.split('\n')) {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith('#')) {
const [key, ...valueParts] = trimmed.split('=');
if (key && valueParts.length > 0) {
const value = valueParts.join('=');
config[key as keyof Config] = value;
}
}
}
if (!config.TOKEN || !config.REPO) {
console.error(`❌ Config invalid. Please set TOKEN=... and REPO=... inside ${CONFIG_FILE}`);
process.exit(1);
}
// Set default URL if not provided
if (!config.URL) {
console.error(`❌ Config invalid. Please set URL=... inside ${CONFIG_FILE}`);
process.exit(1);
}
return config;
}
// --- HTTP helpers ---
function curlCommand(config: Config, options: string): string {
try {
return execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" ${options}`,
{ encoding: 'utf8' });
} catch (error) {
console.error('❌ Request failed');
process.exit(1);
}
}
function curlCommandSilent(config: Config, options: string): void {
try {
execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" ${options} >/dev/null`,
{ stdio: 'ignore' });
} catch (error) {
console.error('❌ Request failed');
process.exit(1);
}
}
// --- Commands ---
function listFiles(config: Config): void {
const response = curlCommand(config, `"${config.URL}/${config.REPO}/dir/?p=/"`);
try {
const files = JSON.parse(response);
for (const file of files) {
console.log(file.name);
}
} catch (error) {
console.error('❌ Failed to parse response');
process.exit(1);
}
}
function catFile(config: Config, fileName: string): void {
const downloadUrlResponse = curlCommand(config, `"${config.URL}/${config.REPO}/file/?p=/${fileName}"`);
const downloadUrl = downloadUrlResponse.replace(/"/g, '');
const content = curlCommand(config, `"${downloadUrl}"`);
console.log(content);
}
function uploadFile(config: Config, localFile: string, remoteFile?: string): void {
if (!fs.existsSync(localFile)) {
console.error(`❌ File not found: ${localFile}`);
process.exit(1);
}
const remoteName = remoteFile || path.basename(localFile);
const uploadUrlResponse = curlCommand(config, `"${config.URL}/${config.REPO}/upload-link/?p=/"`);
const uploadUrl = uploadUrlResponse.replace(/"/g, '');
try {
execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" \
-F "file=@${localFile}" \
-F "filename=${remoteName}" \
-F "parent_dir=/" \
"${uploadUrl}" >/dev/null`, { stdio: 'ignore' });
console.log(`✅ Uploaded ${localFile}${remoteName}`);
} catch (error) {
console.error('❌ Upload failed');
process.exit(1);
}
}
function removeFile(config: Config, fileName: string): void {
curlCommandSilent(config, `-X DELETE "${config.URL}/${config.REPO}/file/?p=/${fileName}"`);
console.log(`🗑️ Removed ${fileName}`);
}
function moveFile(config: Config, oldName: string, newName: string): void {
try {
execSync(`curl -s -X POST -H "Authorization: Token ${config.TOKEN}" \
-d "operation=rename" \
-d "newname=${newName}" \
"${config.URL}/${config.REPO}/file/?p=/${oldName}" >/dev/null`, { stdio: 'ignore' });
console.log(`✏️ Renamed ${oldName}${newName}`);
} catch (error) {
console.error('❌ Rename failed');
process.exit(1);
}
}
function downloadFile(config: Config, remoteFile: string, localFile?: string): void {
const localName = localFile || remoteFile;
const downloadUrlResponse = curlCommand(config, `"${config.URL}/${config.REPO}/file/?p=/${remoteFile}"`);
const downloadUrl = downloadUrlResponse.replace(/"/g, '');
try {
execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" "${downloadUrl}" -o "${localName}"`,
{ stdio: 'ignore' });
console.log(`⬇️ Downloaded ${remoteFile}${localName}`);
} catch (error) {
console.error('❌ Download failed');
process.exit(1);
}
}
function showHelp(): void {
console.log(`note - simple CLI for Seafile notes
Usage:
node note.ts ls List files
node note.ts cat <file> Show file content
node note.ts cp <local> [remote] Upload file
node note.ts rm <remote> Remove file
node note.ts mv <old> <new> Rename/move file
node note.ts get <remote> [local] Download file
node note.ts config Edit config (~/.note.conf)
Config (~/.note.conf):
TOKEN=your_seafile_token
REPO=repos/<repo-id>
URL=your_seafile_url/api2`);
}
// --- Main ---
function not3(): void {
const args = process.argv.slice(2);
const cmd = args[0] || 'help';
// Handle config command without dependency check
if (cmd === 'config') {
editConfig();
return;
}
// Check dependencies for other commands
checkDependencies();
// Load config for other commands
const config = loadConfig();
switch (cmd) {
case 'ls':
listFiles(config);
break;
case 'cat':
if (!args[1]) {
console.error('Usage: node note.ts cat <file>');
process.exit(1);
}
catFile(config, args[1]);
break;
case 'cp':
if (!args[1]) {
console.error('Usage: node note.ts cp <local_file> [remote_file]');
process.exit(1);
}
uploadFile(config, args[1], args[2]);
break;
case 'rm':
if (!args[1]) {
console.error('Usage: node note.ts rm <remote_file>');
process.exit(1);
}
removeFile(config, args[1]);
break;
case 'mv':
if (!args[1] || !args[2]) {
console.error('Usage: node note.ts mv <old_name> <new_name>');
process.exit(1);
}
moveFile(config, args[1], args[2]);
break;
case 'get':
if (!args[1]) {
console.error('Usage: node note.ts get <remote_file> [local_file]');
process.exit(1);
}
downloadFile(config, args[1], args[2]);
break;
case 'help':
default:
showHelp();
break;
}
}
export default not3;

31
bin/src/port.ts Normal file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bun
import net from "net";
interface CheckPortResult {
port: number;
open: boolean;
}
export default function checkPort(port: number, host: string): Promise<CheckPortResult> {
return new Promise((resolve) => {
const socket = new net.Socket();
socket.setTimeout(200);
socket.once("connect", () => {
socket.destroy();
resolve({ port, open: true });
});
socket.once("timeout", () => {
socket.destroy();
resolve({ port, open: false });
});
socket.once("error", () => {
socket.destroy();
resolve({ port, open: false });
});
socket.connect(port, host);
});
}

138
bin/src/route.ts Normal file
View File

@@ -0,0 +1,138 @@
#!/usr/bin/env bun
import fs from "fs";
import path from "path";
import * as parser from "@babel/parser";
import traverse from "@babel/traverse";
import * as t from "@babel/types";
const SRC_DIR = path.resolve(process.cwd(), "src");
const APP_ROUTES_FILE = path.join(SRC_DIR, "AppRoutes.tsx");
interface RouteNode {
path: string;
children: RouteNode[];
}
function getAttributePath(attrs: (t.JSXAttribute | t.JSXSpreadAttribute)[]) {
const pathAttr = attrs.find(
(attr) =>
t.isJSXAttribute(attr) &&
t.isJSXIdentifier(attr.name, { name: "path" })
) as t.JSXAttribute | undefined;
if (pathAttr && t.isStringLiteral(pathAttr.value)) {
return pathAttr.value.value;
}
return "";
}
/**
* Rekursif baca node <Route> beserta anak-anaknya
*/
function extractRouteNodes(node: t.JSXElement): RouteNode | null {
const opening = node.openingElement;
if (!t.isJSXIdentifier(opening.name) || opening.name.name !== "Route") {
return null;
}
const currentPath = getAttributePath(opening.attributes);
// cari anak-anak <Route>
const children: RouteNode[] = [];
for (const child of node.children) {
if (t.isJSXElement(child)) {
const childNode = extractRouteNodes(child);
if (childNode) children.push(childNode);
}
}
return { path: currentPath, children };
}
/**
* Flatten hasil rekursif jadi list path full
*/
function flattenRoutes(
node: RouteNode,
parentPath = ""
): Record<string, string> {
const record: Record<string, string> = {};
// gabung path parent + child
let fullPath = node.path;
if (fullPath) {
if (parentPath) {
if (fullPath === "/") {
fullPath = parentPath;
} else {
fullPath = `${parentPath.replace(/\/$/, "")}/${fullPath.replace(
/^\//,
""
)}`;
}
}
if (!fullPath.startsWith("/")) {
fullPath = `/${fullPath}`;
}
record[fullPath] = fullPath;
}
for (const child of node.children) {
Object.assign(record, flattenRoutes(child, fullPath || parentPath));
}
return record;
}
function extractRoutes(code: string): Record<string, string> {
const ast = parser.parse(code, {
sourceType: "module",
plugins: ["typescript", "jsx"],
});
const routes: Record<string, string> = {};
traverse(ast, {
JSXElement(path) {
const node = path.node;
const opening = node.openingElement;
if (t.isJSXIdentifier(opening.name) && opening.name.name === "Routes") {
for (const child of node.children) {
if (t.isJSXElement(child)) {
const routeNode = extractRouteNodes(child);
if (routeNode) {
Object.assign(routes, flattenRoutes(routeNode));
}
}
}
}
},
});
return routes;
}
export default function route() {
if (!fs.existsSync(APP_ROUTES_FILE)) {
console.error("❌ AppRoutes.tsx not found in src/");
process.exit(1);
}
const code = fs.readFileSync(APP_ROUTES_FILE, "utf-8");
const routes = extractRoutes(code);
console.log("✅ Generated Routes:");
console.log(routes);
const outPath = path.join(SRC_DIR, "clientRoutes.ts");
fs.writeFileSync(
outPath,
`// AUTO-GENERATED FILE\nconst clientRoutes = ${JSON.stringify(
routes,
null,
2
)} as const;\n\nexport default clientRoutes;`
);
console.log(`📄 clientRoutes.ts generated at ${outPath}`);
}