Skip to content
Open
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
161 changes: 160 additions & 1 deletion packages/sdk/echo-start/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ import chalk from 'chalk';
import { spawn } from 'child_process';
import { Command } from 'commander';
import degit from 'degit';
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'fs';
import {
existsSync,
readdirSync,
readFileSync,
unlinkSync,
writeFileSync,
} from 'fs';
import path from 'path';

const program = new Command();
Expand Down Expand Up @@ -202,6 +208,117 @@ function resolveTemplateRepo(template: string): string {
return repo;
}

interface EchoTemplateConfig {
referralCode?: string;
}

function readTemplateConfig(projectPath: string): EchoTemplateConfig | null {
const configPath = path.join(projectPath, 'echo.config.json');

if (!existsSync(configPath)) {
return null;
}

try {
const configContent = readFileSync(configPath, 'utf-8');
const parsed = JSON.parse(configContent) as EchoTemplateConfig;
return parsed;
} catch {
return null;
}
}

function sanitizeReferralCode(code: unknown): string | null {
if (typeof code !== 'string') {
return null;
}

const trimmedCode = code.trim();
if (!trimmedCode) {
return null;
}

const safePattern = /^[a-zA-Z0-9_.-]+$/;
if (!safePattern.test(trimmedCode)) {
return null;
}

if (trimmedCode.length > 128) {
return null;
}

return trimmedCode;
}

function detectReferralEnvVarName(projectPath: string): string | null {
const envFiles = ['.env.local', '.env.example', '.env'];

for (const fileName of envFiles) {
const filePath = path.join(projectPath, fileName);
if (existsSync(filePath)) {
const content = readFileSync(filePath, 'utf-8');
const match = content.match(
/(NEXT_PUBLIC_|VITE_|REACT_APP_|NUXT_PUBLIC_)?ECHO_REFERRAL_CODE/
);
if (match) {
return match[0];
}
}
}

return null;
}

function detectFrameworkReferralEnvVarName(projectPath: string): string {
const packageJsonPath = path.join(projectPath, 'package.json');

if (existsSync(packageJsonPath)) {
try {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
const deps = {
...packageJson.dependencies,
...packageJson.devDependencies,
};

if (deps['next']) {
return 'NEXT_PUBLIC_ECHO_REFERRAL_CODE';
} else if (deps['vite']) {
return 'VITE_ECHO_REFERRAL_CODE';
} else if (deps['react-scripts']) {
return 'REACT_APP_ECHO_REFERRAL_CODE';
} else if (deps['nuxt']) {
return 'NUXT_PUBLIC_ECHO_REFERRAL_CODE';
}
} catch (e) {
// Fall through to default
console.error(e);
}
}

return 'NEXT_PUBLIC_ECHO_REFERRAL_CODE';
}

function upsertEnvVar(
envContent: string,
envVarName: string,
envVarValue: string
): string {
const escapedEnvVarName = envVarName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const envVarRegex = new RegExp(`^(${escapedEnvVarName}\\s*=\\s*).*$`, 'm');

if (envVarRegex.test(envContent)) {
const replaceRegex = new RegExp(
`^(${escapedEnvVarName}\\s*=\\s*).*$`,
'gm'
);
return envContent.replace(replaceRegex, `$1${envVarValue}`);
}

return envContent.trimEnd()
? `${envContent.trimEnd()}\n${envVarName}=${envVarValue}\n`
: `${envVarName}=${envVarValue}\n`;
}

function detectEnvVarName(projectPath: string): string | null {
const envFiles = ['.env.local', '.env.example', '.env'];

Expand Down Expand Up @@ -414,6 +531,48 @@ async function createApp(projectDir: string, options: CreateAppOptions) {
log.message(`Created .env.local with ${envVarName}`);
}

if (isExternal) {
const templateConfig = readTemplateConfig(absoluteProjectPath);
const referralCode = sanitizeReferralCode(templateConfig?.referralCode);

if (templateConfig?.referralCode && !referralCode) {
log.warning(
'Template referral code in echo.config.json was ignored due to invalid format'
);
}

if (referralCode) {
const detectedReferralVarName =
detectReferralEnvVarName(absoluteProjectPath);
const referralEnvVarName =
detectedReferralVarName ||
detectFrameworkReferralEnvVarName(absoluteProjectPath);

const currentEnvContent = existsSync(envPath)
? readFileSync(envPath, 'utf-8')
: '';
const updatedEnvContent = upsertEnvVar(
currentEnvContent,
referralEnvVarName,
referralCode
);

writeFileSync(envPath, updatedEnvContent);
log.message(
`Configured ${referralEnvVarName} from external template metadata`
);
}

const templateConfigPath = path.join(
absoluteProjectPath,
'echo.config.json'
);
if (existsSync(templateConfigPath)) {
unlinkSync(templateConfigPath);
log.message('Removed template metadata file: echo.config.json');
}
}

log.step('Project setup completed successfully');

// Auto-install dependencies unless skipped
Expand Down