@tiden/reporter-commons 0.2.0 → 0.3.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ - **`resolveFilePath` and `isUsableFilePath` on the `/internal` entry point.** The rule behind
6
+ every reporter's `fields.file_path`, which is the key the server joins a requirement's
7
+ repo-relative `repo_file` anchors against. Deliberately NOT `normalizeSpecPath`: that one
8
+ returns a path outside the root unchanged, which is right for a signature segment and wrong
9
+ for a join key — an absolute machine path or a virtual module id can never match an anchor,
10
+ so it would be a field that looks like a join key and silently never joins. `undefined`
11
+ means omit; omitting is not fabricating.
12
+ - **`resolveFilePath` resolves the root rather than trusting the string.** A relative root
13
+ (`..`, `.`, or `''` from a config file) matched no absolute test path, so every file was
14
+ omitted for the whole run while the warning told the user to set the variable they had just
15
+ set. Relative roots now resolve against cwd, `.`/`..` segments are collapsed on both sides,
16
+ and a symlinked root — `/tmp/x` while the runner emits `/private/tmp/x/...` on macOS — is
17
+ compared again via `realpath`, memoized and only after the plain comparison fails.
18
+ `tiden-phpunit-reporter`'s `FilePathResolver` already did this for the same macOS reason.
19
+ - **`normalizeSpecPath` and `resolveRootDir` are deliberately unchanged.** They feed the case
20
+ SIGNATURE, and changing how a root resolves would change the file segment of every signature
21
+ computed under a relative or symlinked root — forking the history of every case already
22
+ reported under the old spelling. A join key can be corrected; an identity cannot.
23
+
3
24
  ## 0.2.0
4
25
 
5
26
  - **`rootDir` / `TIDEN_ROOT_DIR` option**, plus `normalizeSpecPath` and `resolveRootDir` on the
@@ -8,3 +8,5 @@ export { filterPositiveIds } from './filter-positive-ids';
8
8
  export { normalizeSuitePart } from './suite-normalizer';
9
9
  export { normalizeSpecPath } from './spec-path';
10
10
  export { resolveRootDir } from './spec-path';
11
+ export { resolveFilePath } from './spec-path';
12
+ export { isUsableFilePath } from './spec-path';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveRootDir = exports.normalizeSpecPath = exports.normalizeSuitePart = exports.filterPositiveIds = exports.parseTidenIdsFromString = exports.getFile = exports.extractAndCleanStep = exports.removeTidenIdsFromTitle = void 0;
3
+ exports.isUsableFilePath = exports.resolveFilePath = exports.resolveRootDir = exports.normalizeSpecPath = exports.normalizeSuitePart = exports.filterPositiveIds = exports.parseTidenIdsFromString = exports.getFile = exports.extractAndCleanStep = exports.removeTidenIdsFromTitle = void 0;
4
4
  var title_1 = require("./title");
5
5
  Object.defineProperty(exports, "removeTidenIdsFromTitle", { enumerable: true, get: function () { return title_1.removeTidenIdsFromTitle; } });
6
6
  var step_parser_1 = require("./step-parser");
@@ -17,3 +17,7 @@ var spec_path_1 = require("./spec-path");
17
17
  Object.defineProperty(exports, "normalizeSpecPath", { enumerable: true, get: function () { return spec_path_1.normalizeSpecPath; } });
18
18
  var spec_path_2 = require("./spec-path");
19
19
  Object.defineProperty(exports, "resolveRootDir", { enumerable: true, get: function () { return spec_path_2.resolveRootDir; } });
20
+ var spec_path_3 = require("./spec-path");
21
+ Object.defineProperty(exports, "resolveFilePath", { enumerable: true, get: function () { return spec_path_3.resolveFilePath; } });
22
+ var spec_path_4 = require("./spec-path");
23
+ Object.defineProperty(exports, "isUsableFilePath", { enumerable: true, get: function () { return spec_path_4.isUsableFilePath; } });
@@ -10,3 +10,60 @@ export declare function normalizeSpecPath(fullPath: string, cwd?: string): strin
10
10
  * own environment variable.
