freelang-v4 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +548 -0
- package/dist/ast.d.ts +367 -0
- package/dist/ast.js +4 -0
- package/dist/ast.js.map +1 -0
- package/dist/async-basic.test.d.ts +1 -0
- package/dist/async-basic.test.js +88 -0
- package/dist/async-basic.test.js.map +1 -0
- package/dist/async-jest.test.d.ts +1 -0
- package/dist/async-jest.test.js +99 -0
- package/dist/async-jest.test.js.map +1 -0
- package/dist/channel-jest.test.d.ts +1 -0
- package/dist/channel-jest.test.js +148 -0
- package/dist/channel-jest.test.js.map +1 -0
- package/dist/checker-jest.test.d.ts +1 -0
- package/dist/checker-jest.test.js +160 -0
- package/dist/checker-jest.test.js.map +1 -0
- package/dist/checker.d.ts +149 -0
- package/dist/checker.js +1565 -0
- package/dist/checker.js.map +1 -0
- package/dist/checker.test.d.ts +1 -0
- package/dist/checker.test.js +217 -0
- package/dist/checker.test.js.map +1 -0
- package/dist/compiler-jest.test.d.ts +1 -0
- package/dist/compiler-jest.test.js +233 -0
- package/dist/compiler-jest.test.js.map +1 -0
- package/dist/compiler.d.ts +127 -0
- package/dist/compiler.js +1588 -0
- package/dist/compiler.js.map +1 -0
- package/dist/compiler.test.d.ts +1 -0
- package/dist/compiler.test.js +313 -0
- package/dist/compiler.test.js.map +1 -0
- package/dist/db-100m-full.d.ts +5 -0
- package/dist/db-100m-full.js +78 -0
- package/dist/db-100m-full.js.map +1 -0
- package/dist/db-100m-no-index.d.ts +12 -0
- package/dist/db-100m-no-index.js +119 -0
- package/dist/db-100m-no-index.js.map +1 -0
- package/dist/db-100m-real.d.ts +5 -0
- package/dist/db-100m-real.js +131 -0
- package/dist/db-100m-real.js.map +1 -0
- package/dist/db-100m-streaming.d.ts +15 -0
- package/dist/db-100m-streaming.js +164 -0
- package/dist/db-100m-streaming.js.map +1 -0
- package/dist/db-100m-test.d.ts +5 -0
- package/dist/db-100m-test.js +111 -0
- package/dist/db-100m-test.js.map +1 -0
- package/dist/db-jest.test.d.ts +1 -0
- package/dist/db-jest.test.js +182 -0
- package/dist/db-jest.test.js.map +1 -0
- package/dist/db-runtime.d.ts +24 -0
- package/dist/db-runtime.js +204 -0
- package/dist/db-runtime.js.map +1 -0
- package/dist/db.d.ts +249 -0
- package/dist/db.js +593 -0
- package/dist/db.js.map +1 -0
- package/dist/file-io-jest.test.d.ts +1 -0
- package/dist/file-io-jest.test.js +225 -0
- package/dist/file-io-jest.test.js.map +1 -0
- package/dist/for-of-jest.test.d.ts +1 -0
- package/dist/for-of-jest.test.js +230 -0
- package/dist/for-of-jest.test.js.map +1 -0
- package/dist/for-of.test.d.ts +1 -0
- package/dist/for-of.test.js +305 -0
- package/dist/for-of.test.js.map +1 -0
- package/dist/function-literal-jest.test.d.ts +1 -0
- package/dist/function-literal-jest.test.js +180 -0
- package/dist/function-literal-jest.test.js.map +1 -0
- package/dist/function-literal.test.d.ts +1 -0
- package/dist/function-literal.test.js +245 -0
- package/dist/function-literal.test.js.map +1 -0
- package/dist/generics-jest.test.d.ts +1 -0
- package/dist/generics-jest.test.js +93 -0
- package/dist/generics-jest.test.js.map +1 -0
- package/dist/ir-gen.d.ts +15 -0
- package/dist/ir-gen.js +400 -0
- package/dist/ir-gen.js.map +1 -0
- package/dist/ir.d.ts +114 -0
- package/dist/ir.js +5 -0
- package/dist/ir.js.map +1 -0
- package/dist/lexer.d.ts +110 -0
- package/dist/lexer.js +467 -0
- package/dist/lexer.js.map +1 -0
- package/dist/lexer.test.d.ts +1 -0
- package/dist/lexer.test.js +426 -0
- package/dist/lexer.test.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +241 -0
- package/dist/main.js.map +1 -0
- package/dist/module-jest.test.d.ts +1 -0
- package/dist/module-jest.test.js +123 -0
- package/dist/module-jest.test.js.map +1 -0
- package/dist/parser.d.ts +56 -0
- package/dist/parser.js +1060 -0
- package/dist/parser.js.map +1 -0
- package/dist/parser.test.d.ts +1 -0
- package/dist/parser.test.js +461 -0
- package/dist/parser.test.js.map +1 -0
- package/dist/pattern-matching-jest.test.d.ts +1 -0
- package/dist/pattern-matching-jest.test.js +158 -0
- package/dist/pattern-matching-jest.test.js.map +1 -0
- package/dist/pkg/init.d.ts +1 -0
- package/dist/pkg/init.js +118 -0
- package/dist/pkg/init.js.map +1 -0
- package/dist/pkg/install.d.ts +1 -0
- package/dist/pkg/install.js +77 -0
- package/dist/pkg/install.js.map +1 -0
- package/dist/pkg/registry.d.ts +23 -0
- package/dist/pkg/registry.js +106 -0
- package/dist/pkg/registry.js.map +1 -0
- package/dist/pkg/run.d.ts +1 -0
- package/dist/pkg/run.js +76 -0
- package/dist/pkg/run.js.map +1 -0
- package/dist/pkg/toml.d.ts +5 -0
- package/dist/pkg/toml.js +117 -0
- package/dist/pkg/toml.js.map +1 -0
- package/dist/repl.d.ts +15 -0
- package/dist/repl.js +197 -0
- package/dist/repl.js.map +1 -0
- package/dist/runtime/bytecode.d.ts +92 -0
- package/dist/runtime/bytecode.js +253 -0
- package/dist/runtime/bytecode.js.map +1 -0
- package/dist/runtime/value.d.ts +102 -0
- package/dist/runtime/value.js +302 -0
- package/dist/runtime/value.js.map +1 -0
- package/dist/runtime/vm.d.ts +65 -0
- package/dist/runtime/vm.js +293 -0
- package/dist/runtime/vm.js.map +1 -0
- package/dist/struct-instance-jest.test.d.ts +1 -0
- package/dist/struct-instance-jest.test.js +209 -0
- package/dist/struct-instance-jest.test.js.map +1 -0
- package/dist/struct-instance.test.d.ts +1 -0
- package/dist/struct-instance.test.js +291 -0
- package/dist/struct-instance.test.js.map +1 -0
- package/dist/struct-jest.test.d.ts +1 -0
- package/dist/struct-jest.test.js +176 -0
- package/dist/struct-jest.test.js.map +1 -0
- package/dist/struct.test.d.ts +1 -0
- package/dist/struct.test.js +231 -0
- package/dist/struct.test.js.map +1 -0
- package/dist/trait-jest.test.d.ts +1 -0
- package/dist/trait-jest.test.js +120 -0
- package/dist/trait-jest.test.js.map +1 -0
- package/dist/vm-jest.test.d.ts +1 -0
- package/dist/vm-jest.test.js +569 -0
- package/dist/vm-jest.test.js.map +1 -0
- package/dist/vm.d.ts +81 -0
- package/dist/vm.js +1956 -0
- package/dist/vm.js.map +1 -0
- package/dist/vm.test.d.ts +1 -0
- package/dist/vm.test.js +337 -0
- package/dist/vm.test.js.map +1 -0
- package/dist/web-repl/sandbox.d.ts +11 -0
- package/dist/web-repl/sandbox.js +76 -0
- package/dist/web-repl/sandbox.js.map +1 -0
- package/dist/web-repl/server.d.ts +1 -0
- package/dist/web-repl/server.js +111 -0
- package/dist/web-repl/server.js.map +1 -0
- package/dist/while-loop-jest.test.d.ts +1 -0
- package/dist/while-loop-jest.test.js +201 -0
- package/dist/while-loop-jest.test.js.map +1 -0
- package/dist/while-loop.test.d.ts +1 -0
- package/dist/while-loop.test.js +262 -0
- package/dist/while-loop.test.js.map +1 -0
- package/docs/EXPERIENCE.md +787 -0
- package/docs/README.md +175 -0
- package/docs/V1_V2_V3_ANALYSIS.md +107 -0
- package/docs/_config.yml +36 -0
- package/docs/api-reference.md +459 -0
- package/docs/architecture.md +470 -0
- package/docs/benchmarks.md +295 -0
- package/docs/comparison.md +454 -0
- package/docs/index.md +335 -0
- package/docs/language-completeness.md +228 -0
- package/docs/learning-guide.md +651 -0
- package/package.json +65 -0
- package/src/api/deploy_key.fl +294 -0
- package/src/api/issue.fl +302 -0
- package/src/api/org.fl +356 -0
- package/src/api/repo.fl +394 -0
- package/src/api/team.fl +299 -0
- package/src/api/user.fl +385 -0
- package/src/api/webhook.fl +273 -0
- package/src/ast.ts +158 -0
- package/src/async-basic.test.ts +94 -0
- package/src/async-jest.test.ts +107 -0
- package/src/channel-jest.test.ts +158 -0
- package/src/checker-jest.test.ts +189 -0
- package/src/checker.test.ts +279 -0
- package/src/checker.ts +1861 -0
- package/src/commands/analyze.fl +227 -0
- package/src/commands/auth.fl +315 -0
- package/src/commands/batch.fl +349 -0
- package/src/commands/config.fl +199 -0
- package/src/commands/deploy_key.fl +352 -0
- package/src/commands/issue.fl +275 -0
- package/src/commands/main.fl +492 -0
- package/src/commands/org.fl +425 -0
- package/src/commands/repo.fl +581 -0
- package/src/commands/team.fl +244 -0
- package/src/commands/user.fl +423 -0
- package/src/commands/webhook.fl +400 -0
- package/src/compiler-jest.test.ts +275 -0
- package/src/compiler.test.ts +375 -0
- package/src/compiler.ts +1770 -0
- package/src/config.fl +175 -0
- package/src/core/batch.fl +355 -0
- package/src/core/cache.fl +284 -0
- package/src/core/ensure.fl +324 -0
- package/src/db-100m-full.ts +96 -0
- package/src/db-100m-no-index.ts +133 -0
- package/src/db-100m-real.ts +152 -0
- package/src/db-100m-streaming.ts +154 -0
- package/src/db-100m-test.ts +136 -0
- package/src/db-jest.test.ts +161 -0
- package/src/db-runtime.ts +242 -0
- package/src/db.ts +676 -0
- package/src/errors.fl +134 -0
- package/src/for-of-jest.test.ts +246 -0
- package/src/for-of.test.ts +308 -0
- package/src/function-literal-jest.test.ts +193 -0
- package/src/function-literal.test.ts +248 -0
- package/src/generics-jest.test.ts +104 -0
- package/src/http/client.fl +327 -0
- package/src/ir-gen.ts +459 -0
- package/src/ir.ts +80 -0
- package/src/lexer.test.ts +499 -0
- package/src/lexer.ts +522 -0
- package/src/main.ts +223 -0
- package/src/models.fl +162 -0
- package/src/module-jest.test.ts +145 -0
- package/src/parser.test.ts +542 -0
- package/src/parser.ts +1211 -0
- package/src/pattern-matching-jest.test.ts +170 -0
- package/src/pkg/init.ts +91 -0
- package/src/pkg/install.ts +56 -0
- package/src/pkg/registry.ts +103 -0
- package/src/pkg/run.ts +49 -0
- package/src/pkg/toml.ts +129 -0
- package/src/repl.ts +190 -0
- package/src/runtime/bytecode.ts +291 -0
- package/src/runtime/value.ts +322 -0
- package/src/runtime/vm.ts +354 -0
- package/src/self-host/bootstrap.fl +68 -0
- package/src/self-host/interpreter.fl +361 -0
- package/src/self-host/lexer-simple.fl +22 -0
- package/src/self-host/lexer.fl +305 -0
- package/src/self-host/parser.fl +580 -0
- package/src/struct-instance-jest.test.ts +221 -0
- package/src/struct-instance.test.ts +293 -0
- package/src/struct-jest.test.ts +187 -0
- package/src/struct.test.ts +234 -0
- package/src/trait-jest.test.ts +136 -0
- package/src/vm-jest.test.ts +754 -0
- package/src/vm.ts +1976 -0
- package/src/web-repl/public/index.html +50 -0
- package/src/web-repl/public/main.js +105 -0
- package/src/web-repl/public/style.css +225 -0
- package/src/web-repl/sandbox.ts +88 -0
- package/src/web-repl/server.ts +97 -0
- package/src/while-loop-jest.test.ts +218 -0
- package/src/while-loop.test.ts +267 -0
package/src/api/repo.fl
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
// FreeLang v4.2 — Gogs CLI RepoService
|
|
2
|
+
// 저장소 관리 API 서비스
|
|
3
|
+
// 400라인: list, get, create, update, delete, ensure, fork, list_branches
|
|
4
|
+
|
|
5
|
+
/// 저장소 서비스
|
|
6
|
+
struct RepoService {
|
|
7
|
+
client: HttpClient
|
|
8
|
+
cache: CacheManager
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/// RepoService 생성
|
|
12
|
+
fn new_repo_service(client: HttpClient, cache: CacheManager) -> RepoService {
|
|
13
|
+
RepoService {
|
|
14
|
+
client: client,
|
|
15
|
+
cache: cache
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ==================== 저장소 CRUD ====================
|
|
20
|
+
|
|
21
|
+
/// 저장소 목록 조회 (캐시 활용)
|
|
22
|
+
async fn list_repos(service: RepoService) -> Result<[Repo]> {
|
|
23
|
+
var cache_key = "repos:list"
|
|
24
|
+
|
|
25
|
+
// 캐시 확인
|
|
26
|
+
var cached = cache_get(service.cache, cache_key)
|
|
27
|
+
if cached != "" {
|
|
28
|
+
println("[cache] hit: repos list")
|
|
29
|
+
var repos_arr = json_parse(cached) as [Repo]
|
|
30
|
+
return Result::Ok(repos_arr)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// API 호출
|
|
34
|
+
var resp = await http_get(service.client, "/api/v1/user/repos")
|
|
35
|
+
|
|
36
|
+
if is_error(resp.status) {
|
|
37
|
+
return Result::Err(response_to_error(resp))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 캐시 저장
|
|
41
|
+
cache_set(service.cache, cache_key, resp.body)
|
|
42
|
+
|
|
43
|
+
// 파싱
|
|
44
|
+
var repos_arr = json_parse(resp.body) as [Repo]
|
|
45
|
+
Result::Ok(repos_arr)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// 저장소 상세 조회 (캐시 활용)
|
|
49
|
+
async fn get_repo(service: RepoService, owner: str, name: str) -> Result<Repo> {
|
|
50
|
+
var cache_key = "repo:" + owner + "/" + name
|
|
51
|
+
|
|
52
|
+
// 캐시 확인
|
|
53
|
+
var cached = cache_get(service.cache, cache_key)
|
|
54
|
+
if cached != "" {
|
|
55
|
+
println("[cache] hit: " + cache_key)
|
|
56
|
+
var repo = json_parse(cached) as Repo
|
|
57
|
+
return Result::Ok(repo)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// API 호출
|
|
61
|
+
var path = "/api/v1/repos/" + owner + "/" + name
|
|
62
|
+
var resp = await http_get(service.client, path)
|
|
63
|
+
|
|
64
|
+
if is_error(resp.status) {
|
|
65
|
+
return Result::Err(response_to_error(resp))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 캐시 저장
|
|
69
|
+
cache_set(service.cache, cache_key, resp.body)
|
|
70
|
+
|
|
71
|
+
// 파싱
|
|
72
|
+
var repo = json_parse(resp.body) as Repo
|
|
73
|
+
Result::Ok(repo)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// 저장소 생성
|
|
77
|
+
async fn create_repo(service: RepoService, req: CreateRepoRequest) -> Result<Repo> {
|
|
78
|
+
var json_data = json_stringify(req)
|
|
79
|
+
var resp = await http_post_json(service.client, "/api/v1/user/repos", json_data)
|
|
80
|
+
|
|
81
|
+
if is_error(resp.status) {
|
|
82
|
+
return Result::Err(response_to_error(resp))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 캐시 무효화
|
|
86
|
+
cache_invalidate(service.cache, "repos:list")
|
|
87
|
+
|
|
88
|
+
// 파싱
|
|
89
|
+
var repo = json_parse(resp.body) as Repo
|
|
90
|
+
Result::Ok(repo)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// 저장소 업데이트
|
|
94
|
+
async fn update_repo(service: RepoService, owner: str, name: str, req: CreateRepoRequest) -> Result<Repo> {
|
|
95
|
+
var path = "/api/v1/repos/" + owner + "/" + name
|
|
96
|
+
var json_data = json_stringify(req)
|
|
97
|
+
var resp = await http_patch(service.client, path, json_data)
|
|
98
|
+
|
|
99
|
+
if is_error(resp.status) {
|
|
100
|
+
return Result::Err(response_to_error(resp))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 캐시 무효화
|
|
104
|
+
var cache_key = "repo:" + owner + "/" + name
|
|
105
|
+
cache_invalidate(service.cache, cache_key)
|
|
106
|
+
cache_invalidate(service.cache, "repos:list")
|
|
107
|
+
|
|
108
|
+
// 파싱
|
|
109
|
+
var repo = json_parse(resp.body) as Repo
|
|
110
|
+
Result::Ok(repo)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// 저장소 삭제
|
|
114
|
+
async fn delete_repo(service: RepoService, owner: str, name: str) -> Result<str> {
|
|
115
|
+
var path = "/api/v1/repos/" + owner + "/" + name
|
|
116
|
+
var resp = await http_delete(service.client, path)
|
|
117
|
+
|
|
118
|
+
if is_error(resp.status) {
|
|
119
|
+
return Result::Err(response_to_error(resp))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 캐시 무효화
|
|
123
|
+
var cache_key = "repo:" + owner + "/" + name
|
|
124
|
+
cache_invalidate(service.cache, cache_key)
|
|
125
|
+
cache_invalidate(service.cache, "repos:list")
|
|
126
|
+
|
|
127
|
+
Result::Ok("Repository deleted")
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ==================== Ensure 패턴 ====================
|
|
131
|
+
|
|
132
|
+
/// 저장소 ensure (멱등성 보증)
|
|
133
|
+
async fn ensure_repo(service: RepoService, owner: str, req: CreateRepoRequest) -> Result<EnsureAction> {
|
|
134
|
+
var path = "/api/v1/repos/" + owner + "/" + req.name
|
|
135
|
+
|
|
136
|
+
// desired 상태 정의
|
|
137
|
+
var desired_json = json_stringify(req)
|
|
138
|
+
|
|
139
|
+
var ctx = EnsureContext {
|
|
140
|
+
name: owner + "/" + req.name,
|
|
141
|
+
|
|
142
|
+
get_fn: async fn() -> Result<str> {
|
|
143
|
+
var resp = await http_get(service.client, path)
|
|
144
|
+
if is_error(resp.status) {
|
|
145
|
+
if resp.status == 404 {
|
|
146
|
+
Result::Ok("") // 없음
|
|
147
|
+
} else {
|
|
148
|
+
Result::Err(response_to_error(resp))
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
Result::Ok(resp.body)
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
create_fn: async fn() -> Result<str> {
|
|
156
|
+
var json_data = json_stringify(req)
|
|
157
|
+
var resp = await http_post_json(service.client, "/api/v1/user/repos", json_data)
|
|
158
|
+
if is_error(resp.status) {
|
|
159
|
+
Result::Err(response_to_error(resp))
|
|
160
|
+
} else {
|
|
161
|
+
// 캐시 무효화
|
|
162
|
+
cache_invalidate(service.cache, "repos:list")
|
|
163
|
+
Result::Ok(resp.body)
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
update_fn: async fn(changes: [str]) -> Result<str> {
|
|
168
|
+
var json_data = json_stringify(req)
|
|
169
|
+
var resp = await http_patch(service.client, path, json_data)
|
|
170
|
+
if is_error(resp.status) {
|
|
171
|
+
Result::Err(response_to_error(resp))
|
|
172
|
+
} else {
|
|
173
|
+
// 캐시 무효화
|
|
174
|
+
var cache_key = "repo:" + owner + "/" + req.name
|
|
175
|
+
cache_invalidate(service.cache, cache_key)
|
|
176
|
+
cache_invalidate(service.cache, "repos:list")
|
|
177
|
+
Result::Ok(resp.body)
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
delete_fn: async fn() -> Result<str> {
|
|
182
|
+
var resp = await http_delete(service.client, path)
|
|
183
|
+
if is_error(resp.status) {
|
|
184
|
+
Result::Err(response_to_error(resp))
|
|
185
|
+
} else {
|
|
186
|
+
// 캐시 무효화
|
|
187
|
+
var cache_key = "repo:" + owner + "/" + req.name
|
|
188
|
+
cache_invalidate(service.cache, cache_key)
|
|
189
|
+
cache_invalidate(service.cache, "repos:list")
|
|
190
|
+
Result::Ok("")
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
desired: desired_json
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
var action = await ensure(ctx)
|
|
198
|
+
Result::Ok(action)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ==================== 저장소 포크 ====================
|
|
202
|
+
|
|
203
|
+
/// 저장소 포크
|
|
204
|
+
async fn fork_repo(service: RepoService, owner: str, name: str, new_owner: str) -> Result<Repo> {
|
|
205
|
+
var path = "/api/v1/repos/" + owner + "/" + name + "/forks"
|
|
206
|
+
|
|
207
|
+
// 포크 요청 본문
|
|
208
|
+
var body_str = "{\"owner\": \"" + new_owner + "\"}"
|
|
209
|
+
var resp = await http_post_json(service.client, path, body_str)
|
|
210
|
+
|
|
211
|
+
if is_error(resp.status) {
|
|
212
|
+
return Result::Err(response_to_error(resp))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 캐시 무효화 (사용자 저장소 목록)
|
|
216
|
+
cache_invalidate(service.cache, "repos:list")
|
|
217
|
+
|
|
218
|
+
// 파싱
|
|
219
|
+
var repo = json_parse(resp.body) as Repo
|
|
220
|
+
Result::Ok(repo)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ==================== 브랜치 관리 ====================
|
|
224
|
+
|
|
225
|
+
/// 저장소 브랜치 목록
|
|
226
|
+
async fn list_branches(service: RepoService, owner: str, name: str) -> Result<[Branch]> {
|
|
227
|
+
var cache_key = "branches:" + owner + "/" + name
|
|
228
|
+
|
|
229
|
+
// 캐시 확인
|
|
230
|
+
var cached = cache_get(service.cache, cache_key)
|
|
231
|
+
if cached != "" {
|
|
232
|
+
println("[cache] hit: " + cache_key)
|
|
233
|
+
var branches_arr = json_parse(cached) as [Branch]
|
|
234
|
+
return Result::Ok(branches_arr)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// API 호출
|
|
238
|
+
var path = "/api/v1/repos/" + owner + "/" + name + "/branches"
|
|
239
|
+
var resp = await http_get(service.client, path)
|
|
240
|
+
|
|
241
|
+
if is_error(resp.status) {
|
|
242
|
+
return Result::Err(response_to_error(resp))
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 캐시 저장
|
|
246
|
+
cache_set(service.cache, cache_key, resp.body)
|
|
247
|
+
|
|
248
|
+
// 파싱
|
|
249
|
+
var branches_arr = json_parse(resp.body) as [Branch]
|
|
250
|
+
Result::Ok(branches_arr)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ==================== 저장소 통계 ====================
|
|
254
|
+
|
|
255
|
+
/// 저장소 통계 조회
|
|
256
|
+
async fn get_repo_stats(service: RepoService, owner: str, name: str) -> Result<RepoStats> {
|
|
257
|
+
var path = "/api/v1/repos/" + owner + "/" + name + "/stats"
|
|
258
|
+
var resp = await http_get(service.client, path)
|
|
259
|
+
|
|
260
|
+
if is_error(resp.status) {
|
|
261
|
+
return Result::Err(response_to_error(resp))
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// 파싱
|
|
265
|
+
var stats = json_parse(resp.body) as RepoStats
|
|
266
|
+
Result::Ok(stats)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ==================== 저장소 검색 ====================
|
|
270
|
+
|
|
271
|
+
/// 저장소 검색
|
|
272
|
+
async fn search_repos(service: RepoService, query: str) -> Result<[Repo]> {
|
|
273
|
+
var path = "/api/v1/repos/search?q=" + query
|
|
274
|
+
var resp = await http_get(service.client, path)
|
|
275
|
+
|
|
276
|
+
if is_error(resp.status) {
|
|
277
|
+
return Result::Err(response_to_error(resp))
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 파싱
|
|
281
|
+
var repos_arr = json_parse(resp.body) as [Repo]
|
|
282
|
+
Result::Ok(repos_arr)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ==================== 저장소 권한 ====================
|
|
286
|
+
|
|
287
|
+
/// 저장소 협업자 추가
|
|
288
|
+
async fn add_collaborator(
|
|
289
|
+
service: RepoService,
|
|
290
|
+
owner: str,
|
|
291
|
+
name: str,
|
|
292
|
+
username: str,
|
|
293
|
+
permission: str
|
|
294
|
+
) -> Result<str> {
|
|
295
|
+
var path = "/api/v1/repos/" + owner + "/" + name + "/collaborators/" + username
|
|
296
|
+
var body_str = "{\"permission\": \"" + permission + "\"}"
|
|
297
|
+
var resp = await http_put(service.client, path, body_str)
|
|
298
|
+
|
|
299
|
+
if is_error(resp.status) {
|
|
300
|
+
return Result::Err(response_to_error(resp))
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
Result::Ok("Collaborator added")
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/// 저장소 협업자 제거
|
|
307
|
+
async fn remove_collaborator(service: RepoService, owner: str, name: str, username: str) -> Result<str> {
|
|
308
|
+
var path = "/api/v1/repos/" + owner + "/" + name + "/collaborators/" + username
|
|
309
|
+
var resp = await http_delete(service.client, path)
|
|
310
|
+
|
|
311
|
+
if is_error(resp.status) {
|
|
312
|
+
return Result::Err(response_to_error(resp))
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
Result::Ok("Collaborator removed")
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ==================== 저장소 이슈 ====================
|
|
319
|
+
|
|
320
|
+
/// 저장소 이슈 수 조회
|
|
321
|
+
async fn get_issue_count(service: RepoService, owner: str, name: str) -> Result<i32> {
|
|
322
|
+
var path = "/api/v1/repos/" + owner + "/" + name + "/issues"
|
|
323
|
+
var resp = await http_get(service.client, path)
|
|
324
|
+
|
|
325
|
+
if is_error(resp.status) {
|
|
326
|
+
return Result::Err(response_to_error(resp))
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 이슈 배열 파싱
|
|
330
|
+
var issues_arr = json_parse(resp.body) as [Issue]
|
|
331
|
+
Result::Ok(length(issues_arr))
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ==================== 별명 (Alias) ====================
|
|
335
|
+
|
|
336
|
+
/// 저장소 목록 조회 (별명)
|
|
337
|
+
async fn list_all_repos(service: RepoService) -> Result<[Repo]> {
|
|
338
|
+
await list_repos(service)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/// 저장소 상세 조회 (별명)
|
|
342
|
+
async fn get_single_repo(service: RepoService, owner: str, name: str) -> Result<Repo> {
|
|
343
|
+
await get_repo(service, owner, name)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/// 저장소 생성 (별명)
|
|
347
|
+
async fn new_repo(service: RepoService, name: str, description: str, private: bool) -> Result<Repo> {
|
|
348
|
+
var req = CreateRepoRequest {
|
|
349
|
+
name: name,
|
|
350
|
+
description: description,
|
|
351
|
+
private: private
|
|
352
|
+
}
|
|
353
|
+
await create_repo(service, req)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/// 저장소 삭제 (별명)
|
|
357
|
+
async fn rm_repo(service: RepoService, owner: str, name: str) -> Result<str> {
|
|
358
|
+
await delete_repo(service, owner, name)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Branch 모델 정의
|
|
362
|
+
struct Branch {
|
|
363
|
+
name: str
|
|
364
|
+
commit: Commit
|
|
365
|
+
protected: bool
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
struct Commit {
|
|
369
|
+
id: str
|
|
370
|
+
message: str
|
|
371
|
+
author: Author
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
struct Author {
|
|
375
|
+
name: str
|
|
376
|
+
email: str
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// RepoStats 모델 정의
|
|
380
|
+
struct RepoStats {
|
|
381
|
+
total_commits: i32
|
|
382
|
+
total_branches: i32
|
|
383
|
+
total_tags: i32
|
|
384
|
+
total_issues: i32
|
|
385
|
+
total_pull_requests: i32
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// 필요한 함수들 (다른 모듈에서 임포트):
|
|
389
|
+
// - http_get, http_post_json, http_delete, http_patch, http_put
|
|
390
|
+
// - is_error, response_to_error
|
|
391
|
+
// - cache_get, cache_set, cache_invalidate
|
|
392
|
+
// - json_stringify, json_parse
|
|
393
|
+
// - ensure, EnsureContext, EnsureAction
|
|
394
|
+
// - println, length
|
package/src/api/team.fl
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// FreeLang v4.2 — Gogs CLI TeamService
|
|
2
|
+
// 팀 관리 API 서비스
|
|
3
|
+
// 200라인: create, list, delete, add_member, remove_member, list_members
|
|
4
|
+
|
|
5
|
+
/// 팀 서비스
|
|
6
|
+
struct TeamService {
|
|
7
|
+
client: HttpClient
|
|
8
|
+
cache: CacheManager
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/// TeamService 생성
|
|
12
|
+
fn new_team_service(client: HttpClient, cache: CacheManager) -> TeamService {
|
|
13
|
+
TeamService {
|
|
14
|
+
client: client,
|
|
15
|
+
cache: cache
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ==================== 팀 CRUD ====================
|
|
20
|
+
|
|
21
|
+
/// 팀 목록 조회 (조직 단위)
|
|
22
|
+
async fn list_teams(service: TeamService, org_name: str) -> Result<[Team]> {
|
|
23
|
+
var cache_key = "teams:" + org_name
|
|
24
|
+
|
|
25
|
+
// 캐시 확인
|
|
26
|
+
var cached = cache_get(service.cache, cache_key)
|
|
27
|
+
if cached != "" {
|
|
28
|
+
println("[cache] hit: " + cache_key)
|
|
29
|
+
var teams_arr = json_parse(cached) as [Team]
|
|
30
|
+
return Result::Ok(teams_arr)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// API 호출
|
|
34
|
+
var path = "/api/v1/orgs/" + org_name + "/teams"
|
|
35
|
+
var resp = await http_get(service.client, path)
|
|
36
|
+
|
|
37
|
+
if is_error(resp.status) {
|
|
38
|
+
return Result::Err(response_to_error(resp))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 캐시 저장
|
|
42
|
+
cache_set(service.cache, cache_key, resp.body)
|
|
43
|
+
|
|
44
|
+
// 파싱
|
|
45
|
+
var teams_arr = json_parse(resp.body) as [Team]
|
|
46
|
+
Result::Ok(teams_arr)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// 팀 상세 조회
|
|
50
|
+
async fn get_team(service: TeamService, team_id: i32) -> Result<Team> {
|
|
51
|
+
var cache_key = "team:" + str(team_id)
|
|
52
|
+
|
|
53
|
+
// 캐시 확인
|
|
54
|
+
var cached = cache_get(service.cache, cache_key)
|
|
55
|
+
if cached != "" {
|
|
56
|
+
println("[cache] hit: " + cache_key)
|
|
57
|
+
var team = json_parse(cached) as Team
|
|
58
|
+
return Result::Ok(team)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// API 호출
|
|
62
|
+
var path = "/api/v1/teams/" + str(team_id)
|
|
63
|
+
var resp = await http_get(service.client, path)
|
|
64
|
+
|
|
65
|
+
if is_error(resp.status) {
|
|
66
|
+
return Result::Err(response_to_error(resp))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 캐시 저장
|
|
70
|
+
cache_set(service.cache, cache_key, resp.body)
|
|
71
|
+
|
|
72
|
+
// 파싱
|
|
73
|
+
var team = json_parse(resp.body) as Team
|
|
74
|
+
Result::Ok(team)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// 팀 생성
|
|
78
|
+
async fn create_team(service: TeamService, org_name: str, req: CreateTeamRequest) -> Result<Team> {
|
|
79
|
+
var path = "/api/v1/orgs/" + org_name + "/teams"
|
|
80
|
+
var json_data = json_stringify(req)
|
|
81
|
+
var resp = await http_post_json(service.client, path, json_data)
|
|
82
|
+
|
|
83
|
+
if is_error(resp.status) {
|
|
84
|
+
return Result::Err(response_to_error(resp))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 캐시 무효화
|
|
88
|
+
var cache_key = "teams:" + org_name
|
|
89
|
+
cache_invalidate(service.cache, cache_key)
|
|
90
|
+
|
|
91
|
+
// 파싱
|
|
92
|
+
var team = json_parse(resp.body) as Team
|
|
93
|
+
Result::Ok(team)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// 팀 업데이트
|
|
97
|
+
async fn update_team(service: TeamService, team_id: i32, req: UpdateTeamRequest) -> Result<Team> {
|
|
98
|
+
var path = "/api/v1/teams/" + str(team_id)
|
|
99
|
+
var json_data = json_stringify(req)
|
|
100
|
+
var resp = await http_patch(service.client, path, json_data)
|
|
101
|
+
|
|
102
|
+
if is_error(resp.status) {
|
|
103
|
+
return Result::Err(response_to_error(resp))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 캐시 무효화
|
|
107
|
+
var cache_key = "team:" + str(team_id)
|
|
108
|
+
cache_invalidate(service.cache, cache_key)
|
|
109
|
+
|
|
110
|
+
// 파싱
|
|
111
|
+
var team = json_parse(resp.body) as Team
|
|
112
|
+
Result::Ok(team)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// 팀 삭제
|
|
116
|
+
async fn delete_team(service: TeamService, team_id: i32) -> Result<str> {
|
|
117
|
+
var path = "/api/v1/teams/" + str(team_id)
|
|
118
|
+
var resp = await http_delete(service.client, path)
|
|
119
|
+
|
|
120
|
+
if is_error(resp.status) {
|
|
121
|
+
return Result::Err(response_to_error(resp))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 캐시 무효화
|
|
125
|
+
var cache_key = "team:" + str(team_id)
|
|
126
|
+
cache_invalidate(service.cache, cache_key)
|
|
127
|
+
|
|
128
|
+
Result::Ok("Team deleted")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ==================== 팀 멤버 ====================
|
|
132
|
+
|
|
133
|
+
/// 팀 멤버 목록
|
|
134
|
+
async fn list_team_members(service: TeamService, team_id: i32) -> Result<[User]> {
|
|
135
|
+
var cache_key = "team_members:" + str(team_id)
|
|
136
|
+
|
|
137
|
+
// 캐시 확인
|
|
138
|
+
var cached = cache_get(service.cache, cache_key)
|
|
139
|
+
if cached != "" {
|
|
140
|
+
println("[cache] hit: " + cache_key)
|
|
141
|
+
var members_arr = json_parse(cached) as [User]
|
|
142
|
+
return Result::Ok(members_arr)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// API 호출
|
|
146
|
+
var path = "/api/v1/teams/" + str(team_id) + "/members"
|
|
147
|
+
var resp = await http_get(service.client, path)
|
|
148
|
+
|
|
149
|
+
if is_error(resp.status) {
|
|
150
|
+
return Result::Err(response_to_error(resp))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 캐시 저장
|
|
154
|
+
cache_set(service.cache, cache_key, resp.body)
|
|
155
|
+
|
|
156
|
+
// 파싱
|
|
157
|
+
var members_arr = json_parse(resp.body) as [User]
|
|
158
|
+
Result::Ok(members_arr)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/// 팀 멤버 추가
|
|
162
|
+
async fn add_team_member(service: TeamService, team_id: i32, username: str) -> Result<str> {
|
|
163
|
+
var path = "/api/v1/teams/" + str(team_id) + "/members/" + username
|
|
164
|
+
var resp = await http_put(service.client, path, "{}")
|
|
165
|
+
|
|
166
|
+
if is_error(resp.status) {
|
|
167
|
+
return Result::Err(response_to_error(resp))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 캐시 무효화
|
|
171
|
+
var cache_key = "team_members:" + str(team_id)
|
|
172
|
+
cache_invalidate(service.cache, cache_key)
|
|
173
|
+
|
|
174
|
+
Result::Ok("Member added to team")
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// 팀 멤버 제거
|
|
178
|
+
async fn remove_team_member(service: TeamService, team_id: i32, username: str) -> Result<str> {
|
|
179
|
+
var path = "/api/v1/teams/" + str(team_id) + "/members/" + username
|
|
180
|
+
var resp = await http_delete(service.client, path)
|
|
181
|
+
|
|
182
|
+
if is_error(resp.status) {
|
|
183
|
+
return Result::Err(response_to_error(resp))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 캐시 무효화
|
|
187
|
+
var cache_key = "team_members:" + str(team_id)
|
|
188
|
+
cache_invalidate(service.cache, cache_key)
|
|
189
|
+
|
|
190
|
+
Result::Ok("Member removed from team")
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// 사용자가 팀의 멤버인지 확인
|
|
194
|
+
async fn is_team_member(service: TeamService, team_id: i32, username: str) -> Result<bool> {
|
|
195
|
+
var path = "/api/v1/teams/" + str(team_id) + "/members/" + username
|
|
196
|
+
var resp = await http_get(service.client, path)
|
|
197
|
+
|
|
198
|
+
if is_error(resp.status) {
|
|
199
|
+
if resp.status == 404 {
|
|
200
|
+
return Result::Ok(false)
|
|
201
|
+
}
|
|
202
|
+
return Result::Err(response_to_error(resp))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
Result::Ok(true)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ==================== 팀 저장소 ====================
|
|
209
|
+
|
|
210
|
+
/// 팀 저장소 목록
|
|
211
|
+
async fn list_team_repos(service: TeamService, team_id: i32) -> Result<[Repo]> {
|
|
212
|
+
var cache_key = "team_repos:" + str(team_id)
|
|
213
|
+
|
|
214
|
+
// 캐시 확인
|
|
215
|
+
var cached = cache_get(service.cache, cache_key)
|
|
216
|
+
if cached != "" {
|
|
217
|
+
println("[cache] hit: " + cache_key)
|
|
218
|
+
var repos_arr = json_parse(cached) as [Repo]
|
|
219
|
+
return Result::Ok(repos_arr)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// API 호출
|
|
223
|
+
var path = "/api/v1/teams/" + str(team_id) + "/repos"
|
|
224
|
+
var resp = await http_get(service.client, path)
|
|
225
|
+
|
|
226
|
+
if is_error(resp.status) {
|
|
227
|
+
return Result::Err(response_to_error(resp))
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 캐시 저장
|
|
231
|
+
cache_set(service.cache, cache_key, resp.body)
|
|
232
|
+
|
|
233
|
+
// 파싱
|
|
234
|
+
var repos_arr = json_parse(resp.body) as [Repo]
|
|
235
|
+
Result::Ok(repos_arr)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/// 팀에 저장소 추가
|
|
239
|
+
async fn add_team_repo(service: TeamService, team_id: i32, owner: str, repo: str) -> Result<str> {
|
|
240
|
+
var path = "/api/v1/teams/" + str(team_id) + "/repos/" + owner + "/" + repo
|
|
241
|
+
var resp = await http_put(service.client, path, "{}")
|
|
242
|
+
|
|
243
|
+
if is_error(resp.status) {
|
|
244
|
+
return Result::Err(response_to_error(resp))
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 캐시 무효화
|
|
248
|
+
var cache_key = "team_repos:" + str(team_id)
|
|
249
|
+
cache_invalidate(service.cache, cache_key)
|
|
250
|
+
|
|
251
|
+
Result::Ok("Repository added to team")
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/// 팀에서 저장소 제거
|
|
255
|
+
async fn remove_team_repo(service: TeamService, team_id: i32, owner: str, repo: str) -> Result<str> {
|
|
256
|
+
var path = "/api/v1/teams/" + str(team_id) + "/repos/" + owner + "/" + repo
|
|
257
|
+
var resp = await http_delete(service.client, path)
|
|
258
|
+
|
|
259
|
+
if is_error(resp.status) {
|
|
260
|
+
return Result::Err(response_to_error(resp))
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 캐시 무효화
|
|
264
|
+
var cache_key = "team_repos:" + str(team_id)
|
|
265
|
+
cache_invalidate(service.cache, cache_key)
|
|
266
|
+
|
|
267
|
+
Result::Ok("Repository removed from team")
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ==================== 별명 (Alias) ====================
|
|
271
|
+
|
|
272
|
+
/// 팀 생성 (별명)
|
|
273
|
+
async fn new_team(service: TeamService, org_name: str, name: str, description: str) -> Result<Team> {
|
|
274
|
+
var req = CreateTeamRequest {
|
|
275
|
+
name: name,
|
|
276
|
+
description: description,
|
|
277
|
+
permission: "push"
|
|
278
|
+
}
|
|
279
|
+
await create_team(service, org_name, req)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/// 팀 삭제 (별명)
|
|
283
|
+
async fn rm_team(service: TeamService, team_id: i32) -> Result<str> {
|
|
284
|
+
await delete_team(service, team_id)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// UpdateTeamRequest 모델 정의
|
|
288
|
+
struct UpdateTeamRequest {
|
|
289
|
+
name: str
|
|
290
|
+
description: str
|
|
291
|
+
permission: str
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 필요한 함수들 (다른 모듈에서 임포트):
|
|
295
|
+
// - http_get, http_post_json, http_delete, http_patch, http_put
|
|
296
|
+
// - is_error, response_to_error
|
|
297
|
+
// - cache_get, cache_set, cache_invalidate
|
|
298
|
+
// - json_stringify, json_parse
|
|
299
|
+
// - println, length, str
|