@sparkleideas/deployment 3.0.0-alpha.8

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.
@@ -0,0 +1,310 @@
1
+ /**
2
+ * Pre-Release Validator
3
+ * Validates package before release (lint, test, build, dependencies)
4
+ */
5
+
6
+ import { execSync } from 'child_process';
7
+ import { readFileSync, existsSync } from 'fs';
8
+ import { join } from 'path';
9
+ import type { ValidationOptions, ValidationResult, PackageInfo } from './types.js';
10
+
11
+ export class Validator {
12
+ private cwd: string;
13
+
14
+ constructor(cwd: string = process.cwd()) {
15
+ this.cwd = cwd;
16
+ }
17
+
18
+ /**
19
+ * Run all validation checks
20
+ */
21
+ async validate(options: ValidationOptions = {}): Promise<ValidationResult> {
22
+ const {
23
+ lint = true,
24
+ test = true,
25
+ build = true,
26
+ checkDependencies = true,
27
+ checkGitStatus = true,
28
+ lintCommand = 'npm run lint',
29
+ testCommand = 'npm test',
30
+ buildCommand = 'npm run build'
31
+ } = options;
32
+
33
+ const result: ValidationResult = {
34
+ valid: true,
35
+ checks: {},
36
+ errors: [],
37
+ warnings: []
38
+ };
39
+
40
+ // Validate package.json
41
+ console.log('Validating package.json...');
42
+ result.checks.packageJson = await this.validatePackageJson();
43
+ if (!result.checks.packageJson.passed) {
44
+ result.valid = false;
45
+ result.errors.push(...(result.checks.packageJson.errors || []));
46
+ }
47
+
48
+ // Check git status
49
+ if (checkGitStatus) {
50
+ console.log('Checking git status...');
51
+ result.checks.gitStatus = await this.checkGitStatus();
52
+ if (!result.checks.gitStatus.passed) {
53
+ result.warnings.push(...(result.checks.gitStatus.errors || []));
54
+ }
55
+ }
56
+
57
+ // Check dependencies
58
+ if (checkDependencies) {
59
+ console.log('Checking dependencies...');
60
+ result.checks.dependencies = await this.checkDependencies();
61
+ if (!result.checks.dependencies.passed) {
62
+ result.valid = false;
63
+ result.errors.push(...(result.checks.dependencies.errors || []));
64
+ }
65
+ }
66
+
67
+ // Run linter
68
+ if (lint) {
69
+ console.log('Running linter...');
70
+ result.checks.lint = await this.runLint(lintCommand);
71
+ if (!result.checks.lint.passed) {
72
+ result.valid = false;
73
+ result.errors.push(...(result.checks.lint.errors || []));
74
+ }
75
+ }
76
+
77
+ // Run tests
78
+ if (test) {
79
+ console.log('Running tests...');
80
+ result.checks.test = await this.runTests(testCommand);
81
+ if (!result.checks.test.passed) {
82
+ result.valid = false;
83
+ result.errors.push(...(result.checks.test.errors || []));
84
+ }
85
+ }
86
+
87
+ // Run build
88
+ if (build) {
89
+ console.log('Running build...');
90
+ result.checks.build = await this.runBuild(buildCommand);
91
+ if (!result.checks.build.passed) {
92
+ result.valid = false;
93
+ result.errors.push(...(result.checks.build.errors || []));
94
+ }
95
+ }
96
+
97
+ return result;
98
+ }
99
+
100
+ /**
101
+ * Validate package.json structure and required fields
102
+ */
103
+ private async validatePackageJson(): Promise<{ passed: boolean; errors?: string[] }> {
104
+ const errors: string[] = [];
105
+
106
+ try {
107
+ const pkgPath = join(this.cwd, 'package.json');
108
+ if (!existsSync(pkgPath)) {
109
+ errors.push('package.json not found');
110
+ return { passed: false, errors };
111
+ }
112
+
113
+ const pkg: PackageInfo = JSON.parse(readFileSync(pkgPath, 'utf-8'));
114
+
115
+ // Check required fields
116
+ if (!pkg.name) {
117
+ errors.push('package.json missing "name" field');
118
+ }
119
+ if (!pkg.version) {
120
+ errors.push('package.json missing "version" field');
121
+ }
122
+ if (!pkg.description) {
123
+ errors.push('package.json missing "description" field (warning)');
124
+ }
125
+
126
+ // Validate version format
127
+ if (pkg.version && !/^\d+\.\d+\.\d+(?:-[a-z]+\.\d+)?$/.test(pkg.version)) {
128
+ errors.push(`Invalid version format: ${pkg.version}`);
129
+ }
130
+
131
+ // Check for repository field
132
+ if (!pkg.repository) {
133
+ errors.push('package.json missing "repository" field (recommended)');
134
+ }
135
+
136
+ // Warn about private package
137
+ if (pkg.private) {
138
+ errors.push('Package is marked as private - cannot be published');
139
+ }
140
+
141
+ return {
142
+ passed: errors.length === 0,
143
+ errors: errors.length > 0 ? errors : undefined
144
+ };
145
+
146
+ } catch (error) {
147
+ errors.push(`Failed to parse package.json: ${error}`);
148
+ return { passed: false, errors };
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Check git status for uncommitted changes
154
+ */
155
+ private async checkGitStatus(): Promise<{ passed: boolean; errors?: string[] }> {
156
+ try {
157
+ const status = this.execCommand('git status --porcelain', true);
158
+
159
+ if (status.trim()) {
160
+ return {
161
+ passed: false,
162
+ errors: ['Uncommitted changes detected. Commit or stash changes before release.']
163
+ };
164
+ }
165
+
166
+ return { passed: true };
167
+ } catch (error) {
168
+ return {
169
+ passed: false,
170
+ errors: [`Git status check failed: ${error}`]
171
+ };
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Check for dependency issues
177
+ */
178
+ private async checkDependencies(): Promise<{ passed: boolean; errors?: string[] }> {
179
+ const errors: string[] = [];
180
+
181
+ try {
182
+ // Check for npm audit issues
183
+ try {
184
+ this.execCommand('npm audit --audit-level moderate', false);
185
+ } catch (error) {
186
+ errors.push('npm audit found security vulnerabilities');
187
+ }
188
+
189
+ // Check for outdated dependencies (non-critical)
190
+ try {
191
+ const outdated = this.execCommand('npm outdated --json', true);
192
+ if (outdated.trim()) {
193
+ const outdatedPkgs = JSON.parse(outdated);
194
+ const count = Object.keys(outdatedPkgs).length;
195
+ if (count > 0) {
196
+ errors.push(`${count} outdated dependencies found (warning)`);
197
+ }
198
+ }
199
+ } catch {
200
+ // Outdated check is non-critical
201
+ }
202
+
203
+ return {
204
+ passed: errors.length === 0,
205
+ errors: errors.length > 0 ? errors : undefined
206
+ };
207
+
208
+ } catch (error) {
209
+ errors.push(`Dependency check failed: ${error}`);
210
+ return { passed: false, errors };
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Run linter
216
+ */
217
+ private async runLint(command: string): Promise<{ passed: boolean; errors?: string[] }> {
218
+ try {
219
+ this.execCommand(command, false);
220
+ return { passed: true };
221
+ } catch (error) {
222
+ return {
223
+ passed: false,
224
+ errors: [`Linting failed: ${error}`]
225
+ };
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Run tests
231
+ */
232
+ private async runTests(command: string): Promise<{ passed: boolean; errors?: string[] }> {
233
+ try {
234
+ this.execCommand(command, false);
235
+ return { passed: true };
236
+ } catch (error) {
237
+ return {
238
+ passed: false,
239
+ errors: [`Tests failed: ${error}`]
240
+ };
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Run build
246
+ */
247
+ private async runBuild(command: string): Promise<{ passed: boolean; errors?: string[] }> {
248
+ try {
249
+ this.execCommand(command, false);
250
+ return { passed: true };
251
+ } catch (error) {
252
+ return {
253
+ passed: false,
254
+ errors: [`Build failed: ${error}`]
255
+ };
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Allowed commands for security validation
261
+ */
262
+ private static readonly ALLOWED_COMMAND_PREFIXES = [
263
+ 'npm run ',
264
+ 'npm ',
265
+ 'npx ',
266
+ 'git ',
267
+ ];
268
+
269
+ /**
270
+ * Execute command safely with validation
271
+ */
272
+ private execCommand(cmd: string, returnOutput = false): string {
273
+ // Validate: check for shell metacharacters
274
+ if (/[;&|`$()<>]/.test(cmd)) {
275
+ throw new Error(`Invalid command: contains shell metacharacters`);
276
+ }
277
+
278
+ // Validate: must start with allowed prefix
279
+ const isAllowed = Validator.ALLOWED_COMMAND_PREFIXES.some(
280
+ prefix => cmd.startsWith(prefix)
281
+ );
282
+ if (!isAllowed) {
283
+ throw new Error(`Command not allowed: ${cmd.split(' ')[0]}`);
284
+ }
285
+
286
+ try {
287
+ const output = execSync(cmd, {
288
+ cwd: this.cwd,
289
+ encoding: 'utf-8',
290
+ stdio: returnOutput ? 'pipe' : 'inherit',
291
+ timeout: 60000, // 60 second timeout for builds
292
+ maxBuffer: 50 * 1024 * 1024, // 50MB buffer for build output
293
+ });
294
+ return returnOutput ? output : '';
295
+ } catch (error) {
296
+ if (returnOutput && error instanceof Error) {
297
+ throw error;
298
+ }
299
+ throw error;
300
+ }
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Convenience function to validate package
306
+ */
307
+ export async function validate(options: ValidationOptions = {}): Promise<ValidationResult> {
308
+ const validator = new Validator();
309
+ return validator.validate(options);
310
+ }
package/tmp.json ADDED
File without changes
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }