Initial commit: Complete Shopify Price Updater implementation

- Full Node.js application with Shopify GraphQL API integration
- Compare At price support for promotional pricing
- Comprehensive error handling and retry logic
- Progress tracking with markdown logging
- Complete test suite with unit and integration tests
- Production-ready with proper exit codes and signal handling
This commit is contained in:
2025-08-05 10:05:05 -05:00
commit 1e6881ba86
29 changed files with 10663 additions and 0 deletions

119
src/config/environment.js Normal file
View File

@@ -0,0 +1,119 @@
const dotenv = require("dotenv");
// Load environment variables from .env file
dotenv.config();
/**
* Validates and loads environment configuration
* @returns {Object} Configuration object with validated environment variables
* @throws {Error} If required environment variables are missing or invalid
*/
function loadEnvironmentConfig() {
const config = {
shopDomain: process.env.SHOPIFY_SHOP_DOMAIN,
accessToken: process.env.SHOPIFY_ACCESS_TOKEN,
targetTag: process.env.TARGET_TAG,
priceAdjustmentPercentage: process.env.PRICE_ADJUSTMENT_PERCENTAGE,
};
// Validate required environment variables
const requiredVars = [
{
key: "SHOPIFY_SHOP_DOMAIN",
value: config.shopDomain,
name: "Shopify shop domain",
},
{
key: "SHOPIFY_ACCESS_TOKEN",
value: config.accessToken,
name: "Shopify access token",
},
{ key: "TARGET_TAG", value: config.targetTag, name: "Target product tag" },
{
key: "PRICE_ADJUSTMENT_PERCENTAGE",
value: config.priceAdjustmentPercentage,
name: "Price adjustment percentage",
},
];
const missingVars = [];
for (const variable of requiredVars) {
if (!variable.value || variable.value.trim() === "") {
missingVars.push(variable.key);
}
}
if (missingVars.length > 0) {
const errorMessage =
`Missing required environment variables: ${missingVars.join(", ")}\n` +
"Please check your .env file and ensure all required variables are set.\n" +
"See .env.example for reference.";
throw new Error(errorMessage);
}
// Validate and convert price adjustment percentage
const percentage = parseFloat(config.priceAdjustmentPercentage);
if (isNaN(percentage)) {
throw new Error(
`Invalid PRICE_ADJUSTMENT_PERCENTAGE: "${config.priceAdjustmentPercentage}". Must be a valid number.`
);
}
// Validate shop domain format
if (
!config.shopDomain.includes(".myshopify.com") &&
!config.shopDomain.includes(".")
) {
throw new Error(
`Invalid SHOPIFY_SHOP_DOMAIN: "${config.shopDomain}". Must be a valid Shopify domain (e.g., your-shop.myshopify.com)`
);
}
// Validate access token format (basic check)
if (config.accessToken.length < 10) {
throw new Error(
"Invalid SHOPIFY_ACCESS_TOKEN: Token appears to be too short. Please verify your access token."
);
}
// Validate target tag is not empty after trimming
const trimmedTag = config.targetTag.trim();
if (trimmedTag === "") {
throw new Error(
"Invalid TARGET_TAG: Tag cannot be empty or contain only whitespace."
);
}
// Return validated configuration
return {
shopDomain: config.shopDomain.trim(),
accessToken: config.accessToken.trim(),
targetTag: trimmedTag,
priceAdjustmentPercentage: percentage,
};
}
/**
* Get validated environment configuration
* Caches the configuration after first load
*/
let cachedConfig = null;
function getConfig() {
if (!cachedConfig) {
try {
cachedConfig = loadEnvironmentConfig();
} catch (error) {
console.error("Environment Configuration Error:");
console.error(error.message);
process.exit(1);
}
}
return cachedConfig;
}
module.exports = {
getConfig,
loadEnvironmentConfig, // Export for testing purposes
};