@opul/cli 0.1.0 → 0.1.2
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 +85 -26
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -280,7 +280,11 @@ function overlayInitSource() {
|
|
|
280
280
|
lower.style.cssText = "position:absolute;left:24px;bottom:24px;max-width:60%;background:" + INK2 + ";color:" + PAPER + ";font-size:16px;font-weight:600;line-height:1.2;letter-spacing:-0.025em;padding:8px 12px;border-radius:6px;opacity:0;transition:opacity 150ms ease;white-space:nowrap;";
|
|
281
281
|
root.appendChild(lower);
|
|
282
282
|
root.appendChild(cursor);
|
|
283
|
-
|
|
283
|
+
document.documentElement.appendChild(root);
|
|
284
|
+
}
|
|
285
|
+
function ensure() {
|
|
286
|
+
if (!root || !document.documentElement.contains(root))
|
|
287
|
+
build();
|
|
284
288
|
}
|
|
285
289
|
function ready() {
|
|
286
290
|
return new Promise((res) => {
|
|
@@ -298,6 +302,7 @@ function overlayInitSource() {
|
|
|
298
302
|
const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
|
|
299
303
|
function move2(x, y, dur) {
|
|
300
304
|
return new Promise((res) => {
|
|
305
|
+
ensure();
|
|
301
306
|
const sx = pos.x;
|
|
302
307
|
const sy = pos.y;
|
|
303
308
|
const dx = x - sx;
|
|
@@ -321,6 +326,7 @@ function overlayInitSource() {
|
|
|
321
326
|
}
|
|
322
327
|
function click() {
|
|
323
328
|
return new Promise((res) => {
|
|
329
|
+
ensure();
|
|
324
330
|
cursor.animate([
|
|
325
331
|
{ transform: "translate(" + pos.x + "px," + pos.y + "px) scale(1)" },
|
|
326
332
|
{
|
|
@@ -340,6 +346,7 @@ function overlayInitSource() {
|
|
|
340
346
|
}
|
|
341
347
|
function showLower(text) {
|
|
342
348
|
return new Promise((res) => {
|
|
349
|
+
ensure();
|
|
343
350
|
lower.textContent = text;
|
|
344
351
|
void lower.offsetWidth;
|
|
345
352
|
lower.style.opacity = "1";
|
|
@@ -348,6 +355,7 @@ function overlayInitSource() {
|
|
|
348
355
|
}
|
|
349
356
|
function hideLower2() {
|
|
350
357
|
return new Promise((res) => {
|
|
358
|
+
ensure();
|
|
351
359
|
lower.style.opacity = "0";
|
|
352
360
|
setTimeout(res, 150);
|
|
353
361
|
});
|
|
@@ -487,6 +495,7 @@ async function runStep(page, cast, step) {
|
|
|
487
495
|
}
|
|
488
496
|
case "hover": {
|
|
489
497
|
const c2 = await centerOf(page, step.selector, timeout);
|
|
498
|
+
await ensureReady(page);
|
|
490
499
|
if (step.name)
|
|
491
500
|
await moveOverlay(page, c2.x, c2.y, MOVE_MS, step.name);
|
|
492
501
|
else
|
|
@@ -497,6 +506,7 @@ async function runStep(page, cast, step) {
|
|
|
497
506
|
}
|
|
498
507
|
case "click": {
|
|
499
508
|
const c2 = await centerOf(page, step.selector, timeout);
|
|
509
|
+
await ensureReady(page);
|
|
500
510
|
await moveOverlay(page, c2.x, c2.y, MOVE_MS, step.name);
|
|
501
511
|
await sleep(PRE_CLICK_MS);
|
|
502
512
|
await overlayClick(page);
|
|
@@ -508,6 +518,7 @@ async function runStep(page, cast, step) {
|
|
|
508
518
|
}
|
|
509
519
|
case "type": {
|
|
510
520
|
const c2 = await centerOf(page, step.selector, timeout);
|
|
521
|
+
await ensureReady(page);
|
|
511
522
|
await moveOverlay(page, c2.x, c2.y, MOVE_MS, step.name);
|
|
512
523
|
await sleep(PRE_CLICK_MS);
|
|
513
524
|
await overlayClick(page);
|
|
@@ -889,29 +900,48 @@ function captureSource() {
|
|
|
889
900
|
if (w.__opulCap || !w.__opulEmit)
|
|
890
901
|
return;
|
|
891
902
|
w.__opulCap = true;
|
|
903
|
+
const uniq = (s) => {
|
|
904
|
+
try {
|
|
905
|
+
return document.querySelectorAll(s).length === 1;
|
|
906
|
+
} catch {
|
|
907
|
+
return false;
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
const q = (s) => s.replace(/'/g, "\\'");
|
|
892
911
|
const sel = (el) => {
|
|
893
912
|
if (!el || el.nodeType !== 1)
|
|
894
913
|
return null;
|
|
895
914
|
const dd = el.closest("[data-demo]");
|
|
896
915
|
if (dd)
|
|
897
916
|
return `[data-demo='${dd.getAttribute("data-demo")}']`;
|
|
898
|
-
if (el.id &&
|
|
917
|
+
if (el.id && uniq(`#${CSS.escape(el.id)}`))
|
|
899
918
|
return `#${CSS.escape(el.id)}`;
|
|
919
|
+
const al = el.getAttribute("aria-label");
|
|
920
|
+
if (al && uniq(`[aria-label='${q(al)}']`))
|
|
921
|
+
return `[aria-label='${q(al)}']`;
|
|
922
|
+
if (el.tagName === "A") {
|
|
923
|
+
const href = el.getAttribute("href");
|
|
924
|
+
if (href && href !== "#" && uniq(`a[href='${q(href)}']`))
|
|
925
|
+
return `a[href='${q(href)}']`;
|
|
926
|
+
}
|
|
900
927
|
const parts = [];
|
|
901
928
|
let node = el;
|
|
902
|
-
while (node && node.nodeType === 1 &&
|
|
929
|
+
while (node && node.nodeType === 1 && node.tagName !== "HTML") {
|
|
903
930
|
if (node.id) {
|
|
904
931
|
parts.unshift(`#${CSS.escape(node.id)}`);
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
part += `:nth-
|
|
932
|
+
if (uniq(parts.join(" > ")))
|
|
933
|
+
return parts.join(" > ");
|
|
934
|
+
} else {
|
|
935
|
+
const parent = node.parentElement;
|
|
936
|
+
let part = node.tagName.toLowerCase();
|
|
937
|
+
if (parent) {
|
|
938
|
+
const idx = Array.from(parent.children).indexOf(node) + 1;
|
|
939
|
+
part += `:nth-child(${idx})`;
|
|
940
|
+
}
|
|
941
|
+
parts.unshift(part);
|
|
942
|
+
if (uniq(parts.join(" > ")))
|
|
943
|
+
return parts.join(" > ");
|
|
913
944
|
}
|
|
914
|
-
parts.unshift(part);
|
|
915
945
|
node = node.parentElement;
|
|
916
946
|
}
|
|
917
947
|
return parts.join(" > ");
|
|
@@ -1091,23 +1121,52 @@ import { join as join5, resolve as resolve2, relative as relative2 } from "path"
|
|
|
1091
1121
|
async function demoCommand(opts) {
|
|
1092
1122
|
const stepsPath = resolve2(process.cwd(), opts.steps);
|
|
1093
1123
|
const outDir = resolve2(process.cwd(), opts.out);
|
|
1094
|
-
let raw;
|
|
1124
|
+
let raw = null;
|
|
1095
1125
|
try {
|
|
1096
1126
|
raw = await readFile(stepsPath, "utf8");
|
|
1097
1127
|
} catch {
|
|
1098
|
-
|
|
1099
|
-
`no steps file at ${ink.bold(opts.steps)}
|
|
1100
|
-
run ${ink.bold("opul init")} to create one.`
|
|
1101
|
-
);
|
|
1102
|
-
return;
|
|
1128
|
+
raw = null;
|
|
1103
1129
|
}
|
|
1104
1130
|
let steps;
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1131
|
+
if (raw === null) {
|
|
1132
|
+
if (!opts.url) {
|
|
1133
|
+
fail(
|
|
1134
|
+
`no steps file at ${ink.bold(opts.steps)} and no ${ink.bold("--url")}.
|
|
1135
|
+
run ${ink.bold("opul record --url <url>")} first, or ${ink.bold("opul init")}.`
|
|
1136
|
+
);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
console.error(
|
|
1140
|
+
`${ink.dim("no steps file \u2014 let's capture your click-through first.")}
|
|
1141
|
+
`
|
|
1142
|
+
);
|
|
1143
|
+
try {
|
|
1144
|
+
steps = await recordSteps({
|
|
1145
|
+
url: opts.url,
|
|
1146
|
+
outPath: stepsPath,
|
|
1147
|
+
chromePath: opts.chrome
|
|
1148
|
+
});
|
|
1149
|
+
} catch (e) {
|
|
1150
|
+
if (e instanceof ChromeNotFoundError) fail(e.message);
|
|
1151
|
+
else fail(e.message);
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
if (steps.steps.length === 0) {
|
|
1155
|
+
fail("no steps captured \u2014 click or type in the window before saving, then re-run.");
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
console.error(
|
|
1159
|
+
`${ink.green("\u2713")} captured ${steps.steps.length} step${steps.steps.length === 1 ? "" : "s"} \u2192 ${ink.bold(opts.steps)}. rendering the video\u2026
|
|
1160
|
+
`
|
|
1161
|
+
);
|
|
1162
|
+
} else {
|
|
1163
|
+
try {
|
|
1164
|
+
steps = parseSteps(JSON.parse(raw));
|
|
1165
|
+
} catch (e) {
|
|
1166
|
+
if (e instanceof StepsValidationError) fail(`invalid steps: ${e.message}`);
|
|
1167
|
+
else fail(`steps file is not valid JSON: ${e.message}`);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1111
1170
|
}
|
|
1112
1171
|
if (opts.url) steps.startUrl = opts.url;
|
|
1113
1172
|
if (opts.profile) {
|
|
@@ -1374,11 +1433,11 @@ function fail4(msg) {
|
|
|
1374
1433
|
|
|
1375
1434
|
// src/index.ts
|
|
1376
1435
|
var program = new Command();
|
|
1377
|
-
program.name("opul").description("Record a product demo from your running web app.").version("0.1.
|
|
1436
|
+
program.name("opul").description("Record a product demo from your running web app.").version("0.1.2");
|
|
1378
1437
|
program.command("init").description("Create an opul.steps.json stub").option("--out <file>", "output path", "opul.steps.json").action(initCommand);
|
|
1379
1438
|
program.command("doctor").description("Check that Chrome and ffmpeg are installed").action(doctorCommand);
|
|
1380
1439
|
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);
|
|
1381
|
-
program.command("demo").description("Record a demo MP4
|
|
1440
|
+
program.command("demo").description("Record a demo MP4 (captures a click-through first if no steps file)").option("--url <url>", "app URL to open first (overrides startUrl)").option("--steps <file>", "steps file", "opul.steps.json").option("--out <dir>", "output directory", "opul-out").option("--headed", "run Chrome headed with a 3s pre-roll").option("--height <px>", "output height (width derived to 16:9)").option("--profile <dir>", "use an existing Chrome profile (prints a warning)").option("--chrome <path>", "path to Chrome executable").action(demoCommand);
|
|
1382
1441
|
program.command("publish <file>").description("Publish an MP4 and get a shareable link").option("--title <title>", "demo title").option("--replace <slug>", "replace an existing demo, keeping its URL").option("--poster <file>", "poster image (defaults to ./poster.jpg)").action(publishCommand);
|
|
1383
1442
|
program.command("ls").description("List your published demos").action(lsCommand);
|
|
1384
1443
|
program.command("rm <slug>").description("Delete a published demo").action(rmCommand);
|