@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 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.3
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://` (or `http://localhost`). Redirects with Bearer token are refused. |
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.3
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: parse `origin` remote) |
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 isLocalHttpHost = LOCAL_HTTP_HOSTS.has(host) || host.endsWith(".localhost");
1059
+ const local = isLocalDevHost(host);
1043
1060
  if (u.protocol === "https:") {
1044
- } else if (u.protocol === "http:" && isLocalHttpHost) {
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 normalized = assertSafe(rawApi);
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.3",
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 isLocalHttpHost = LOCAL_HTTP_HOSTS.has(host) || host.endsWith(".localhost");
1059
+ const local = isLocalDevHost(host);
1043
1060
  if (u.protocol === "https:") {
1044
- } else if (u.protocol === "http:" && isLocalHttpHost) {
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 normalized = assertSafe(rawApi);
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.3",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tested/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Coverage your agent can use. CLI for patch + project coverage with agent-readable JSON output.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://tested.dev",