@xera-ai/core 0.12.0 → 0.12.1

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/bin/internal.ts CHANGED
@@ -1,11 +1,19 @@
1
1
  #!/usr/bin/env bun
2
+ import { existsSync } from 'node:fs';
2
3
  import { config } from 'dotenv';
3
4
  import { run } from '../src/bin-internal/index';
4
5
 
5
- // Load .env.local (secrets, gitignored) then .env (defaults) so every
6
- // xera-internal subcommand has access to credentials without requiring
7
- // the caller to pre-load env.
8
- config({ path: '.env.local' });
6
+ // xera canonicalizes on `.env` (gitignored; see `xera init`, `xera doctor`,
7
+ // scaffolded `.gitignore`). Earlier versions also loaded `.env.local` first,
8
+ // which silently overrode `.env` when both files existed — see issue #92. We
9
+ // now load only `.env`; if `.env.local` is present, warn loudly so legacy
10
+ // users migrate rather than wondering why their values are ignored.
11
+ if (existsSync('.env.local')) {
12
+ console.error(
13
+ '\nwarning: .env.local detected but ignored. xera uses .env only — ' +
14
+ 'merge values from .env.local into .env and delete .env.local to silence this warning.\n',
15
+ );
16
+ }
9
17
  config();
10
18
 
11
19
  const code = await run(process.argv.slice(2));
