@pantoken/cli 0.1.8 → 0.1.10

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.
Files changed (2) hide show
  1. package/dist/index.mjs +62 -27
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { mkdirSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
2
+ import { dirname, join, relative, resolve } from "node:path";
3
3
  import { generateAndroid } from "@pantoken/android";
4
4
  import { generateCompose } from "@pantoken/compose";
5
5
  import { generateFlutter } from "@pantoken/flutter";
@@ -44,6 +44,51 @@ const SUPPORTED = /* @__PURE__ */ new Set([
44
44
  "mintlify"
45
45
  ]);
46
46
  const PLANNED = /* @__PURE__ */ new Set();
47
+ const VALID_THEMES = /* @__PURE__ */ new Set([
48
+ "rebrand",
49
+ "canvas",
50
+ "canvasHighContrast"
51
+ ]);
52
+ const KNOWN_FLAGS = /* @__PURE__ */ new Set([
53
+ "out",
54
+ "theme",
55
+ "class",
56
+ "icons",
57
+ "format",
58
+ "no-scope",
59
+ "no-important",
60
+ "no-prune"
61
+ ]);
62
+ const VALID_CLASS_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
63
+ const RUST_FORMATS = /* @__PURE__ */ new Set(["egui", "iced"]);
64
+ /** Write an array of path+content assets into `outDir`, creating intermediate directories. */
65
+ function writeAssets(outDir, assets) {
66
+ for (const asset of assets) {
67
+ const file = join(outDir, asset.path);
68
+ mkdirSync(dirname(file), { recursive: true });
69
+ writeFileSync(file, asset.content);
70
+ console.log(`✓ pantoken: wrote ${file}`);
71
+ }
72
+ }
73
+ /** Parse argv into positionals and a validated flags record; throw on unknown flags. */
74
+ function parseFlags(argv) {
75
+ const positionals = [];
76
+ const flags = {};
77
+ for (let i = 0; i < argv.length; i++) {
78
+ const arg = argv[i];
79
+ if (arg.startsWith("--")) {
80
+ const key = arg.slice(2);
81
+ const next = argv[i + 1];
82
+ if (!KNOWN_FLAGS.has(key)) throw new Error(`Unknown flag "--${key}". Run pantoken generate --help for usage.`);
83
+ if (next === void 0 || next.startsWith("--")) flags[key] = "true";
84
+ else flags[key] = argv[++i];
85
+ } else positionals.push(arg);
86
+ }
87
+ return {
88
+ positionals,
89
+ flags
90
+ };
91
+ }
47
92
  /**
48
93
  * Parse `generate <target> [--out dir] [--theme t] [--class Name]`.
49
94
  *
@@ -66,22 +111,17 @@ const PLANNED = /* @__PURE__ */ new Set();
66
111
  * ```
67
112
  */
68
113
  function parseArgs(argv) {
69
- const positionals = [];
70
- const flags = {};
71
- for (let i = 0; i < argv.length; i++) {
72
- const arg = argv[i];
73
- if (arg.startsWith("--")) {
74
- const next = argv[i + 1];
75
- if (next === void 0 || next.startsWith("--")) flags[arg.slice(2)] = "true";
76
- else flags[arg.slice(2)] = argv[++i];
77
- } else positionals.push(arg);
78
- }
114
+ const { positionals, flags } = parseFlags(argv);
115
+ const theme = flags.theme ?? "rebrand";
116
+ if (!VALID_THEMES.has(theme)) throw new Error(`Unknown theme "${theme}". Valid themes: ${[...VALID_THEMES].join(", ")}.`);
117
+ const className = flags.class ?? "PanTokens";
118
+ if (!VALID_CLASS_RE.test(className)) throw new Error(`Invalid class name "${className}". Must be a valid identifier.`);
79
119
  return {
80
120
  command: positionals[0] ?? "",
81
121
  target: positionals[1] ?? "",
82
122
  out: flags.out ?? "./pantoken-out",
83
- theme: flags.theme ?? "rebrand",
84
- className: flags.class ?? "PanTokens",
123
+ theme,
124
+ className,
85
125
  icons: flags.icons ? flags.icons.split(",").filter(Boolean) : void 0,
86
126
  format: flags.format,
87
127
  noScope: "no-scope" in flags,
@@ -171,16 +211,12 @@ function runMintlify(args) {
171
211
  }
172
212
  /** Write the Drupal theme assets. */
173
213
  function runDrupal(args) {
174
- for (const asset of toDrupalTheme()) {
175
- const file = join(args.out, asset.path);
176
- mkdirSync(dirname(file), { recursive: true });
177
- writeFileSync(file, asset.content);
178
- console.log(`✓ pantoken: wrote ${file}`);
179
- }
214
+ writeAssets(args.out, toDrupalTheme());
180
215
  }
181
216
  /** Write the Rust token source for the egui or iced format. */
182
217
  function runRust(args) {
183
- const format = args.format === "iced" ? "iced" : "egui";
218
+ const format = args.format ?? "egui";
219
+ if (!RUST_FORMATS.has(format)) throw new Error(`Unknown Rust format "${format}". Use egui or iced.`);
184
220
  const file = args.out.includes(".") ? args.out : join(args.out, "tokens.rs");
185
221
  mkdirSync(dirname(file), { recursive: true });
186
222
  writeFileSync(file, generateRust({
@@ -228,13 +264,7 @@ async function runIconFont(args) {
228
264
  }
229
265
  /** Write the Jekyll or Hugo static-site assets (same asset shape, different source). */
230
266
  function runStaticSite(args) {
231
- const assets = args.target === "jekyll" ? toJekyllAssets() : toHugoAssets();
232
- for (const asset of assets) {
233
- const file = join(args.out, asset.path);
234
- mkdirSync(dirname(file), { recursive: true });
235
- writeFileSync(file, asset.content);
236
- console.log(`✓ pantoken: wrote ${file}`);
237
- }
267
+ writeAssets(args.out, args.target === "jekyll" ? toJekyllAssets() : toHugoAssets());
238
268
  }
239
269
  /** Write the Pendo `global.css`, honouring the `--no-scope`/`--no-important`/`--no-prune` flags. */
240
270
  function runPendo(args) {
@@ -248,6 +278,10 @@ function runPendo(args) {
248
278
  }));
249
279
  console.log(`✓ pantoken: wrote ${file}`);
250
280
  }
281
+ /** Warn when the resolved output path escapes cwd — guards against accidental ../traversal. */
282
+ function warnIfUnsafePath(out) {
283
+ if (relative(process.cwd(), resolve(out)).startsWith("..")) console.warn(`⚠️ pantoken: output path "${out}" escapes the current directory.`);
284
+ }
251
285
  /**
252
286
  * Run the CLI.
253
287
  *
@@ -269,6 +303,7 @@ function runPendo(args) {
269
303
  async function run(argv) {
270
304
  const args = parseArgs(argv);
271
305
  assertGenerateTarget(args);
306
+ warnIfUnsafePath(args.out);
272
307
  if (args.target === "swift") return runSwift(args);
273
308
  if (args.target === "android") return runAndroid(args);
274
309
  if (args.target === "compose") return runCompose(args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pantoken/cli",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "pantoken generate <target> — emit native design-token source (Swift now; Kotlin/Dart/Drupal to follow).",
5
5
  "homepage": "https://pantoken.iywahl.com",
6
6
  "bugs": "https://github.com/thedannywahl/pantoken/issues",