tools-cc 1.0.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.
Files changed (49) hide show
  1. package/dist/commands/config.d.ts +3 -0
  2. package/dist/commands/config.js +56 -0
  3. package/dist/commands/help.d.ts +1 -0
  4. package/dist/commands/help.js +84 -0
  5. package/dist/commands/source.d.ts +4 -0
  6. package/dist/commands/source.js +72 -0
  7. package/dist/commands/use.d.ts +6 -0
  8. package/dist/commands/use.js +133 -0
  9. package/dist/core/config.d.ts +5 -0
  10. package/dist/core/config.js +37 -0
  11. package/dist/core/manifest.d.ts +3 -0
  12. package/dist/core/manifest.js +56 -0
  13. package/dist/core/project.d.ts +4 -0
  14. package/dist/core/project.js +118 -0
  15. package/dist/core/source.d.ts +6 -0
  16. package/dist/core/source.js +86 -0
  17. package/dist/core/symlink.d.ts +3 -0
  18. package/dist/core/symlink.js +56 -0
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +165 -0
  21. package/dist/types/config.d.ts +23 -0
  22. package/dist/types/config.js +2 -0
  23. package/dist/types/index.d.ts +1 -0
  24. package/dist/types/index.js +17 -0
  25. package/dist/utils/path.d.ts +8 -0
  26. package/dist/utils/path.js +22 -0
  27. package/docs/plans/2026-02-25-tools-cc-design.md +195 -0
  28. package/docs/plans/2026-02-25-tools-cc-impl.md +1600 -0
  29. package/package.json +44 -0
  30. package/readme.md +182 -0
  31. package/src/commands/config.ts +50 -0
  32. package/src/commands/help.ts +79 -0
  33. package/src/commands/source.ts +63 -0
  34. package/src/commands/use.ts +147 -0
  35. package/src/core/config.ts +37 -0
  36. package/src/core/manifest.ts +57 -0
  37. package/src/core/project.ts +136 -0
  38. package/src/core/source.ts +100 -0
  39. package/src/core/symlink.ts +56 -0
  40. package/src/index.ts +186 -0
  41. package/src/types/config.ts +27 -0
  42. package/src/types/index.ts +1 -0
  43. package/src/utils/path.ts +18 -0
  44. package/tests/core/config.test.ts +37 -0
  45. package/tests/core/manifest.test.ts +37 -0
  46. package/tests/core/project.test.ts +50 -0
  47. package/tests/core/source.test.ts +75 -0
  48. package/tests/core/symlink.test.ts +39 -0
  49. package/tsconfig.json +17 -0
@@ -0,0 +1,75 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import { addSource, listSources, removeSource, updateSource, getSourcePath } from '../../src/core/source';
5
+
6
+ describe('Source Module', () => {
7
+ const testConfigDir = path.join(__dirname, '../fixtures/.tools-cc-test');
8
+ const testSourcesDir = path.join(__dirname, '../fixtures/sources');
9
+
10
+ beforeEach(async () => {
11
+ await fs.ensureDir(testConfigDir);
12
+ await fs.ensureDir(testSourcesDir);
13
+ });
14
+
15
+ afterEach(async () => {
16
+ await fs.remove(testConfigDir);
17
+ await fs.remove(testSourcesDir);
18
+ });
19
+
20
+ it('should add a local source', async () => {
21
+ const result = await addSource('test-local', testSourcesDir, testConfigDir);
22
+ expect(result.type).toBe('local');
23
+ expect(result.path).toBe(testSourcesDir);
24
+ });
25
+
26
+ it('should list sources', async () => {
27
+ await addSource('test-1', testSourcesDir, testConfigDir);
28
+ const sources = await listSources(testConfigDir);
29
+ expect(sources).toHaveProperty('test-1');
30
+ });
31
+
32
+ it('should remove a source', async () => {
33
+ await addSource('test-remove', testSourcesDir, testConfigDir);
34
+ await removeSource('test-remove', testConfigDir);
35
+ const sources = await listSources(testConfigDir);
36
+ expect(sources).not.toHaveProperty('test-remove');
37
+ });
38
+
39
+ describe('updateSource', () => {
40
+ it('should throw error when source not found', async () => {
41
+ await expect(updateSource('non-existent', testConfigDir)).rejects.toThrow('Source not found: non-existent');
42
+ });
43
+
44
+ it('should skip update for local source', async () => {
45
+ await addSource('local-source', testSourcesDir, testConfigDir);
46
+ const consoleSpy = vi.spyOn(console, 'log');
47
+ await updateSource('local-source', testConfigDir);
48
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('is local, no update needed'));
49
+ consoleSpy.mockRestore();
50
+ });
51
+ });
52
+
53
+ describe('getSourcePath', () => {
54
+ it('should throw error when source not found', async () => {
55
+ await expect(getSourcePath('non-existent', testConfigDir)).rejects.toThrow('Source not found: non-existent');
56
+ });
57
+
58
+ it('should return path for local source', async () => {
59
+ await addSource('local-path-test', testSourcesDir, testConfigDir);
60
+ const result = await getSourcePath('local-path-test', testConfigDir);
61
+ expect(result).toBe(testSourcesDir);
62
+ });
63
+ });
64
+
65
+ describe('error handling', () => {
66
+ it('should throw error when adding non-existent local path', async () => {
67
+ const nonExistentPath = path.join(__dirname, 'non-existent-path');
68
+ await expect(addSource('invalid', nonExistentPath, testConfigDir)).rejects.toThrow('Path does not exist');
69
+ });
70
+
71
+ it('should throw error when removing non-existent source', async () => {
72
+ await expect(removeSource('non-existent', testConfigDir)).rejects.toThrow('Source not found: non-existent');
73
+ });
74
+ });
75
+ });
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import { createSymlink, removeSymlink, isSymlink } from '../../src/core/symlink';
5
+
6
+ describe('Symlink Module', () => {
7
+ const testDir = path.join(__dirname, '../fixtures/symlink-test');
8
+ const targetDir = path.join(testDir, 'target');
9
+ const linkPath = path.join(testDir, 'link');
10
+
11
+ beforeEach(async () => {
12
+ await fs.ensureDir(targetDir);
13
+ await fs.writeJson(path.join(targetDir, 'test.json'), { test: true });
14
+ });
15
+
16
+ afterEach(async () => {
17
+ await fs.remove(testDir);
18
+ });
19
+
20
+ it('should create symlink', async () => {
21
+ await createSymlink(targetDir, linkPath);
22
+ expect(await isSymlink(linkPath)).toBe(true);
23
+ });
24
+
25
+ it('should remove symlink', async () => {
26
+ await createSymlink(targetDir, linkPath);
27
+ await removeSymlink(linkPath);
28
+ expect(await fs.pathExists(linkPath)).toBe(false);
29
+ expect(await fs.pathExists(targetDir)).toBe(true);
30
+ });
31
+
32
+ it('should replace existing directory with symlink', async () => {
33
+ await fs.ensureDir(linkPath);
34
+ await fs.writeFile(path.join(linkPath, 'old.txt'), 'old');
35
+
36
+ await createSymlink(targetDir, linkPath, true);
37
+ expect(await isSymlink(linkPath)).toBe(true);
38
+ });
39
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }