uvako-cli 0.4.0 → 0.5.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/locales/en.js CHANGED
@@ -191,15 +191,100 @@ 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.",
194
268
  };
195
269
 
196
- export const help = (version) => `uvako-cli ${version} — Uvako theme development tool
270
+ export const help = (version) => `uvako-cli ${version} — Uvako theme and app development tool
197
271
  https://uvako.com
198
272
 
199
273
  Sign-in:
274
+ uvako login Signs in with your partner account (approved in the browser; app commands)
200
275
  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
276
+ uvako logout [--store <store>] [--partner] Deletes the saved sign-in
277
+ uvako whoami Shows the connected store and the partner sign-in
278
+
279
+ App (public apps, with the partner sign-in):
280
+ uvako app init <name> [--dir folder] [--offline]
281
+ A sample app (OAuth install, signed webhooks) and uvako.app.json
282
+ uvako app dev [folder] --store <store> [--port 3000] [--no-serve] [--no-update]
283
+ Runs the app locally; the development store points to the local address
284
+ and its webhooks are relayed to the receiver on your computer
285
+ uvako app deploy [folder] [--submit] Deploys uvako.app.json as a new version (first only on your own stores)
286
+ uvako app submit [folder] Submits the app for Uvako review so every store can install it
287
+ uvako app list [--json] Lists your registered apps
203
288
 
204
289
  Theme:
205
290
  uvako theme init <key> [--base linea] [--name "Name"] [--author "Author"]
@@ -219,7 +304,7 @@ A theme is a package that runs no code: settings, section order, SCSS and images
219
304
  Packages containing JavaScript, templates or server code are rejected. An uploaded theme is
220
305
  visible only in your own store; publishing it in the theme store depends on Uvako's review.
221
306
 
222
- For CI: the UVAKO_STORE and UVAKO_TOKEN environment variables take the place of signing in.
307
+ For CI: the UVAKO_STORE and UVAKO_TOKEN (theme) and UVAKO_PARTNER_TOKEN (app) environment variables take the place of signing in.
223
308
  Language: --lang tr or UVAKO_LANG=tr prints the output in Turkish.
224
309
 
225
310
  Uvako — https://uvako.com
package/src/main.js CHANGED
@@ -7,18 +7,31 @@ import { session, siteOrigin } from "./config.js";
7
7
  import { initTheme } from "./init.js";
8
8
  import { packageTheme } from "./package.js";
9
9
  import { dev, list, login, logout, pull, push, remove, submit, whoami } from "./remote.js";
10
+ import { appCommand } from "./app.js";
11
+ import { partnerLogin, partnerLogout, partnerWhoami } from "./partner.js";
10
12
  import { getLocale, t } from "./i18n.js";
11
13
  import { help as helpEn } from "./locales/en.js";
12
14
 
