this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

CLI: Add label edit command

futurGH 8e2e687b 739a1ae0

+101 -11
+100 -11
src/bin.ts
··· 1 1 #!/usr/bin/env node 2 2 import type { ComAtprotoLabelDefs } from "@atcute/client/lexicons"; 3 + import { spawn } from "node:child_process"; 4 + import * as fs from "node:fs/promises"; 5 + import * as os from "node:os"; 6 + import * as path from "node:path"; 3 7 import prompt from "prompts"; 4 8 import { 5 9 declareLabeler, ··· 85 89 console.error("Error setting up labeler:", error); 86 90 } 87 91 } 88 - } else if (command === "label" && (subcommand === "add" || subcommand === "delete")) { 92 + } else if ( 93 + command === "label" 94 + && (subcommand === "add" || subcommand === "delete" || subcommand === "edit") 95 + ) { 89 96 const credentials = await promptCredentials(); 90 97 const labelDefinitions = await getLabelerLabelDefinitions(credentials) ?? []; 91 98 ··· 96 103 const newDefinitions = await promptLabelDefinitions(); 97 104 if (newDefinitions.length) { 98 105 const definitions = [...labelDefinitions, ...newDefinitions]; 99 - await setLabelerLabelDefinitions(credentials, definitions); 100 - console.log("Declared label(s):", definitions.map((d) => d.identifier).join(", ")); 106 + 107 + try { 108 + await setLabelerLabelDefinitions(credentials, definitions); 109 + console.log("Declared label(s):", definitions.map((d) => d.identifier).join(", ")); 110 + } catch (error) { 111 + console.error("Error adding label(s):", error); 112 + } 101 113 } else { 102 - console.log( 103 - "No labels were defined. You can use the `label add` command later to define new labels.", 104 - ); 114 + console.log("No labels were defined."); 105 115 } 106 - } else { 116 + } else if (subcommand === "delete") { 107 117 if (!labelDefinitions.length) { 108 118 console.log( 109 119 "No labels are currently declared. Use the `label add` command to define new labels.", ··· 122 132 })), 123 133 }, { onCancel: () => process.exit(1) }); 124 134 125 - const definitions = labelDefinitions.filter((def) => !identifiers.includes(def.identifier)); 135 + const [newDefinitions, removedIdentifiers] = labelDefinitions.reduce< 136 + [Array<ComAtprotoLabelDefs.LabelValueDefinition>, Array<string>] 137 + >(([newDefs, removed], def) => { 138 + if (!identifiers.includes(def.identifier)) { 139 + newDefs.push(def); 140 + } else { 141 + removed.push(def.identifier); 142 + } 143 + return [newDefs, removed]; 144 + }, [[], []]); 126 145 127 146 try { 128 - if (definitions.length) { 129 - await setLabelerLabelDefinitions(credentials, definitions); 130 - console.log("Deleted label(s):", identifiers.join(", ")); 147 + if (removedIdentifiers.length) { 148 + await setLabelerLabelDefinitions(credentials, newDefinitions); 149 + console.log("Deleted label(s):", removedIdentifiers.join(", ")); 131 150 } else { 132 151 console.log("No labels were selected. Nothing to delete."); 133 152 } 134 153 } catch (error) { 135 154 console.error("Failed to delete labels:", error); 136 155 } 156 + } else if (subcommand === "edit") { 157 + const labelDefinitions = await getLabelerLabelDefinitions(credentials) ?? []; 158 + 159 + try { 160 + const newDefinitions = await editLabelDefinitions(labelDefinitions); 161 + if (newDefinitions.length) { 162 + await setLabelerLabelDefinitions(credentials, newDefinitions); 163 + console.log("Label definitions updated."); 164 + } else { 165 + console.log("No changes were made."); 166 + } 167 + } catch (error) { 168 + console.error("Error updating label definitions:", error); 169 + } 137 170 } 138 171 } else { 139 172 console.log("Usage: npx @skyware/labeler [command]"); ··· 142 175 console.log(" clear - Restore a labeler account to normal."); 143 176 console.log(" label add - Add new label declarations to a labeler account."); 144 177 console.log(" label delete - Remove label declarations from a labeler account."); 178 + console.log(" label edit - Bulk edit label definitions."); 145 179 } 146 180 147 181 async function promptCredentials(): Promise<LoginCredentials> { ··· 294 328 295 329 return definitions; 296 330 } 331 + 332 + async function editLabelDefinitions( 333 + labelDefinitions: Array<ComAtprotoLabelDefs.LabelValueDefinition>, 334 + ): Promise<Array<ComAtprotoLabelDefs.LabelValueDefinition>> { 335 + // os.tmpdir() returns a symlink on macOS 336 + const tmpdir = await fs.realpath(os.tmpdir()); 337 + 338 + const tmpFile = path.join(tmpdir, "labels.json"); 339 + await fs.writeFile(tmpFile, JSON.stringify(labelDefinitions, null, 4)); 340 + await fs.chmod(tmpFile, 0o600); 341 + 342 + const editor = process.env.VISUAL || process.env.EDITOR || "vi"; 343 + await new Promise<void>((resolve, reject) => { 344 + const child = spawn(editor, [tmpFile], { stdio: "inherit" }); 345 + child.on("error", reject); 346 + child.on("exit", (code) => { 347 + if (code === 0) { 348 + resolve(); 349 + } else { 350 + reject(new Error(`Code ${code}`)); 351 + } 352 + }); 353 + }); 354 + 355 + const definitionsText = await fs.readFile(tmpFile, "utf8"); 356 + let newDefinitions: Array<ComAtprotoLabelDefs.LabelValueDefinition>; 357 + try { 358 + newDefinitions = JSON.parse(definitionsText); 359 + } catch (error) { 360 + throw new Error( 361 + `Error parsing JSON: ${error}` + "\n\nFull definitions:\n" + definitionsText, 362 + ); 363 + } finally { 364 + await fs.unlink(tmpFile); 365 + } 366 + 367 + if (!Array.isArray(newDefinitions)) { 368 + throw new Error("Definitions must be an array.\n\nFull definitions:\n" + definitionsText); 369 + } 370 + 371 + for (const definition of newDefinitions) { 372 + if ( 373 + !definition || typeof definition !== "object" || !definition.identifier 374 + || !definition.locales.length || !definition.blurs || !definition.severity 375 + ) { 376 + throw new Error( 377 + "Invalid label definition: " + JSON.stringify(definition) 378 + + "\n\nFull definitions:\n" 379 + + definitionsText, 380 + ); 381 + } 382 + } 383 + 384 + return newDefinitions; 385 + }
+1
src/scripts/declareLabeler.ts
··· 39 39 policies: { labelValues, labelValueDefinitions: labelDefinitions }, 40 40 createdAt: new Date().toISOString(), 41 41 } satisfies AppBskyLabelerService.Record, 42 + validate: true, 42 43 } satisfies ComAtprotoRepoCreateRecord.Input; 43 44 44 45 // We check if existing is truthy because an empty array means the record exists, but contains no definitions.