uvako-cli 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/package.json +1 -1
- package/src/api.js +6 -5
- package/src/check.js +127 -67
- package/src/config.js +6 -5
- package/src/i18n.js +67 -0
- package/src/init.js +74 -17
- package/src/locales/en.js +226 -0
- package/src/main.js +15 -10
- package/src/package.js +3 -2
- package/src/remote.js +51 -50
- package/src/zip.js +5 -4
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ paneline giriş yapmış olmanız gerekir. Tema, panelden yayınlanana kadar vit
|
|
|
44
44
|
| `uvako login --store <mağaza>` | Mağazaya tema erişim anahtarıyla bağlanır. |
|
|
45
45
|
| `uvako logout` · `uvako whoami` | Kayıtlı girişi siler · bağlı mağazayı gösterir. |
|
|
46
46
|
| `uvako theme init <anahtar>` | Yeni tema iskeleti (`--base`, `--name`, `--author`). |
|
|
47
|
-
| `uvako theme check [klasör]` | Temayı denetler: yapı, ayarlar, bölümler, görseller, güvenlik. |
|
|
47
|
+
| `uvako theme check [klasör]` | Temayı denetler: yapı, ayarlar, bölümler, görseller, çeviriler, güvenlik. |
|
|
48
48
|
| `uvako theme dev [klasör]` | Canlı geliştirme: kaydettikçe yükler, önizleme yenilenir. Durdurunca mağaza eski hâline döner (`--keep` ile kütüphanede kalır). |
|
|
49
49
|
| `uvako theme push [klasör]` | Temayı mağazanın tema kütüphanesine yükler. |
|
|
50
50
|
| `uvako theme pull <anahtar>` | Yüklediğiniz temayı indirir (`--dir`, `--force`). |
|
|
@@ -66,6 +66,7 @@ theme.json kimlik: key, name, version, base; mağaza vitrini
|
|
|
66
66
|
config/settings_data.json temel temadan farklı olan ayar değerleri
|
|
67
67
|
config/settings_schema.json (isteğe bağlı) panelde görünen ayarlar
|
|
68
68
|
templates/index.json (isteğe bağlı) ana sayfadaki bölüm sırası
|
|
69
|
+
locales/en.json, de.json, tr.json (isteğe bağlı) temanın kendi metinlerinin çevirisi
|
|
69
70
|
styles/_tokens.scss renk, yazı tipi, köşe değişkenleri (temanın tamamına yayılır)
|
|
70
71
|
styles/theme.scss temaya özgü SCSS
|
|
71
72
|
assets/ logo, slayt ve banner görselleri, yazı tipleri
|
|
@@ -75,6 +76,37 @@ reference/ temel temanın ayarları ve bölüm türleri (pak
|
|
|
75
76
|
Bölümlerin kendisi (slayt, banner, ürün vitrini…) temel temadan gelir; paket hangilerinin hangi sırayla
|
|
76
77
|
ve hangi görünümle kullanılacağını söyler. Katalog temaları: [themes.uvako.com](https://themes.uvako.com).
|
|
77
78
|
|
|
79
|
+
### Diller
|
|
80
|
+
|
|
81
|
+
Mağazalar vitrinini Türkçe, İngilizce ve Almanca yayınlayabilir. Temanın kendi metinlerini
|
|
82
|
+
(`config/settings_data.json` içindeki başlık ve buton yazıları, `theme.json` içindeki açıklama, sektör ve
|
|
83
|
+
özellikler) çevirmek için pakete sözlük dosyası koyun:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"Yeni sezon geldi": "The new season is here",
|
|
88
|
+
"Alışverişe başla": "Start shopping"
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- Dosyalar: `locales/en.json`, `locales/de.json`, `locales/tr.json`. Anahtar temadaki metnin kendisi,
|
|
93
|
+
değer o dildeki karşılığıdır. Düz bir sözlüktür; iç içe nesne olamaz.
|
|
94
|
+
- Temayı Türkçe yazdıysanız `en.json` ve `de.json` yeterlidir. Başka bir dilde yazdıysanız Türkçe
|
|
95
|
+
karşılıkları `tr.json` dosyasına koyun.
|
|
96
|
+
- Çevirisi olmayan metin yazıldığı gibi gösterilir. `uvako theme check` her dosyada eksik kalan
|
|
97
|
+
metinleri uyarı olarak listeler.
|
|
98
|
+
- Mağaza sahibinin panelden değiştirdiği metin sözlükte yoksa olduğu gibi kalır.
|
|
99
|
+
- Sınırlar: dosya başına 256 KB ve 2.000 metin; metin 500, çevirisi 2.000 karakter.
|
|
100
|
+
|
|
101
|
+
Temel temadan gelen metinler (sepet, ödeme, üyelik, menü) platform tarafından çevrilir; onları
|
|
102
|
+
sözlüğe yazmanız gerekmez.
|
|
103
|
+
|
|
104
|
+
### CLI dili
|
|
105
|
+
|
|
106
|
+
Çıktı Türkçe ya da İngilizcedir. Dil sırasıyla `--lang` seçeneğinden, `UVAKO_LANG` ortam değişkeninden
|
|
107
|
+
ve sistemin dilinden okunur: `uvako theme check --lang en`. İngilizce çalışırken `theme init` iskeleti
|
|
108
|
+
(README, SCSS açıklamaları) İngilizce yazar ve Türkçe karşılığı `locales/tr.json` dosyasına koyar.
|
|
109
|
+
|
|
78
110
|
### Ürün sayfası sekmeleri
|
|
79
111
|
|
|
80
112
|
Ürün detay sayfasındaki sekmeler `config/settings_data.json` içindeki `product_detail_page_tabs` ile açılır
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t } from "./i18n.js";
|
|
1
2
|
// Mağazanın tema uç noktası (/api/theme-cli). Erişim anahtarı yalnızca girişte seçilen mağazaya
|
|
2
3
|
// gönderilir: yönlendirmeler izlenmez, böylece anahtar başka bir adrese taşınmaz.
|
|
3
4
|
|
|
@@ -21,7 +22,7 @@ export async function call({ origin, token }, method, route, { body, raw = false
|
|
|
21
22
|
signal: AbortSignal.timeout(timeout),
|
|
22
23
|
});
|
|
23
24
|
} catch (error) {
|
|
24
|
-
throw new ApiError(
|
|
25
|
+
throw new ApiError(t("Mağazaya bağlanılamadı ({0}): {1}", origin, error.cause?.code ?? error.message));
|
|
25
26
|
}
|
|
26
27
|
if (response.ok) return raw ? Buffer.from(await response.arrayBuffer()) : response.json();
|
|
27
28
|
let payload = null;
|
|
@@ -31,7 +32,7 @@ export async function call({ origin, token }, method, route, { body, raw = false
|
|
|
31
32
|
// JSON olmayan yanıt (ör. ağ geçidi hatası).
|
|
32
33
|
}
|
|
33
34
|
const problem = payload?.error ?? {};
|
|
34
|
-
const fallback = response.status === 404 ? "Bu adreste Uvako tema uç noktası yok. Mağaza adresini kontrol edin." :
|
|
35
|
+
const fallback = response.status === 404 ? t("Bu adreste Uvako tema uç noktası yok. Mağaza adresini kontrol edin.") : t("Sunucu {0} yanıtı verdi.", response.status);
|
|
35
36
|
throw new ApiError(problem.message || fallback, { status: response.status, code: problem.code || "", problems: Array.isArray(problem.problems) ? problem.problems : [] });
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -41,9 +42,9 @@ export async function starter(site, base) {
|
|
|
41
42
|
try {
|
|
42
43
|
response = await fetch(`${site}/api/site/themes/${encodeURIComponent(base)}/starter`, { headers: { Accept: "application/json", "User-Agent": "uvako-cli" }, signal: AbortSignal.timeout(30000) });
|
|
43
44
|
} catch (error) {
|
|
44
|
-
throw new ApiError(
|
|
45
|
+
throw new ApiError(t("uvako.com'a bağlanılamadı: {0}", error.cause?.code ?? error.message));
|
|
45
46
|
}
|
|
46
|
-
if (response.status === 404) throw new ApiError(
|
|
47
|
-
if (!response.ok) throw new ApiError(
|
|
47
|
+
if (response.status === 404) throw new ApiError(t("\"{0}\" adında bir temel tema yok. Temalar: https://themes.uvako.com", base), { status: 404 });
|
|
48
|
+
if (!response.ok) throw new ApiError(t("Temel tema okunamadı ({0}).", response.status), { status: response.status });
|
|
48
49
|
return response.json();
|
|
49
50
|
}
|
package/src/check.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { walk } from "./files.js";
|
|
4
|
+
import { t } from "./i18n.js";
|
|
4
5
|
|
|
5
6
|
// Tema denetimi. Mağazaya yüklenen paketi platform aynı kurallarla yeniden denetler; buradaki
|
|
6
7
|
// denetim geliştiriciye hatayı yüklemeden önce göstermek içindir, güvenlik kararı sunucudadır.
|
|
@@ -10,7 +11,11 @@ import { walk } from "./files.js";
|
|
|
10
11
|
// üstüne ayar, bölüm sırası, SCSS ve görsel getirir.
|
|
11
12
|
// depo — platform deposundaki katalog teması (themes/<anahtar>).
|
|
12
13
|
|
|
13
|
-
export const limits = { fileBytes: 5 * 1024 * 1024, totalBytes: 60 * 1024 * 1024, files: 600, styleBytes: 512 * 1024, archiveBytes: 40 * 1024 * 1024 };
|
|
14
|
+
export const limits = { fileBytes: 5 * 1024 * 1024, totalBytes: 60 * 1024 * 1024, files: 600, styleBytes: 512 * 1024, archiveBytes: 40 * 1024 * 1024, localeBytes: 256 * 1024, localeEntries: 2000 };
|
|
15
|
+
|
|
16
|
+
// Temanın kendi metinlerinin çevirisi: locales/<dil>.json, {"temadaki metin": "çevirisi"}.
|
|
17
|
+
// Vitrin ve panel bu sözlükle temanın varsayılan metinlerini ziyaretçinin diline çevirir.
|
|
18
|
+
export const textLocales = ["tr", "en", "de"];
|
|
14
19
|
|
|
15
20
|
const keyPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
16
21
|
const versionPattern = /^\d+\.\d+\.\d+$/;
|
|
@@ -30,8 +35,8 @@ const allowedStyleHosts = new Set(["fonts.googleapis.com", "fonts.gstatic.com",
|
|
|
30
35
|
const scriptPatterns = [
|
|
31
36
|
{ pattern: /<script\b/i, text: "<script> etiketi" },
|
|
32
37
|
{ pattern: /javascript:/i, text: "javascript: adresi" },
|
|
33
|
-
{ pattern: /\son[a-z]+\s*=/i, text: "olay özniteliği (onclick, onload…)" },
|
|
34
|
-
{ pattern: /<iframe\b|<object\b|<embed\b|<foreignObject\b/i, text: "gömülü içerik etiketi" },
|
|
38
|
+
{ pattern: /\son[a-z]+\s*=/i, text: t("olay özniteliği (onclick, onload…)") },
|
|
39
|
+
{ pattern: /<iframe\b|<object\b|<embed\b|<foreignObject\b/i, text: t("gömülü içerik etiketi") },
|
|
35
40
|
];
|
|
36
41
|
|
|
37
42
|
const secretPatterns = [
|
|
@@ -51,9 +56,54 @@ function readJson(file, report, label, required = true) {
|
|
|
51
56
|
try {
|
|
52
57
|
return JSON.parse(readFileSync(file, "utf8").replace(/^\uFEFF/, ""));
|
|
53
58
|
} catch (error) {
|
|
54
|
-
report.errors.push(
|
|
59
|
+
report.errors.push(t("{0} geçerli JSON değil: {1}", label, error.message));
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// locales/<dil>.json: düz bir metin sözlüğü olmalı. Geçerliyse sözlüğü, değilse null döndürür.
|
|
65
|
+
function checkLocale(rel, file, report) {
|
|
66
|
+
let texts;
|
|
67
|
+
try {
|
|
68
|
+
texts = JSON.parse(readFileSync(file, "utf8").replace(/^\uFEFF/, ""));
|
|
69
|
+
} catch (error) {
|
|
70
|
+
report.errors.push(t("{0} geçerli JSON değil: {1}", rel, error.message));
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (!texts || typeof texts !== "object" || Array.isArray(texts) || Object.values(texts).some((value) => typeof value !== "string")) {
|
|
74
|
+
report.errors.push(t("{0}: düz bir metin sözlüğü olmalı ({\"temadaki metin\": \"çevirisi\"}).", rel));
|
|
55
75
|
return null;
|
|
56
76
|
}
|
|
77
|
+
const entries = Object.entries(texts);
|
|
78
|
+
if (entries.length > limits.localeEntries) report.errors.push(t("{0}: en çok {1} metin içerebilir.", rel, limits.localeEntries));
|
|
79
|
+
if (entries.some(([source, text]) => !source.trim() || source.length > 500 || text.length > 2000)) report.errors.push(t("{0}: metin en çok 500, çevirisi en çok 2000 karakter olabilir; boş metin olamaz.", rel));
|
|
80
|
+
return texts;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Çevrilebilecek metinler: theme.json'daki tanıtım alanları ve settings_data.json'daki yazılar.
|
|
84
|
+
// Renk, adres, dosya yolu ve sayılar metin sayılmaz.
|
|
85
|
+
function translatable(meta, data) {
|
|
86
|
+
const out = new Set();
|
|
87
|
+
const add = (value) => {
|
|
88
|
+
const text = typeof value === "string" ? value.trim() : "";
|
|
89
|
+
if (!text || !/\p{L}{2}/u.test(text)) return;
|
|
90
|
+
if (/^(#|https?:|\/|uploads\/|assets\/|rgba?\(|[\w.-]+\.(png|jpe?g|webp|avif|gif|svg|ico|woff2?|ttf|otf)$)/i.test(text)) return;
|
|
91
|
+
if (/^[a-z0-9_.,:-]+$/.test(text)) return;
|
|
92
|
+
out.add(text);
|
|
93
|
+
};
|
|
94
|
+
add(meta?.description);
|
|
95
|
+
for (const item of [...(Array.isArray(meta?.industries) ? meta.industries : []), ...(Array.isArray(meta?.features) ? meta.features : [])]) add(item);
|
|
96
|
+
if (data && typeof data === "object" && !Array.isArray(data)) for (const value of Object.values(data)) add(value);
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function checkTranslations(locales, meta, data, report) {
|
|
101
|
+
const texts = translatable(meta, data);
|
|
102
|
+
for (const [locale, dict] of Object.entries(locales)) {
|
|
103
|
+
if (!dict) continue;
|
|
104
|
+
const missing = [...texts].filter((text) => !(text in dict));
|
|
105
|
+
if (missing.length) report.warnings.push(t("locales/{0}.json: {1} metnin çevirisi yok (ör. \"{2}\"). Çevirisi olmayan metin yazıldığı gibi gösterilir.", locale, missing.length, missing[0].slice(0, 60)));
|
|
106
|
+
}
|
|
57
107
|
}
|
|
58
108
|
|
|
59
109
|
// Şemadaki ayar adlarını toplar ({"type":"tab","values":[{name, settings, group}]}).
|
|
@@ -64,7 +114,7 @@ function schemaKeys(node, into, report, trail = "") {
|
|
|
64
114
|
const where = trail ? `${trail} › ${tab.name ?? "?"}` : String(tab.name ?? "?");
|
|
65
115
|
for (const [key, setting] of Object.entries(tab.settings ?? {})) {
|
|
66
116
|
into.add(key);
|
|
67
|
-
if (!setting || typeof setting !== "object" || !setting.type) report.errors.push(
|
|
117
|
+
if (!setting || typeof setting !== "object" || !setting.type) report.errors.push(t("Ayar şeması: \"{0}\" ayarının türü yok ({1}).", key, where));
|
|
68
118
|
}
|
|
69
119
|
schemaKeys(tab.group, into, report, where);
|
|
70
120
|
}
|
|
@@ -83,10 +133,10 @@ function locate(dir) {
|
|
|
83
133
|
|
|
84
134
|
function scanText(rel, text, report) {
|
|
85
135
|
for (const { pattern, text: what } of scriptPatterns) {
|
|
86
|
-
if (pattern.test(text)) report.errors.push(
|
|
136
|
+
if (pattern.test(text)) report.errors.push(t("{0}: {1} içeriyor. Tema içinde betik çalıştırılamaz.", rel, what));
|
|
87
137
|
}
|
|
88
138
|
for (const pattern of secretPatterns) {
|
|
89
|
-
if (pattern.test(text)) report.errors.push(
|
|
139
|
+
if (pattern.test(text)) report.errors.push(t("{0}: gizli anahtar ya da parola gibi görünen bir değer içeriyor.", rel));
|
|
90
140
|
}
|
|
91
141
|
}
|
|
92
142
|
|
|
@@ -113,19 +163,19 @@ function scanStyle(rel, source, report, { strict = false, styles = new Set(), to
|
|
|
113
163
|
return (match ? held[Number(match[1])] ?? "" : value).trim();
|
|
114
164
|
};
|
|
115
165
|
const all = `${text} ${held.join(" ")}`;
|
|
116
|
-
if (/expression\s*\(|behavior\s*:|-moz-binding|<\/style/i.test(all)) report.errors.push(
|
|
117
|
-
if (/javascript:/i.test(all)) report.errors.push(
|
|
166
|
+
if (/expression\s*\(|behavior\s*:|-moz-binding|<\/style/i.test(all)) report.errors.push(t("{0}: tarayıcıda kod çalıştıran CSS (expression, behavior, -moz-binding) içeriyor.", rel));
|
|
167
|
+
if (/javascript:/i.test(all)) report.errors.push(t("{0}: javascript: adresi içeriyor.", rel));
|
|
118
168
|
if (strict) {
|
|
119
169
|
const rule = /@(while|for|each|function|mixin|use|forward|debug|warn|error|charset)\b/i.exec(text);
|
|
120
|
-
if (rule) report.errors.push(
|
|
121
|
-
if (text.includes("\\")) report.errors.push(
|
|
170
|
+
if (rule) report.errors.push(t("{0}: @{1} kullanılamaz. Tema stilleri döngü, fonksiyon ve mixin tanımı içeremez; temel temanın mixin'leri @include ile kullanılabilir.", rel, rule[1].toLowerCase()));
|
|
171
|
+
if (text.includes("\\")) report.errors.push(t("{0}: tırnak dışında ters eğik çizgi kullanılamaz.", rel));
|
|
122
172
|
}
|
|
123
173
|
text = text.replace(/url\(([^)]*)\)/gi, (_, inner) => {
|
|
124
174
|
const target = resolve(inner);
|
|
125
175
|
if (/^data:/i.test(target)) {
|
|
126
|
-
if (strict && !/^data:(image\/(png|jpe?g|gif|webp)|font\/woff2?|application\/font-woff2?);base64,/i.test(target)) report.errors.push(
|
|
176
|
+
if (strict && !/^data:(image\/(png|jpe?g|gif|webp)|font\/woff2?|application\/font-woff2?);base64,/i.test(target)) report.errors.push(t("{0}: yalnızca görsel ve yazı tipi data: adresleri kullanılabilir.", rel));
|
|
127
177
|
} else if ((target.includes("//") || (strict && target.includes(":"))) && !allowedUrl(target)) {
|
|
128
|
-
report.errors.push(
|
|
178
|
+
report.errors.push(t("{0}: izin verilmeyen dış adres ({1}). Görsel ve yazı tiplerini assets/ altına koyun; yalnızca şu adresler çağrılabilir: {2}.", rel, hostOf(target), [...allowedStyleHosts].join(", ")));
|
|
129
179
|
}
|
|
130
180
|
return "url()";
|
|
131
181
|
});
|
|
@@ -134,9 +184,9 @@ function scanStyle(rel, source, report, { strict = false, styles = new Set(), to
|
|
|
134
184
|
if (/^\s*url\(\)/i.test(part)) continue;
|
|
135
185
|
const target = resolve(part);
|
|
136
186
|
if (target.includes("//") || target.includes(":")) {
|
|
137
|
-
if (!allowedUrl(target)) report.errors.push(
|
|
187
|
+
if (!allowedUrl(target)) report.errors.push(t("{0}: izin verilmeyen dış adres ({1}).", rel, hostOf(target)));
|
|
138
188
|
} else if (strict && !(/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/.test(target) && (styles.has(`${target}.scss`) || styles.has(`_${target}.scss`)))) {
|
|
139
|
-
report.errors.push(
|
|
189
|
+
report.errors.push(t("{0}: @import \"{1}\" pakette yok. Yalnızca paketin kendi styles/ dosyaları içe aktarılabilir.", rel, target));
|
|
140
190
|
}
|
|
141
191
|
}
|
|
142
192
|
}
|
|
@@ -144,7 +194,7 @@ function scanStyle(rel, source, report, { strict = false, styles = new Set(), to
|
|
|
144
194
|
for (const raw of text.split(";")) {
|
|
145
195
|
const statement = raw.trim();
|
|
146
196
|
if (statement === "" || /^\$[A-Za-z0-9_-]+\s*:\s*[^{}@]+$/.test(statement) || /^@import/i.test(statement)) continue;
|
|
147
|
-
report.errors.push(
|
|
197
|
+
report.errors.push(t("{0}: yalnızca değişken ataması ($ad: değer;) ve yazı tipi @import'u içerebilir.", rel));
|
|
148
198
|
break;
|
|
149
199
|
}
|
|
150
200
|
}
|
|
@@ -154,21 +204,21 @@ function checkIdentity(dir, meta, report) {
|
|
|
154
204
|
const key = typeof meta?.key === "string" ? meta.key.trim() : "";
|
|
155
205
|
report.key = key;
|
|
156
206
|
if (!meta) return key;
|
|
157
|
-
if (!keyPattern.test(key) || key.length > 40) report.errors.push(
|
|
158
|
-
else if (key !== path.basename(dir)) report.errors.push(
|
|
207
|
+
if (!keyPattern.test(key) || key.length > 40) report.errors.push(t("theme.json: \"key\" küçük harf, rakam ve tireden oluşmalı (ör. \"linea\")."));
|
|
208
|
+
else if (key !== path.basename(dir)) report.errors.push(t("theme.json: \"key\" ({0}) klasör adıyla ({1}) aynı olmalı.", key, path.basename(dir)));
|
|
159
209
|
if (!String(meta.name ?? "").trim()) report.errors.push(`theme.json: "name" gerekli.`);
|
|
160
|
-
if (!versionPattern.test(String(meta.version ?? ""))) report.errors.push(
|
|
161
|
-
if (!String(meta.description ?? "").trim()) report.warnings.push(
|
|
210
|
+
if (!versionPattern.test(String(meta.version ?? ""))) report.errors.push(t("theme.json: \"version\" 1.0.0 biçiminde olmalı."));
|
|
211
|
+
if (!String(meta.description ?? "").trim()) report.warnings.push(t("theme.json: \"description\" boş; tema listesinde açıklama görünmez."));
|
|
162
212
|
return key;
|
|
163
213
|
}
|
|
164
214
|
|
|
165
215
|
function checkTemplate(index, types, report, source) {
|
|
166
216
|
if (!index) return;
|
|
167
|
-
if (!Array.isArray(index.order) || index.order.length === 0) report.errors.push(
|
|
217
|
+
if (!Array.isArray(index.order) || index.order.length === 0) report.errors.push(t("templates/index.json: \"order\" boş; ana sayfada bölüm görünmez."));
|
|
168
218
|
for (const id of index.order ?? []) {
|
|
169
219
|
const section = index.sections?.[id];
|
|
170
|
-
if (!section) report.errors.push(
|
|
171
|
-
else if (types && !types.has(section.type)) report.errors.push(
|
|
220
|
+
if (!section) report.errors.push(t("templates/index.json: \"order\" içindeki \"{0}\" bölümü \"sections\" altında tanımlı değil.", id));
|
|
221
|
+
else if (types && !types.has(section.type)) report.errors.push(t("templates/index.json: \"{0}\" bölümünün türü ({1}) {2} yok.", id, section.type, source));
|
|
172
222
|
}
|
|
173
223
|
}
|
|
174
224
|
|
|
@@ -188,17 +238,17 @@ function checkProductTabs(data, report) {
|
|
|
188
238
|
if ([0, 1, "0", "1", true, false].includes(value)) return;
|
|
189
239
|
const where = 'config/settings_data.json: "product_detail_page_tabs"';
|
|
190
240
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
191
|
-
report.errors.push(
|
|
241
|
+
report.errors.push(t("{0} 1 (hepsi açık) ya da {\"display_product_comments\": 1, …} nesnesi olmalı.", where));
|
|
192
242
|
return;
|
|
193
243
|
}
|
|
194
244
|
for (const [key, flag] of Object.entries(value)) {
|
|
195
245
|
if (!(key in productTabKeys)) {
|
|
196
|
-
report.warnings.push(
|
|
246
|
+
report.warnings.push(t("{0}: \"{1}\" bilinen bir sekme değil ({2}).", where, key, Object.keys(productTabKeys).join(", ")));
|
|
197
247
|
continue;
|
|
198
248
|
}
|
|
199
|
-
if (![0, 1, "0", "1", true, false].includes(flag)) report.errors.push(
|
|
249
|
+
if (![0, 1, "0", "1", true, false].includes(flag)) report.errors.push(t("{0}: \"{1}\" değeri 1 ya da 0 olmalı.", where, key));
|
|
200
250
|
const label = productTabKeys[key];
|
|
201
|
-
if (label && label in data && typeof data[label] !== "string") report.errors.push(
|
|
251
|
+
if (label && label in data && typeof data[label] !== "string") report.errors.push(t("config/settings_data.json: \"{0}\" sekme başlığı metin olmalı.", label));
|
|
202
252
|
}
|
|
203
253
|
}
|
|
204
254
|
|
|
@@ -206,7 +256,7 @@ function checkImages(data, assets, report) {
|
|
|
206
256
|
if (!data || typeof data !== "object") return;
|
|
207
257
|
for (const [name, value] of Object.entries(data)) {
|
|
208
258
|
if (typeof value !== "string" || !/^uploads\/[^\s]+\.(png|jpe?g|webp|avif|gif|svg|ico)$/i.test(value)) continue;
|
|
209
|
-
if (!existsSync(path.join(assets, value))) report.errors.push(
|
|
259
|
+
if (!existsSync(path.join(assets, value))) report.errors.push(t("config/settings_data.json: \"{0}\" ayarındaki görsel bulunamadı (assets/{1}).", name, value));
|
|
210
260
|
}
|
|
211
261
|
}
|
|
212
262
|
|
|
@@ -214,17 +264,18 @@ function checkImages(data, assets, report) {
|
|
|
214
264
|
|
|
215
265
|
function checkPackage(dir, meta, report) {
|
|
216
266
|
const base = String(meta?.base ?? "").trim();
|
|
217
|
-
if (meta && !keyPattern.test(base)) report.errors.push(
|
|
218
|
-
if (meta && (Number(meta.price ?? 0) < 0 || Number(meta.price ?? 0) > 100000 || Number.isNaN(Number(meta.price ?? 0)))) report.errors.push(
|
|
219
|
-
if (meta?.demo_url && !/^https:\/\/[^\s]+$/.test(String(meta.demo_url))) report.errors.push(
|
|
267
|
+
if (meta && !keyPattern.test(base)) report.errors.push(t("theme.json: \"base\" gerekli: paketin temel aldığı katalog teması (ör. \"linea\"). Temalar: https://themes.uvako.com"));
|
|
268
|
+
if (meta && (Number(meta.price ?? 0) < 0 || Number(meta.price ?? 0) > 100000 || Number.isNaN(Number(meta.price ?? 0)))) report.errors.push(t("theme.json: \"price\" 0 ile 100.000 arasında olmalı."));
|
|
269
|
+
if (meta?.demo_url && !/^https:\/\/[^\s]+$/.test(String(meta.demo_url))) report.errors.push(t("theme.json: \"demo_url\" https:// ile başlayan bir adres olmalı."));
|
|
220
270
|
|
|
221
271
|
const { files, links } = walk(dir);
|
|
222
|
-
for (const link of links) report.errors.push(
|
|
272
|
+
for (const link of links) report.errors.push(t("{0}: sembolik bağ pakete alınamaz.", link));
|
|
223
273
|
const styles = new Set(files.filter((file) => file.rel.startsWith("styles/")).map((file) => file.rel.slice("styles/".length)));
|
|
224
274
|
const totals = { files: 0, bytes: 0, style: 0 };
|
|
225
275
|
let schema = null;
|
|
226
276
|
let data = null;
|
|
227
277
|
let index = null;
|
|
278
|
+
const locales = {};
|
|
228
279
|
for (const file of files) {
|
|
229
280
|
const rel = file.rel;
|
|
230
281
|
if (localOnly(rel)) continue;
|
|
@@ -232,15 +283,23 @@ function checkPackage(dir, meta, report) {
|
|
|
232
283
|
totals.bytes += file.size;
|
|
233
284
|
const parts = rel.split("/");
|
|
234
285
|
if (parts.some((part) => !segmentPattern.test(part))) {
|
|
235
|
-
report.errors.push(
|
|
286
|
+
report.errors.push(t("{0}: dosya adında yalnızca harf, rakam, nokta, tire ve alt çizgi olabilir; gizli dosya olamaz.", rel));
|
|
236
287
|
continue;
|
|
237
288
|
}
|
|
238
289
|
const ext = path.extname(rel).toLowerCase();
|
|
239
290
|
const text = () => readFileSync(file.full, "utf8");
|
|
240
291
|
if (rel === "theme.json") scanText(rel, text(), report);
|
|
241
292
|
else if (rel === "config/settings_schema.json" || rel === "config/settings_data.json" || rel === "templates/index.json") {
|
|
242
|
-
if (file.size > 1024 * 1024) report.errors.push(
|
|
293
|
+
if (file.size > 1024 * 1024) report.errors.push(t("{0}: 1 MB sınırını aşıyor.", rel));
|
|
243
294
|
else scanText(rel, text(), report);
|
|
295
|
+
} else if (parts[0] === "locales" && parts.length === 2 && rel.endsWith(".json")) {
|
|
296
|
+
const locale = parts[1].slice(0, -5);
|
|
297
|
+
if (!textLocales.includes(locale)) report.errors.push(t("{0}: desteklenmeyen dil. Kullanılabilir dosyalar: {1}.", rel, textLocales.map((item) => `locales/${item}.json`).join(", ")));
|
|
298
|
+
else if (file.size > limits.localeBytes) report.errors.push(t("{0}: {1} KB sınırını aşıyor.", rel, limits.localeBytes / 1024));
|
|
299
|
+
else {
|
|
300
|
+
scanText(rel, text(), report);
|
|
301
|
+
locales[locale] = checkLocale(rel, file.full, report);
|
|
302
|
+
}
|
|
244
303
|
} else if (parts[0] === "styles" && parts.length === 2 && stylePattern.test(parts[1])) {
|
|
245
304
|
totals.style += file.size;
|
|
246
305
|
if (file.size <= limits.styleBytes) {
|
|
@@ -250,40 +309,41 @@ function checkPackage(dir, meta, report) {
|
|
|
250
309
|
}
|
|
251
310
|
} else if (parts[0] === "assets" && parts.length >= 2) {
|
|
252
311
|
if (!assetExtensions.has(ext)) {
|
|
253
|
-
report.errors.push(
|
|
312
|
+
report.errors.push(t("{0}: bu dosya türü ({1}) temada bulunamaz.", rel, ext || "uzantısız"));
|
|
254
313
|
continue;
|
|
255
314
|
}
|
|
256
|
-
if (file.size > limits.fileBytes) report.errors.push(
|
|
257
|
-
else if (file.size > 1024 * 1024 && /\.(png|jpe?g|gif)$/i.test(ext)) report.warnings.push(
|
|
315
|
+
if (file.size > limits.fileBytes) report.errors.push(t("{0}: {1} MB; tek dosya sınırı {2} MB.", rel, (file.size / 1024 / 1024).toFixed(1), limits.fileBytes / 1024 / 1024));
|
|
316
|
+
else if (file.size > 1024 * 1024 && /\.(png|jpe?g|gif)$/i.test(ext)) report.warnings.push(t("{0}: {1} MB. WebP ya da sıkıştırılmış JPEG sayfayı hızlandırır.", rel, (file.size / 1024 / 1024).toFixed(1)));
|
|
258
317
|
if (ext === ".svg" && file.size <= limits.fileBytes) scanText(rel, text(), report);
|
|
259
318
|
} else {
|
|
260
|
-
report.errors.push(
|
|
319
|
+
report.errors.push(t("{0}: bu dosya pakette bulunamaz. İzin verilenler: theme.json, config/settings_schema.json, config/settings_data.json, templates/index.json, locales/tr.json, locales/en.json, locales/de.json, styles/*.scss, assets/**.", rel));
|
|
261
320
|
}
|
|
262
321
|
}
|
|
263
|
-
if (totals.style > limits.styleBytes) report.errors.push(
|
|
264
|
-
if (styles.size > 0 && !styles.has("theme.scss") && !(styles.size === 1 && styles.has("_tokens.scss"))) report.errors.push("styles/theme.scss yok: stil dosyaları oradan içe aktarılır.");
|
|
322
|
+
if (totals.style > limits.styleBytes) report.errors.push(t("Stil dosyaları toplam {0} KB sınırını aşıyor.", limits.styleBytes / 1024));
|
|
323
|
+
if (styles.size > 0 && !styles.has("theme.scss") && !(styles.size === 1 && styles.has("_tokens.scss"))) report.errors.push(t("styles/theme.scss yok: stil dosyaları oradan içe aktarılır."));
|
|
265
324
|
|
|
266
325
|
schema = readJson(path.join(dir, "config", "settings_schema.json"), report, "config/settings_schema.json", false);
|
|
267
326
|
if (schema) {
|
|
268
|
-
if (schema.group?.type !== "tab" || !Array.isArray(schema.group?.values) || schema.group.values.length === 0) report.errors.push(
|
|
327
|
+
if (schema.group?.type !== "tab" || !Array.isArray(schema.group?.values) || schema.group.values.length === 0) report.errors.push(t("config/settings_schema.json: \"group\" sekmeli olmalı ({\"type\":\"tab\",\"values\":[…]})."));
|
|
269
328
|
schemaKeys(schema.group, new Set(), report);
|
|
270
329
|
}
|
|
271
330
|
data = readJson(path.join(dir, "config", "settings_data.json"), report, "config/settings_data.json", false);
|
|
272
|
-
if (data && (typeof data !== "object" || Array.isArray(data))) report.errors.push("config/settings_data.json: anahtar-değer nesnesi olmalı.");
|
|
331
|
+
if (data && (typeof data !== "object" || Array.isArray(data))) report.errors.push(t("config/settings_data.json: anahtar-değer nesnesi olmalı."));
|
|
273
332
|
index = readJson(path.join(dir, "templates", "index.json"), report, "templates/index.json", false);
|
|
274
333
|
// Bölüm türleri temel temadan gelir; `theme init` bunları reference/sections.json dosyasına yazar.
|
|
275
334
|
const reference = existsSync(path.join(dir, "reference", "sections.json")) ? readJson(path.join(dir, "reference", "sections.json"), { errors: [] }, "") : null;
|
|
276
|
-
checkTemplate(index, reference?.sections ? new Set(Object.keys(reference.sections)) : null, report,
|
|
335
|
+
checkTemplate(index, reference?.sections ? new Set(Object.keys(reference.sections)) : null, report, t("{0} temasında", base));
|
|
277
336
|
checkImages(data, path.join(dir, "assets"), report);
|
|
278
337
|
checkProductTabs(data, report);
|
|
338
|
+
checkTranslations(locales, meta, data, report);
|
|
279
339
|
for (const shot of [meta?.screenshot, ...(Array.isArray(meta?.screenshots) ? meta.screenshots : [])]) {
|
|
280
340
|
const rel = String(shot ?? "").trim().replace(/^\//, "").replace(/^assets\//, "");
|
|
281
|
-
if (rel && !existsSync(path.join(dir, "assets", rel))) report.errors.push(
|
|
341
|
+
if (rel && !existsSync(path.join(dir, "assets", rel))) report.errors.push(t("theme.json: ekran görüntüsü pakette yok (assets/{0}).", rel));
|
|
282
342
|
}
|
|
283
343
|
report.files = totals.files;
|
|
284
344
|
report.bytes = totals.bytes;
|
|
285
|
-
if (totals.bytes > limits.totalBytes) report.errors.push(
|
|
286
|
-
if (totals.files > limits.files) report.errors.push(
|
|
345
|
+
if (totals.bytes > limits.totalBytes) report.errors.push(t("Tema {0} MB; sınır {1} MB.", (totals.bytes / 1024 / 1024).toFixed(0), limits.totalBytes / 1024 / 1024));
|
|
346
|
+
if (totals.files > limits.files) report.errors.push(t("Tema {0} dosya içeriyor; sınır {1}.", totals.files, limits.files));
|
|
287
347
|
}
|
|
288
348
|
|
|
289
349
|
// ── platform deposundaki katalog teması ────────────────────────────────────────────────────────
|
|
@@ -291,19 +351,19 @@ function checkPackage(dir, meta, report) {
|
|
|
291
351
|
function scanRepoFiles(root, label, report, totals) {
|
|
292
352
|
if (!existsSync(root)) return;
|
|
293
353
|
const { files, links } = walk(root);
|
|
294
|
-
for (const link of links) report.errors.push(
|
|
354
|
+
for (const link of links) report.errors.push(t("{0}: sembolik bağ pakete alınamaz.", `${label}/${link}`));
|
|
295
355
|
for (const file of files) {
|
|
296
356
|
const rel = `${label}/${file.rel}`;
|
|
297
357
|
const ext = path.extname(file.rel).toLowerCase();
|
|
298
358
|
totals.files += 1;
|
|
299
359
|
totals.bytes += file.size;
|
|
300
|
-
if (file.rel.split("/").some((part) => part === ".." || part.startsWith("."))) report.errors.push(
|
|
360
|
+
if (file.rel.split("/").some((part) => part === ".." || part.startsWith("."))) report.errors.push(t("{0}: gizli dosya ya da üst klasöre çıkan yol.", rel));
|
|
301
361
|
if (!repoExtensions.has(ext)) {
|
|
302
|
-
report.errors.push(
|
|
362
|
+
report.errors.push(t("{0}: bu dosya türü ({1}) temada bulunamaz.", rel, ext || "uzantısız"));
|
|
303
363
|
continue;
|
|
304
364
|
}
|
|
305
|
-
if (file.size > limits.fileBytes) report.errors.push(
|
|
306
|
-
else if (file.size > 1024 * 1024 && /\.(png|jpe?g|gif)$/i.test(ext)) report.warnings.push(
|
|
365
|
+
if (file.size > limits.fileBytes) report.errors.push(t("{0}: {1} MB; tek dosya sınırı {2} MB.", rel, (file.size / 1024 / 1024).toFixed(1), limits.fileBytes / 1024 / 1024));
|
|
366
|
+
else if (file.size > 1024 * 1024 && /\.(png|jpe?g|gif)$/i.test(ext)) report.warnings.push(t("{0}: {1} MB. WebP ya da sıkıştırılmış JPEG sayfayı hızlandırır.", rel, (file.size / 1024 / 1024).toFixed(1)));
|
|
307
367
|
if ([".json", ".scss", ".css", ".svg", ".md", ".txt"].includes(ext) && file.size <= limits.fileBytes) {
|
|
308
368
|
const text = readFileSync(file.full, "utf8");
|
|
309
369
|
scanText(rel, text, report);
|
|
@@ -315,44 +375,44 @@ function scanRepoFiles(root, label, report, totals) {
|
|
|
315
375
|
function checkRepo(dir, meta, key, where, report) {
|
|
316
376
|
const schema = readJson(path.join(dir, "config", "settings_schema.json"), report, "config/settings_schema.json");
|
|
317
377
|
if (schema) {
|
|
318
|
-
if (schema.group?.type !== "tab" || !Array.isArray(schema.group?.values)) report.errors.push(
|
|
378
|
+
if (schema.group?.type !== "tab" || !Array.isArray(schema.group?.values)) report.errors.push(t("config/settings_schema.json: \"group\" sekmeli olmalı ({\"type\":\"tab\",\"values\":[…]})."));
|
|
319
379
|
schemaKeys(schema.group, new Set(), report);
|
|
320
380
|
}
|
|
321
381
|
const data = readJson(path.join(dir, "config", "settings_data.json"), report, "config/settings_data.json");
|
|
322
|
-
if (data && (typeof data !== "object" || Array.isArray(data))) report.errors.push("config/settings_data.json: anahtar-değer nesnesi olmalı.");
|
|
382
|
+
if (data && (typeof data !== "object" || Array.isArray(data))) report.errors.push(t("config/settings_data.json: anahtar-değer nesnesi olmalı."));
|
|
323
383
|
else checkProductTabs(data, report);
|
|
324
384
|
|
|
325
385
|
const sections = readJson(path.join(dir, "config", "sections.json"), report, "config/sections.json");
|
|
326
386
|
const types = new Set(Object.keys(sections?.sections ?? {}));
|
|
327
|
-
if (sections && types.size === 0) report.errors.push(
|
|
387
|
+
if (sections && types.size === 0) report.errors.push(t("config/sections.json: \"sections\" altında en az bir bölüm türü olmalı."));
|
|
328
388
|
for (const [type, section] of Object.entries(sections?.sections ?? {})) {
|
|
329
|
-
if (key && !type.startsWith(`${key}-`)) report.warnings.push(
|
|
330
|
-
if (!String(section?.name ?? "").trim()) report.errors.push(
|
|
389
|
+
if (key && !type.startsWith(`${key}-`)) report.warnings.push(t("config/sections.json: \"{0}\" bölüm türü tema anahtarıyla (\"{1}-\") başlamıyor.", type, key));
|
|
390
|
+
if (!String(section?.name ?? "").trim()) report.errors.push(t("config/sections.json: \"{0}\" bölümünün adı yok.", type));
|
|
331
391
|
// "keys" isteğe bağlıdır; verilmişse liste olmalı.
|
|
332
|
-
if (section?.keys !== undefined && !Array.isArray(section.keys)) report.errors.push(
|
|
392
|
+
if (section?.keys !== undefined && !Array.isArray(section.keys)) report.errors.push(t("config/sections.json: \"{0}\" bölümünün \"keys\" alanı liste olmalı.", type));
|
|
333
393
|
}
|
|
334
|
-
checkTemplate(readJson(path.join(dir, "templates", "index.json"), report, "templates/index.json (ana sayfa şablonu)"), sections ? types : null, report, "config/sections.json içinde");
|
|
394
|
+
checkTemplate(readJson(path.join(dir, "templates", "index.json"), report, t("templates/index.json (ana sayfa şablonu)")), sections ? types : null, report, t("config/sections.json içinde"));
|
|
335
395
|
|
|
336
396
|
const totals = { files: 0, bytes: 0 };
|
|
337
397
|
for (const sub of ["config", "templates"]) scanRepoFiles(path.join(dir, sub), sub, report, totals);
|
|
338
398
|
scanRepoFiles(where.styles, "styles", report, totals);
|
|
339
399
|
scanRepoFiles(where.assets, "assets", report, totals);
|
|
340
|
-
if (!existsSync(path.join(where.styles, "theme.scss"))) report.errors.push(
|
|
400
|
+
if (!existsSync(path.join(where.styles, "theme.scss"))) report.errors.push(t("Stil dosyası yok: {0}", path.join(where.styles, "theme.scss")));
|
|
341
401
|
report.files = totals.files;
|
|
342
402
|
report.bytes = totals.bytes;
|
|
343
|
-
if (totals.bytes > limits.totalBytes) report.errors.push(
|
|
344
|
-
if (totals.files > limits.files) report.errors.push(
|
|
403
|
+
if (totals.bytes > limits.totalBytes) report.errors.push(t("Tema {0} MB; sınır {1} MB.", (totals.bytes / 1024 / 1024).toFixed(0), limits.totalBytes / 1024 / 1024));
|
|
404
|
+
if (totals.files > limits.files) report.errors.push(t("Tema {0} dosya içeriyor; sınır {1}.", totals.files, limits.files));
|
|
345
405
|
checkImages(data, where.assets, report);
|
|
346
406
|
if (meta?.screenshot) {
|
|
347
407
|
const shot = String(meta.screenshot).replace(new RegExp(`^/themes/${key}/assets/`), "");
|
|
348
|
-
if (!existsSync(path.join(where.assets, shot))) report.warnings.push(
|
|
408
|
+
if (!existsSync(path.join(where.assets, shot))) report.warnings.push(t("theme.json: ekran görüntüsü bulunamadı (assets/{0}).", shot));
|
|
349
409
|
}
|
|
350
410
|
}
|
|
351
411
|
|
|
352
412
|
export function checkTheme(dir) {
|
|
353
413
|
const report = { dir, key: "", layout: "", errors: [], warnings: [], files: 0, bytes: 0 };
|
|
354
414
|
if (!existsSync(path.join(dir, "theme.json"))) {
|
|
355
|
-
report.errors.push(
|
|
415
|
+
report.errors.push(t("{0} bir tema klasörü değil: theme.json bulunamadı.", dir));
|
|
356
416
|
return report;
|
|
357
417
|
}
|
|
358
418
|
const where = locate(dir);
|
|
@@ -365,10 +425,10 @@ export function checkTheme(dir) {
|
|
|
365
425
|
}
|
|
366
426
|
|
|
367
427
|
export function printReport(report) {
|
|
368
|
-
const title = report.key ? `${report.key} (${report.layout === "repo" ? "platform deposu" : "paket"})` : report.dir;
|
|
428
|
+
const title = report.key ? `${report.key} (${report.layout === "repo" ? t("platform deposu") : t("paket")})` : report.dir;
|
|
369
429
|
for (const error of report.errors) console.error(` ✖ ${error}`);
|
|
370
430
|
for (const warning of report.warnings) console.warn(` ! ${warning}`);
|
|
371
|
-
const size = report.files ?
|
|
372
|
-
if (report.errors.length) console.error(
|
|
373
|
-
else console.log(
|
|
431
|
+
const size = report.files ? t(" · {0} dosya, {1} MB", report.files, (report.bytes / 1024 / 1024).toFixed(1)) : "";
|
|
432
|
+
if (report.errors.length) console.error(t("{0}: {1} hata, {2} uyarı{3}", title, report.errors.length, report.warnings.length, size));
|
|
433
|
+
else console.log(t("{0}: denetim geçti{1}{2}", title, report.warnings.length ? t(", {0} uyarı", report.warnings.length) : "", size));
|
|
374
434
|
}
|
package/src/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { t } from "./i18n.js";
|
|
4
5
|
|
|
5
6
|
// Giriş bilgileri kullanıcının ev klasöründe, yalnızca kullanıcının okuyabildiği bir dosyada durur.
|
|
6
7
|
// Tema klasörüne hiçbir zaman yazılmaz; böylece pakete ya da sürüm kontrolüne karışmaz.
|
|
@@ -45,7 +46,7 @@ const local = (hostname) => hostname === "localhost" || hostname === "127.0.0.1"
|
|
|
45
46
|
*/
|
|
46
47
|
export function storeOrigin(input) {
|
|
47
48
|
let value = String(input ?? "").trim().replace(/\/+$/, "");
|
|
48
|
-
if (!value) throw new Error("Mağaza adresi gerekli (ör. magazam ya da magazam.uvoo.store).");
|
|
49
|
+
if (!value) throw new Error(t("Mağaza adresi gerekli (ör. magazam ya da magazam.uvoo.store)."));
|
|
49
50
|
if (!/^https?:\/\//i.test(value)) {
|
|
50
51
|
if (!value.includes(".") && !value.includes(":")) value = `${value}.${defaultStoreApex}`;
|
|
51
52
|
value = `${local(value.split(":")[0]) ? "http" : "https"}://${value}`;
|
|
@@ -54,10 +55,10 @@ export function storeOrigin(input) {
|
|
|
54
55
|
try {
|
|
55
56
|
url = new URL(value);
|
|
56
57
|
} catch {
|
|
57
|
-
throw new Error(
|
|
58
|
+
throw new Error(t("Mağaza adresi okunamadı: {0}", input));
|
|
58
59
|
}
|
|
59
|
-
if (url.protocol !== "https:" && !local(url.hostname)) throw new Error("Mağazaya yalnızca https ile bağlanılır.");
|
|
60
|
-
if (url.username || url.password) throw new Error("Mağaza adresinde kullanıcı adı ya da parola olamaz.");
|
|
60
|
+
if (url.protocol !== "https:" && !local(url.hostname)) throw new Error(t("Mağazaya yalnızca https ile bağlanılır."));
|
|
61
|
+
if (url.username || url.password) throw new Error(t("Mağaza adresinde kullanıcı adı ya da parola olamaz."));
|
|
61
62
|
return url.origin;
|
|
62
63
|
}
|
|
63
64
|
|
|
@@ -70,7 +71,7 @@ export function session(storeOption) {
|
|
|
70
71
|
const origin = storeOption ? storeOrigin(storeOption) : data.current;
|
|
71
72
|
const entry = origin ? data.stores[origin] : null;
|
|
72
73
|
if (!origin || !entry?.token) {
|
|
73
|
-
throw new Error(storeOption ?
|
|
74
|
+
throw new Error(storeOption ? t("Bu mağaza için giriş yapılmamış: {0}\nÖnce: uvako login --store {1}", origin, storeOption) : "Giriş yapılmamış. Önce: uvako login --store <mağaza>");
|
|
74
75
|
}
|
|
75
76
|
return { origin, token: entry.token, name: entry.name };
|
|
76
77
|
}
|