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,70 @@
|
|
|
1
|
+
import { cpSync, mkdirSync, writeFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join, dirname, resolve } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { buildEmbedFiles } from './embed.js';
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
export function scaffold(config, manifest, tempDir) {
|
|
8
|
+
const templateDir = join(__dirname, '..', '..', 'templates', 'site');
|
|
9
|
+
mkdirSync(tempDir, { recursive: true });
|
|
10
|
+
cpSync(templateDir, tempDir, { recursive: true });
|
|
11
|
+
const siteConfigData = {
|
|
12
|
+
title: config.title,
|
|
13
|
+
logo: config.logo,
|
|
14
|
+
primaryColor: config.primaryColor,
|
|
15
|
+
font: config.font,
|
|
16
|
+
baseUrl: config.baseUrl,
|
|
17
|
+
lang: config.lang,
|
|
18
|
+
stepsLayout: config.tutorials?.stepsLayout || 'text',
|
|
19
|
+
showStrip: config.tutorials?.showStrip !== false
|
|
20
|
+
};
|
|
21
|
+
if (config.logo && existsSync(config.logo)) {
|
|
22
|
+
const logoExt = config.logo.split('.').pop();
|
|
23
|
+
mkdirSync(join(tempDir, 'public'), { recursive: true });
|
|
24
|
+
cpSync(config.logo, join(tempDir, 'public', `logo.${logoExt}`));
|
|
25
|
+
siteConfigData.logo = `/logo.${logoExt}`;
|
|
26
|
+
}
|
|
27
|
+
mkdirSync(join(tempDir, 'src', 'data'), { recursive: true });
|
|
28
|
+
writeFileSync(join(tempDir, 'src', 'data', 'site-config.json'), JSON.stringify(siteConfigData, null, 2));
|
|
29
|
+
writeFileSync(join(tempDir, 'src', 'data', 'tutorials.json'), JSON.stringify(manifest, null, 2));
|
|
30
|
+
// In-app widget data: one JSON payload per tutorial + an index, served
|
|
31
|
+
// next to widget.js (template public/) so any app can embed tutorials.
|
|
32
|
+
const embedDir = join(tempDir, 'public', 'embed');
|
|
33
|
+
mkdirSync(embedDir, { recursive: true });
|
|
34
|
+
for (const file of buildEmbedFiles(manifest, config)) {
|
|
35
|
+
writeFileSync(join(tempDir, 'public', file.path), JSON.stringify(file.data, null, 2));
|
|
36
|
+
}
|
|
37
|
+
const videosDir = join(resolve(config.input), 'videos');
|
|
38
|
+
const publicVideos = join(tempDir, 'public', 'videos');
|
|
39
|
+
if (existsSync(videosDir)) {
|
|
40
|
+
mkdirSync(publicVideos, { recursive: true });
|
|
41
|
+
cpSync(videosDir, publicVideos, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
let siteDirective = '';
|
|
44
|
+
let baseDirective = '';
|
|
45
|
+
if (config.baseUrl && config.baseUrl.startsWith('http')) {
|
|
46
|
+
siteDirective = `site: ${JSON.stringify(config.baseUrl)},`;
|
|
47
|
+
try {
|
|
48
|
+
const basePath = new URL(config.baseUrl).pathname;
|
|
49
|
+
if (basePath && basePath !== '/') {
|
|
50
|
+
baseDirective = `base: ${JSON.stringify(basePath)},`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch { /* invalid URL, skip */ }
|
|
54
|
+
}
|
|
55
|
+
const astroConfig = `import { defineConfig } from 'astro/config';
|
|
56
|
+
|
|
57
|
+
export default defineConfig({
|
|
58
|
+
srcDir: './src',
|
|
59
|
+
publicDir: './public',
|
|
60
|
+
outDir: './dist',
|
|
61
|
+
${siteDirective}
|
|
62
|
+
${baseDirective}
|
|
63
|
+
build: { assets: 'assets' },
|
|
64
|
+
});
|
|
65
|
+
`;
|
|
66
|
+
writeFileSync(join(tempDir, 'astro.config.mjs'), astroConfig);
|
|
67
|
+
writeFileSync(join(tempDir, 'package.json'), JSON.stringify({ name: 'tutorial-site-build', type: 'module', private: true }, null, 2));
|
|
68
|
+
return tempDir;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/site/scaffold.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TimelineData } from '../timeline.js';
|
|
2
|
+
export interface ScannedTutorial {
|
|
3
|
+
timeline: TimelineData;
|
|
4
|
+
videoFile: string;
|
|
5
|
+
/** Screenshot filenames with their step number, sorted by step number */
|
|
6
|
+
stepScreenshots: {
|
|
7
|
+
n: number;
|
|
8
|
+
file: string;
|
|
9
|
+
}[];
|
|
10
|
+
}
|
|
11
|
+
export declare function scanTutorials(inputDir: string): ScannedTutorial[];
|
|
12
|
+
//# sourceMappingURL=scan-tutorials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan-tutorials.d.ts","sourceRoot":"","sources":["../../src/site/scan-tutorials.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,eAAe,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC/C;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,EAAE,CAqDjE"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
export function scanTutorials(inputDir) {
|
|
4
|
+
const outputDir = join(inputDir, 'output');
|
|
5
|
+
const videosDir = join(inputDir, 'videos');
|
|
6
|
+
const results = [];
|
|
7
|
+
if (!existsSync(videosDir))
|
|
8
|
+
return results;
|
|
9
|
+
const videoFiles = readdirSync(videosDir).filter((f) => f.endsWith('.webm'));
|
|
10
|
+
const allVideoDirFiles = readdirSync(videosDir);
|
|
11
|
+
// Index timelines by testName
|
|
12
|
+
const timelinesByName = new Map();
|
|
13
|
+
if (existsSync(outputDir)) {
|
|
14
|
+
for (const tf of readdirSync(outputDir).filter((f) => f.endsWith('_timeline.json'))) {
|
|
15
|
+
const timeline = JSON.parse(readFileSync(join(outputDir, tf), 'utf-8'));
|
|
16
|
+
timelinesByName.set(timeline.testName, timeline);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
for (const videoFileName of videoFiles) {
|
|
20
|
+
const baseName = videoFileName.replace(/\.webm$/, '');
|
|
21
|
+
const timeline = timelinesByName.get(baseName);
|
|
22
|
+
const stepPattern = new RegExp(`^${escapeRegex(baseName)}-step-(\\d+)\\.(png|jpe?g|webp|avif)$`);
|
|
23
|
+
const steps = [];
|
|
24
|
+
for (const file of allVideoDirFiles) {
|
|
25
|
+
const m = file.match(stepPattern);
|
|
26
|
+
if (m)
|
|
27
|
+
steps.push({ n: parseInt(m[1], 10), file });
|
|
28
|
+
}
|
|
29
|
+
const syntheticTimeline = timeline ?? {
|
|
30
|
+
testName: baseName,
|
|
31
|
+
testTitle: baseName,
|
|
32
|
+
testFile: '',
|
|
33
|
+
projectName: '',
|
|
34
|
+
lang: 'fr',
|
|
35
|
+
totalDurationMs: 0,
|
|
36
|
+
videoTrimMs: 0,
|
|
37
|
+
steps: [],
|
|
38
|
+
videoPath: '',
|
|
39
|
+
mergeCommand: '',
|
|
40
|
+
// No timeline to read the variant from — recognize the common suffixes.
|
|
41
|
+
variant: baseName.match(/-(mobile|tablet)$/)?.[1]
|
|
42
|
+
};
|
|
43
|
+
results.push({
|
|
44
|
+
timeline: syntheticTimeline,
|
|
45
|
+
videoFile: videoFileName,
|
|
46
|
+
stepScreenshots: steps.sort((a, b) => a.n - b.n)
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
function escapeRegex(s) {
|
|
52
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=scan-tutorials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan-tutorials.js","sourceRoot":"","sources":["../../src/site/scan-tutorials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export interface SiteConfig {
|
|
2
|
+
title: string;
|
|
3
|
+
logo?: string;
|
|
4
|
+
primaryColor: string;
|
|
5
|
+
font: string;
|
|
6
|
+
input: string;
|
|
7
|
+
output: string;
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
/** Single lang or comma-separated list, e.g. "fr,en,ar" */
|
|
10
|
+
lang: string;
|
|
11
|
+
tutorials?: {
|
|
12
|
+
categories?: Record<string, {
|
|
13
|
+
icon: string;
|
|
14
|
+
label: string;
|
|
15
|
+
}>;
|
|
16
|
+
ui?: {
|
|
17
|
+
heroTitle?: string;
|
|
18
|
+
heroSubtitle?: string;
|
|
19
|
+
};
|
|
20
|
+
/** Video page step guide layout:
|
|
21
|
+
* - 'text': text-only cards, click to reveal the screenshot (default)
|
|
22
|
+
* - 'cards': numbered text cards with a small screenshot, click to enlarge
|
|
23
|
+
* - 'full': full-width screenshot under each step's text
|
|
24
|
+
* - 'none': no step guide */
|
|
25
|
+
stepsLayout?: 'text' | 'cards' | 'full' | 'none';
|
|
26
|
+
/** Horizontal thumbnail strip under the video info (default true).
|
|
27
|
+
* Independent of stepsLayout; clicking a thumbnail opens the screenshot
|
|
28
|
+
* with its step's title + text. */
|
|
29
|
+
showStrip?: boolean;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** One entry of a video page's step-by-step guide, built from the timeline
|
|
33
|
+
* narration merged with the step screenshots. */
|
|
34
|
+
export interface ManifestStep {
|
|
35
|
+
/** Timeline step number; absent for mid-flow context narrations */
|
|
36
|
+
n?: number;
|
|
37
|
+
title?: string;
|
|
38
|
+
/** Narration text spoken during this step */
|
|
39
|
+
text?: string;
|
|
40
|
+
/** Screenshot filename relative to videos/ */
|
|
41
|
+
image?: string;
|
|
42
|
+
/** True for a context narration shown between steps (no number, no image) */
|
|
43
|
+
context?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface VideoManifestEntry {
|
|
46
|
+
id: string;
|
|
47
|
+
category: string;
|
|
48
|
+
title: string;
|
|
49
|
+
duration: string;
|
|
50
|
+
premium: boolean;
|
|
51
|
+
/** Filename relative to videos/ */
|
|
52
|
+
file: string;
|
|
53
|
+
feature?: string;
|
|
54
|
+
/** Recording variant (e.g. 'mobile'); absent = regular/desktop */
|
|
55
|
+
variant?: string;
|
|
56
|
+
uploadDate: string;
|
|
57
|
+
dateModified?: string;
|
|
58
|
+
steps: number;
|
|
59
|
+
/** Intro narration (leading context steps), shown under the video title */
|
|
60
|
+
description?: string;
|
|
61
|
+
/** Step-by-step guide rendered on the video page */
|
|
62
|
+
stepsDetail?: ManifestStep[];
|
|
63
|
+
}
|
|
64
|
+
export interface VideoManifest {
|
|
65
|
+
categories: Record<string, {
|
|
66
|
+
icon: string;
|
|
67
|
+
label: string;
|
|
68
|
+
}>;
|
|
69
|
+
videos: VideoManifestEntry[];
|
|
70
|
+
ui: {
|
|
71
|
+
heroTitle: string;
|
|
72
|
+
heroSubtitle: string;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/site/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE;QACX,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC7D,EAAE,CAAC,EAAE;YAAE,SAAS,CAAC,EAAE,MAAM,CAAC;YAAC,YAAY,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACnD;;;;uCAI+B;QAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;QACjD;;4CAEoC;QACpC,SAAS,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;CACF;AAED;kDACkD;AAClD,MAAM,WAAW,YAAY;IAC5B,mEAAmE;IACnE,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,WAAW,CAAC,EAAE,YAAY,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC7B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,EAAE,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAChD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/site/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The skill and agent are copied into a project's `.claude/` directory, so they
|
|
3
|
+
* are snapshots — a package upgrade cannot reach them. This stamp records which
|
|
4
|
+
* version produced each copy, which is what makes staleness detectable instead
|
|
5
|
+
* of silent.
|
|
6
|
+
*/
|
|
7
|
+
export interface SkillStamp {
|
|
8
|
+
skill?: string;
|
|
9
|
+
agent?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const STAMP_FILE = ".playwright-director.json";
|
|
12
|
+
export declare function stampPath(claudeDir: string): string;
|
|
13
|
+
export declare function readStamp(claudeDir: string): SkillStamp;
|
|
14
|
+
export declare function writeStamp(claudeDir: string, stamp: SkillStamp): void;
|
|
15
|
+
export declare function packageVersion(packageRoot: string): string;
|
|
16
|
+
/** What `init` would bring up to date, given what is on disk. */
|
|
17
|
+
export declare function outdated(claudeDir: string, version: string, installed: {
|
|
18
|
+
skill: boolean;
|
|
19
|
+
agent: boolean;
|
|
20
|
+
}): string[];
|
|
21
|
+
//# sourceMappingURL=skill-stamp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-stamp.d.ts","sourceRoot":"","sources":["../src/skill-stamp.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,UAAU,8BAA8B,CAAC;AAEtD,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAQvD;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAErE;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED,iEAAiE;AACjE,wBAAgB,QAAQ,CACvB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC3C,MAAM,EAAE,CAMV"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
export const STAMP_FILE = '.playwright-director.json';
|
|
4
|
+
export function stampPath(claudeDir) {
|
|
5
|
+
return join(claudeDir, STAMP_FILE);
|
|
6
|
+
}
|
|
7
|
+
export function readStamp(claudeDir) {
|
|
8
|
+
const path = stampPath(claudeDir);
|
|
9
|
+
if (!existsSync(path))
|
|
10
|
+
return {};
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function writeStamp(claudeDir, stamp) {
|
|
19
|
+
writeFileSync(stampPath(claudeDir), `${JSON.stringify(stamp, null, 2)}\n`);
|
|
20
|
+
}
|
|
21
|
+
export function packageVersion(packageRoot) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf-8')).version ?? '0.0.0';
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return '0.0.0';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** What `init` would bring up to date, given what is on disk. */
|
|
30
|
+
export function outdated(claudeDir, version, installed) {
|
|
31
|
+
const stamp = readStamp(claudeDir);
|
|
32
|
+
const stale = [];
|
|
33
|
+
if (installed.skill && stamp.skill !== version)
|
|
34
|
+
stale.push('skill');
|
|
35
|
+
if (installed.agent && stamp.agent !== version)
|
|
36
|
+
stale.push('agent');
|
|
37
|
+
return stale;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=skill-stamp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-stamp.js","sourceRoot":"","sources":["../src/skill-stamp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAa5B,MAAM,CAAC,MAAM,UAAU,GAAG,2BAA2B,CAAC;AAEtD,MAAM,UAAU,SAAS,CAAC,SAAiB;IAC1C,OAAO,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,SAAiB;IAC1C,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAe,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AACF,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,SAAiB,EAAE,KAAiB;IAC9D,aAAa,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,WAAmB;IACjD,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC;IAChG,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,OAAO,CAAC;IAChB,CAAC;AACF,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,QAAQ,CACvB,SAAiB,EACjB,OAAe,EACf,SAA6C;IAE7C,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,SAAS,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpE,IAAI,SAAS,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpE,OAAO,KAAK,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build an SEO-friendly filename slug from a human-readable string.
|
|
3
|
+
*
|
|
4
|
+
* - NFD-normalize then drop every Unicode combining mark — this strips Latin
|
|
5
|
+
* diacritics (é→e) AND the hamza marks that NFD splits off Arabic letters
|
|
6
|
+
* (إ → ا + U+0655 → ا). Without the Arabic pass, titles like
|
|
7
|
+
* "إنشاء أول فاتورة" would emit stray dashes where the marks were.
|
|
8
|
+
* - Lowercase (no-op on Arabic).
|
|
9
|
+
* - Any run of non-letter/non-number → single dash.
|
|
10
|
+
* - Leading / trailing dashes trimmed; consecutive dashes collapsed.
|
|
11
|
+
*
|
|
12
|
+
* Examples:
|
|
13
|
+
* "Créer votre première facture" → "creer-votre-premiere-facture"
|
|
14
|
+
* "Set Up Your Company" → "set-up-your-company"
|
|
15
|
+
* "إنشاء أول فاتورة" → "انشاء-اول-فاتورة"
|
|
16
|
+
* "S'abonner avec BaridPay" → "s-abonner-avec-baridpay"
|
|
17
|
+
*/
|
|
18
|
+
export declare function slugify(input: string): string;
|
|
19
|
+
//# sourceMappingURL=slugify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slugify.d.ts","sourceRoot":"","sources":["../src/slugify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS7C"}
|
package/dist/slugify.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build an SEO-friendly filename slug from a human-readable string.
|
|
3
|
+
*
|
|
4
|
+
* - NFD-normalize then drop every Unicode combining mark — this strips Latin
|
|
5
|
+
* diacritics (é→e) AND the hamza marks that NFD splits off Arabic letters
|
|
6
|
+
* (إ → ا + U+0655 → ا). Without the Arabic pass, titles like
|
|
7
|
+
* "إنشاء أول فاتورة" would emit stray dashes where the marks were.
|
|
8
|
+
* - Lowercase (no-op on Arabic).
|
|
9
|
+
* - Any run of non-letter/non-number → single dash.
|
|
10
|
+
* - Leading / trailing dashes trimmed; consecutive dashes collapsed.
|
|
11
|
+
*
|
|
12
|
+
* Examples:
|
|
13
|
+
* "Créer votre première facture" → "creer-votre-premiere-facture"
|
|
14
|
+
* "Set Up Your Company" → "set-up-your-company"
|
|
15
|
+
* "إنشاء أول فاتورة" → "انشاء-اول-فاتورة"
|
|
16
|
+
* "S'abonner avec BaridPay" → "s-abonner-avec-baridpay"
|
|
17
|
+
*/
|
|
18
|
+
export function slugify(input) {
|
|
19
|
+
if (!input)
|
|
20
|
+
return '';
|
|
21
|
+
return input
|
|
22
|
+
.normalize('NFD')
|
|
23
|
+
.replace(/\p{M}+/gu, '') // strip ALL combining marks (Latin + Arabic hamza marks)
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.replace(/[^\p{L}\p{N}]+/gu, '-') // any non-letter/number → dash
|
|
26
|
+
.replace(/-{2,}/g, '-')
|
|
27
|
+
.replace(/^-+|-+$/g, '');
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=slugify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slugify.js","sourceRoot":"","sources":["../src/slugify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACpC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,OAAO,KAAK;SACV,SAAS,CAAC,KAAK,CAAC;SAChB,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,yDAAyD;SACjF,WAAW,EAAE;SACb,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC,+BAA+B;SAChE,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;SACtB,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface MobileStageOptions {
|
|
2
|
+
/** Video oversampling factor (default 2). Playwright records video at *window*
|
|
3
|
+
* pixels and never upscales, so a phone-sized viewport yields a blurry ~400px-wide
|
|
4
|
+
* video. The scale is applied as `--force-device-scale-factor` + matching
|
|
5
|
+
* `deviceScaleFactor`, and the video size is multiplied accordingly — the page
|
|
6
|
+
* layout (CSS viewport) is unchanged. Set to 1 to record at native CSS pixels. */
|
|
7
|
+
scale?: number;
|
|
8
|
+
/** Extra Chromium args. `test.use()` REPLACES the config's `launchOptions`
|
|
9
|
+
* wholesale, so any arg your playwright.config sets for tutorial runs
|
|
10
|
+
* (e.g. `--autoplay-policy=no-user-gesture-required`) must be repeated here. */
|
|
11
|
+
launchArgs?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Playwright `test.use()` options for recording the mobile variant of a
|
|
15
|
+
* tutorial: N phone-sized panes side by side. Inert unless
|
|
16
|
+
* `TUTORIAL_VARIANT=mobile`, so the same spec records desktop and mobile.
|
|
17
|
+
*
|
|
18
|
+
* Must be passed to `test.use()` at the top of the spec — the video size is
|
|
19
|
+
* frozen when the browser context is created, a later `setViewportSize`
|
|
20
|
+
* would not resize the recording.
|
|
21
|
+
*
|
|
22
|
+
* The viewport is widened to N phones; in tutorial mode the video is recorded
|
|
23
|
+
* oversampled (see {@link MobileStageOptions.scale}) for a sharp result. Other
|
|
24
|
+
* device traits (userAgent, isMobile…) stay whatever the consuming
|
|
25
|
+
* playwright.config sets.
|
|
26
|
+
*
|
|
27
|
+
* @param scenes number of phones side by side (1 for a single-scene tutorial)
|
|
28
|
+
* @param device a Playwright device name (default 'Pixel 7') or an explicit viewport
|
|
29
|
+
* @param options scale / extra launch args, see {@link MobileStageOptions}
|
|
30
|
+
*/
|
|
31
|
+
export declare function mobileStage(scenes?: number, device?: string | {
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
}, options?: MobileStageOptions): {
|
|
35
|
+
viewport?: {
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
};
|
|
39
|
+
deviceScaleFactor?: number;
|
|
40
|
+
video?: {
|
|
41
|
+
mode: 'on';
|
|
42
|
+
size: {
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
launchOptions?: {
|
|
48
|
+
args: string[];
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=stage-presets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage-presets.d.ts","sourceRoot":"","sources":["../src/stage-presets.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IAClC;;;;uFAImF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;qFAEiF;IACjF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAC1B,MAAM,SAAI,EACV,MAAM,GAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAc,EAC9D,OAAO,GAAE,kBAAuB,GAC9B;IACF,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IAChE,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CACnC,CAwBA"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { devices } from '@playwright/test';
|
|
2
|
+
/**
|
|
3
|
+
* Playwright `test.use()` options for recording the mobile variant of a
|
|
4
|
+
* tutorial: N phone-sized panes side by side. Inert unless
|
|
5
|
+
* `TUTORIAL_VARIANT=mobile`, so the same spec records desktop and mobile.
|
|
6
|
+
*
|
|
7
|
+
* Must be passed to `test.use()` at the top of the spec — the video size is
|
|
8
|
+
* frozen when the browser context is created, a later `setViewportSize`
|
|
9
|
+
* would not resize the recording.
|
|
10
|
+
*
|
|
11
|
+
* The viewport is widened to N phones; in tutorial mode the video is recorded
|
|
12
|
+
* oversampled (see {@link MobileStageOptions.scale}) for a sharp result. Other
|
|
13
|
+
* device traits (userAgent, isMobile…) stay whatever the consuming
|
|
14
|
+
* playwright.config sets.
|
|
15
|
+
*
|
|
16
|
+
* @param scenes number of phones side by side (1 for a single-scene tutorial)
|
|
17
|
+
* @param device a Playwright device name (default 'Pixel 7') or an explicit viewport
|
|
18
|
+
* @param options scale / extra launch args, see {@link MobileStageOptions}
|
|
19
|
+
*/
|
|
20
|
+
export function mobileStage(scenes = 1, device = 'Pixel 7', options = {}) {
|
|
21
|
+
if (process.env.TUTORIAL_VARIANT !== 'mobile')
|
|
22
|
+
return {};
|
|
23
|
+
const base = typeof device === 'string' ? devices[device]?.viewport : device;
|
|
24
|
+
if (!base)
|
|
25
|
+
throw new Error(`[Tutorial] Unknown Playwright device "${device}"`);
|
|
26
|
+
const scale = options.scale ?? 2;
|
|
27
|
+
const viewport = { width: base.width * scenes, height: base.height };
|
|
28
|
+
if (process.env.TUTORIAL_MODE !== 'true')
|
|
29
|
+
return { viewport };
|
|
30
|
+
return {
|
|
31
|
+
viewport,
|
|
32
|
+
// The window must actually render at `scale`× — Playwright pastes frames onto
|
|
33
|
+
// the video canvas without upscaling (gray padding otherwise). The emulated
|
|
34
|
+
// deviceScaleFactor is aligned with the window's so screenshots match.
|
|
35
|
+
deviceScaleFactor: scale,
|
|
36
|
+
launchOptions: {
|
|
37
|
+
args: [`--force-device-scale-factor=${scale}`, ...(options.launchArgs ?? [])]
|
|
38
|
+
},
|
|
39
|
+
video: {
|
|
40
|
+
mode: 'on',
|
|
41
|
+
size: { width: viewport.width * scale, height: viewport.height * scale }
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=stage-presets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage-presets.js","sourceRoot":"","sources":["../src/stage-presets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAe3C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,WAAW,CAC1B,MAAM,GAAG,CAAC,EACV,SAAqD,SAAS,EAC9D,UAA8B,EAAE;IAOhC,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IAEzD,MAAM,IAAI,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;IAC7E,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,MAAM,GAAG,CAAC,CAAC;IAE/E,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;IACrE,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,MAAM;QAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IAE9D,OAAO;QACN,QAAQ;QACR,8EAA8E;QAC9E,4EAA4E;QAC5E,uEAAuE;QACvE,iBAAiB,EAAE,KAAK;QACxB,aAAa,EAAE;YACd,IAAI,EAAE,CAAC,+BAA+B,KAAK,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;SAC7E;QACD,KAAK,EAAE;YACN,IAAI,EAAE,IAAa;YACnB,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,GAAG,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,GAAG,KAAK,EAAE;SACxE;KACD,CAAC;AACH,CAAC"}
|