Skip to main content

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;