@xera-ai/core 0.12.0 → 0.12.2
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 +28 -5
- package/dist/bin/internal.js +56 -2
- package/dist/src/index.js +1 -1
- package/package.json +3 -3
- package/src/bin-internal/auth-setup.ts +60 -0
- package/src/config/schema.ts +1 -1
package/bin/internal.ts
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { config, parse } from 'dotenv';
|
|
3
4
|
import { run } from '../src/bin-internal/index';
|
|
4
5
|
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
6
|
+
// xera canonicalizes on `.env` (gitignored; see `xera init`, `xera doctor`,
|
|
7
|
+
// scaffolded `.gitignore`). The Bun runtime auto-loads dotenv files BEFORE
|
|
8
|
+
// this script runs, and Bun's precedence puts `.env.local` ahead of `.env` —
|
|
9
|
+
// so a stale value in `.env.local` would silently override the canonical
|
|
10
|
+
// value in `.env` (issue #92, post-#103 followup).
|
|
11
|
+
//
|
|
12
|
+
// Mitigation: warn when `.env.local` exists AND surgically force `.env`'s
|
|
13
|
+
// values to win for any key present in both files. We touch only keys
|
|
14
|
+
// already in `.env.local` so shell-injected and CI-injected env vars
|
|
15
|
+
// (which the user did not put in `.env.local`) stay untouched.
|
|
16
|
+
if (existsSync('.env.local')) {
|
|
17
|
+
console.error(
|
|
18
|
+
'\nwarning: .env.local detected — xera uses .env as the canonical source. ' +
|
|
19
|
+
'Values in .env will be forced to win for any key in both files; ' +
|
|
20
|
+
'merge values into .env and delete .env.local to silence this warning.\n',
|
|
21
|
+
);
|
|
22
|
+
if (existsSync('.env')) {
|
|
23
|
+
const localKeys = Object.keys(parse(readFileSync('.env.local')));
|
|
24
|
+
const envValues = parse(readFileSync('.env'));
|
|
25
|
+
for (const k of localKeys) {
|
|
26
|
+
const v = envValues[k];
|
|
27
|
+
if (v !== undefined) process.env[k] = v;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Safety net for non-Bun invocations (Bun already auto-loaded `.env`).
|
|
9
32
|
config();
|
|
10
33
|
|
|
11
34
|
const code = await run(process.argv.slice(2));
|
package/dist/bin/internal.js
CHANGED
|
@@ -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, readFileSync as readFileSync29 } 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.
|
|
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,20 @@ Commands: ${Object.keys(COMMANDS).join(", ")}`);
|
|
|
13313
13354
|
}
|
|
13314
13355
|
|
|
13315
13356
|
// bin/internal.ts
|
|
13316
|
-
|
|
13357
|
+
if (existsSync33(".env.local")) {
|
|
13358
|
+
console.error(`
|
|
13359
|
+
warning: .env.local detected \u2014 xera uses .env as the canonical source. ` + "Values in .env will be forced to win for any key in both files; " + `merge values into .env and delete .env.local to silence this warning.
|
|
13360
|
+
`);
|
|
13361
|
+
if (existsSync33(".env")) {
|
|
13362
|
+
const localKeys = Object.keys(import_dotenv.parse(readFileSync29(".env.local")));
|
|
13363
|
+
const envValues = import_dotenv.parse(readFileSync29(".env"));
|
|
13364
|
+
for (const k of localKeys) {
|
|
13365
|
+
const v = envValues[k];
|
|
13366
|
+
if (v !== undefined)
|
|
13367
|
+
process.env[k] = v;
|
|
13368
|
+
}
|
|
13369
|
+
}
|
|
13370
|
+
}
|
|
13317
13371
|
import_dotenv.config();
|
|
13318
13372
|
var code = await run(process.argv.slice(2));
|
|
13319
13373
|
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.
|
|
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.
|
|
3
|
+
"version": "0.12.2",
|
|
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.
|
|
35
|
-
"@xera-ai/http": "^0.12.
|
|
34
|
+
"@xera-ai/web": "^0.12.2",
|
|
35
|
+
"@xera-ai/http": "^0.12.2",
|
|
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') &&
|