uvako-cli 0.3.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/README.md +65 -2
- package/bin/uvako-cli.js +12 -7
- package/package.json +4 -3
- package/src/api.js +6 -5
- package/src/app.js +412 -0
- package/src/check.js +127 -67
- package/src/config.js +50 -7
- package/src/i18n.js +67 -0
- package/src/init.js +74 -17
- package/src/locales/en.js +311 -0
- package/src/main.js +56 -16
- package/src/package.js +3 -2
- package/src/partner.js +135 -0
- package/src/remote.js +51 -50
- package/src/zip.js +5 -4
- package/templates/app/README.md +39 -0
- package/templates/app/blocks/yildiz-puani/block.css +5 -0
- package/templates/app/blocks/yildiz-puani/block.html +8 -0
- package/templates/app/blocks/yildiz-puani/block.json +11 -0
- package/templates/app/server.js +357 -0
package/src/package.js
CHANGED
|
@@ -4,13 +4,14 @@ import path from "node:path";
|
|
|
4
4
|
import { checkTheme, limits, localOnly } from "./check.js";
|
|
5
5
|
import { walk } from "./files.js";
|
|
6
6
|
import { zip } from "./zip.js";
|
|
7
|
+
import { t } from "./i18n.js";
|
|
7
8
|
|
|
8
9
|
/** Denetimden geçen temanın zip paketini bellekte üretir. Yardımcı dosyalar (README, reference/) pakete girmez. */
|
|
9
10
|
export function buildArchive(dir) {
|
|
10
11
|
const report = checkTheme(dir);
|
|
11
12
|
if (report.errors.length) return { ok: false, report };
|
|
12
13
|
if (report.layout !== "package") {
|
|
13
|
-
report.errors.push("Bu klasör platform deposundaki bir tema; paketleme ve yükleme yalnızca bağımsız tema klasörlerinde yapılır.");
|
|
14
|
+
report.errors.push(t("Bu klasör platform deposundaki bir tema; paketleme ve yükleme yalnızca bağımsız tema klasörlerinde yapılır."));
|
|
14
15
|
return { ok: false, report };
|
|
15
16
|
}
|
|
16
17
|
const meta = JSON.parse(readFileSync(path.join(dir, "theme.json"), "utf8"));
|
|
@@ -19,7 +20,7 @@ export function buildArchive(dir) {
|
|
|
19
20
|
.map((file) => ({ name: `${meta.key}/${file.rel}`, data: readFileSync(file.full) }));
|
|
20
21
|
const buffer = zip(entries);
|
|
21
22
|
if (buffer.length > limits.archiveBytes) {
|
|
22
|
-
report.errors.push(
|
|
23
|
+
report.errors.push(t("Paket {0} MB; yükleme sınırı {1} MB.", (buffer.length / 1024 / 1024).toFixed(0), limits.archiveBytes / 1024 / 1024));
|
|
23
24
|
return { ok: false, report };
|
|
24
25
|
}
|
|
25
26
|
return { ok: true, report, meta, buffer, files: entries.length, sha256: createHash("sha256").update(buffer).digest("hex") };
|
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
|
+
}
|
package/src/remote.js
CHANGED
|
@@ -5,6 +5,7 @@ import { printReport } from "./check.js";
|
|
|
5
5
|
import { dropSession, saveSession, session, sessions, storeOrigin } from "./config.js";
|
|
6
6
|
import { buildArchive } from "./package.js";
|
|
7
7
|
import { unzip } from "./zip.js";
|
|
8
|
+
import { t, tm } from "./i18n.js";
|
|
8
9
|
|
|
9
10
|
// Mağazayla konuşan komutlar: giriş, yükleme, canlı geliştirme, indirme, incelemeye gönderme.
|
|
10
11
|
|
|
@@ -35,7 +36,7 @@ function askSecret(question) {
|
|
|
35
36
|
const onData = (chunk) => {
|
|
36
37
|
for (const char of chunk) {
|
|
37
38
|
if (char === "\r" || char === "\n") return done(resolve, value.trim());
|
|
38
|
-
if (char === "\u0003") return done(reject, new Error("İptal edildi."));
|
|
39
|
+
if (char === "\u0003") return done(reject, new Error(t("İptal edildi.")));
|
|
39
40
|
if (char === "\u007f" || char === "\b") value = value.slice(0, -1);
|
|
40
41
|
else if (char >= " ") value += char;
|
|
41
42
|
}
|
|
@@ -46,9 +47,9 @@ function askSecret(question) {
|
|
|
46
47
|
|
|
47
48
|
function explain(error) {
|
|
48
49
|
if (!(error instanceof ApiError)) throw error;
|
|
49
|
-
console.error(`✖ ${error.message}`);
|
|
50
|
-
for (const problem of error.problems) console.error(` ✖ ${problem}`);
|
|
51
|
-
if (error.status === 401) console.error(" Yeni anahtar: mağaza paneli → Ayarlar → Temalar → Geliştirici erişimi, sonra `uvako login`.");
|
|
50
|
+
console.error(`✖ ${tm(error.message)}`);
|
|
51
|
+
for (const problem of error.problems) console.error(` ✖ ${tm(problem)}`);
|
|
52
|
+
if (error.status === 401) console.error(t(" Yeni anahtar: mağaza paneli → Ayarlar → Temalar → Geliştirici erişimi, sonra `uvako login`."));
|
|
52
53
|
return 1;
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -56,16 +57,16 @@ const previewUrl = (origin, installationId) => `${origin}/?preview=${installatio
|
|
|
56
57
|
const editorUrl = (origin, installationId) => `${origin}/admin/ayarlar/temalar/${installationId}/editor`;
|
|
57
58
|
|
|
58
59
|
export async function login(options) {
|
|
59
|
-
if (!options.store || options.store === true) throw new Error("Mağaza adresi gerekli: uvako login --store <mağaza>");
|
|
60
|
-
if (options.token) throw new Error("Erişim anahtarı komut satırına yazılmaz. Komutu --token olmadan çalıştırın; anahtar sorulacak (ya da UVAKO_TOKEN ortam değişkenini kullanın).");
|
|
60
|
+
if (!options.store || options.store === true) throw new Error(t("Mağaza adresi gerekli: uvako login --store <mağaza>"));
|
|
61
|
+
if (options.token) throw new Error(t("Erişim anahtarı komut satırına yazılmaz. Komutu --token olmadan çalıştırın; anahtar sorulacak (ya da UVAKO_TOKEN ortam değişkenini kullanın)."));
|
|
61
62
|
const origin = storeOrigin(options.store);
|
|
62
|
-
const token = (process.env.UVAKO_TOKEN || (await askSecret(
|
|
63
|
-
if (!/^uvt_[A-Za-z0-9_-]{20,}$/.test(token)) throw new Error("Bu bir tema erişim anahtarı değil (uvt_ ile başlar).");
|
|
63
|
+
const token = (process.env.UVAKO_TOKEN || (await askSecret(t("{0} için tema erişim anahtarı (panel → Ayarlar → Temalar → Geliştirici erişimi): ", origin)))).trim();
|
|
64
|
+
if (!/^uvt_[A-Za-z0-9_-]{20,}$/.test(token)) throw new Error(t("Bu bir tema erişim anahtarı değil (uvt_ ile başlar)."));
|
|
64
65
|
try {
|
|
65
66
|
const me = await call({ origin, token }, "GET", "/me");
|
|
66
67
|
saveSession(origin, token, me.store?.name ?? "");
|
|
67
|
-
console.log(
|
|
68
|
-
if (me.uploadBlocked) console.warn("! Bu mağaza için tema yükleme kapatılmış. Uvako desteğine yazın.");
|
|
68
|
+
console.log(t("Giriş yapıldı: {0} ({1})", me.store?.name ?? origin, origin));
|
|
69
|
+
if (me.uploadBlocked) console.warn(t("! Bu mağaza için tema yükleme kapatılmış. Uvako desteğine yazın."));
|
|
69
70
|
return 0;
|
|
70
71
|
} catch (error) {
|
|
71
72
|
return explain(error);
|
|
@@ -74,8 +75,8 @@ export async function login(options) {
|
|
|
74
75
|
|
|
75
76
|
export function logout(options) {
|
|
76
77
|
const origin = dropSession(typeof options.store === "string" ? options.store : "");
|
|
77
|
-
console.log(origin ?
|
|
78
|
-
console.log("Anahtarı tamamen geçersiz kılmak için mağaza panelinden iptal edin.");
|
|
78
|
+
console.log(origin ? t("Çıkış yapıldı: {0}", origin) : t("Kayıtlı giriş yok."));
|
|
79
|
+
console.log(t("Anahtarı tamamen geçersiz kılmak için mağaza panelinden iptal edin."));
|
|
79
80
|
return 0;
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -84,14 +85,14 @@ export async function whoami(options) {
|
|
|
84
85
|
const current = session(typeof options.store === "string" ? options.store : "");
|
|
85
86
|
const me = await call(current, "GET", "/me");
|
|
86
87
|
console.log(`${me.store?.name ?? ""} — ${current.origin} (anahtar: ${me.token})`);
|
|
87
|
-
for (const other of sessions()) if (!other.current) console.log(
|
|
88
|
+
for (const other of sessions()) if (!other.current) console.log(t(" diğer giriş: {0} — {1}", other.name || other.origin, other.origin));
|
|
88
89
|
return 0;
|
|
89
90
|
} catch (error) {
|
|
90
91
|
return explain(error);
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
const statusLabels = { private: "yalnızca bu mağazada", pending: "incelemede", approved: "tema mağazasında", rejected: "reddedildi" };
|
|
95
|
+
const statusLabels = { private: t("yalnızca bu mağazada"), pending: "incelemede", approved: t("tema mağazasında"), rejected: "reddedildi" };
|
|
95
96
|
|
|
96
97
|
export async function list(options) {
|
|
97
98
|
try {
|
|
@@ -101,11 +102,11 @@ export async function list(options) {
|
|
|
101
102
|
console.log(JSON.stringify(data, null, 2));
|
|
102
103
|
return 0;
|
|
103
104
|
}
|
|
104
|
-
console.log(
|
|
105
|
-
for (const item of data.installations) console.log(` ${item.role === "published" ? "● yayında " : "○ kütüphane"} ${item.name} [${item.key}]${item.custom ? " (paket)" : ""}`);
|
|
106
|
-
console.log(data.packages.length ? "\nYüklediğiniz paketler:" : "\nYüklediğiniz paket yok.");
|
|
105
|
+
console.log(t("Mağazadaki temalar ({0}):", current.origin));
|
|
106
|
+
for (const item of data.installations) console.log(` ${item.role === "published" ? t("● yayında ") : t("○ kütüphane")} ${item.name} [${item.key}]${item.custom ? " (paket)" : ""}`);
|
|
107
|
+
console.log(data.packages.length ? t("\nYüklediğiniz paketler:") : t("\nYüklediğiniz paket yok."));
|
|
107
108
|
for (const pkg of data.packages) {
|
|
108
|
-
console.log(` ${pkg.key} ${pkg.version} (temel: ${pkg.base}) ${statusLabels[pkg.status] ?? pkg.status}${pkg.publishedVersion ?
|
|
109
|
+
console.log(` ${pkg.key} ${pkg.version} (temel: ${pkg.base}) ${statusLabels[pkg.status] ?? pkg.status}${pkg.publishedVersion ? t(" · mağazadaki sürüm {0}", pkg.publishedVersion) : ""}`);
|
|
109
110
|
if (pkg.status === "rejected" && pkg.reviewNote) console.log(` not: ${pkg.reviewNote}`);
|
|
110
111
|
}
|
|
111
112
|
return 0;
|
|
@@ -132,15 +133,15 @@ export async function push(target, options) {
|
|
|
132
133
|
const current = session(typeof options.store === "string" ? options.store : "");
|
|
133
134
|
const result = await upload(current, dir);
|
|
134
135
|
if (!result) {
|
|
135
|
-
console.error("Yüklenmedi: önce hataları düzeltin.");
|
|
136
|
+
console.error(t("Yüklenmedi: önce hataları düzeltin."));
|
|
136
137
|
return 1;
|
|
137
138
|
}
|
|
138
139
|
const pkg = result.package;
|
|
139
|
-
console.log(
|
|
140
|
-
console.log(
|
|
141
|
-
console.log(
|
|
142
|
-
console.log("Önizleme için tarayıcıda mağaza paneline giriş yapmış olmalısınız. Tema, panelden yayınlanana kadar vitrini değiştirmez.");
|
|
143
|
-
if (pkg.publishedVersion) console.log(
|
|
140
|
+
console.log(t("{0}: {1} {2} (sürüm no {3}) → {4}", result.created ? t("Yüklendi") : t("Güncellendi"), pkg.name, pkg.version, pkg.revision, current.origin));
|
|
141
|
+
console.log(t("Önizleme: {0}", previewUrl(current.origin, result.installationId)));
|
|
142
|
+
console.log(t("Düzenleme: {0}", editorUrl(current.origin, result.installationId)));
|
|
143
|
+
console.log(t("Önizleme için tarayıcıda mağaza paneline giriş yapmış olmalısınız. Tema, panelden yayınlanana kadar vitrini değiştirmez."));
|
|
144
|
+
if (pkg.publishedVersion) console.log(t("Tema mağazasındaki sürüm ({0}) değişmedi. Yeni sürümü incelemeye göndermek için: uvako theme submit {1}", pkg.publishedVersion, pkg.key));
|
|
144
145
|
return 0;
|
|
145
146
|
} catch (error) {
|
|
146
147
|
return explain(error);
|
|
@@ -167,18 +168,18 @@ export async function dev(target, options) {
|
|
|
167
168
|
first = await upload(current, dir);
|
|
168
169
|
} catch (error) {
|
|
169
170
|
if (error instanceof ApiError) return explain(error);
|
|
170
|
-
console.error(`✖ ${error.message}`);
|
|
171
|
+
console.error(`✖ ${tm(error.message)}`);
|
|
171
172
|
return 1;
|
|
172
173
|
}
|
|
173
174
|
if (!first) {
|
|
174
|
-
console.error("Başlatılamadı: önce hataları düzeltin.");
|
|
175
|
+
console.error(t("Başlatılamadı: önce hataları düzeltin."));
|
|
175
176
|
return 1;
|
|
176
177
|
}
|
|
177
|
-
console.log(
|
|
178
|
-
console.log(
|
|
179
|
-
console.log(
|
|
180
|
-
console.log("Önizleme için tarayıcıda mağaza paneline giriş yapın. Dosyaları kaydettikçe sayfa yenilenir. Durdurmak için Ctrl+C.\n");
|
|
181
|
-
console.log(options.keep ? "Durdurduğunuzda tema son hâliyle kütüphanede kalır (--keep)." : existed ? "Durdurduğunuzda mağazadaki paket geliştirme öncesindeki sürümüne döner. Kalıcı yüklemek için: uvako theme push" : "Durdurduğunuzda geliştirme kopyası mağazadan silinir. Kalıcı yüklemek için: uvako theme push");
|
|
178
|
+
console.log(t("\nCanlı geliştirme: {0} → {1}", first.package.name, current.origin));
|
|
179
|
+
console.log(t("Önizleme: {0}", previewUrl(current.origin, first.installationId)));
|
|
180
|
+
console.log(t("Düzenleme: {0}", editorUrl(current.origin, first.installationId)));
|
|
181
|
+
console.log(t("Önizleme için tarayıcıda mağaza paneline giriş yapın. Dosyaları kaydettikçe sayfa yenilenir. Durdurmak için Ctrl+C.\n"));
|
|
182
|
+
console.log(options.keep ? t("Durdurduğunuzda tema son hâliyle kütüphanede kalır (--keep).") : existed ? t("Durdurduğunuzda mağazadaki paket geliştirme öncesindeki sürümüne döner. Kalıcı yüklemek için: uvako theme push") : t("Durdurduğunuzda geliştirme kopyası mağazadan silinir. Kalıcı yüklemek için: uvako theme push"));
|
|
182
183
|
console.log("");
|
|
183
184
|
|
|
184
185
|
let timer = null;
|
|
@@ -193,15 +194,15 @@ export async function dev(target, options) {
|
|
|
193
194
|
running = true;
|
|
194
195
|
try {
|
|
195
196
|
const result = await upload(current, dir, true);
|
|
196
|
-
if (result) console.log(
|
|
197
|
-
else console.error(
|
|
197
|
+
if (result) console.log(t("[{0}] yüklendi (sürüm no {1})", stamp(), result.package.revision));
|
|
198
|
+
else console.error(t("[{0}] yüklenmedi: hataları düzeltip kaydedin.", stamp()));
|
|
198
199
|
} catch (error) {
|
|
199
200
|
if (error instanceof ApiError) {
|
|
200
|
-
console.error(`[${stamp()}] ✖ ${error.message}`);
|
|
201
|
-
for (const problem of error.problems) console.error(` ✖ ${problem}`);
|
|
201
|
+
console.error(`[${stamp()}] ✖ ${tm(error.message)}`);
|
|
202
|
+
for (const problem of error.problems) console.error(` ✖ ${tm(problem)}`);
|
|
202
203
|
if (error.status === 401 || error.status === 403) process.exit(1);
|
|
203
204
|
} else {
|
|
204
|
-
console.error(`[${stamp()}] ✖ ${error.message}`);
|
|
205
|
+
console.error(`[${stamp()}] ✖ ${tm(error.message)}`);
|
|
205
206
|
}
|
|
206
207
|
} finally {
|
|
207
208
|
running = false;
|
|
@@ -227,18 +228,18 @@ export async function dev(target, options) {
|
|
|
227
228
|
const key = first.package.key;
|
|
228
229
|
try {
|
|
229
230
|
if (options.keep) {
|
|
230
|
-
console.log("\nCanlı geliştirme durdu. Tema son hâliyle mağaza kütüphanesinde duruyor.");
|
|
231
|
+
console.log(t("\nCanlı geliştirme durdu. Tema son hâliyle mağaza kütüphanesinde duruyor."));
|
|
231
232
|
} else if (before) {
|
|
232
233
|
await call(current, "PUT", "/themes", { body: before });
|
|
233
|
-
console.log("\nCanlı geliştirme durdu. Mağazadaki paket geliştirme öncesindeki sürümüne döndü.");
|
|
234
|
+
console.log(t("\nCanlı geliştirme durdu. Mağazadaki paket geliştirme öncesindeki sürümüne döndü."));
|
|
234
235
|
} else {
|
|
235
236
|
await call(current, "DELETE", `/themes/${encodeURIComponent(key)}`);
|
|
236
|
-
console.log("\nCanlı geliştirme durdu. Geliştirme kopyası mağazadan silindi; vitrin ve kütüphane eski hâlinde.");
|
|
237
|
+
console.log(t("\nCanlı geliştirme durdu. Geliştirme kopyası mağazadan silindi; vitrin ve kütüphane eski hâlinde."));
|
|
237
238
|
}
|
|
238
239
|
resolve(0);
|
|
239
240
|
} catch (error) {
|
|
240
|
-
console.error(
|
|
241
|
-
console.error(
|
|
241
|
+
console.error(t("\nCanlı geliştirme durdu ama mağaza eski hâline döndürülemedi: {0}", error.message));
|
|
242
|
+
console.error(t("Elle kaldırmak için: uvako theme delete {0} --yes", key));
|
|
242
243
|
resolve(1);
|
|
243
244
|
}
|
|
244
245
|
});
|
|
@@ -246,10 +247,10 @@ export async function dev(target, options) {
|
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
export async function pull(key, options) {
|
|
249
|
-
if (!key) throw new Error("Tema anahtarı gerekli: uvako theme pull <anahtar>");
|
|
250
|
+
if (!key) throw new Error(t("Tema anahtarı gerekli: uvako theme pull <anahtar>"));
|
|
250
251
|
const parent = path.resolve(typeof options.dir === "string" ? options.dir : ".");
|
|
251
252
|
const dir = path.join(parent, key);
|
|
252
|
-
if (existsSync(dir) && !options.force) throw new Error(
|
|
253
|
+
if (existsSync(dir) && !options.force) throw new Error(t("Klasör zaten var: {0}\nÜstüne yazmak için --force ekleyin.", dir));
|
|
253
254
|
try {
|
|
254
255
|
const current = session(typeof options.store === "string" ? options.store : "");
|
|
255
256
|
const archive = await call(current, "GET", `/themes/${encodeURIComponent(key)}/package`, { raw: true });
|
|
@@ -258,12 +259,12 @@ export async function pull(key, options) {
|
|
|
258
259
|
// Paket yalnızca kendi klasörünün içine açılır.
|
|
259
260
|
const rel = entry.name.split("/").slice(1).join("/");
|
|
260
261
|
const file = path.resolve(dir, rel);
|
|
261
|
-
if (!rel || !entry.name.startsWith(`${key}/`) || rel.split("/").includes("..") || !file.startsWith(dir + path.sep)) throw new Error(
|
|
262
|
+
if (!rel || !entry.name.startsWith(`${key}/`) || rel.split("/").includes("..") || !file.startsWith(dir + path.sep)) throw new Error(t("Paketteki yol güvenli değil: {0}", entry.name));
|
|
262
263
|
mkdirSync(path.dirname(file), { recursive: true });
|
|
263
264
|
writeFileSync(file, entry.data);
|
|
264
265
|
count++;
|
|
265
266
|
}
|
|
266
|
-
console.log(
|
|
267
|
+
console.log(t("İndirildi: {0} ({1} dosya)", path.relative(process.cwd(), dir) || ".", count));
|
|
267
268
|
return 0;
|
|
268
269
|
} catch (error) {
|
|
269
270
|
return explain(error);
|
|
@@ -271,12 +272,12 @@ export async function pull(key, options) {
|
|
|
271
272
|
}
|
|
272
273
|
|
|
273
274
|
export async function submit(key, options) {
|
|
274
|
-
if (!key) throw new Error("Tema anahtarı gerekli: uvako theme submit <anahtar>");
|
|
275
|
+
if (!key) throw new Error(t("Tema anahtarı gerekli: uvako theme submit <anahtar>"));
|
|
275
276
|
try {
|
|
276
277
|
const current = session(typeof options.store === "string" ? options.store : "");
|
|
277
278
|
const pkg = await call(current, "POST", `/themes/${encodeURIComponent(key)}/submit`);
|
|
278
|
-
console.log(
|
|
279
|
-
console.log("İnceleme sürerken yeni sürüm yüklerseniz onay o sürüm için yeniden istenir.");
|
|
279
|
+
console.log(t("İncelemeye gönderildi: {0} {1}. Uvako ekibi onayladığında themes.uvako.com'da yayınlanır.", pkg.name, pkg.version));
|
|
280
|
+
console.log(t("İnceleme sürerken yeni sürüm yüklerseniz onay o sürüm için yeniden istenir."));
|
|
280
281
|
return 0;
|
|
281
282
|
} catch (error) {
|
|
282
283
|
return explain(error);
|
|
@@ -284,8 +285,8 @@ export async function submit(key, options) {
|
|
|
284
285
|
}
|
|
285
286
|
|
|
286
287
|
export async function remove(key, options) {
|
|
287
|
-
if (!key) throw new Error("Tema anahtarı gerekli: uvako theme delete <anahtar>");
|
|
288
|
-
if (!options.yes) throw new Error(
|
|
288
|
+
if (!key) throw new Error(t("Tema anahtarı gerekli: uvako theme delete <anahtar>"));
|
|
289
|
+
if (!options.yes) throw new Error(t("\"{0}\" paketi ve mağazadaki kopyaları silinir; geri alınamaz. Onaylamak için --yes ekleyin.", key));
|
|
289
290
|
try {
|
|
290
291
|
const current = session(typeof options.store === "string" ? options.store : "");
|
|
291
292
|
await call(current, "DELETE", `/themes/${encodeURIComponent(key)}`);
|
package/src/zip.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import zlib from "node:zlib";
|
|
2
|
+
import { t } from "./i18n.js";
|
|
2
3
|
|
|
3
4
|
// Bağımlılıksız zip yazıcı ve okuyucu. Dosya tarihleri sabit tutulur; aynı içerik her zaman aynı
|
|
4
5
|
// özeti verir.
|
|
@@ -62,12 +63,12 @@ export function unzip(buffer) {
|
|
|
62
63
|
break;
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
|
-
if (end < 0) throw new Error("Geçerli bir zip dosyası değil.");
|
|
66
|
+
if (end < 0) throw new Error(t("Geçerli bir zip dosyası değil."));
|
|
66
67
|
const count = buffer.readUInt16LE(end + 10);
|
|
67
68
|
let cursor = buffer.readUInt32LE(end + 16);
|
|
68
69
|
const entries = [];
|
|
69
70
|
for (let i = 0; i < count; i++) {
|
|
70
|
-
if (buffer.readUInt32LE(cursor) !== 0x02014b50) throw new Error("Zip dizini okunamadı.");
|
|
71
|
+
if (buffer.readUInt32LE(cursor) !== 0x02014b50) throw new Error(t("Zip dizini okunamadı."));
|
|
71
72
|
const method = buffer.readUInt16LE(cursor + 10);
|
|
72
73
|
const compressedSize = buffer.readUInt32LE(cursor + 20);
|
|
73
74
|
const size = buffer.readUInt32LE(cursor + 24);
|
|
@@ -78,13 +79,13 @@ export function unzip(buffer) {
|
|
|
78
79
|
const name = buffer.subarray(cursor + 46, cursor + 46 + nameLength).toString("utf8");
|
|
79
80
|
cursor += 46 + nameLength + extraLength + commentLength;
|
|
80
81
|
if (name.endsWith("/")) continue;
|
|
81
|
-
if (size > maxEntryBytes) throw new Error(
|
|
82
|
+
if (size > maxEntryBytes) throw new Error(t("{0}: dosya çok büyük.", name));
|
|
82
83
|
const dataStart = localOffset + 30 + buffer.readUInt16LE(localOffset + 26) + buffer.readUInt16LE(localOffset + 28);
|
|
83
84
|
const raw = buffer.subarray(dataStart, dataStart + compressedSize);
|
|
84
85
|
let data;
|
|
85
86
|
if (method === 0) data = Buffer.from(raw);
|
|
86
87
|
else if (method === 8) data = zlib.inflateRawSync(raw, { maxOutputLength: maxEntryBytes });
|
|
87
|
-
else throw new Error(
|
|
88
|
+
else throw new Error(t("{0}: desteklenmeyen sıkıştırma yöntemi.", name));
|
|
88
89
|
entries.push({ name, data });
|
|
89
90
|
}
|
|
90
91
|
return entries;
|
|
@@ -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
|
+
}
|