@vizzly-testing/cli 0.20.0 → 0.20.1-beta.1
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.
- package/dist/api/client.js +134 -0
- package/dist/api/core.js +341 -0
- package/dist/api/endpoints.js +314 -0
- package/dist/api/index.js +19 -0
- package/dist/auth/client.js +91 -0
- package/dist/auth/core.js +176 -0
- package/dist/auth/index.js +30 -0
- package/dist/auth/operations.js +148 -0
- package/dist/cli.js +178 -3
- package/dist/client/index.js +144 -77
- package/dist/commands/doctor.js +121 -36
- package/dist/commands/finalize.js +49 -18
- package/dist/commands/init.js +13 -18
- package/dist/commands/login.js +49 -55
- package/dist/commands/logout.js +17 -9
- package/dist/commands/project.js +100 -71
- package/dist/commands/run.js +189 -95
- package/dist/commands/status.js +101 -66
- package/dist/commands/tdd-daemon.js +61 -32
- package/dist/commands/tdd.js +104 -98
- package/dist/commands/upload.js +78 -34
- package/dist/commands/whoami.js +44 -42
- package/dist/config/core.js +438 -0
- package/dist/config/index.js +13 -0
- package/dist/config/operations.js +327 -0
- package/dist/index.js +1 -1
- package/dist/project/core.js +295 -0
- package/dist/project/index.js +13 -0
- package/dist/project/operations.js +393 -0
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +16 -16
- package/dist/screenshot-server/core.js +157 -0
- package/dist/screenshot-server/index.js +11 -0
- package/dist/screenshot-server/operations.js +183 -0
- package/dist/sdk/index.js +3 -2
- package/dist/server/handlers/api-handler.js +14 -5
- package/dist/server/handlers/tdd-handler.js +191 -53
- package/dist/server/http-server.js +9 -3
- package/dist/server/routers/baseline.js +58 -0
- package/dist/server/routers/dashboard.js +10 -6
- package/dist/server/routers/screenshot.js +32 -0
- package/dist/server-manager/core.js +186 -0
- package/dist/server-manager/index.js +81 -0
- package/dist/server-manager/operations.js +209 -0
- package/dist/services/build-manager.js +2 -69
- package/dist/services/index.js +21 -48
- package/dist/services/screenshot-server.js +40 -74
- package/dist/services/server-manager.js +45 -80
- package/dist/services/test-runner.js +90 -250
- package/dist/services/uploader.js +56 -358
- package/dist/tdd/core/hotspot-coverage.js +112 -0
- package/dist/tdd/core/signature.js +101 -0
- package/dist/tdd/index.js +19 -0
- package/dist/tdd/metadata/baseline-metadata.js +103 -0
- package/dist/tdd/metadata/hotspot-metadata.js +93 -0
- package/dist/tdd/services/baseline-downloader.js +151 -0
- package/dist/tdd/services/baseline-manager.js +166 -0
- package/dist/tdd/services/comparison-service.js +230 -0
- package/dist/tdd/services/hotspot-service.js +71 -0
- package/dist/tdd/services/result-service.js +123 -0
- package/dist/tdd/tdd-service.js +1145 -0
- package/dist/test-runner/core.js +255 -0
- package/dist/test-runner/index.js +13 -0
- package/dist/test-runner/operations.js +483 -0
- package/dist/types/client.d.ts +25 -2
- package/dist/uploader/core.js +396 -0
- package/dist/uploader/index.js +11 -0
- package/dist/uploader/operations.js +412 -0
- package/dist/utils/colors.js +187 -39
- package/dist/utils/config-loader.js +3 -6
- package/dist/utils/context.js +228 -0
- package/dist/utils/output.js +449 -14
- package/docs/api-reference.md +173 -8
- package/docs/tui-elements.md +560 -0
- package/package.json +13 -13
- package/dist/services/api-service.js +0 -412
- package/dist/services/auth-service.js +0 -226
- package/dist/services/config-service.js +0 -369
- package/dist/services/html-report-generator.js +0 -455
- package/dist/services/project-service.js +0 -326
- package/dist/services/report-generator/report.css +0 -411
- package/dist/services/report-generator/viewer.js +0 -102
- package/dist/services/static-report-generator.js +0 -207
- package/dist/services/tdd-service.js +0 -1437
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Operations - Configuration operations with dependency injection
|
|
3
|
+
*
|
|
4
|
+
* Each operation takes its dependencies as parameters:
|
|
5
|
+
* - explorer: cosmiconfig explorer for project config
|
|
6
|
+
* - globalConfigStore: for reading/writing global config
|
|
7
|
+
* - fileWriter: for writing project config files
|
|
8
|
+
*
|
|
9
|
+
* This makes them trivially testable without mocking modules.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
14
|
+
import { buildGlobalConfigResult, buildMergedConfigResult, buildProjectConfigResult, deepMerge, extractCosmiconfigResult, extractEnvOverrides, getConfigFormat, serializeConfig, validateReadScope, validateWriteScope } from './core.js';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Read Operations
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get project-level config from vizzly.config.js or similar
|
|
22
|
+
* @param {Object} explorer - Cosmiconfig explorer with search method
|
|
23
|
+
* @param {string} projectRoot - Project root directory
|
|
24
|
+
* @returns {Promise<{ config: Object, filepath: string|null, isEmpty: boolean }>}
|
|
25
|
+
*/
|
|
26
|
+
export async function getProjectConfig(explorer, projectRoot) {
|
|
27
|
+
let result = explorer.search(projectRoot);
|
|
28
|
+
let {
|
|
29
|
+
config,
|
|
30
|
+
filepath
|
|
31
|
+
} = extractCosmiconfigResult(result);
|
|
32
|
+
return buildProjectConfigResult(config, filepath);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get global config from ~/.vizzly/config.json
|
|
37
|
+
* @param {Object} globalConfigStore - Store with load and getPath methods
|
|
38
|
+
* @returns {Promise<{ config: Object, filepath: string, isEmpty: boolean }>}
|
|
39
|
+
*/
|
|
40
|
+
export async function getGlobalConfig(globalConfigStore) {
|
|
41
|
+
let config = await globalConfigStore.load();
|
|
42
|
+
let filepath = globalConfigStore.getPath();
|
|
43
|
+
return buildGlobalConfigResult(config, filepath);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get merged config from all sources with source tracking
|
|
48
|
+
* @param {Object} options - Options
|
|
49
|
+
* @param {Object} options.explorer - Cosmiconfig explorer
|
|
50
|
+
* @param {Object} options.globalConfigStore - Global config store
|
|
51
|
+
* @param {string} options.projectRoot - Project root directory
|
|
52
|
+
* @param {Object} [options.env] - Environment variables (defaults to process.env)
|
|
53
|
+
* @returns {Promise<{ config: Object, sources: Object, projectFilepath: string|null, globalFilepath: string }>}
|
|
54
|
+
*/
|
|
55
|
+
export async function getMergedConfig({
|
|
56
|
+
explorer,
|
|
57
|
+
globalConfigStore,
|
|
58
|
+
projectRoot,
|
|
59
|
+
env = process.env
|
|
60
|
+
}) {
|
|
61
|
+
let projectConfigData = await getProjectConfig(explorer, projectRoot);
|
|
62
|
+
let globalConfigData = await getGlobalConfig(globalConfigStore);
|
|
63
|
+
let envOverrides = extractEnvOverrides(env);
|
|
64
|
+
return buildMergedConfigResult({
|
|
65
|
+
projectConfig: projectConfigData.config,
|
|
66
|
+
globalConfig: globalConfigData.config,
|
|
67
|
+
envOverrides,
|
|
68
|
+
projectFilepath: projectConfigData.filepath,
|
|
69
|
+
globalFilepath: globalConfigData.filepath
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get configuration based on scope
|
|
75
|
+
* @param {Object} options - Options
|
|
76
|
+
* @param {string} options.scope - 'project', 'global', or 'merged'
|
|
77
|
+
* @param {Object} options.explorer - Cosmiconfig explorer
|
|
78
|
+
* @param {Object} options.globalConfigStore - Global config store
|
|
79
|
+
* @param {string} options.projectRoot - Project root directory
|
|
80
|
+
* @param {Object} [options.env] - Environment variables
|
|
81
|
+
* @returns {Promise<Object>} Config result based on scope
|
|
82
|
+
*/
|
|
83
|
+
export async function getConfig({
|
|
84
|
+
scope = 'merged',
|
|
85
|
+
explorer,
|
|
86
|
+
globalConfigStore,
|
|
87
|
+
projectRoot,
|
|
88
|
+
env
|
|
89
|
+
}) {
|
|
90
|
+
let validation = validateReadScope(scope);
|
|
91
|
+
if (!validation.valid) {
|
|
92
|
+
throw validation.error;
|
|
93
|
+
}
|
|
94
|
+
if (scope === 'project') {
|
|
95
|
+
return getProjectConfig(explorer, projectRoot);
|
|
96
|
+
}
|
|
97
|
+
if (scope === 'global') {
|
|
98
|
+
return getGlobalConfig(globalConfigStore);
|
|
99
|
+
}
|
|
100
|
+
return getMergedConfig({
|
|
101
|
+
explorer,
|
|
102
|
+
globalConfigStore,
|
|
103
|
+
projectRoot,
|
|
104
|
+
env
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ============================================================================
|
|
109
|
+
// Write Operations
|
|
110
|
+
// ============================================================================
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Update project-level config
|
|
114
|
+
* @param {Object} options - Options
|
|
115
|
+
* @param {Object} options.updates - Config updates to apply
|
|
116
|
+
* @param {Object} options.explorer - Cosmiconfig explorer
|
|
117
|
+
* @param {string} options.projectRoot - Project root directory
|
|
118
|
+
* @param {Function} options.writeFile - Async file writer (path, content) => Promise
|
|
119
|
+
* @param {Function} options.readFile - Async file reader (path) => Promise<string>
|
|
120
|
+
* @param {Function} options.validate - Config validator
|
|
121
|
+
* @returns {Promise<{ config: Object, filepath: string }>}
|
|
122
|
+
*/
|
|
123
|
+
export async function updateProjectConfig({
|
|
124
|
+
updates,
|
|
125
|
+
explorer,
|
|
126
|
+
projectRoot,
|
|
127
|
+
writeFile,
|
|
128
|
+
readFile,
|
|
129
|
+
validate
|
|
130
|
+
}) {
|
|
131
|
+
let result = explorer.search(projectRoot);
|
|
132
|
+
let {
|
|
133
|
+
config: currentConfig,
|
|
134
|
+
filepath: configPath
|
|
135
|
+
} = extractCosmiconfigResult(result);
|
|
136
|
+
|
|
137
|
+
// Determine config file path - create new if none exists
|
|
138
|
+
if (!configPath) {
|
|
139
|
+
configPath = join(projectRoot, 'vizzly.config.js');
|
|
140
|
+
currentConfig = {};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Merge updates with current config
|
|
144
|
+
let newConfig = deepMerge(currentConfig, updates);
|
|
145
|
+
|
|
146
|
+
// Validate before writing
|
|
147
|
+
try {
|
|
148
|
+
validate(newConfig);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
throw new VizzlyError(`Invalid configuration: ${error.message}`, 'CONFIG_VALIDATION_ERROR', {
|
|
151
|
+
errors: error.errors
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Write config file based on format
|
|
156
|
+
await writeProjectConfigFile({
|
|
157
|
+
filepath: configPath,
|
|
158
|
+
config: newConfig,
|
|
159
|
+
writeFile,
|
|
160
|
+
readFile
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Clear cosmiconfig cache
|
|
164
|
+
explorer.clearCaches();
|
|
165
|
+
return {
|
|
166
|
+
config: newConfig,
|
|
167
|
+
filepath: configPath
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Write project config to file
|
|
173
|
+
* @param {Object} options - Options
|
|
174
|
+
* @param {string} options.filepath - Path to write to
|
|
175
|
+
* @param {Object} options.config - Config object
|
|
176
|
+
* @param {Function} options.writeFile - Async file writer
|
|
177
|
+
* @param {Function} options.readFile - Async file reader (for package.json)
|
|
178
|
+
* @returns {Promise<void>}
|
|
179
|
+
*/
|
|
180
|
+
export async function writeProjectConfigFile({
|
|
181
|
+
filepath,
|
|
182
|
+
config,
|
|
183
|
+
writeFile,
|
|
184
|
+
readFile
|
|
185
|
+
}) {
|
|
186
|
+
let format = getConfigFormat(filepath);
|
|
187
|
+
if (format === 'package') {
|
|
188
|
+
// For package.json, merge into existing
|
|
189
|
+
let pkgContent = await readFile(filepath);
|
|
190
|
+
let pkg;
|
|
191
|
+
try {
|
|
192
|
+
pkg = JSON.parse(pkgContent);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
throw new VizzlyError(`Failed to parse package.json: ${error.message}`, 'INVALID_PACKAGE_JSON');
|
|
195
|
+
}
|
|
196
|
+
pkg.vizzly = config;
|
|
197
|
+
await writeFile(filepath, JSON.stringify(pkg, null, 2));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
let serialized = serializeConfig(config, filepath);
|
|
201
|
+
if (serialized.error) {
|
|
202
|
+
throw serialized.error;
|
|
203
|
+
}
|
|
204
|
+
await writeFile(filepath, serialized.content);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Update global config
|
|
209
|
+
* @param {Object} options - Options
|
|
210
|
+
* @param {Object} options.updates - Config updates to apply
|
|
211
|
+
* @param {Object} options.globalConfigStore - Global config store with load and save methods
|
|
212
|
+
* @returns {Promise<{ config: Object, filepath: string }>}
|
|
213
|
+
*/
|
|
214
|
+
export async function updateGlobalConfig({
|
|
215
|
+
updates,
|
|
216
|
+
globalConfigStore
|
|
217
|
+
}) {
|
|
218
|
+
let currentConfig = await globalConfigStore.load();
|
|
219
|
+
let newConfig = deepMerge(currentConfig, updates);
|
|
220
|
+
await globalConfigStore.save(newConfig);
|
|
221
|
+
return {
|
|
222
|
+
config: newConfig,
|
|
223
|
+
filepath: globalConfigStore.getPath()
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Update configuration based on scope
|
|
229
|
+
* @param {Object} options - Options
|
|
230
|
+
* @param {string} options.scope - 'project' or 'global'
|
|
231
|
+
* @param {Object} options.updates - Config updates to apply
|
|
232
|
+
* @param {Object} options.explorer - Cosmiconfig explorer
|
|
233
|
+
* @param {Object} options.globalConfigStore - Global config store
|
|
234
|
+
* @param {string} options.projectRoot - Project root directory
|
|
235
|
+
* @param {Function} options.writeFile - Async file writer
|
|
236
|
+
* @param {Function} options.readFile - Async file reader
|
|
237
|
+
* @param {Function} options.validate - Config validator
|
|
238
|
+
* @returns {Promise<{ config: Object, filepath: string }>}
|
|
239
|
+
*/
|
|
240
|
+
export async function updateConfig({
|
|
241
|
+
scope,
|
|
242
|
+
updates,
|
|
243
|
+
explorer,
|
|
244
|
+
globalConfigStore,
|
|
245
|
+
projectRoot,
|
|
246
|
+
writeFile,
|
|
247
|
+
readFile,
|
|
248
|
+
validate
|
|
249
|
+
}) {
|
|
250
|
+
let validation = validateWriteScope(scope);
|
|
251
|
+
if (!validation.valid) {
|
|
252
|
+
throw validation.error;
|
|
253
|
+
}
|
|
254
|
+
if (scope === 'project') {
|
|
255
|
+
return updateProjectConfig({
|
|
256
|
+
updates,
|
|
257
|
+
explorer,
|
|
258
|
+
projectRoot,
|
|
259
|
+
writeFile,
|
|
260
|
+
readFile,
|
|
261
|
+
validate
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return updateGlobalConfig({
|
|
265
|
+
updates,
|
|
266
|
+
globalConfigStore
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ============================================================================
|
|
271
|
+
// Validation Operations
|
|
272
|
+
// ============================================================================
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Validate configuration object
|
|
276
|
+
* @param {Object} config - Config to validate
|
|
277
|
+
* @param {Function} validateFn - Validation function
|
|
278
|
+
* @returns {{ valid: boolean, config: Object|null, errors: Array }}
|
|
279
|
+
*/
|
|
280
|
+
export function validateConfig(config, validateFn) {
|
|
281
|
+
try {
|
|
282
|
+
let validated = validateFn(config);
|
|
283
|
+
return {
|
|
284
|
+
valid: true,
|
|
285
|
+
config: validated,
|
|
286
|
+
errors: []
|
|
287
|
+
};
|
|
288
|
+
} catch (error) {
|
|
289
|
+
return {
|
|
290
|
+
valid: false,
|
|
291
|
+
config: null,
|
|
292
|
+
errors: error.errors || [{
|
|
293
|
+
message: error.message
|
|
294
|
+
}]
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ============================================================================
|
|
300
|
+
// Source Lookup
|
|
301
|
+
// ============================================================================
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get the source of a specific config key
|
|
305
|
+
* @param {Object} options - Options
|
|
306
|
+
* @param {string} options.key - Config key to look up
|
|
307
|
+
* @param {Object} options.explorer - Cosmiconfig explorer
|
|
308
|
+
* @param {Object} options.globalConfigStore - Global config store
|
|
309
|
+
* @param {string} options.projectRoot - Project root directory
|
|
310
|
+
* @param {Object} [options.env] - Environment variables
|
|
311
|
+
* @returns {Promise<string>} Source ('default', 'global', 'project', 'env', 'unknown')
|
|
312
|
+
*/
|
|
313
|
+
export async function getConfigSource({
|
|
314
|
+
key,
|
|
315
|
+
explorer,
|
|
316
|
+
globalConfigStore,
|
|
317
|
+
projectRoot,
|
|
318
|
+
env
|
|
319
|
+
}) {
|
|
320
|
+
let merged = await getMergedConfig({
|
|
321
|
+
explorer,
|
|
322
|
+
globalConfigStore,
|
|
323
|
+
projectRoot,
|
|
324
|
+
env
|
|
325
|
+
});
|
|
326
|
+
return merged.sources[key] || 'unknown';
|
|
327
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -15,9 +15,9 @@ export { UploadError } from './errors/vizzly-error.js';
|
|
|
15
15
|
// Primary SDK export
|
|
16
16
|
export { createVizzly } from './sdk/index.js';
|
|
17
17
|
export { createServices } from './services/index.js';
|
|
18
|
-
export { createTDDService } from './services/tdd-service.js';
|
|
19
18
|
// Core services (for advanced usage)
|
|
20
19
|
export { createUploader } from './services/uploader.js';
|
|
20
|
+
export { createTDDService } from './tdd/tdd-service.js';
|
|
21
21
|
// Configuration helper
|
|
22
22
|
export { defineConfig } from './utils/config-helpers.js';
|
|
23
23
|
// Utilities
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Core - Pure functions for project logic
|
|
3
|
+
*
|
|
4
|
+
* No I/O, no side effects - just data transformations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Validation
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validate that a directory path is provided
|
|
15
|
+
* @param {string} directory - Directory path to validate
|
|
16
|
+
* @returns {{ valid: boolean, error: Error|null }}
|
|
17
|
+
*/
|
|
18
|
+
export function validateDirectory(directory) {
|
|
19
|
+
if (!directory) {
|
|
20
|
+
return {
|
|
21
|
+
valid: false,
|
|
22
|
+
error: new VizzlyError('Directory path is required', 'INVALID_DIRECTORY')
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
valid: true,
|
|
27
|
+
error: null
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Validate project data for mapping creation
|
|
33
|
+
* @param {Object} projectData - Project data to validate
|
|
34
|
+
* @param {string} [projectData.projectSlug] - Project slug
|
|
35
|
+
* @param {string} [projectData.organizationSlug] - Organization slug
|
|
36
|
+
* @param {string} [projectData.token] - Project token
|
|
37
|
+
* @returns {{ valid: boolean, error: Error|null }}
|
|
38
|
+
*/
|
|
39
|
+
export function validateProjectData(projectData) {
|
|
40
|
+
if (!projectData.projectSlug) {
|
|
41
|
+
return {
|
|
42
|
+
valid: false,
|
|
43
|
+
error: new VizzlyError('Project slug is required', 'INVALID_PROJECT_DATA')
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (!projectData.organizationSlug) {
|
|
47
|
+
return {
|
|
48
|
+
valid: false,
|
|
49
|
+
error: new VizzlyError('Organization slug is required', 'INVALID_PROJECT_DATA')
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (!projectData.token) {
|
|
53
|
+
return {
|
|
54
|
+
valid: false,
|
|
55
|
+
error: new VizzlyError('Project token is required', 'INVALID_PROJECT_DATA')
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
valid: true,
|
|
60
|
+
error: null
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Mapping Transformations
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Convert mappings object to array with directory paths included
|
|
70
|
+
* @param {Object} mappings - Object with directory paths as keys
|
|
71
|
+
* @returns {Array<Object>} Array of mappings with directory property
|
|
72
|
+
*/
|
|
73
|
+
export function mappingsToArray(mappings) {
|
|
74
|
+
return Object.entries(mappings).map(([directory, data]) => ({
|
|
75
|
+
directory,
|
|
76
|
+
...data
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Build a mapping result object
|
|
82
|
+
* @param {string} directory - Directory path
|
|
83
|
+
* @param {Object} projectData - Project data
|
|
84
|
+
* @returns {Object} Mapping result with directory included
|
|
85
|
+
*/
|
|
86
|
+
export function buildMappingResult(directory, projectData) {
|
|
87
|
+
return {
|
|
88
|
+
directory,
|
|
89
|
+
...projectData
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// API Request Helpers
|
|
95
|
+
// ============================================================================
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Build query params for builds API request
|
|
99
|
+
* @param {Object} options - Query options
|
|
100
|
+
* @param {number} [options.limit] - Number of builds to fetch
|
|
101
|
+
* @param {string} [options.branch] - Filter by branch
|
|
102
|
+
* @returns {string} Query string (empty string if no params)
|
|
103
|
+
*/
|
|
104
|
+
export function buildBuildsQueryParams(options = {}) {
|
|
105
|
+
let params = new globalThis.URLSearchParams();
|
|
106
|
+
if (options.limit) {
|
|
107
|
+
params.append('limit', String(options.limit));
|
|
108
|
+
}
|
|
109
|
+
if (options.branch) {
|
|
110
|
+
params.append('branch', options.branch);
|
|
111
|
+
}
|
|
112
|
+
let query = params.toString();
|
|
113
|
+
return query ? `?${query}` : '';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build organization header object
|
|
118
|
+
* @param {string} organizationSlug - Organization slug
|
|
119
|
+
* @returns {Object} Headers object with X-Organization header
|
|
120
|
+
*/
|
|
121
|
+
export function buildOrgHeader(organizationSlug) {
|
|
122
|
+
return {
|
|
123
|
+
'X-Organization': organizationSlug
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build API URL for project endpoint
|
|
129
|
+
* @param {string} projectSlug - Project slug
|
|
130
|
+
* @returns {string} API URL path
|
|
131
|
+
*/
|
|
132
|
+
export function buildProjectUrl(projectSlug) {
|
|
133
|
+
return `/api/project/${projectSlug}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Build API URL for builds endpoint
|
|
138
|
+
* @param {string} projectSlug - Project slug
|
|
139
|
+
* @param {Object} options - Query options
|
|
140
|
+
* @returns {string} Full API URL path with query params
|
|
141
|
+
*/
|
|
142
|
+
export function buildBuildsUrl(projectSlug, options = {}) {
|
|
143
|
+
let queryString = buildBuildsQueryParams(options);
|
|
144
|
+
return `/api/build/${projectSlug}${queryString}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Build API URL for project tokens endpoint
|
|
149
|
+
* @param {string} organizationSlug - Organization slug
|
|
150
|
+
* @param {string} projectSlug - Project slug
|
|
151
|
+
* @param {string} [tokenId] - Optional token ID for specific token operations
|
|
152
|
+
* @returns {string} API URL path
|
|
153
|
+
*/
|
|
154
|
+
export function buildTokensUrl(organizationSlug, projectSlug, tokenId) {
|
|
155
|
+
let base = `/api/cli/organizations/${organizationSlug}/projects/${projectSlug}/tokens`;
|
|
156
|
+
return tokenId ? `${base}/${tokenId}` : base;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// Response Extraction
|
|
161
|
+
// ============================================================================
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Extract projects from API response
|
|
165
|
+
* @param {Object} response - API response
|
|
166
|
+
* @returns {Array} Array of projects
|
|
167
|
+
*/
|
|
168
|
+
export function extractProjects(response) {
|
|
169
|
+
return response?.projects || [];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Extract project from API response
|
|
174
|
+
* @param {Object} response - API response
|
|
175
|
+
* @returns {Object} Project object
|
|
176
|
+
*/
|
|
177
|
+
export function extractProject(response) {
|
|
178
|
+
return response?.project || response;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Extract builds from API response
|
|
183
|
+
* @param {Object} response - API response
|
|
184
|
+
* @returns {Array} Array of builds
|
|
185
|
+
*/
|
|
186
|
+
export function extractBuilds(response) {
|
|
187
|
+
return response?.builds || [];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Extract token from API response
|
|
192
|
+
* @param {Object} response - API response
|
|
193
|
+
* @returns {Object} Token object
|
|
194
|
+
*/
|
|
195
|
+
export function extractToken(response) {
|
|
196
|
+
return response?.token;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Extract tokens from API response
|
|
201
|
+
* @param {Object} response - API response
|
|
202
|
+
* @returns {Array} Array of tokens
|
|
203
|
+
*/
|
|
204
|
+
export function extractTokens(response) {
|
|
205
|
+
return response?.tokens || [];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Enrich projects with organization info
|
|
210
|
+
* @param {Array} projects - Array of projects
|
|
211
|
+
* @param {Object} org - Organization object
|
|
212
|
+
* @param {string} org.slug - Organization slug
|
|
213
|
+
* @param {string} org.name - Organization name
|
|
214
|
+
* @returns {Array} Projects with organization info added
|
|
215
|
+
*/
|
|
216
|
+
export function enrichProjectsWithOrg(projects, org) {
|
|
217
|
+
return projects.map(project => ({
|
|
218
|
+
...project,
|
|
219
|
+
organizationSlug: org.slug,
|
|
220
|
+
organizationName: org.name
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Extract organizations from whoami response
|
|
226
|
+
* @param {Object} whoamiResponse - Whoami API response
|
|
227
|
+
* @returns {Array} Array of organizations
|
|
228
|
+
*/
|
|
229
|
+
export function extractOrganizations(whoamiResponse) {
|
|
230
|
+
return whoamiResponse?.organizations || [];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ============================================================================
|
|
234
|
+
// Error Building
|
|
235
|
+
// ============================================================================
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Build error for project fetch failure
|
|
239
|
+
* @param {Error} originalError - Original error
|
|
240
|
+
* @returns {VizzlyError} Wrapped error
|
|
241
|
+
*/
|
|
242
|
+
export function buildProjectFetchError(originalError) {
|
|
243
|
+
return new VizzlyError(`Failed to fetch project: ${originalError.message}`, 'PROJECT_FETCH_FAILED', {
|
|
244
|
+
originalError
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Build error for no authentication available
|
|
250
|
+
* @returns {VizzlyError} No auth error
|
|
251
|
+
*/
|
|
252
|
+
export function buildNoAuthError() {
|
|
253
|
+
return new VizzlyError('No authentication available', 'NO_AUTH_SERVICE');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Build error for no API service available
|
|
258
|
+
* @returns {VizzlyError} No API service error
|
|
259
|
+
*/
|
|
260
|
+
export function buildNoApiServiceError() {
|
|
261
|
+
return new VizzlyError('API service not available', 'NO_API_SERVICE');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Build error for token creation failure
|
|
266
|
+
* @param {Error} originalError - Original error
|
|
267
|
+
* @returns {VizzlyError} Wrapped error
|
|
268
|
+
*/
|
|
269
|
+
export function buildTokenCreateError(originalError) {
|
|
270
|
+
return new VizzlyError(`Failed to create project token: ${originalError.message}`, 'TOKEN_CREATE_FAILED', {
|
|
271
|
+
originalError
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Build error for token fetch failure
|
|
277
|
+
* @param {Error} originalError - Original error
|
|
278
|
+
* @returns {VizzlyError} Wrapped error
|
|
279
|
+
*/
|
|
280
|
+
export function buildTokensFetchError(originalError) {
|
|
281
|
+
return new VizzlyError(`Failed to fetch project tokens: ${originalError.message}`, 'TOKENS_FETCH_FAILED', {
|
|
282
|
+
originalError
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Build error for token revoke failure
|
|
288
|
+
* @param {Error} originalError - Original error
|
|
289
|
+
* @returns {VizzlyError} Wrapped error
|
|
290
|
+
*/
|
|
291
|
+
export function buildTokenRevokeError(originalError) {
|
|
292
|
+
return new VizzlyError(`Failed to revoke project token: ${originalError.message}`, 'TOKEN_REVOKE_FAILED', {
|
|
293
|
+
originalError
|
|
294
|
+
});
|
|
295
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Module - Public exports
|
|
3
|
+
*
|
|
4
|
+
* Provides functional project management primitives:
|
|
5
|
+
* - core.js: Pure functions for validation, URL building, response extraction
|
|
6
|
+
* - operations.js: Project operations with dependency injection
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Core pure functions
|
|
10
|
+
export { buildBuildsQueryParams, buildBuildsUrl, buildMappingResult, buildNoApiServiceError, buildNoAuthError, buildOrgHeader, buildProjectFetchError, buildProjectUrl, buildTokenCreateError, buildTokenRevokeError, buildTokensFetchError, buildTokensUrl, enrichProjectsWithOrg, extractBuilds, extractOrganizations, extractProject, extractProjects, extractToken, extractTokens, mappingsToArray, validateDirectory, validateProjectData } from './core.js';
|
|
11
|
+
|
|
12
|
+
// Project operations (take dependencies as parameters)
|
|
13
|
+
export { createMapping, createProjectToken, getMapping, getProject, getProjectWithApiToken, getProjectWithOAuth, getRecentBuilds, getRecentBuildsWithApiToken, getRecentBuildsWithOAuth, listMappings, listProjects, listProjectsWithApiToken, listProjectsWithOAuth, listProjectTokens, removeMapping, revokeProjectToken, switchProject } from './operations.js';
|