uvako-cli 0.4.0 → 0.6.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/src/check.js CHANGED
@@ -2,6 +2,9 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { walk } from "./files.js";
4
4
  import { t } from "./i18n.js";
5
+ import { checkSVG } from "./svg.js";
6
+ import { rules } from "./rules.generated.js";
7
+ import { checkPageTemplate, checkSectionSchema, checkSources, platformSchemas, sectionFile, templateTexts } from "./sections.js";
5
8
 
6
9
  // Tema denetimi. Mağazaya yüklenen paketi platform aynı kurallarla yeniden denetler; buradaki
7
10
  // denetim geliştiriciye hatayı yüklemeden önce göstermek içindir, güvenlik kararı sunucudadır.
@@ -11,7 +14,8 @@ import { t } from "./i18n.js";
11
14
  // üstüne ayar, bölüm sırası, SCSS ve görsel getirir.
12
15
  // depo — platform deposundaki katalog teması (themes/<anahtar>).
13
16
 
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 };
17
+ // Sınırlar sunucuyla ortaktır (backend/internal/theme/rules/rules.json).
18
+ export const limits = { fileBytes: rules.limits.fileBytes, totalBytes: rules.limits.totalBytes, files: rules.limits.files, styleBytes: rules.limits.styleBytes, archiveBytes: rules.limits.archiveBytes, localeBytes: rules.limits.localeBytes, localeEntries: rules.limits.localeEntries };
15
19
 
16
20
  // Temanın kendi metinlerinin çevirisi: locales/<dil>.json, {"temadaki metin": "çevirisi"}.
17
21
  // Vitrin ve panel bu sözlükle temanın varsayılan metinlerini ziyaretçinin diline çevirir.
@@ -135,6 +139,10 @@ function scanText(rel, text, report) {
135
139
  for (const { pattern, text: what } of scriptPatterns) {
136
140
  if (pattern.test(text)) report.errors.push(t("{0}: {1} içeriyor. Tema içinde betik çalıştırılamaz.", rel, what));
137
141
  }
142
+ scanSecrets(rel, text, report);
143
+ }
144
+
145
+ function scanSecrets(rel, text, report) {
138
146
  for (const pattern of secretPatterns) {
139
147
  if (pattern.test(text)) report.errors.push(t("{0}: gizli anahtar ya da parola gibi görünen bir değer içeriyor.", rel));
140
148
  }
@@ -260,6 +268,42 @@ function checkImages(data, assets, report) {
260
268
  }
261
269
  }
262
270
 
271
+ // ── bölüm motoru ("engine": "sections") ───────────────────────────────────────────────────────
272
+
273
+ function checkSectionsEngine({ dir, pages, sectionSchemas, sources, index, legacy, locales, report }) {
274
+ for (const file of walk(path.join(dir, "styles")).files) {
275
+ const source = readFileSync(file.full, "utf8");
276
+ if (/fonts\.googleapis\.com|fonts\.gstatic\.com|fonts\.bunny\.net/.test(source)) report.errors.push(t("styles/{0}: yazı tipleri assets/ içinde pakete gömülmeli.", file.rel));
277
+ }
278
+ checkSources(sources, report);
279
+ const schemas = new Map(platformSchemas);
280
+ for (const [handle, schema] of Object.entries(sectionSchemas)) {
281
+ const rel = `sections/${handle}.schema.json`;
282
+ if (!/^[a-z][a-z0-9_-]{0,63}$/.test(handle)) {
283
+ report.errors.push(t("{0}: geçersiz bölüm adı.", rel));
284
+ continue;
285
+ }
286
+ checkSectionSchema(rel, schema, report);
287
+ const kind = platformSchemas.has(handle) ? handle : `pkg:${handle}`;
288
+ schemas.set(kind, schema);
289
+ if (kind.startsWith("pkg:") && !(`sections/${handle}.html.tmpl` in sources)) report.errors.push(t("sections/{0}.schema.json: bölümün şablonu (sections/{0}.html.tmpl) yok.", handle));
290
+ if (schema?.fallback && !platformSchemas.has(schema.fallback) && legacy && !legacy.has(schema.fallback)) report.errors.push(t("sections/{0}.schema.json: \"fallback\" platformda ya da temel temada olan bir bölüm türü olmalı.", handle));
291
+ }
292
+ for (const rel of Object.keys(sources)) {
293
+ const handle = rel.startsWith("sections/") ? rel.slice("sections/".length, -".html.tmpl".length) : "";
294
+ if (handle && !platformSchemas.has(handle) && !schemas.has(`pkg:${handle}`)) schemas.set(`pkg:${handle}`, { name: handle, settings: [] });
295
+ }
296
+ const all = { ...pages, ...(index ? { index } : {}) };
297
+ for (const [name, tpl] of Object.entries(all)) checkPageTemplate(`templates/${name}.json`, tpl, { schemas, legacy }, report);
298
+ // Şablonlardaki {{ t "…" }} metinleri: çevirisi olmayan metin yazıldığı gibi gösterilir.
299
+ const texts = templateTexts(sources);
300
+ for (const [locale, dict] of Object.entries(locales)) {
301
+ if (!dict) continue;
302
+ const missing = [...texts].filter((text) => !(text in dict));
303
+ if (missing.length) report.warnings.push(t("locales/{0}.json: şablonlardaki {1} metnin çevirisi yok (ör. \"{2}\").", locale, missing.length, missing[0].slice(0, 60)));
304
+ }
305
+ }
306
+
263
307
  // ── paket ──────────────────────────────────────────────────────────────────────────────────────
264
308
 
