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
@@ -4,7 +4,7 @@ type CustomBunType = {
4
4
  YAML: {
5
5
  parse: (input: string) => Record<string, unknown>;
6
6
  };
7
- file: (path: string) => { text: () => string };
7
+ file: (path: string) => { text: () => Promise<string>; exists: () => Promise<boolean> };
8
8
  };
9
9
 
10
10
  describe("EntityCompose", () => {
@@ -55,13 +55,6 @@ describe("EntityCompose", () => {
55
55
  originalBunFile = (Bun as unknown as CustomBunType).file;
56
56
  });
57
57
 
58
- // Restore mocks after each test
59
- afterEach(() => {
60
- // Restore original Bun methods
61
- (Bun as unknown as CustomBunType).YAML = originalBunYaml;
62
- (Bun as unknown as CustomBunType).file = originalBunFile;
63
- });
64
-
65
58
  // Store original methods to restore after tests
66
59
  let originalEntityPackageGetAllPackages: () => Promise<string[]>;
67
60
  let originalEntityAffectedGetAffectedPackages: (
@@ -104,13 +97,22 @@ describe("EntityCompose", () => {
104
97
  }
105
98
  };
106
99
 
100
+ afterEach(async () => {
101
+ (Bun as unknown as CustomBunType).YAML = originalBunYaml;
102
+ (Bun as unknown as CustomBunType).file = originalBunFile;
103
+ await cleanupMocks();
104
+ });
105
+
107
106
  // Common mock setup for Bun
108
107
  const setupBunMocks = (
109
108
  mockYamlParse: ReturnType<typeof mock>,
110
109
  mockFileText: ReturnType<typeof mock>,
111
110
  ) => {
112
111
  (Bun as unknown as CustomBunType).YAML = { parse: mockYamlParse };
113
- (Bun as unknown as CustomBunType).file = mock(() => ({ text: mockFileText }));
112
+ (Bun as unknown as CustomBunType).file = mock(() => ({
113
+ text: mockFileText,
114
+ exists: () => Promise.resolve(true),
115
+ }));
114
116
  };
115
117
 
116
118
  it("should handle core functionality - parsing, validation, and basic operations", async () => {
@@ -329,8 +331,8 @@ describe("EntityCompose", () => {
329
331
  return defaultYaml;
330
332
  });
331
333
 
332
- // Set up Bun mock for static method testing
333
- (Bun as unknown as CustomBunType).YAML = { parse: mockYamlParse };
334
+ const mockFileText = mock(() => Promise.resolve(""));
335
+ setupBunMocks(mockYamlParse, mockFileText);
334
336
 
335
337
  const yamlInput = `
336
338
  version: "3.9"
@@ -378,6 +380,28 @@ volumes:
378
380
  it("should handle depends_on in object format (long syntax)", async () => {
379
381
  await setupMocks();
380
382
 
383
+ const yamlInput = `
384
+ version: "3.8"
385
+ services:
386
+ app:
387
+ image: node:18
388
+ ports:
389
+ - "3000:3000"
390
+ depends_on:
391
+ postgres:
392
+ condition: service_healthy
393
+ redis:
394
+ condition: service_started
395
+ postgres:
396
+ image: postgres:13
397
+ ports:
398
+ - "5432:5432"
399
+ redis:
400
+ image: redis:alpine
401
+ ports:
402
+ - "6379:6379"
403
+ `;
404
+
381
405
  const mockYamlParse = mock((input: string) => {
382
406
  if (input.includes("service_healthy")) {
383
407
  return {
@@ -411,35 +435,12 @@ volumes:
411
435
  return defaultYaml;
412
436
  });
413
437
 
414
- const mockFileText = mock(() => Promise.resolve("mock yaml content"));
438
+ const mockFileText = mock(() => Promise.resolve(yamlInput));
415
439
  setupBunMocks(mockYamlParse, mockFileText);
416
440
 
417
441
  const { EntityCompose } = await import("./compose");
418
442
  const entityCompose = new EntityCompose("docker-compose.yml");
419
443
 
420
- // Test parsing with object format depends_on
421
- const yamlInput = `
422
- version: "3.8"
423
- services:
424
- app:
425
- image: node:18
426
- ports:
427
- - "3000:3000"
428
- depends_on:
429
- postgres:
430
- condition: service_healthy
431
- redis:
432
- condition: service_started
433
- postgres:
434
- image: postgres:13
435
- ports:
436
- - "5432:5432"
437
- redis:
438
- image: redis:alpine
439
- ports:
440
- - "6379:6379"
441
- `;
442
-
443
444
  const composeData = EntityCompose.parseDockerCompose(yamlInput);
444
445
  expect(composeData.services.app.depends_on).toEqual(["postgres", "redis"]);
445
446
 
@@ -1,5 +1,5 @@
1
1
  import { EntityAffected } from "../affected";
2
- import { EntityPackage } from "../package";
2
+ import { EntityPackage, stripWorkspaceScope } from "../package";
3
3
  import type {
4
4
  ComposeData,
5
5
  ComposeValidationResult,
@@ -158,9 +158,7 @@ export class EntityCompose {
158
158
 
159
159
  // Find services associated with affected packages
160
160
  for (const service of services) {
161
- const associatedPackage = allPackages.find(
162
- (p) => p.replace(/^@repo\//, "") === service.name,
163
- );
161
+ const associatedPackage = allPackages.find((p) => stripWorkspaceScope(p) === service.name);
164
162
 
165
163
  if (keys.some((k: string) => k === associatedPackage)) {
166
164
  affectedServices.add(service.name);
@@ -151,7 +151,7 @@ export const defaultConfig = {
151
151
  },
152
152
  staged: [
153
153
  {
154
- filePattern: [/coverage\/.*/, /dist\/.*/, /node_modules\/.*/, /\.env/, /\.act/],
154
+ filePattern: [/coverage\/.*/, /dist\/.*/, /node_modules\/.*/, /(^|\/)\.env$/, /\.act/],
155
155
  description: "development files should not be manually committed",
156
156
  },
157
157
  {
@@ -1,5 +1,9 @@
1
- import { existsSync, readdirSync, readFileSync } from "node:fs";
1
+ import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import {
4
+ discoverWorkspacePackageNamesSync,
5
+ findWorkspaceRootSync,
6
+ } from "../package/workspace-discovery";
3
7
  import { defaultConfig } from "./intershell-config.default";
4
8
  import type { CustomConfigJson, IConfig } from "./intershell-config.types";
5
9
 
@@ -96,29 +100,10 @@ class Config {
96
100
  const packages: string[] = ["root"];
97
101
 
98
102
  try {
99
- // Get workspace root
100
- const workspaceRoot = process.cwd();
101
-
102
- // Check for apps directory
103
- const appsPath = join(workspaceRoot, "apps");
104
- if (existsSync(appsPath)) {
105
- const apps = readdirSync(appsPath, { withFileTypes: true })
106
- .filter((dirent) => dirent.isDirectory())
107
- .map((dirent) => dirent.name);
108
- packages.push(...apps);
109
- }
110
-
111
- // Check for packages directory
112
- const packagesPath = join(workspaceRoot, "packages");
113
- if (existsSync(packagesPath)) {
114
- const pkgs = readdirSync(packagesPath, { withFileTypes: true })
115
- .filter((dirent) => dirent.isDirectory())
116
- .map((dirent) => `@repo/${dirent.name}`);
117
- packages.push(...pkgs);
118
- }
103
+ const workspaceRoot = findWorkspaceRootSync();
104
+ packages.push(...discoverWorkspacePackageNamesSync(workspaceRoot));
119
105
  } catch {
120
- // If we can't read directories, just return basic packages
121
- packages.push("admin", "api", "storefront", "ui", "utils");
106
+ // If we can't read workspaces, fall back to root only
122
107
  }
123
108
 
124
109
  return packages;
@@ -1,3 +1,4 @@
1
1
  export * from "./package";
2
2
  export * from "./package.shell";
3
3
  export * from "./package.types";
4
+ export * from "./workspace-discovery";
@@ -0,0 +1,33 @@
1
+ import { mock } from "bun:test";
2
+ import { packagesShell } from "./package.shell";
3
+ import type { PackageJson } from "./package.types";
4
+
5
+ const defaultRootPackageJson = (): PackageJson => ({
6
+ name: "root",
7
+ version: "1.0.0",
8
+ private: false,
9
+ });
10
+
11
+ const defaultAppsApiPackageJson = (): PackageJson => ({
12
+ name: "@apps/api",
13
+ version: "0.1.0",
14
+ private: false,
15
+ });
16
+
17
+ export function installPackagesShellTestMock(): () => void {
18
+ const originalReadJsonFile = packagesShell.readJsonFile;
19
+
20
+ packagesShell.readJsonFile = mock((filePath: string) => {
21
+ if (filePath === "./package.json") {
22
+ return defaultRootPackageJson();
23
+ }
24
+ if (filePath === "apps/api/package.json") {
25
+ return defaultAppsApiPackageJson();
26
+ }
27
+ throw new Error(`Unexpected package.json path: ${filePath}`);
28
+ });
29
+
30
+ return () => {
31
+ packagesShell.readJsonFile = originalReadJsonFile;
32
+ };
33
+ }
@@ -226,13 +226,23 @@ describe("EntityPackage", () => {
226
226
  expect(rootPackages.getPath()).toBe(".");
227
227
  });
228
228
 
229
- it("should return packages path for @repo packages", () => {
230
- const repoPackages = new EntityPackage("@repo/test-package");
231
- expect(repoPackages.getPath()).toBe("packages/test-package");
229
+ it("should return packages path for @packages packages", () => {
230
+ const packagesPackage = new EntityPackage("@packages/test-package");
231
+ expect(packagesPackage.getPath()).toBe("packages/test-package");
232
232
  });
233
233
 
234
- it("should return apps path for regular packages", () => {
235
- expect(packages.getPath()).toBe("apps/test-package");
234
+ it("should return apps path for @apps packages", () => {
235
+ const appsPackage = new EntityPackage("@apps/test-app");
236
+ expect(appsPackage.getPath()).toBe("apps/test-app");
237
+ });
238
+
239
+ it("should return tools path for @tools packages", () => {
240
+ const toolsPackage = new EntityPackage("@tools/test-preset");
241
+ expect(toolsPackage.getPath()).toBe("tools/test-preset");
242
+ });
243
+
244
+ it("should return package name as path for unscoped packages", () => {
245
+ expect(packages.getPath()).toBe("test-package");
236
246
  });
237
247
  });
238
248
 
@@ -242,7 +252,7 @@ describe("EntityPackage", () => {
242
252
  });
243
253
 
244
254
  it("should return correct package.json path", () => {
245
- expect(packages.getJsonPath()).toBe("apps/test-package/package.json");
255
+ expect(packages.getJsonPath()).toBe("test-package/package.json");
246
256
  });
247
257
 
248
258
  it("should return correct path for root package", () => {
@@ -250,9 +260,9 @@ describe("EntityPackage", () => {
250
260
  expect(rootPackages.getJsonPath()).toBe("./package.json");
251
261
  });
252
262
 
253
- it("should return correct path for @repo package", () => {
254
- const repoPackages = new EntityPackage("@repo/test-package");
255
- expect(repoPackages.getJsonPath()).toBe("packages/test-package/package.json");
263
+ it("should return correct path for @packages package", () => {
264
+ const packagesPackage = new EntityPackage("@packages/test-package");
265
+ expect(packagesPackage.getJsonPath()).toBe("packages/test-package/package.json");
256
266
  });
257
267
  });
258
268
 
@@ -358,7 +368,7 @@ describe("EntityPackage", () => {
358
368
  });
359
369
 
360
370
  it("should return correct changelog path", () => {
361
- expect(packages.getChangelogPath()).toBe("apps/test-package/CHANGELOG.md");
371
+ expect(packages.getChangelogPath()).toBe("test-package/CHANGELOG.md");
362
372
  });
363
373
  });
364
374
 
@@ -540,14 +550,13 @@ describe("EntityPackage", () => {
540
550
  expect(rootPackages.getTagSeriesName()).toBe("v");
541
551
  });
542
552
 
543
- it.skip("should return 'package-name-v' for @repo packages", () => {
544
- // Mock packagesShell to return @repo package for this test
553
+ it.skip("should return 'packages/package-name-v' for @packages packages", () => {
545
554
  mockPackagesShell.readJsonFile.mockImplementationOnce(() =>
546
- mockPackageJson({ name: "@repo/package-name", private: false }),
555
+ mockPackageJson({ name: "@packages/package-name", private: false }),
547
556
  );
548
557
 
549
- const intershellPackages = new EntityPackage("@repo/package-name");
550
- expect(intershellPackages.getTagSeriesName()).toBe("package-name-v");
558
+ const intershellPackages = new EntityPackage("@packages/package-name");
559
+ expect(intershellPackages.getTagSeriesName()).toBe("packages/package-name-v");
551
560
  });
552
561
 
553
562
  it.skip("should return 'package-name-v' for regular packages", () => {
@@ -675,51 +684,79 @@ describe("EntityPackage", () => {
675
684
 
676
685
  describe("getAllPackages", () => {
677
686
  it("should return list of packages including root", async () => {
678
- // Mock packagesShell methods for this test
679
687
  mockPackagesShell.getWorkspaceRoot.mockResolvedValueOnce("/workspace");
680
- mockPackagesShell.readDirectory
681
- .mockResolvedValueOnce(["test-app", "another-app"]) // apps
682
- .mockResolvedValueOnce(["ui", "utils"]); // packages
683
- mockPackagesShell.canAccessFile
684
- .mockResolvedValueOnce(true) // apps/test-app/package.json
685
- .mockResolvedValueOnce(true) // apps/another-app/package.json
686
- .mockResolvedValueOnce(true) // packages/ui/package.json
687
- .mockResolvedValueOnce(true); // packages/utils/package.json
688
- mockPackagesShell.readFileAsText
689
- .mockResolvedValueOnce('{"name": "test-app", "version": "1.0.0"}')
690
- .mockResolvedValueOnce('{"name": "another-app", "version": "1.0.0"}')
691
- .mockResolvedValueOnce('{"name": "@repo/ui", "version": "1.0.0"}')
692
- .mockResolvedValueOnce('{"name": "@repo/utils", "version": "1.0.0"}');
688
+ mockPackagesShell.readJsonFile.mockImplementation((filePath: string) => {
689
+ if (filePath === "/workspace/package.json") {
690
+ return {
691
+ name: "root",
692
+ workspaces: ["apps/*", "packages/*", "tools/*"],
693
+ };
694
+ }
695
+ return mockPackageJson();
696
+ });
697
+ mockPackagesShell.readDirectory.mockImplementation((dirPath: string) => {
698
+ if (dirPath.endsWith("/apps")) return Promise.resolve(["test-app", "another-app"]);
699
+ if (dirPath.endsWith("/packages")) return Promise.resolve(["ui", "utils"]);
700
+ if (dirPath.endsWith("/tools")) return Promise.resolve(["typescript-config"]);
701
+ return Promise.resolve([]);
702
+ });
703
+ mockPackagesShell.canAccessFile.mockResolvedValue(true);
704
+ mockPackagesShell.readFileAsText.mockImplementation((filePath: string) => {
705
+ const packageNames: Record<string, string> = {
706
+ "/workspace/apps/test-app/package.json":
707
+ '{"name": "@apps/test-app", "version": "1.0.0"}',
708
+ "/workspace/apps/another-app/package.json":
709
+ '{"name": "@apps/another-app", "version": "1.0.0"}',
710
+ "/workspace/packages/ui/package.json": '{"name": "@packages/ui", "version": "1.0.0"}',
711
+ "/workspace/packages/utils/package.json":
712
+ '{"name": "@packages/utils", "version": "1.0.0"}',
713
+ "/workspace/tools/typescript-config/package.json":
714
+ '{"name": "@tools/typescript-config", "private": true}',
715
+ };
716
+ return Promise.resolve(packageNames[filePath] ?? "");
717
+ });
693
718
 
694
719
  const result = await EntityPackage.getAllPackages();
695
720
 
696
721
  expect(result).toContain("root");
697
- expect(result).toContain("test-app");
698
- expect(result).toContain("another-app");
699
- expect(result).toContain("@repo/ui");
700
- expect(result).toContain("@repo/utils");
722
+ expect(result).toContain("@apps/test-app");
723
+ expect(result).toContain("@apps/another-app");
724
+ expect(result).toContain("@packages/ui");
725
+ expect(result).toContain("@packages/utils");
726
+ expect(result).toContain("@tools/typescript-config");
701
727
  });
702
728
 
703
- it("should handle empty directories gracefully", async () => {
704
- // Mock packagesShell methods for this test
729
+ it("should handle missing workspaces field gracefully", async () => {
705
730
  mockPackagesShell.getWorkspaceRoot.mockResolvedValueOnce("/workspace");
706
- mockPackagesShell.readDirectory
707
- .mockResolvedValueOnce([]) // apps
708
- .mockResolvedValueOnce([]); // packages
731
+ mockPackagesShell.readJsonFile.mockImplementation((filePath: string) => {
732
+ if (filePath === "/workspace/package.json") {
733
+ return { name: "root" };
734
+ }
735
+ return mockPackageJson();
736
+ });
709
737
 
710
738
  const result = await EntityPackage.getAllPackages();
711
739
  expect(result).toEqual(["root"]);
712
740
  });
713
741
 
714
742
  it("should filter out packages without valid package.json", async () => {
715
- // Mock packagesShell methods for this test
716
743
  mockPackagesShell.getWorkspaceRoot.mockResolvedValueOnce("/workspace");
717
- mockPackagesShell.readDirectory
718
- .mockResolvedValueOnce(["invalid-app"]) // apps
719
- .mockResolvedValueOnce(["invalid-pkg"]); // packages
720
- mockPackagesShell.canAccessFile
721
- .mockResolvedValueOnce(false) // apps/invalid-app/package.json
722
- .mockResolvedValueOnce(false); // packages/invalid-pkg/package.json
744
+ mockPackagesShell.readJsonFile.mockImplementation((filePath: string) => {
745
+ if (filePath === "/workspace/package.json") {
746
+ return {
747
+ name: "root",
748
+ workspaces: ["apps/*", "packages/*", "tools/*"],
749
+ };
750
+ }
751
+ return mockPackageJson();
752
+ });
753
+ mockPackagesShell.readDirectory.mockImplementation((dirPath: string) => {
754
+ if (dirPath.endsWith("/apps")) return Promise.resolve(["invalid-app"]);
755
+ if (dirPath.endsWith("/packages")) return Promise.resolve(["invalid-pkg"]);
756
+ if (dirPath.endsWith("/tools")) return Promise.resolve(["invalid-tool"]);
757
+ return Promise.resolve([]);
758
+ });
759
+ mockPackagesShell.canAccessFile.mockResolvedValue(false);
723
760
 
724
761
  const result = await EntityPackage.getAllPackages();
725
762
  expect(result).toEqual(["root"]);
@@ -730,23 +767,23 @@ describe("EntityPackage", () => {
730
767
  describe("edge cases and error handling", () => {
731
768
  it("should handle package names with special characters", () => {
732
769
  const specialPackages = new EntityPackage("test-package@1.0.0");
733
- expect(specialPackages.getPath()).toBe("apps/test-package@1.0.0");
770
+ expect(specialPackages.getPath()).toBe("test-package@1.0.0");
734
771
  });
735
772
 
736
773
  it("should handle empty package name", () => {
737
774
  const emptyPackages = new EntityPackage("");
738
- expect(emptyPackages.getPath()).toBe("apps/");
775
+ expect(emptyPackages.getPath()).toBe("");
739
776
  });
740
777
 
741
778
  it("should handle very long package names", () => {
742
779
  const longName = "a".repeat(1000);
743
780
  const longPackages = new EntityPackage(longName);
744
- expect(longPackages.getPath()).toBe(`apps/${longName}`);
781
+ expect(longPackages.getPath()).toBe(longName);
745
782
  });
746
783
 
747
784
  it("should handle package names with spaces", () => {
748
785
  const spacedPackages = new EntityPackage("test package");
749
- expect(spacedPackages.getPath()).toBe("apps/test package");
786
+ expect(spacedPackages.getPath()).toBe("test package");
750
787
  });
751
788
  });
752
789
 
@@ -762,12 +799,12 @@ describe("EntityPackage", () => {
762
799
  .mockResolvedValueOnce(true); // packages/ui/package.json
763
800
  mockPackagesShell.readFileAsText
764
801
  .mockResolvedValueOnce('{"name": "test-app", "version": "1.0.0", "private": false}')
765
- .mockResolvedValueOnce('{"name": "@repo/ui", "version": "1.0.0", "private": false}');
802
+ .mockResolvedValueOnce('{"name": "@packages/ui", "version": "1.0.0", "private": false}');
766
803
 
767
804
  const result = await EntityPackage.getVersionedPackages();
768
805
  expect(Array.isArray(result)).toBe(true);
769
806
  expect(result).toContain("test-app");
770
- expect(result).toContain("@repo/ui");
807
+ expect(result).toContain("@packages/ui");
771
808
  });
772
809
  });
773
810
 
@@ -779,7 +816,7 @@ describe("EntityPackage", () => {
779
816
  return { name: "test-app", version: "1.0.0", private: true };
780
817
  }
781
818
  if (path.includes("ui")) {
782
- return { name: "@repo/ui", version: "1.0.0", private: true };
819
+ return { name: "@packages/ui", version: "1.0.0", private: true };
783
820
  }
784
821
  // Default fallback
785
822
  return mockPackageJson();
@@ -795,12 +832,12 @@ describe("EntityPackage", () => {
795
832
  .mockResolvedValueOnce(true); // packages/ui/package.json
796
833
  mockPackagesShell.readFileAsText
797
834
  .mockResolvedValueOnce('{"name": "test-app", "version": "1.0.0", "private": true}')
798
- .mockResolvedValueOnce('{"name": "@repo/ui", "version": "1.0.0", "private": true}');
835
+ .mockResolvedValueOnce('{"name": "@packages/ui", "version": "1.0.0", "private": true}');
799
836
 
800
837
  const result = await EntityPackage.getUnversionedPackages();
801
838
  expect(Array.isArray(result)).toBe(true);
802
839
  expect(result).toContain("test-app");
803
- expect(result).toContain("@repo/ui");
840
+ expect(result).toContain("@packages/ui");
804
841
  });
805
842
  });
806
843
 
@@ -816,7 +853,7 @@ describe("EntityPackage", () => {
816
853
  .mockResolvedValueOnce(true); // packages/ui/package.json
817
854
  mockPackagesShell.readFileAsText
818
855
  .mockResolvedValueOnce('{"name": "test-app", "version": "1.0.0", "private": false}')
819
- .mockResolvedValueOnce('{"name": "@repo/ui", "version": "1.0.0", "private": false}');
856
+ .mockResolvedValueOnce('{"name": "@packages/ui", "version": "1.0.0", "private": false}');
820
857
 
821
858
  const result = await EntityPackage.validateAllPackages();
822
859
  expect(Array.isArray(result)).toBe(true);
@@ -824,7 +861,7 @@ describe("EntityPackage", () => {
824
861
  expect(result).toHaveLength(3);
825
862
  expect(result).toContain("root: Consider adding a description to package.json");
826
863
  expect(result).toContain("test-app: Consider adding a description to package.json");
827
- expect(result).toContain("@repo/ui: Consider adding a description to package.json");
864
+ expect(result).toContain("@packages/ui: Consider adding a description to package.json");
828
865
  });
829
866
  });
830
867
  });
@@ -2,6 +2,12 @@ import { entitiesShell } from "../entities.shell";
2
2
  import { entitiesConfig } from "../intershell-config/intershell-config";
3
3
  import { packagesShell } from "./package.shell";
4
4
  import type { PackageJson, TsConfig } from "./package.types";
5
+ import {
6
+ discoverWorkspacePackageNamesAsync,
7
+ discoverWorkspacePackagesSync,
8
+ findWorkspaceRootSync,
9
+ getWorkspacePackagePath,
10
+ } from "./workspace-discovery";
5
11
 
6
12
  export class EntityPackage {
7
13
  private readonly packageName: string;
@@ -20,9 +26,16 @@ export class EntityPackage {
20
26
 
21
27
  getPath(): string {
22
28
  if (this.packageName === "root") return ".";
23
- if (this.packageName.startsWith("@repo/"))
24
- return `packages/${this.packageName.replace("@repo/", "")}`;
25
- return `apps/${this.packageName}`;
29
+ const workspacePath = getWorkspacePackagePath(this.packageName);
30
+ if (workspacePath !== null) return workspacePath;
31
+
32
+ const workspaceRoot = findWorkspaceRootSync();
33
+ const entry = discoverWorkspacePackagesSync(workspaceRoot).find(
34
+ (pkg) => pkg.name === this.packageName,
35
+ );
36
+ if (entry) return entry.relativePath;
37
+
38
+ return this.packageName;
26
39
  }
27
40
 
28
41
  getJsonPath(): string {
@@ -138,14 +151,13 @@ export class EntityPackage {
138
151
 
139
152
  /**
140
153
  * Gets the tag series name for this package
141
- * @returns tag series prefix (e.g., 'v', 'intershell-v') or null if package shouldn't be versioned
154
+ * @returns tag series prefix (e.g., 'v', 'packages/intershell-v') or null if package shouldn't be versioned
142
155
  */
143
156
  getTagSeriesName(): string | null {
144
157
  if (!this.shouldVersion()) return null;
145
158
 
146
- // Generate tag series name based on package name
147
159
  if (this.packageName === "root") return "v";
148
- return `${this.packageName.replace("@repo/", "")}-v`;
160
+ return `${this.packageName.replaceAll("@", "")}-v`;
149
161
  }
150
162
 
151
163
  static getRepoUrl(): string {
@@ -156,58 +168,18 @@ export class EntityPackage {
156
168
  }
157
169
  static async getAllPackages(): Promise<string[]> {
158
170
  const packages: string[] = ["root"];
159
-
160
- // Get workspace root
161
171
  const workspaceRoot = await packagesShell.getWorkspaceRoot();
162
-
163
- // Read apps directory
164
- let apps: string[] = [];
165
- try {
166
- const appsPath = `${workspaceRoot}/apps`;
167
- apps = await packagesShell.readDirectory(appsPath);
168
- } catch {
169
- // apps directory doesn't exist or can't be read
170
- }
171
-
172
- // Read packages directory
173
- let pkgs: string[] = [];
174
- try {
175
- const packagesPath = `${workspaceRoot}/packages`;
176
- const packageNames = await packagesShell.readDirectory(packagesPath);
177
- pkgs = packageNames.map((name) => `@repo/${name}`);
178
- } catch {
179
- // packages directory doesn't exist or can't be read
180
- }
181
-
182
- // Filter packages that have valid package.json files
183
- const filteredPackages = await Promise.all(
184
- [...apps, ...pkgs].map(async (pkg) => {
185
- const packageInstance = new EntityPackage(pkg);
186
- const packageJsonPath = packageInstance.getJsonPath();
187
-
188
- try {
189
- const exists = await packagesShell.canAccessFile(packageJsonPath);
190
- if (!exists) return null;
191
-
192
- const packageJsonContent = await packagesShell.readFileAsText(packageJsonPath);
193
- const packageJson = JSON.parse(packageJsonContent);
194
- const name = packageJson.name;
195
-
196
- if (!name) return null;
197
-
198
- if (name !== pkg) {
199
- throw new Error(`Package ${pkg} has a different name in package.json: ${name}`);
200
- }
201
-
202
- return name;
203
- } catch {
204
- return null;
205
- }
206
- }),
172
+ const rootPackageJson = packagesShell.readJsonFile(`${workspaceRoot}/package.json`);
173
+
174
+ const workspacePackages = await discoverWorkspacePackageNamesAsync(
175
+ workspaceRoot,
176
+ packagesShell.readDirectory,
177
+ packagesShell.canAccessFile,
178
+ packagesShell.readFileAsText,
179
+ rootPackageJson,
207
180
  );
208
181
 
209
- packages.push(...filteredPackages.filter((pkg): pkg is string => pkg !== null));
210
-
182
+ packages.push(...workspacePackages);
211
183
  return packages;
212
184
  }
213
185
 
@@ -17,7 +17,7 @@ export interface PackageJson {
17
17
  homepage?: string;
18
18
  keywords?: string[];
19
19
  private?: boolean;
20
- workspaces?: string[];
20
+ workspaces?: string[] | { packages?: string[] };
21
21
  [key: string]: unknown;
22
22
  }
23
23