@figtools/cli 0.1.0 → 0.2.1

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.es.md CHANGED
@@ -72,10 +72,14 @@ Omite el mensaje `Resolving N URL(s)...` en stderr — útil si estás capturand
72
72
  | `--format` | `json`, `markdown` | `json` | Formato de salida |
73
73
  | `--output` | ruta de archivo o carpeta | stdout (json) / `.` (markdown) | Dónde escribir el resultado |
74
74
  | `--quiet` | — | `false` | Omite el mensaje de progreso en stderr |
75
+ | `--help`, `-h` | — | — | Imprime información de uso y termina |
76
+ | `--version`, `-v` | — | — | Imprime la versión del CLI y termina |
75
77
 
76
78
  ## Troubleshooting
77
79
 
78
80
  - **`Error: unsupported extension "<ext>"`**: con `--format markdown`, `--output` siempre se trata como una carpeta. Con `--format json`, solo se acepta una ruta terminada en `.json` o sin extensión (tratada como carpeta); cualquier otra extensión falla explícitamente.
81
+ - **`--format` argument 'xml' is invalid. Allowed choices are json, markdown.`**: un valor de `--format` no soportado se rechaza explícitamente en vez de caer en `json` en silencio.
82
+ - **`error: unknown option '--foo'`**: una flag no reconocida se rechaza explícitamente en vez de ser ignorada en silencio.
79
83
  - **El proceso termina con código `1` pero imprimió resultados**: significa que al menos una de las URLs falló — revisa el bloque `URLs with errors:` al final de stderr para ver el código (`FigmaScraperErrorCode`) y mensaje de cada una. Ver la tabla de errores en el [README de `@figtools/core`](../core/README.es.md#errores-posibles).
80
84
  - **`figtools login` no avanza**: la ventana de Chromium espera indefinidamente a que termines el login manual; confirma que llegaste a `https://www.figma.com/files/...` antes de cerrar la ventana.
81
85
 
package/README.md CHANGED
@@ -72,10 +72,14 @@ Omits the `Resolving N URL(s)...` message on stderr — useful if you're capturi
72
72
  | `--format` | `json`, `markdown` | `json` | Output format |
73
73
  | `--output` | file or folder path | stdout (json) / `.` (markdown) | Where to write the result |
74
74
  | `--quiet` | — | `false` | Omits the progress message on stderr |
75
+ | `--help`, `-h` | — | — | Prints usage information and exits |
76
+ | `--version`, `-v` | — | — | Prints the CLI's version and exits |
75
77
 
76
78
  ## Troubleshooting
77
79
 
78
80
  - **`Error: unsupported extension "<ext>"`**: with `--format markdown`, `--output` is always treated as a folder. With `--format json`, only a path ending in `.json` or with no extension (treated as a folder) is accepted; any other extension fails explicitly.
81
+ - **`--format` argument 'xml' is invalid. Allowed choices are json, markdown.**: an unsupported `--format` value is rejected explicitly instead of silently falling back to `json`.
82
+ - **`error: unknown option '--foo'`**: an unrecognized flag is rejected explicitly instead of being silently ignored.
79
83
  - **The process exits with code `1` but printed results**: at least one of the URLs failed — check the `URLs with errors:` block at the end of stderr for each one's code (`FigmaScraperErrorCode`) and message. See the error table in the [`@figtools/core` README](../core#possible-errors).
80
84
  - **`figtools login` doesn't progress**: the Chromium window waits indefinitely for you to finish the manual login; confirm you reached `https://www.figma.com/files/...` before closing the window.
81
85
 
package/dist/cli.d.ts CHANGED
@@ -7,15 +7,22 @@ export interface ParsedArgs {
7
7
  command?: "login";
8
8
  }
9
9
  type ParseArgsError = {
10
- code: "VALIDATION_NO_URLS" | "VALIDATION_UNSUPPORTED_EXTENSION";
10
+ code: "VALIDATION_NO_URLS" | "VALIDATION_UNSUPPORTED_EXTENSION" | "COMMANDER_ERROR";
11
11
  message: string;
12
12
  };
13
+ type ParseArgsInfo = {
14
+ code: "HELP_DISPLAYED" | "VERSION_DISPLAYED";
15
+ output: string;
16
+ };
13
17
  export type ParseArgsResult = {
14
18
  ok: true;
15
19
  value: ParsedArgs;
16
20
  } | {
17
21
  ok: false;
18
22
  error: ParseArgsError;
23
+ } | {
24
+ ok: false;
25
+ info: ParseArgsInfo;
19
26
  };
