Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion src/helper/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export async function downloadFile(url: string, filePath: string) {

response.data.pipe(writer);

return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
Expand Down
278 changes: 278 additions & 0 deletions src/helper/locales.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
import axios, { AxiosResponse } from 'axios';
import { getActiveContext } from '../helper/utils';
import { promises as fs } from 'fs';
import fs_sync from 'fs';
import * as path from 'path';
import LocalesService from '../lib/api/services/locales.service';

/**
* Defines the synchronization mode for locale operations
* @enum {string}
*/
export enum SyncMode {
/** Pull changes from remote to local */
PULL = 'pull',
/** Push changes from local to remote */
PUSH = 'push'
}

interface ApiConfig {
baseUrl: string;
headers: Record<string, string>;
}

interface LocaleResource {
locale: string;
resource: Record<string, any>;
type: string;
}

export const hasAnyDeltaBetweenLocalAndRemoteLocales = async (): Promise<boolean> => {
let comparedFiles = 0; // Track the number of files compared
try {
console.log('Starting comparison between local and remote data');

const localesFolder: string = path.resolve(process.cwd(), 'theme/locales');

if (!fs_sync.existsSync(localesFolder)) {
console.log('Locales folder does not exist');
return false;
}


// Fetch remote data from the API
const response: AxiosResponse = await LocalesService.getLocalesByThemeId(null);

console.log('Response received from API:', response.status);

if (response.status === 200) {
const data = response.data; // Extract the data from the API response
console.log('Remote data retrieved:', data);

// Ensure the locales folder exists
await fs.mkdir(localesFolder, { recursive: true });
console.log('Locales folder ensured at:', localesFolder);

// Read all files in the local locales folder
const localFiles = await fs.readdir(localesFolder);
console.log('Local files found:', localFiles);

// Compare each local file with the corresponding remote resource
for (const file of localFiles) {
let locale = path.basename(file, '.json'); // Extract the locale name from the file name
console.log('Processing local file:', file);
const localeType = locale.includes('schema') ? 'locale_schema' : 'locale';
locale = locale.includes('schema') ? locale.replace('.schema', '') : locale;
const localData = JSON.parse(await fs.readFile(path.join(localesFolder, file), 'utf-8')); // Read and parse the local file
const matchingItem = data.items.find((item: LocaleResource) => item.locale === locale && item.type === localeType); // Find the corresponding remote item

if (!matchingItem) { // If no matching remote item exists
console.log('No matching remote item found for locale:', locale);
return true; // Changes detected
}

if (JSON.stringify(localData) !== JSON.stringify(matchingItem.resource)) { // Compare the local and remote data
console.log(`Data mismatch found for locale: ${locale}, Type: ${localeType}`);
return true; // Changes detected
}

comparedFiles++; // Increment compared file count
}

// Compare each remote resource with the corresponding local file
for (const item of data.items) {
const localeFile = path.join(localesFolder, `${item.locale}${item.type === 'locale_schema' ? '.schema' : ''}.json`);; // Construct the local file path
console.log('Processing remote item for locale file:', localeFile);

const localeData = item.resource; // Extract the remote resource data
let currentData = {};

try {
// Attempt to read and parse the local file
currentData = JSON.parse(await fs.readFile(localeFile, 'utf-8'));
} catch (error) {
console.log('Error reading local file or file not found for locale:', item.locale, error);
currentData = {}; // Default to an empty object if the file is missing or invalid
}

if (JSON.stringify(currentData) !== JSON.stringify(localeData)) { // Compare the local and remote data
console.log(`Data mismatch found for remote locale: ${item.locale}, Type: ${item?.type}`);
return true; // Changes detected
}

comparedFiles++; // Increment compared file count
}
} else {
console.error(`Unexpected status code: ${response.status}.`); // Handle unexpected response status codes
return false;
}
} catch (error) {
console.error('Error checking for changes:', error); // Log errors during the comparison process
return false;
}

console.log(`Comparison completed. Total files compared: ${comparedFiles}`); // Log the summary of comparisons
console.log('No changes detected between local and remote data'); // Log when no changes are detected
return false; // Return false if no changes were found
};

export const syncLocales = async (syncMode: SyncMode): Promise<void> => {

console.log(`Starting fetchData with SyncMode=${syncMode}`);

try {
const response: AxiosResponse = await LocalesService.getLocalesByThemeId(null);

console.log('API response received');

if (response.status === 200) {
const data = response.data;
console.log(`Fetched ${data.items.length} items from API`);

const localesFolder: string = path.resolve(process.cwd(),'theme/locales');

try {
await fs.mkdir(localesFolder, { recursive: true });
console.log('Ensured locales folder exists');
} catch (err) {
console.error(`Error ensuring locales folder exists: ${(err as Error).message}`);
return;
}

const unmatchedLocales: LocaleResource[] = [];

let localFiles: string[];
try {
localFiles = await fs.readdir(localesFolder);
console.log(`Found ${localFiles.length} local files`);
} catch (err) {
console.error(`Error reading locales folder: ${(err as Error).message}`);
return;
}

for (const file of localFiles) {
let locale: string = path.basename(file, '.json');
console.log(`Processing local file: ${locale}`);
const localeType = locale.includes('schema') ? 'locale_schema' : 'locale';
locale = locale.includes('schema') ? locale.replace('.schema', '') : locale;
let localData: Record<string, any>;
try {
localData = JSON.parse(await fs.readFile(path.join(localesFolder, file), 'utf-8'));
} catch (err) {
console.error(`Error reading file ${file}: ${(err as Error).message}`);
continue;
}
const matchingItem = data.items.find((item: LocaleResource) => item.locale === locale && item.type === localeType);
if (!matchingItem) {
console.log(`No matching item found for locale: ${locale}`);
unmatchedLocales.push({ locale, resource: localData, type: localeType });
if (syncMode === SyncMode.PUSH) {
console.log(`Creating new resource in API for locale: ${locale}`);
await createLocaleInAPI(localData, locale, localeType);
}
} else {
if (syncMode === SyncMode.PUSH) {
if (JSON.stringify(localData) !== JSON.stringify(matchingItem.resource)) {
console.log(`Updating API resource for locale: ${locale}`);
await updateLocaleInAPI(localData, matchingItem._id);
} else {
console.log(`No changes detected for API resource: ${locale}`);
}
} else {
if (JSON.stringify(localData) !== JSON.stringify(matchingItem.resource)) {
console.log(`Updating local file for locale: ${locale}`);
await updateLocaleFile(path.join(localesFolder, file), matchingItem.resource, matchingItem._id);
} else {
console.log(`No changes detected for local file: ${locale}`);
}
}
}
}

if (unmatchedLocales.length > 0) {
console.log('Unmatched locales:', unmatchedLocales);
}

if (syncMode === SyncMode.PULL) {
for (const item of data.items) {
const locale: string = item.locale;
const localeFile = path.join(localesFolder, `${item.locale}${item.type === 'locale_schema' ? '.schema' : ''}.json`);
const localeData: Record<string, any> = item.resource;
if (!localeData) {
console.log(`Skipping empty resource for locale: ${locale}`);
continue;
}

let currentData: Record<string, any>;
try {
currentData = JSON.parse(await fs.readFile(localeFile, 'utf-8').catch(() => '{}'));
} catch (err) {
console.error(`Error reading file ${localeFile}: ${(err as Error).message}`);
continue;
}

if (JSON.stringify(currentData) !== JSON.stringify(localeData)) {
try {
console.log(`Writing updated data to local file: ${localeFile}`);
await fs.writeFile(localeFile, JSON.stringify(localeData, null, 2), 'utf-8');
} catch (err) {
console.error(`Error writing to file ${localeFile}: ${(err as Error).message}`);
}
} else {
console.log(`No changes detected for local file: ${locale}`);
}
}
}

console.log('Sync completed successfully.');
} else {
console.error(`Unexpected status code: ${response.status}.`);
}
} catch (error) {
if (axios.isAxiosError(error) && error.code === 'ECONNABORTED') {
console.error('Error: The request timed out. Please try again later.');
} else {
console.error(`Error fetching data: ${error?.response?.status} - ${error?.response?.statusText || error?.message}`);
}
}
};

const createLocaleInAPI = async (data: Record<string, any>, locale: string, localeType: string): Promise<void> => {
try {
console.log(`Creating resource in API for locale: ${locale}`);
const activeContext = getActiveContext();
const response: AxiosResponse = await LocalesService.createLocale(null, {
theme_id: activeContext.theme_id,
locale: locale,
resource: data,
type: localeType,
template: false,
})
console.log('Locale created in API:', response.data);
} catch (error) {
console.log(error);
console.error('Error creating locale in API:', (error as Error).message);
}
};

const updateLocaleInAPI = async (data: Record<string, any>, id: string): Promise<void> => {
try {
console.log(`Updating resource in API for ID: ${id}`);

const response: AxiosResponse = await LocalesService.updateLocale(null, id, { resource: data });

console.log('Locale updated in API:', response.data);
} catch (error) {
console.log(error);
console.error('Error updating locale in API:', (error as Error).message);
}
};

const updateLocaleFile = async (filePath: string, data: Record<string, any>, id: string): Promise<void> => {
try {
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
console.log(`Locale file updated: ${filePath}`);
} catch (err) {
console.error(`Error writing to file ${filePath}: ${(err as Error).message}`);
}
};
53 changes: 52 additions & 1 deletion src/helper/serve.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import CommandError from '../lib/CommandError';
import Debug from '../lib/Debug';
import { SupportedFrameworks } from '../lib/ExtensionSection';
import https from 'https';
import Tunnel from '../lib/Tunnel';
const packageJSON = require('../../package.json');

const BUILD_FOLDER = './.fdk/dist';
Expand Down Expand Up @@ -360,9 +361,32 @@ export async function startServer({ domain, host, isSSR, port }) {
});
}

