@scrymore/scry-deployer 0.0.2
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/LICENSE +21 -0
- package/README.md +889 -0
- package/bin/cli.js +351 -0
- package/lib/analysis.js +445 -0
- package/lib/apiClient.js +130 -0
- package/lib/archive.js +31 -0
- package/lib/archiveUtils.js +95 -0
- package/lib/config-store.js +47 -0
- package/lib/config.js +217 -0
- package/lib/errors.js +49 -0
- package/lib/init.js +478 -0
- package/lib/logger.js +48 -0
- package/lib/screencap.js +55 -0
- package/lib/templates.js +226 -0
- package/package.json +61 -0
- package/scripts/postinstall.js +7 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), '.scry');
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
7
|
+
|
|
8
|
+
function ensureConfigDir() {
|
|
9
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
10
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function loadConfig() {
|
|
15
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function saveConfig(config) {
|
|
26
|
+
ensureConfigDir();
|
|
27
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getAuthToken() {
|
|
31
|
+
const config = loadConfig();
|
|
32
|
+
return config.authToken;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function setAuthToken(token) {
|
|
36
|
+
const config = loadConfig();
|
|
37
|
+
config.authToken = token;
|
|
38
|
+
saveConfig(config);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
loadConfig,
|
|
43
|
+
saveConfig,
|
|
44
|
+
getAuthToken,
|
|
45
|
+
setAuthToken,
|
|
46
|
+
CONFIG_FILE
|
|
47
|
+
};
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default configuration for storybook-deployer
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_CONFIG = {
|
|
9
|
+
apiUrl: 'https://api.default-service.com/v1',
|
|
10
|
+
verbose: false,
|
|
11
|
+
// Users should set these values
|
|
12
|
+
apiKey: '',
|
|
13
|
+
dir: './storybook-static',
|
|
14
|
+
// Project and version for deployment
|
|
15
|
+
project: '',
|
|
16
|
+
version: '',
|
|
17
|
+
// Analysis options
|
|
18
|
+
withAnalysis: false,
|
|
19
|
+
storiesDir: null, // null enables auto-detection of .stories.* files
|
|
20
|
+
screenshotsDir: './__screenshots__',
|
|
21
|
+
storybookUrl: '',
|
|
22
|
+
storycapOptions: {
|
|
23
|
+
chromiumPath: '',
|
|
24
|
+
outDir: './__screenshots__',
|
|
25
|
+
parallel: undefined,
|
|
26
|
+
delay: undefined,
|
|
27
|
+
include: undefined,
|
|
28
|
+
exclude: undefined,
|
|
29
|
+
omitBackground: true
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get the config file path in the project directory
|
|
35
|
+
*/
|
|
36
|
+
function getConfigPath() {
|
|
37
|
+
return path.join(process.cwd(), '.storybook-deployer.json');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a default config file if it doesn't exist
|
|
42
|
+
*/
|
|
43
|
+
function createDefaultConfig() {
|
|
44
|
+
const configPath = getConfigPath();
|
|
45
|
+
|
|
46
|
+
if (!fs.existsSync(configPath)) {
|
|
47
|
+
try {
|
|
48
|
+
fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
49
|
+
console.log(`✅ Created config file at: ${configPath}`);
|
|
50
|
+
console.log('📝 Please edit this file to set your API key and other preferences.');
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.warn(`⚠️ Could not create config file at ${configPath}: ${error.message}`);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
console.log(`📁 Config file already exists at: ${configPath}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Detect if running in GitHub Actions and extract version information
|
|
61
|
+
*/
|
|
62
|
+
function detectGitHubActionsContext() {
|
|
63
|
+
// Only detect if running in GitHub Actions environment
|
|
64
|
+
if (!process.env.GITHUB_ACTIONS) {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const context = {};
|
|
69
|
+
|
|
70
|
+
// Detect version based on GitHub event type
|
|
71
|
+
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
72
|
+
const ref = process.env.GITHUB_REF; // e.g., refs/pull/17/merge, refs/heads/main
|
|
73
|
+
|
|
74
|
+
if (eventName === 'pull_request' && ref) {
|
|
75
|
+
// Extract PR number from ref like "refs/pull/17/merge"
|
|
76
|
+
const prMatch = ref.match(/refs\/pull\/(\d+)\//);
|
|
77
|
+
if (prMatch && prMatch[1]) {
|
|
78
|
+
context.version = `pr-${prMatch[1]}`;
|
|
79
|
+
}
|
|
80
|
+
} else if (ref) {
|
|
81
|
+
// For other events, extract branch name
|
|
82
|
+
if (ref.startsWith('refs/heads/')) {
|
|
83
|
+
const branch = ref.replace('refs/heads/', '');
|
|
84
|
+
// Use branch name, sanitize it for URL safety
|
|
85
|
+
context.version = branch.replace(/[^a-zA-Z0-9-_]/g, '-');
|
|
86
|
+
} else if (ref.startsWith('refs/tags/')) {
|
|
87
|
+
const tag = ref.replace('refs/tags/', '');
|
|
88
|
+
context.version = tag.replace(/[^a-zA-Z0-9-_.]/g, '-');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// If we couldn't determine version from ref, use commit SHA
|
|
93
|
+
if (!context.version && process.env.GITHUB_SHA) {
|
|
94
|
+
context.version = process.env.GITHUB_SHA.substring(0, 7);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return context;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Load environment variables with SCRY_ or STORYBOOK_DEPLOYER_ prefix
|
|
102
|
+
*/
|
|
103
|
+
function loadEnvConfig() {
|
|
104
|
+
const envConfig = {};
|
|
105
|
+
|
|
106
|
+
// Map environment variable names to config keys
|
|
107
|
+
const envMapping = {
|
|
108
|
+
'API_URL': 'apiUrl',
|
|
109
|
+
'VERBOSE': 'verbose',
|
|
110
|
+
'API_KEY': 'apiKey',
|
|
111
|
+
'DIR': 'dir',
|
|
112
|
+
'PROJECT': 'project',
|
|
113
|
+
'VERSION': 'version',
|
|
114
|
+
'WITH_ANALYSIS': 'withAnalysis',
|
|
115
|
+
'STORIES_DIR': 'storiesDir',
|
|
116
|
+
'SCREENSHOTS_DIR': 'screenshotsDir',
|
|
117
|
+
'STORYBOOK_URL': 'storybookUrl'
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
Object.keys(envMapping).forEach(envKey => {
|
|
121
|
+
const configKey = envMapping[envKey];
|
|
122
|
+
|
|
123
|
+
// Check SCRY_ prefix first (new standard), then fall back to STORYBOOK_DEPLOYER_ for backward compatibility
|
|
124
|
+
const scryEnvKey = 'SCRY_' + envKey;
|
|
125
|
+
const legacyEnvKey = 'STORYBOOK_DEPLOYER_' + envKey;
|
|
126
|
+
|
|
127
|
+
// Handle special case for PROJECT_ID vs PROJECT
|
|
128
|
+
let envValue = process.env[scryEnvKey];
|
|
129
|
+
if (!envValue && envKey === 'PROJECT') {
|
|
130
|
+
envValue = process.env['SCRY_PROJECT_ID'];
|
|
131
|
+
}
|
|
132
|
+
if (!envValue) {
|
|
133
|
+
envValue = process.env[legacyEnvKey];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Filter out undefined and empty string values to prevent overriding CLI args
|
|
137
|
+
if (envValue !== undefined && envValue !== '') {
|
|
138
|
+
// Convert string values to appropriate types
|
|
139
|
+
if (configKey === 'verbose' || configKey === 'withAnalysis') {
|
|
140
|
+
envConfig[configKey] = envValue.toLowerCase() === 'true';
|
|
141
|
+
} else {
|
|
142
|
+
envConfig[configKey] = envValue;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return envConfig;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Load configuration from file, merging with provided options
|
|
152
|
+
*/
|
|
153
|
+
function loadConfig(cliArgs = {}) {
|
|
154
|
+
const configPath = getConfigPath();
|
|
155
|
+
let fileConfig = {};
|
|
156
|
+
|
|
157
|
+
if (fs.existsSync(configPath)) {
|
|
158
|
+
try {
|
|
159
|
+
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
160
|
+
fileConfig = JSON.parse(configContent);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.warn(`⚠️ Could not read config file at ${configPath}: ${error.message}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Load environment variables
|
|
167
|
+
const envConfig = loadEnvConfig();
|
|
168
|
+
|
|
169
|
+
// Detect GitHub Actions context
|
|
170
|
+
const githubContext = detectGitHubActionsContext();
|
|
171
|
+
|
|
172
|
+
// Log configuration sources for debugging
|
|
173
|
+
console.log('[CONFIG] Configuration sources:');
|
|
174
|
+
console.log(`[CONFIG] - Default apiUrl: ${DEFAULT_CONFIG.apiUrl}`);
|
|
175
|
+
console.log(`[CONFIG] - File config apiUrl: ${fileConfig.apiUrl || 'not set'}`);
|
|
176
|
+
console.log(`[CONFIG] - Env SCRY_API_URL: ${process.env.SCRY_API_URL || 'not set'}`);
|
|
177
|
+
console.log(`[CONFIG] - Env STORYBOOK_DEPLOYER_API_URL: ${process.env.STORYBOOK_DEPLOYER_API_URL || 'not set'}`);
|
|
178
|
+
console.log(`[CONFIG] - CLI args apiUrl: ${cliArgs.apiUrl || 'not set'}`);
|
|
179
|
+
|
|
180
|
+
// Precedence: CLI arguments > GitHub Actions context > environment variables > file config > defaults
|
|
181
|
+
// This ensures GitHub Actions auto-detection works even if env vars are set to empty strings
|
|
182
|
+
|
|
183
|
+
// Map 'deployVersion' (from yargs --deploy-version) to 'version' for internal use
|
|
184
|
+
const normalizedCliArgs = { ...cliArgs };
|
|
185
|
+
if (normalizedCliArgs.deployVersion) {
|
|
186
|
+
normalizedCliArgs.version = normalizedCliArgs.deployVersion;
|
|
187
|
+
delete normalizedCliArgs.deployVersion;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const finalConfig = {
|
|
191
|
+
...DEFAULT_CONFIG,
|
|
192
|
+
...fileConfig,
|
|
193
|
+
...envConfig,
|
|
194
|
+
...githubContext,
|
|
195
|
+
...normalizedCliArgs
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
console.log(`[CONFIG] Final apiUrl: ${finalConfig.apiUrl}`);
|
|
199
|
+
console.log(`[CONFIG] Final project: ${finalConfig.project}`);
|
|
200
|
+
console.log(`[CONFIG] Final version: ${finalConfig.version}`);
|
|
201
|
+
|
|
202
|
+
return finalConfig;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get the path to the config file (for user reference)
|
|
207
|
+
*/
|
|
208
|
+
function getConfigFilePath() {
|
|
209
|
+
return getConfigPath();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = {
|
|
213
|
+
createDefaultConfig,
|
|
214
|
+
loadConfig,
|
|
215
|
+
getConfigFilePath,
|
|
216
|
+
DEFAULT_CONFIG
|
|
217
|
+
};
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for all custom application errors.
|
|
3
|
+
*/
|
|
4
|
+
class AppError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = this.constructor.name;
|
|
8
|
+
Error.captureStackTrace(this, this.constructor);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Error for issues related to file system operations (e.g., zipping).
|
|
14
|
+
*/
|
|
15
|
+
class FileSystemError extends AppError {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Error for failures in API communication.
|
|
23
|
+
*/
|
|
24
|
+
class ApiError extends AppError {
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} message The error message.
|
|
27
|
+
* @param {number} [statusCode] The HTTP status code of the response.
|
|
28
|
+
*/
|
|
29
|
+
constructor(message, statusCode) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.statusCode = statusCode;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Error for failures during the file upload process to cloud storage.
|
|
37
|
+
*/
|
|
38
|
+
class UploadError extends AppError {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = {
|
|
45
|
+
AppError,
|
|
46
|
+
FileSystemError,
|
|
47
|
+
ApiError,
|
|
48
|
+
UploadError,
|
|
49
|
+
};
|