13
15
  const version = JSON.parse(readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf8")).version;
14
16
 
15
- const helpTr = `uvako-cli ${version} — Uvako tema geliştirme aracı
17
+ const helpTr = `uvako-cli ${version} — Uvako tema ve uygulama geliştirme aracı
16
18
  https://uvako.com
17
19
 
18
20
  Giriş:
21
+ uvako login Partner hesabıyla giriş (tarayıcıda onaylanır; uygulama komutları)
19
22
  uvako login --store <mağaza> Mağazaya tema erişim anahtarıyla bağlanır (anahtar sorulur)
20
- uvako logout [--store <mağaza>] Kayıtlı girişi siler
21
- uvako whoami Bağlı mağazayı gösterir
23
+ uvako logout [--store <mağaza>] [--partner] Kayıtlı girişi siler
24
+ uvako whoami Bağlı mağazayı ve partner girişini gösterir
25
+
26
+ Uygulama (herkese açık uygulamalar, partner girişiyle):
27
+ uvako app init <ad> [--dir klasör] [--offline]
28
+ Örnek uygulama (OAuth kurulumu, imzalı webhook'lar) ve uvako.app.json
29
+ uvako app dev [klasör] --store <mağaza> [--port 3000] [--no-serve] [--no-update]
30
+ Uygulamayı yerelde çalıştırır; geliştirme mağazası yerel adrese bağlanır,
31
+ o mağazanın webhook'ları bilgisayarınızdaki alıcıya aktarılır
32
+ uvako app deploy [klasör] [--submit] uvako.app.json'u yeni sürüm olarak dağıtır (önce yalnızca kendi mağazalarınızda)
33
+ uvako app submit [klasör] Uygulamayı herkese açılması için Uvako incelemesine gönderir
34
+ uvako app list [--json] Kayıtlı uygulamalarınızı listeler
22
35
 
23
36
  Tema:
24
37
  uvako theme init <anahtar> [--base linea] [--name "Ad"] [--author "Yazar"]
@@ -38,7 +51,7 @@ Tema, kod çalıştırmayan bir pakettir: ayarlar, bölüm sırası, SCSS ve gö
38
51
  JavaScript, şablon ya da sunucu kodu içeren paketler reddedilir. Yüklenen tema yalnızca kendi
39
52
  mağazanızda görünür; tema mağazasında yayınlanması Uvako incelemesine bağlıdır.
40
53
 
41
- CI için: UVAKO_STORE ve UVAKO_TOKEN ortam değişkenleri girişin yerini tutar.
54
+ CI için: UVAKO_STORE ve UVAKO_TOKEN (tema), UVAKO_PARTNER_TOKEN (uygulama) ortam değişkenleri girişin yerini tutar.
42
55
  Dil: --lang en ya da UVAKO_LANG=en çıktıyı İngilizce verir.
43
56
 
44
57
  Uvako — https://uvako.com
@@ -93,9 +106,31 @@ export async function main(argv) {
93
106
  console.log(version);
94
107
  return 0;
95
108
  }
96
- if (group === "login") return login(options);
97
- if (group === "logout") return logout(options);
98
- if (group === "whoami") return whoami(options);
109
+ // Tek giriş: mağaza belirtilmezse partner hesabıyla (uygulama komutları), belirtilirse tema anahtarıyla.
110
+ if (group === "login") return options.store ? login(options) : partnerLogin(options);
111
+ if (group === "logout") {
112
+ if (options.partner) return partnerLogout();
113
+ const code = await logout(options);
114
+ if (options.all) await partnerLogout();
115
+ return code;
116
+ }
117
+ if (group === "whoami") {
118
+ const partner = await partnerWhoami();
119
+ if (partner && !options.store && !process.env.UVAKO_STORE) {
120
+ try {
121
+ session("");
122
+ } catch {
123
+ return 0;
124
+ }
125
+ }
126
+ return whoami(options);
127
+ }
128
+ if (group === "app") {
129
+ const code = await appCommand(command, target, options);
130
+ if (code !== null) return code;
131
+ console.error(t("Bilinmeyen uygulama komutu: {0}\n\n{1}", command ?? "(yok)", help));
132
+ return 1;
133
+ }
99
134
  if (group !== "theme") {
100
135
  console.error(t("Bilinmeyen komut: {0}\n\n{1}", group, help));
101
136
  return 1;
package/src/partner.js ADDED
@@ -0,0 +1,135 @@
1
+ import { spawn } from "node:child_process";
2
+ import os from "node:os";
3
+ import { dropPartner, partnerOrigin, partnerSaved, partnerSession, savePartner } from "./config.js";
4
+ import { ApiError } from "./api.js";
5
+ import { t, tm } from "./i18n.js";
6
+
7
+ // Partner paneliyle konuşan istemci (/api/partner/cli). Giriş cihaz akışıyla yapılır: CLI bir kod
8
+ // alır, partner tarayıcıda partner paneline (şifre + iki adımlı doğrulama) girip onaylar, CLI uvp_
9
+ // anahtarını alır. Anahtar ekrana yazılmaz, yalnızca partner adresine gönderilir; yönlendirme izlenmez.
10
+
11
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
12
+
13
+ async function request(origin, method, route, { token, body, timeout = 60000 } = {}) {
14
+ let response;
15
+ try {
16
+ response = await fetch(`${origin}/api/partner/cli${route}`, {
17
+ method,
18
+ redirect: "error",
19
+ headers: { Accept: "application/json", "User-Agent": "uvako-cli", ...(token ? { Authorization: `Bearer ${token}` } : {}), ...(body !== undefined ? { "Content-Type": "application/json" } : {}) },
20
+ body: body !== undefined ? JSON.stringify(body) : undefined,
21
+ signal: AbortSignal.timeout(timeout),
22
+ });
23
+ } catch (error) {
24
+ throw new ApiError(t("Partner paneline bağlanılamadı ({0}): {1}", origin, error.cause?.code ?? error.message));
25
+ }
26
+ let payload = null;
27
+ try {
28
+ payload = await response.json();
29
+ } catch {
30
+ // JSON olmayan yanıt (ör. ağ geçidi hatası).
31
+ }
32
+ if (response.ok) return payload;
33
+ // Cihaz girişinin RFC 8628 yanıtları {"error": "kod", "error_description": "…"} biçimindedir.
34
+ if (typeof payload?.error === "string") throw new ApiError(payload.error_description || payload.error, { status: response.status, code: payload.error });
35
+ const problem = payload?.error ?? {};
36
+ const fallback = response.status === 404 ? t("Bu adreste Uvako partner uç noktası yok (UVAKO_PARTNER_URL?).") : t("Sunucu {0} yanıtı verdi.", response.status);
37
+ throw new ApiError(problem.message || fallback, { status: response.status, code: problem.field || "", problems: [] });
38
+ }
39
+
40
+ /** Giriş yapmış partner olarak uç noktayı çağırır. */
41
+ export function partnerCall(method, route, options = {}) {
42
+ const current = partnerSession();
43
+ return request(current.origin, method, route, { ...options, token: current.token });
44
+ }
45
+
46
+ export function explainPartner(error) {
47
+ if (!(error instanceof ApiError)) throw error;
48
+ console.error(`✖ ${tm(error.message)}${error.status === 422 && error.code ? ` (${error.code})` : ""}`);
49
+ if (error.status === 401) console.error(t(" Yeniden giriş yapın: uvako login"));
50
+ return 1;
51
+ }
52
+
53
+ function openBrowser(url) {
54
+ const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
55
+ try {
56
+ const child = spawn(command, args, { stdio: "ignore", detached: true, windowsHide: true });
57
+ child.on("error", () => undefined);
58
+ child.unref();
59
+ } catch {
60
+ // Tarayıcı açılamazsa adres zaten ekranda.
61
+ }
62
+ }
63
+
64
+ /** `uvako login` (mağaza belirtilmeden): partner hesabıyla cihaz girişi. */
65
+ export async function partnerLogin(options) {
66
+ const origin = partnerOrigin();
67
+ let device;
68
+ try {
69
+ device = await request(origin, "POST", "/device", { body: { label: `uvako-cli · ${os.hostname()}`.slice(0, 80) } });
70
+ } catch (error) {
71
+ return explainPartner(error);
72
+ }
73
+ // Sunucunun verdiği adres yalnızca partner panelinin kendi alan adındaysa açılır (yerel geliştirmede
74
+ // panel ile API farklı portlarda olabilir; şifresiz bağlantı yalnızca yerel adreste kabul edilir).
75
+ const verify = new URL(device.verification_uri_complete);
76
+ const api = new URL(origin);
77
+ const sameHost = verify.hostname === api.hostname && (verify.protocol === "https:" || (verify.protocol === "http:" && api.protocol === "http:"));
78
+ if (!sameHost) throw new Error(t("Sunucu beklenmeyen bir doğrulama adresi verdi: {0}", verify.origin));
79
+ console.log(t("Partner paneline giriş: tarayıcıda açılan sayfada kodu onaylayın."));
80
+ console.log(t(" Adres: {0}", verify.href));
81
+ console.log(t(" Kod: {0}\n", device.user_code));
82
+ if (!options["no-browser"]) openBrowser(verify.href);
83
+ let interval = Math.max(1, Number(device.interval) || 3) * 1000;
84
+ const deadline = Date.now() + (Number(device.expires_in) || 600) * 1000;
85
+ while (Date.now() < deadline) {
86
+ await sleep(interval);
87
+ try {
88
+ const result = await request(origin, "POST", "/token", { body: { device_code: device.device_code } });
89
+ const me = await request(origin, "GET", "/me", { token: result.access_token });
90
+ const name = `${me.member?.firstName ?? ""} ${me.member?.lastName ?? ""}`.trim();
91
+ savePartner(origin, result.access_token, me.company?.name ?? "", me.member?.email ?? "");
92
+ console.log(t("Giriş yapıldı: {0} ({1}) — {2}", name || me.member?.email, me.company?.name ?? "", origin));
93
+ if (me.company?.status !== "approved") console.warn(t("! Partner başvurunuz henüz onaylanmadı; uygulama kaydı onaydan sonra açılır."));
94
+ return 0;
95
+ } catch (error) {
96
+ if (!(error instanceof ApiError)) throw error;
97
+ if (error.code === "authorization_pending") continue;
98
+ if (error.code === "slow_down") {
99
+ interval += 2000;
100
+ continue;
101
+ }
102
+ return explainPartner(error);
103
+ }
104
+ }
105
+ console.error(t("✖ Giriş kodunun süresi doldu. `uvako login` komutunu yeniden çalıştırın."));
106
+ return 1;
107
+ }
108
+
109
+ export async function partnerLogout() {
110
+ const saved = partnerSaved();
111
+ if (!saved) {
112
+ console.log(t("Kayıtlı partner girişi yok."));
113
+ return 0;
114
+ }
115
+ try {
116
+ await partnerCall("POST", "/logout", { body: {} });
117
+ } catch {
118
+ // Sunucuya ulaşılamasa da yerel giriş silinir; anahtar partner panelinden de kapatılabilir.
119
+ }
120
+ dropPartner();
121
+ console.log(t("Partner girişinden çıkıldı: {0}", saved.origin));
122
+ return 0;
123
+ }
124
+
125
+ export async function partnerWhoami() {
126
+ if (!partnerSaved() && !process.env.UVAKO_PARTNER_TOKEN) return false;
127
+ try {
128
+ const me = await partnerCall("GET", "/me");
129
+ console.log(t("Partner: {0} — {1} ({2})", me.company?.name ?? "", me.member?.email ?? "", partnerSession().origin));
130
+ } catch (error) {
131
+ if (error instanceof ApiError) console.error(t("Partner: {0}", tm(error.message)));
132
+ else throw error;
133
+ }
134
+ return true;
135
+ }
@@ -0,0 +1,39 @@
1
+ # {{name}}
2
+
3
+ Uvako için herkese açık uygulama. `uvako app init` ile oluşturuldu.
4
+
5
+ ## Yerelde çalıştırma
6
+
7
+ ```bash
8
+ uvako login # partner hesabıyla bir kez
9
+ uvako app dev --store <mağaza> # geliştirme mağazalarınızdan biri
10
+ ```
11
+
12
+ `uvako app dev`:
13
+
14
+ - uygulamayı (`node server.js`) client id ve secret ortam değişkenleriyle başlatır,
15
+ - seçtiğiniz geliştirme mağazasında uygulamanın adresini `http://localhost:3000` yapar,
16
+ - o mağazanın webhook'larını bilgisayarınıza aktarır (imza ve başlıklar aynen gelir),
17
+ - kurulum bağlantısını yazar. Bağlantıyı açıp onay ekranında **Uygulamayı kur** deyin.
18
+
19
+ Durdurduğunuzda (Ctrl+C) mağaza dağıtılan adresleri yeniden kullanır.
20
+
21
+ ## Yapılandırma
22
+
23
+ `uvako.app.json` uygulamanın yapılandırmasıdır: ad, adresler, kapsamlar, webhook'lar ve uygulama
24
+ mağazası sayfası. Değiştirdikten sonra:
25
+
26
+ ```bash
27
+ uvako app deploy # yeni sürüm: önce yalnızca kendi mağazalarınızda geçerli
28
+ uvako app submit # herkese açmak için Uvako incelemesine gönderir
29
+ ```
30
+
31
+ Client secret bu klasöre yazılmaz. Sunucunuzda `UVAKO_CLIENT_SECRET` ortam değişkeniyle verin.
32
+
33
+ ## Güvenlik
34
+
35
+ - Kurulum dönüşünde `hmac` ve `state` doğrulanır; `shop` yalnızca `*.uvoo.store` olabilir.
36
+ - Webhook gövdesi ayrıştırılmadan önce `X-Uvako-Hmac-Sha256` imzası doğrulanır.
37
+ - Erişim anahtarları `.uvako/tokens.json` dosyasındadır; bu klasörü depoya koymayın.
38
+
39
+ Ayrıntı: Uvako Admin API belgesi → Herkese açık uygulamalar.
@@ -0,0 +1,5 @@
1
+ .yp-block { max-width: 720px; margin: 32px auto; padding: 24px; border-radius: 16px; background: #fafafa; text-align: center; }
2
+ .yp-title { margin: 0 0 8px; font-size: 22px; }
3
+ .yp-stars { margin: 0 0 8px; font-size: 28px; letter-spacing: 4px; }
4
+ .yp-text { margin: 0; color: #555; }
5
+ .yp-link { display: inline-block; margin-top: 12px; }
@@ -0,0 +1,8 @@
1
+ <section class="yp-block">
2
+ <h2 class="yp-title">{{ .settings.title }}</h2>
3
+ <p class="yp-stars" style="color: {{ .settings.color }}" aria-label="{{ .settings.stars }} / 5">
4
+ {{ if eq .settings.stars 5.0 }}★★★★★{{ else if eq .settings.stars 4.0 }}★★★★☆{{ else if eq .settings.stars 3.0 }}★★★☆☆{{ else if eq .settings.stars 2.0 }}★★☆☆☆{{ else }}★☆☆☆☆{{ end }}
5
+ </p>
6
+ <p class="yp-text">{{ .settings.text }}</p>
7
+ {{ if and .settings.show_link .settings.link }}<a class="yp-link" href="{{ .settings.link }}">Tüm yorumlar</a>{{ end }}
8
+ </section>
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "Yıldız puanı",
3
+ "settings": [
4
+ { "id": "title", "type": "text", "label": "Başlık", "default": "Müşterilerimiz ne diyor?" },
5
+ { "id": "text", "type": "textarea", "label": "Metin", "default": "Binlerce mutlu müşteri." },
6
+ { "id": "stars", "type": "range", "label": "Yıldız", "min": 1, "max": 5, "default": 5 },
7
+ { "id": "color", "type": "color", "label": "Yıldız rengi", "default": "#f5a524" },
8
+ { "id": "link", "type": "url", "label": "Bağlantı", "info": "https adresi ya da mağaza içi yol (ör. /yorumlar)" },
9
+ { "id": "show_link", "type": "checkbox", "label": "Bağlantıyı göster", "default": false }
10
+ ]
11
+ }