playwright-director 0.2.0
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.md +860 -0
- package/dist/Tutorial.d.ts +108 -0
- package/dist/Tutorial.d.ts.map +1 -0
- package/dist/Tutorial.js +767 -0
- package/dist/Tutorial.js.map +1 -0
- package/dist/bin/export-transcript.d.ts +3 -0
- package/dist/bin/export-transcript.d.ts.map +1 -0
- package/dist/bin/export-transcript.js +111 -0
- package/dist/bin/export-transcript.js.map +1 -0
- package/dist/cursor.d.ts +24 -0
- package/dist/cursor.d.ts.map +1 -0
- package/dist/cursor.js +96 -0
- package/dist/cursor.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +3 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +183 -0
- package/dist/init.js.map +1 -0
- package/dist/merge.d.ts +26 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/merge.js +65 -0
- package/dist/merge.js.map +1 -0
- package/dist/music.d.ts +29 -0
- package/dist/music.d.ts.map +1 -0
- package/dist/music.js +107 -0
- package/dist/music.js.map +1 -0
- package/dist/overlay-html.d.ts +44 -0
- package/dist/overlay-html.d.ts.map +1 -0
- package/dist/overlay-html.js +150 -0
- package/dist/overlay-html.js.map +1 -0
- package/dist/overlay.d.ts +39 -0
- package/dist/overlay.d.ts.map +1 -0
- package/dist/overlay.js +134 -0
- package/dist/overlay.js.map +1 -0
- package/dist/postinstall.d.ts +3 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +38 -0
- package/dist/postinstall.js.map +1 -0
- package/dist/reporter.d.ts +22 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +166 -0
- package/dist/reporter.js.map +1 -0
- package/dist/site/build-site.d.ts +2 -0
- package/dist/site/build-site.d.ts.map +1 -0
- package/dist/site/build-site.js +82 -0
- package/dist/site/build-site.js.map +1 -0
- package/dist/site/embed.d.ts +48 -0
- package/dist/site/embed.d.ts.map +1 -0
- package/dist/site/embed.js +45 -0
- package/dist/site/embed.js.map +1 -0
- package/dist/site/generate-manifest.d.ts +15 -0
- package/dist/site/generate-manifest.d.ts.map +1 -0
- package/dist/site/generate-manifest.js +106 -0
- package/dist/site/generate-manifest.js.map +1 -0
- package/dist/site/scaffold.d.ts +3 -0
- package/dist/site/scaffold.d.ts.map +1 -0
- package/dist/site/scaffold.js +70 -0
- package/dist/site/scaffold.js.map +1 -0
- package/dist/site/scan-tutorials.d.ts +12 -0
- package/dist/site/scan-tutorials.d.ts.map +1 -0
- package/dist/site/scan-tutorials.js +54 -0
- package/dist/site/scan-tutorials.js.map +1 -0
- package/dist/site/types.d.ts +75 -0
- package/dist/site/types.d.ts.map +1 -0
- package/dist/site/types.js +2 -0
- package/dist/site/types.js.map +1 -0
- package/dist/skill-stamp.d.ts +21 -0
- package/dist/skill-stamp.d.ts.map +1 -0
- package/dist/skill-stamp.js +39 -0
- package/dist/skill-stamp.js.map +1 -0
- package/dist/slugify.d.ts +19 -0
- package/dist/slugify.d.ts.map +1 -0
- package/dist/slugify.js +29 -0
- package/dist/slugify.js.map +1 -0
- package/dist/stage-presets.d.ts +51 -0
- package/dist/stage-presets.d.ts.map +1 -0
- package/dist/stage-presets.js +45 -0
- package/dist/stage-presets.js.map +1 -0
- package/dist/styles.css +637 -0
- package/dist/timeline.d.ts +87 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +108 -0
- package/dist/timeline.js.map +1 -0
- package/dist/transcript.d.ts +87 -0
- package/dist/transcript.d.ts.map +1 -0
- package/dist/transcript.js +291 -0
- package/dist/transcript.js.map +1 -0
- package/dist/tts-provider.d.ts +69 -0
- package/dist/tts-provider.d.ts.map +1 -0
- package/dist/tts-provider.js +182 -0
- package/dist/tts-provider.js.map +1 -0
- package/dist/types.d.ts +109 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/voice-prerender.d.ts +41 -0
- package/dist/voice-prerender.d.ts.map +1 -0
- package/dist/voice-prerender.js +122 -0
- package/dist/voice-prerender.js.map +1 -0
- package/dist/voice.d.ts +76 -0
- package/dist/voice.d.ts.map +1 -0
- package/dist/voice.js +244 -0
- package/dist/voice.js.map +1 -0
- package/package.json +87 -0
- package/skills/tutorialize/SKILL.md +54 -0
- package/skills/tutorialize/agent.md +58 -0
- package/skills/tutorialize/references/api.md +489 -0
- package/skills/tutorialize/references/storytelling.md +193 -0
- package/src/styles.css +637 -0
- package/templates/site/public/.gitkeep +0 -0
- package/templates/site/public/widget.js +273 -0
- package/templates/site/src/components/TutorialsHome.astro +141 -0
- package/templates/site/src/components/VideoCard.astro +192 -0
- package/templates/site/src/components/VideoModal.astro +170 -0
- package/templates/site/src/data/.gitkeep +0 -0
- package/templates/site/src/layouts/Base.astro +56 -0
- package/templates/site/src/pages/[slug].astro +745 -0
- package/templates/site/src/pages/index.astro +5 -0
- package/templates/site/src/styles/tutorials.css +303 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { outdated, packageVersion } from './skill-stamp.js';
|
|
6
|
+
/**
|
|
7
|
+
* Prints a one-line reminder when a project's copied skill/agent no longer
|
|
8
|
+
* matches the installed package. It never writes anything: a dependency has no
|
|
9
|
+
* business editing a project's `.claude/` directory unasked.
|
|
10
|
+
*
|
|
11
|
+
* Silent unless there is something to say, and it must never fail an install โ
|
|
12
|
+
* every path exits 0.
|
|
13
|
+
*/
|
|
14
|
+
try {
|
|
15
|
+
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
|
+
// npm sets INIT_CWD to the directory the install was launched from.
|
|
17
|
+
const projectRoot = process.env.INIT_CWD || process.cwd();
|
|
18
|
+
const claudeDir = join(projectRoot, '.claude');
|
|
19
|
+
const installed = {
|
|
20
|
+
skill: existsSync(join(claudeDir, 'skills', 'tutorialize')),
|
|
21
|
+
agent: existsSync(join(claudeDir, 'agents', 'tutorial-crafter.md'))
|
|
22
|
+
};
|
|
23
|
+
// Nothing installed means the project never opted in โ stay quiet.
|
|
24
|
+
if (installed.skill || installed.agent) {
|
|
25
|
+
const version = packageVersion(packageRoot);
|
|
26
|
+
const stale = outdated(claudeDir, version, installed);
|
|
27
|
+
if (stale.length > 0) {
|
|
28
|
+
const what = stale.join(' and ');
|
|
29
|
+
const verb = stale.length > 1 ? 'are' : 'is';
|
|
30
|
+
console.log(`\nplaywright-director ${version}: your ${what} in .claude/ ${verb} out of date.` +
|
|
31
|
+
`\n Run: npx playwright-director init\n`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// A reminder is never worth breaking an install over.
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=postinstall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postinstall.js","sourceRoot":"","sources":["../src/postinstall.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE5D;;;;;;;GAOG;AACH,IAAI,CAAC;IACJ,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACxE,oEAAoE;IACpE,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IAE/C,MAAM,SAAS,GAAG;QACjB,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;QAC3D,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAC;KACnE,CAAC;IAEF,mEAAmE;IACnE,IAAI,SAAS,CAAC,KAAK,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;QAEtD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACjC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;YAC7C,OAAO,CAAC,GAAG,CACV,yBAAyB,OAAO,UAAU,IAAI,gBAAgB,IAAI,eAAe;gBAChF,yCAAyC,CAC1C,CAAC;QACH,CAAC;IACF,CAAC;AACF,CAAC;AAAC,MAAM,CAAC;IACR,sDAAsD;AACvD,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Reporter, TestCase, TestResult } from '@playwright/test/reporter';
|
|
2
|
+
export interface TutorialReporterOptions {
|
|
3
|
+
mappingFile?: string;
|
|
4
|
+
tutorialsJson?: string;
|
|
5
|
+
}
|
|
6
|
+
declare class TutorialMergeReporter implements Reporter {
|
|
7
|
+
private mappingFile;
|
|
8
|
+
private tutorialsJson;
|
|
9
|
+
constructor(options?: TutorialReporterOptions);
|
|
10
|
+
onBegin(): void;
|
|
11
|
+
onTestEnd(test: TestCase, result: TestResult): void;
|
|
12
|
+
/**
|
|
13
|
+
* Find the full-screen black sync marker in the source video and return
|
|
14
|
+
* the timestamp (seconds) of its END โ the exact video time of timeline
|
|
15
|
+
* zero. Returns null when no black interval is found.
|
|
16
|
+
*/
|
|
17
|
+
private detectSyncMarker;
|
|
18
|
+
private patchTutorialsDuration;
|
|
19
|
+
private findTimelineByTitle;
|
|
20
|
+
}
|
|
21
|
+
export default TutorialMergeReporter;
|
|
22
|
+
//# sourceMappingURL=reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAOhF,MAAM,WAAW,uBAAuB;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,cAAM,qBAAsB,YAAW,QAAQ;IAC9C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAS;gBAElB,OAAO,GAAE,uBAA4B;IAKjD,OAAO,IAAI,IAAI;IAIf,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IA+DnD;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,sBAAsB;IA+C9B,OAAO,CAAC,mBAAmB;CAwB3B;AAED,eAAe,qBAAqB,CAAC"}
|
package/dist/reporter.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
const OUTPUT_DIR = process.env.TUTORIAL_OUTPUT_DIR || 'tutorials/output';
|
|
5
|
+
class TutorialMergeReporter {
|
|
6
|
+
mappingFile;
|
|
7
|
+
tutorialsJson;
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.mappingFile = options.mappingFile ?? process.env.TUTORIAL_MAPPING_FILE ?? 'tutorial-mapping.txt';
|
|
10
|
+
this.tutorialsJson = options.tutorialsJson ?? process.env.TUTORIAL_TUTORIALS_JSON ?? 'tutorials.json';
|
|
11
|
+
}
|
|
12
|
+
onBegin() {
|
|
13
|
+
console.log('[TutorialMerge] Reporter loaded โ will merge videos after each tutorial test');
|
|
14
|
+
}
|
|
15
|
+
onTestEnd(test, result) {
|
|
16
|
+
if (result.status !== 'passed')
|
|
17
|
+
return;
|
|
18
|
+
if (!test.tags.includes('@tutorial'))
|
|
19
|
+
return;
|
|
20
|
+
const timelinePath = this.findTimelineByTitle(test.title);
|
|
21
|
+
if (!timelinePath) {
|
|
22
|
+
console.log(`[TutorialMerge] No timeline for "${test.title}", skipping`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const timeline = JSON.parse(readFileSync(timelinePath, 'utf-8'));
|
|
27
|
+
const mergeCmd = timeline.mergeCommand;
|
|
28
|
+
if (!mergeCmd) {
|
|
29
|
+
console.log(`[TutorialMerge] No merge command for "${test.title}"`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const videoPath = mergeCmd.match(/-i "([^"]*\.webm)"/)?.[1];
|
|
33
|
+
if (videoPath) {
|
|
34
|
+
if (!existsSync(videoPath)) {
|
|
35
|
+
console.log(`[TutorialMerge] Waiting for video: ${videoPath}`);
|
|
36
|
+
for (let i = 0; i < 30; i++) {
|
|
37
|
+
execSync('sleep 1');
|
|
38
|
+
if (existsSync(videoPath))
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (existsSync(videoPath)) {
|
|
43
|
+
let prevSize = -1;
|
|
44
|
+
for (let i = 0; i < 15; i++) {
|
|
45
|
+
const { size } = statSync(videoPath);
|
|
46
|
+
if (size > 0 && size === prevSize)
|
|
47
|
+
break;
|
|
48
|
+
prevSize = size;
|
|
49
|
+
execSync('sleep 0.5');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
let finalCmd = mergeCmd;
|
|
54
|
+
if (timeline.syncMarker && videoPath && existsSync(videoPath)) {
|
|
55
|
+
const exactTrim = this.detectSyncMarker(videoPath);
|
|
56
|
+
if (exactTrim !== null) {
|
|
57
|
+
const declared = (timeline.videoTrimMs ?? 0) / 1000;
|
|
58
|
+
console.log(`[TutorialMerge] Sync marker at ${exactTrim.toFixed(3)}s (wall-clock trim was ${declared.toFixed(3)}s)`);
|
|
59
|
+
const ss = `-ss ${exactTrim.toFixed(3)}`;
|
|
60
|
+
finalCmd = /-ss [\d.]+/.test(finalCmd)
|
|
61
|
+
? finalCmd.replace(/-ss [\d.]+/, ss)
|
|
62
|
+
: finalCmd.replace('ffmpeg -y', `ffmpeg -y ${ss}`);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log('[TutorialMerge] Sync marker not found on tape โ using wall-clock trim');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
console.log(`[TutorialMerge] Merging: ${test.title}`);
|
|
69
|
+
execSync(finalCmd, { stdio: 'inherit' });
|
|
70
|
+
console.log(`[TutorialMerge] Done: ${test.title}`);
|
|
71
|
+
this.patchTutorialsDuration(timeline);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
console.error(`[TutorialMerge] Failed for "${test.title}": ${e.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Find the full-screen black sync marker in the source video and return
|
|
79
|
+
* the timestamp (seconds) of its END โ the exact video time of timeline
|
|
80
|
+
* zero. Returns null when no black interval is found.
|
|
81
|
+
*/
|
|
82
|
+
detectSyncMarker(videoPath) {
|
|
83
|
+
try {
|
|
84
|
+
const out = execSync(`ffmpeg -hide_banner -i "${videoPath}" -vf "blackdetect=d=0.2:pic_th=0.95" -an -f null - 2>&1`, { encoding: 'utf-8', maxBuffer: 32 * 1024 * 1024 });
|
|
85
|
+
const m = out.match(/black_end:([\d.]+)/);
|
|
86
|
+
return m ? parseFloat(m[1]) : null;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
patchTutorialsDuration(timeline) {
|
|
93
|
+
const { testName, lang, totalDurationMs } = timeline;
|
|
94
|
+
const mins = Math.floor(totalDurationMs / 60000);
|
|
95
|
+
const secs = Math.floor((totalDurationMs % 60000) / 1000);
|
|
96
|
+
const duration = `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
97
|
+
if (!existsSync(this.mappingFile)) {
|
|
98
|
+
console.log(`[TutorialMerge] ${this.mappingFile} not found, skipping duration patch`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const mappingLines = readFileSync(this.mappingFile, 'utf-8').split('\n');
|
|
102
|
+
const videoId = mappingLines
|
|
103
|
+
.filter(line => !line.startsWith('#') && line.trim())
|
|
104
|
+
.map(line => line.split(':'))
|
|
105
|
+
.find(([name]) => name === testName)?.[1];
|
|
106
|
+
if (!videoId) {
|
|
107
|
+
console.log(`[TutorialMerge] No mapping for "${testName}", add to ${this.mappingFile} + ${this.tutorialsJson}`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (!existsSync(this.tutorialsJson)) {
|
|
111
|
+
console.log(`[TutorialMerge] ${this.tutorialsJson} not found, skipping duration patch`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const tutorialsData = JSON.parse(readFileSync(this.tutorialsJson, 'utf-8'));
|
|
115
|
+
const video = (tutorialsData.videos ?? []).find((v) => v.id === videoId);
|
|
116
|
+
if (!video) {
|
|
117
|
+
console.log(`[TutorialMerge] "${videoId}" not in ${this.tutorialsJson}, add it manually`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (typeof video.duration === 'string') {
|
|
121
|
+
video.duration = { fr: video.duration };
|
|
122
|
+
}
|
|
123
|
+
video.duration[lang] = duration;
|
|
124
|
+
if (timeline.feature) {
|
|
125
|
+
video.feature = timeline.feature;
|
|
126
|
+
}
|
|
127
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
128
|
+
if (!video.uploadDate)
|
|
129
|
+
video.uploadDate = today;
|
|
130
|
+
video.dateModified = today;
|
|
131
|
+
writeFileSync(this.tutorialsJson, JSON.stringify(tutorialsData, null, 2) + '\n');
|
|
132
|
+
console.log(`[TutorialMerge] Duration[${lang}] ${duration} โ ${this.tutorialsJson} (${videoId})`);
|
|
133
|
+
}
|
|
134
|
+
findTimelineByTitle(testTitle) {
|
|
135
|
+
if (!existsSync(OUTPUT_DIR))
|
|
136
|
+
return null;
|
|
137
|
+
// The same test recorded in several variants leaves one timeline per
|
|
138
|
+
// variant with the same testTitle: keep the one matching this run's
|
|
139
|
+
// TUTORIAL_VARIANT (older JSONs without the field count as no-variant),
|
|
140
|
+
// and break remaining ties by most recent mtime.
|
|
141
|
+
const runVariant = process.env.TUTORIAL_VARIANT || undefined;
|
|
142
|
+
const candidates = [];
|
|
143
|
+
for (const entry of readdirSync(OUTPUT_DIR)) {
|
|
144
|
+
if (!entry.endsWith('_timeline.json'))
|
|
145
|
+
continue;
|
|
146
|
+
const path = join(OUTPUT_DIR, entry);
|
|
147
|
+
try {
|
|
148
|
+
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
149
|
+
if (data.testTitle !== testTitle)
|
|
150
|
+
continue;
|
|
151
|
+
if ((data.variant || undefined) !== runVariant)
|
|
152
|
+
continue;
|
|
153
|
+
candidates.push({ path, mtimeMs: statSync(path).mtimeMs });
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// ignore malformed
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (candidates.length === 0)
|
|
160
|
+
return null;
|
|
161
|
+
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
162
|
+
return candidates[0].path;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
export default TutorialMergeReporter;
|
|
166
|
+
//# sourceMappingURL=reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACpF,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,kBAAkB,CAAC;AAOzE,MAAM,qBAAqB;IAClB,WAAW,CAAS;IACpB,aAAa,CAAS;IAE9B,YAAY,UAAmC,EAAE;QAChD,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,sBAAsB,CAAC;QACtG,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,gBAAgB,CAAC;IACvG,CAAC;IAED,OAAO;QACN,OAAO,CAAC,GAAG,CAAC,8EAA8E,CAAC,CAAC;IAC7F,CAAC;IAED,SAAS,CAAC,IAAc,EAAE,MAAkB;QAC3C,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO;QACvC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,OAAO;QAE7C,MAAM,YAAY,GAAG,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,IAAI,CAAC,YAAY,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,oCAAoC,IAAI,CAAC,KAAK,aAAa,CAAC,CAAC;YACzE,OAAO;QACR,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;YACjE,MAAM,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAC;YACvC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACf,OAAO,CAAC,GAAG,CAAC,yCAAyC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;gBACpE,OAAO;YACR,CAAC;YAED,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC5D,IAAI,SAAS,EAAE,CAAC;gBACf,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC5B,OAAO,CAAC,GAAG,CAAC,sCAAsC,SAAS,EAAE,CAAC,CAAC;oBAC/D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;wBAC7B,QAAQ,CAAC,SAAS,CAAC,CAAC;wBACpB,IAAI,UAAU,CAAC,SAAS,CAAC;4BAAE,MAAM;oBAClC,CAAC;gBACF,CAAC;gBACD,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC3B,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC;oBAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;wBAC7B,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;wBACrC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,KAAK,QAAQ;4BAAE,MAAM;wBACzC,QAAQ,GAAG,IAAI,CAAC;wBAChB,QAAQ,CAAC,WAAW,CAAC,CAAC;oBACvB,CAAC;gBACF,CAAC;YACF,CAAC;YAED,IAAI,QAAQ,GAAG,QAAQ,CAAC;YACxB,IAAI,QAAQ,CAAC,UAAU,IAAI,SAAS,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC/D,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;gBACnD,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;oBACxB,MAAM,QAAQ,GAAG,CAAC,QAAQ,CAAC,WAAW,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;oBACpD,OAAO,CAAC,GAAG,CAAC,kCAAkC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,0BAA0B,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;oBACrH,MAAM,EAAE,GAAG,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;oBACzC,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;wBACrC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;wBACpC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;gBACrD,CAAC;qBAAM,CAAC;oBACP,OAAO,CAAC,GAAG,CAAC,uEAAuE,CAAC,CAAC;gBACtF,CAAC;YACF,CAAC;YAED,OAAO,CAAC,GAAG,CAAC,4BAA4B,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;YACtD,QAAQ,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;YAEnD,IAAI,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YACjB,OAAO,CAAC,KAAK,CAAC,+BAA+B,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3E,CAAC;IACF,CAAC;IAED;;;;OAIG;IACK,gBAAgB,CAAC,SAAiB;QACzC,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,QAAQ,CACnB,2BAA2B,SAAS,0DAA0D,EAC9F,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAClD,CAAC;YACF,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;YAC1C,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IAEO,sBAAsB,CAAC,QAAuF;QACrH,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,QAAQ,CAAC;QAErD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,CAAC;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,eAAe,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAC1D,MAAM,QAAQ,GAAG,GAAG,IAAI,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;QAE/D,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YACnC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,CAAC,WAAW,qCAAqC,CAAC,CAAC;YACtF,OAAO;QACR,CAAC;QACD,MAAM,YAAY,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,YAAY;aAC1B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;aACpD,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;aAC5B,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAE3C,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,OAAO,CAAC,GAAG,CAAC,mCAAmC,QAAQ,aAAa,IAAI,CAAC,WAAW,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;YAChH,OAAO;QACR,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YACrC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,CAAC,aAAa,qCAAqC,CAAC,CAAC;YACxF,OAAO;QACR,CAAC;QACD,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC;QAC5E,MAAM,KAAK,GAAG,CAAC,aAAa,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;QACzF,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,oBAAoB,OAAO,YAAY,IAAI,CAAC,aAAa,mBAAmB,CAAC,CAAC;YAC1F,OAAO;QACR,CAAC;QAED,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACxC,KAAK,CAAC,QAAQ,GAAG,EAAE,EAAE,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;QACzC,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC;QAChC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;YACtB,KAAK,CAAC,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;QAClC,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC,KAAK,CAAC,UAAU;YAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC;QAChD,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC;QAC3B,aAAa,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACjF,OAAO,CAAC,GAAG,CAAC,4BAA4B,IAAI,KAAK,QAAQ,MAAM,IAAI,CAAC,aAAa,KAAK,OAAO,GAAG,CAAC,CAAC;IACnG,CAAC;IAEO,mBAAmB,CAAC,SAAiB;QAC5C,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAC;QACzC,qEAAqE;QACrE,oEAAoE;QACpE,wEAAwE;QACxE,iDAAiD;QACjD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,SAAS,CAAC;QAC7D,MAAM,UAAU,GAAwC,EAAE,CAAC;QAC3D,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7C,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC;gBAAE,SAAS;YAChD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;YACrC,IAAI,CAAC;gBACJ,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;gBACrD,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;oBAAE,SAAS;gBAC3C,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,SAAS,CAAC,KAAK,UAAU;oBAAE,SAAS;gBACzD,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,CAAC;YAAC,MAAM,CAAC;gBACR,mBAAmB;YACpB,CAAC;QACF,CAAC;QACD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACzC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;QACjD,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3B,CAAC;CACD;AAED,eAAe,qBAAqB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-site.d.ts","sourceRoot":"","sources":["../../src/site/build-site.ts"],"names":[],"mappings":"AAiCA,wBAAsB,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM,iBAkElD"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { existsSync, writeFileSync, cpSync, rmSync } from 'fs';
|
|
2
|
+
import { join, resolve, dirname } from 'path';
|
|
3
|
+
import { pathToFileURL, fileURLToPath } from 'url';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { createInterface } from 'readline';
|
|
6
|
+
import { scanTutorials } from './scan-tutorials.js';
|
|
7
|
+
import { generateManifest } from './generate-manifest.js';
|
|
8
|
+
import { scaffold } from './scaffold.js';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
title: 'Tutorials',
|
|
13
|
+
primaryColor: '#6366f1',
|
|
14
|
+
font: 'system-ui, -apple-system, sans-serif',
|
|
15
|
+
input: 'tutorials/',
|
|
16
|
+
output: 'tutorial-site-dist/',
|
|
17
|
+
baseUrl: '/',
|
|
18
|
+
lang: 'fr'
|
|
19
|
+
};
|
|
20
|
+
function ask(question) {
|
|
21
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
22
|
+
return new Promise((res) => {
|
|
23
|
+
rl.question(question, (answer) => {
|
|
24
|
+
rl.close();
|
|
25
|
+
res(answer.trim().toLowerCase());
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export async function buildSite(configPath) {
|
|
30
|
+
const resolvedConfigPath = resolve(configPath || join(process.cwd(), 'tutorial-site.config.js'));
|
|
31
|
+
let config;
|
|
32
|
+
if (existsSync(resolvedConfigPath)) {
|
|
33
|
+
console.log(` Using config: ${resolvedConfigPath}`);
|
|
34
|
+
const imported = await import(pathToFileURL(resolvedConfigPath).href);
|
|
35
|
+
config = { ...DEFAULT_CONFIG, ...imported.default };
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
console.log(' No config file found. Generating tutorial-site.config.js with defaults...');
|
|
39
|
+
const configContent = `/** @type {import('playwright-director/site').SiteConfig} */
|
|
40
|
+
export default ${JSON.stringify(DEFAULT_CONFIG, null, 2)};
|
|
41
|
+
`;
|
|
42
|
+
writeFileSync(resolvedConfigPath, configContent);
|
|
43
|
+
console.log(` Created ${resolvedConfigPath}`);
|
|
44
|
+
const answer = await ask(' Continue with defaults? [Y/n] ');
|
|
45
|
+
if (answer && answer !== 'y' && answer !== 'yes') {
|
|
46
|
+
console.log(' Edit the config file, then re-run build-site.');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
config = { ...DEFAULT_CONFIG };
|
|
50
|
+
}
|
|
51
|
+
config.input = resolve(config.input);
|
|
52
|
+
config.output = resolve(config.output);
|
|
53
|
+
console.log(`\n Scanning ${config.input} for tutorials...`);
|
|
54
|
+
const tutorials = scanTutorials(config.input);
|
|
55
|
+
if (tutorials.length === 0) {
|
|
56
|
+
console.error(' No tutorials found. Make sure you have _timeline.json files in the output/ subdirectory and .webm files in the videos/ subdirectory.');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
console.log(` Found ${tutorials.length} tutorial(s).`);
|
|
60
|
+
const manifest = generateManifest(tutorials, config);
|
|
61
|
+
console.log(` Generated manifest: ${manifest.videos.length} videos, ${Object.keys(manifest.categories).length} categories.`);
|
|
62
|
+
const tempDir = join(config.output, '.astro-build');
|
|
63
|
+
console.log(' Scaffolding Astro project...');
|
|
64
|
+
scaffold(config, manifest, tempDir);
|
|
65
|
+
console.log(' Building static site...');
|
|
66
|
+
try {
|
|
67
|
+
execSync('npx astro build', {
|
|
68
|
+
cwd: tempDir,
|
|
69
|
+
stdio: 'inherit',
|
|
70
|
+
env: { ...process.env, NODE_ENV: 'production' }
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
console.error(' Astro build failed.');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
cpSync(join(tempDir, 'dist'), config.output, { recursive: true });
|
|
78
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
79
|
+
console.log(`\n โ Site built to ${config.output}`);
|
|
80
|
+
console.log(` Open ${join(config.output, 'index.html')} to preview.\n`);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=build-site.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-site.js","sourceRoot":"","sources":["../../src/site/build-site.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SiteConfig, VideoManifest, VideoManifestEntry } from './types.js';
|
|
2
|
+
/** One step of a widget payload. Display numbers are precomputed
|
|
3
|
+
* (sequential, contexts don't count) so the widget stays dumb. */
|
|
4
|
+
export interface EmbedStep {
|
|
5
|
+
num?: number;
|
|
6
|
+
title?: string;
|
|
7
|
+
text?: string;
|
|
8
|
+
/** Site-relative path, e.g. "videos/demo-step-1.webp" */
|
|
9
|
+
image?: string;
|
|
10
|
+
context?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Payload served at embed/<id>.json โ everything the in-app widget needs
|
|
13
|
+
* to render one tutorial. All paths are relative to the site base URL,
|
|
14
|
+
* which the widget derives from its own script src. */
|
|
15
|
+
export interface EmbedTutorial {
|
|
16
|
+
id: string;
|
|
17
|
+
title: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
duration: string;
|
|
20
|
+
variant?: string;
|
|
21
|
+
lang: string;
|
|
22
|
+
/** Site primary color โ the widget's default accent */
|
|
23
|
+
accent: string;
|
|
24
|
+
/** Site-relative video path ("videos/<file>") */
|
|
25
|
+
video: string;
|
|
26
|
+
/** First step screenshot, used as the video poster */
|
|
27
|
+
poster?: string;
|
|
28
|
+
/** Site-relative path of the full tutorial page ("<id>/") */
|
|
29
|
+
page: string;
|
|
30
|
+
steps: EmbedStep[];
|
|
31
|
+
}
|
|
32
|
+
export interface EmbedIndex {
|
|
33
|
+
tutorials: {
|
|
34
|
+
id: string;
|
|
35
|
+
title: string;
|
|
36
|
+
duration: string;
|
|
37
|
+
variant?: string;
|
|
38
|
+
}[];
|
|
39
|
+
}
|
|
40
|
+
export declare function buildEmbedTutorial(video: VideoManifestEntry, config: SiteConfig): EmbedTutorial;
|
|
41
|
+
/** All the JSON files the widget consumes: one payload per tutorial plus a
|
|
42
|
+
* small index (slug discovery / debugging). Paths are relative to the site
|
|
43
|
+
* root โ scaffold writes them under public/. */
|
|
44
|
+
export declare function buildEmbedFiles(manifest: VideoManifest, config: SiteConfig): {
|
|
45
|
+
path: string;
|
|
46
|
+
data: EmbedTutorial | EmbedIndex;
|
|
47
|
+
}[];
|
|
48
|
+
//# sourceMappingURL=embed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embed.d.ts","sourceRoot":"","sources":["../../src/site/embed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhF;mEACmE;AACnE,MAAM,WAAW,SAAS;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;wDAEwD;AACxD,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,SAAS,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IAC1B,SAAS,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC/E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,EAAE,MAAM,EAAE,UAAU,GAAG,aAAa,CA0B/F;AAED;;iDAEiD;AACjD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,aAAa,GAAG,UAAU,CAAA;CAAE,EAAE,CAiBjI"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export function buildEmbedTutorial(video, config) {
|
|
2
|
+
let counter = 0;
|
|
3
|
+
const steps = (video.stepsDetail ?? []).map((s) => ({
|
|
4
|
+
...(s.context ? {} : { num: ++counter }),
|
|
5
|
+
...(s.title ? { title: s.title } : {}),
|
|
6
|
+
...(s.text ? { text: s.text } : {}),
|
|
7
|
+
...(s.image ? { image: `videos/${s.image}` } : {}),
|
|
8
|
+
...(s.context ? { context: true } : {})
|
|
9
|
+
}));
|
|
10
|
+
const poster = (video.stepsDetail ?? []).find((s) => s.image)?.image;
|
|
11
|
+
return {
|
|
12
|
+
id: video.id,
|
|
13
|
+
title: video.title,
|
|
14
|
+
...(video.description ? { description: video.description } : {}),
|
|
15
|
+
duration: video.duration,
|
|
16
|
+
...(video.variant ? { variant: video.variant } : {}),
|
|
17
|
+
// config.lang may be a comma-separated list โ the first entry is the site default
|
|
18
|
+
lang: (config.lang || 'en').split(',')[0].trim(),
|
|
19
|
+
accent: config.primaryColor || '#6366f1',
|
|
20
|
+
video: `videos/${video.file}`,
|
|
21
|
+
...(poster ? { poster: `videos/${poster}` } : {}),
|
|
22
|
+
page: `${video.id}/`,
|
|
23
|
+
steps
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** All the JSON files the widget consumes: one payload per tutorial plus a
|
|
27
|
+
* small index (slug discovery / debugging). Paths are relative to the site
|
|
28
|
+
* root โ scaffold writes them under public/. */
|
|
29
|
+
export function buildEmbedFiles(manifest, config) {
|
|
30
|
+
const files = manifest.videos.map((v) => ({
|
|
31
|
+
path: `embed/${v.id}.json`,
|
|
32
|
+
data: buildEmbedTutorial(v, config)
|
|
33
|
+
}));
|
|
34
|
+
const index = {
|
|
35
|
+
tutorials: manifest.videos.map((v) => ({
|
|
36
|
+
id: v.id,
|
|
37
|
+
title: v.title,
|
|
38
|
+
duration: v.duration,
|
|
39
|
+
...(v.variant ? { variant: v.variant } : {})
|
|
40
|
+
}))
|
|
41
|
+
};
|
|
42
|
+
files.push({ path: 'embed/index.json', data: index });
|
|
43
|
+
return files;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=embed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embed.js","sourceRoot":"","sources":["../../src/site/embed.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ManifestStep, SiteConfig, VideoManifest } from './types.js';
|
|
2
|
+
import type { ScannedTutorial } from './scan-tutorials.js';
|
|
3
|
+
/**
|
|
4
|
+
* Merge a tutorial's timeline narration with its step screenshots into the
|
|
5
|
+
* step-by-step guide shown on the video page. Leading context narrations
|
|
6
|
+
* become the page description; mid-flow contexts stay in the list as
|
|
7
|
+
* unnumbered entries; screenshots of non-voiced steps (absent from the
|
|
8
|
+
* timeline) are inserted at their step position with no text.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildStepsDetail(t: ScannedTutorial): {
|
|
11
|
+
description?: string;
|
|
12
|
+
stepsDetail: ManifestStep[];
|
|
13
|
+
};
|
|
14
|
+
export declare function generateManifest(tutorials: ScannedTutorial[], config: SiteConfig): VideoManifest;
|
|
15
|
+
//# sourceMappingURL=generate-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-manifest.d.ts","sourceRoot":"","sources":["../../src/site/generate-manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAsB,MAAM,YAAY,CAAC;AAC9F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAiB3D;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,eAAe,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,YAAY,EAAE,CAAA;CAAE,CAiC1G;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,eAAe,EAAE,EAAE,MAAM,EAAE,UAAU,GAAG,aAAa,CAkDhG"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const DEFAULT_CATEGORY_ICONS = {
|
|
2
|
+
'getting-started': 'โญ',
|
|
3
|
+
premium: '๐',
|
|
4
|
+
portal: '๐ฅ',
|
|
5
|
+
accountant: '๐งพ',
|
|
6
|
+
payroll: '๐ฐ',
|
|
7
|
+
accounting: '๐',
|
|
8
|
+
settings: 'โ๏ธ'
|
|
9
|
+
};
|
|
10
|
+
function humanize(slug) {
|
|
11
|
+
const s = slug.replace(/[-_]/g, ' ').trim();
|
|
12
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Merge a tutorial's timeline narration with its step screenshots into the
|
|
16
|
+
* step-by-step guide shown on the video page. Leading context narrations
|
|
17
|
+
* become the page description; mid-flow contexts stay in the list as
|
|
18
|
+
* unnumbered entries; screenshots of non-voiced steps (absent from the
|
|
19
|
+
* timeline) are inserted at their step position with no text.
|
|
20
|
+
*/
|
|
21
|
+
export function buildStepsDetail(t) {
|
|
22
|
+
const shotByN = new Map(t.stepScreenshots.map((s) => [s.n, s.file]));
|
|
23
|
+
const usedShots = new Set();
|
|
24
|
+
const stepsDetail = [];
|
|
25
|
+
const leadingContext = [];
|
|
26
|
+
let seenStep = false;
|
|
27
|
+
for (const s of t.timeline.steps) {
|
|
28
|
+
if (s.step === 0 && s.title === 'Context') {
|
|
29
|
+
if (!s.text)
|
|
30
|
+
continue;
|
|
31
|
+
if (seenStep)
|
|
32
|
+
stepsDetail.push({ text: s.text, context: true });
|
|
33
|
+
else
|
|
34
|
+
leadingContext.push(s.text);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (s.title === 'Complete')
|
|
38
|
+
continue;
|
|
39
|
+
seenStep = true;
|
|
40
|
+
const image = shotByN.get(s.step);
|
|
41
|
+
if (image !== undefined)
|
|
42
|
+
usedShots.add(s.step);
|
|
43
|
+
stepsDetail.push({ n: s.step, title: s.title, ...(s.text ? { text: s.text } : {}), ...(image ? { image } : {}) });
|
|
44
|
+
}
|
|
45
|
+
for (const shot of t.stepScreenshots) {
|
|
46
|
+
if (usedShots.has(shot.n))
|
|
47
|
+
continue;
|
|
48
|
+
const at = stepsDetail.findIndex((d) => d.n !== undefined && d.n > shot.n);
|
|
49
|
+
const entry = { n: shot.n, image: shot.file };
|
|
50
|
+
if (at === -1)
|
|
51
|
+
stepsDetail.push(entry);
|
|
52
|
+
else
|
|
53
|
+
stepsDetail.splice(at, 0, entry);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
description: leadingContext.length > 0 ? leadingContext.join(' ') : undefined,
|
|
57
|
+
stepsDetail
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function generateManifest(tutorials, config) {
|
|
61
|
+
const detectedCategories = new Set();
|
|
62
|
+
for (const t of tutorials) {
|
|
63
|
+
detectedCategories.add(t.timeline.feature || 'general');
|
|
64
|
+
}
|
|
65
|
+
const categories = {};
|
|
66
|
+
for (const cat of detectedCategories) {
|
|
67
|
+
categories[cat] = {
|
|
68
|
+
icon: DEFAULT_CATEGORY_ICONS[cat] || '๐น',
|
|
69
|
+
label: cat.charAt(0).toUpperCase() + cat.slice(1).replace(/-/g, ' ')
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (config.tutorials?.categories) {
|
|
73
|
+
Object.assign(categories, config.tutorials.categories);
|
|
74
|
+
}
|
|
75
|
+
const videos = tutorials.map((t) => {
|
|
76
|
+
const durationSec = Math.round(t.timeline.totalDurationMs / 1000);
|
|
77
|
+
const minutes = Math.floor(durationSec / 60);
|
|
78
|
+
const seconds = durationSec % 60;
|
|
79
|
+
const { description, stepsDetail } = buildStepsDetail(t);
|
|
80
|
+
return {
|
|
81
|
+
id: t.timeline.testName,
|
|
82
|
+
category: t.timeline.feature || 'general',
|
|
83
|
+
// Older timelines don't carry `title` (the Tutorial display title) โ
|
|
84
|
+
// fall back to the Playwright test title, then the slug.
|
|
85
|
+
title: t.timeline.title || t.timeline.testTitle || humanize(t.timeline.testName),
|
|
86
|
+
duration: durationSec > 0 ? `${minutes}:${seconds.toString().padStart(2, '0')}` : '',
|
|
87
|
+
premium: false,
|
|
88
|
+
file: t.videoFile,
|
|
89
|
+
feature: t.timeline.feature,
|
|
90
|
+
variant: t.timeline.variant,
|
|
91
|
+
uploadDate: new Date().toISOString().slice(0, 10),
|
|
92
|
+
steps: t.stepScreenshots.length,
|
|
93
|
+
description,
|
|
94
|
+
stepsDetail: stepsDetail.length > 0 ? stepsDetail : undefined
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
categories,
|
|
99
|
+
videos,
|
|
100
|
+
ui: {
|
|
101
|
+
heroTitle: config.tutorials?.ui?.heroTitle || config.title,
|
|
102
|
+
heroSubtitle: config.tutorials?.ui?.heroSubtitle || 'Video tutorials'
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=generate-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-manifest.js","sourceRoot":"","sources":["../../src/site/generate-manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/site/scaffold.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAK5D,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAmF7F"}
|