@tested/cli 0.1.3 → 0.1.5
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/README.md +6 -6
- package/dist/td.js +40 -6
- package/dist/tested.js +40 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ CI uses the composite Action (`tested-hq/cli/action@main`). It installs `@tested
|
|
|
25
25
|
```yaml
|
|
26
26
|
- uses: tested-hq/cli/action@main
|
|
27
27
|
with:
|
|
28
|
-
version: 0.1.
|
|
28
|
+
version: 0.1.5
|
|
29
29
|
token: ${{ secrets.TESTED_TOKEN }}
|
|
30
30
|
```
|
|
31
31
|
|
|
@@ -51,7 +51,7 @@ tested push --pr <n> # needs TESTED_TOKEN
|
|
|
51
51
|
|---------|--------|
|
|
52
52
|
| Ingest token | Prefer `TESTED_TOKEN` / `TESTED_INGEST_TOKEN` / `TESTED_TOKEN_FILE` (mode `0600`). Avoid `--token` on shared hosts — it appears on process argv (`ps`). |
|
|
53
53
|
| Safe run args | In CI, non-interactive shells, or when `TESTED_SAFE_RUN=1`, `tested run` rejects `--watch` / `--watchAll` and `--config` paths outside the repo root. |
|
|
54
|
-
| API URL | Ingest posts only to `https
|
|
54
|
+
| API URL | Ingest posts only to `https://app.tested.dev` / `*.tested.dev` (or `http://localhost`). Other HTTPS hosts need `TESTED_ALLOW_CUSTOM_API_URL=1`. Redirects with Bearer token are refused. |
|
|
55
55
|
| MCP hosts | For always-on agent hosts, set `TESTED_ALLOWED_CWDS` (see `@tested/mcp`) so tools cannot target arbitrary directories. |
|
|
56
56
|
|
|
57
57
|
## Agent loop
|
|
@@ -133,7 +133,7 @@ If `thresholds` is missing from `.tested.yaml`, `tested check` prints a notice o
|
|
|
133
133
|
```yaml
|
|
134
134
|
- uses: tested-hq/cli/action@main
|
|
135
135
|
with:
|
|
136
|
-
version: 0.1.
|
|
136
|
+
version: 0.1.5
|
|
137
137
|
push: true
|
|
138
138
|
pr-number: ${{ github.event.pull_request.number }}
|
|
139
139
|
token: ${{ secrets.TESTED_TOKEN }}
|
|
@@ -167,8 +167,8 @@ $ tested push
|
|
|
167
167
|
| `TESTED_TOKEN` / `TESTED_INGEST_TOKEN` / `TESTED_TOKEN_FILE` | Ingest auth (**preferred** — not on argv) |
|
|
168
168
|
| `--token` | Ingest auth (works; warns on TTY; visible in `ps`) |
|
|
169
169
|
| `--pr` / `GITHUB_PR_NUMBER` / `PR_NUMBER` | PR number (required) |
|
|
170
|
-
| `--url` / `TESTED_API_URL` | API base (default `https://app.tested.dev`) |
|
|
171
|
-
| `--owner` / `--name` | Repo identity (default:
|
|
170
|
+
| `--url` / `TESTED_API_URL` | API base (default `https://app.tested.dev`; other hosts need `TESTED_ALLOW_CUSTOM_API_URL=1`) |
|
|
171
|
+
| `--owner` / `--name` | Repo identity (default: `GITHUB_REPOSITORY`, else `origin` remote) |
|
|
172
172
|
| `--pr-title`, `--author`, `--base-ref`, `--head-ref` | PR metadata overrides |
|
|
173
173
|
| `--base` | Git base for the coverage diff (same as `tested diff --base`) |
|
|
174
174
|
| `--run-url` | Optional CI run URL |
|
|
@@ -185,7 +185,7 @@ non-interactive mode / when `TESTED_SAFE_RUN=1`:
|
|
|
185
185
|
Typical CI step after `tested check`:
|
|
186
186
|
|
|
187
187
|
```yaml
|
|
188
|
-
- run: pnpm exec tested push --pr "${{ github.event.pull_request.number }}"
|
|
188
|
+
- run: pnpm exec tested push --pr "${{ github.event.pull_request.number }}" --base "${{ github.event.pull_request.base.sha }}"
|
|
189
189
|
env:
|
|
190
190
|
TESTED_TOKEN: ${{ secrets.TESTED_TOKEN }}
|
|
191
191
|
```
|
package/dist/td.js
CHANGED
|
@@ -965,7 +965,24 @@ async function computeDiff(opts) {
|
|
|
965
965
|
|
|
966
966
|
// src/commands/push.ts
|
|
967
967
|
var DEFAULT_API_BASE = "https://app.tested.dev";
|
|
968
|
+
var DEFAULT_API_HOST = "app.tested.dev";
|
|
968
969
|
var LOCAL_HTTP_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
970
|
+
var SAFE_GITHUB_REPOSITORY_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
971
|
+
function isAllowedApiHost(hostname) {
|
|
972
|
+
const host = hostname.toLowerCase();
|
|
973
|
+
return host === "tested.dev" || host === DEFAULT_API_HOST || host.endsWith(".tested.dev");
|
|
974
|
+
}
|
|
975
|
+
function isLocalDevHost(hostname) {
|
|
976
|
+
const host = hostname.toLowerCase();
|
|
977
|
+
return LOCAL_HTTP_HOSTS.has(host) || host.endsWith(".localhost");
|
|
978
|
+
}
|
|
979
|
+
function parseGitHubRepository(repo) {
|
|
980
|
+
const raw = repo?.trim();
|
|
981
|
+
if (!raw || !SAFE_GITHUB_REPOSITORY_RE.test(raw)) return null;
|
|
982
|
+
const [owner, name] = raw.split("/");
|
|
983
|
+
if (!owner || !name) return null;
|
|
984
|
+
return { owner, name };
|
|
985
|
+
}
|
|
969
986
|
var tokenArgvWarned = false;
|
|
970
987
|
function readTokenFile(filePath, opts) {
|
|
971
988
|
const read = opts?.readFileSyncFn ?? readFileSync2;
|
|
@@ -1022,7 +1039,7 @@ function resolveToken(opts) {
|
|
|
1022
1039
|
}
|
|
1023
1040
|
return null;
|
|
1024
1041
|
}
|
|
1025
|
-
function assertSafeApiBase(raw) {
|
|
1042
|
+
function assertSafeApiBase(raw, opts = {}) {
|
|
1026
1043
|
const trimmed = raw.trim();
|
|
1027
1044
|
if (!trimmed) {
|
|
1028
1045
|
throw new Error("API URL must not be empty");
|
|
@@ -1039,9 +1056,14 @@ function assertSafeApiBase(raw) {
|
|
|
1039
1056
|
throw new Error("API URL must not embed credentials");
|
|
1040
1057
|
}
|
|
1041
1058
|
const host = u.hostname.toLowerCase();
|
|
1042
|
-
const
|
|
1059
|
+
const local = isLocalDevHost(host);
|
|
1043
1060
|
if (u.protocol === "https:") {
|
|
1044
|
-
|
|
1061
|
+
if (!local && !isAllowedApiHost(host) && !opts.allowCustom) {
|
|
1062
|
+
throw new Error(
|
|
1063
|
+
`API URL host "${u.hostname}" is not allowed. Ingest tokens are sent to this host. Use https://app.tested.dev, or set TESTED_ALLOW_CUSTOM_API_URL=1 for a private endpoint.`
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
} else if (u.protocol === "http:" && local) {
|
|
1045
1067
|
} else {
|
|
1046
1068
|
throw new Error(
|
|
1047
1069
|
`API URL must use https:// (http:// allowed only for localhost). Got ${u.protocol}//${u.host}`
|
|
@@ -1051,12 +1073,16 @@ function assertSafeApiBase(raw) {
|
|
|
1051
1073
|
const pathPart = !path || path === "/" ? "" : path;
|
|
1052
1074
|
return `${u.origin}${pathPart}`;
|
|
1053
1075
|
}
|
|
1076
|
+
function allowCustomApiUrl(env) {
|
|
1077
|
+
const raw = env.TESTED_ALLOW_CUSTOM_API_URL;
|
|
1078
|
+
return raw === "1" || raw === "true";
|
|
1079
|
+
}
|
|
1054
1080
|
function resolveApiBase(opts) {
|
|
1055
1081
|
const env = opts.env ?? process.env;
|
|
1056
1082
|
const flag = opts.flag?.trim();
|
|
1057
1083
|
const fromEnv = env.TESTED_API_URL?.trim();
|
|
1058
1084
|
const raw = flag || fromEnv || DEFAULT_API_BASE;
|
|
1059
|
-
return assertSafeApiBase(raw);
|
|
1085
|
+
return assertSafeApiBase(raw, { allowCustom: allowCustomApiUrl(env) });
|
|
1060
1086
|
}
|
|
1061
1087
|
function redactGitRemote(url) {
|
|
1062
1088
|
const scrubbed = url.replace(/\/\/([^/@\s]+)@/g, "//***@");
|
|
@@ -1403,6 +1429,13 @@ async function executePush(cli, deps) {
|
|
|
1403
1429
|
}
|
|
1404
1430
|
let owner = cli.owner;
|
|
1405
1431
|
let name = cli.name;
|
|
1432
|
+
if (!owner || !name) {
|
|
1433
|
+
const fromActions = parseGitHubRepository(env.GITHUB_REPOSITORY);
|
|
1434
|
+
if (fromActions) {
|
|
1435
|
+
owner = owner ?? fromActions.owner;
|
|
1436
|
+
name = name ?? fromActions.name;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1406
1439
|
if (!owner || !name) {
|
|
1407
1440
|
let origin;
|
|
1408
1441
|
try {
|
|
@@ -1767,7 +1800,8 @@ async function runDoctor(deps) {
|
|
|
1767
1800
|
const rawApi = env.TESTED_API_URL;
|
|
1768
1801
|
if (rawApi !== void 0 && rawApi !== "") {
|
|
1769
1802
|
try {
|
|
1770
|
-
const
|
|
1803
|
+
const allowCustom = env.TESTED_ALLOW_CUSTOM_API_URL === "1" || env.TESTED_ALLOW_CUSTOM_API_URL === "true";
|
|
1804
|
+
const normalized = assertSafe(rawApi, { allowCustom });
|
|
1771
1805
|
checks.push({
|
|
1772
1806
|
id: "api_url",
|
|
1773
1807
|
label: "API URL",
|
|
@@ -1881,7 +1915,7 @@ import pc3 from "picocolors";
|
|
|
1881
1915
|
// package.json
|
|
1882
1916
|
var package_default = {
|
|
1883
1917
|
name: "@tested/cli",
|
|
1884
|
-
version: "0.1.
|
|
1918
|
+
version: "0.1.5",
|
|
1885
1919
|
description: "Coverage your agent can use. CLI for patch + project coverage with agent-readable JSON output.",
|
|
1886
1920
|
license: "MIT",
|
|
1887
1921
|
homepage: "https://tested.dev",
|
package/dist/tested.js
CHANGED
|
@@ -965,7 +965,24 @@ async function computeDiff(opts) {
|
|
|
965
965
|
|
|
966
966
|
// src/commands/push.ts
|
|
967
967
|
var DEFAULT_API_BASE = "https://app.tested.dev";
|
|
968
|
+
var DEFAULT_API_HOST = "app.tested.dev";
|
|
968
969
|
var LOCAL_HTTP_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
970
|
+
var SAFE_GITHUB_REPOSITORY_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
971
|
+
function isAllowedApiHost(hostname) {
|
|
972
|
+
const host = hostname.toLowerCase();
|
|
973
|
+
return host === "tested.dev" || host === DEFAULT_API_HOST || host.endsWith(".tested.dev");
|
|
974
|
+
}
|
|
975
|
+
function isLocalDevHost(hostname) {
|
|
976
|
+
const host = hostname.toLowerCase();
|
|
977
|
+
return LOCAL_HTTP_HOSTS.has(host) || host.endsWith(".localhost");
|
|
978
|
+
}
|
|
979
|
+
function parseGitHubRepository(repo) {
|
|
980
|
+
const raw = repo?.trim();
|
|
981
|
+
if (!raw || !SAFE_GITHUB_REPOSITORY_RE.test(raw)) return null;
|
|
982
|
+
const [owner, name] = raw.split("/");
|
|
983
|
+
if (!owner || !name) return null;
|
|
984
|
+
return { owner, name };
|
|
985
|
+
}
|
|
969
986
|
var tokenArgvWarned = false;
|
|
970
987
|
function readTokenFile(filePath, opts) {
|
|
971
988
|
const read = opts?.readFileSyncFn ?? readFileSync2;
|
|
@@ -1022,7 +1039,7 @@ function resolveToken(opts) {
|
|
|
1022
1039
|
}
|
|
1023
1040
|
return null;
|
|
1024
1041
|
}
|
|
1025
|
-
function assertSafeApiBase(raw) {
|
|
1042
|
+
function assertSafeApiBase(raw, opts = {}) {
|
|
1026
1043
|
const trimmed = raw.trim();
|
|
1027
1044
|
if (!trimmed) {
|
|
1028
1045
|
throw new Error("API URL must not be empty");
|
|
@@ -1039,9 +1056,14 @@ function assertSafeApiBase(raw) {
|
|
|
1039
1056
|
throw new Error("API URL must not embed credentials");
|
|
1040
1057
|
}
|
|
1041
1058
|
const host = u.hostname.toLowerCase();
|
|
1042
|
-
const
|
|
1059
|
+
const local = isLocalDevHost(host);
|
|
1043
1060
|
if (u.protocol === "https:") {
|
|
1044
|
-
|
|
1061
|
+
if (!local && !isAllowedApiHost(host) && !opts.allowCustom) {
|
|
1062
|
+
throw new Error(
|
|
1063
|
+
`API URL host "${u.hostname}" is not allowed. Ingest tokens are sent to this host. Use https://app.tested.dev, or set TESTED_ALLOW_CUSTOM_API_URL=1 for a private endpoint.`
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
} else if (u.protocol === "http:" && local) {
|
|
1045
1067
|
} else {
|
|
1046
1068
|
throw new Error(
|
|
1047
1069
|
`API URL must use https:// (http:// allowed only for localhost). Got ${u.protocol}//${u.host}`
|
|
@@ -1051,12 +1073,16 @@ function assertSafeApiBase(raw) {
|
|
|
1051
1073
|
const pathPart = !path || path === "/" ? "" : path;
|
|
1052
1074
|
return `${u.origin}${pathPart}`;
|
|
1053
1075
|
}
|
|
1076
|
+
function allowCustomApiUrl(env) {
|
|
1077
|
+
const raw = env.TESTED_ALLOW_CUSTOM_API_URL;
|
|
1078
|
+
return raw === "1" || raw === "true";
|
|
1079
|
+
}
|
|
1054
1080
|
function resolveApiBase(opts) {
|
|
1055
1081
|
const env = opts.env ?? process.env;
|
|
1056
1082
|
const flag = opts.flag?.trim();
|
|
1057
1083
|
const fromEnv = env.TESTED_API_URL?.trim();
|
|
1058
1084
|
const raw = flag || fromEnv || DEFAULT_API_BASE;
|
|
1059
|
-
return assertSafeApiBase(raw);
|
|
1085
|
+
return assertSafeApiBase(raw, { allowCustom: allowCustomApiUrl(env) });
|
|
1060
1086
|
}
|
|
1061
1087
|
function redactGitRemote(url) {
|
|
1062
1088
|
const scrubbed = url.replace(/\/\/([^/@\s]+)@/g, "//***@");
|
|
@@ -1403,6 +1429,13 @@ async function executePush(cli, deps) {
|
|
|
1403
1429
|
}
|
|
1404
1430
|
let owner = cli.owner;
|
|
1405
1431
|
let name = cli.name;
|
|
1432
|
+
if (!owner || !name) {
|
|
1433
|
+
const fromActions = parseGitHubRepository(env.GITHUB_REPOSITORY);
|
|
1434
|
+
if (fromActions) {
|
|
1435
|
+
owner = owner ?? fromActions.owner;
|
|
1436
|
+
name = name ?? fromActions.name;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1406
1439
|
if (!owner || !name) {
|
|
1407
1440
|
let origin;
|
|
1408
1441
|
try {
|
|
@@ -1767,7 +1800,8 @@ async function runDoctor(deps) {
|
|
|
1767
1800
|
const rawApi = env.TESTED_API_URL;
|
|
1768
1801
|
if (rawApi !== void 0 && rawApi !== "") {
|
|
1769
1802
|
try {
|
|
1770
|
-
const
|
|
1803
|
+
const allowCustom = env.TESTED_ALLOW_CUSTOM_API_URL === "1" || env.TESTED_ALLOW_CUSTOM_API_URL === "true";
|
|
1804
|
+
const normalized = assertSafe(rawApi, { allowCustom });
|
|
1771
1805
|
checks.push({
|
|
1772
1806
|
id: "api_url",
|
|
1773
1807
|
label: "API URL",
|
|
@@ -1881,7 +1915,7 @@ import pc3 from "picocolors";
|
|
|
1881
1915
|
// package.json
|
|
1882
1916
|
var package_default = {
|
|
1883
1917
|
name: "@tested/cli",
|
|
1884
|
-
version: "0.1.
|
|
1918
|
+
version: "0.1.5",
|
|
1885
1919
|
description: "Coverage your agent can use. CLI for patch + project coverage with agent-readable JSON output.",
|
|
1886
1920
|
license: "MIT",
|
|
1887
1921
|
homepage: "https://tested.dev",
|
package/package.json
CHANGED