11
11
  */
12
12
  export declare function resolveRootDir(explicit?: string | undefined): string | undefined;
13
+ /**
14
+ * The repo-relative source file of a test, for the `file_path` result field.
15
+ *
16
+ * Held to a stricter standard than `normalizeSpecPath`, which returns a path
17
+ * outside the root unchanged. `file_path` is the key the server joins a
18
+ * requirement's repo-relative `repo_file` anchors against, so an absolute
19
+ * machine path — or a virtual module id — can never match one: reporting it
20
+ * yields a field that looks like a join key and silently never joins.
21
+ * `undefined` means "omit the field", and the caller warns. Omitting is not
22
+ * fabricating; it is what the Go CLI's filemap and the PHPUnit reporter's
23
+ * FilePathResolver already do.
24
+ *
25
+ * Unlike `normalizeSpecPath` this is NOT purely path-shaped, because the root
26
+ * is user-supplied and three plausible spellings of the same directory would
27
+ * otherwise turn the feature off for a whole run:
28
+ * - a RELATIVE root (`..`, `.`, `''` from a config file) matches no absolute
29
+ * test path, so every file is omitted and the warning tells the user to set
30
+ * the variable they just set. Resolved against cwd.
31
+ * - UNNORMALIZED segments (`/repo/tests/../src/a.ts`) pass through verbatim
32
+ * and can never match an anchor. Collapsed.
33
+ * - a SYMLINKED root — `/tmp/checkout` on macOS while the runner emits
34
+ * `/private/tmp/checkout/...` — mismatches as a string. Compared again via
35
+ * realpath, and only when the plain comparison already failed, so the happy
36
+ * path touches the filesystem at most once per distinct root.
37
+ * `repos/phpunit`'s FilePathResolver calls realpath() on both sides for exactly
38
+ * the macOS /tmp reason; this brings the JS rule in line with it.
39
+ *
40
+ * Deliberately NOT applied to `normalizeSpecPath`/`resolveRootDir`: those feed
41
+ * the case SIGNATURE, and changing how a root resolves would change the file
42
+ * segment of every signature computed under a relative or symlinked root —
43
+ * forking the history of every case already reported under the old spelling.
44
+ * A join key can be corrected; an identity cannot.
45
+ *
46
+ * Note what this still cannot detect: a path that resolves cleanly against the
47
+ * WRONG root. Playwright run from `tests/api` with no `TIDEN_ROOT_DIR` yields
48
+ * `Tests/v1/x.api.spec.ts` — well-formed, matching no anchor. Set
49
+ * `rootDir`/`TIDEN_ROOT_DIR` to the repository root wherever the runner's cwd
50
+ * is not already it.
51
+ */
52
+ export declare function resolveFilePath(fullPath: string, root?: string): string | undefined;
53
+ /**
54
+ * Whether a `file_path` the TEST set for itself could ever match a requirement
55
+ * anchor.
56
+ *
57
+ * The reporters keep a test-provided `file_path` — it may deliberately name the
58
+ * source file under test rather than the spec's own location — but the value is
59
+ * held to the same standard a derived one is: anchors are repo-relative, so an
60
+ * absolute path, an empty string, or one that escapes the root is unlinkable by
61
+ * construction. Keeping such a value would let a hand-written field do the one
62
+ * thing the resolver refuses to do, which is fabricate a link that never joins.
63
+ *
64
+ * This deliberately does NOT decide the larger question of whether a test
65
+ * should be able to override `file_path` at all — `tiden-phpunit-reporter`'s
66
+ * AttributeReader drops a hand-written `#[Field('file_path')]` outright. That
67
+ * divergence is a product decision, recorded in the PR, not settled here.
68
+ */
69
+ export declare function isUsableFilePath(value: string): boolean;
@@ -1,7 +1,44 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.normalizeSpecPath = normalizeSpecPath;
4
37
  exports.resolveRootDir = resolveRootDir;
