@nimblebrain/mpak 0.0.2 → 0.2.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 (151) hide show
  1. package/LICENSE +10 -198
  2. package/README.md +50 -360
  3. package/dist/index.d.ts +0 -2
  4. package/dist/index.js +2113 -4
  5. package/dist/index.js.map +1 -1
  6. package/package.json +32 -29
  7. package/.claude/settings.local.json +0 -19
  8. package/.env.example +0 -13
  9. package/.github/workflows/ci.yml +0 -27
  10. package/CLAUDE.md +0 -271
  11. package/dist/commands/config.d.ts +0 -31
  12. package/dist/commands/config.d.ts.map +0 -1
  13. package/dist/commands/config.js +0 -129
  14. package/dist/commands/config.js.map +0 -1
  15. package/dist/commands/packages/pull.d.ts +0 -11
  16. package/dist/commands/packages/pull.d.ts.map +0 -1
  17. package/dist/commands/packages/pull.js +0 -72
  18. package/dist/commands/packages/pull.js.map +0 -1
  19. package/dist/commands/packages/run.d.ts +0 -36
  20. package/dist/commands/packages/run.d.ts.map +0 -1
  21. package/dist/commands/packages/run.js +0 -348
  22. package/dist/commands/packages/run.js.map +0 -1
  23. package/dist/commands/packages/search.d.ts +0 -12
  24. package/dist/commands/packages/search.d.ts.map +0 -1
  25. package/dist/commands/packages/search.js +0 -63
  26. package/dist/commands/packages/search.js.map +0 -1
  27. package/dist/commands/packages/show.d.ts +0 -8
  28. package/dist/commands/packages/show.d.ts.map +0 -1
  29. package/dist/commands/packages/show.js +0 -109
  30. package/dist/commands/packages/show.js.map +0 -1
  31. package/dist/commands/search.d.ts +0 -12
  32. package/dist/commands/search.d.ts.map +0 -1
  33. package/dist/commands/search.js +0 -144
  34. package/dist/commands/search.js.map +0 -1
  35. package/dist/commands/skills/index.d.ts +0 -8
  36. package/dist/commands/skills/index.d.ts.map +0 -1
  37. package/dist/commands/skills/index.js +0 -8
  38. package/dist/commands/skills/index.js.map +0 -1
  39. package/dist/commands/skills/install.d.ts +0 -9
  40. package/dist/commands/skills/install.d.ts.map +0 -1
  41. package/dist/commands/skills/install.js +0 -110
  42. package/dist/commands/skills/install.js.map +0 -1
  43. package/dist/commands/skills/list.d.ts +0 -8
  44. package/dist/commands/skills/list.d.ts.map +0 -1
  45. package/dist/commands/skills/list.js +0 -89
  46. package/dist/commands/skills/list.js.map +0 -1
  47. package/dist/commands/skills/pack.d.ts +0 -22
  48. package/dist/commands/skills/pack.d.ts.map +0 -1
  49. package/dist/commands/skills/pack.js +0 -116
  50. package/dist/commands/skills/pack.js.map +0 -1
  51. package/dist/commands/skills/pull.d.ts +0 -9
  52. package/dist/commands/skills/pull.d.ts.map +0 -1
  53. package/dist/commands/skills/pull.js +0 -68
  54. package/dist/commands/skills/pull.js.map +0 -1
  55. package/dist/commands/skills/search.d.ts +0 -14
  56. package/dist/commands/skills/search.d.ts.map +0 -1
  57. package/dist/commands/skills/search.js +0 -53
  58. package/dist/commands/skills/search.js.map +0 -1
  59. package/dist/commands/skills/show.d.ts +0 -8
  60. package/dist/commands/skills/show.d.ts.map +0 -1
  61. package/dist/commands/skills/show.js +0 -64
  62. package/dist/commands/skills/show.js.map +0 -1
  63. package/dist/commands/skills/validate.d.ts +0 -25
  64. package/dist/commands/skills/validate.d.ts.map +0 -1
  65. package/dist/commands/skills/validate.js +0 -191
  66. package/dist/commands/skills/validate.js.map +0 -1
  67. package/dist/index.d.ts.map +0 -1
  68. package/dist/lib/api/registry-client.d.ts +0 -63
  69. package/dist/lib/api/registry-client.d.ts.map +0 -1
  70. package/dist/lib/api/registry-client.js +0 -167
  71. package/dist/lib/api/registry-client.js.map +0 -1
  72. package/dist/lib/api/skills-client.d.ts +0 -30
  73. package/dist/lib/api/skills-client.d.ts.map +0 -1
  74. package/dist/lib/api/skills-client.js +0 -110
  75. package/dist/lib/api/skills-client.js.map +0 -1
  76. package/dist/program.d.ts +0 -12
  77. package/dist/program.d.ts.map +0 -1
  78. package/dist/program.js +0 -174
  79. package/dist/program.js.map +0 -1
  80. package/dist/schemas/generated/api-responses.d.ts +0 -541
  81. package/dist/schemas/generated/api-responses.d.ts.map +0 -1
  82. package/dist/schemas/generated/api-responses.js +0 -313
  83. package/dist/schemas/generated/api-responses.js.map +0 -1
  84. package/dist/schemas/generated/auth.d.ts +0 -18
  85. package/dist/schemas/generated/auth.d.ts.map +0 -1
  86. package/dist/schemas/generated/auth.js +0 -18
  87. package/dist/schemas/generated/auth.js.map +0 -1
  88. package/dist/schemas/generated/index.d.ts +0 -5
  89. package/dist/schemas/generated/index.d.ts.map +0 -1
  90. package/dist/schemas/generated/index.js +0 -6
  91. package/dist/schemas/generated/index.js.map +0 -1
  92. package/dist/schemas/generated/package.d.ts +0 -43
  93. package/dist/schemas/generated/package.d.ts.map +0 -1
  94. package/dist/schemas/generated/package.js +0 -20
  95. package/dist/schemas/generated/package.js.map +0 -1
  96. package/dist/schemas/generated/skill.d.ts +0 -381
  97. package/dist/schemas/generated/skill.d.ts.map +0 -1
  98. package/dist/schemas/generated/skill.js +0 -216
  99. package/dist/schemas/generated/skill.js.map +0 -1
  100. package/dist/utils/config-manager.d.ts +0 -66
  101. package/dist/utils/config-manager.d.ts.map +0 -1
  102. package/dist/utils/config-manager.js +0 -193
  103. package/dist/utils/config-manager.js.map +0 -1
  104. package/dist/utils/errors.d.ts +0 -12
  105. package/dist/utils/errors.d.ts.map +0 -1
  106. package/dist/utils/errors.js +0 -27
  107. package/dist/utils/errors.js.map +0 -1
  108. package/dist/utils/version.d.ts +0 -5
  109. package/dist/utils/version.d.ts.map +0 -1
  110. package/dist/utils/version.js +0 -19
  111. package/dist/utils/version.js.map +0 -1
  112. package/eslint.config.js +0 -63
  113. package/src/commands/config.ts +0 -162
  114. package/src/commands/packages/pull.ts +0 -96
  115. package/src/commands/packages/run.test.ts +0 -222
  116. package/src/commands/packages/run.ts +0 -451
  117. package/src/commands/packages/search.ts +0 -83
  118. package/src/commands/packages/show.ts +0 -128
  119. package/src/commands/search.ts +0 -191
  120. package/src/commands/skills/index.ts +0 -7
  121. package/src/commands/skills/install.ts +0 -129
  122. package/src/commands/skills/list.ts +0 -116
  123. package/src/commands/skills/pack.test.ts +0 -260
  124. package/src/commands/skills/pack.ts +0 -145
  125. package/src/commands/skills/pull.ts +0 -88
  126. package/src/commands/skills/search.ts +0 -73
  127. package/src/commands/skills/show.ts +0 -72
  128. package/src/commands/skills/validate.test.ts +0 -466
  129. package/src/commands/skills/validate.ts +0 -227
  130. package/src/index.ts +0 -11
  131. package/src/lib/api/registry-client.ts +0 -223
  132. package/src/lib/api/schema.d.ts +0 -520
  133. package/src/lib/api/skills-client.ts +0 -148
  134. package/src/program.test.ts +0 -22
  135. package/src/program.ts +0 -212
  136. package/src/schemas/config.v1.schema.json +0 -37
  137. package/src/schemas/generated/api-responses.ts +0 -386
  138. package/src/schemas/generated/auth.ts +0 -21
  139. package/src/schemas/generated/index.ts +0 -5
  140. package/src/schemas/generated/package.ts +0 -29
  141. package/src/schemas/generated/skill.ts +0 -271
  142. package/src/utils/config-manager.test.ts +0 -330
  143. package/src/utils/config-manager.ts +0 -272
  144. package/src/utils/errors.test.ts +0 -25
  145. package/src/utils/errors.ts +0 -33
  146. package/src/utils/version.test.ts +0 -16
  147. package/src/utils/version.ts +0 -18
  148. package/test/integration/registry-client.test.ts +0 -180
  149. package/tsconfig.check.json +0 -9
  150. package/tsconfig.json +0 -25
  151. package/vitest.config.ts +0 -14
