@showrun/core 0.1.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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/dist/__tests__/dsl-validation.test.d.ts +2 -0
  3. package/dist/__tests__/dsl-validation.test.d.ts.map +1 -0
  4. package/dist/__tests__/dsl-validation.test.js +203 -0
  5. package/dist/__tests__/pack-versioning.test.d.ts +2 -0
  6. package/dist/__tests__/pack-versioning.test.d.ts.map +1 -0
  7. package/dist/__tests__/pack-versioning.test.js +165 -0
  8. package/dist/__tests__/validator.test.d.ts +2 -0
  9. package/dist/__tests__/validator.test.d.ts.map +1 -0
  10. package/dist/__tests__/validator.test.js +149 -0
  11. package/dist/authResilience.d.ts +146 -0
  12. package/dist/authResilience.d.ts.map +1 -0
  13. package/dist/authResilience.js +378 -0
  14. package/dist/browserLauncher.d.ts +74 -0
  15. package/dist/browserLauncher.d.ts.map +1 -0
  16. package/dist/browserLauncher.js +159 -0
  17. package/dist/browserPersistence.d.ts +49 -0
  18. package/dist/browserPersistence.d.ts.map +1 -0
  19. package/dist/browserPersistence.js +143 -0
  20. package/dist/context.d.ts +10 -0
  21. package/dist/context.d.ts.map +1 -0
  22. package/dist/context.js +30 -0
  23. package/dist/dsl/builders.d.ts +340 -0
  24. package/dist/dsl/builders.d.ts.map +1 -0
  25. package/dist/dsl/builders.js +416 -0
  26. package/dist/dsl/conditions.d.ts +33 -0
  27. package/dist/dsl/conditions.d.ts.map +1 -0
  28. package/dist/dsl/conditions.js +169 -0
  29. package/dist/dsl/interpreter.d.ts +24 -0
  30. package/dist/dsl/interpreter.d.ts.map +1 -0
  31. package/dist/dsl/interpreter.js +491 -0
  32. package/dist/dsl/stepHandlers.d.ts +32 -0
  33. package/dist/dsl/stepHandlers.d.ts.map +1 -0
  34. package/dist/dsl/stepHandlers.js +787 -0
  35. package/dist/dsl/target.d.ts +28 -0
  36. package/dist/dsl/target.d.ts.map +1 -0
  37. package/dist/dsl/target.js +110 -0
  38. package/dist/dsl/templating.d.ts +21 -0
  39. package/dist/dsl/templating.d.ts.map +1 -0
  40. package/dist/dsl/templating.js +73 -0
  41. package/dist/dsl/types.d.ts +695 -0
  42. package/dist/dsl/types.d.ts.map +1 -0
  43. package/dist/dsl/types.js +7 -0
  44. package/dist/dsl/validation.d.ts +15 -0
  45. package/dist/dsl/validation.d.ts.map +1 -0
  46. package/dist/dsl/validation.js +974 -0
  47. package/dist/index.d.ts +20 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +20 -0
  50. package/dist/jsonPackValidator.d.ts +11 -0
  51. package/dist/jsonPackValidator.d.ts.map +1 -0
  52. package/dist/jsonPackValidator.js +61 -0
  53. package/dist/loader.d.ts +35 -0
  54. package/dist/loader.d.ts.map +1 -0
  55. package/dist/loader.js +107 -0
  56. package/dist/networkCapture.d.ts +107 -0
  57. package/dist/networkCapture.d.ts.map +1 -0
  58. package/dist/networkCapture.js +390 -0
  59. package/dist/packUtils.d.ts +36 -0
  60. package/dist/packUtils.d.ts.map +1 -0
  61. package/dist/packUtils.js +97 -0
  62. package/dist/packVersioning.d.ts +25 -0
  63. package/dist/packVersioning.d.ts.map +1 -0
  64. package/dist/packVersioning.js +137 -0
  65. package/dist/runner.d.ts +62 -0
  66. package/dist/runner.d.ts.map +1 -0
  67. package/dist/runner.js +170 -0
  68. package/dist/types.d.ts +336 -0
  69. package/dist/types.d.ts.map +1 -0
  70. package/dist/types.js +1 -0
  71. package/dist/validator.d.ts +20 -0
  72. package/dist/validator.d.ts.map +1 -0
  73. package/dist/validator.js +68 -0
  74. package/package.json +49 -0
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Unified browser launcher
3
+ *
4
+ * Provides a unified interface for launching browsers with different engines
5
+ * (Chromium, Camoufox) and persistence modes.
6
+ */
7
+ import { chromium } from 'playwright';
8
+ import { resolveBrowserDataDir } from './browserPersistence.js';
9
+ /**
10
+ * Launches a browser with the specified configuration
11
+ *
12
+ * @param config - Browser launch configuration
13
+ * @returns Browser session with context, page, and close function
14
+ */
15
+ export async function launchBrowser(config) {
16
+ const { browserSettings = {}, headless = true, sessionId, packPath, } = config;
17
+ const engine = browserSettings.engine ?? 'camoufox';
18
+ const persistence = browserSettings.persistence ?? 'none';
19
+ // Resolve user data directory based on persistence mode
20
+ const userDataDir = resolveBrowserDataDir({
21
+ persistence,
22
+ sessionId,
23
+ packPath,
24
+ });
25
+ if (engine === 'camoufox') {
26
+ return launchCamoufox({
27
+ headless,
28
+ userDataDir,
29
+ persistence,
30
+ });
31
+ }
32
+ // Default: Chromium
33
+ return launchChromium({
34
+ headless,
35
+ userDataDir,
36
+ persistence,
37
+ });
38
+ }
39
+ /**
40
+ * Launches Chromium browser
41
+ */
42
+ async function launchChromium(config) {
43
+ const { headless, userDataDir, persistence } = config;
44
+ let browser;
45
+ let context;
46
+ let page;
47
+ if (userDataDir) {
48
+ // Use persistent context when user data dir is specified
49
+ context = await chromium.launchPersistentContext(userDataDir, {
50
+ headless,
51
+ });
52
+ browser = null; // Persistent context doesn't expose browser
53
+ page = context.pages()[0] || await context.newPage();
54
+ }
55
+ else {
56
+ // Ephemeral browser
57
+ browser = await chromium.launch({ headless });
58
+ context = await browser.newContext();
59
+ page = await context.newPage();
60
+ }
61
+ return {
62
+ context,
63
+ page,
64
+ browser,
65
+ engine: 'chromium',
66
+ persistence,
67
+ userDataDir,
68
+ async close() {
69
+ if (browser) {
70
+ await browser.close().catch(() => { });
71
+ }
72
+ else {
73
+ await context.close().catch(() => { });
74
+ }
75
+ },
76
+ };
77
+ }
78
+ /**
79
+ * Launches Camoufox browser (Firefox-based anti-detection)
80
+ *
81
+ * Note: When user_data_dir is provided, Camoufox returns a BrowserContext directly
82
+ * (not a Browser). This is different from the ephemeral case where it returns Browser.
83
+ */
84
+ async function launchCamoufox(config) {
85
+ const { headless, userDataDir, persistence } = config;
86
+ // Desktop-only screen constraints to prevent mobile fingerprints
87
+ const screen = { minWidth: 1024, minHeight: 768 };
88
+ // Dynamic import to avoid loading Camoufox when not needed
89
+ let Camoufox;
90
+ try {
91
+ const camoufoxModule = await import('camoufox-js');
92
+ Camoufox = camoufoxModule.Camoufox;
93
+ }
94
+ catch (error) {
95
+ throw new Error('Camoufox is not available. Run "npx camoufox-js fetch" to download the browser. ' +
96
+ `Error: ${error instanceof Error ? error.message : String(error)}`);
97
+ }
98
+ if (userDataDir) {
99
+ // With user_data_dir, Camoufox returns BrowserContext directly (persistent context)
100
+ // humanize: adds human-like cursor movement delays (up to 2 seconds)
101
+ const context = await Camoufox({
102
+ headless,
103
+ humanize: 2.0,
104
+ screen,
105
+ user_data_dir: userDataDir,
106
+ });
107
+ const page = context.pages()[0] || await context.newPage();
108
+ return {
109
+ context,
110
+ page,
111
+ browser: null, // No browser instance with persistent context
112
+ engine: 'camoufox',
113
+ persistence,
114
+ userDataDir,
115
+ async close() {
116
+ await context.close().catch(() => { });
117
+ },
118
+ };
119
+ }
120
+ // Without user_data_dir, Camoufox returns Browser (ephemeral)
121
+ // humanize: adds human-like cursor movement delays (up to 2 seconds)
122
+ const browser = await Camoufox({
123
+ headless,
124
+ humanize: 2.0,
125
+ screen,
126
+ });
127
+ const context = await browser.newContext();
128
+ const page = await context.newPage();
129
+ return {
130
+ context,
131
+ page,
132
+ browser,
133
+ engine: 'camoufox',
134
+ persistence,
135
+ userDataDir,
136
+ async close() {
137
+ await browser.close().catch(() => { });
138
+ },
139
+ };
140
+ }
141
+ /**
142
+ * Checks if a browser engine is available
143
+ */
144
+ export async function isBrowserEngineAvailable(engine) {
145
+ if (engine === 'chromium') {
146
+ // Chromium is always available via Playwright
147
+ return true;
148
+ }
149
+ if (engine === 'camoufox') {
150
+ try {
151
+ await import('camoufox-js');
152
+ return true;
153
+ }
154
+ catch {
155
+ return false;
156
+ }
157
+ }
158
+ return false;
159
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Browser data persistence management
3
+ *
4
+ * Handles user data directory resolution for different persistence modes:
5
+ * - none: No persistence, browser uses ephemeral profile
6
+ * - session: Temp directory persistence with automatic cleanup
7
+ * - profile: Pack-local persistent profile
8
+ */
9
+ import type { BrowserPersistence } from './types.js';
10
+ /**
11
+ * Configuration for resolving browser data directory
12
+ */
13
+ export interface BrowserDataDirConfig {
14
+ /**
15
+ * Persistence mode
16
+ */
17
+ persistence: BrowserPersistence;
18
+ /**
19
+ * Session ID (required for 'session' mode)
20
+ */
21
+ sessionId?: string;
22
+ /**
23
+ * Pack directory path (required for 'profile' mode)
24
+ */
25
+ packPath?: string;
26
+ }
27
+ /**
28
+ * Resolves the user data directory path based on persistence configuration
29
+ *
30
+ * @param config - Persistence configuration
31
+ * @returns User data directory path, or undefined for 'none' persistence
32
+ */
33
+ export declare function resolveBrowserDataDir(config: BrowserDataDirConfig): string | undefined;
34
+ /**
35
+ * Cleans up inactive session directories
36
+ *
37
+ * Sessions that haven't been accessed in SESSION_INACTIVITY_MS are removed.
38
+ * Should be called periodically (e.g., on dashboard startup or interval).
39
+ */
40
+ export declare function cleanupInactiveSessions(): number;
41
+ /**
42
+ * Gets the session base directory path (for testing/debugging)
43
+ */
44
+ export declare function getSessionBaseDir(): string;
45
+ /**
46
+ * Gets the profile directory path for a pack
47
+ */
48
+ export declare function getProfileDir(packPath: string): string;
49
+ //# sourceMappingURL=browserPersistence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserPersistence.d.ts","sourceRoot":"","sources":["../src/browserPersistence.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAiBrD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,WAAW,EAAE,kBAAkB,CAAC;IAChC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,GAAG,SAAS,CAkCtF;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAsChD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Browser data persistence management
3
+ *
4
+ * Handles user data directory resolution for different persistence modes:
5
+ * - none: No persistence, browser uses ephemeral profile
6
+ * - session: Temp directory persistence with automatic cleanup
7
+ * - profile: Pack-local persistent profile
8
+ */
9
+ import { existsSync, mkdirSync, readdirSync, statSync, rmSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { tmpdir } from 'os';
12
+ /**
13
+ * Session inactivity timeout for cleanup (30 minutes in milliseconds)
14
+ */
15
+ const SESSION_INACTIVITY_MS = 30 * 60 * 1000;
16
+ /**
17
+ * Base directory name for session storage in temp
18
+ */
19
+ const SESSION_BASE_DIR = 'showrun-browser-sessions';
20
+ /**
21
+ * Profile directory name within pack directory
22
+ */
23
+ const PROFILE_DIR_NAME = '.browser-profile';
24
+ /**
25
+ * Resolves the user data directory path based on persistence configuration
26
+ *
27
+ * @param config - Persistence configuration
28
+ * @returns User data directory path, or undefined for 'none' persistence
29
+ */
30
+ export function resolveBrowserDataDir(config) {
31
+ const { persistence, sessionId, packPath } = config;
32
+ switch (persistence) {
33
+ case 'none':
34
+ return undefined;
35
+ case 'session': {
36
+ if (!sessionId) {
37
+ console.warn('[browserPersistence] Session persistence requires sessionId, falling back to none');
38
+ return undefined;
39
+ }
40
+ const baseDir = join(tmpdir(), SESSION_BASE_DIR);
41
+ ensureDir(baseDir);
42
+ const sessionDir = join(baseDir, sanitizeSessionId(sessionId));
43
+ ensureDir(sessionDir);
44
+ // Touch the directory to update mtime for activity tracking
45
+ touchDirectory(sessionDir);
46
+ return sessionDir;
47
+ }
48
+ case 'profile': {
49
+ if (!packPath) {
50
+ console.warn('[browserPersistence] Profile persistence requires packPath, falling back to none');
51
+ return undefined;
52
+ }
53
+ const profileDir = join(packPath, PROFILE_DIR_NAME);
54
+ ensureDir(profileDir);
55
+ return profileDir;
56
+ }
57
+ default:
58
+ return undefined;
59
+ }
60
+ }
61
+ /**
62
+ * Cleans up inactive session directories
63
+ *
64
+ * Sessions that haven't been accessed in SESSION_INACTIVITY_MS are removed.
65
+ * Should be called periodically (e.g., on dashboard startup or interval).
66
+ */
67
+ export function cleanupInactiveSessions() {
68
+ const baseDir = join(tmpdir(), SESSION_BASE_DIR);
69
+ if (!existsSync(baseDir)) {
70
+ return 0;
71
+ }
72
+ const now = Date.now();
73
+ let cleanedCount = 0;
74
+ try {
75
+ const entries = readdirSync(baseDir);
76
+ for (const entry of entries) {
77
+ const sessionDir = join(baseDir, entry);
78
+ try {
79
+ const stats = statSync(sessionDir);
80
+ if (!stats.isDirectory())
81
+ continue;
82
+ const lastAccess = stats.mtime.getTime();
83
+ const inactiveMs = now - lastAccess;
84
+ if (inactiveMs > SESSION_INACTIVITY_MS) {
85
+ rmSync(sessionDir, { recursive: true, force: true });
86
+ cleanedCount++;
87
+ console.log(`[browserPersistence] Cleaned up inactive session: ${entry}`);
88
+ }
89
+ }
90
+ catch (error) {
91
+ // Ignore errors for individual sessions (might be in use)
92
+ console.warn(`[browserPersistence] Could not check/clean session ${entry}:`, error);
93
+ }
94
+ }
95
+ }
96
+ catch (error) {
97
+ console.warn('[browserPersistence] Error during session cleanup:', error);
98
+ }
99
+ return cleanedCount;
100
+ }
101
+ /**
102
+ * Gets the session base directory path (for testing/debugging)
103
+ */
104
+ export function getSessionBaseDir() {
105
+ return join(tmpdir(), SESSION_BASE_DIR);
106
+ }
107
+ /**
108
+ * Gets the profile directory path for a pack
109
+ */
110
+ export function getProfileDir(packPath) {
111
+ return join(packPath, PROFILE_DIR_NAME);
112
+ }
113
+ /**
114
+ * Ensures a directory exists
115
+ */
116
+ function ensureDir(dir) {
117
+ if (!existsSync(dir)) {
118
+ mkdirSync(dir, { recursive: true });
119
+ }
120
+ }
121
+ /**
122
+ * Sanitizes a session ID for use as a directory name
123
+ */
124
+ function sanitizeSessionId(sessionId) {
125
+ // Replace unsafe characters with underscores
126
+ return sessionId.replace(/[^a-zA-Z0-9_-]/g, '_');
127
+ }
128
+ /**
129
+ * Touches a directory to update its mtime
130
+ */
131
+ function touchDirectory(dir) {
132
+ try {
133
+ const now = new Date();
134
+ // Node doesn't have a direct utimes for directories, but we can use a marker file
135
+ const markerPath = join(dir, '.last_access');
136
+ const fs = require('fs');
137
+ fs.closeSync(fs.openSync(markerPath, 'w'));
138
+ fs.utimesSync(dir, now, now);
139
+ }
140
+ catch {
141
+ // Ignore errors - touch is best-effort for cleanup tracking
142
+ }
143
+ }
@@ -0,0 +1,10 @@
1
+ import type { Browser, Page } from 'playwright';
2
+ import type { Logger, RunContext } from './types.js';
3
+ import type { NetworkCaptureApi } from './networkCapture.js';
4
+ /**
5
+ * Creates a RunContext for task pack execution
6
+ */
7
+ export declare class RunContextFactory {
8
+ static create(page: Page, browser: Browser, logger: Logger, artifactsDir: string, networkCapture?: NetworkCaptureApi): RunContext;
9
+ }
10
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAmB,UAAU,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,MAAM,CAAC,MAAM,CACX,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,cAAc,CAAC,EAAE,iBAAiB,GACjC,UAAU;CAyBd"}
@@ -0,0 +1,30 @@
1
+ import { writeFileSync, mkdirSync } from 'fs';
2
+ import { join } from 'path';
3
+ /**
4
+ * Creates a RunContext for task pack execution
5
+ */
6
+ export class RunContextFactory {
7
+ static create(page, browser, logger, artifactsDir, networkCapture) {
8
+ // Ensure artifacts directory exists
9
+ mkdirSync(artifactsDir, { recursive: true });
10
+ const artifacts = {
11
+ async saveScreenshot(name) {
12
+ const path = join(artifactsDir, `${name}.png`);
13
+ await page.screenshot({ path, fullPage: true });
14
+ return path;
15
+ },
16
+ async saveHTML(name, html) {
17
+ const path = join(artifactsDir, `${name}.html`);
18
+ writeFileSync(path, html, 'utf-8');
19
+ return path;
20
+ },
21
+ };
22
+ return {
23
+ page,
24
+ browser,
25
+ logger,
26
+ artifacts,
27
+ networkCapture,
28
+ };
29
+ }
30
+ }