tiendu 0.1.1
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/LICENSE +21 -0
- package/README.md +194 -0
- package/bin/tiendu.js +111 -0
- package/bin/tiendu.mjs +111 -0
- package/lib/api.mjs +316 -0
- package/lib/config.mjs +76 -0
- package/lib/dev.mjs +222 -0
- package/lib/init.mjs +92 -0
- package/lib/preview.mjs +295 -0
- package/lib/publish.mjs +34 -0
- package/lib/pull.mjs +41 -0
- package/lib/push.mjs +95 -0
- package/lib/zip.mjs +36 -0
- package/package.json +41 -0
package/lib/preview.mjs
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { loadConfigOrFail, writeConfig } from "./config.mjs";
|
|
2
|
+
import { apiFetch } from "./api.mjs";
|
|
3
|
+
|
|
4
|
+
const buildPreviewUrl = (apiBaseUrl, previewHostname) => {
|
|
5
|
+
const base = new URL(apiBaseUrl);
|
|
6
|
+
const hasExplicitPort = previewHostname.includes(":");
|
|
7
|
+
return `${base.protocol}//${previewHostname}${!hasExplicitPort && base.port ? `:${base.port}` : ""}/`;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} apiBaseUrl
|
|
12
|
+
* @param {string} apiKey
|
|
13
|
+
* @param {number} storeId
|
|
14
|
+
* @param {string} [name]
|
|
15
|
+
* @returns {Promise<{ ok: true, data: any } | { ok: false, error: string }>}
|
|
16
|
+
*/
|
|
17
|
+
export const createPreview = async (apiBaseUrl, apiKey, storeId, name) => {
|
|
18
|
+
try {
|
|
19
|
+
const response = await apiFetch(
|
|
20
|
+
apiBaseUrl,
|
|
21
|
+
apiKey,
|
|
22
|
+
`/api/v2/stores/${storeId}/theme-previews`,
|
|
23
|
+
{
|
|
24
|
+
method: "POST",
|
|
25
|
+
body: JSON.stringify({ name: name ?? "Dev" }),
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (response.status === 409) {
|
|
30
|
+
const body = await response.json().catch(() => ({}));
|
|
31
|
+
const message =
|
|
32
|
+
body?.error?.message ??
|
|
33
|
+
"Ya existe un preview para esta tienda. Eliminalo antes de crear uno nuevo.";
|
|
34
|
+
return { ok: false, error: message };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
const body = await response.text().catch(() => "");
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
error: `Error del servidor: ${response.status}${body ? ` — ${body}` : ""}`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const preview = await response.json();
|
|
46
|
+
return { ok: true, data: preview };
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
error: `No se pudo crear el preview: ${error.message}`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} apiBaseUrl
|
|
57
|
+
* @param {string} apiKey
|
|
58
|
+
* @param {number} storeId
|
|
59
|
+
* @returns {Promise<{ ok: true, data: any[] } | { ok: false, error: string }>}
|
|
60
|
+
*/
|
|
61
|
+
export const listPreviews = async (apiBaseUrl, apiKey, storeId) => {
|
|
62
|
+
try {
|
|
63
|
+
const response = await apiFetch(
|
|
64
|
+
apiBaseUrl,
|
|
65
|
+
apiKey,
|
|
66
|
+
`/api/v2/stores/${storeId}/theme-previews`,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
return { ok: false, error: `Error del servidor: ${response.status}` };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const body = await response.json();
|
|
74
|
+
const previews = body?.previews ?? [];
|
|
75
|
+
return { ok: true, data: previews };
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return { ok: false, error: `No se pudo listar previews: ${error.message}` };
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {string} apiBaseUrl
|
|
83
|
+
* @param {string} apiKey
|
|
84
|
+
* @param {number} storeId
|
|
85
|
+
* @param {string} previewKey
|
|
86
|
+
* @returns {Promise<{ ok: true } | { ok: false, error: string }>}
|
|
87
|
+
*/
|
|
88
|
+
export const deletePreview = async (
|
|
89
|
+
apiBaseUrl,
|
|
90
|
+
apiKey,
|
|
91
|
+
storeId,
|
|
92
|
+
previewKey,
|
|
93
|
+
) => {
|
|
94
|
+
try {
|
|
95
|
+
const response = await apiFetch(
|
|
96
|
+
apiBaseUrl,
|
|
97
|
+
apiKey,
|
|
98
|
+
`/api/v2/stores/${storeId}/theme-previews/${previewKey}`,
|
|
99
|
+
{
|
|
100
|
+
method: "DELETE",
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
const body = await response.text().catch(() => "");
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
error: `Error del servidor: ${response.status}${body ? ` — ${body}` : ""}`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { ok: true };
|
|
113
|
+
} catch (error) {
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
error: `No se pudo eliminar el preview: ${error.message}`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @param {string} apiBaseUrl
|
|
123
|
+
* @param {string} apiKey
|
|
124
|
+
* @param {number} storeId
|
|
125
|
+
* @param {string} previewKey
|
|
126
|
+
* @returns {Promise<{ ok: true } | { ok: false, error: string }>}
|
|
127
|
+
*/
|
|
128
|
+
export const publishPreview = async (
|
|
129
|
+
apiBaseUrl,
|
|
130
|
+
apiKey,
|
|
131
|
+
storeId,
|
|
132
|
+
previewKey,
|
|
133
|
+
) => {
|
|
134
|
+
try {
|
|
135
|
+
const response = await apiFetch(
|
|
136
|
+
apiBaseUrl,
|
|
137
|
+
apiKey,
|
|
138
|
+
`/api/v2/stores/${storeId}/theme-previews/${previewKey}/publish`,
|
|
139
|
+
{
|
|
140
|
+
method: "POST",
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
const body = await response.text().catch(() => "");
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
error: `Error del servidor: ${response.status}${body ? ` — ${body}` : ""}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { ok: true };
|
|
153
|
+
} catch (error) {
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
error: `No se pudo publicar el preview: ${error.message}`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// CLI commands
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
export const previewCreate = async (name) => {
|
|
166
|
+
const { config, credentials } = await loadConfigOrFail();
|
|
167
|
+
|
|
168
|
+
console.log("");
|
|
169
|
+
console.log("Creando preview...");
|
|
170
|
+
|
|
171
|
+
const result = await createPreview(
|
|
172
|
+
config.apiBaseUrl,
|
|
173
|
+
credentials.apiKey,
|
|
174
|
+
config.storeId,
|
|
175
|
+
name,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (!result.ok) {
|
|
179
|
+
console.error(`Error: ${result.error}`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const preview = result.data;
|
|
184
|
+
console.log(`Preview creado: ${preview.previewKey}`);
|
|
185
|
+
console.log(
|
|
186
|
+
`URL: ${buildPreviewUrl(config.apiBaseUrl, preview.previewHostname)}`,
|
|
187
|
+
);
|
|
188
|
+
console.log("");
|
|
189
|
+
|
|
190
|
+
// Save preview key to config
|
|
191
|
+
await writeConfig({ ...config, previewKey: preview.previewKey });
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export const previewList = async () => {
|
|
195
|
+
const { config, credentials } = await loadConfigOrFail();
|
|
196
|
+
|
|
197
|
+
const result = await listPreviews(
|
|
198
|
+
config.apiBaseUrl,
|
|
199
|
+
credentials.apiKey,
|
|
200
|
+
config.storeId,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
if (!result.ok) {
|
|
204
|
+
console.error(`Error: ${result.error}`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log("");
|
|
209
|
+
if (result.data.length === 0) {
|
|
210
|
+
console.log("No hay previews para esta tienda.");
|
|
211
|
+
} else {
|
|
212
|
+
console.log("Previews:");
|
|
213
|
+
for (const preview of result.data) {
|
|
214
|
+
const active =
|
|
215
|
+
config.previewKey === preview.previewKey ? " (activo)" : "";
|
|
216
|
+
console.log(
|
|
217
|
+
` ${preview.previewKey} ${preview.name} ${buildPreviewUrl(config.apiBaseUrl, preview.previewHostname)}${active}`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
console.log("");
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export const previewDelete = async () => {
|
|
225
|
+
const { config, credentials } = await loadConfigOrFail();
|
|
226
|
+
|
|
227
|
+
if (!config.previewKey) {
|
|
228
|
+
console.error("No hay preview activo. Creá uno con: tiendu preview create");
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log("");
|
|
233
|
+
console.log(`Eliminando preview ${config.previewKey}...`);
|
|
234
|
+
|
|
235
|
+
const result = await deletePreview(
|
|
236
|
+
config.apiBaseUrl,
|
|
237
|
+
credentials.apiKey,
|
|
238
|
+
config.storeId,
|
|
239
|
+
config.previewKey,
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
if (!result.ok) {
|
|
243
|
+
console.error(`Error: ${result.error}`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
console.log("Preview eliminado.");
|
|
248
|
+
console.log("");
|
|
249
|
+
|
|
250
|
+
// Remove preview key from config
|
|
251
|
+
const { previewKey, ...rest } = config;
|
|
252
|
+
await writeConfig(rest);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
export const previewOpen = async () => {
|
|
256
|
+
const { config } = await loadConfigOrFail();
|
|
257
|
+
|
|
258
|
+
if (!config.previewKey) {
|
|
259
|
+
console.error("No hay preview activo. Creá uno con: tiendu preview create");
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Find the preview to get its hostname
|
|
264
|
+
const { credentials } = await loadConfigOrFail();
|
|
265
|
+
const result = await listPreviews(
|
|
266
|
+
config.apiBaseUrl,
|
|
267
|
+
credentials.apiKey,
|
|
268
|
+
config.storeId,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
if (!result.ok) {
|
|
272
|
+
console.error(`Error: ${result.error}`);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const preview = result.data.find((p) => p.previewKey === config.previewKey);
|
|
277
|
+
if (!preview) {
|
|
278
|
+
console.error("El preview activo ya no existe en el servidor.");
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const url = buildPreviewUrl(config.apiBaseUrl, preview.previewHostname);
|
|
283
|
+
console.log(`Abriendo ${url}...`);
|
|
284
|
+
|
|
285
|
+
// Open URL in browser
|
|
286
|
+
const { exec } = await import("node:child_process");
|
|
287
|
+
const platform = process.platform;
|
|
288
|
+
const cmd =
|
|
289
|
+
platform === "darwin"
|
|
290
|
+
? "open"
|
|
291
|
+
: platform === "win32"
|
|
292
|
+
? "start"
|
|
293
|
+
: "xdg-open";
|
|
294
|
+
exec(`${cmd} ${url}`);
|
|
295
|
+
};
|
package/lib/publish.mjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { loadConfigOrFail, writeConfig } from "./config.mjs";
|
|
2
|
+
import { publishPreview } from "./preview.mjs";
|
|
3
|
+
|
|
4
|
+
export const publish = async () => {
|
|
5
|
+
const { config, credentials } = await loadConfigOrFail();
|
|
6
|
+
|
|
7
|
+
if (!config.previewKey) {
|
|
8
|
+
console.error("No hay preview activo. Creá uno con: tiendu preview create");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
console.log("");
|
|
13
|
+
console.log(`Publicando preview ${config.previewKey} al storefront live...`);
|
|
14
|
+
|
|
15
|
+
const result = await publishPreview(
|
|
16
|
+
config.apiBaseUrl,
|
|
17
|
+
credentials.apiKey,
|
|
18
|
+
config.storeId,
|
|
19
|
+
config.previewKey,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (!result.ok) {
|
|
23
|
+
console.error(`Error: ${result.error}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log("Preview publicado. El storefront live fue actualizado.");
|
|
28
|
+
console.log("Todos los previews de esta tienda fueron eliminados.");
|
|
29
|
+
console.log("");
|
|
30
|
+
|
|
31
|
+
// Remove preview key from config
|
|
32
|
+
const { previewKey, ...rest } = config;
|
|
33
|
+
await writeConfig(rest);
|
|
34
|
+
};
|
package/lib/pull.mjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { loadConfigOrFail } from "./config.mjs";
|
|
2
|
+
import { downloadStorefrontArchive } from "./api.mjs";
|
|
3
|
+
import { extractZip } from "./zip.mjs";
|
|
4
|
+
|
|
5
|
+
export const pull = async () => {
|
|
6
|
+
const { config, credentials } = await loadConfigOrFail();
|
|
7
|
+
|
|
8
|
+
console.log("");
|
|
9
|
+
console.log(`Descargando tema de tienda #${config.storeId}...`);
|
|
10
|
+
|
|
11
|
+
const result = await downloadStorefrontArchive(
|
|
12
|
+
config.apiBaseUrl,
|
|
13
|
+
credentials.apiKey,
|
|
14
|
+
config.storeId,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
if (!result.ok) {
|
|
18
|
+
console.error(`Error: ${result.error}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log(`Archivo ZIP recibido (${formatBytes(result.data.length)})`);
|
|
23
|
+
console.log("Extrayendo archivos...");
|
|
24
|
+
|
|
25
|
+
const outputDir = process.cwd();
|
|
26
|
+
const extractedFiles = await extractZip(result.data, outputDir);
|
|
27
|
+
|
|
28
|
+
console.log("");
|
|
29
|
+
console.log(`${extractedFiles.length} archivos extraídos:`);
|
|
30
|
+
for (const file of extractedFiles) {
|
|
31
|
+
console.log(` ${file}`);
|
|
32
|
+
}
|
|
33
|
+
console.log("");
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** @param {number} bytes */
|
|
37
|
+
const formatBytes = (bytes) => {
|
|
38
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
39
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
40
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
41
|
+
};
|
package/lib/push.mjs
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { zipSync } from "fflate";
|
|
4
|
+
import { loadConfigOrFail } from "./config.mjs";
|
|
5
|
+
import { uploadPreviewZip } from "./api.mjs";
|
|
6
|
+
|
|
7
|
+
const isDotfile = (name) => name.startsWith(".");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Recursively list all files, skipping dotfiles/dotdirs.
|
|
11
|
+
* @param {string} rootDir
|
|
12
|
+
* @param {string} currentDir
|
|
13
|
+
* @returns {Promise<string[]>}
|
|
14
|
+
*/
|
|
15
|
+
const listAllFiles = async (rootDir, currentDir) => {
|
|
16
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
17
|
+
const files = [];
|
|
18
|
+
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (isDotfile(entry.name)) continue;
|
|
21
|
+
|
|
22
|
+
const absolutePath = path.join(currentDir, entry.name);
|
|
23
|
+
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
const nested = await listAllFiles(rootDir, absolutePath);
|
|
26
|
+
files.push(...nested);
|
|
27
|
+
} else if (entry.isFile()) {
|
|
28
|
+
files.push(absolutePath);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return files;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create a zip buffer from the current directory, skipping dotfiles.
|
|
37
|
+
* @param {string} rootDir
|
|
38
|
+
* @returns {Promise<Buffer>}
|
|
39
|
+
*/
|
|
40
|
+
const createZipFromDirectory = async (rootDir) => {
|
|
41
|
+
const absoluteFiles = await listAllFiles(rootDir, rootDir);
|
|
42
|
+
/** @type {Record<string, Uint8Array>} */
|
|
43
|
+
const entries = {};
|
|
44
|
+
|
|
45
|
+
for (const absoluteFilePath of absoluteFiles) {
|
|
46
|
+
const relativePath = path
|
|
47
|
+
.relative(rootDir, absoluteFilePath)
|
|
48
|
+
.split(path.sep)
|
|
49
|
+
.join("/");
|
|
50
|
+
const fileBuffer = await readFile(absoluteFilePath);
|
|
51
|
+
entries[relativePath] = new Uint8Array(fileBuffer);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return Buffer.from(zipSync(entries, { level: 6 }));
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const push = async () => {
|
|
58
|
+
const { config, credentials } = await loadConfigOrFail();
|
|
59
|
+
|
|
60
|
+
if (!config.previewKey) {
|
|
61
|
+
console.error("No hay preview activo. Creá uno con: tiendu preview create");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const rootDir = process.cwd();
|
|
66
|
+
|
|
67
|
+
console.log("");
|
|
68
|
+
console.log(`Subiendo archivos al preview ${config.previewKey}...`);
|
|
69
|
+
|
|
70
|
+
const zipBuffer = await createZipFromDirectory(rootDir);
|
|
71
|
+
console.log(`ZIP creado (${formatBytes(zipBuffer.length)})`);
|
|
72
|
+
|
|
73
|
+
const result = await uploadPreviewZip(
|
|
74
|
+
config.apiBaseUrl,
|
|
75
|
+
credentials.apiKey,
|
|
76
|
+
config.storeId,
|
|
77
|
+
config.previewKey,
|
|
78
|
+
zipBuffer,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!result.ok) {
|
|
82
|
+
console.error(`Error: ${result.error}`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log("Archivos subidos al preview.");
|
|
87
|
+
console.log("");
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** @param {number} bytes */
|
|
91
|
+
const formatBytes = (bytes) => {
|
|
92
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
93
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
94
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
95
|
+
};
|
package/lib/zip.mjs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { unzipSync } from "fflate";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extract a zip buffer into the given output directory.
|
|
7
|
+
* Returns an array of extracted file paths (relative).
|
|
8
|
+
*
|
|
9
|
+
* @param {Buffer} zipBuffer
|
|
10
|
+
* @param {string} outputDir
|
|
11
|
+
* @returns {Promise<string[]>}
|
|
12
|
+
*/
|
|
13
|
+
export const extractZip = async (zipBuffer, outputDir) => {
|
|
14
|
+
const archiveEntries = unzipSync(new Uint8Array(zipBuffer));
|
|
15
|
+
const extractedFiles = [];
|
|
16
|
+
const resolvedOutputDir = path.resolve(outputDir);
|
|
17
|
+
|
|
18
|
+
for (const [relativePath, fileContent] of Object.entries(archiveEntries)) {
|
|
19
|
+
if (!relativePath || relativePath.endsWith("/")) continue;
|
|
20
|
+
|
|
21
|
+
const outputPath = path.join(outputDir, relativePath);
|
|
22
|
+
const resolvedPath = path.resolve(outputPath);
|
|
23
|
+
if (
|
|
24
|
+
!resolvedPath.startsWith(`${resolvedOutputDir}${path.sep}`) &&
|
|
25
|
+
resolvedPath !== resolvedOutputDir
|
|
26
|
+
) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
31
|
+
await writeFile(outputPath, fileContent);
|
|
32
|
+
extractedFiles.push(relativePath);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return extractedFiles.sort((left, right) => left.localeCompare(right));
|
|
36
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tiendu",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "CLI para desarrollar y publicar temas en Tiendu",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"tiendu": "./bin/tiendu.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"lib",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"dev": "node bin/tiendu.mjs"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=20"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"tiendu",
|
|
23
|
+
"ecommerce",
|
|
24
|
+
"theme",
|
|
25
|
+
"storefront",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"author": "Tiendu",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/squiel91/tiendu-cli"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://tiendu.uy",
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"fflate": "^0.8.2"
|
|
40
|
+
}
|
|
41
|
+
}
|