Compare commits
4 Commits
0f2e6b56e7
...
ef29b029a2
| Author | SHA1 | Date |
|---|---|---|
|
|
ef29b029a2 | |
|
|
d9446e7c5b | |
|
|
d9f8b7f740 | |
|
|
4231075253 |
|
|
@ -0,0 +1,69 @@
|
|||
name: CI Pipeline
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, master, develop]
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches: [main, master, develop]
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'tests/**'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'tsconfig.json'
|
||||
- '.eslintrc.json'
|
||||
- '.github/workflows/**'
|
||||
- '.GitLink/workflows/**'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint-and-type-check:
|
||||
name: Lint & Type Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Type Check
|
||||
run: npm run type-check
|
||||
|
||||
test:
|
||||
name: Test Suite
|
||||
runs-on: ubuntu-latest
|
||||
needs: lint-and-type-check
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: npm test -- --coverage
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
name: Deploy to Production
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
confirm:
|
||||
description: '确认要部署到生产环境吗?请输入 deploy'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy to Production
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.confirm == 'deploy'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Deploy to Production Server
|
||||
env:
|
||||
PROD_API_KEY: ${{ secrets.PROD_API_KEY }}
|
||||
run: |
|
||||
echo "Replace with production deploy (kubectl / SSH / platform action)."
|
||||
echo "Use secrets (e.g. PROD_API_KEY) from the production environment — do not echo secret values."
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
name: Deploy to Staging
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'Dockerfile'
|
||||
- '.github/workflows/deploy-staging.yml'
|
||||
- '.GitLink/workflows/deploy-staging.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-staging-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and Push Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository }}:staging
|
||||
ghcr.io/${{ github.repository }}:${{ github.sha }}
|
||||
|
||||
deploy:
|
||||
name: Deploy to Staging Environment
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-push
|
||||
environment: staging
|
||||
steps:
|
||||
- name: Deploy to Staging Server
|
||||
run: |
|
||||
echo "Replace with real deploy: kubectl apply, SSH, or your GitLink deployment action."
|
||||
echo "Image: ghcr.io/${{ github.repository }}:staging"
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
"jest": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest"
|
||||
},
|
||||
"rules": {
|
||||
"indent": ["error", 2],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
name: CI Pipeline
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, master, develop]
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches: [main, master, develop]
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'tests/**'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'tsconfig.json'
|
||||
- '.eslintrc.json'
|
||||
- '.github/workflows/**'
|
||||
- '.GitLink/workflows/**'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint-and-type-check:
|
||||
name: Lint & Type Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Type Check
|
||||
run: npm run type-check
|
||||
|
||||
test:
|
||||
name: Test Suite
|
||||
runs-on: ubuntu-latest
|
||||
needs: lint-and-type-check
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: npm test -- --coverage
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
name: Deploy to Production
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
confirm:
|
||||
description: '确认要部署到生产环境吗?请输入 deploy'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy to Production
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.confirm == 'deploy'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Deploy to Production Server
|
||||
env:
|
||||
PROD_API_KEY: ${{ secrets.PROD_API_KEY }}
|
||||
run: |
|
||||
echo "Replace with production deploy (kubectl / SSH / platform action)."
|
||||
echo "Use secrets (e.g. PROD_API_KEY) from the production environment — do not echo secret values."
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
name: Deploy to Staging
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'Dockerfile'
|
||||
- '.github/workflows/deploy-staging.yml'
|
||||
- '.GitLink/workflows/deploy-staging.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-staging-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and Push Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository }}:staging
|
||||
ghcr.io/${{ github.repository }}:${{ github.sha }}
|
||||
|
||||
deploy:
|
||||
name: Deploy to Staging Environment
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-push
|
||||
environment: staging
|
||||
steps:
|
||||
- name: Deploy to Staging Server
|
||||
run: |
|
||||
echo "Replace with real deploy: kubectl apply, SSH, or your GitLink deployment action."
|
||||
echo "Image: ghcr.io/${{ github.repository }}:staging"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
node_modules/
|
||||
dist/
|
||||
coverage/
|
||||
.env
|
||||
.DS_Store
|
||||
*.log
|
||||
.vscode/
|
||||
.idea/
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
COPY src ./src
|
||||
|
||||
CMD ["node", "src/index.js"]
|
||||
103
README.md
103
README.md
|
|
@ -1,2 +1,103 @@
|
|||
# CI
|
||||
# GitLink CI/CD Demo Project(danshen/CI)
|
||||
|
||||
一个用于演示 GitLink / GitHub Actions 风格 CI/CD 的示例项目。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
.
|
||||
├── .github/workflows/ # 标准 Actions 目录(多数平台识别这里)
|
||||
│ ├── ci.yml
|
||||
│ ├── deploy-staging.yml
|
||||
│ └── deploy-prod.yml
|
||||
├── .GitLink/workflows/ # 与上面内容相同;若 GitLink 只认此目录,保留其一即可
|
||||
│ ├── ci.yml
|
||||
│ ├── deploy-staging.yml
|
||||
│ └── deploy-prod.yml
|
||||
├── Dockerfile
|
||||
├── src/
|
||||
├── tests/
|
||||
├── package.json
|
||||
├── package-lock.json # 提交到 Git,CI 里 npm ci 需要
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
|
||||
- **greet(name)**: 生成问候语
|
||||
- **add(a, b)**: 加法运算
|
||||
- **isValidEmail(email)**: 邮箱验证
|
||||
|
||||
## 流水线做什么
|
||||
|
||||
| 工作流 | 何时运行 |
|
||||
|--------|----------|
|
||||
| **CI Pipeline** | 推送到 `main` / `master` / `develop`,或针对这些分支的 PR;也可手动运行 |
|
||||
| **Deploy to Staging** | 推送到 `main` 或 `master` 且改动了应用相关路径;也可手动运行 |
|
||||
| **Deploy to Production** | 仅手动运行,且输入框必须填 **deploy** |
|
||||
|
||||
## 针对仓库 `https://gitlink.org.cn/danshen/CI.git` 的操作示例
|
||||
|
||||
你的 GitLink 项目页:[danshen/CI](https://gitlink.org.cn/danshen/CI)(默认分支一般是 **`master`**)。流水线入口在顶部 **「流水线(devops)」**。
|
||||
|
||||
### 1. 在本机绑定远程并推送(PowerShell)
|
||||
|
||||
在项目根目录执行(若已添加过 `origin`,先 `git remote remove origin`):
|
||||
|
||||
```powershell
|
||||
cd d:\Litter\111
|
||||
git remote add origin https://gitlink.org.cn/danshen/CI.git
|
||||
git checkout -B master
|
||||
git add -A
|
||||
git status
|
||||
git commit -m "chore: add CI/CD workflows and demo app"
|
||||
```
|
||||
|
||||
若远程已有一次提交(例如只有 `README.md`),第一次推送前需要合并历史:
|
||||
|
||||
```powershell
|
||||
git pull origin master --allow-unrelated-histories
|
||||
# 若有冲突,按提示解决后:git add -A && git commit -m "merge remote"
|
||||
git push -u origin master
|
||||
```
|
||||
|
||||
若你确认可以覆盖远程、只保留本地这一套代码:
|
||||
|
||||
```powershell
|
||||
git push -u origin master --force
|
||||
```
|
||||
|
||||
### 2. 在网页上点哪里
|
||||
|
||||
1. 浏览器打开:<https://gitlink.org.cn/danshen/CI>
|
||||
2. 点 **「流水线(devops)」**,应能看到 **CI Pipeline**、**Deploy to Staging**、**Deploy to Production**。
|
||||
3. 若列表为空:到 **「代码库」** 确认已推送 **`.github/workflows/`** 或 **`.GitLink/workflows/`** 下的 yml;GitLink 帮助中心里会说明认哪个目录(常见为与 GitHub 类似的 `workflows` 目录)。
|
||||
|
||||
### 3. 分支说明
|
||||
|
||||
本仓库流水线已对 **`master`**、`main`、`develop` 配置触发;与你当前 GitLink 默认 **`master`** 一致,推送到 `master` 即可跑 CI;合并到 `master` 也会触发预发布镜像流水线(在改动命中 `paths` 时)。
|
||||
|
||||
---
|
||||
|
||||
## 你在 GitLink 上还需要做的事(网页权限)
|
||||
|
||||
1. **把本仓库推送到 GitLink**(见上文命令)。
|
||||
2. 打开 **「流水线(devops)」**,确认能看到上述三个工作流。
|
||||
3. **若同一次推送触发了两套完全相同的流水线**(因为同时存在 `.github` 与 `.GitLink` 两套 yml):删掉你平台**不读取**的那一套目录里的 yml 即可,只保留一个目录。
|
||||
4. 在 **Settings → Environments**(或 GitLink 等价菜单)里新建 **`staging`** 和 **`production`**;生产环境建议开启 **审批人**。
|
||||
5. (可选)在 Secrets 里添加 **`CODECOV_TOKEN`**、在 production 环境配置 **`PROD_API_KEY`**。
|
||||
6. 若推镜像到 `ghcr.io` 失败:在平台文档中开启 **Packages 写入权限**,或按说明改用个人访问令牌(PAT)登录镜像仓库。
|
||||
|
||||
## 开发命令
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run lint
|
||||
npm run type-check
|
||||
npm test
|
||||
npm run build
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "gitlink-ci-cd-demo",
|
||||
"version": "1.0.0",
|
||||
"description": "GitLink CI/CD Pipeline Demo Project",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"lint": "eslint src/",
|
||||
"type-check": "tsc --noEmit",
|
||||
"test": "jest",
|
||||
"build": "node -e \"require('fs').cpSync('src','dist',{recursive:true})\""
|
||||
},
|
||||
"keywords": ["ci-cd", "gitlink", "demo"],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"eslint": "^8.57.0",
|
||||
"jest": "^29.7.0",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/** @param {string} [name] */
|
||||
function greet(name = 'World') {
|
||||
return `Hello, ${name}!`;
|
||||
}
|
||||
|
||||
/** @param {number} a @param {number} b */
|
||||
function add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
/** @param {string} email */
|
||||
function isValidEmail(email) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
greet,
|
||||
add,
|
||||
isValidEmail
|
||||
};
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
console.log('=== Node.js 环境测试 ===');
|
||||
console.log('Node.js 版本:', process.version);
|
||||
|
||||
try {
|
||||
const { greet, add, isValidEmail } = require('./src/index');
|
||||
|
||||
console.log('\n=== 功能测试 ===');
|
||||
console.log('greet():', greet());
|
||||
console.log('greet("GitLink"):', greet('GitLink'));
|
||||
console.log('add(2, 3):', add(2, 3));
|
||||
console.log('isValidEmail("test@example.com"):', isValidEmail('test@example.com'));
|
||||
|
||||
console.log('\n✅ 测试通过!项目配置正确!');
|
||||
} catch (error) {
|
||||
console.log('\n❌ 测试失败:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
const { greet, add, isValidEmail } = require('../src/index');
|
||||
|
||||
describe('greet function', () => {
|
||||
test('greets with default name', () => {
|
||||
expect(greet()).toBe('Hello, World!');
|
||||
});
|
||||
|
||||
test('greets with custom name', () => {
|
||||
expect(greet('GitLink')).toBe('Hello, GitLink!');
|
||||
});
|
||||
|
||||
test('handles empty string', () => {
|
||||
expect(greet('')).toBe('Hello, !');
|
||||
});
|
||||
});
|
||||
|
||||
describe('add function', () => {
|
||||
test('adds two positive numbers', () => {
|
||||
expect(add(2, 3)).toBe(5);
|
||||
});
|
||||
|
||||
test('adds negative numbers', () => {
|
||||
expect(add(-1, -1)).toBe(-2);
|
||||
});
|
||||
|
||||
test('adds zero', () => {
|
||||
expect(add(0, 5)).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidEmail function', () => {
|
||||
test('validates correct email', () => {
|
||||
expect(isValidEmail('test@example.com')).toBe(true);
|
||||
});
|
||||
|
||||
test('rejects email without @', () => {
|
||||
expect(isValidEmail('test.example.com')).toBe(false);
|
||||
});
|
||||
|
||||
test('rejects empty email', () => {
|
||||
expect(isValidEmail('')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "CommonJS",
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Loading…
Reference in New Issue