@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.
- package/dist/index.js +42 -8
- 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
|
|
1358
|
+
let size;
|
|
1325
1359
|
try {
|
|
1326
|
-
|
|
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.
|
|
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);
|