20
27
  export type OutputTarget = {
21
28
  kind: "stdout";
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,CAAC;AAE/C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,oBAAoB,GAAG,kCAAkC,CAAC;IAChE,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAC/B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,CAAC;AAEzC,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,uBAAuB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,eAAe,CA+BzD;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,MAAM,EAAE,YAAY,GACnB,YAAY,CAad"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,CAAC;AAE/C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,oBAAoB,GAAG,kCAAkC,GAAG,iBAAiB,CAAC;IACpF,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAMF,KAAK,aAAa,GAAG;IAAE,IAAI,EAAE,gBAAgB,GAAG,mBAAmB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtF,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAC/B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,GACpC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAEvC,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,uBAAuB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AA8DzD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,eAAe,CAmCzD;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,MAAM,EAAE,YAAY,GACnB,YAAY,CAad"}
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { extname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { Command, CommanderError, InvalidArgumentError } from "commander";
4
5
  import { CookieSessionStore, PlaywrightFigmaGateway, PlaywrightLogin, createFigmaScraperCore } from "@figtools/core";
5
6
  import { mkdir, writeFile } from "node:fs/promises";
6
7
  async function resolveAll(core, urls) {
@@ -54,8 +55,28 @@ function slugifyWithCollisions(names) {
54
55
  function isFigmaPage(result) {
55
56
  return "nodes" in result;
56
57
  }
58
+ function formatPaint(paint) {
59
+ const { r, g, b, a } = paint.color;
60
+ const color = `rgba(${r}, ${g}, ${b}, ${a})`;
61
+ return paint.styleName ? `${paint.styleName} (${color})` : color;
62
+ }
63
+ function metadataLines(node) {
64
+ const lines = [
65
+ `type: ${node.type}`
66
+ ];
67
+ if (null !== node.position.x && null !== node.position.y) lines.push(`position: x=${node.position.x}, y=${node.position.y}`);
68
+ if (null !== node.size.width && null !== node.size.height) lines.push(`size: width=${node.size.width}, height=${node.size.height}`);
69
+ const { typography, fills, strokes, ...rest } = node.styles;
70
+ for (const [key, value] of Object.entries(rest))if (void 0 !== value) lines.push(`${key}: ${value}`);
71
+ if (fills && fills.length > 0) lines.push(`fills: ${fills.map(formatPaint).join(", ")}`);
72
+ if (strokes && strokes.length > 0) lines.push(`strokes: ${strokes.map(formatPaint).join(", ")}`);
73
+ if (typography) {
74
+ for (const [key, value] of Object.entries(typography))if (null != value) lines.push(`typography.${key}: ${value}`);
75
+ }
76
+ return lines;
77
+ }
57
78
  function leafContent(node) {
58
- return `# ${node.name}\n\ntype: ${node.type}\n`;
79
+ return `# ${node.name}\n\n${metadataLines(node).join("\n")}\n`;
59
80
  }
60
81
  function containerContent(name, children, childSlugs) {
61
82
  const links = children.map((child, i)=>{
@@ -97,28 +118,89 @@ async function writeAsMarkdownTree(fileKey, result, options) {
97
118
  await writeFile(join(fileKeyDir, "index.md"), containerContent(result.name, result.children, childSlugs), "utf8");
98
119
  for(let i = 0; i < result.children.length; i++)await writeChildNode(result.children[i], fileKeyDir, childSlugs[i]);
99
120
  }
100
- function parseArgs(argv) {
101
- if ("login" === argv[0]) return {
102
- ok: true,
103
- value: {
121
+ var package_namespaceObject = /*#__PURE__*/ JSON.parse('{"rE":"0.2.1","h_":"Resolves one or more Figma URLs from the command line and writes the result as JSON or as a navigable markdown tree meant to be used as a data source by an LLM."}');
122
+ function createProgram() {
123
+ let output = "";
124
+ let parsed;
125
+ const program = new Command().name("figtools").description(package_namespaceObject.h_).version(package_namespaceObject.rE, "-v, --version").exitOverride().configureOutput({
126
+ writeOut: (str)=>{
127
+ output += str;
128
+ },
129
+ writeErr: (str)=>{
130
+ output += str;
131
+ }
132
+ });
133
+ const resolveCommand = program.command("resolve", {
134
+ isDefault: true
135
+ }).description("Resolve one or more Figma URLs and write the result").argument("<urls...>", "One or more Figma URLs to resolve").option("--format <format>", "Output format (json or markdown)", parseFormat, "json").option("--output <path>", "File or directory to write the result to").option("--quiet", "Suppress progress messages on stderr", false).action((urls, opts)=>{
136
+ parsed = {
137
+ urls,
138
+ format: opts.format,
139
+ outputPath: opts.output,
140
+ quiet: opts.quiet
141
+ };
142
+ });
143
+ resolveCommand.exitOverride();
144
+ const loginCommand = program.command("login").description("Force a new interactive login, discarding any saved session").action(()=>{
145
+ parsed = {
104
146
  command: "login",
105
147
  urls: [],
106
148
  format: "json",
107
149
  quiet: false
108
- }
150
+ };
151
+ });
152
+ loginCommand.exitOverride();
153
+ return {
154
+ program,
155
+ getParsed: ()=>parsed,
156
+ getOutput: ()=>output
109
157
  };
110
- let format = "json";
111
- let outputPath;
112
- let quiet = false;
113
- const urls = [];
114
- for(let i = 0; i < argv.length; i++){
115
- const arg = argv[i];
116
- if ("--format" === arg) format = argv[++i];
117
- else if ("--output" === arg) outputPath = argv[++i];
118
- else if ("--quiet" === arg) quiet = true;
119
- else if (!arg.startsWith("--")) urls.push(arg);
158
+ }
159
+ function parseFormat(value) {
160
+ if ("json" !== value && "markdown" !== value) throw new InvalidArgumentError("Allowed choices are json, markdown.");
161
+ return value;
162
+ }
163
+ function parseArgs(argv) {
164
+ const { program, getParsed, getOutput } = createProgram();
165
+ try {
166
+ program.parse(argv, {
167
+ from: "user"
168
+ });
169
+ } catch (err) {
170
+ if (err instanceof CommanderError) {
171
+ if ("commander.helpDisplayed" === err.code) return {
172
+ ok: false,
173
+ info: {
174
+ code: "HELP_DISPLAYED",
175
+ output: getOutput()
176
+ }
177
+ };
178
+ if ("commander.version" === err.code) return {
179
+ ok: false,
180
+ info: {
181
+ code: "VERSION_DISPLAYED",
182
+ output: getOutput()
183
+ }
184
+ };
185
+ if ("commander.missingArgument" === err.code) return {
186
+ ok: false,
187
+ error: {
188
+ code: "VALIDATION_NO_URLS",
189
+ message: "At least one Figma URL is required"
190
+ }
191
+ };
192
+ return {
193
+ ok: false,
194
+ error: {
195
+ code: "COMMANDER_ERROR",
196
+ message: getOutput() || err.message
197
+ }
198
+ };
199
+ }
200
+ throw err;
120
201
  }
121
- if (0 === urls.length) return {
202
+ const value = getParsed();
203
+ if (!value) return {
122
204
  ok: false,
123
205
  error: {
124
206
  code: "VALIDATION_NO_URLS",
@@ -127,12 +209,7 @@ function parseArgs(argv) {
127
209
  };
128
210
  return {
129
211
  ok: true,
130
- value: {
131
- urls,
132
- format,
133
- outputPath,
134
- quiet
135
- }
212
+ value
136
213
  };
137
214
  }
138
215
  function decideOutputTarget(outputPath, format) {
@@ -166,6 +243,10 @@ function extractFileKey(url) {
166
243
  }
167
244
  async function main(argv) {
168
245
  const parsed = parseArgs(argv);
246
+ if (!parsed.ok && "info" in parsed) {
247
+ process.stdout.write(parsed.info.output);
248
+ process.exit(0);
249
+ }
169
250
  if (!parsed.ok) {
170
251
  process.stderr.write(`Error: ${parsed.error.message}\n`);
171
252
  process.exit(1);
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-writer.d.ts","sourceRoot":"","sources":["../../src/output/markdown-writer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAwB,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAG9E,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;CACnB;AAoCD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAgCf"}
1
+ {"version":3,"file":"markdown-writer.d.ts","sourceRoot":"","sources":["../../src/output/markdown-writer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAoC,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAG1F,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;CACnB;AAyED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAgCf"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@figtools/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Resolves one or more Figma URLs from the command line and writes the result as JSON or as a navigable markdown tree meant to be used as a data source by an LLM.",
5
5
  "keywords": [
6
6
  "figma",
@@ -34,6 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
+ "commander": "^15.0.0",
37
38
  "playwright": "^1.61.1",
38
39
  "@figtools/core": "0.1.0"
39
40
  },