intershell 0.6.1 → 0.6.3

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.
Files changed (64) hide show
  1. package/README.md +2 -0
  2. package/dist/compose/compose.d.ts.map +1 -1
  3. package/dist/compose/compose.js +2 -2
  4. package/dist/compose/compose.js.map +1 -1
  5. package/dist/compose/compose.test.js +33 -32
  6. package/dist/compose/compose.test.js.map +1 -1
  7. package/dist/intershell-config/intershell-config.d.ts.map +1 -1
  8. package/dist/intershell-config/intershell-config.default.js +1 -1
  9. package/dist/intershell-config/intershell-config.default.js.map +1 -1
  10. package/dist/intershell-config/intershell-config.js +5 -21
  11. package/dist/intershell-config/intershell-config.js.map +1 -1
  12. package/dist/package/index.d.ts +1 -0
  13. package/dist/package/index.d.ts.map +1 -1
  14. package/dist/package/index.js +1 -0
  15. package/dist/package/index.js.map +1 -1
  16. package/dist/package/package-test-mock.d.ts +2 -0
  17. package/dist/package/package-test-mock.d.ts.map +1 -0
  18. package/dist/package/package-test-mock.js +28 -0
  19. package/dist/package/package-test-mock.js.map +1 -0
  20. package/dist/package/package.d.ts +1 -1
  21. package/dist/package/package.d.ts.map +1 -1
  22. package/dist/package/package.js +14 -49
  23. package/dist/package/package.js.map +1 -1
  24. package/dist/package/package.test.js +93 -56
  25. package/dist/package/package.test.js.map +1 -1
  26. package/dist/package/package.types.d.ts +3 -1
  27. package/dist/package/package.types.d.ts.map +1 -1
  28. package/dist/package/workspace-discovery.d.ts +18 -0
  29. package/dist/package/workspace-discovery.d.ts.map +1 -0
  30. package/dist/package/workspace-discovery.js +166 -0
  31. package/dist/package/workspace-discovery.js.map +1 -0
  32. package/dist/package/workspace-discovery.test.d.ts +2 -0
  33. package/dist/package/workspace-discovery.test.d.ts.map +1 -0
  34. package/dist/package/workspace-discovery.test.js +27 -0
  35. package/dist/package/workspace-discovery.test.js.map +1 -0
  36. package/dist/package-commits/dependency-analyzer.d.ts +1 -3
  37. package/dist/package-commits/dependency-analyzer.d.ts.map +1 -1
  38. package/dist/package-commits/dependency-analyzer.js +21 -35
  39. package/dist/package-commits/dependency-analyzer.js.map +1 -1
  40. package/dist/package-commits/package-commits.test.js +12 -7
  41. package/dist/package-commits/package-commits.test.js.map +1 -1
  42. package/dist/package-tags/package-tags.js +1 -1
  43. package/dist/package-tags/package-tags.js.map +1 -1
  44. package/dist/package-tags/package-tags.test.js +24 -20
  45. package/dist/package-tags/package-tags.test.js.map +1 -1
  46. package/dist/package-version/package-version.test.js +43 -34
  47. package/dist/package-version/package-version.test.js.map +1 -1
  48. package/package.json +2 -2
  49. package/src/compose/compose.test.ts +36 -35
  50. package/src/compose/compose.ts +2 -4
  51. package/src/intershell-config/intershell-config.default.ts +1 -1
  52. package/src/intershell-config/intershell-config.ts +8 -23
  53. package/src/package/index.ts +1 -0
  54. package/src/package/package-test-mock.ts +33 -0
  55. package/src/package/package.test.ts +93 -56
  56. package/src/package/package.ts +27 -55
  57. package/src/package/package.types.ts +1 -1
  58. package/src/package/workspace-discovery.test.ts +51 -0
  59. package/src/package/workspace-discovery.ts +219 -0
  60. package/src/package-commits/dependency-analyzer.ts +61 -40
  61. package/src/package-commits/package-commits.test.ts +15 -7
  62. package/src/package-tags/package-tags.test.ts +27 -20
  63. package/src/package-tags/package-tags.ts +1 -1
  64. package/src/package-version/package-version.test.ts +48 -35