@@ -8559,6 +8559,7 @@ var init_graph_backfill = __esm(() => {
8559
8559
 
8560
8560
  // bin/internal.ts
8561
8561
  var import_dotenv = __toESM(require_main(), 1);
8562
+ import { existsSync as existsSync33 } from "fs";
8562
8563
 
8563
8564
  // src/bin-internal/ac-coverage-backfill-finalize.ts
8564
8565
  init_store();
@@ -8797,7 +8798,7 @@ var CoverageSchema = z3.object({
8797
8798
  criticalAreas: z3.array(z3.string().regex(/^[a-z0-9-]+$/)).default([]),
8798
8799
  autoSnapshotOnCoverage: z3.boolean().default(true)
8799
8800
  }).prefault({});
8800
- var XeraConfigSchema = z3.object({
8801
+ var XeraConfigSchema = z3.strictObject({
8801
8802
  jira: JiraSchema,
8802
8803
  web: WebSchema.optional(),
8803
8804
  http: HttpSchema.optional(),
@@ -8852,6 +8853,46 @@ async function authSetupCmd(argv) {
8852
8853
  }
8853
8854
  const mod = await import(pathToFileURL2(authSetupScript).href);
8854
8855
  let exitCode = 0;
8856
+ const shapeRequestsWeb = opts.shape === "all" || opts.shape === "web";
8857
+ const shapeRequestsHttp = opts.shape === "all" || opts.shape === "http";
8858
+ const explicit = opts.shape !== "all";
8859
+ if (shapeRequestsWeb && config.web && typeof mod.web !== "function") {
8860
+ console.error(`[xera:auth-setup] web adapter is configured in xera.config.ts but shared/auth-setup.ts is missing the \`web\` export.
8861
+ ` + ` Add: \`export const web = defineAuthSetup(async (page, role, creds) => { ... })\` \u2014 see docs/CONFIGURATION.md`);
8862
+ exitCode = 1;
8863
+ }
8864
+ if (shapeRequestsHttp && config.http && typeof mod.http !== "function") {
8865
+ console.error(`[xera:auth-setup] http adapter is configured in xera.config.ts but shared/auth-setup.ts is missing the \`http\` export.
8866
+ ` + ` Add: \`export const http = defineHttpAuthSetup(async (request, role, creds) => { ... })\` \u2014 see docs/CONFIGURATION.md`);
8867
+ exitCode = 1;
8868
+ }
8869
+ if (explicit && opts.shape === "web" && !config.web) {
8870
+ console.error(`[xera:auth-setup] --shape web requested, but xera.config.ts has no \`web\` block. Add a web: {...} block or use --shape http/all.`);
8871
+ exitCode = 1;
8872
+ }
8873
+ if (explicit && opts.shape === "http" && !config.http) {
8874
+ console.error(`[xera:auth-setup] --shape http requested, but xera.config.ts has no \`http\` block. Add an http: {...} block or use --shape web/all.`);
8875
+ exitCode = 1;
8876
+ }
8877
+ if (!config.web && !config.http) {
8878
+ console.error(`[xera:auth-setup] no \`web\` or \`http\` block found in xera.config.ts \u2014 nothing to authenticate.`);
8879
+ exitCode = 1;
8880
+ }
8881
+ if (opts.role !== undefined) {
8882
+ const webRoles = shapeRequestsWeb && config.web ? Object.keys(config.web.auth.roles) : [];
8883
+ const httpRoles = shapeRequestsHttp && config.http ? Object.keys(config.http.auth.roles) : [];
8884
+ const allRoles = Array.from(new Set([...webRoles, ...httpRoles]));
8885
+ if (allRoles.length > 0 && !allRoles.includes(opts.role)) {
8886
+ const detail = [];
8887
+ if (webRoles.length > 0)
8888
+ detail.push(`web roles: ${webRoles.join(", ")}`);
8889
+ if (httpRoles.length > 0)
8890
+ detail.push(`http roles: ${httpRoles.join(", ")}`);
8891
+ console.error(`[xera:auth-setup] unknown role '${opts.role}' \u2014 configured roles: ${allRoles.join(", ")}
8892
+ (${detail.join("; ")})`);
8893
+ return 1;
8894
+ }
8895
+ }
8855
8896
  if ((opts.shape === "all" || opts.shape === "web") && config.web && typeof mod.web === "function") {
8856
8897
  const webConfig = config.web;
8857
8898
  const envName = process.env.XERA_ENV ?? webConfig.defaultEnv;
@@ -13313,7 +13354,11 @@ Commands: ${Object.keys(COMMANDS).join(", ")}`);
13313
13354
  }
13314
13355
 
13315
13356
  // bin/internal.ts
13316
- import_dotenv.config({ path: ".env.local" });
13357
+ if (existsSync33(".env.local")) {
13358
+ console.error(`
13359
+ warning: .env.local detected but ignored. xera uses .env only \u2014 ` + `merge values from .env.local into .env and delete .env.local to silence this warning.
13360
+ `);
13361
+ }
13317
13362
  import_dotenv.config();
13318
13363
  var code = await run(process.argv.slice(2));
13319
13364
  process.exit(code);
package/dist/src/index.js CHANGED
@@ -401,7 +401,7 @@ var CoverageSchema = z4.object({
401
401
  criticalAreas: z4.array(z4.string().regex(/^[a-z0-9-]+$/)).default([]),
402
402
  autoSnapshotOnCoverage: z4.boolean().default(true)
403
403
  }).prefault({});
404
- var XeraConfigSchema = z4.object({
404
+ var XeraConfigSchema = z4.strictObject({
405
405
  jira: JiraSchema,
406
406
  web: WebSchema.optional(),
407
407
  http: HttpSchema.optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xera-ai/core",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -31,8 +31,8 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "zod": "4.4.3",
34
- "@xera-ai/web": "^0.12.0",
35
- "@xera-ai/http": "^0.12.0",
34
+ "@xera-ai/web": "^0.12.1",
35
+ "@xera-ai/http": "^0.12.1",
36
36
  "@playwright/test": "1.60.0",
37
37
  "dotenv": "^16.0.0",
38
38
  "fflate": "0.8.3",
@@ -44,6 +44,66 @@ export async function authSetupCmd(argv: string[]): Promise<number> {
44
44
 
45
45
  let exitCode = 0;
46
46
 
47
+ // Pre-flight: detect requested-but-impossible shapes before we silently no-op.
48
+ // This is the issue #93 fix: previously `--shape http` against a project where
49
+ // shared/auth-setup.ts only exports `web` would print nothing and exit 0,
50
+ // leaving the user in an infinite "doctor says run auth-setup" loop.
51
+ const shapeRequestsWeb = opts.shape === 'all' || opts.shape === 'web';
52
+ const shapeRequestsHttp = opts.shape === 'all' || opts.shape === 'http';
53
+ const explicit = opts.shape !== 'all';
54
+
55
+ if (shapeRequestsWeb && config.web && typeof mod.web !== 'function') {
56
+ console.error(
57
+ `[xera:auth-setup] web adapter is configured in xera.config.ts but shared/auth-setup.ts is missing the \`web\` export.\n` +
58
+ ` Add: \`export const web = defineAuthSetup(async (page, role, creds) => { ... })\` — see docs/CONFIGURATION.md`,
59
+ );
60
+ exitCode = 1;
61
+ }
62
+ if (shapeRequestsHttp && config.http && typeof mod.http !== 'function') {
63
+ console.error(
64
+ `[xera:auth-setup] http adapter is configured in xera.config.ts but shared/auth-setup.ts is missing the \`http\` export.\n` +
65
+ ` Add: \`export const http = defineHttpAuthSetup(async (request, role, creds) => { ... })\` — see docs/CONFIGURATION.md`,
66
+ );
67
+ exitCode = 1;
68
+ }
69
+ if (explicit && opts.shape === 'web' && !config.web) {
70
+ console.error(
71
+ `[xera:auth-setup] --shape web requested, but xera.config.ts has no \`web\` block. Add a web: {...} block or use --shape http/all.`,
72
+ );
73
+ exitCode = 1;
74
+ }
75
+ if (explicit && opts.shape === 'http' && !config.http) {
76
+ console.error(
77
+ `[xera:auth-setup] --shape http requested, but xera.config.ts has no \`http\` block. Add an http: {...} block or use --shape web/all.`,
78
+ );
79
+ exitCode = 1;
80
+ }
81
+ if (!config.web && !config.http) {
82
+ console.error(
83
+ `[xera:auth-setup] no \`web\` or \`http\` block found in xera.config.ts — nothing to authenticate.`,
84
+ );
85
+ exitCode = 1;
86
+ }
87
+
88
+ // Unknown-role detection (#98): without this, a typoed --role silently
89
+ // matches no iteration of the per-adapter loops and we exit 0 — leaving
90
+ // the user wondering why `xera doctor` still reports the auth file missing.
91
+ if (opts.role !== undefined) {
92
+ const webRoles = shapeRequestsWeb && config.web ? Object.keys(config.web.auth.roles) : [];
93
+ const httpRoles = shapeRequestsHttp && config.http ? Object.keys(config.http.auth.roles) : [];
94
+ const allRoles = Array.from(new Set([...webRoles, ...httpRoles]));
95
+ if (allRoles.length > 0 && !allRoles.includes(opts.role)) {
96
+ const detail: string[] = [];
97
+ if (webRoles.length > 0) detail.push(`web roles: ${webRoles.join(', ')}`);
98
+ if (httpRoles.length > 0) detail.push(`http roles: ${httpRoles.join(', ')}`);
99
+ console.error(
100
+ `[xera:auth-setup] unknown role '${opts.role}' — configured roles: ${allRoles.join(', ')}\n` +
101
+ ` (${detail.join('; ')})`,
102
+ );
103
+ return 1;
104
+ }
105
+ }
106
+
47
107
  // Web roles
48
108
  if (
49
109
  (opts.shape === 'all' || opts.shape === 'web') &&
@@ -120,7 +120,7 @@ const CoverageSchema = z
120
120
  .prefault({});
121
121
 
122
122
  export const XeraConfigSchema = z
123
- .object({
123
+ .strictObject({
124
124
  jira: JiraSchema,
125
125
  web: WebSchema.optional(),
126
126
  http: HttpSchema.optional(),