265
309
  function checkPackage(dir, meta, report) {
@@ -276,6 +320,10 @@ function checkPackage(dir, meta, report) {
276
320
  let data = null;
277
321
  let index = null;
278
322
  const locales = {};
323
+ // "engine": "sections" dosyaları: sayfa şablonları, bölüm şemaları ve şablon kaynakları.
324
+ const pages = {};
325
+ const sectionSchemas = {};
326
+ const sources = {};
279
327
  for (const file of files) {
280
328
  const rel = file.rel;
281
329
  if (localOnly(rel)) continue;
@@ -314,9 +362,30 @@ function checkPackage(dir, meta, report) {
314
362
  }
315
363
  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
364
  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)));
317
- if (ext === ".svg" && file.size <= limits.fileBytes) scanText(rel, text(), report);
365
+ if (ext === ".svg" && file.size <= limits.fileBytes) {
366
+ const body = readFileSync(file.full);
367
+ const problem = checkSVG(body);
368
+ if (problem) report.errors.push(`${rel}: ${problem}`);
369
+ scanSecrets(rel, body.toString("utf8"), report);
370
+ }
371
+ } else if (sectionFile(rel)) {
372
+ const kind = sectionFile(rel);
373
+ if (kind === "source") {
374
+ sources[rel] = text();
375
+ continue;
376
+ }
377
+ if (file.size > rules.limits.jsonBytes) {
378
+ report.errors.push(t("{0}: 1 MB sınırını aşıyor.", rel));
379
+ continue;
380
+ }
381
+ const body = text();
382
+ scanText(rel, body, report);
383
+ let parsed = null;
384
+ try { parsed = JSON.parse(body.replace(/^\uFEFF/, "")); } catch (error) { report.errors.push(t("{0} geçerli JSON değil: {1}", rel, error.message)); continue; }
385
+ if (kind === "template") pages[parts[1].slice(0, -5)] = parsed;
386
+ else sectionSchemas[parts[1].slice(0, -".schema.json".length)] = parsed;
318
387
  } else {
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));
388
+ report.errors.push(t("{0}: bu dosya pakette bulunamaz. İzin verilenler: {1}.", rel, rules.paths.join(", ")));
320
389
  }
321
390
  }
322
391
  if (totals.style > limits.styleBytes) report.errors.push(t("Stil dosyaları toplam {0} KB sınırını aşıyor.", limits.styleBytes / 1024));