@@ -0,0 +1,51 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ packageNameFromAbsolutePath,
4
+ parseWorkspacePatterns,
5
+ type WorkspacePackageEntry,
6
+ } from "./workspace-discovery";
7
+
8
+ describe("workspace-discovery", () => {
9
+ test("parseWorkspacePatterns handles array format", () => {
10
+ expect(parseWorkspacePatterns(["apps/*", "packages/*"])).toEqual(["apps/*", "packages/*"]);
11
+ });
12
+
13
+ test("parseWorkspacePatterns handles object format", () => {
14
+ expect(parseWorkspacePatterns({ packages: ["tools/*"] })).toEqual(["tools/*"]);
15
+ });
16
+
17
+ test("parseWorkspacePatterns returns empty for missing workspaces", () => {
18
+ expect(parseWorkspacePatterns(undefined)).toEqual([]);
19
+ });
20
+
21
+ test("packageNameFromAbsolutePath resolves workspace package from path", () => {
22
+ const workspacePackages: WorkspacePackageEntry[] = [
23
+ { relativePath: "packages/ui", name: "@packages/ui" },
24
+ { relativePath: "apps/api", name: "@apps/api" },
25
+ ];
26
+
27
+ expect(
28
+ packageNameFromAbsolutePath(
29
+ "/workspace/packages/ui/src/index.ts",
30
+ "/workspace",
31
+ workspacePackages,
32
+ ),
33
+ ).toBe("@packages/ui");
34
+
35
+ expect(
36
+ packageNameFromAbsolutePath(
37
+ "/workspace/apps/api/Dockerfile",
38
+ "/workspace",
39
+ workspacePackages,
40
+ ),
41
+ ).toBe("@apps/api");
42
+ });
43
+
44
+ test("packageNameFromAbsolutePath returns null for paths outside workspace", () => {
45
+ expect(
46
+ packageNameFromAbsolutePath("/other/path", "/workspace", [
47
+ { relativePath: "packages/ui", name: "@packages/ui" },
48
+ ]),
49
+ ).toBeNull();
50
+ });
51
+ });
@@ -0,0 +1,219 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import type { PackageJson } from "./package.types";
4
+
5
+ export interface WorkspacePackageEntry {
6
+ readonly relativePath: string;
7
+ readonly name: string;
8
+ }
9
+
10
+ /** Scoped workspace package: `@scope/name` → filesystem path `scope/name` */
11
+ export function getWorkspacePackagePath(packageName: string): string | null {
12
+ if (!packageName.startsWith("@") || !packageName.includes("/")) return null;
13
+ return packageName.slice(1);
14
+ }
15
+
16
+ /** Last path segment of a scoped package (e.g. `@packages/ui` → `ui`) for service name matching */
17
+ export function stripWorkspaceScope(packageName: string): string {
18
+ const workspacePath = getWorkspacePackagePath(packageName);
19
+ if (workspacePath === null) return packageName;
20
+ const slashIndex = workspacePath.lastIndexOf("/");
21
+ return slashIndex === -1 ? workspacePath : workspacePath.slice(slashIndex + 1);
22
+ }
23
+
24
+ export function parseWorkspacePatterns(workspaces: PackageJson["workspaces"]): string[] {
25
+ if (!workspaces) return [];
26
+ if (Array.isArray(workspaces)) return workspaces;
27
+ return workspaces.packages ?? [];
28
+ }
29
+
30
+ export function findWorkspaceRootSync(startDir: string = process.cwd()): string {
31
+ let workspaceRoot = startDir;
32
+ while (workspaceRoot !== dirname(workspaceRoot)) {
33
+ if (existsSync(join(workspaceRoot, "package.json"))) return workspaceRoot;
34
+ workspaceRoot = dirname(workspaceRoot);
35
+ }
36
+ return startDir;
37
+ }
38
+
39
+ export function expandWorkspacePatterns(workspaceRoot: string, patterns: string[]): string[] {
40
+ const paths = new Set<string>();
41
+ for (const pattern of patterns) {
42
+ for (const path of expandWorkspacePattern(workspaceRoot, pattern)) {
43
+ paths.add(path);
44
+ }
45
+ }
46
+ return [...paths];
47
+ }
48
+
49
+ function expandWorkspacePattern(workspaceRoot: string, pattern: string): string[] {
50
+ const normalized = pattern.replace(/\\/g, "/");
51
+ if (!normalized.includes("*")) return [normalized];
52
+
53
+ const parts = normalized.split("/");
54
+ const starIndex = parts.findIndex((part) => part === "*");
55
+ if (starIndex === -1) return [normalized];
56
+
57
+ const baseParts = parts.slice(0, starIndex);
58
+ const suffixParts = parts.slice(starIndex + 1);
59
+ const baseDir = join(workspaceRoot, ...baseParts);
60
+ if (!existsSync(baseDir)) return [];
61
+
62
+ return readdirSync(baseDir, { withFileTypes: true })
63
+ .filter((entry) => entry.isDirectory())
64
+ .flatMap((entry) => {
65
+ const childPattern = [...baseParts, entry.name, ...suffixParts].join("/");
66
+ return expandWorkspacePattern(workspaceRoot, childPattern);
67
+ });
68
+ }
69
+
70
+ export function discoverWorkspacePackagesSync(workspaceRoot: string): WorkspacePackageEntry[] {
71
+ const rootPackageJsonPath = join(workspaceRoot, "package.json");
72
+ if (!existsSync(rootPackageJsonPath)) return [];
73
+
74
+ const rootPackageJson = JSON.parse(readFileSync(rootPackageJsonPath, "utf-8")) as PackageJson;
75
+ const patterns = parseWorkspacePatterns(rootPackageJson.workspaces);
76
+ if (patterns.length === 0) return [];
77
+
78
+ const entries: WorkspacePackageEntry[] = [];
79
+ for (const relativePath of expandWorkspacePatterns(workspaceRoot, patterns)) {
80
+ const packageJsonPath = join(workspaceRoot, relativePath, "package.json");
81
+ if (!existsSync(packageJsonPath)) continue;
82
+
83
+ try {
84
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as PackageJson;
85
+ if (packageJson.name) {
86
+ entries.push({ relativePath, name: packageJson.name });
87
+ }
88
+ } catch {
89
+ // skip invalid package.json
90
+ }
91
+ }
92
+
93
+ return entries;
94
+ }
95
+
96
+ export function discoverWorkspacePackageNamesSync(workspaceRoot: string): string[] {
97
+ return discoverWorkspacePackagesSync(workspaceRoot).map((entry) => entry.name);
98
+ }
99
+
100
+ export function packageNameFromAbsolutePath(
101
+ absolutePath: string,
102
+ workspaceRoot: string,
103
+ workspacePackages: readonly WorkspacePackageEntry[],
104
+ ): string | null {
105
+ const normalizedPath = absolutePath.replace(/\\/g, "/");
106
+ const normalizedRoot = workspaceRoot.replace(/\\/g, "/").replace(/\/$/, "");
107
+
108
+ if (!normalizedPath.startsWith(normalizedRoot)) return null;
109
+
110
+ const relativePath = normalizedPath.slice(normalizedRoot.length).replace(/^\//, "");
111
+ const sortedEntries = [...workspacePackages].sort(
112
+ (a, b) => b.relativePath.length - a.relativePath.length,
113
+ );
114
+
115
+ for (const entry of sortedEntries) {
116
+ if (relativePath === entry.relativePath || relativePath.startsWith(`${entry.relativePath}/`)) {
117
+ return entry.name;
118
+ }
119
+ }
120
+
121
+ return null;
122
+ }
123
+
124
+ export async function discoverWorkspacePackagesAsync(
125
+ workspaceRoot: string,
126
+ readDirectory: (dirPath: string) => Promise<string[]>,
127
+ canAccessFile: (filePath: string) => Promise<boolean>,
128
+ readFileAsText: (filePath: string) => Promise<string>,
129
+ rootPackageJson: PackageJson,
130
+ ): Promise<WorkspacePackageEntry[]> {
131
+ const patterns = parseWorkspacePatterns(rootPackageJson.workspaces);
132
+ if (patterns.length === 0) return [];
133
+
134
+ const entries: WorkspacePackageEntry[] = [];
135
+ for (const relativePath of await expandWorkspacePatternsAsync(
136
+ workspaceRoot,
137
+ patterns,
138
+ readDirectory,
139
+ )) {
140
+ const packageJsonPath = join(workspaceRoot, relativePath, "package.json");
141
+ try {
142
+ const exists = await canAccessFile(packageJsonPath);
143
+ if (!exists) continue;
144
+
145
+ const content = await readFileAsText(packageJsonPath);
146
+ const packageJson = JSON.parse(content) as PackageJson;
147
+ if (packageJson.name) {
148
+ entries.push({ relativePath, name: packageJson.name });
149
+ }
150
+ } catch {
151
+ // skip invalid package.json
152
+ }
153
+ }
154
+
155
+ return entries;
156
+ }
157
+
158
+ export async function discoverWorkspacePackageNamesAsync(
159
+ workspaceRoot: string,
160
+ readDirectory: (dirPath: string) => Promise<string[]>,
161
+ canAccessFile: (filePath: string) => Promise<boolean>,
162
+ readFileAsText: (filePath: string) => Promise<string>,
163
+ rootPackageJson: PackageJson,
164
+ ): Promise<string[]> {
165
+ const entries = await discoverWorkspacePackagesAsync(
166
+ workspaceRoot,
167
+ readDirectory,
168
+ canAccessFile,
169
+ readFileAsText,
170
+ rootPackageJson,
171
+ );
172
+ return entries.map((entry) => entry.name);
173
+ }
174
+
175
+ async function expandWorkspacePatternsAsync(
176
+ workspaceRoot: string,
177
+ patterns: string[],
178
+ readDirectory: (dirPath: string) => Promise<string[]>,
179
+ ): Promise<string[]> {
180
+ const paths = new Set<string>();
181
+ for (const pattern of patterns) {
182
+ for (const path of await expandWorkspacePatternAsync(workspaceRoot, pattern, readDirectory)) {
183
+ paths.add(path);
184
+ }
185
+ }
186
+ return [...paths];
187
+ }
188
+
189
+ async function expandWorkspacePatternAsync(
190
+ workspaceRoot: string,
191
+ pattern: string,
192
+ readDirectory: (dirPath: string) => Promise<string[]>,
193
+ ): Promise<string[]> {
194
+ const normalized = pattern.replace(/\\/g, "/");
195
+ if (!normalized.includes("*")) return [normalized];
196
+
197
+ const parts = normalized.split("/");
198
+ const starIndex = parts.findIndex((part) => part === "*");
199
+ if (starIndex === -1) return [normalized];
200
+
201
+ const baseParts = parts.slice(0, starIndex);
202
+ const suffixParts = parts.slice(starIndex + 1);
203
+ const baseDir = join(workspaceRoot, ...baseParts);
204
+
205
+ let entries: string[] = [];
206
+ try {
207
+ entries = await readDirectory(baseDir);
208
+ } catch {
209
+ return [];
210
+ }
211
+
212
+ const results: string[] = [];
213
+ for (const entry of entries) {
214
+ const childPattern = [...baseParts, entry, ...suffixParts].join("/");
215
+ const expanded = await expandWorkspacePatternAsync(workspaceRoot, childPattern, readDirectory);
216
+ results.push(...expanded);
217
+ }
218
+ return results;
219
+ }
@@ -1,7 +1,15 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { entitiesShell } from "../entities.shell";
4
- import { EntityPackage, type TsConfig, type TsConfigPaths } from "../package";
4
+ import {
5
+ discoverWorkspacePackagesAsync,
6
+ EntityPackage,
7
+ packageNameFromAbsolutePath,
8
+ type TsConfig,
9
+ type TsConfigPaths,
10
+ type WorkspacePackageEntry,
11
+ } from "../package";
12
+ import { packagesShell } from "../package/package.shell";
5
13
 
6
14
  export class EntityDependencyAnalyzer {
7
15
  private readonly package: EntityPackage;
@@ -16,28 +24,48 @@ export class EntityDependencyAnalyzer {
16
24
  */
17
25
  async getPackageDependenciesAtRef(reference: string): Promise<string[]> {
18
26
  try {
19
- // Get all internal packages in the monorepo
20
27
  const allPackages = await EntityPackage.getAllPackages();
28
+ const internalPackages = new Set(allPackages);
29
+ const workspaceContext = await this.getWorkspaceContext();
21
30
 
22
- // Get package.json dependencies
23
- const packageJsonDeps = await this.getPackageJsonDependencies(reference);
24
-
25
- // Get tsconfig dependencies
26
- const tsconfigDeps = await this.getTsConfigDependencies(reference);
31
+ const packageJsonDeps = await this.getPackageJsonDependencies(reference, internalPackages);
32
+ const tsconfigDeps = await this.getTsConfigDependencies(
33
+ reference,
34
+ internalPackages,
35
+ workspaceContext,
36
+ );
27
37
 
28
- // Combine and filter for internal dependencies only
29
38
  const allDeps = [...new Set([...packageJsonDeps, ...tsconfigDeps])];
30
-
31
- return allDeps.filter((dep) => allPackages.includes(dep));
39
+ return allDeps.filter((dep) => internalPackages.has(dep));
32
40
  } catch {
33
41
  return [];
34
42
  }
35
43
  }
36
44
 
45
+ private async getWorkspaceContext(): Promise<{
46
+ readonly workspaceRoot: string;
47
+ readonly workspacePackages: WorkspacePackageEntry[];
48
+ }> {
49
+ const workspaceRoot = await packagesShell.getWorkspaceRoot();
50
+ const rootPackageJson = packagesShell.readJsonFile(`${workspaceRoot}/package.json`);
51
+ const workspacePackages = await discoverWorkspacePackagesAsync(
52
+ workspaceRoot,
53
+ packagesShell.readDirectory,
54
+ packagesShell.canAccessFile,
55
+ packagesShell.readFileAsText,
56
+ rootPackageJson,
57
+ );
58
+
59
+ return { workspaceRoot, workspacePackages };
60
+ }
61
+
37
62
  /**
38
63
  * Get package.json dependencies for a package at a specific reference
39
64
  */
40
- private async getPackageJsonDependencies(reference: string): Promise<string[]> {
65
+ private async getPackageJsonDependencies(
66
+ reference: string,
67
+ internalPackages: ReadonlySet<string>,
68
+ ): Promise<string[]> {
41
69
  try {
42
70
  const result = await entitiesShell.gitShowPackageJsonAtTag(
43
71
  reference,
@@ -61,8 +89,7 @@ export class EntityDependencyAnalyzer {
61
89
  : []),
62
90
  ];
63
91
 
64
- // Filter for @repo/ packages and return without @repo/ prefix
65
- return deps.filter((dep) => dep.startsWith("@repo/")).map((dep) => dep.replace("@repo/", ""));
92
+ return deps.filter((dep) => internalPackages.has(dep));
66
93
  } catch {
67
94
  return [];
68
95
  }
@@ -71,7 +98,14 @@ export class EntityDependencyAnalyzer {
71
98
  /**
72
99
  * Get tsconfig dependencies by resolving paths to actual internal packages
73
100
  */
74
- private async getTsConfigDependencies(reference: string): Promise<string[]> {
101
+ private async getTsConfigDependencies(
102
+ reference: string,
103
+ internalPackages: ReadonlySet<string>,
104
+ workspaceContext: {
105
+ readonly workspaceRoot: string;
106
+ readonly workspacePackages: WorkspacePackageEntry[];
107
+ },
108
+ ): Promise<string[]> {
75
109
  try {
76
110
  const packagePath = this.package.getPath();
77
111
  const tsconfigPaths = await this.getTsConfigPaths(reference);
@@ -79,16 +113,18 @@ export class EntityDependencyAnalyzer {
79
113
  const deps: string[] = [];
80
114
 
81
115
  for (const [alias, paths] of Object.entries(tsconfigPaths)) {
82
- // Check if alias itself is an internal package
83
- if (alias.startsWith("@repo/")) {
84
- deps.push(alias.replace("@repo/", ""));
116
+ if (internalPackages.has(alias)) {
117
+ deps.push(alias);
85
118
  }
86
119
 
87
- // Check if paths point to internal packages
88
120
  if (Array.isArray(paths)) {
89
121
  for (const path of paths) {
90
122
  const resolvedPath = resolve(packagePath, path);
91
- const internalPackage = this.findInternalPackageFromPath(resolvedPath);
123
+ const internalPackage = this.findInternalPackageFromPath(
124
+ resolvedPath,
125
+ workspaceContext.workspaceRoot,
126
+ workspaceContext.workspacePackages,
127
+ );
92
128
  if (internalPackage) {
93
129
  deps.push(internalPackage);
94
130
  }
@@ -102,23 +138,12 @@ export class EntityDependencyAnalyzer {
102
138
  }
103
139
  }
104
140
 
105
- /**
106
- * Find internal package name from absolute path
107
- */
108
- private findInternalPackageFromPath(absolutePath: string): string | null {
109
- // Check if path points to packages/ or apps/ directory
110
- const packagesMatch = absolutePath.match(/\/packages\/([^/]+)/);
111
- const appsMatch = absolutePath.match(/\/apps\/([^/]+)/);
112
-
113
- if (packagesMatch) {
114
- return `@repo/${packagesMatch[1]}`;
115
- }
116
-
117
- if (appsMatch) {
118
- return appsMatch[1];
119
- }
120
-
121
- return null;
141
+ private findInternalPackageFromPath(
142
+ absolutePath: string,
143
+ workspaceRoot: string,
144
+ workspacePackages: readonly WorkspacePackageEntry[],
145
+ ): string | null {
146
+ return packageNameFromAbsolutePath(absolutePath, workspaceRoot, workspacePackages);
122
147
  }
123
148
 
124
149
  /**
@@ -142,14 +167,12 @@ export class EntityDependencyAnalyzer {
142
167
  try {
143
168
  const result = await entitiesShell.gitShow(`${reference}:${this.package.getTsconfigPath()}`);
144
169
  if (result.exitCode !== 0) {
145
- // If tsconfig.json doesn't exist at this reference, return empty config
146
170
  return {};
147
171
  }
148
172
 
149
173
  const content = result.text();
150
174
  return JSON.parse(content);
151
175
  } catch {
152
- // If parsing fails, return empty config
153
176
  return {};
154
177
  }
155
178
  }
@@ -171,10 +194,8 @@ export class EntityDependencyAnalyzer {
171
194
  const extendedContent = readFileSync(extendedPath, "utf-8");
172
195
  const extendedConfig: TsConfig = JSON.parse(extendedContent);
173
196
 
174
- // Recursively resolve extended configs
175
197
  const resolvedExtended = await this.resolveExtendedTsConfig(extendedConfig, packagePath);
176
198
 
177
- // Merge configurations
178
199
  return {
179
200
  ...resolvedExtended,
180
201
  ...config,
@@ -1,10 +1,21 @@
1
- import { describe, expect, test } from "bun:test";
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
2
  import { EntityPackage } from "../package";
3
+ import { installPackagesShellTestMock } from "../package/package-test-mock";
3
4
  import { EntityPackageCommits } from "./package-commits";
4
5
 
5
6
  describe("EntityPackageCommits", () => {
7
+ let restorePackagesShellMock: () => void;
8
+
9
+ beforeEach(() => {
10
+ restorePackagesShellMock = installPackagesShellTestMock();
11
+ });
12
+
13
+ afterEach(() => {
14
+ restorePackagesShellMock();
15
+ });
16
+
6
17
  test("should create instance", () => {
7
- const packageInstance = new EntityPackage("api");
18
+ const packageInstance = new EntityPackage("@apps/api");
8
19
  const commitPackage = new EntityPackageCommits(packageInstance);
9
20
  expect(commitPackage).toBeDefined();
10
21
  });
@@ -16,9 +27,8 @@ describe("EntityPackageCommits", () => {
16
27
  });
17
28
 
18
29
  test("should have main method", () => {
19
- const packageInstance = new EntityPackage("api");
30
+ const packageInstance = new EntityPackage("@apps/api");
20
31
  const commitPackage = new EntityPackageCommits(packageInstance);
21
- // Check that the main method exists
22
32
  expect(typeof commitPackage.getCommitsInRange).toBe("function");
23
33
  });
24
34
 
@@ -26,16 +36,14 @@ describe("EntityPackageCommits", () => {
26
36
  test("should return empty array when git operations fail", async () => {
27
37
  const packageInstance = new EntityPackage("root");
28
38
  const commitPackage = new EntityPackageCommits(packageInstance);
29
- // Test with invalid range to trigger error handling
30
39
  const result = await commitPackage.getCommitsInRange("invalid", "invalid");
31
40
  expect(Array.isArray(result)).toBe(true);
32
41
  expect(result.length).toBe(0);
33
42
  });
34
43
 
35
44
  test("should handle package-specific commits", async () => {
36
- const packageInstance = new EntityPackage("api");
45
+ const packageInstance = new EntityPackage("@apps/api");
37
46
  const commitPackage = new EntityPackageCommits(packageInstance);
38
- // Test with invalid range to trigger error handling
39
47
  const result = await commitPackage.getCommitsInRange("invalid", "invalid");
40
48
  expect(Array.isArray(result)).toBe(true);
41
49
  expect(result.length).toBe(0);
@@ -1,10 +1,21 @@
1
- import { describe, expect, test } from "bun:test";
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
2
  import { EntityPackage } from "../package";
3
+ import { installPackagesShellTestMock } from "../package/package-test-mock";
3
4
  import { EntityPackageTags } from "./package-tags";
4
5
 
5
6
  describe("EntityPackageTags", () => {
7
+ let restorePackagesShellMock: () => void;
8
+
9
+ beforeEach(() => {
10
+ restorePackagesShellMock = installPackagesShellTestMock();
11
+ });
12
+
13
+ afterEach(() => {
14
+ restorePackagesShellMock();
15
+ });
16
+
6
17
  test("should create instance", () => {
7
- const packageInstance = new EntityPackage("api");
18
+ const packageInstance = new EntityPackage("@apps/api");
8
19
  const tagPackage = new EntityPackageTags(packageInstance);
9
20
  expect(tagPackage).toBeDefined();
10
21
  });
@@ -25,49 +36,45 @@ describe("EntityPackageTags", () => {
25
36
  });
26
37
 
27
38
  test("should detect tag prefix for package-specific tags", () => {
28
- const packageInstance = new EntityPackage("api");
39
+ const packageInstance = new EntityPackage("@apps/api");
29
40
  const tagPackage = new EntityPackageTags(packageInstance);
30
41
 
31
- expect(tagPackage.detectTagPrefix("api-v1.0.0")).toBe("api-v");
32
- expect(tagPackage.detectTagPrefix("intershell-v2.1.3")).toBe("intershell-v");
33
- expect(tagPackage.detectTagPrefix("ui-v0.0.1")).toBe("ui-v");
42
+ expect(tagPackage.detectTagPrefix("apps/api-v1.0.0")).toBe("apps/api-v");
43
+ expect(tagPackage.detectTagPrefix("packages/intershell-v2.1.3")).toBe("packages/intershell-v");
44
+ expect(tagPackage.detectTagPrefix("packages/ui-v0.0.1")).toBe("packages/ui-v");
34
45
  });
35
46
 
36
47
  test("should return undefined for invalid tag formats", () => {
37
- const packageInstance = new EntityPackage("api");
48
+ const packageInstance = new EntityPackage("@apps/api");
38
49
  const tagPackage = new EntityPackageTags(packageInstance);
39
50
 
40
51
  expect(tagPackage.detectTagPrefix("1.0.0")).toBeUndefined();
41
- expect(tagPackage.detectTagPrefix("invalid")).toBeUndefined();
52
+ expect(tagPackage.detectTagPrefix("invalid-tag")).toBeUndefined();
42
53
  expect(tagPackage.detectTagPrefix("")).toBeUndefined();
43
54
  });
44
55
 
45
56
  test("should compare versions correctly", () => {
46
- const packageInstance = new EntityPackage("api");
57
+ const packageInstance = new EntityPackage("@apps/api");
47
58
  const tagPackage = new EntityPackageTags(packageInstance);
48
59
 
49
- expect(tagPackage.compareVersions("1.0.0", "1.0.1")).toBe(-1);
50
- expect(tagPackage.compareVersions("1.0.1", "1.0.0")).toBe(1);
51
60
  expect(tagPackage.compareVersions("1.0.0", "1.0.0")).toBe(0);
52
- expect(tagPackage.compareVersions("2.0.0", "1.9.9")).toBe(1);
53
- expect(tagPackage.compareVersions("1.0.0", "2.0.0")).toBe(-1);
61
+ expect(tagPackage.compareVersions("1.1.0", "1.0.0")).toBe(1);
62
+ expect(tagPackage.compareVersions("1.0.0", "1.1.0")).toBe(-1);
54
63
  });
55
64
 
56
65
  test("should handle version comparison with different lengths", () => {
57
- const packageInstance = new EntityPackage("api");
66
+ const packageInstance = new EntityPackage("@apps/api");
58
67
  const tagPackage = new EntityPackageTags(packageInstance);
59
68
 
69
+ expect(tagPackage.compareVersions("1.0.0.1", "1.0.0")).toBe(1);
60
70
  expect(tagPackage.compareVersions("1.0", "1.0.0")).toBe(0);
61
- expect(tagPackage.compareVersions("1.0.0", "1.0")).toBe(0);
62
- expect(tagPackage.compareVersions("1", "1.0.0")).toBe(0);
63
71
  });
64
72
 
65
73
  test("should handle version comparison edge cases", () => {
66
- const packageInstance = new EntityPackage("api");
74
+ const packageInstance = new EntityPackage("@apps/api");
67
75
  const tagPackage = new EntityPackageTags(packageInstance);
68
76
 
69
- expect(tagPackage.compareVersions("0.0.0", "0.0.1")).toBe(-1);
70
- expect(tagPackage.compareVersions("0.1.0", "0.0.9")).toBe(1);
71
- expect(tagPackage.compareVersions("1.0.0", "0.9.9")).toBe(1);
77
+ expect(tagPackage.compareVersions("0.0.1", "0.0.0")).toBe(1);
78
+ expect(tagPackage.compareVersions("10.0.0", "9.0.0")).toBe(1);
72
79
  });
73
80
  });
@@ -188,7 +188,7 @@ export class EntityPackageTags {
188
188
  packageName = matchingPackage;
189
189
  } else {
190
190
  throw new Error(
191
- `Invalid tag prefix format: "${prefix}". Expected format: v (root) or package-name-v (e.g., api-v, intershell-v)`,
191
+ `Invalid tag prefix format: "${prefix}". Expected format: v (root) or <scope>/<name>-v (e.g., apps/api-v, packages/intershell-v)`,
192
192
  );
193
193
  }
194
194