@opul/cli 0.1.2 → 0.1.3

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.js +42 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -243,6 +243,10 @@ function overlayInitSource() {
243
243
  const POS_KEY = "__opul_cursor_pos";
244
244
  const INK2 = "#0E0E0E";
245
245
  const PAPER = "#F4F4F2";
246
+ const DEFAULT_COLOR = "#C8F24E";
247
+ if (typeof config.color !== "string" || !/^#[0-9a-fA-F]{3,8}$|^(?:rgb|rgba|hsl|hsla)\([0-9.,%\s]+\)$|^[a-zA-Z]{1,20}$/.test(config.color.trim())) {
248
+ config = { color: DEFAULT_COLOR };
249
+ }
246
250
  function ensureFont() {
247
251
  if (document.getElementById("__opul_font"))
248
252
  return;
@@ -977,6 +981,18 @@ var ACTIONS = [
977
981
  ];
978
982
  var StepsValidationError = class extends Error {
979
983
  };
984
+ var SAFE_COLOR = /^#[0-9a-fA-F]{3,8}$|^(?:rgb|rgba|hsl|hsla)\(\s*[0-9.,%\s]+\)$|^[a-zA-Z]{1,20}$/;
985
+ function isSafeColor(c2) {
986
+ return SAFE_COLOR.test(c2.trim());
987
+ }
988
+ function isSafeUrl(u) {
989
+ try {
990
+ const p = new URL(u);
991
+ return p.protocol === "http:" || p.protocol === "https:";
992
+ } catch {
993
+ return false;
994
+ }
995
+ }
980
996
  function parseSteps(raw) {
981
997
  if (typeof raw !== "object" || raw === null) {
982
998
  throw new StepsValidationError("steps file must be a JSON object");
@@ -989,6 +1005,16 @@ function parseSteps(raw) {
989
1005
  throw new StepsValidationError('"steps" must be a non-empty array');
990
1006
  }
991
1007
  const steps = obj.steps.map((s, i) => validateStep(s, i));
1008
+ for (const key of ["url", "startUrl"]) {
1009
+ const v = obj[key];
1010
+ if (typeof v === "string" && v !== "" && !isSafeUrl(v)) {
1011
+ throw new StepsValidationError(`"${key}" must be an http(s) URL`);
1012
+ }
1013
+ }
1014
+ const cursorColor = obj.cursor?.color;
1015
+ if (typeof cursorColor === "string" && !isSafeColor(cursorColor)) {
1016
+ throw new StepsValidationError(`cursor.color "${cursorColor}" is not a valid CSS color`);
1017
+ }
992
1018
  const vp = obj.viewport;
993
1019
  const viewport = vp && typeof vp.width === "number" && typeof vp.height === "number" ? { width: vp.width, height: vp.height } : { width: 1440, height: 900 };
994
1020
  return {
@@ -1016,8 +1042,12 @@ function validateStep(s, i) {
1016
1042
  throw new StepsValidationError(`step ${i} (${a}): "${field}" is required`);
1017
1043
  }
1018
1044
  };
1019
- if (a === "goto")
1045
+ if (a === "goto") {
1020
1046
  need("url");
1047
+ if (typeof step.url === "string" && !isSafeUrl(step.url)) {
1048
+ throw new StepsValidationError(`step ${i} (goto): "url" must be http(s)`);
1049
+ }
1050
+ }
1021
1051
  if (a === "click" || a === "hover" || a === "wait")
1022
1052
  need("selector");
1023
1053
  if (a === "type") {
@@ -1242,7 +1272,7 @@ import { createInterface as createInterface2 } from "readline/promises";
1242
1272
  import { stdin, stdout } from "process";
1243
1273
 
1244
1274
  // src/config.ts
1245
- import { readFile as readFile2, writeFile as writeFile7, mkdir as mkdir3 } from "fs/promises";
1275
+ import { readFile as readFile2, writeFile as writeFile7, mkdir as mkdir3, chmod } from "fs/promises";
1246
1276
  import { homedir } from "os";
1247
1277
  import { join as join6 } from "path";
1248
1278
  var dir = join6(homedir(), ".opul");
@@ -1256,8 +1286,12 @@ async function loadConfig() {
1256
1286
  }
1257
1287
  }
1258
1288
  async function saveConfig(cfg) {
1259
- await mkdir3(dir, { recursive: true });
1289
+ await mkdir3(dir, { recursive: true, mode: 448 });
1290
+ await chmod(dir, 448).catch(() => {
1291
+ });
1260
1292
  await writeFile7(file, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
1293
+ await chmod(file, 384).catch(() => {
1294
+ });
1261
1295
  }
1262
1296
  async function requireToken() {
1263
1297
  const { token } = await loadConfig();
@@ -1321,15 +1355,15 @@ async function publishCommand(file2, opts) {
1321
1355
  }
1322
1356
  const videoPath = resolve3(process.cwd(), file2);
1323
1357
  const posterPath = opts.poster ? resolve3(process.cwd(), opts.poster) : join7(dirname(videoPath), "poster.jpg");
1324
- let video;
1358
+ let size;
1325
1359
  try {
1326
- video = await readFile3(videoPath);
1360
+ size = (await stat2(videoPath)).size;
1327
1361
  } catch {
1328
1362
  return fail3(`file not found: ${file2}`);
1329
1363
  }
1330
- const poster = await readFile3(posterPath).catch(() => null);
1331
- const size = (await stat2(videoPath)).size;
1332
1364
  if (size > 200 * 1024 * 1024) return fail3("file exceeds 200 MB upload cap.");
1365
+ const video = await readFile3(videoPath);
1366
+ const poster = await readFile3(posterPath).catch(() => null);
1333
1367
  const meta = await readFile3(join7(dirname(videoPath), "demo.json"), "utf8").then((s) => JSON.parse(s)).catch(() => ({}));
1334
1368
  const title = opts.title ?? meta.title ?? basename(videoPath).replace(/\.[^.]+$/, "");
1335
1369
  const auth = { authorization: `Bearer ${token}` };
@@ -1433,7 +1467,7 @@ function fail4(msg) {
1433
1467
 
1434
1468
  // src/index.ts
1435
1469
  var program = new Command();
1436
- program.name("opul").description("Record a product demo from your running web app.").version("0.1.2");
1470
+ program.name("opul").description("Record a product demo from your running web app.").version("0.1.3");
1437
1471
  program.command("init").description("Create an opul.steps.json stub").option("--out <file>", "output path", "opul.steps.json").action(initCommand);
1438
1472
  program.command("doctor").description("Check that Chrome and ffmpeg are installed").action(doctorCommand);
1439
1473
  program.command("record").description("Click through your app to generate a steps file").option("--url <url>", "app URL to open").option("--out <file>", "steps file to write", "opul.steps.json").option("--name <name>", "product/demo name").option("--chrome <path>", "path to Chrome executable").action(recordCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opul/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Record a polished product-demo video from your running web app — from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {