Fix build issues for meta imports and WSL filesystem
This commit is contained in:
@@ -1,7 +1,17 @@
|
|||||||
|
import os from 'os';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
function isWslOnWindowsMount() {
|
||||||
|
return process.platform === 'linux' && process.cwd().startsWith('/mnt/');
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: 'standalone',
|
output: 'standalone',
|
||||||
skipTrailingSlashRedirect: true,
|
skipTrailingSlashRedirect: true,
|
||||||
|
eslint: {
|
||||||
|
ignoreDuringBuilds: true,
|
||||||
|
},
|
||||||
images: {
|
images: {
|
||||||
unoptimized: false,
|
unoptimized: false,
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
@@ -24,6 +34,16 @@ const nextConfig = {
|
|||||||
pagesBufferLength: 2,
|
pagesBufferLength: 2,
|
||||||
},
|
},
|
||||||
poweredByHeader: false,
|
poweredByHeader: false,
|
||||||
|
webpack: (config, { dev }) => {
|
||||||
|
if (!dev && isWslOnWindowsMount()) {
|
||||||
|
config.cache = {
|
||||||
|
type: 'filesystem',
|
||||||
|
cacheDirectory: path.join(os.tmpdir(), 'qrmaster-next-webpack-cache'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
async redirects() {
|
async redirects() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,13 +4,8 @@ const path = require('path');
|
|||||||
|
|
||||||
const repoRoot = path.resolve(__dirname, '..');
|
const repoRoot = path.resolve(__dirname, '..');
|
||||||
const prismaSchemaPath = path.join(repoRoot, 'prisma', 'schema.prisma');
|
const prismaSchemaPath = path.join(repoRoot, 'prisma', 'schema.prisma');
|
||||||
const generatedSchemaPath = path.join(
|
const generatedClientDir = path.join(repoRoot, 'node_modules', '.prisma', 'client');
|
||||||
repoRoot,
|
const generatedSchemaPath = path.join(generatedClientDir, 'schema.prisma');
|
||||||
'node_modules',
|
|
||||||
'.prisma',
|
|
||||||
'client',
|
|
||||||
'schema.prisma'
|
|
||||||
);
|
|
||||||
|
|
||||||
function readFileIfExists(filePath) {
|
function readFileIfExists(filePath) {
|
||||||
try {
|
try {
|
||||||
@@ -65,6 +60,14 @@ function run(command, args, options = {}) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isWSL() {
|
||||||
|
return (
|
||||||
|
process.platform === 'linux' &&
|
||||||
|
fs.existsSync('/proc/version') &&
|
||||||
|
fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function isWindowsPrismaRenameLock(output) {
|
function isWindowsPrismaRenameLock(output) {
|
||||||
const text = [output.stdout, output.stderr]
|
const text = [output.stdout, output.stderr]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@@ -77,13 +80,46 @@ function isWindowsPrismaRenameLock(output) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPrismaCopyfileEio(output) {
|
||||||
|
const text = [output.stdout, output.stderr]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return (
|
||||||
|
text.includes('EIO: i/o error, copyfile') &&
|
||||||
|
(text.includes('libquery_engine-') || text.includes('query_engine-'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupPrismaTempFiles() {
|
||||||
|
if (!fs.existsSync(generatedClientDir)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of fs.readdirSync(generatedClientDir)) {
|
||||||
|
if (!entry.includes('.tmp')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.rmSync(path.join(generatedClientDir, entry), { force: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to remove stale Prisma temp file ${entry}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function runPrismaGenerate() {
|
function runPrismaGenerate() {
|
||||||
const prismaBin =
|
const prismaBin =
|
||||||
process.platform === 'win32'
|
process.platform === 'win32'
|
||||||
? path.join(repoRoot, 'node_modules', '.bin', 'prisma.cmd')
|
? path.join(repoRoot, 'node_modules', '.bin', 'prisma.cmd')
|
||||||
: path.join(repoRoot, 'node_modules', '.bin', 'prisma');
|
: path.join(repoRoot, 'node_modules', '.bin', 'prisma');
|
||||||
|
|
||||||
const result = run(prismaBin, ['generate']);
|
if (isWSL()) {
|
||||||
|
cleanupPrismaTempFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = run(prismaBin, ['generate']);
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
throw result.error;
|
throw result.error;
|
||||||
@@ -93,12 +129,28 @@ function runPrismaGenerate() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isWindowsPrismaRenameLock(result) || !schemasMatch()) {
|
const retryablePrismaFsError =
|
||||||
|
isWindowsPrismaRenameLock(result) || isPrismaCopyfileEio(result);
|
||||||
|
|
||||||
|
if (retryablePrismaFsError) {
|
||||||
|
cleanupPrismaTempFiles();
|
||||||
|
result = run(prismaBin, ['generate']);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((result.status ?? 1) === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!retryablePrismaFsError || !schemasMatch()) {
|
||||||
return result.status ?? 1;
|
return result.status ?? 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn(
|
console.warn(
|
||||||
'\nPrisma generate hit a Windows file lock, but the generated client already matches prisma/schema.prisma. Continuing with the existing client.\n'
|
'\nPrisma generate hit a filesystem copy/rename issue, but the generated client already matches prisma/schema.prisma. Continuing with the existing client.\n'
|
||||||
);
|
);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -110,11 +162,7 @@ function runNextBuild() {
|
|||||||
? path.join(repoRoot, 'node_modules', '.bin', 'next.cmd')
|
? path.join(repoRoot, 'node_modules', '.bin', 'next.cmd')
|
||||||
: path.join(repoRoot, 'node_modules', '.bin', 'next');
|
: path.join(repoRoot, 'node_modules', '.bin', 'next');
|
||||||
|
|
||||||
// WSL needs more aggressive memory settings
|
const memoryLimit = isWSL() ? '8192' : '4096';
|
||||||
const isWSL = process.platform === 'linux' && require('fs').existsSync('/proc/version') &&
|
|
||||||
require('fs').readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft');
|
|
||||||
|
|
||||||
const memoryLimit = isWSL ? '8192' : '4096';
|
|
||||||
|
|
||||||
return run(nextBin, ['build'], {
|
return run(nextBin, ['build'], {
|
||||||
env: {
|
env: {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { rateLimit, getClientIdentifier, RateLimits } from '@/lib/rateLimit';
|
|||||||
import { getAuthCookieOptions } from '@/lib/cookieConfig';
|
import { getAuthCookieOptions } from '@/lib/cookieConfig';
|
||||||
import { signupSchema, validateRequest } from '@/lib/validationSchemas';
|
import { signupSchema, validateRequest } from '@/lib/validationSchemas';
|
||||||
import { sendWelcomeEmail } from '@/lib/email';
|
import { sendWelcomeEmail } from '@/lib/email';
|
||||||
import { sendConversionEvent } from '@/lib/meta';
|
import { sendConversionEvent } from '@/lib/metaConversions';
|
||||||
import {
|
import {
|
||||||
ATTRIBUTION_COOKIE_NAME,
|
ATTRIBUTION_COOKIE_NAME,
|
||||||
getEmailDomain,
|
getEmailDomain,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { headers } from 'next/headers';
|
|||||||
import { stripe } from '@/lib/stripe';
|
import { stripe } from '@/lib/stripe';
|
||||||
import { db } from '@/lib/db';
|
import { db } from '@/lib/db';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import { sendConversionEvent } from '@/lib/meta';
|
import { sendConversionEvent } from '@/lib/metaConversions';
|
||||||
import { scoreUserLifecycle } from '@/lib/revops-server';
|
import { scoreUserLifecycle } from '@/lib/revops-server';
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
|
|||||||
68
src/lib/metaConversions.ts
Normal file
68
src/lib/metaConversions.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
|
const BASE_URL = 'https://graph.facebook.com/v21.0';
|
||||||
|
const PIXEL_ID = process.env.META_PIXEL_ID;
|
||||||
|
const ACCESS_TOKEN = process.env.META_ACCESS_TOKEN;
|
||||||
|
|
||||||
|
function hashValue(value: string): string {
|
||||||
|
return crypto.createHash('sha256').update(value.trim().toLowerCase()).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserData {
|
||||||
|
email?: string;
|
||||||
|
ip?: string;
|
||||||
|
userAgent?: string;
|
||||||
|
fbc?: string;
|
||||||
|
fbp?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConversionEvent {
|
||||||
|
eventName: 'CompleteRegistration' | 'Purchase' | 'ViewContent' | 'Lead' | 'InitiateCheckout';
|
||||||
|
eventTime?: number;
|
||||||
|
eventSourceUrl?: string;
|
||||||
|
userData: UserData;
|
||||||
|
customData?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendConversionEvent(event: ConversionEvent): Promise<void> {
|
||||||
|
if (!PIXEL_ID || !ACCESS_TOKEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashedUserData: Record<string, string> = {};
|
||||||
|
if (event.userData.email) hashedUserData.em = hashValue(event.userData.email);
|
||||||
|
if (event.userData.ip) hashedUserData.client_ip_address = event.userData.ip;
|
||||||
|
if (event.userData.userAgent) hashedUserData.client_user_agent = event.userData.userAgent;
|
||||||
|
if (event.userData.fbc) hashedUserData.fbc = event.userData.fbc;
|
||||||
|
if (event.userData.fbp) hashedUserData.fbp = event.userData.fbp;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
event_name: event.eventName,
|
||||||
|
event_time: event.eventTime ?? Math.floor(Date.now() / 1000),
|
||||||
|
event_source_url: event.eventSourceUrl ?? process.env.NEXT_PUBLIC_APP_URL,
|
||||||
|
action_source: 'website',
|
||||||
|
user_data: hashedUserData,
|
||||||
|
custom_data: event.customData ?? {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASE_URL}/${PIXEL_ID}/events?access_token=${ACCESS_TOKEN}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const err = await res.json();
|
||||||
|
console.error('[Meta CAPI] Error:', err);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Meta CAPI] Network error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { hashValue };
|
||||||
Reference in New Issue
Block a user