@@ -1,271 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- // =============================================================================
4
- // Agent Skills Specification - Skill Frontmatter Schema
5
- // https://agentskills.io/specification
6
- // =============================================================================
7
-
8
- /**
9
- * Skill name validation
10
- * - 1-64 characters
11
- * - Lowercase alphanumeric with single hyphens
12
- * - Cannot start or end with hyphen
13
- * - Must match directory name (validated separately)
14
- */
15
- export const SkillNameSchema = z
16
- .string()
17
- .min(1)
18
- .max(64)
19
- .regex(
20
- /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,
21
- 'Lowercase alphanumeric with single hyphens, cannot start/end with hyphen'
22
- );
23
-
24
- /**
25
- * Skill description
26
- * - 1-1024 characters
27
- * - Should describe what the skill does AND when to use it
28
- */
29
- export const SkillDescriptionSchema = z.string().min(1).max(1024);
30
-
31
- // =============================================================================
32
- // Discovery Metadata Extension (via metadata: field)
33
- // =============================================================================
34
-
35
- /**
36
- * Category taxonomy for skill discovery
37
- */
38
- export const SkillCategorySchema = z.enum([
39
- 'development', // Code, debugging, architecture
40
- 'writing', // Documentation, content, editing
41
- 'research', // Investigation, analysis, learning
42
- 'consulting', // Strategy, decisions, planning
43
- 'data', // Analysis, visualization, processing
44
- 'design', // UI/UX, visual, creative
45
- 'operations', // DevOps, infrastructure, automation
46
- 'security', // Auditing, compliance, protection
47
- 'other', // Uncategorized
48
- ]);
49
-
50
- /**
51
- * Surfaces where the skill works
52
- */
53
- export const SkillSurfaceSchema = z.enum([
54
- 'claude-code', // Claude Code CLI
55
- 'claude-api', // Claude API with code execution
56
- 'claude-ai', // claude.ai web interface
57
- ]);
58
-
59
- /**
60
- * Author information for attribution
61
- */
62
- export const SkillAuthorSchema = z.object({
63
- name: z.string().min(1),
64
- url: z.string().url().optional(),
65
- email: z.string().email().optional(),
66
- });
67
-
68
- /**
69
- * Example usage for discovery
70
- */
71
- export const SkillExampleSchema = z.object({
72
- prompt: z.string().min(1),
73
- context: z.string().optional(),
74
- });
75
-
76
- /**
77
- * Discovery metadata (via metadata: field in frontmatter)
78
- * All fields optional - skills can start minimal and add discovery metadata later
79
- */
80
- export const SkillDiscoveryMetadataSchema = z
81
- .object({
82
- // Discovery
83
- tags: z.array(z.string().max(32)).max(10).optional(),
84
- category: SkillCategorySchema.optional(),
85
- triggers: z.array(z.string().max(128)).max(20).optional(),
86
- keywords: z.array(z.string().max(32)).max(30).optional(),
87
- surfaces: z.array(SkillSurfaceSchema).optional(),
88
-
89
- // Attribution
90
- author: SkillAuthorSchema.optional(),
91
-
92
- // Version (for registry tracking)
93
- version: z.string().optional(),
94
-
95
- // Examples
96
- examples: z.array(SkillExampleSchema).max(5).optional(),
97
- })
98
- .passthrough(); // Allow additional custom keys
99
-
100
- // =============================================================================
101
- // Complete SKILL.md Frontmatter Schema
102
- // =============================================================================
103
-
104
- /**
105
- * Complete SKILL.md frontmatter schema
106
- * Combines official Agent Skills spec with discovery metadata extension
107
- */
108
- export const SkillFrontmatterSchema = z.object({
109
- // Required (official spec)
110
- name: SkillNameSchema,
111
- description: SkillDescriptionSchema,
112
-
113
- // Optional (official spec - informational pass-through)
114
- license: z.string().optional(),
115
- compatibility: z.string().max(500).optional(),
116
- 'allowed-tools': z.string().optional(), // space-delimited, experimental
117
-
118
- // Extensible metadata (official spec) - our discovery extension
119
- metadata: SkillDiscoveryMetadataSchema.optional(),
120
- });
121
-
122
- // =============================================================================
123
- // Registry API Schemas
124
- // =============================================================================
125
-
126
- /**
127
- * Scoped skill name for registry (e.g., @nimblebraininc/strategic-thought-partner)
128
- */
129
- export const ScopedSkillNameSchema = z
130
- .string()
131
- .regex(
132
- /^@[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/,
133
- 'Scoped name format: @scope/name'
134
- );
135
-
136
- /**
137
- * Skill artifact info for announce endpoint
138
- */
139
- export const SkillArtifactSchema = z.object({
140
- filename: z.string().regex(/\.skill$/, 'Must have .skill extension'),
141
- sha256: z.string().length(64),
142
- size: z.number().int().positive(),
143
- });
144
-
145
- /**
146
- * Announce request schema for POST /v1/skills/announce
147
- */
148
- export const SkillAnnounceRequestSchema = z.object({
149
- name: ScopedSkillNameSchema,
150
- version: z.string(),
151
- skill: SkillFrontmatterSchema,
152
- release_tag: z.string(),
153
- prerelease: z.boolean().optional().default(false),
154
- artifact: SkillArtifactSchema,
155
- });
156
-
157
- /**
158
- * Announce response schema
159
- */
160
- export const SkillAnnounceResponseSchema = z.object({
161
- skill: z.string(),
162
- version: z.string(),
163
- status: z.enum(['created', 'exists']),
164
- });
165
-
166
- // =============================================================================
167
- // Search/List API Schemas
168
- // =============================================================================
169
-
170
- /**
171
- * Skill search parameters
172
- */
173
- export const SkillSearchParamsSchema = z.object({
174
- q: z.string().optional(),
175
- tags: z.string().optional(), // comma-separated
176
- category: SkillCategorySchema.optional(),
177
- surface: SkillSurfaceSchema.optional(),
178
- sort: z.enum(['downloads', 'recent', 'name']).optional(),
179
- limit: z.union([z.string(), z.number()]).optional(),
180
- offset: z.union([z.string(), z.number()]).optional(),
181
- });
182
-
183
- /**
184
- * Skill summary for search results
185
- */
186
- export const SkillSummarySchema = z.object({
187
- name: z.string(), // scoped name
188
- description: z.string(),
189
- latest_version: z.string(),
190
- tags: z.array(z.string()).optional(),
191
- category: SkillCategorySchema.optional(),
192
- surfaces: z.array(SkillSurfaceSchema).optional(),
193
- downloads: z.number(),
194
- published_at: z.string(),
195
- author: SkillAuthorSchema.optional(),
196
- });
197
-
198
- /**
199
- * Skill search response
200
- */
201
- export const SkillSearchResponseSchema = z.object({
202
- skills: z.array(SkillSummarySchema),
203
- total: z.number(),
204
- pagination: z.object({
205
- limit: z.number(),
206
- offset: z.number(),
207
- has_more: z.boolean(),
208
- }),
209
- });
210
-
211
- /**
212
- * Skill detail response
213
- */
214
- export const SkillDetailSchema = z.object({
215
- name: z.string(), // scoped name
216
- description: z.string(),
217
- latest_version: z.string(),
218
- license: z.string().optional(),
219
- compatibility: z.string().optional(),
220
- allowed_tools: z.array(z.string()).optional(),
221
- tags: z.array(z.string()).optional(),
222
- category: SkillCategorySchema.optional(),
223
- triggers: z.array(z.string()).optional(),
224
- surfaces: z.array(SkillSurfaceSchema).optional(),
225
- downloads: z.number(),
226
- published_at: z.string(),
227
- author: SkillAuthorSchema.optional(),
228
- examples: z.array(SkillExampleSchema).optional(),
229
- versions: z.array(
230
- z.object({
231
- version: z.string(),
232
- published_at: z.string(),
233
- downloads: z.number(),
234
- })
235
- ),
236
- });
237
-
238
- /**
239
- * Skill download info response
240
- */
241
- export const SkillDownloadInfoSchema = z.object({
242
- url: z.string(),
243
- skill: z.object({
244
- name: z.string(),
245
- version: z.string(),
246
- sha256: z.string(),
247
- size: z.number(),
248
- }),
249
- expires_at: z.string(),
250
- });
251
-
252
- // =============================================================================
253
- // TypeScript Types
254
- // =============================================================================
255
-
256
- export type SkillName = z.infer<typeof SkillNameSchema>;
257
- export type SkillCategory = z.infer<typeof SkillCategorySchema>;
258
- export type SkillSurface = z.infer<typeof SkillSurfaceSchema>;
259
- export type SkillAuthor = z.infer<typeof SkillAuthorSchema>;
260
- export type SkillExample = z.infer<typeof SkillExampleSchema>;
261
- export type SkillDiscoveryMetadata = z.infer<typeof SkillDiscoveryMetadataSchema>;
262
- export type SkillFrontmatter = z.infer<typeof SkillFrontmatterSchema>;
263
- export type ScopedSkillName = z.infer<typeof ScopedSkillNameSchema>;
264
- export type SkillArtifact = z.infer<typeof SkillArtifactSchema>;
265
- export type SkillAnnounceRequest = z.infer<typeof SkillAnnounceRequestSchema>;
266
- export type SkillAnnounceResponse = z.infer<typeof SkillAnnounceResponseSchema>;
267
- export type SkillSearchParams = z.infer<typeof SkillSearchParamsSchema>;
268
- export type SkillSummary = z.infer<typeof SkillSummarySchema>;
269
- export type SkillSearchResponse = z.infer<typeof SkillSearchResponseSchema>;
270
- export type SkillDetail = z.infer<typeof SkillDetailSchema>;
271
- export type SkillDownloadInfo = z.infer<typeof SkillDownloadInfoSchema>;
@@ -1,330 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { ConfigManager, ConfigCorruptedError } from './config-manager.js';
3
- import { existsSync, rmSync, writeFileSync, mkdirSync } from 'fs';
4
- import { join } from 'path';
5
- import { homedir } from 'os';
6
-
7
- describe('ConfigManager', () => {
8
- const testConfigDir = join(homedir(), '.mpak');
9
- const testConfigFile = join(testConfigDir, 'config.json');
10
-
11
- beforeEach(() => {
12
- // Clean up test config before each test
13
- if (existsSync(testConfigFile)) {
14
- rmSync(testConfigFile, { force: true });
15
- }
16
- });
17
-
18
- afterEach(() => {
19
- // Clean up test config after each test
20
- if (existsSync(testConfigFile)) {
21
- rmSync(testConfigFile, { force: true });
22
- }
23
- });
24
-
25
- describe('loadConfig', () => {
26
- it('should create a new config if none exists', () => {
27
- const manager = new ConfigManager();
28
- const config = manager.loadConfig();
29
-
30
- expect(config).toBeDefined();
31
- expect(config.version).toBe('1.0.0');
32
- expect(config.lastUpdated).toBeTruthy();
33
- });
34
-
35
- it('should set registryUrl in config', () => {
36
- const manager = new ConfigManager();
37
- manager.setRegistryUrl('http://test.example.com');
38
-
39
- // Get the config to verify it's set
40
- expect(manager.getRegistryUrl()).toBe('http://test.example.com');
41
- });
42
- });
43
-
44
- describe('getRegistryUrl', () => {
45
- it('should return default registry URL', () => {
46
- const manager = new ConfigManager();
47
- const url = manager.getRegistryUrl();
48
-
49
- expect(url).toBe('https://api.mpak.dev');
50
- });
51
-
52
- it('should return configured registry URL', () => {
53
- const manager = new ConfigManager();
54
- manager.setRegistryUrl('http://custom.example.com');
55
-
56
- expect(manager.getRegistryUrl()).toBe('http://custom.example.com');
57
- });
58
- });
59
-
60
- describe('package config', () => {
61
- it('should set and get package config value', () => {
62
- const manager = new ConfigManager();
63
- manager.setPackageConfigValue('@scope/name', 'api_key', 'test-value');
64
-
65
- expect(manager.getPackageConfigValue('@scope/name', 'api_key')).toBe('test-value');
66
- });
67
-
68
- it('should return undefined for non-existent package', () => {
69
- const manager = new ConfigManager();
70
-
71
- expect(manager.getPackageConfig('@nonexistent/pkg')).toBeUndefined();
72
- });
73
-
74
- it('should return undefined for non-existent key', () => {
75
- const manager = new ConfigManager();
76
- manager.setPackageConfigValue('@scope/name', 'existing', 'value');
77
-
78
- expect(manager.getPackageConfigValue('@scope/name', 'nonexistent')).toBeUndefined();
79
- });
80
-
81
- it('should get all package config', () => {
82
- const manager = new ConfigManager();
83
- manager.setPackageConfigValue('@scope/name', 'key1', 'value1');
84
- manager.setPackageConfigValue('@scope/name', 'key2', 'value2');
85
-
86
- const config = manager.getPackageConfig('@scope/name');
87
- expect(config).toEqual({
88
- key1: 'value1',
89
- key2: 'value2',
90
- });
91
- });
92
-
93
- it('should clear specific package config value', () => {
94
- const manager = new ConfigManager();
95
- manager.setPackageConfigValue('@scope/name', 'key1', 'value1');
96
- manager.setPackageConfigValue('@scope/name', 'key2', 'value2');
97
-
98
- const cleared = manager.clearPackageConfigValue('@scope/name', 'key1');
99
- expect(cleared).toBe(true);
100
- expect(manager.getPackageConfigValue('@scope/name', 'key1')).toBeUndefined();
101
- expect(manager.getPackageConfigValue('@scope/name', 'key2')).toBe('value2');
102
- });
103
-
104
- it('should return false when clearing non-existent key', () => {
105
- const manager = new ConfigManager();
106
- manager.setPackageConfigValue('@scope/name', 'key1', 'value1');
107
-
108
- const cleared = manager.clearPackageConfigValue('@scope/name', 'nonexistent');
109
- expect(cleared).toBe(false);
110
- });
111
-
112
- it('should clear all package config', () => {
113
- const manager = new ConfigManager();
114
- manager.setPackageConfigValue('@scope/name', 'key1', 'value1');
115
- manager.setPackageConfigValue('@scope/name', 'key2', 'value2');
116
-
117
- const cleared = manager.clearPackageConfig('@scope/name');
118
- expect(cleared).toBe(true);
119
- expect(manager.getPackageConfig('@scope/name')).toBeUndefined();
120
- });
121
-
122
- it('should return false when clearing non-existent package', () => {
123
- const manager = new ConfigManager();
124
-
125
- const cleared = manager.clearPackageConfig('@nonexistent/pkg');
126
- expect(cleared).toBe(false);
127
- });
128
-
129
- it('should list packages with config', () => {
130
- const manager = new ConfigManager();
131
- manager.setPackageConfigValue('@scope/pkg1', 'key', 'value');
132
- manager.setPackageConfigValue('@scope/pkg2', 'key', 'value');
133
-
134
- const packages = manager.listPackagesWithConfig();
135
- expect(packages).toContain('@scope/pkg1');
136
- expect(packages).toContain('@scope/pkg2');
137
- expect(packages).toHaveLength(2);
138
- });
139
-
140
- it('should clean up empty package entry after clearing last key', () => {
141
- const manager = new ConfigManager();
142
- manager.setPackageConfigValue('@scope/name', 'only_key', 'value');
143
- manager.clearPackageConfigValue('@scope/name', 'only_key');
144
-
145
- expect(manager.getPackageConfig('@scope/name')).toBeUndefined();
146
- expect(manager.listPackagesWithConfig()).not.toContain('@scope/name');
147
- });
148
- });
149
-
150
- describe('config validation', () => {
151
- beforeEach(() => {
152
- // Ensure config directory exists for writing test files
153
- if (!existsSync(testConfigDir)) {
154
- mkdirSync(testConfigDir, { recursive: true, mode: 0o700 });
155
- }
156
- });
157
-
158
- it('should throw ConfigCorruptedError for invalid JSON', () => {
159
- writeFileSync(testConfigFile, 'not valid json {{{', { mode: 0o600 });
160
-
161
- const manager = new ConfigManager();
162
- expect(() => manager.loadConfig()).toThrow(ConfigCorruptedError);
163
- expect(() => manager.loadConfig()).toThrow(/invalid JSON/);
164
- });
165
-
166
- it('should throw ConfigCorruptedError when version is missing', () => {
167
- writeFileSync(
168
- testConfigFile,
169
- JSON.stringify({ lastUpdated: '2024-01-01T00:00:00Z' }),
170
- { mode: 0o600 }
171
- );
172
-
173
- const manager = new ConfigManager();
174
- expect(() => manager.loadConfig()).toThrow(ConfigCorruptedError);
175
- expect(() => manager.loadConfig()).toThrow(/version/);
176
- });
177
-
178
- it('should throw ConfigCorruptedError when lastUpdated is missing', () => {
179
- writeFileSync(
180
- testConfigFile,
181
- JSON.stringify({ version: '1.0.0' }),
182
- { mode: 0o600 }
183
- );
184
-
185
- const manager = new ConfigManager();
186
- expect(() => manager.loadConfig()).toThrow(ConfigCorruptedError);
187
- expect(() => manager.loadConfig()).toThrow(/lastUpdated/);
188
- });
189
-
190
- it('should throw ConfigCorruptedError when registryUrl is not a string', () => {
191
- writeFileSync(
192
- testConfigFile,
193
- JSON.stringify({
194
- version: '1.0.0',
195
- lastUpdated: '2024-01-01T00:00:00Z',
196
- registryUrl: 12345,
197
- }),
198
- { mode: 0o600 }
199
- );
200
-
201
- const manager = new ConfigManager();
202
- expect(() => manager.loadConfig()).toThrow(ConfigCorruptedError);
203
- expect(() => manager.loadConfig()).toThrow(/registryUrl must be a string/);
204
- });
205
-
206
- it('should throw ConfigCorruptedError when packages is not an object', () => {
207
- writeFileSync(
208
- testConfigFile,
209
- JSON.stringify({
210
- version: '1.0.0',
211
- lastUpdated: '2024-01-01T00:00:00Z',
212
- packages: 'not an object',
213
- }),
214
- { mode: 0o600 }
215
- );
216
-
217
- const manager = new ConfigManager();
218
- expect(() => manager.loadConfig()).toThrow(ConfigCorruptedError);
219
- expect(() => manager.loadConfig()).toThrow(/packages must be an object/);
220
- });
221
-
222
- it('should throw ConfigCorruptedError when package config is not an object', () => {
223
- writeFileSync(
224
- testConfigFile,
225
- JSON.stringify({
226
- version: '1.0.0',
227
- lastUpdated: '2024-01-01T00:00:00Z',
228
- packages: {
229
- '@scope/pkg': 'not an object',
230
- },
231
- }),
232
- { mode: 0o600 }
233
- );
234
-
235
- const manager = new ConfigManager();
236
- expect(() => manager.loadConfig()).toThrow(ConfigCorruptedError);
237
- expect(() => manager.loadConfig()).toThrow(/packages.@scope\/pkg must be an object/);
238
- });
239
-
240
- it('should throw ConfigCorruptedError when package config value is not a string', () => {
241
- writeFileSync(
242
- testConfigFile,
243
- JSON.stringify({
244
- version: '1.0.0',
245
- lastUpdated: '2024-01-01T00:00:00Z',
246
- packages: {
247
- '@scope/pkg': {
248
- api_key: 12345,
249
- },
250
- },
251
- }),
252
- { mode: 0o600 }
253
- );
254
-
255
- const manager = new ConfigManager();
256
- expect(() => manager.loadConfig()).toThrow(ConfigCorruptedError);
257
- expect(() => manager.loadConfig()).toThrow(/packages.@scope\/pkg.api_key must be a string/);
258
- });
259
-
260
- it('should throw ConfigCorruptedError for unknown fields', () => {
261
- writeFileSync(
262
- testConfigFile,
263
- JSON.stringify({
264
- version: '1.0.0',
265
- lastUpdated: '2024-01-01T00:00:00Z',
266
- unknownField: 'should not be here',
267
- }),
268
- { mode: 0o600 }
269
- );
270
-
271
- const manager = new ConfigManager();
272
- expect(() => manager.loadConfig()).toThrow(ConfigCorruptedError);
273
- expect(() => manager.loadConfig()).toThrow(/unknown field: unknownField/);
274
- });
275
-
276
- it('should include config path in error', () => {
277
- writeFileSync(testConfigFile, 'invalid json', { mode: 0o600 });
278
-
279
- const manager = new ConfigManager();
280
- try {
281
- manager.loadConfig();
282
- expect.fail('Should have thrown');
283
- } catch (err) {
284
- expect(err).toBeInstanceOf(ConfigCorruptedError);
285
- expect((err as ConfigCorruptedError).configPath).toBe(testConfigFile);
286
- }
287
- });
288
-
289
- it('should load valid minimal config', () => {
290
- writeFileSync(
291
- testConfigFile,
292
- JSON.stringify({
293
- version: '1.0.0',
294
- lastUpdated: '2024-01-01T00:00:00Z',
295
- }),
296
- { mode: 0o600 }
297
- );
298
-
299
- const manager = new ConfigManager();
300
- const config = manager.loadConfig();
301
- expect(config.version).toBe('1.0.0');
302
- expect(config.lastUpdated).toBe('2024-01-01T00:00:00Z');
303
- });
304
-
305
- it('should load valid full config', () => {
306
- writeFileSync(
307
- testConfigFile,
308
- JSON.stringify({
309
- version: '1.0.0',
310
- lastUpdated: '2024-01-01T00:00:00Z',
311
- registryUrl: 'https://custom.registry.com',
312
- packages: {
313
- '@scope/pkg': {
314
- api_key: 'secret',
315
- other_key: 'value',
316
- },
317
- },
318
- }),
319
- { mode: 0o600 }
320
- );
321
-
322
- const manager = new ConfigManager();
323
- const config = manager.loadConfig();
324
- expect(config.version).toBe('1.0.0');
325
- expect(config.registryUrl).toBe('https://custom.registry.com');
326
- expect(config.packages?.['@scope/pkg']?.api_key).toBe('secret');
327
- });
328
- });
329
-
330
- });