option('source') ?? $this->choice('Select a source:', self::SOURCES, 0); $type = $this->option('type') ?? $this->choice('Select an import type:', self::TYPES, 0); if ($type === 'api') { $credentials = [ 'api_key' => $this->secret('API key:'), 'api_secret' => $this->secret('API secret:'), ]; $filePath = null; } else { $importsPath = storage_path('app/private/imports'); $filePath = $this->option('file') ?? $this->resolveImportPath($importsPath, $this->ask("Path to the export file, relative to {$importsPath}:")); while (blank($filePath) || ! is_file($filePath)) { if (filled($filePath)) { $this->error("File not found: {$filePath}"); } $filePath = $this->resolveImportPath( $importsPath, $this->ask("Path to the export file, relative to {$importsPath}:"), ); } $credentials = null; } // Shopify's own product export is a flat CSV — one Title/Body // (HTML)/etc. column per row, no per-locale columns at all — so // its text is necessarily written in exactly one language, and // there is no reliable way to detect which one from the file // itself. Modules\Core\MigrateImport\Services\ImportLocale::code() used to // (as its former name, DefaultLocale, admits) assume it always // matched this store's own Lunar\Models\ // Language::getDefault(), which is often wrong (a store's default // admin/storefront language and the language a given export // happens to be written in are two independent facts) — every // imported product's name/description then saved silently under // the wrong language, invisible unless that language happened to // also be selected when viewing/editing the product afterward. $locale = $source === 'shopify' && $type === 'export' ? $this->askImportLocale() : null; $spec = new ImportSpec( source: $source, type: $type, filePath: $filePath, credentials: $credentials, locale: $locale, ); RunMigrateImportJob::dispatch($spec); $this->info('Import queued.'); } /** * Choices come from Language::all() — the same list an admin manages * from the Filament panel (Settings > Languages) — not a hardcoded * set, so a language this store doesn't have yet simply isn't * offered here; the hint below says where to add it instead of this * command silently accepting an arbitrary code Lunar has no row for. */ private function askImportLocale(): string { $languages = Language::orderBy('default', 'desc')->get(['code', 'name']); return $this->choice( "Which language is the export file's own text (product titles, descriptions, etc.) written in?\n". ' (Not necessarily this store\'s default language — the two are independent. '. "If the language you need isn't listed, add it first from the admin panel under Languages.)", $languages->mapWithKeys(fn (Language $language) => [$language->code => "{$language->name} ({$language->code})"])->all(), $languages->first()?->code, ); } // Answers are relative to storage/app/private/imports (e.g. "shopify" or // "shopify/products_export.csv"); absolute paths are used as-is. A // directory answer picks the first CSV file found inside it. private function resolveImportPath(string $importsPath, ?string $answer): ?string { if (blank($answer)) { return $answer; } $path = str_starts_with($answer, '/') ? $answer : "{$importsPath}/{$answer}"; if (is_dir($path)) { $csv = collect(glob("{$path}/*.csv"))->first(); return $csv ?? $path; } return $path; } }