Initial commit
This commit is contained in:
80
.gitignore
vendored
Normal file
80
.gitignore
vendored
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
.env.production
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
163
README.md
Normal file
163
README.md
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# Gitea Creator
|
||||||
|
|
||||||
|
A command line tool to create repositories on Gitea servers with automatic git setup.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Create repositories on Gitea servers via API
|
||||||
|
- Interactive prompts for git initialization and setup
|
||||||
|
- Automatic remote configuration
|
||||||
|
- Optional automatic pushing to the new repository
|
||||||
|
- Support for public/private repository creation
|
||||||
|
- Comprehensive .gitignore creation for Node.js projects
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Option 1: Local Development
|
||||||
|
1. Clone this repository
|
||||||
|
2. Install dependencies: `npm install`
|
||||||
|
3. Build the project: `npm run build`
|
||||||
|
4. The CLI will be available at `./dist/index.js`
|
||||||
|
|
||||||
|
### Option 2: Global Installation (Development)
|
||||||
|
1. Clone this repository
|
||||||
|
2. Install dependencies: `npm install`
|
||||||
|
3. Build the project: `npm run build`
|
||||||
|
4. Link globally: `npm link`
|
||||||
|
5. Use `gitea-creator` command anywhere
|
||||||
|
|
||||||
|
### Option 3: Global Installation (from npm)
|
||||||
|
```bash
|
||||||
|
# If published to npm
|
||||||
|
npm install -g gitea-creator
|
||||||
|
|
||||||
|
# Use anywhere
|
||||||
|
gitea-creator my-repo --public
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 4: Direct Usage
|
||||||
|
```bash
|
||||||
|
# Run directly without global installation
|
||||||
|
node dist/index.js <repository-name> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Setup
|
||||||
|
|
||||||
|
### For Local Development
|
||||||
|
Create a `.env` file (copy from `env.example`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy the example file
|
||||||
|
cp env.example .env
|
||||||
|
|
||||||
|
# Edit the .env file with your values
|
||||||
|
# GITEA_TOKEN=your_gitea_access_token
|
||||||
|
# GITEA_API_URL=https://your-gitea-server.com/api/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
### For Global Installation
|
||||||
|
When installed globally, the tool supports multiple configuration methods:
|
||||||
|
|
||||||
|
#### Option 1: System Environment Variables (Recommended)
|
||||||
|
```bash
|
||||||
|
# Add to your shell profile (~/.bashrc, ~/.zshrc, etc.)
|
||||||
|
export GITEA_TOKEN=your_gitea_access_token
|
||||||
|
export GITEA_API_URL=https://your-gitea-server.com/api/v1
|
||||||
|
|
||||||
|
# Reload your shell
|
||||||
|
source ~/.zshrc # or ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option 2: Global Configuration File
|
||||||
|
```bash
|
||||||
|
# Create global config directory
|
||||||
|
mkdir -p ~/.gitea-creator
|
||||||
|
|
||||||
|
# Create .env file
|
||||||
|
echo "GITEA_TOKEN=your_token_here" > ~/.gitea-creator/.env
|
||||||
|
echo "GITEA_API_URL=https://your-gitea-server.com/api/v1" >> ~/.gitea-creator/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option 3: View Configuration Instructions
|
||||||
|
```bash
|
||||||
|
# Get setup instructions anytime
|
||||||
|
gitea-creator --config
|
||||||
|
# or short form
|
||||||
|
gitea-creator -c
|
||||||
|
```
|
||||||
|
|
||||||
|
### Getting a Gitea Access Token
|
||||||
|
1. Log into your Gitea server
|
||||||
|
2. Go to Settings → Applications
|
||||||
|
3. Click "Manage Access Tokens"
|
||||||
|
4. Generate a new token with repository permissions
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gitea-creator <repository-name> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
- `-p, --public`: Create a public repository (non-private)
|
||||||
|
- `-d, --description <description>`: Add a description to the repository
|
||||||
|
- `-h, --help`: Display help information
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a private repository
|
||||||
|
gitea-creator my-new-repo
|
||||||
|
|
||||||
|
# Create a public repository with description
|
||||||
|
gitea-creator my-public-repo --public --description "My awesome project"
|
||||||
|
|
||||||
|
# Short form
|
||||||
|
gitea-creator my-repo -p -d "Short description"
|
||||||
|
|
||||||
|
# Get configuration help
|
||||||
|
gitea-creator --config
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
When you run the tool, it will:
|
||||||
|
|
||||||
|
1. **Create Repository**: Create the repository on your Gitea server with the specified options
|
||||||
|
2. **Display URL**: Show the remote clone URL for the new repository
|
||||||
|
3. **Git Initialization**: If no git repository exists, ask if you want to initialize one
|
||||||
|
- Creates `.gitignore` with Node.js patterns if initializing
|
||||||
|
4. **Remote Setup**: Ask if you want to set the new repository as the remote origin (defaults to yes)
|
||||||
|
5. **Pushing**: Ask if you want to push the current repository (defaults to yes)
|
||||||
|
- Automatically handles initial commits if there are uncommitted changes
|
||||||
|
- Renames `master` branch to `main` if needed
|
||||||
|
|
||||||
|
### Interactive Prompts
|
||||||
|
All prompts default to "yes" - you can just press Enter to accept the defaults:
|
||||||
|
- Initialize git repository? (if none exists)
|
||||||
|
- Set remote origin?
|
||||||
|
- Push to remote?
|
||||||
|
- Commit changes first? (if there are uncommitted changes)
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Build the project
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Run in development mode
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Test the CLI
|
||||||
|
node dist/index.js my-test-repo --public
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Node.js 18+
|
||||||
|
- Git (for git operations)
|
||||||
|
- Valid Gitea server access token
|
||||||
13
env.example
Normal file
13
env.example
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Gitea Creator Configuration
|
||||||
|
# Copy this file to .env and fill in your values
|
||||||
|
|
||||||
|
# Your Gitea personal access token
|
||||||
|
# You can create one in your Gitea settings under "Applications" -> "Manage Access Tokens"
|
||||||
|
GITEA_TOKEN=26e3b54850d1b433117c1494853ffcfab501c53b
|
||||||
|
|
||||||
|
# Your Gitea server API URL (include /api/v1 at the end)
|
||||||
|
# Examples:
|
||||||
|
# GITEA_API_URL=https://git.harvmaster.com/api/v1
|
||||||
|
# GITEA_API_URL=https://gitea.mycompany.com/api/v1
|
||||||
|
# GITEA_API_URL=https://codeberg.org/api/v1
|
||||||
|
GITEA_API_URL=https://git.harvmaster.com/api/v1
|
||||||
104
package-lock.json
generated
Normal file
104
package-lock.json
generated
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
{
|
||||||
|
"name": "gitea-creator",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "gitea-creator",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^12.1.0",
|
||||||
|
"prompts": "^2.4.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"gitea-creator": "dist/index.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^24.2.0",
|
||||||
|
"@types/prompts": "^2.4.9",
|
||||||
|
"typescript": "^5.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "24.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz",
|
||||||
|
"integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/prompts": {
|
||||||
|
"version": "2.4.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.9.tgz",
|
||||||
|
"integrity": "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"kleur": "^3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/commander": {
|
||||||
|
"version": "12.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||||
|
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/kleur": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prompts": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"kleur": "^3.0.3",
|
||||||
|
"sisteransi": "^1.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sisteransi": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||||
|
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||||
|
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
package.json
Normal file
33
package.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "gitea-creator",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "CLI tool to create repositories on Gitea servers",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"gitea-creator": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"dev": "tsc && node dist/index.js",
|
||||||
|
"prepublishOnly": "npm run build",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/**/*",
|
||||||
|
"README.md",
|
||||||
|
"env.example"
|
||||||
|
],
|
||||||
|
"keywords": ["gitea", "git", "cli", "repository"],
|
||||||
|
"author": "harvmaster",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^12.1.0",
|
||||||
|
"prompts": "^2.4.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^24.2.0",
|
||||||
|
"@types/prompts": "^2.4.9",
|
||||||
|
"typescript": "^5.9.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/cli.ts
Normal file
80
src/cli.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { Command } from 'commander';
|
||||||
|
import { GlobalConfig } from './global-config.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command line interface options for creating a Gitea repository
|
||||||
|
*/
|
||||||
|
export interface CliOptions {
|
||||||
|
/** Repository name (required) */
|
||||||
|
name: string;
|
||||||
|
/** Repository description */
|
||||||
|
description?: string;
|
||||||
|
/** Whether the repository should be public (non-private) */
|
||||||
|
public?: boolean;
|
||||||
|
/** Show configuration instructions */
|
||||||
|
config?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLI service for parsing command line arguments
|
||||||
|
*/
|
||||||
|
export class CliService {
|
||||||
|
private readonly program: Command;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.program = new Command();
|
||||||
|
this.setupCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the command line interface with all options and arguments
|
||||||
|
*/
|
||||||
|
private setupCommand(): void {
|
||||||
|
this.program
|
||||||
|
.name('gitea-creator')
|
||||||
|
.description('CLI tool to create repositories on Gitea servers')
|
||||||
|
.version('1.0.0')
|
||||||
|
.argument('<name>', 'Name of the repository to create')
|
||||||
|
.option('-p, --public', 'Create a public repository (non-private)', false)
|
||||||
|
.option('-d, --description <description>', 'Repository description')
|
||||||
|
.option('-c, --config', 'Show configuration setup instructions')
|
||||||
|
.helpOption('-h, --help', 'Display help for command');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse command line arguments and return options
|
||||||
|
* @param argv - Command line arguments (defaults to process.argv)
|
||||||
|
* @returns Parsed CLI options
|
||||||
|
*/
|
||||||
|
parse(argv?: string[]): CliOptions {
|
||||||
|
const parsed = this.program.parse(argv);
|
||||||
|
const options = parsed.opts();
|
||||||
|
const [name] = parsed.args;
|
||||||
|
|
||||||
|
// Handle config option
|
||||||
|
if (options.config) {
|
||||||
|
console.log(GlobalConfig.getSetupInstructions());
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
console.error('❌ Error: Repository name is required');
|
||||||
|
this.program.help();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
description: options.description,
|
||||||
|
public: options.public,
|
||||||
|
config: options.config,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display help information
|
||||||
|
*/
|
||||||
|
help(): void {
|
||||||
|
this.program.help();
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/config.ts
Normal file
35
src/config.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
export type ConfigRaw = {
|
||||||
|
GITEA_TOKEN: string;
|
||||||
|
GITEA_API_URL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
import { GlobalConfig } from './global-config.js';
|
||||||
|
|
||||||
|
export class Config {
|
||||||
|
static from(config: Record<string, string>) {
|
||||||
|
const token = config.GITEA_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
const instructions = GlobalConfig.getSetupInstructions();
|
||||||
|
throw new Error(`GITEA_TOKEN is not set.\n${instructions}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiUrl = config.GITEA_API_URL;
|
||||||
|
if (!apiUrl) {
|
||||||
|
const instructions = GlobalConfig.getSetupInstructions();
|
||||||
|
throw new Error(`GITEA_API_URL is not set.\n${instructions}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Config({
|
||||||
|
GITEA_TOKEN: token,
|
||||||
|
GITEA_API_URL: apiUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly token: string;
|
||||||
|
readonly apiUrl: string;
|
||||||
|
|
||||||
|
constructor(config: ConfigRaw) {
|
||||||
|
this.token = config.GITEA_TOKEN;
|
||||||
|
this.apiUrl = config.GITEA_API_URL;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/env.ts
Normal file
28
src/env.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { GlobalConfig } from './global-config.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Environment variables configuration
|
||||||
|
* Supports local .env, global config, and system environment variables
|
||||||
|
*/
|
||||||
|
function getConfig(): Record<string, string> {
|
||||||
|
// For local development, try to read local .env first
|
||||||
|
try {
|
||||||
|
// This will only work if we're in a project directory with .env
|
||||||
|
const localEnv = {
|
||||||
|
GITEA_TOKEN: process.env.GITEA_TOKEN || '',
|
||||||
|
GITEA_API_URL: process.env.GITEA_API_URL || '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we have local env vars, use them
|
||||||
|
if (localEnv.GITEA_TOKEN && localEnv.GITEA_API_URL) {
|
||||||
|
return localEnv;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore local env errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to global configuration
|
||||||
|
return GlobalConfig.getConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const env = getConfig();
|
||||||
215
src/git.ts
Normal file
215
src/git.ts
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
import { exec } from 'child_process';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
import { writeFile } from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Git service for handling git operations
|
||||||
|
*/
|
||||||
|
export class GitService {
|
||||||
|
/**
|
||||||
|
* Check if the current directory is a git repository
|
||||||
|
* @returns true if git is initialized, false otherwise
|
||||||
|
*/
|
||||||
|
async isGitInitialized(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await execAsync('git status');
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a new git repository
|
||||||
|
*/
|
||||||
|
async initializeGit(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await execAsync('git init');
|
||||||
|
console.log('✅ Git repository initialized');
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to initialize git: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a .gitignore file with common Node.js patterns
|
||||||
|
*/
|
||||||
|
async createGitignore(): Promise<void> {
|
||||||
|
const gitignoreContent = `# Dependencies
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
.env.production
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await writeFile('.gitignore', gitignoreContent);
|
||||||
|
console.log('✅ .gitignore file created');
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to create .gitignore: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a remote origin to the git repository
|
||||||
|
* @param remoteUrl - The remote URL to add
|
||||||
|
*/
|
||||||
|
async addRemote(remoteUrl: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Check if origin already exists
|
||||||
|
try {
|
||||||
|
await execAsync('git remote get-url origin');
|
||||||
|
// If we get here, origin exists, so update it
|
||||||
|
await execAsync(`git remote set-url origin ${remoteUrl}`);
|
||||||
|
console.log('✅ Updated remote origin');
|
||||||
|
} catch {
|
||||||
|
// Origin doesn't exist, add it
|
||||||
|
await execAsync(`git remote add origin ${remoteUrl}`);
|
||||||
|
console.log('✅ Added remote origin');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to add remote: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there are any commits in the repository
|
||||||
|
* @returns true if there are commits, false otherwise
|
||||||
|
*/
|
||||||
|
async hasCommits(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await execAsync('git log --oneline -1');
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there are any staged or unstaged changes
|
||||||
|
* @returns true if there are changes, false otherwise
|
||||||
|
*/
|
||||||
|
async hasChanges(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const { stdout } = await execAsync('git status --porcelain');
|
||||||
|
return stdout.trim().length > 0;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all files and commit them
|
||||||
|
* @param message - Commit message
|
||||||
|
*/
|
||||||
|
async addAndCommit(message: string = 'Initial commit'): Promise<void> {
|
||||||
|
try {
|
||||||
|
await execAsync('git add .');
|
||||||
|
await execAsync(`git commit -m "${message}"`);
|
||||||
|
console.log('✅ Files committed');
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to commit: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push to the remote repository
|
||||||
|
* @param branch - Branch to push (defaults to current branch)
|
||||||
|
*/
|
||||||
|
async push(branch: string = 'main'): Promise<void> {
|
||||||
|
try {
|
||||||
|
// First, check if we're on the main/master branch and rename if needed
|
||||||
|
const { stdout: currentBranch } = await execAsync('git branch --show-current');
|
||||||
|
const current = currentBranch.trim();
|
||||||
|
|
||||||
|
if (current === 'master' && branch === 'main') {
|
||||||
|
await execAsync('git branch -m master main');
|
||||||
|
console.log('✅ Renamed branch from master to main');
|
||||||
|
}
|
||||||
|
|
||||||
|
await execAsync(`git push -u origin ${branch}`);
|
||||||
|
console.log('✅ Pushed to remote repository');
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to push: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
127
src/global-config.ts
Normal file
127
src/global-config.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { readFileSync, existsSync } from 'fs';
|
||||||
|
import { homedir } from 'os';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global configuration manager for the CLI tool
|
||||||
|
* Supports both environment variables and global config files
|
||||||
|
*/
|
||||||
|
export class GlobalConfig {
|
||||||
|
private static readonly CONFIG_DIR = path.join(homedir(), '.gitea-creator');
|
||||||
|
private static readonly CONFIG_FILE = path.join(this.CONFIG_DIR, 'config');
|
||||||
|
private static readonly ENV_FILE = path.join(this.CONFIG_DIR, '.env');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configuration from multiple sources in priority order:
|
||||||
|
* 1. Environment variables
|
||||||
|
* 2. Global .env file (~/.gitea-creator/.env)
|
||||||
|
* 3. Global config file (~/.gitea-creator/config)
|
||||||
|
* @returns Configuration object
|
||||||
|
*/
|
||||||
|
static getConfig(): Record<string, string> {
|
||||||
|
const config: Record<string, string> = {};
|
||||||
|
|
||||||
|
// Try global config file first (lowest priority)
|
||||||
|
try {
|
||||||
|
if (existsSync(this.CONFIG_FILE)) {
|
||||||
|
const configContent = readFileSync(this.CONFIG_FILE, 'utf-8');
|
||||||
|
this.parseConfigContent(configContent, config);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore config file errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try global .env file (medium priority)
|
||||||
|
try {
|
||||||
|
if (existsSync(this.ENV_FILE)) {
|
||||||
|
const envContent = readFileSync(this.ENV_FILE, 'utf-8');
|
||||||
|
this.parseEnvContent(envContent, config);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore .env file errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment variables (highest priority)
|
||||||
|
if (process.env.GITEA_TOKEN) {
|
||||||
|
config.GITEA_TOKEN = process.env.GITEA_TOKEN;
|
||||||
|
}
|
||||||
|
if (process.env.GITEA_API_URL) {
|
||||||
|
config.GITEA_API_URL = process.env.GITEA_API_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse configuration file content
|
||||||
|
* Format: KEY=value (one per line)
|
||||||
|
*/
|
||||||
|
private static parseConfigContent(content: string, config: Record<string, string>): void {
|
||||||
|
const lines = content.split('\n');
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (trimmed && !trimmed.startsWith('#')) {
|
||||||
|
const [key, ...valueParts] = trimmed.split('=');
|
||||||
|
if (key && valueParts.length > 0) {
|
||||||
|
config[key.trim()] = valueParts.join('=').trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse .env file content
|
||||||
|
* Format: KEY=value (supports quotes)
|
||||||
|
*/
|
||||||
|
private static parseEnvContent(content: string, config: Record<string, string>): void {
|
||||||
|
const lines = content.split('\n');
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (trimmed && !trimmed.startsWith('#')) {
|
||||||
|
const [key, ...valueParts] = trimmed.split('=');
|
||||||
|
if (key && valueParts.length > 0) {
|
||||||
|
let value = valueParts.join('=').trim();
|
||||||
|
// Remove quotes if present
|
||||||
|
if ((value.startsWith('"') && value.endsWith('"')) ||
|
||||||
|
(value.startsWith("'") && value.endsWith("'"))) {
|
||||||
|
value = value.slice(1, -1);
|
||||||
|
}
|
||||||
|
config[key.trim()] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path where global config should be stored
|
||||||
|
*/
|
||||||
|
static getConfigPath(): string {
|
||||||
|
return this.CONFIG_DIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get instructions for setting up global configuration
|
||||||
|
*/
|
||||||
|
static getSetupInstructions(): string {
|
||||||
|
return `
|
||||||
|
Global Configuration Setup:
|
||||||
|
|
||||||
|
Option 1: Environment Variables (Recommended)
|
||||||
|
Add to your shell profile (~/.bashrc, ~/.zshrc, etc.):
|
||||||
|
export GITEA_TOKEN=your_token_here
|
||||||
|
export GITEA_API_URL=https://your-gitea-server.com/api/v1
|
||||||
|
|
||||||
|
Option 2: Global Config File
|
||||||
|
Create: ${this.CONFIG_DIR}/.env
|
||||||
|
Content:
|
||||||
|
GITEA_TOKEN=your_token_here
|
||||||
|
GITEA_API_URL=https://your-gitea-server.com/api/v1
|
||||||
|
|
||||||
|
Option 3: Simple Config File
|
||||||
|
Create: ${this.CONFIG_DIR}/config
|
||||||
|
Content:
|
||||||
|
GITEA_TOKEN=your_token_here
|
||||||
|
GITEA_API_URL=https://your-gitea-server.com/api/v1
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
174
src/index.ts
Normal file
174
src/index.ts
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { Repo, type InitializationOptions, type GiteaRepository } from "./repo.js";
|
||||||
|
import { Config } from "./config.js";
|
||||||
|
import { env } from "./env.js";
|
||||||
|
import { CliService } from "./cli.js";
|
||||||
|
import { GitService } from "./git.js";
|
||||||
|
import { PromptService } from "./prompts.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main application class that orchestrates the CLI workflow
|
||||||
|
*/
|
||||||
|
export class App {
|
||||||
|
private config?: Config;
|
||||||
|
private readonly gitService: GitService;
|
||||||
|
private readonly promptService: PromptService;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.gitService = new GitService();
|
||||||
|
this.promptService = new PromptService();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize configuration (lazy loading)
|
||||||
|
*/
|
||||||
|
private initConfig(): void {
|
||||||
|
if (!this.config) {
|
||||||
|
this.config = Config.from(env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a repository on Gitea with the given options
|
||||||
|
* @param options - Repository creation options
|
||||||
|
* @returns Repository data from Gitea API
|
||||||
|
*/
|
||||||
|
async createRepo(options: InitializationOptions): Promise<GiteaRepository> {
|
||||||
|
this.initConfig();
|
||||||
|
const repo = await Repo.create(this.config!, options);
|
||||||
|
return repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main application workflow
|
||||||
|
*/
|
||||||
|
async run(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Parse command line arguments
|
||||||
|
const cliService = new CliService();
|
||||||
|
const options = cliService.parse();
|
||||||
|
|
||||||
|
console.log(`🚀 Creating repository: ${options.name}`);
|
||||||
|
if (options.description) {
|
||||||
|
console.log(`📝 Description: ${options.description}`);
|
||||||
|
}
|
||||||
|
console.log(`🔒 Visibility: ${options.public ? 'Public' : 'Private'}`);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Create repository on Gitea
|
||||||
|
const repoData = await this.createRepo({
|
||||||
|
name: options.name,
|
||||||
|
description: options.description || '',
|
||||||
|
private: !options.public, // CLI uses --public flag, but API expects private boolean
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ Repository created successfully!');
|
||||||
|
console.log(`🌐 Remote URL: ${repoData.clone_url}`);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Handle git operations
|
||||||
|
await this.handleGitOperations(repoData.clone_url);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error:', error instanceof Error ? error.message : String(error));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle git-related operations (initialization, remote setup, pushing)
|
||||||
|
* @param remoteUrl - The remote repository URL
|
||||||
|
*/
|
||||||
|
private async handleGitOperations(remoteUrl: string): Promise<void> {
|
||||||
|
const isGitRepo = await this.gitService.isGitInitialized();
|
||||||
|
|
||||||
|
// If no git repo, ask if user wants to initialize one
|
||||||
|
if (!isGitRepo) {
|
||||||
|
const shouldInit = await this.promptService.askInitializeGit();
|
||||||
|
if (!shouldInit) {
|
||||||
|
console.log('ℹ️ Skipping git setup');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.gitService.initializeGit();
|
||||||
|
await this.gitService.createGitignore();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask if user wants to set remote
|
||||||
|
const shouldSetRemote = await this.promptService.askSetRemote(remoteUrl);
|
||||||
|
if (shouldSetRemote) {
|
||||||
|
await this.gitService.addRemote(remoteUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask if user wants to push
|
||||||
|
const shouldPush = await this.promptService.askPushRepo();
|
||||||
|
if (!shouldPush) {
|
||||||
|
console.log('ℹ️ Skipping push to remote');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have commits
|
||||||
|
const hasCommits = await this.gitService.hasCommits();
|
||||||
|
if (!hasCommits) {
|
||||||
|
// Check if we have changes to commit
|
||||||
|
const hasChanges = await this.gitService.hasChanges();
|
||||||
|
if (hasChanges) {
|
||||||
|
const shouldCommit = await this.promptService.askCommitChanges();
|
||||||
|
if (shouldCommit) {
|
||||||
|
const message = await this.promptService.askCommitMessage();
|
||||||
|
await this.gitService.addAndCommit(message);
|
||||||
|
} else {
|
||||||
|
console.log('ℹ️ Skipping commit and push (no commits to push)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('ℹ️ No changes to commit and push');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push to remote
|
||||||
|
await this.gitService.push();
|
||||||
|
console.log('🎉 All done!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main entry point for the CLI application
|
||||||
|
*/
|
||||||
|
async function main(): Promise<void> {
|
||||||
|
// Set up prompt cancellation handler
|
||||||
|
const promptService = new PromptService();
|
||||||
|
process.on('SIGINT', () => promptService.onCancel());
|
||||||
|
|
||||||
|
// Check if user just wants help or config
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
||||||
|
const cliService = new CliService();
|
||||||
|
cliService.help();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle config option early (before requiring repository name)
|
||||||
|
if (args.includes('--config') || args.includes('-c')) {
|
||||||
|
const { GlobalConfig } = await import('./global-config.js');
|
||||||
|
console.log(GlobalConfig.getSetupInstructions());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = new App();
|
||||||
|
await app.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the application - handles both direct execution and npm global installation
|
||||||
|
// Check if this file is being executed as the main module
|
||||||
|
const isMainModule = import.meta.url === `file://${process.argv[1]}` ||
|
||||||
|
process.argv[1]?.includes('gitea-creator');
|
||||||
|
|
||||||
|
if (isMainModule) {
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error('❌ Unexpected error:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
90
src/prompts.ts
Normal file
90
src/prompts.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import prompts from 'prompts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interactive prompt service for user input
|
||||||
|
*/
|
||||||
|
export class PromptService {
|
||||||
|
/**
|
||||||
|
* Ask user if they want to initialize git
|
||||||
|
* @returns true if user wants to initialize git, false otherwise
|
||||||
|
*/
|
||||||
|
async askInitializeGit(): Promise<boolean> {
|
||||||
|
const response = await prompts({
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'initGit',
|
||||||
|
message: 'No git repository found. Would you like to initialize one?',
|
||||||
|
initial: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.initGit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask user if they want to set the repository as the remote origin
|
||||||
|
* @param remoteUrl - The remote URL that will be set
|
||||||
|
* @returns true if user wants to set remote, false otherwise
|
||||||
|
*/
|
||||||
|
async askSetRemote(remoteUrl: string): Promise<boolean> {
|
||||||
|
const response = await prompts({
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'setRemote',
|
||||||
|
message: `Set ${remoteUrl} as the remote origin?`,
|
||||||
|
initial: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.setRemote;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask user if they want to push the current repository
|
||||||
|
* @returns true if user wants to push, false otherwise
|
||||||
|
*/
|
||||||
|
async askPushRepo(): Promise<boolean> {
|
||||||
|
const response = await prompts({
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'pushRepo',
|
||||||
|
message: 'Push the current repository to the remote?',
|
||||||
|
initial: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.pushRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask user if they want to add and commit current changes before pushing
|
||||||
|
* @returns true if user wants to commit changes, false otherwise
|
||||||
|
*/
|
||||||
|
async askCommitChanges(): Promise<boolean> {
|
||||||
|
const response = await prompts({
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'commitChanges',
|
||||||
|
message: 'You have uncommitted changes. Would you like to commit them first?',
|
||||||
|
initial: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.commitChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask user for a commit message
|
||||||
|
* @returns the commit message
|
||||||
|
*/
|
||||||
|
async askCommitMessage(): Promise<string> {
|
||||||
|
const response = await prompts({
|
||||||
|
type: 'text',
|
||||||
|
name: 'message',
|
||||||
|
message: 'Enter commit message:',
|
||||||
|
initial: 'Initial commit',
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.message || 'Initial commit';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the case when prompts are cancelled (Ctrl+C)
|
||||||
|
*/
|
||||||
|
onCancel() {
|
||||||
|
console.log('\n❌ Operation cancelled by user');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/repo.ts
Normal file
60
src/repo.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { Config } from "./config.js";
|
||||||
|
|
||||||
|
export type InitializationOptions = {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
private: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gitea repository response type
|
||||||
|
*/
|
||||||
|
export type GiteaRepository = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
full_name: string;
|
||||||
|
description: string;
|
||||||
|
private: boolean;
|
||||||
|
clone_url: string;
|
||||||
|
ssh_url: string;
|
||||||
|
html_url: string;
|
||||||
|
owner: {
|
||||||
|
login: string;
|
||||||
|
full_name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new repository on Gitea
|
||||||
|
*/
|
||||||
|
export class Repo {
|
||||||
|
static async create(config: Config, options: Partial<InitializationOptions> = {}): Promise<GiteaRepository> {
|
||||||
|
const { name, description, private: isPrivate } = options;
|
||||||
|
|
||||||
|
const response = await fetch(`${config.apiUrl}/user/repos`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `token ${config.token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
private: isPrivate,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
let errorMessage = `HTTP ${response.status}`;
|
||||||
|
try {
|
||||||
|
const error = await response.json() as { message?: string };
|
||||||
|
errorMessage = error.message || errorMessage;
|
||||||
|
} catch {
|
||||||
|
// Ignore JSON parsing errors, use status code
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to create repository (${response.status}): ${errorMessage}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json() as Promise<GiteaRepository>;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
tsconfig.json
Normal file
41
tsconfig.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
// Visit https://aka.ms/tsconfig to read more about this file
|
||||||
|
"compilerOptions": {
|
||||||
|
// File Layout
|
||||||
|
"rootDir": "./src",
|
||||||
|
"outDir": "./dist",
|
||||||
|
|
||||||
|
// Environment Settings
|
||||||
|
// See also https://aka.ms/tsconfig/module
|
||||||
|
"module": "nodenext",
|
||||||
|
"target": "esnext",
|
||||||
|
"lib": ["esnext"],
|
||||||
|
"types": ["node"],
|
||||||
|
|
||||||
|
// Other Outputs
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
|
||||||
|
// Stricter Typechecking Options
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
|
|
||||||
|
// Style Options
|
||||||
|
// "noImplicitReturns": true,
|
||||||
|
// "noImplicitOverride": true,
|
||||||
|
// "noUnusedLocals": true,
|
||||||
|
// "noUnusedParameters": true,
|
||||||
|
// "noFallthroughCasesInSwitch": true,
|
||||||
|
// "noPropertyAccessFromIndexSignature": true,
|
||||||
|
|
||||||
|
// Recommended Options
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user