tiendu 0.1.2 → 0.1.3
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/lib/api.mjs +10 -11
- package/lib/init.mjs +116 -77
- package/package.json +2 -1
package/lib/api.mjs
CHANGED
|
@@ -43,21 +43,16 @@ const checkAuthErrors = (response) => {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
46
|
+
* Fetch all stores accessible by the current API key.
|
|
47
|
+
* Also serves as API key validation — 401/403 means invalid key.
|
|
47
48
|
*
|
|
48
49
|
* @param {string} apiBaseUrl
|
|
49
50
|
* @param {string} apiKey
|
|
50
|
-
* @
|
|
51
|
-
* @returns {Promise<{ ok: true, data: { name: string } } | { ok: false, error: string }>}
|
|
51
|
+
* @returns {Promise<{ ok: true, data: Array<{ id: number, name: string, hostname: string }> } | { ok: false, error: string }>}
|
|
52
52
|
*/
|
|
53
|
-
export const
|
|
53
|
+
export const fetchUserStores = async (apiBaseUrl, apiKey) => {
|
|
54
54
|
try {
|
|
55
|
-
const response = await apiFetch(
|
|
56
|
-
apiBaseUrl,
|
|
57
|
-
apiKey,
|
|
58
|
-
`/api/admin/stores/${storeId}/code/download`,
|
|
59
|
-
{ method: "HEAD" },
|
|
60
|
-
);
|
|
55
|
+
const response = await apiFetch(apiBaseUrl, apiKey, `/api/v2/stores`);
|
|
61
56
|
|
|
62
57
|
const authError = checkAuthErrors(response);
|
|
63
58
|
if (authError) return authError;
|
|
@@ -69,7 +64,11 @@ export const fetchStoreInfo = async (apiBaseUrl, apiKey, storeId) => {
|
|
|
69
64
|
};
|
|
70
65
|
}
|
|
71
66
|
|
|
72
|
-
|
|
67
|
+
const stores = await response.json();
|
|
68
|
+
return {
|
|
69
|
+
ok: true,
|
|
70
|
+
data: Array.isArray(stores) ? stores : [],
|
|
71
|
+
};
|
|
73
72
|
} catch (error) {
|
|
74
73
|
return {
|
|
75
74
|
ok: false,
|
package/lib/init.mjs
CHANGED
|
@@ -1,97 +1,136 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { stdin, stdout } from "node:process";
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
3
2
|
import {
|
|
4
3
|
readConfig,
|
|
5
4
|
readCredentials,
|
|
6
5
|
writeConfig,
|
|
7
6
|
writeCredentials,
|
|
8
7
|
} from "./config.mjs";
|
|
9
|
-
import {
|
|
8
|
+
import { fetchUserStores } from "./api.mjs";
|
|
9
|
+
|
|
10
|
+
/** @param {string} key */
|
|
11
|
+
const maskApiKey = (key) => {
|
|
12
|
+
if (key.length <= 8) return "****";
|
|
13
|
+
return key.slice(0, 4) + "..." + key.slice(-4);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** @param {string} url */
|
|
17
|
+
const normalizeBaseUrl = (url) => {
|
|
18
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
19
|
+
};
|
|
10
20
|
|
|
11
21
|
export const init = async () => {
|
|
12
22
|
const existingConfig = await readConfig();
|
|
13
23
|
const existingCredentials = await readCredentials();
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
console.log("");
|
|
19
|
-
console.log("Tiendu CLI — Inicialización");
|
|
20
|
-
console.log("===========================");
|
|
21
|
-
console.log("");
|
|
22
|
-
|
|
23
|
-
// API key
|
|
24
|
-
const defaultApiKey = existingCredentials?.apiKey ?? "";
|
|
25
|
-
const apiKeyPrompt = defaultApiKey
|
|
26
|
-
? `API Key [${maskApiKey(defaultApiKey)}]: `
|
|
27
|
-
: "API Key: ";
|
|
28
|
-
const apiKeyInput = (await rl.question(apiKeyPrompt)).trim();
|
|
29
|
-
const apiKey = apiKeyInput || defaultApiKey;
|
|
30
|
-
|
|
31
|
-
if (!apiKey) {
|
|
32
|
-
console.error("La API Key es requerida.");
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
25
|
+
p.intro("Tiendu CLI — Inicialización");
|
|
35
26
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
27
|
+
// ─── API Key ──────────────────────────────────────────────────────────────
|
|
28
|
+
const apiKeyDefault = existingCredentials?.apiKey ?? "";
|
|
29
|
+
|
|
30
|
+
const apiKeyInput = await p.password({
|
|
31
|
+
message: "API Key",
|
|
32
|
+
mask: "*",
|
|
33
|
+
validate: (value) => {
|
|
34
|
+
const resolved = value.trim() || apiKeyDefault;
|
|
35
|
+
if (!resolved) return "La API Key es requerida.";
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (p.isCancel(apiKeyInput)) {
|
|
40
|
+
p.cancel("Inicialización cancelada.");
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const apiKey = apiKeyInput.trim() || apiKeyDefault;
|
|
45
|
+
|
|
46
|
+
// ─── API Base URL ─────────────────────────────────────────────────────────
|
|
47
|
+
const baseUrlDefault = existingConfig?.apiBaseUrl ?? "https://tiendu.uy";
|
|
48
|
+
|
|
49
|
+
const baseUrlInput = await p.text({
|
|
50
|
+
message: "URL base de la API",
|
|
51
|
+
placeholder: baseUrlDefault,
|
|
52
|
+
defaultValue: baseUrlDefault,
|
|
53
|
+
validate: (value) => {
|
|
54
|
+
const resolved = value.trim() || baseUrlDefault;
|
|
55
|
+
try {
|
|
56
|
+
new URL(resolved);
|
|
57
|
+
} catch {
|
|
58
|
+
return "URL inválida.";
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (p.isCancel(baseUrlInput)) {
|
|
64
|
+
p.cancel("Inicialización cancelada.");
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
56
67
|
|
|
57
|
-
|
|
58
|
-
console.log("");
|
|
59
|
-
console.log("Verificando credenciales...");
|
|
68
|
+
const apiBaseUrl = normalizeBaseUrl(baseUrlInput.trim() || baseUrlDefault);
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
// ─── Fetch stores (validates API key implicitly) ───────────────────────────
|
|
71
|
+
const spinner = p.spinner();
|
|
72
|
+
spinner.start("Verificando credenciales y obteniendo tiendas...");
|
|
73
|
+
|
|
74
|
+
const storesResult = await fetchUserStores(apiBaseUrl, apiKey);
|
|
75
|
+
|
|
76
|
+
if (!storesResult.ok) {
|
|
77
|
+
spinner.stop("Error al verificar credenciales.", 1);
|
|
78
|
+
p.cancel(storesResult.error);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const stores = storesResult.data;
|
|
83
|
+
|
|
84
|
+
if (stores.length === 0) {
|
|
85
|
+
spinner.stop("No se encontraron tiendas.", 1);
|
|
86
|
+
p.cancel("Tu API Key no tiene acceso a ninguna tienda.");
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
spinner.stop(
|
|
91
|
+
`${stores.length} tienda${stores.length === 1 ? "" : "s"} encontrada${stores.length === 1 ? "" : "s"}.`,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// ─── Select store ─────────────────────────────────────────────────────────
|
|
95
|
+
let storeId;
|
|
96
|
+
|
|
97
|
+
if (stores.length === 1) {
|
|
98
|
+
// Auto-select if only one store
|
|
99
|
+
storeId = stores[0].id;
|
|
100
|
+
p.note(`${stores[0].name} (ID: ${storeId})`, "Tienda seleccionada");
|
|
101
|
+
} else {
|
|
102
|
+
const selectedId = await p.select({
|
|
103
|
+
message: "Seleccioná una tienda",
|
|
104
|
+
options: stores.map((store) => ({
|
|
105
|
+
value: store.id,
|
|
106
|
+
label: store.name,
|
|
107
|
+
hint: `ID: ${store.id}`,
|
|
108
|
+
})),
|
|
109
|
+
initialValue: existingConfig?.storeId ?? stores[0].id,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (p.isCancel(selectedId)) {
|
|
113
|
+
p.cancel("Inicialización cancelada.");
|
|
114
|
+
process.exit(0);
|
|
65
115
|
}
|
|
66
116
|
|
|
67
|
-
|
|
68
|
-
console.log("");
|
|
69
|
-
|
|
70
|
-
// Save
|
|
71
|
-
await writeConfig({ storeId, apiBaseUrl });
|
|
72
|
-
await writeCredentials({ apiKey });
|
|
73
|
-
|
|
74
|
-
console.log("Configuración guardada en .cli/");
|
|
75
|
-
console.log("");
|
|
76
|
-
console.log('Próximo paso: ejecutá "tiendu pull" para descargar el tema.');
|
|
77
|
-
console.log("");
|
|
78
|
-
console.log("Nota: habilitá el modo dev en la plataforma Tiendu");
|
|
79
|
-
console.log(
|
|
80
|
-
"(Ajustes > General) para que los datos del preview se muestren correctamente.",
|
|
81
|
-
);
|
|
82
|
-
console.log("");
|
|
83
|
-
} finally {
|
|
84
|
-
rl.close();
|
|
117
|
+
storeId = selectedId;
|
|
85
118
|
}
|
|
86
|
-
};
|
|
87
119
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return key.slice(0, 4) + "..." + key.slice(-4);
|
|
92
|
-
};
|
|
120
|
+
// ─── Save ─────────────────────────────────────────────────────────────────
|
|
121
|
+
await writeConfig({ storeId, apiBaseUrl });
|
|
122
|
+
await writeCredentials({ apiKey });
|
|
93
123
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
124
|
+
p.note(
|
|
125
|
+
[
|
|
126
|
+
'Ejecutá "tiendu pull" para descargar el tema.',
|
|
127
|
+
"",
|
|
128
|
+
"Nota: habilitá el modo dev en la plataforma Tiendu",
|
|
129
|
+
"(Ajustes > General) para que los datos del preview",
|
|
130
|
+
"se muestren correctamente.",
|
|
131
|
+
].join("\n"),
|
|
132
|
+
"Próximos pasos",
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
p.outro("Configuración guardada en .cli/");
|
|
97
136
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tiendu",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "CLI para desarrollar y publicar temas en Tiendu",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
+
"@clack/prompts": "^1.1.0",
|
|
39
40
|
"fflate": "^0.8.2"
|
|
40
41
|
}
|
|
41
42
|
}
|