38
+ exports.resolveFilePath = resolveFilePath;
39
+ exports.isUsableFilePath = isUsableFilePath;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
5
42
  const env_enum_1 = require("../env/env-enum");
6
43
  /**
7
44
  * Normalizes an absolute spec-file path to a project-relative one, for use as
@@ -53,3 +90,138 @@ function resolveRootDir(explicit) {
53
90
  const fromEnv = process.env[env_enum_1.EnvEnum.rootDir];
54
91
  return explicit ?? (fromEnv !== undefined && fromEnv !== '' ? fromEnv : undefined);
55
92
  }
93
+ /**
94
+ * The repo-relative source file of a test, for the `file_path` result field.
95
+ *
96
+ * Held to a stricter standard than `normalizeSpecPath`, which returns a path
97
+ * outside the root unchanged. `file_path` is the key the server joins a
98
+ * requirement's repo-relative `repo_file` anchors against, so an absolute
99
+ * machine path — or a virtual module id — can never match one: reporting it
100
+ * yields a field that looks like a join key and silently never joins.
101
+ * `undefined` means "omit the field", and the caller warns. Omitting is not
102
+ * fabricating; it is what the Go CLI's filemap and the PHPUnit reporter's
103
+ * FilePathResolver already do.
104
+ *
105
+ * Unlike `normalizeSpecPath` this is NOT purely path-shaped, because the root
106
+ * is user-supplied and three plausible spellings of the same directory would
107
+ * otherwise turn the feature off for a whole run:
108
+ * - a RELATIVE root (`..`, `.`, `''` from a config file) matches no absolute
109
+ * test path, so every file is omitted and the warning tells the user to set
110
+ * the variable they just set. Resolved against cwd.
111
+ * - UNNORMALIZED segments (`/repo/tests/../src/a.ts`) pass through verbatim
112
+ * and can never match an anchor. Collapsed.
113
+ * - a SYMLINKED root — `/tmp/checkout` on macOS while the runner emits
114
+ * `/private/tmp/checkout/...` — mismatches as a string. Compared again via
115
+ * realpath, and only when the plain comparison already failed, so the happy
116
+ * path touches the filesystem at most once per distinct root.
117
+ * `repos/phpunit`'s FilePathResolver calls realpath() on both sides for exactly
118
+ * the macOS /tmp reason; this brings the JS rule in line with it.
119
+ *
120
+ * Deliberately NOT applied to `normalizeSpecPath`/`resolveRootDir`: those feed
121
+ * the case SIGNATURE, and changing how a root resolves would change the file
122
+ * segment of every signature computed under a relative or symlinked root —
123
+ * forking the history of every case already reported under the old spelling.
124
+ * A join key can be corrected; an identity cannot.
125
+ *
126
+ * Note what this still cannot detect: a path that resolves cleanly against the
127
+ * WRONG root. Playwright run from `tests/api` with no `TIDEN_ROOT_DIR` yields
128
+ * `Tests/v1/x.api.spec.ts` — well-formed, matching no anchor. Set
129
+ * `rootDir`/`TIDEN_ROOT_DIR` to the repository root wherever the runner's cwd
130
+ * is not already it.
131
+ */
132
+ function resolveFilePath(fullPath, root = process.cwd()) {
133
+ const file = collapse(fullPath);
134
+ const base = collapse(absoluteRoot(root));
135
+ const direct = relativeUnder(file, base);
136
+ if (direct !== undefined) {
137
+ return direct;
138
+ }
139
+ // Only now pay for the filesystem: a symlinked root (or file) is the one
140
+ // remaining way two spellings of the same location can differ as strings.
141
+ const realBase = collapse(realPathOrSelf(base));
142
+ const realFile = collapse(realPathOrSelf(file));
143
+ if (realBase === base && realFile === file) {
144
+ return undefined;
145
+ }
146
+ return relativeUnder(realFile, realBase);
147
+ }
148
+ /** `full` expressed relative to `base`, or undefined when it is not under it. */
149
+ function relativeUnder(full, base) {
150
+ const prefix = rootPrefix(base);
151
+ if (!full.startsWith(prefix)) {
152
+ return undefined;
153
+ }
154
+ const relative = full.slice(prefix.length);
155
+ return relative === '' ? undefined : relative;
156
+ }
157
+ /**
158
+ * Backslashes to forward slashes, then `.`/`..` segments collapsed.
159
+ * `path.posix` regardless of host: the string is already slash-normalized, and
160
+ * a Windows path handed to a POSIX runner (or the reverse) must collapse the
161
+ * same way for the fixtures to mean anything.
162
+ */
163
+ function collapse(value) {
164
+ return path.posix.normalize(value.replace(/\\/g, '/'));
165
+ }
166
+ /**
167
+ * A relative root resolved against cwd; an already-absolute one left alone.
168
+ *
169
+ * `path.resolve` is host-aware, so it cannot be applied unconditionally: on a
170
+ * POSIX runner it would turn the absolute Windows root `C:/repo` into
171
+ * `<cwd>/C:/repo`. The drive-letter test keeps that spelling absolute on both
172
+ * hosts. An empty root is relative by this test, so it resolves to cwd — which
173
+ * is what `normalizeSpecPath` already does with a falsy root, and closes the
174
+ * gap where `""` reported the absolute path minus its leading slash.
175
+ */
176
+ function absoluteRoot(root) {
177
+ const slashed = root.replace(/\\/g, '/');
178
+ return isAbsoluteLike(slashed) ? slashed : path.resolve(root);
179
+ }
180
+ function isAbsoluteLike(value) {
181
+ return value.charCodeAt(0) === SLASH || /^[A-Za-z]:\//.test(value);
182
+ }
183
+ /** realpath, memoized per input; the input itself when it does not resolve. */
184
+ const realPathCache = new Map();
185
+ function realPathOrSelf(value) {
186
+ const cached = realPathCache.get(value);
187
+ if (cached !== undefined) {
188
+ return cached;
189
+ }
190
+ let resolved = value;
191
+ try {
192
+ resolved = fs.realpathSync(value);
193
+ }
194
+ catch {
195
+ // Not on disk (a virtual module id, a deleted file, a root that does not
196
+ // exist). The raw string is the honest answer — same fall-back the PHP
197
+ // resolver takes when realpath() returns false.
198
+ }
199
+ realPathCache.set(value, resolved);
200
+ return resolved;
201
+ }
202
+ /**
203
+ * Whether a `file_path` the TEST set for itself could ever match a requirement
204
+ * anchor.
205
+ *
206
+ * The reporters keep a test-provided `file_path` — it may deliberately name the
207
+ * source file under test rather than the spec's own location — but the value is
208
+ * held to the same standard a derived one is: anchors are repo-relative, so an
209
+ * absolute path, an empty string, or one that escapes the root is unlinkable by
210
+ * construction. Keeping such a value would let a hand-written field do the one
211
+ * thing the resolver refuses to do, which is fabricate a link that never joins.
212
+ *
213
+ * This deliberately does NOT decide the larger question of whether a test
214
+ * should be able to override `file_path` at all — `tiden-phpunit-reporter`'s
215
+ * AttributeReader drops a hand-written `#[Field('file_path')]` outright. That
216
+ * divergence is a product decision, recorded in the PR, not settled here.
217
+ */
218
+ function isUsableFilePath(value) {
219
+ const collapsed = collapse(value);
220
+ if (collapsed === '' || collapsed === '.' || collapsed === '..') {
221
+ return false;
222
+ }
223
+ if (isAbsoluteLike(collapsed)) {
224
+ return false;
225
+ }
226
+ return !collapsed.startsWith('../');
227
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiden/reporter-commons",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Tiden JS Reporters",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",