1use std::collections::BTreeMap;
4
5use crate::{
6 DefaultValue, FieldSig, FileId, MethodSig, ObjectField, PackageDeclaration, Param, Span, Type,
7 TypeKind, TypeSymbol, Visibility,
8};
9
10use super::MODULE_NAME;
11
12pub fn package_declaration() -> PackageDeclaration {
13 let mut defs = PackageDeclaration::with_package(MODULE_NAME);
14 let mut statics = BTreeMap::new();
15 insert_static(
16 &mut statics,
17 "open",
18 vec![string("path")],
19 repository_type(),
20 "/** Open an ordinary repository under the VFS root. Gated operations check the current caller's grants. */",
21 );
22 insert_static(
23 &mut statics,
24 "init",
25 vec![string("path"), options(&[("branch", Type::String)])],
26 repository_type(),
27 "/** Initialize a repository, default branch main. Requires blueprint Git identity.\n * @capability git.init { path }\n */",
28 );
29 insert_static(
30 &mut statics,
31 "clone",
32 vec![
33 string("url"),
34 string("path"),
35 options(&[("branch", Type::String)]),
36 ],
37 repository_type(),
38 "/** Clone HTTPS into an empty VFS directory, following remote HEAD unless branch is specified. Requires only git.clone; authentication uses host-only GIT_TOKEN when needed.\n * @capability git.clone { path, remote: $url, remoteName: \"origin\", branch: string }\n */",
39 );
40 insert_repository_class(&mut defs, statics);
41 defs
42}
43
44fn insert_static(
45 statics: &mut BTreeMap<String, MethodSig>,
46 name: &str,
47 params: Vec<Param>,
48 ret: Type,
49 doc: &str,
50) {
51 statics.insert(
52 name.into(),
53 MethodSig {
54 optional: false,
55 generics: vec![],
56 params,
57 ret,
58 predicate: None,
59 doc: crate::doc(FileId::GIT, doc),
60 },
61 );
62}
63
64fn insert_repository_class(defs: &mut PackageDeclaration, statics: BTreeMap<String, MethodSig>) {
65 let mut methods = BTreeMap::new();
66 let entry = object(
67 &[
68 ("path", Type::String),
69 ("staged", Type::String),
70 ("unstaged", Type::String),
71 ("untracked", Type::Boolean),
72 ],
73 false,
74 );
75 let commit = object(
76 &[
77 ("id", Type::String),
78 ("message", Type::String),
79 ("authorName", Type::String),
80 ("authorEmail", Type::String),
81 ],
82 false,
83 );
84 methods.insert(
85 "status".into(),
86 MethodSig {
87 optional: false,
88 generics: vec![],
89 params: vec![],
90 ret: with_optional(
91 object(
92 &[("entries", array(entry)), ("clean", Type::Boolean)],
93 false,
94 ),
95 "branch",
96 Type::String,
97 ),
98 predicate: None,
99 doc: crate::doc(
100 FileId::GIT,
101 "/** Inspect staged, unstaged and untracked paths. */",
102 ),
103 },
104 );
105 methods.insert(
106 "log".into(),
107 MethodSig {
108 optional: false,
109 generics: vec![],
110 params: vec![options(&[
111 ("limit", Type::Number),
112 ("offset", Type::Number),
113 ])],
114 ret: with_optional(
115 object(&[("commits", array(commit))], false),
116 "nextOffset",
117 Type::Number,
118 ),
119 predicate: None,
120 doc: crate::doc(FileId::GIT, "/** Read history, default 50 commits; limit 1..1000, nonnegative integer offset. Traversal is resource-bounded. */"),
121 },
122 );
123 methods.insert(
124 "diff".into(),
125 MethodSig {
126 optional: false,
127 generics: vec![],
128 params: vec![options(&[
129 ("mode", Type::String),
130 ("from", Type::String),
131 ("to", Type::String),
132 ])],
133 ret: object(
134 &[
135 ("patch", Type::String),
136 ("binaryPaths", array(Type::String)),
137 ],
138 false,
139 ),
140 predicate: None,
141 doc: crate::doc(FileId::GIT, "/** Compare working (default), staged, or refs (requires from and to). Bounded patches and binary path markers. */"),
142 },
143 );
144 methods.insert(
145 "show".into(),
146 MethodSig {
147 optional: false,
148 generics: vec![],
149 params: vec![string("ref"), string("path")],
150 ret: Type::Uint8Array,
151 predicate: None,
152 doc: crate::doc(FileId::GIT, "/** Read file bytes from a commit. */"),
153 },
154 );
155 methods.insert(
156 "branches".into(),
157 MethodSig {
158 optional: false,
159 generics: vec![],
160 params: vec![],
161 ret: array(object(
162 &[
163 ("name", Type::String),
164 ("id", Type::String),
165 ("current", Type::Boolean),
166 ],
167 false,
168 )),
169 predicate: None,
170 doc: crate::doc(FileId::GIT, "/** List local branches. */"),
171 },
172 );
173 methods.insert(
174 "remotes".into(),
175 MethodSig {
176 optional: false,
177 generics: vec![],
178 params: vec![],
179 ret: array(object(
180 &[("name", Type::String), ("url", Type::String)],
181 false,
182 )),
183 predicate: None,
184 doc: crate::doc(
185 FileId::GIT,
186 "/** List named remotes without credentials. */",
187 ),
188 },
189 );
190 methods.insert(
191 "add".into(),
192 MethodSig {
193 optional: false,
194 generics: vec![],
195 params: vec![Param::new("paths", array(Type::String))],
196 ret: Type::Void,
197 predicate: None,
198 doc: crate::doc(FileId::GIT, "/** Stage explicit files or directories, including deletions. Use . for the working tree. */"),
199 },
200 );
201 methods.insert(
202 "commit".into(),
203 MethodSig {
204 optional: false,
205 generics: vec![],
206 params: vec![string("message")],
207 ret: Type::String,
208 predicate: None,
209 doc: crate::doc(FileId::GIT, "/** Commit staged changes with Blueprint identity and return the commit ID. Empty commits are refused.\n * @capability git.commit { path: string, branch: string }\n */"),
210 },
211 );
212 methods.insert(
213 "createBranch".into(),
214 MethodSig {
215 optional: false,
216 generics: vec![],
217 params: vec![string("name"), default_string("start", "HEAD")],
218 ret: Type::Void,
219 predicate: None,
220 doc: crate::doc(
221 FileId::GIT,
222 "/** Create a branch without overwriting an existing branch. */",
223 ),
224 },
225 );
226 methods.insert(
227 "switchBranch".into(),
228 MethodSig {
229 optional: false,
230 generics: vec![],
231 params: vec![string("name")],
232 ret: Type::Void,
233 predicate: None,
234 doc: crate::doc(
235 FileId::GIT,
236 "/** Switch local branches; requires a completely clean working tree. */",
237 ),
238 },
239 );
240 methods.insert(
241 "addRemote".into(),
242 MethodSig {
243 optional: false,
244 generics: vec![],
245 params: vec![string("name"), string("url")],
246 ret: Type::Void,
247 predicate: None,
248 doc: crate::doc(
249 FileId::GIT,
250 "/** Add a named HTTPS remote, for example origin or upstream. */",
251 ),
252 },
253 );
254 methods.insert(
255 "setRemoteUrl".into(),
256 MethodSig {
257 optional: false,
258 generics: vec![],
259 params: vec![string("name"), string("url")],
260 ret: Type::Void,
261 predicate: None,
262 doc: crate::doc(FileId::GIT, "/** Update an existing remote's HTTPS URL. */"),
263 },
264 );
265 methods.insert(
266 "fetch".into(),
267 MethodSig {
268 optional: false,
269 generics: vec![],
270 params: vec![
271 default_string("remote", "origin"),
272 default_string("branch", ""),
273 ],
274 ret: object(&[("branches", array(Type::String))], false),
275 predicate: None,
276 doc: crate::doc(FileId::GIT, "/** Fetch remote-tracking branches. Empty branch requests every remote branch; every selected branch must be authorized. GIT_TOKEN is optional for public repositories.\n * @capability git.fetch { path: string, remoteName: $remote, remote: string, branch: string }\n */"),
277 },
278 );
279 methods.insert(
280 "pull".into(),
281 MethodSig {
282 optional: false,
283 generics: vec![],
284 params: vec![
285 default_string("remote", "origin"),
286 default_string("branch", ""),
287 ],
288 ret: object(
289 &[("previous", Type::String), ("current", Type::String)],
290 false,
291 ),
292 predicate: None,
293 doc: crate::doc(FileId::GIT, "/** Fetch and fast-forward the current branch under git.fetch. Divergence and dirty worktrees are refused.\n * @capability git.fetch { path: string, remoteName: $remote, remote: string, branch: string }\n */"),
294 },
295 );
296 defs.types.insert(
297 "Repository".into(),
298 TypeSymbol {
299 name: "Repository".into(),
300 mangled_name: crate::mangle::package_symbol(MODULE_NAME, "Repository"),
301 declaration_span: Span::at(FileId::GIT),
302 kind: TypeKind::Class {
303 generics: vec![],
304 methods,
305 fields: BTreeMap::from([("path".into(), FieldSig {
306 ty: Type::String, visibility: Visibility::Private, readonly: true,
307 optional: false, doc: None,
308 })]),
309 narrowing_checks: BTreeMap::new(),
310 method_visibility: BTreeMap::new(),
311 accessors: vec![],
312 constructor: vec![string("path")],
313 constructor_visibility: crate::Visibility::Public,
314 statics,
315 static_visibility: BTreeMap::new(),
316 static_fields: BTreeMap::new(),
317 extends: None,
318 implements: vec![],
319 doc: crate::doc(
320 FileId::GIT,
321 "/** A repository under the VFS root. Construct to open an existing repository, or use Repository.open, Repository.init or Repository.clone. */",
322 ),
323 },
324 },
325 );
326}
327
328fn object(fields: &[(&str, Type)], optional: bool) -> Type {
329 Type::Object {
330 index: None,
331 fields: fields
332 .iter()
333 .map(|(name, ty)| {
334 (
335 (*name).to_owned(),
336 if optional {
337 ObjectField::optional(ty.clone())
338 } else {
339 ObjectField::required(ty.clone())
340 },
341 )
342 })
343 .collect(),
344 }
345}
346
347fn with_optional(mut ty: Type, name: &str, field: Type) -> Type {
349 if let Type::Object { fields, .. } = &mut ty {
350 fields.insert(name.to_owned(), ObjectField::optional(field));
351 }
352 ty
353}
354
355fn array(ty: Type) -> Type {
356 Type::Array(Box::new(ty))
357}
358
359fn string(name: &str) -> Param {
360 Param::new(name, Type::String)
361}
362
363fn default_string(name: &str, value: &str) -> Param {
364 Param::with_default(name, Type::String, DefaultValue::String(value.into()))
365}
366
367fn options(fields: &[(&str, Type)]) -> Param {
368 Param::with_default(
369 "options",
370 Type::union(vec![object(fields, true), Type::Undefined]),
371 DefaultValue::Undefined,
372 )
373}
374
375fn repository_type() -> Type {
376 Type::ClassRef {
377 mangled: crate::mangle::package_symbol(MODULE_NAME, "Repository"),
378 package: crate::Package(MODULE_NAME.into()),
379 name: "Repository".into(),
380 args: vec![],
381 }
382}