async function startTunnel(port: number) {

try {
const tunnelInstance = new Tunnel({
port,
})

const tunnelUrl = await tunnelInstance.startTunnel();

console.info(`
Started cloudflare tunnel at ${port}: ${tunnelUrl}`)
return {
url: tunnelUrl,
port,
};
} catch (error) {
Logger.error('Error during starting cloudflare tunnel: ' + error.message);
return;
}
}

export async function startReactServer({ domain, host, isHMREnabled, port }) {
const { currentContext, app, server, io } = await setupServer({ domain });

const { url } = await startTunnel(port);

if (isHMREnabled) {
let webpackConfigFromTheme = {};
const themeWebpackConfigPath = path.join(
Expand Down Expand Up @@ -421,6 +445,29 @@ export async function startReactServer({ domain, host, isHMREnabled, port }) {

const uploadedFiles = {};

app.get('/getAllStaticResources', (req, res) => {
const locale = req.query.locale || 'en';
const localesFolder: string = path.resolve(process.cwd(), 'theme/locales');
const locales = fs.readdirSync(localesFolder).filter(file => file.split('.')[0] === locale);
const localesArray = [];

// Read content of each locale file
locales.forEach(locale => {
const filePath = path.join(localesFolder, locale);
try {
const content = fs.readFileSync(filePath, 'utf8');
localesArray.push({
"locale":locale.replace('.json', ''),
"resource":JSON.parse(content)
});
} catch (error) {
Logger.error(`Error reading locale file ${locale}: ${error.message}`);
}
});

res.json({"items":localesArray});
});

app.get('/*', async (req, res) => {
try {
// If browser is not requesting for html page (it can be file, API call, etc...), then fetch and send requested data directly from source
Expand Down Expand Up @@ -490,10 +537,14 @@ export async function startReactServer({ domain, host, isHMREnabled, port }) {
cliMeta: {
port,
domain: getFullLocalUrl(port),
tunnelUrl: url,
},
},
{
headers,
headers: {
...headers,
cookie: req.headers.cookie,
},
},
)
.catch((error) => {
Expand Down
Loading
Loading