@xera-ai/web 0.1.5 → 0.1.6

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.
@@ -2,4 +2,9 @@ export interface TypecheckResult {
2
2
  ok: boolean;
3
3
  errors: string[];
4
4
  }
5
+ /**
6
+ * Type-check the ticket's TypeScript files using the project's root tsconfig.
7
+ * Errors are filtered to those whose path contains the ticket directory, so the
8
+ * skill sees only the locally relevant ones.
9
+ */
5
10
  export declare function typecheckTicket(ticketDir: string): Promise<TypecheckResult>;
package/dist/index.js CHANGED
@@ -357,18 +357,45 @@ function validateGherkin(content) {
357
357
  }
358
358
  // src/generator/typecheck.ts
359
359
  import { spawnSync } from "child_process";
360
+ import { existsSync as existsSync2 } from "fs";
361
+ import { dirname, join as join5 } from "path";
362
+ function findNearestTsconfig(start) {
363
+ let dir = start;
364
+ while (true) {
365
+ const candidate = join5(dir, "tsconfig.json");
366
+ if (existsSync2(candidate))
367
+ return candidate;
368
+ const parent = dirname(dir);
369
+ if (parent === dir)
370
+ return null;
371
+ dir = parent;
372
+ }
373
+ }
360
374
  async function typecheckTicket(ticketDir) {
361
- const proc = spawnSync("npx", ["tsc", "--noEmit", "--project", ticketDir], { encoding: "utf8" });
375
+ const tsconfig = findNearestTsconfig(ticketDir);
376
+ if (!tsconfig) {
377
+ return {
378
+ ok: false,
379
+ errors: [
380
+ `No tsconfig.json found walking up from ${ticketDir}. Run \`xera init\` to scaffold one at the project root.`
381
+ ]
382
+ };
383
+ }
384
+ const proc = spawnSync("npx", ["tsc", "--noEmit", "-p", tsconfig], { encoding: "utf8" });
362
385
  if (proc.status === 0)
363
386
  return { ok: true, errors: [] };
364
- const out = (proc.stdout || "") + (proc.stderr || "");
365
- const errors = out.split(`
387
+ const out = `${proc.stdout ?? ""}${proc.stderr ?? ""}`;
388
+ const allErrors = out.split(`
366
389
  `).filter((line) => /error TS\d+/.test(line));
367
- return { ok: false, errors };
390
+ const ticketErrors = allErrors.filter((line) => line.includes(ticketDir));
391
+ return {
392
+ ok: false,
393
+ errors: ticketErrors.length > 0 ? ticketErrors : allErrors
394
+ };
368
395
  }
369
396
  // src/generator/lint.ts
370
- import { readFileSync as readFileSync3, existsSync as existsSync2, readdirSync } from "fs";
371
- import { join as join5 } from "path";
397
+ import { readFileSync as readFileSync3, existsSync as existsSync3, readdirSync } from "fs";
398
+ import { join as join6 } from "path";
372
399
 
373
400
  // src/generator/selector-rules.ts
374
401
  var AUTO_CLASS_RE = /\.(?:Mui|css|ant|chakra|MuiButton)[A-Za-z]*-[A-Za-z0-9_]*-[A-Za-z0-9_]{3,}/;
@@ -401,11 +428,11 @@ function lintSelectors(source) {
401
428
 
402
429
  // src/generator/lint.ts
403
430
  function listTsFiles(dir) {
404
- if (!existsSync2(dir))
431
+ if (!existsSync3(dir))
405
432
  return [];
406
433
  const out = [];
407
434
  for (const name of readdirSync(dir, { withFileTypes: true })) {
408
- const full = join5(dir, name.name);
435
+ const full = join6(dir, name.name);
409
436
  if (name.isDirectory())
410
437
  out.push(...listTsFiles(full));
411
438
  else if (name.name.endsWith(".ts"))
@@ -425,18 +452,18 @@ async function lintTicket(ticketDir) {
425
452
  return { ok: warnings.length === 0, warnings };
426
453
  }
427
454
  // src/generator/pom-scan.ts
428
- import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
429
- import { join as join6 } from "path";
455
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
456
+ import { join as join7 } from "path";
430
457
  var CLASS_RE = /export\s+class\s+([A-Z][A-Za-z0-9_]*)/g;
431
458
  function scanSharedPoms(repoRoot) {
432
- const dir = join6(repoRoot, "shared", "page-objects");
433
- if (!existsSync3(dir))
459
+ const dir = join7(repoRoot, "shared", "page-objects");
460
+ if (!existsSync4(dir))
434
461
  return [];
435
462
  const found = [];
436
463
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
437
464
  if (!entry.isFile() || !entry.name.endsWith(".ts"))
438
465
  continue;
439
- const path = join6(dir, entry.name);
466
+ const path = join7(dir, entry.name);
440
467
  const src = readFileSync4(path, "utf8");
441
468
  for (const m of src.matchAll(CLASS_RE)) {
442
469
  found.push({ className: m[1], absolutePath: path });
@@ -445,23 +472,23 @@ function scanSharedPoms(repoRoot) {
445
472
  return found;
446
473
  }
447
474
  // src/generator/promote.ts
448
- import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync } from "fs";
449
- import { join as join7 } from "path";
475
+ import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync } from "fs";
476
+ import { join as join8 } from "path";
450
477
  async function promotePom(input) {
451
- const fromDir = join7(input.repoRoot, ".xera", input.ticket, "page-objects");
452
- const toDir = join7(input.repoRoot, "shared", "page-objects");
478
+ const fromDir = join8(input.repoRoot, ".xera", input.ticket, "page-objects");
479
+ const toDir = join8(input.repoRoot, "shared", "page-objects");
453
480
  const file = `${input.className}.ts`;
454
- const fromPath = join7(fromDir, file);
455
- const toPath = join7(toDir, file);
456
- if (!existsSync4(fromPath)) {
481
+ const fromPath = join8(fromDir, file);
482
+ const toPath = join8(toDir, file);
483
+ if (!existsSync5(fromPath)) {
457
484
  throw new Error(`POM ${file} not found at ${fromPath}`);
458
485
  }
459
- if (existsSync4(toPath)) {
486
+ if (existsSync5(toPath)) {
460
487
  throw new Error(`POM ${file} already exists at ${toPath}. Reconcile manually before promoting.`);
461
488
  }
462
489
  renameSync(fromPath, toPath);
463
- const specPath = join7(input.repoRoot, ".xera", input.ticket, "spec.ts");
464
- if (existsSync4(specPath)) {
490
+ const specPath = join8(input.repoRoot, ".xera", input.ticket, "spec.ts");
491
+ if (existsSync5(specPath)) {
465
492
  const src = readFileSync5(specPath, "utf8");
466
493
  const updated = src.replace(new RegExp(`from\\s+['"]\\./page-objects/${input.className}['"]`, "g"), `from '../../shared/page-objects/${input.className}'`);
467
494
  writeFileSync3(specPath, updated);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xera-ai/web",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,14 +1,54 @@
1
1
  import { spawnSync } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
2
4
 
3
5
  export interface TypecheckResult {
4
6
  ok: boolean;
5
7
  errors: string[];
6
8
  }
7
9
 
10
+ /**
11
+ * Walks up from `start` looking for the first `tsconfig.json`. Returns null if
12
+ * none is found before reaching the filesystem root.
13
+ */
14
+ function findNearestTsconfig(start: string): string | null {
15
+ let dir = start;
16
+ // eslint-disable-next-line no-constant-condition
17
+ while (true) {
18
+ const candidate = join(dir, 'tsconfig.json');
19
+ if (existsSync(candidate)) return candidate;
20
+ const parent = dirname(dir);
21
+ if (parent === dir) return null;
22
+ dir = parent;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Type-check the ticket's TypeScript files using the project's root tsconfig.
28
+ * Errors are filtered to those whose path contains the ticket directory, so the
29
+ * skill sees only the locally relevant ones.
30
+ */
8
31
  export async function typecheckTicket(ticketDir: string): Promise<TypecheckResult> {
9
- const proc = spawnSync('npx', ['tsc', '--noEmit', '--project', ticketDir], { encoding: 'utf8' });
32
+ const tsconfig = findNearestTsconfig(ticketDir);
33
+ if (!tsconfig) {
34
+ return {
35
+ ok: false,
36
+ errors: [
37
+ `No tsconfig.json found walking up from ${ticketDir}. Run \`xera init\` to scaffold one at the project root.`,
38
+ ],
39
+ };
40
+ }
41
+
42
+ const proc = spawnSync('npx', ['tsc', '--noEmit', '-p', tsconfig], { encoding: 'utf8' });
10
43
  if (proc.status === 0) return { ok: true, errors: [] };
11
- const out = (proc.stdout || '') + (proc.stderr || '');
12
- const errors = out.split('\n').filter(line => /error TS\d+/.test(line));
13
- return { ok: false, errors };
44
+
45
+ const out = `${proc.stdout ?? ''}${proc.stderr ?? ''}`;
46
+ const allErrors = out.split('\n').filter((line) => /error TS\d+/.test(line));
47
+ // Keep errors that originate inside the ticket dir; if none match, fall back
48
+ // to all errors so callers don't see "ok" for a broken root.
49
+ const ticketErrors = allErrors.filter((line) => line.includes(ticketDir));
50
+ return {
51
+ ok: false,
52
+ errors: ticketErrors.length > 0 ? ticketErrors : allErrors,
53
+ };
14
54
  }