@vulcn/engine 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -1,218 +1,182 @@
1
- // session.ts
2
- import { z } from "zod";
3
- import { parse, stringify } from "yaml";
4
- var StepSchema = z.discriminatedUnion("type", [
5
- z.object({
6
- id: z.string(),
7
- type: z.literal("navigate"),
8
- url: z.string(),
9
- timestamp: z.number()
10
- }),
11
- z.object({
12
- id: z.string(),
13
- type: z.literal("click"),
14
- selector: z.string(),
15
- position: z.object({ x: z.number(), y: z.number() }).optional(),
16
- timestamp: z.number()
17
- }),
18
- z.object({
19
- id: z.string(),
20
- type: z.literal("input"),
21
- selector: z.string(),
22
- value: z.string(),
23
- injectable: z.boolean().optional().default(true),
24
- timestamp: z.number()
25
- }),
26
- z.object({
27
- id: z.string(),
28
- type: z.literal("keypress"),
29
- key: z.string(),
30
- modifiers: z.array(z.string()).optional(),
31
- timestamp: z.number()
32
- }),
33
- z.object({
34
- id: z.string(),
35
- type: z.literal("scroll"),
36
- selector: z.string().optional(),
37
- position: z.object({ x: z.number(), y: z.number() }),
38
- timestamp: z.number()
39
- }),
40
- z.object({
41
- id: z.string(),
42
- type: z.literal("wait"),
43
- duration: z.number(),
44
- timestamp: z.number()
45
- })
46
- ]);
47
- var SessionSchema = z.object({
48
- version: z.string().default("1"),
49
- name: z.string(),
50
- recordedAt: z.string(),
51
- browser: z.enum(["chromium", "firefox", "webkit"]).default("chromium"),
52
- viewport: z.object({
53
- width: z.number(),
54
- height: z.number()
55
- }),
56
- startUrl: z.string(),
57
- steps: z.array(StepSchema)
58
- });
59
- function createSession(options) {
60
- return {
61
- version: "1",
62
- name: options.name,
63
- recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
64
- browser: options.browser ?? "chromium",
65
- viewport: options.viewport ?? { width: 1280, height: 720 },
66
- startUrl: options.startUrl,
67
- steps: []
68
- };
69
- }
70
- function parseSession(yaml) {
71
- const data = parse(yaml);
72
- return SessionSchema.parse(data);
73
- }
74
- function serializeSession(session) {
75
- return stringify(session, { lineWidth: 0 });
76
- }
77
-
78
- // browser.ts
79
- import { chromium, firefox, webkit } from "playwright";
80
- import { exec } from "child_process";
81
- import { promisify } from "util";
82
- var execAsync = promisify(exec);
83
- var BrowserNotFoundError = class extends Error {
84
- constructor(message) {
85
- super(message);
86
- this.name = "BrowserNotFoundError";
87
- }
88
- };
89
- async function launchBrowser(options = {}) {
90
- const browserType = options.browser ?? "chromium";
91
- const headless = options.headless ?? false;
92
- if (browserType === "chromium") {
93
- try {
94
- const browser = await chromium.launch({
95
- channel: "chrome",
96
- headless
97
- });
98
- return { browser, channel: "chrome" };
99
- } catch {
100
- }
101
- try {
102
- const browser = await chromium.launch({
103
- channel: "msedge",
104
- headless
105
- });
106
- return { browser, channel: "msedge" };
107
- } catch {
1
+ // src/driver-manager.ts
2
+ import { isAbsolute, resolve } from "path";
3
+ var DriverManager = class {
4
+ drivers = /* @__PURE__ */ new Map();
5
+ defaultDriver = null;
6
+ /**
7
+ * Register a driver
8
+ */
9
+ register(driver, source = "builtin") {
10
+ this.validateDriver(driver);
11
+ this.drivers.set(driver.name, { driver, source });
12
+ if (this.drivers.size === 1) {
13
+ this.defaultDriver = driver.name;
108
14
  }
109
- try {
110
- const browser = await chromium.launch({ headless });
111
- return { browser, channel: "chromium" };
112
- } catch {
113
- throw new BrowserNotFoundError(
114
- "No Chromium browser found. Install Chrome or run: vulcn install chromium"
115
- );
15
+ }
16
+ /**
17
+ * Load a driver from npm or local path
18
+ */
19
+ async load(nameOrPath) {
20
+ let driver;
21
+ let source;
22
+ if (nameOrPath.startsWith("./") || nameOrPath.startsWith("../") || isAbsolute(nameOrPath)) {
23
+ const resolved = isAbsolute(nameOrPath) ? nameOrPath : resolve(process.cwd(), nameOrPath);
24
+ const module = await import(resolved);
25
+ driver = module.default || module;
26
+ source = "local";
27
+ } else {
28
+ const module = await import(nameOrPath);
29
+ driver = module.default || module;
30
+ source = "npm";
116
31
  }
32
+ this.register(driver, source);
117
33
  }
118
- if (browserType === "firefox") {
119
- try {
120
- const browser = await firefox.launch({ headless });
121
- return { browser, channel: "firefox" };
122
- } catch {
123
- throw new BrowserNotFoundError(
124
- "Firefox not found. Run: vulcn install firefox"
125
- );
34
+ /**
35
+ * Get a loaded driver by name
36
+ */
37
+ get(name) {
38
+ return this.drivers.get(name)?.driver;
39
+ }
40
+ /**
41
+ * Get the default driver
42
+ */
43
+ getDefault() {
44
+ if (!this.defaultDriver) return void 0;
45
+ return this.get(this.defaultDriver);
46
+ }
47
+ /**
48
+ * Set the default driver
49
+ */
50
+ setDefault(name) {
51
+ if (!this.drivers.has(name)) {
52
+ throw new Error(`Driver "${name}" is not registered`);
126
53
  }
54
+ this.defaultDriver = name;
127
55
  }
128
- if (browserType === "webkit") {
129
- try {
130
- const browser = await webkit.launch({ headless });
131
- return { browser, channel: "webkit" };
132
- } catch {
133
- throw new BrowserNotFoundError(
134
- "WebKit not found. Run: vulcn install webkit"
56
+ /**
57
+ * Check if a driver is registered
58
+ */
59
+ has(name) {
60
+ return this.drivers.has(name);
61
+ }
62
+ /**
63
+ * Get all registered drivers
64
+ */
65
+ list() {
66
+ return Array.from(this.drivers.values());
67
+ }
68
+ /**
69
+ * Get driver for a session
70
+ */
71
+ getForSession(session) {
72
+ const driverName = session.driver;
73
+ const driver = this.get(driverName);
74
+ if (!driver) {
75
+ throw new Error(
76
+ `Driver "${driverName}" not found. Install @vulcn/driver-${driverName} or load it manually.`
135
77
  );
136
78
  }
79
+ return driver;
137
80
  }
138
- throw new BrowserNotFoundError(`Unknown browser type: ${browserType}`);
139
- }
140
- async function installBrowsers(browsers = ["chromium"]) {
141
- const browserArg = browsers.join(" ");
142
- await execAsync(`npx playwright install ${browserArg}`);
143
- }
144
- async function checkBrowsers() {
145
- const results = {
146
- systemChrome: false,
147
- systemEdge: false,
148
- playwrightChromium: false,
149
- playwrightFirefox: false,
150
- playwrightWebkit: false
151
- };
152
- try {
153
- const browser = await chromium.launch({
154
- channel: "chrome",
155
- headless: true
156
- });
157
- await browser.close();
158
- results.systemChrome = true;
159
- } catch {
160
- }
161
- try {
162
- const browser = await chromium.launch({
163
- channel: "msedge",
164
- headless: true
165
- });
166
- await browser.close();
167
- results.systemEdge = true;
168
- } catch {
81
+ /**
82
+ * Start recording with a driver
83
+ */
84
+ async startRecording(driverName, config, options = {}) {
85
+ const driver = this.get(driverName);
86
+ if (!driver) {
87
+ throw new Error(`Driver "${driverName}" not found`);
88
+ }
89
+ return driver.recorder.start(config, options);
169
90
  }
170
- try {
171
- const browser = await chromium.launch({ headless: true });
172
- await browser.close();
173
- results.playwrightChromium = true;
174
- } catch {
91
+ /**
92
+ * Execute a session
93
+ */
94
+ async execute(session, pluginManager2, options = {}) {
95
+ const driver = this.getForSession(session);
96
+ const findings = [];
97
+ const logger = this.createLogger(driver.name);
98
+ const ctx = {
99
+ session,
100
+ pluginManager: pluginManager2,
101
+ payloads: pluginManager2.getPayloads(),
102
+ findings,
103
+ addFinding: (finding) => {
104
+ findings.push(finding);
105
+ pluginManager2.addFinding(finding);
106
+ options.onFinding?.(finding);
107
+ },
108
+ logger,
109
+ options
110
+ };
111
+ return driver.runner.execute(session, ctx);
175
112
  }
176
- try {
177
- const browser = await firefox.launch({ headless: true });
178
- await browser.close();
179
- results.playwrightFirefox = true;
180
- } catch {
113
+ /**
114
+ * Validate driver structure
115
+ */
116
+ validateDriver(driver) {
117
+ if (!driver || typeof driver !== "object") {
118
+ throw new Error("Driver must be an object");
119
+ }
120
+ const d = driver;
121
+ if (typeof d.name !== "string" || !d.name) {
122
+ throw new Error("Driver must have a name");
123
+ }
124
+ if (typeof d.version !== "string" || !d.version) {
125
+ throw new Error("Driver must have a version");
126
+ }
127
+ if (!Array.isArray(d.stepTypes) || d.stepTypes.length === 0) {
128
+ throw new Error("Driver must define stepTypes");
129
+ }
130
+ if (!d.recorder || typeof d.recorder !== "object") {
131
+ throw new Error("Driver must have a recorder");
132
+ }
133
+ if (!d.runner || typeof d.runner !== "object") {
134
+ throw new Error("Driver must have a runner");
135
+ }
181
136
  }
182
- try {
183
- const browser = await webkit.launch({ headless: true });
184
- await browser.close();
185
- results.playwrightWebkit = true;
186
- } catch {
137
+ /**
138
+ * Create a scoped logger for a driver
139
+ */
140
+ createLogger(name) {
141
+ const prefix = `[driver:${name}]`;
142
+ return {
143
+ debug: (msg, ...args) => console.debug(prefix, msg, ...args),
144
+ info: (msg, ...args) => console.info(prefix, msg, ...args),
145
+ warn: (msg, ...args) => console.warn(prefix, msg, ...args),
146
+ error: (msg, ...args) => console.error(prefix, msg, ...args)
147
+ };
187
148
  }
188
- return results;
189
- }
149
+ };
150
+ var driverManager = new DriverManager();
151
+
152
+ // src/driver-types.ts
153
+ var DRIVER_API_VERSION = 1;
190
154
 
191
- // plugin-manager.ts
155
+ // src/plugin-manager.ts
192
156
  import { readFile } from "fs/promises";
193
157
  import { existsSync } from "fs";
194
- import { resolve, isAbsolute } from "path";
158
+ import { resolve as resolve2, isAbsolute as isAbsolute2 } from "path";
195
159
  import YAML from "yaml";
196
- import { z as z2 } from "zod";
160
+ import { z } from "zod";
197
161
 
198
- // plugin-types.ts
162
+ // src/plugin-types.ts
199
163
  var PLUGIN_API_VERSION = 1;
200
164
 
201
- // plugin-manager.ts
165
+ // src/plugin-manager.ts
202
166
  var ENGINE_VERSION = "0.2.0";
203
- var VulcnConfigSchema = z2.object({
204
- version: z2.string().default("1"),
205
- plugins: z2.array(
206
- z2.object({
207
- name: z2.string(),
208
- config: z2.record(z2.unknown()).optional(),
209
- enabled: z2.boolean().default(true)
167
+ var VulcnConfigSchema = z.object({
168
+ version: z.string().default("1"),
169
+ plugins: z.array(
170
+ z.object({
171
+ name: z.string(),
172
+ config: z.record(z.unknown()).optional(),
173
+ enabled: z.boolean().default(true)
210
174
  })
211
175
  ).optional(),
212
- settings: z2.object({
213
- browser: z2.enum(["chromium", "firefox", "webkit"]).optional(),
214
- headless: z2.boolean().optional(),
215
- timeout: z2.number().optional()
176
+ settings: z.object({
177
+ browser: z.enum(["chromium", "firefox", "webkit"]).optional(),
178
+ headless: z.boolean().optional(),
179
+ timeout: z.number().optional()
216
180
  }).optional()
217
181
  });
218
182
  var PluginManager = class {
@@ -237,7 +201,7 @@ var PluginManager = class {
237
201
  ".vulcnrc.json"
238
202
  ];
239
203
  for (const path of paths) {
240
- const resolved = isAbsolute(path) ? path : resolve(process.cwd(), path);
204
+ const resolved = isAbsolute2(path) ? path : resolve2(process.cwd(), path);
241
205
  if (existsSync(resolved)) {
242
206
  const content = await readFile(resolved, "utf-8");
243
207
  const parsed = path.endsWith(".json") ? JSON.parse(content) : YAML.parse(content);
@@ -276,8 +240,8 @@ var PluginManager = class {
276
240
  const { name, config: pluginConfig = {} } = config;
277
241
  let plugin;
278
242
  let source;
279
- if (name.startsWith("./") || name.startsWith("../") || isAbsolute(name)) {
280
- const resolved = isAbsolute(name) ? name : resolve(process.cwd(), name);
243
+ if (name.startsWith("./") || name.startsWith("../") || isAbsolute2(name)) {
244
+ const resolved = isAbsolute2(name) ? name : resolve2(process.cwd(), name);
281
245
  const module = await import(resolved);
282
246
  plugin = module.default || module;
283
247
  source = "local";
@@ -517,7 +481,197 @@ var PluginManager = class {
517
481
  };
518
482
  var pluginManager = new PluginManager();
519
483
 
520
- // recorder.ts
484
+ // src/session.ts
485
+ import { z as z2 } from "zod";
486
+ import { parse, stringify } from "yaml";
487
+ var StepSchema = z2.discriminatedUnion("type", [
488
+ z2.object({
489
+ id: z2.string(),
490
+ type: z2.literal("navigate"),
491
+ url: z2.string(),
492
+ timestamp: z2.number()
493
+ }),
494
+ z2.object({
495
+ id: z2.string(),
496
+ type: z2.literal("click"),
497
+ selector: z2.string(),
498
+ position: z2.object({ x: z2.number(), y: z2.number() }).optional(),
499
+ timestamp: z2.number()
500
+ }),
501
+ z2.object({
502
+ id: z2.string(),
503
+ type: z2.literal("input"),
504
+ selector: z2.string(),
505
+ value: z2.string(),
506
+ injectable: z2.boolean().optional().default(true),
507
+ timestamp: z2.number()
508
+ }),
509
+ z2.object({
510
+ id: z2.string(),
511
+ type: z2.literal("keypress"),
512
+ key: z2.string(),
513
+ modifiers: z2.array(z2.string()).optional(),
514
+ timestamp: z2.number()
515
+ }),
516
+ z2.object({
517
+ id: z2.string(),
518
+ type: z2.literal("scroll"),
519
+ selector: z2.string().optional(),
520
+ position: z2.object({ x: z2.number(), y: z2.number() }),
521
+ timestamp: z2.number()
522
+ }),
523
+ z2.object({
524
+ id: z2.string(),
525
+ type: z2.literal("wait"),
526
+ duration: z2.number(),
527
+ timestamp: z2.number()
528
+ })
529
+ ]);
530
+ var SessionSchema = z2.object({
531
+ version: z2.string().default("1"),
532
+ name: z2.string(),
533
+ recordedAt: z2.string(),
534
+ browser: z2.enum(["chromium", "firefox", "webkit"]).default("chromium"),
535
+ viewport: z2.object({
536
+ width: z2.number(),
537
+ height: z2.number()
538
+ }),
539
+ startUrl: z2.string(),
540
+ steps: z2.array(StepSchema)
541
+ });
542
+ function createSession(options) {
543
+ return {
544
+ version: "1",
545
+ name: options.name,
546
+ recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
547
+ browser: options.browser ?? "chromium",
548
+ viewport: options.viewport ?? { width: 1280, height: 720 },
549
+ startUrl: options.startUrl,
550
+ steps: []
551
+ };
552
+ }
553
+ function parseSession(yaml) {
554
+ const data = parse(yaml);
555
+ return SessionSchema.parse(data);
556
+ }
557
+ function serializeSession(session) {
558
+ return stringify(session, { lineWidth: 0 });
559
+ }
560
+
561
+ // src/browser.ts
562
+ import { chromium, firefox, webkit } from "playwright";
563
+ import { exec } from "child_process";
564
+ import { promisify } from "util";
565
+ var execAsync = promisify(exec);
566
+ var BrowserNotFoundError = class extends Error {
567
+ constructor(message) {
568
+ super(message);
569
+ this.name = "BrowserNotFoundError";
570
+ }
571
+ };
572
+ async function launchBrowser(options = {}) {
573
+ const browserType = options.browser ?? "chromium";
574
+ const headless = options.headless ?? false;
575
+ if (browserType === "chromium") {
576
+ try {
577
+ const browser = await chromium.launch({
578
+ channel: "chrome",
579
+ headless
580
+ });
581
+ return { browser, channel: "chrome" };
582
+ } catch {
583
+ }
584
+ try {
585
+ const browser = await chromium.launch({
586
+ channel: "msedge",
587
+ headless
588
+ });
589
+ return { browser, channel: "msedge" };
590
+ } catch {
591
+ }
592
+ try {
593
+ const browser = await chromium.launch({ headless });
594
+ return { browser, channel: "chromium" };
595
+ } catch {
596
+ throw new BrowserNotFoundError(
597
+ "No Chromium browser found. Install Chrome or run: vulcn install chromium"
598
+ );
599
+ }
600
+ }
601
+ if (browserType === "firefox") {
602
+ try {
603
+ const browser = await firefox.launch({ headless });
604
+ return { browser, channel: "firefox" };
605
+ } catch {
606
+ throw new BrowserNotFoundError(
607
+ "Firefox not found. Run: vulcn install firefox"
608
+ );
609
+ }
610
+ }
611
+ if (browserType === "webkit") {
612
+ try {
613
+ const browser = await webkit.launch({ headless });
614
+ return { browser, channel: "webkit" };
615
+ } catch {
616
+ throw new BrowserNotFoundError(
617
+ "WebKit not found. Run: vulcn install webkit"
618
+ );
619
+ }
620
+ }
621
+ throw new BrowserNotFoundError(`Unknown browser type: ${browserType}`);
622
+ }
623
+ async function installBrowsers(browsers = ["chromium"]) {
624
+ const browserArg = browsers.join(" ");
625
+ await execAsync(`npx playwright install ${browserArg}`);
626
+ }
627
+ async function checkBrowsers() {
628
+ const results = {
629
+ systemChrome: false,
630
+ systemEdge: false,
631
+ playwrightChromium: false,
632
+ playwrightFirefox: false,
633
+ playwrightWebkit: false
634
+ };
635
+ try {
636
+ const browser = await chromium.launch({
637
+ channel: "chrome",
638
+ headless: true
639
+ });
640
+ await browser.close();
641
+ results.systemChrome = true;
642
+ } catch {
643
+ }
644
+ try {
645
+ const browser = await chromium.launch({
646
+ channel: "msedge",
647
+ headless: true
648
+ });
649
+ await browser.close();
650
+ results.systemEdge = true;
651
+ } catch {
652
+ }
653
+ try {
654
+ const browser = await chromium.launch({ headless: true });
655
+ await browser.close();
656
+ results.playwrightChromium = true;
657
+ } catch {
658
+ }
659
+ try {
660
+ const browser = await firefox.launch({ headless: true });
661
+ await browser.close();
662
+ results.playwrightFirefox = true;
663
+ } catch {
664
+ }
665
+ try {
666
+ const browser = await webkit.launch({ headless: true });
667
+ await browser.close();
668
+ results.playwrightWebkit = true;
669
+ } catch {
670
+ }
671
+ return results;
672
+ }
673
+
674
+ // src/recorder.ts
521
675
  var Recorder = class _Recorder {
522
676
  /**
523
677
  * Start a new recording session
@@ -823,7 +977,7 @@ var Recorder = class _Recorder {
823
977
  }
824
978
  };
825
979
 
826
- // runner.ts
980
+ // src/runner.ts
827
981
  var Runner = class _Runner {
828
982
  /**
829
983
  * Execute a session with security payloads from plugins
@@ -1098,6 +1252,8 @@ var Runner = class _Runner {
1098
1252
  };
1099
1253
  export {
1100
1254
  BrowserNotFoundError,
1255
+ DRIVER_API_VERSION,
1256
+ DriverManager,
1101
1257
  PLUGIN_API_VERSION,
1102
1258
  PluginManager,
1103
1259
  Recorder,
@@ -1106,6 +1262,7 @@ export {
1106
1262
  StepSchema,
1107
1263
  checkBrowsers,
1108
1264
  createSession,
1265
+ driverManager,
1109
1266
  installBrowsers,
1110
1267
  launchBrowser,
1111
1268
  parseSession,