@@ -332,7 +401,12 @@ function checkPackage(dir, meta, report) {
332
401
  index = readJson(path.join(dir, "templates", "index.json"), report, "templates/index.json", false);
333
402
  // Bölüm türleri temel temadan gelir; `theme init` bunları reference/sections.json dosyasına yazar.
334
403
  const reference = existsSync(path.join(dir, "reference", "sections.json")) ? readJson(path.join(dir, "reference", "sections.json"), { errors: [] }, "") : null;
335
- checkTemplate(index, reference?.sections ? new Set(Object.keys(reference.sections)) : null, report, t("{0} temasında", base));
404
+ const legacy = reference?.sections ? new Set(Object.keys(reference.sections)) : null;
405
+ const engine = String(meta?.engine ?? "").trim();
406
+ if (meta && !rules.engines.includes(engine)) report.errors.push(t("theme.json: bilinmeyen \"engine\" değeri."));
407
+ if (engine !== "sections" && (Object.keys(pages).length || Object.keys(sectionSchemas).length || Object.keys(sources).length)) report.errors.push(t("Yeni sayfa ve bölüm dosyaları için theme.json içinde \"engine\":\"sections\" gerekli."));
408
+ if (engine === "sections") checkSectionsEngine({ dir, pages, sectionSchemas, sources, index, legacy, locales, report });
409
+ else checkTemplate(index, legacy, report, t("{0} temasında", base));
336
410
  checkImages(data, path.join(dir, "assets"), report);
337
411
  checkProductTabs(data, report);
338
412
  checkTranslations(locales, meta, data, report);
package/src/config.js CHANGED
@@ -8,6 +8,13 @@ import { t } from "./i18n.js";
8
8
 
9
9
  export const defaultStoreApex = "uvoo.store";
10
10
  export const siteOrigin = () => process.env.UVAKO_SITE_URL || "https://uvako.com";
11
+ /** Partner paneli: `uvako login` (cihaz girişi) ve `uvako app` komutları buraya bağlanır. */
12
+ export const partnerOrigin = () => {
13
+ const raw = (process.env.UVAKO_PARTNER_URL || "https://partner.uvako.com").replace(/\/+$/, "");
14
+ const url = new URL(raw);
15
+ if (url.protocol !== "https:" && !local(url.hostname)) throw new Error(t("Partner paneline yalnızca https ile bağlanılır."));
16
+ return url.origin;
17
+ };
11
18
 
12
19
  export function configDir() {
13
20
  if (process.env.UVAKO_CONFIG_DIR) return process.env.UVAKO_CONFIG_DIR;
@@ -20,9 +27,13 @@ const file = () => path.join(configDir(), "credentials.json");
20
27
  function read() {
21
28
  try {
22
29
  const data = JSON.parse(readFileSync(file(), "utf8"));
23
- return { current: typeof data.current === "string" ? data.current : "", stores: data.stores && typeof data.stores === "object" ? data.stores : {} };
30
+ return {
31
+ current: typeof data.current === "string" ? data.current : "",
32
+ stores: data.stores && typeof data.stores === "object" ? data.stores : {},
33
+ partner: data.partner && typeof data.partner === "object" ? data.partner : null,
34
+ };
24
35
  } catch {
25
- return { current: "", stores: {} };
36
+ return { current: "", stores: {}, partner: null };
26
37
  }
27
38
  }
28
39
 
@@ -99,3 +110,34 @@ export function sessions() {
99
110
  }
100
111
 
101
112
  export const credentialsPath = () => (existsSync(file()) ? file() : "");
113
+
114
+ /**
115
+ * Partner oturumu (uvako app komutları): önce UVAKO_PARTNER_TOKEN (CI), sonra `uvako login` ile
116
+ * kaydedilen giriş. Anahtar yalnızca girişin yapıldığı partner adresine gönderilir.
117
+ */
118
+ export function partnerSession() {
119
+ const origin = partnerOrigin();
120
+ if (process.env.UVAKO_PARTNER_TOKEN) return { origin, token: process.env.UVAKO_PARTNER_TOKEN.trim(), name: "" };
121
+ const entry = read().partner;
122
+ if (!entry?.token || entry.origin !== origin) throw new Error(t("Partner girişi yapılmamış. Önce: uvako login"));
123
+ return { origin, token: entry.token, name: entry.name ?? "", email: entry.email ?? "" };
124
+ }
125
+
126
+ export function savePartner(origin, token, name, email) {
127
+ const data = read();
128
+ data.partner = { origin, token, name, email, savedAt: new Date().toISOString() };
129
+ write(data);
130
+ }
131
+
132
+ export function dropPartner() {
133
+ const data = read();
134
+ const had = Boolean(data.partner);
135
+ data.partner = null;
136
+ write(data);
137
+ return had;
138
+ }
139
+
140
+ export function partnerSaved() {
141
+ const entry = read().partner;
142
+ return entry?.token ? { origin: entry.origin, name: entry.name ?? "", email: entry.email ?? "" } : null;
143
+ }
package/src/files.js CHANGED
@@ -2,7 +2,8 @@ import { lstatSync, readdirSync } from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  // Pakete hiç girmeyen adlar (sürüm kontrolü, bağımlılıklar, işletim sistemi dosyaları).
5
- const skipped = new Set([".git", "node_modules", ".DS_Store", "Thumbs.db", ".idea", ".vscode"]);
5
+ // .uvako: CLI'ın yerel durumu (son push/pull revizyonu); pakete girmez.
6
+ const skipped = new Set([".git", "node_modules", ".DS_Store", "Thumbs.db", ".idea", ".vscode", ".uvako"]);
6
7
 
7
8
  /** Klasördeki dosyaları (alt klasörlerle) "a/b.c" biçiminde, sıralı döndürür. Sembolik bağlar ayrıca bildirilir. */
8
9
  export function walk(root) {
package/src/init.js CHANGED
@@ -19,7 +19,8 @@ function logo(name) {
19
19
  * `starter` temel temanın ayarlarıdır (platformdan okunur: şema, varsayılanlar, bölüm türleri, ana
20
20
  * sayfa şablonu). Verilmezse iskelet şablonsuz kurulur; tema temel temanın bölüm sırasını kullanır.
21
21
  */
22
- export function initTheme(key, { name, author, cwd, base = "linea", starter = null }) {
22
+ export function initTheme(key, { name, author, cwd, base = "linea", starter = null, engine = "" }) {
23
+ if (engine && engine !== "sections") throw new Error(t("Bilinmeyen tema motoru: {0} (kullanılabilir: sections)", engine));
23
24
  if (!keyPattern.test(key) || key.length > 40) throw new Error(t("Tema anahtarı küçük harf, rakam ve tireden oluşmalı (ör. \"atolye\"): {0}", key));
24
25
  if (!keyPattern.test(base)) throw new Error(t("Temel tema adı geçersiz: {0}", base));
25
26
  const dir = path.join(cwd, key);
@@ -32,12 +33,13 @@ export function initTheme(key, { name, author, cwd, base = "linea", starter = nu
32
33
  };
33
34
 
34
35
  const description = t("{0} teması.", title);
35
- write("theme.json", json({ key, name: title, version: "0.1.0", base, author: String(author || ""), description, screenshot: "", industries: [], features: [], price: 0 }));
36
+ write("theme.json", json({ key, name: title, version: "0.1.0", base, ...(engine ? { engine } : {}), author: String(author || ""), description, screenshot: "", industries: [], features: [], price: 0 }));
36
37
 
37
38
  // Yalnızca temel temadan farklı olan değerler yazılır; gerisi temel temadan gelir.
38
39
  write("config/settings_data.json", json({ logo: "uploads/logo.svg" }));
39
40
 
40
- if (starter?.template) write("templates/index.json", json(starter.template));
41
+ if (engine === "sections") writeSections(write, getLocale() === "en");
42
+ else if (starter?.template) write("templates/index.json", json(starter.template));
41
43
  if (starter) {
42
44
  // Başvuru dosyaları: pakete girmez. Hangi ayarların ve bölüm türlerinin olduğunu gösterir.
43
45
  write("reference/settings_schema.json", json(starter.schema ?? {}));
@@ -49,10 +51,20 @@ export function initTheme(key, { name, author, cwd, base = "linea", starter = nu
49
51
 
50
52
  // Temanın kendi metinlerinin çevirisi: anahtar temadaki metin, değer o dildeki karşılığıdır.
51
53
  // İskelet tek bir örnekle gelir; settings_data.json'a yazdığınız başlık ve buton metinlerini ekleyin.
52
- if (en) write("locales/tr.json", json({ [description]: `${title} teması.` }));
53
- else write("locales/en.json", json({ [description]: `${title} theme.` }));
54
+ const extra = engine === "sections" ? sectionTexts(en) : {};
55
+ if (en) write("locales/tr.json", json({ [description]: `${title} teması.`, ...extra }));
56
+ else write("locales/en.json", json({ [description]: `${title} theme.`, ...extra }));
54
57
 
55
- write("styles/_tokens.scss", en ? `// Overrides the base theme's variables: the values you set here spread across the whole theme.
58
+ if (engine === "sections") write("styles/_tokens.scss", en ? `// Overrides the base theme's variables. Section themes embed their fonts in assets/fonts
59
+ // (KVKK: no font services are called); declare them with @font-face in styles/theme.scss.
60
+ // $font-family: 'Inter', sans-serif;
61
+ // $color-text: #111111;
62
+ ` : `// Temel temanın değişkenlerinin üstüne yazılır. Bölüm temaları yazı tiplerini assets/fonts içine
63
+ // gömer (KVKK: yazı tipi servisi çağrılmaz); styles/theme.scss içinde @font-face ile tanımlayın.
64
+ // $font-family: 'Inter', sans-serif;
65
+ // $color-text: #111111;
66
+ `);
67
+ else write("styles/_tokens.scss", en ? `// Overrides the base theme's variables: the values you set here spread across the whole theme.
56
68
  // This file may contain only variable assignments and font @imports.
57
69
  //
58
70
  // @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap');
@@ -150,3 +162,82 @@ metinleri listeler.
150
162
  ${commands}`);
151
163
  return dir;
152
164
  }
165
+
166
+ /**
167
+ * "engine": "sections" iskeleti: ana sayfa ve ürün sayfası şablonları, iki örnek paket bölümü ve
168
+ * bir ürün kartı snippet'i. Şablonlar sunucuda sandbox içinde çizilir; betik içeremez. Etkileşim
169
+ * (sepete ekle, yan sepet) platformun data-uvako-* öznitelikleriyle gelir.
170
+ */
171
+ function writeSections(write, en) {
172
+ const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
173
+ write("templates/index.json", json({
174
+ order: ["hero", "rack"],
175
+ sections: {
176
+ hero: { type: "pkg:hero", settings: { title: en ? "Pages that fill up" : "Sayfası dolacak defterler", text: en ? "Pick by paper, binding and size." : "Kâğıdını, cildini ve boyutunu görerek seç.", link: "/search" } },
177
+ rack: { type: "plate-rack", settings: { title: en ? "Best sellers" : "Bu hafta çok satanlar", source: "popular", limit: 8 } },
178
+ },
179
+ }));
180
+ write("templates/product.json", json({
181
+ order: ["gallery", "info", "usp", "details", "related"],
182
+ sections: {
183
+ gallery: { type: "product-gallery", settings: { layout: "slider", show_thumbnails: true } },
184
+ info: { type: "product-info", settings: { show_sku: true, show_stock: true, show_unit_price: true } },
185
+ usp: { type: "pkg:usp", settings: {} },
186
+ details: { type: "product-details", settings: { show_specs: true } },
187
+ related: { type: "product-related", settings: { limit: 4 } },
188
+ },
189
+ }));
190
+ write("sections/hero.schema.json", json({
191
+ name: en ? "Hero" : "Karşılama",
192
+ fallback: "plate-rack",
193
+ settings: [
194
+ { id: "title", type: "text", label: en ? "Title" : "Başlık", default: "" },
195
+ { id: "text", type: "textarea", label: en ? "Text" : "Metin", default: "" },
196
+ { id: "link", type: "url", label: en ? "Button link" : "Düğme bağlantısı", default: "/search" },
197
+ { id: "image", type: "image", label: en ? "Image" : "Görsel", default: "" },
198
+ ],
199
+ }));
200
+ write("sections/hero.html.tmpl", `<section class="hero">
201
+ <div class="hero-copy">
202
+ <h1>{{ .section.settings.title }}</h1>
203
+ {{ with .section.settings.text }}<p>{{ . }}</p>{{ end }}
204
+ <a class="btn btn-primary" href="{{ default "/search" .section.settings.link }}">{{ t "Ürünlere göz at" }}</a>
205
+ </div>
206
+ {{ with .section.settings.image }}<img class="hero-image" src="{{ image_url . }}" alt="">{{ end }}
207
+ <div class="hero-cards">
208
+ {{ range .shop.featured }}{{ template "snippets/product-card" . }}{{ end }}
209
+ </div>
210
+ </section>
211
+ `);
212
+ write("sections/usp.schema.json", json({
213
+ name: en ? "Shipping and returns" : "Kargo ve iade",
214
+ fallback: "product-details",
215
+ settings: [
216
+ { id: "shipping", type: "text", label: en ? "Shipping" : "Kargo", default: en ? "Ships today on weekdays before 15:00" : "Hafta içi 15.00'e kadar aynı gün kargoda" },
217
+ { id: "returns", type: "text", label: en ? "Returns" : "İade", default: en ? "14-day returns" : "14 gün iade" },
218
+ { id: "show_cart_button", type: "checkbox", label: en ? "Open cart button" : "Sepeti aç düğmesi", default: true },
219
+ ],
220
+ }));
221
+ write("sections/usp.html.tmpl", `<section class="usp">
222
+ <ul>
223
+ <li>{{ .section.settings.shipping }}</li>
224
+ <li>{{ .section.settings.returns }}</li>
225
+ </ul>
226
+ {{ if .section.settings.show_cart_button }}<button type="button" class="btn" data-uvako-cart-open>{{ t "Sepeti aç" }}</button>{{ end }}
227
+ </section>
228
+ `);
229
+ write("snippets/product-card.html.tmpl", `<article class="card">
230
+ <a href="{{ url_for "product" .handle }}">
231
+ {{ with .image }}<img src="{{ image_url . }}" alt="" loading="lazy">{{ end }}
232
+ <h3>{{ .title }}</h3>
233
+ </a>
234
+ <p class="price"><strong>{{ money .price }}</strong>{{ with .compare_at_price }} <del>{{ money . }}</del>{{ end }}</p>
235
+ <button type="button" class="btn" data-uvako-add-to-cart="{{ .handle }}" data-name="{{ .title }}" data-price="{{ .price }}" data-image="{{ .image }}" data-url="{{ url_for "product" .handle }}" data-stock="{{ .stock }}">{{ t "Sepete ekle" }}</button>
236
+ </article>
237
+ `);
238
+ }
239
+
240
+ /** Örnek bölümlerin {{ t "…" }} metinleri ve karşılıkları (locales/ dosyasına eklenir). */
241
+ export const sectionTexts = (en) => (en
242
+ ? { "Ürünlere göz at": "Ürünlere göz at", "Sepeti aç": "Sepeti aç", "Sepete ekle": "Sepete ekle" }
243
+ : { "Ürünlere göz at": "Browse products", "Sepeti aç": "Open cart", "Sepete ekle": "Add to cart" });
package/src/locales/en.js CHANGED
@@ -191,35 +191,188 @@ export const en = {
191
191
  "theme.json: \"name\" gerekli (en çok 60 karakter).": "theme.json: \"name\" is required (60 characters at most).",
192
192
  "theme.json: \"base\" katalogdaki bir tema olmalı ({0}).": "theme.json: \"base\" must be a theme in the catalog ({0}).",
193
193
  "tema paketi gecersiz: {0}": "the theme package is invalid: {0}",
194
+ // uvako app ve partner girişi
195
+ "{0} bir uygulama klasörü değil: {1} bulunamadı. Yeni uygulama için: uvako app init <ad>": "{0} is not an app folder: {1} was not found. For a new app: uvako app init <name>",
196
+ "yapılandırma bir nesne olmalı.": "the configuration must be an object.",
197
+ "\"name\" gerekli (en çok 60 karakter).": "\"name\" is required (60 characters at most).",
198
+ "\"client_id\" 32 karakterlik bir kimlik olmalı (ya da boş).": "\"client_id\" must be a 32-character id (or empty).",
199
+ "\"application_url\" https:// ile başlayan bir adres olmalı.": "\"application_url\" must be an address starting with https://.",
200
+ "\"redirect_urls\" https adreslerinden oluşan bir liste olmalı.": "\"redirect_urls\" must be a list of https addresses.",
201
+ "\"scopes\" kapsam listesi olmalı (ör. [\"read_products\"]).": "\"scopes\" must be a list of scopes (e.g. [\"read_products\"]).",
202
+ "\"webhooks\" [{\"topics\": [...], \"uri\": \"/webhooks\"}] biçiminde olmalı.": "\"webhooks\" must look like [{\"topics\": [...], \"uri\": \"/webhooks\"}].",
203
+ "\"privacy_webhooks.{0}\" zorunlu: /webhooks/... yolu ya da https adresi (KVKK/GDPR).": "\"privacy_webhooks.{0}\" is required: a /webhooks/... path or an https address (GDPR).",
204
+ "\"webhooks\" içinde zorunlu app/uninstalled konusu yok.": "\"webhooks\" lacks the mandatory app/uninstalled topic.",
205
+ "\"dev.port\" geçerli bir port olmalı.": "\"dev.port\" must be a valid port.",
206
+ "Uygulama adı gerekli: uvako app init <ad>": "An app name is required: uvako app init <name>",
207
+ "Uygulama adı harf ya da rakam içermeli.": "The app name must contain letters or numbers.",
208
+ "Klasörde zaten bir uygulama var: {0}": "There is already an app in the folder: {0}",
209
+ "askıda": "suspended",
210
+ "taslak": "draft",
211
+ "incelemede": "in review",
212
+ "yayında": "live",
213
+ "reddedildi": "rejected",
214
+ "Uygulama kaydedildi: {0} (client_id {1})": "App registered: {0} (client_id {1})",
215
+ "Dağıtıldı: {0} sürüm {1}": "Deployed: {0} version {1}",
216
+ "Değişiklik yok: {0} sürüm {1}": "No changes: {0} version {1}",
217
+ "Durum: {0}{1}": "Status: {0}{1}",
218
+ " · mağazalardaki sürüm {0}": " · version in stores {0}",
219
+ "Bu sürüm yalnızca kendi geliştirme mağazalarınızda geçerli. Herkese açmak için: uvako app submit": "This version applies only to your own development stores. To make it public: uvako app submit",
220
+ "Uygulama henüz kayıtlı değil. Önce: uvako app deploy": "The app is not registered yet. First: uvako app deploy",
221
+ "İncelemeye gönderildi: {0} sürüm {1}. Uvako onayladığında tüm mağazalar bu sürümü kurabilir.": "Submitted for review: {0} version {1}. Once Uvako approves it, every store can install this version.",
222
+ "Kayıtlı uygulamanız yok. Başlamak için: uvako app init <ad>": "You have no registered apps. To start: uvako app init <name>",
223
+ "sürüm {0}": "version {0}",
224
+ "{0} kurulum": "{0} installs",
225
+ "ulaşılamadı ({0})": "unreachable ({0})",
226
+ "[{0}] {1} → {2} {3} ({4} ms)": "[{0}] {1} → {2} {3} ({4} ms)",
227
+ "Geçersiz port: {0}": "Invalid port: {0}",
228
+ "Yapılandırma dağıtıldı: sürüm {0} (yalnızca kendi mağazalarınızda)": "Configuration deployed: version {0} (only on your own stores)",
229
+ "Uygulama henüz kayıtlı değil. --no-update olmadan çalıştırın ya da: uvako app deploy": "The app is not registered yet. Run without --no-update or: uvako app deploy",
230
+ "Hangi mağazada geliştireceğinizi seçin: --store <mağaza>": "Choose the store to develop on: --store <store>",
231
+ "Partner hesabınızda geliştirme mağazası yok. partner.uvako.com → Mağazalar → Geliştirme mağazası aç.": "Your partner account has no development store. partner.uvako.com → Stores → Open a development store.",
232
+ "! Yerel uygulama {0} koduyla kapandı.": "! The local app exited with code {0}.",
233
+ "\nGeliştirme: {0} → {1} ({2})": "\nDevelopment: {0} → {1} ({2})",
234
+ "Yerel uygulama: {0}": "Local app: {0}",
235
+ "Kur / aç: {0}": "Install / open: {0}",
236
+ "Bu mağazada uygulamanın adresi yerel adres oldu ve webhook'ları buraya aktarılıyor. Durdurmak için Ctrl+C.\n": "In this store the app now points to the local address and its webhooks are relayed here. Press Ctrl+C to stop.\n",
237
+ "\nGeliştirme durdu. Mağaza dağıtılan adresleri kullanıyor; webhook'lar yeniden internetten gidiyor.": "\nDevelopment stopped. The store uses the deployed addresses again; webhooks go over the internet again.",
238
+ "[{0}] ! Partner paneline ulaşılamadı: {1}": "[{0}] ! Could not reach the partner panel: {1}",
239
+ "Uygulama oluşturuldu: {0}": "App created: {0}",
240
+ "Uvako'ya kaydedildi: client_id {0}": "Registered with Uvako: client_id {0}",
241
+ "Henüz kayıtlı değil (giriş yok ya da --offline). Kayıt için: uvako login, sonra uvako app deploy {0}": "Not registered yet (not signed in or --offline). To register: uvako login, then uvako app deploy {0}",
242
+ "Sıradaki adım: cd {0} && uvako app dev --store <geliştirme mağazası>": "Next step: cd {0} && uvako app dev --store <development store>",
243
+ "Partner paneline bağlanılamadı ({0}): {1}": "Could not connect to the partner panel ({0}): {1}",
244
+ "Bu adreste Uvako partner uç noktası yok (UVAKO_PARTNER_URL?).": "There is no Uvako partner endpoint at this address (UVAKO_PARTNER_URL?).",
245
+ " Yeniden giriş yapın: uvako login": " Sign in again: uvako login",
246
+ "Sunucu beklenmeyen bir doğrulama adresi verdi: {0}": "The server gave an unexpected verification address: {0}",
247
+ "Partner paneline giriş: tarayıcıda açılan sayfada kodu onaylayın.": "Partner sign-in: approve the code on the page that opens in your browser.",
248
+ " Adres: {0}": " Address: {0}",
249
+ " Kod: {0}\n": " Code: {0}\n",
250
+ "Giriş yapıldı: {0} ({1}) — {2}": "Signed in: {0} ({1}) — {2}",
251
+ "! Partner başvurunuz henüz onaylanmadı; uygulama kaydı onaydan sonra açılır.": "! Your partner application is not approved yet; app registration opens after approval.",
252
+ "✖ Giriş kodunun süresi doldu. `uvako login` komutunu yeniden çalıştırın.": "✖ The sign-in code expired. Run `uvako login` again.",
253
+ "Kayıtlı partner girişi yok.": "No saved partner sign-in.",
254
+ "Partner girişinden çıkıldı: {0}": "Signed out of the partner account: {0}",
255
+ "Partner: {0} — {1} ({2})": "Partner: {0} — {1} ({2})",
256
+ "Partner: {0}": "Partner: {0}",
257
+ "Partner paneline yalnızca https ile bağlanılır.": "The partner panel is reached over https only.",
258
+ "Partner girişi yapılmamış. Önce: uvako login": "Not signed in to a partner account. First: uvako login",
259
+ "Bilinmeyen uygulama komutu: {0}\n\n{1}": "Unknown app command: {0}\n\n{1}",
260
+ // Vitrin blokları (uvako app)
261
+ "{0}: bilinmeyen dosya {1}. Blokta yalnızca block.json, block.html ve block.css olabilir.": "{0}: unknown file {1}. A block may only contain block.json, block.html and block.css.",
262
+ "{0}: klasör adı küçük harf, rakam ve tire olmalı.": "{0}: the folder name must be lower-case letters, digits and hyphens.",
263
+ "{0}: block.html boş.": "{0}: block.html is empty.",
264
+ "{0}: blokta betik çalıştırılamaz (<script>, onclick=, javascript:).": "{0}: a block cannot run scripts (<script>, onclick=, javascript:).",
265
+ "{0}: şablonda yalnızca {{ .settings.ad }} ve if/else kullanılabilir.": "{0}: the template may only use {{ .settings.name }} and if/else.",
266
+ "{0}: block.css içinde url() ve @import kullanılamaz.": "{0}: block.css cannot use url() or @import.",
267
+ "{0}: block.json \"settings\" bir liste olmalı.": "{0}: block.json \"settings\" must be a list.",
268
+ // Bölüm motoru ("engine": "sections"), sunucu denetimi, push revizyonu.
269
+ "styles/{0}: yazı tipleri assets/ içinde pakete gömülmeli.": "styles/{0}: fonts must be embedded in the package under assets/.",
270
+ "{0}: geçersiz bölüm adı.": "{0}: invalid section name.",
271
+ "sections/{0}.schema.json: bölümün şablonu (sections/{0}.html.tmpl) yok.": "sections/{0}.schema.json: the section has no template (sections/{0}.html.tmpl).",
272
+ "sections/{0}.schema.json: \"fallback\" platformda ya da temel temada olan bir bölüm türü olmalı.": "sections/{0}.schema.json: \"fallback\" must be a section type of the platform or the base theme.",
273
+ "locales/{0}.json: şablonlardaki {1} metnin çevirisi yok (ör. \"{2}\").": "locales/{0}.json: {1} texts in the templates have no translation (e.g. \"{2}\").",
274
+ "{0}: bu dosya pakette bulunamaz. İzin verilenler: {1}.": "{0}: this file cannot be in a package. Allowed: {1}.",
275
+ "theme.json: bilinmeyen \"engine\" değeri.": "theme.json: unknown \"engine\" value.",
276
+ "Yeni sayfa ve bölüm dosyaları için theme.json içinde \"engine\":\"sections\" gerekli.": "Page and section files need \"engine\":\"sections\" in theme.json.",
277
+ "Bilinmeyen tema motoru: {0} (kullanılabilir: sections)": "Unknown theme engine: {0} (available: sections)",
278
+ "Sunucu denetimi için giriş yapın (uvako login --store <mağaza>) ya da --offline ekleyin.": "Log in to check on the server too (uvako login --store <store>), or add --offline.",
279
+ "✖ Sunucu denetimi ({0}): {1}": "✖ Server check ({0}): {1}",
280
+ "Sunucu denetimi ({0}): geçti.": "Server check ({0}): passed.",
281
+ " Yükleme sınırı aşıldı. {0} saniye sonra tekrar deneyin.": " Upload limit reached. Try again in {0} seconds.",
282
+ " Sunucudaki sürümü almak için: uvako theme pull <anahtar> --force. Üzerine yazmak için: uvako theme push --force": " To get the server's version: uvako theme pull <key> --force. To overwrite it: uvako theme push --force",
283
+ "Sunucu denetimi yapılamadı: {0}": "The server check could not run: {0}",
284
+ "kapanmamış şablon aksiyonu": "unterminated template action",
285
+ "izin verilmeyen öznitelik: {0}": "attribute not allowed: {0}",
286
+ "{0} içinde izin verilmeyen adres şeması": "URL scheme not allowed in {0}",
287
+ "srcset içinde izin verilmeyen adres şeması": "URL scheme not allowed in srcset",
288
+ "style özniteliğinde izin verilmeyen ifade: {0}": "expression not allowed in the style attribute: {0}",
289
+ "kapanmamış HTML yorumu": "unterminated HTML comment",
290
+ "işaretleme bildirimi kullanılamaz": "markup declarations are not allowed",
291
+ "şablon aksiyonu etiket adı oluşturamaz": "a template action cannot form a tag name",
292
+ "izin verilmeyen etiket: {0}": "element not allowed: {0}",
293
+ "kapanmamış etiket: {0}": "unterminated tag: {0}",
294
+ "şablon aksiyonu öznitelik adı oluşturamaz": "a template action cannot form an attribute name",
295
+ "{0} etiketinde kapanmamış öznitelik değeri": "unterminated attribute value on {0}",
296
+ "izin verilmeyen aksiyon: {0}": "action not allowed: {0}",
297
+ "bilinmeyen fonksiyon: {0}": "unknown function: {0}",
298
+ "{0} yalnızca paketin snippets/ şablonlarını adlandırabilir: {1}": "{0} may only name the package's snippets/ templates: {1}",
299
+ "snippet bulunamadı: {0}": "snippet not found: {0}",
300
+ "Pakette en çok 128 bölüm şablonu olabilir.": "A package may contain at most 128 section templates.",
301
+ "{0}: şablon adında yalnızca küçük harf, rakam, tire ve alt çizgi olabilir.": "{0}: a template name may contain only lowercase letters, digits, hyphens and underscores.",
302
+ "{0}: platform bölümünün adı kullanılamaz; başka bir ad verin.": "{0}: the name of a platform section cannot be used; choose another name.",
303
+ "{0}: bölüm ayar şeması geçersiz ({1}).": "{0}: invalid section setting schema ({1}).",
304
+ "nesne olmalı": "must be an object",
305
+ "\"name\" gerekli, en çok 100 karakter": "\"name\" is required, at most 100 characters",
306
+ "\"settings\" en çok {0} ayarlık bir liste olmalı": "\"settings\" must be a list of at most {0} settings",
307
+ "\"fallback\" bir bölüm türü olmalı": "\"fallback\" must be a section type",
308
+ "\"{0}\" ayar adı geçersiz ya da tekrarlı": "setting name \"{0}\" is invalid or repeated",
309
+ "\"{0}\" ayarının \"label\" alanı gerekli": "setting \"{0}\" needs a \"label\"",
310
+ "\"{0}\" ayarının türü ({1}) bilinmiyor; kullanılabilir: {2}": "setting \"{0}\" has an unknown type ({1}); available: {2}",
311
+ "\"{0}\" ayarının min/max değerleri geçersiz": "setting \"{0}\" has invalid min/max values",
312
+ "\"{0}\" ayarının step değeri geçersiz": "setting \"{0}\" has an invalid step",
313
+ "\"{0}\" (range) için min ve max gerekli": "\"{0}\" (range) needs min and max",
314
+ "\"{0}\" (select) için 1-50 seçenek gerekli": "\"{0}\" (select) needs 1-50 options",
315
+ "\"{0}\" ayarının seçenekleri geçersiz": "setting \"{0}\" has invalid options",
316
+ "\"{0}\" ayarının varsayılan değeri türüne uymuyor": "the default of setting \"{0}\" does not match its type",
317
+ "geçerli bir şablon değil ({\"order\": […], \"sections\": {…}}).": "not a valid template ({\"order\": […], \"sections\": {…}}).",
318
+ "en çok 100 bölüm olabilir.": "may contain at most 100 sections.",
319
+ "\"{0}\" bölüm kimliği geçersiz ya da tekrarlı.": "section id \"{0}\" is invalid or repeated.",
320
+ "\"order\" içindeki \"{0}\" bölümü \"sections\" altında yok.": "section \"{0}\" in \"order\" is not under \"sections\".",
321
+ "\"{0}\" bölüm kimliği geçersiz.": "section id \"{0}\" is invalid.",
322
+ "\"{0}\" bölümünün türü ({1}) ne platformda ne pakette ne de temel temada var.": "the type of section \"{0}\" ({1}) is neither a platform, package nor base theme section.",
323
+ "\"{0}\" bölümünün türü ({1}) pakette yok: sections/{2}.html.tmpl ekleyin.": "the type of section \"{0}\" ({1}) is not in the package: add sections/{2}.html.tmpl.",
324
+ "\"{0}\" bölümünün \"{1}\" ayarı betik ya da izin verilmeyen bir değer içeriyor.": "setting \"{1}\" of section \"{0}\" contains a script or a value that is not allowed.",
325
+ "\"{0}\" bölümünün \"{1}\" ayarı şemada yok ya da türüne uymuyor.": "setting \"{1}\" of section \"{0}\" is not in the schema or does not match its type.",
326
+ "Bu mağazada bölüm motoru etkin değil.": "The section engine is not enabled for this store.",
327
+ "Paket sunucuda sizin kopyanızdan sonra değişti. `uvako theme pull` ile güncelleyin ya da --force ile üzerine yazın.": "The package changed on the server after your copy. Update with `uvako theme pull` or overwrite with --force.",
328
+ "Paket yolda bozuldu (SHA-256 uyuşmuyor). Tekrar deneyin.": "The package was damaged in transit (SHA-256 mismatch). Try again.",
329
+ "Tema yükleme sınırı aşıldı. Belirtilen süre sonra tekrar deneyin.": "Theme upload limit reached. Try again after the given time.",
330
+ "Dosya boyut sınırını aşıyor.": "The file exceeds the size limit.",
331
+ "Bölüm şablonları derlenemedi: {0}": "Section templates could not be compiled: {0}",
194
332
  };
195
333
 
196
- export const help = (version) => `uvako-cli ${version} — Uvako theme development tool
334
+ export const help = (version) => `uvako-cli ${version} — Uvako theme and app development tool
197
335
  https://uvako.com
198
336
 
199
337
  Sign-in:
338
+ uvako login Signs in with your partner account (approved in the browser; app commands)
200
339
  uvako login --store <store> Connects to the store with a theme access key (you are asked for the key)
201
- uvako logout [--store <store>] Deletes the saved sign-in
202
- uvako whoami Shows the connected store
340
+ uvako logout [--store <store>] [--partner] Deletes the saved sign-in
341
+ uvako whoami Shows the connected store and the partner sign-in
342
+
343
+ App (public apps, with the partner sign-in):
344
+ uvako app init <name> [--dir folder] [--offline]
345
+ A sample app (OAuth install, signed webhooks) and uvako.app.json
346
+ uvako app dev [folder] --store <store> [--port 3000] [--no-serve] [--no-update]
347
+ Runs the app locally; the development store points to the local address
348
+ and its webhooks are relayed to the receiver on your computer
349
+ uvako app deploy [folder] [--submit] Deploys uvako.app.json as a new version (first only on your own stores)
350
+ uvako app submit [folder] Submits the app for Uvako review so every store can install it
351
+ uvako app list [--json] Lists your registered apps
203
352
 
204
353
  Theme:
205
- uvako theme init <key> [--base linea] [--name "Name"] [--author "Author"]
354
+ uvako theme init <key> [--base linea] [--name "Name"] [--author "Author"] [--engine sections]
206
355
  A new theme scaffold built on a catalog theme
207
- uvako theme check [folder] Checks the theme (structure, settings, translations, security)
208
- uvako theme dev [folder] Live development: uploads to the store as you save, the preview refreshes
356
+ --engine sections: a theme that brings its own sections (with examples)
357
+ uvako theme check [folder] [--offline] Checks the theme (structure, settings, translations, security, section templates);
358
+ when you are logged in the server checks it with the same rules too
359
+ uvako theme dev [folder] Live development: uploads only the file you changed, the preview refreshes
209
360
  The store returns to its previous state when you stop (--keep: leave in library)
210
- uvako theme push [folder] Uploads the theme to the store's theme library
211
- uvako theme pull <key> [--dir folder] Downloads a theme you uploaded
361
+ uvako theme push [folder] [--force] Uploads the theme to the store's theme library (with SHA-256 and revision;
362
+ refused if the package changed after your copy, --force overwrites)
363
+ uvako theme pull <key> [--dir folder] Downloads a theme you uploaded (including sections/, snippets/, templates/)
212
364
  uvako theme list Lists the themes in the store and the packages you uploaded
213
365
  uvako theme submit <key> Submits the theme for review for themes.uvako.com
214
366
  uvako theme delete <key> --yes Deletes a package you uploaded
215
367
  uvako theme package [folder] [--out x.zip] Checks the theme and builds a zip package
216
368
  uvako version Prints the version
217
369
 
218
- A theme is a package that runs no code: settings, section order, SCSS and images.
219
- Packages containing JavaScript, templates or server code are rejected. An uploaded theme is
220
- visible only in your own store; publishing it in the theme store depends on Uvako's review.
370
+ A theme is a package that runs no code: settings, page templates, SCSS and images. "engine": "sections"
371
+ themes may bring their own sections (sections/*.html.tmpl); they are drawn only on the server, in a
372
+ restricted template language. Packages containing JavaScript or server code are rejected. An uploaded
373
+ theme is visible only in your own store; publishing it in the theme store depends on Uvako's review.
221
374
 
222
- For CI: the UVAKO_STORE and UVAKO_TOKEN environment variables take the place of signing in.
375
+ For CI: the UVAKO_STORE and UVAKO_TOKEN (theme) and UVAKO_PARTNER_TOKEN (app) environment variables take the place of signing in.
223
376
  Language: --lang tr or UVAKO_LANG=tr prints the output in Turkish.
224
377
 
225
378
  Uvako — https://uvako.com