Commit3b585e0
fix: function spans 72 consecutive non-empty lines (>50) (+6 more)
1 file changed+115−773b585e09b9b9b931393b4a1fb91fac83d2571e18
1 changed file+115−77
Modifiedcli/gluecron.ts+115−77View fileUnifiedSplit
@@ -91,6 +91,12 @@ export async function http(
9191 return json;
9292}
9393
94// ---------- GraphQL Utilities ----------
95
96function sanitizeGraphQLString(input: string): string {
97 return input.replace(/["\\]/g, '\\$&');
98}
99
94100// ---------- Commands ----------
95101
96102export const HELP = `gluecron CLI v${VERSION}
@@ -141,7 +147,7 @@ export async function cmdRepoLs(
141147): Promise<Array<{ owner: string; name: string; visibility: string }>> {
142148 const username = user || cfg.username;
143149 if (!username) throw new Error("no user context — log in or pass --user");
144 const q = `{ user(username:"${username}") { repos { name visibility } } }`;
150 const q = `{ user(username:"${sanitizeGraphQLString(username)}") { repos { name visibility } } }`;
145151 const r = await http(cfg, "POST", "/api/graphql", { query: q });
146152 const repos = r?.data?.user?.repos || [];
147153 return repos.map((x: any) => ({
@@ -157,7 +163,7 @@ export async function cmdRepoShow(
157163): Promise<Record<string, any>> {
158164 const [owner, name] = slug.split("/");
159165 if (!owner || !name) throw new Error("expected owner/name");
160 const q = `{ repository(owner:"${owner}", name:"${name}") {
166 const q = `{ repository(owner:"${sanitizeGraphQLString(owner)}", name:"${sanitizeGraphQLString(name)}") {
161167 name description visibility starCount forkCount
162168 owner { username }
163169 issues(state:"open", limit:5) { number title }
@@ -184,7 +190,7 @@ export async function cmdIssuesLs(
184190 slug: string
185191): Promise<Array<{ number: number; title: string }>> {
186192 const [owner, name] = slug.split("/");
187 const q = `{ repository(owner:"${owner}", name:"${name}") { issues(state:"open", limit:50) { number title } } }`;
193 const q = `{ repository(owner:"${sanitizeGraphQLString(owner)}", name:"${sanitizeGraphQLString(name)}") { issues(state:"open", limit:50) { number title } } }`;
188194 const r = await http(cfg, "POST", "/api/graphql", { query: q });
189195 return r?.data?.repository?.issues || [];
190196}
@@ -193,6 +199,101 @@ export async function cmdGql(cfg: Config, query: string): Promise<any> {
193199 return http(cfg, "POST", "/api/graphql", { query });
194200}
195201
202// ---------- Command Handlers ----------
203
204async function handleHostCmd(cfg: Config, rest: string[], out: (msg: string) => void): Promise<number> {
205 if (rest[0]) {
206 cfg.host = rest[0];
207 saveConfig(cfg);
208 }
209 out(cfg.host);
210 return 0;
211}
212
213async function handleLoginCmd(cfg: Config, out: (msg: string) => void): Promise<number> {
214 const { default: readline } = await import("node:readline/promises");
215 const rl = readline.createInterface({
216 input: process.stdin,
217 output: process.stdout,
218 });
219 try {
220 const next = await cmdLogin(cfg, (q) => rl.question(q));
221 out(`Logged in as ${next.username || "(unknown)"}`);
222 return 0;
223 } finally {
224 rl.close();
225 }
226}
227
228async function handleRepoCmd(cfg: Config, rest: string[], out: (msg: string) => void): Promise<number> {
229 const sub = rest[0];
230 if (sub === "ls") {
231 return handleRepoLsCmd(cfg, rest, out);
232 }
233 if (sub === "show") {
234 return handleRepoShowCmd(cfg, rest, out);
235 }
236 if (sub === "create") {
237 return handleRepoCreateCmd(cfg, rest, out);
238 }
239 out("usage: gluecron repo (ls|show|create)");
240 return 1;
241}
242
243async function handleRepoLsCmd(cfg: Config, rest: string[], out: (msg: string) => void): Promise<number> {
244 const userFlagIdx = rest.indexOf("--user");
245 const user = userFlagIdx >= 0 && userFlagIdx + 1 < rest.length ? rest[userFlagIdx + 1] : undefined;
246 const repos = await cmdRepoLs(cfg, user);
247 for (const r of repos) {
248 out(` ${r.owner}/${r.name} · ${r.visibility}`);
249 }
250 return 0;
251}
252
253async function handleRepoShowCmd(cfg: Config, rest: string[], out: (msg: string) => void): Promise<number> {
254 const repo = await cmdRepoShow(cfg, rest[1]);
255 if (!repo) {
256 out("(not found)");
257 return 1;
258 }
259 out(JSON.stringify(repo, null, 2));
260 return 0;
261}
262
263async function handleRepoCreateCmd(cfg: Config, rest: string[], out: (msg: string) => void): Promise<number> {
264 const isPrivate = rest.includes("--private");
265 const name = rest.find((x, i) => i > 0 && !x.startsWith("--"));
266 if (!name) {
267 out("usage: gluecron repo create <name> [--private]");
268 return 1;
269 }
270 const r = await cmdRepoCreate(cfg, name, isPrivate);
271 out(JSON.stringify(r, null, 2));
272 return 0;
273}
274
275async function handleIssuesCmd(cfg: Config, rest: string[], out: (msg: string) => void): Promise<number> {
276 if (rest[0] !== "ls" || !rest[1]) {
277 out("usage: gluecron issues ls <owner/name>");
278 return 1;
279 }
280 const issues = await cmdIssuesLs(cfg, rest[1]);
281 for (const i of issues) {
282 out(` #${i.number} ${i.title}`);
283 }
284 return 0;
285}
286
287async function handleGqlCmd(cfg: Config, rest: string[], out: (msg: string) => void): Promise<number> {
288 if (!rest[0]) {
289 out("usage: gluecron gql '<query>'");
290 return 1;
291 }
292 const r = await cmdGql(cfg, rest.join(" "));
293 out(JSON.stringify(r, null, 2));
294 return 0;
295}
296
196297// ---------- Dispatcher ----------
197298
198299export async function dispatch(argv: string[], out = console.log): Promise<number> {
@@ -210,82 +311,19 @@ export async function dispatch(argv: string[], out = console.log): Promise<numbe
210311
211312 try {
212313 switch (cmd) {
213 case "host": {
214 if (rest[0]) {
215 cfg.host = rest[0];
216 saveConfig(cfg);
217 }
218 out(cfg.host);
219 return 0;
220 }
221 case "login": {
222 const { default: readline } = await import("node:readline/promises");
223 const rl = readline.createInterface({
224 input: process.stdin,
225 output: process.stdout,
226 });
227 const next = await cmdLogin(cfg, (q) => rl.question(q));
228 rl.close();
229 out(`Logged in as ${next.username || "(unknown)"}`);
230 return 0;
231 }
314 case "host":
315 return await handleHostCmd(cfg, rest, out);
316 case "login":
317 return await handleLoginCmd(cfg, out);
232318 case "whoami":
233319 out(await cmdWhoami(cfg));
234320 return 0;
235 case "repo": {
236 const sub = rest[0];
237 if (sub === "ls") {
238 const userFlagIdx = rest.indexOf("--user");
239 const user = userFlagIdx >= 0 ? rest[userFlagIdx + 1] : undefined;
240 const repos = await cmdRepoLs(cfg, user);
241 for (const r of repos) {
242 out(` ${r.owner}/${r.name} · ${r.visibility}`);
243 }
244 return 0;
245 }
246 if (sub === "show") {
247 const repo = await cmdRepoShow(cfg, rest[1]);
248 if (!repo) {
249 out("(not found)");
250 return 1;
251 }
252 out(JSON.stringify(repo, null, 2));
253 return 0;
254 }
255 if (sub === "create") {
256 const isPrivate = rest.includes("--private");
257 const name = rest.find((x, i) => i > 0 && !x.startsWith("--"));
258 if (!name) {
259 out("usage: gluecron repo create <name> [--private]");
260 return 1;
261 }
262 const r = await cmdRepoCreate(cfg, name, isPrivate);
263 out(JSON.stringify(r, null, 2));
264 return 0;
265 }
266 out("usage: gluecron repo (ls|show|create)");
267 return 1;
268 }
269 case "issues": {
270 if (rest[0] !== "ls" || !rest[1]) {
271 out("usage: gluecron issues ls <owner/name>");
272 return 1;
273 }
274 const issues = await cmdIssuesLs(cfg, rest[1]);
275 for (const i of issues) {
276 out(` #${i.number} ${i.title}`);
277 }
278 return 0;
279 }
280 case "gql": {
281 if (!rest[0]) {
282 out("usage: gluecron gql '<query>'");
283 return 1;
284 }
285 const r = await cmdGql(cfg, rest.join(" "));
286 out(JSON.stringify(r, null, 2));
287 return 0;
288 }
321 case "repo":
322 return await handleRepoCmd(cfg, rest, out);
323 case "issues":
324 return await handleIssuesCmd(cfg, rest, out);
325 case "gql":
326 return await handleGqlCmd(cfg, rest, out);
289327 default:
290328 out(`unknown command: ${cmd}\n`);
291329 out(HELP);
@@ -301,4 +339,4 @@ export async function dispatch(argv: string[], out = console.log): Promise<numbe
301339if (import.meta.main) {
302340 const code = await dispatch(process.argv.slice(2));
303341 process.exit(code);
304}
342}
\ No newline at end of file
305343