git_policy_cli/dependent_version_paths.rs
1//! What: Which repository paths the dependent-version planner reads: workspace manifests,
2//! the registry configuration, and a package's non-test source files.
3//! Why: The planner selects these from the tracked-path list itself, so the provider needs
4//! no pathspec support. The rules are the incumbent's: `isWorkspaceManifestPath`
5//! (`dependent-version-bump-policy.ts:66-73`), the manifest pathspec
6//! `:(glob)package/*/*/package.json`, `PNPR_CONFIG_PATH`
7//! (`publishable-names.ts:10`) and `isNonTestSourcePath` (`source-imports.ts:38-55`).
8//!
9//! In TS you'd write (pseudocode):
10//! ```ts
11//! // isWorkspaceManifestPath(path); packageDirectory(manifestPath); isNonTestSourcePath({ directory, path });
12//! ```
13
14/// What: The generated registry configuration whose OIDC package list is the publish set.
15/// `&[u8]` is a borrowed list of bytes baked into the program.
16/// Why: Only packages listed there receive a dependent bump.
17///
18/// In TS you'd write (pseudocode):
19/// ```ts
20/// export const PNPR_CONFIG_PATH = 'package/config/pnpr/config.yaml';
21/// ```
22pub const PNPR_CONFIG_PATH: &[u8] = b"package/config/pnpr/config.yaml";
23
24/// What: The file name every workspace manifest has, with the separator before it.
25/// Why: A package's directory is its manifest path without this suffix.
26///
27/// In TS you'd write (pseudocode):
28/// ```ts
29/// const MANIFEST_SUFFIX = '/package.json';
30/// ```
31const MANIFEST_SUFFIX: &[u8] = b"/package.json";
32
33/// What: Extensions of source files a bundler follows imports from.
34/// Why: A development dependency counts as bundled only when such a file imports it.
35///
36/// In TS you'd write (pseudocode):
37/// ```ts
38/// const SOURCE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];
39/// ```
40const SOURCE_EXTENSIONS: &[&[u8]] = &[
41 b".ts", b".tsx", b".mts", b".cts", b".js", b".jsx", b".mjs", b".cjs",
42];
43
44/// What: The file-name segment that marks a test file.
45/// Why: Test files are not bundled, so their imports decide nothing.
46///
47/// In TS you'd write (pseudocode):
48/// ```ts
49/// const TEST_MARKER = '.test.';
50/// ```
51const TEST_MARKER: &[u8] = b".test.";
52
53/// What: Whether a path is `package/<category>/<name>/package.json`. `.split(...)` yields the
54/// `/`-separated segments; `Vec<&[u8]>` collects borrowed views of them.
55/// Why: Only such a manifest is a workspace package; the incumbent's candidate test and
56/// its tracked-file pathspec both select exactly these paths.
57///
58/// In TS you'd write (pseudocode):
59/// ```ts
60/// const segments = path.split('/'); return segments.length === 4 && segments[0] === 'package' && segments[3] === 'package.json';
61/// ```
62pub fn is_workspace_manifest_path(path: &[u8]) -> bool {
63 let segments: Vec<&[u8]> = path.split(|byte: &u8| return *byte == b'/').collect();
64 // `matches!` tests a value against a pattern; the slice pattern names all four segments.
65 return matches!(segments.as_slice(), [b"package", _, _, b"package.json"]);
66}
67
68/// What: The package directory of a workspace manifest path. `.strip_suffix(...)` returns
69/// the bytes before the suffix, or nothing when the path does not end with it.
70/// Why: Source files are looked up under `<directory>/src/`.
71///
72/// In TS you'd write (pseudocode):
73/// ```ts
74/// const directory = manifestPath.slice(0, -'/package.json'.length);
75/// ```
76pub fn package_directory(manifest_path: &[u8]) -> &[u8] {
77 return manifest_path
78 .strip_suffix(MANIFEST_SUFFIX)
79 .unwrap_or(manifest_path);
80}
81
82/// What: Whether a path is non-test source of the package in `directory`.
83/// Why: The file must sit under the package's `src/`, end with a source extension, and
84/// its name must not contain `.test.`. A sibling directory sharing a prefix
85/// (`package/module/ab` for `package/module/a`) does not count.
86///
87/// In TS you'd write (pseudocode):
88/// ```ts
89/// return path.startsWith(`${directory}/src/`) && SOURCE_EXTENSIONS.some(e => name.endsWith(e)) && !name.includes('.test.');
90/// ```
91pub fn is_non_test_source_path(directory: &[u8], path: &[u8]) -> bool {
92 // `[a, b].concat()` joins two byte lists into one owned list.
93 let prefix: Vec<u8> = [directory, b"/src/"].concat();
94 if !path.starts_with(&prefix) {
95 return false;
96 }
97 // `.rsplit(...)` yields segments from the end, so its first item is the file name.
98 let name: &[u8] = path
99 .rsplit(|byte: &u8| return *byte == b'/')
100 .next()
101 .unwrap_or(path);
102 let has_extension: bool = SOURCE_EXTENSIONS
103 .iter()
104 .any(|extension: &&[u8]| return name.ends_with(extension));
105 // `.windows(n)` yields every run of `n` consecutive bytes.
106 let is_test: bool = name
107 .windows(TEST_MARKER.len())
108 .any(|window: &[u8]| return window == TEST_MARKER);
109 return has_extension && !is_test;
110}
111
112/// Manifest, configuration and source path selection.
113#[cfg(test)]
114#[path = "dependent_version_paths_tests.rs"]
115mod tests;