tiendu 0.3.1 → 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 +64 -14
- package/bin/tiendu.js +6 -4
- package/bin/tiendu.mjs +1 -134
- package/lib/api.mjs +18 -50
- package/lib/archive.mjs +30 -0
- package/lib/assets.mjs +245 -0
- package/lib/build.mjs +299 -41
- package/lib/dev.mjs +239 -136
- package/lib/fs-utils.mjs +35 -0
- package/lib/local-preview.mjs +350 -0
- package/lib/postcss.mjs +166 -0
- package/lib/preview.mjs +19 -9
- package/lib/publish.mjs +12 -2
- package/lib/push.mjs +51 -52
- package/lib/retry.mjs +69 -0
- package/package.json +2 -2
package/lib/push.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
1
|
import * as p from "@clack/prompts";
|
|
4
|
-
import { zipSync } from "fflate";
|
|
5
2
|
import { loadConfigOrFail, isBuiltTheme, getDistDir } from "./config.mjs";
|
|
6
3
|
import { uploadPreviewZip } from "./api.mjs";
|
|
4
|
+
import { createZipFromDirectory } from "./archive.mjs";
|
|
5
|
+
import { build } from "./build.mjs";
|
|
6
|
+
import { retryAsync } from "./retry.mjs";
|
|
7
7
|
|
|
8
8
|
/** @param {number} bytes */
|
|
9
9
|
const formatBytes = (bytes) => {
|
|
@@ -12,46 +12,40 @@ const formatBytes = (bytes) => {
|
|
|
12
12
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
export const pushPreparedDirectoryToPreview = async ({
|
|
16
|
+
apiBaseUrl,
|
|
17
|
+
apiKey,
|
|
18
|
+
storeId,
|
|
19
|
+
previewKey,
|
|
20
|
+
rootDir,
|
|
21
|
+
spinner,
|
|
22
|
+
packMessage = "Packing files...",
|
|
23
|
+
uploadMessage,
|
|
24
|
+
retryMessage,
|
|
25
|
+
}) => {
|
|
26
|
+
spinner.message(packMessage);
|
|
16
27
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* @returns {Promise<string[]>}
|
|
22
|
-
*/
|
|
23
|
-
const listAllFiles = async (rootDir, currentDir) => {
|
|
24
|
-
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
25
|
-
const files = [];
|
|
26
|
-
for (const entry of entries) {
|
|
27
|
-
if (isDotfile(entry.name)) continue;
|
|
28
|
-
const abs = path.join(currentDir, entry.name);
|
|
29
|
-
if (entry.isDirectory()) {
|
|
30
|
-
files.push(...(await listAllFiles(rootDir, abs)));
|
|
31
|
-
} else if (entry.isFile()) {
|
|
32
|
-
files.push(abs);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return files;
|
|
36
|
-
};
|
|
28
|
+
const zipBuffer = await createZipFromDirectory(rootDir);
|
|
29
|
+
spinner.message(
|
|
30
|
+
uploadMessage ?? `Uploading to preview ${previewKey} (${formatBytes(zipBuffer.length)})...`,
|
|
31
|
+
);
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return Buffer.from(zipSync(entries, { level: 6 }));
|
|
33
|
+
return retryAsync(
|
|
34
|
+
() => uploadPreviewZip(apiBaseUrl, apiKey, storeId, previewKey, zipBuffer),
|
|
35
|
+
{
|
|
36
|
+
attempts: 3,
|
|
37
|
+
shouldRetry: (uploadResult) => !uploadResult.ok && Boolean(uploadResult.retriable),
|
|
38
|
+
onRetry: async (uploadResult, nextAttempt) => {
|
|
39
|
+
spinner.message(
|
|
40
|
+
retryMessage?.(uploadResult, nextAttempt) ??
|
|
41
|
+
`Upload failed. Retrying ${nextAttempt}/3... ${uploadResult.error}`,
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
);
|
|
52
46
|
};
|
|
53
47
|
|
|
54
|
-
export const push = async () => {
|
|
48
|
+
export const push = async ({ skipBuild = false } = {}) => {
|
|
55
49
|
const { config, credentials } = await loadConfigOrFail();
|
|
56
50
|
|
|
57
51
|
if (!config.previewKey) {
|
|
@@ -59,22 +53,27 @@ export const push = async () => {
|
|
|
59
53
|
process.exit(1);
|
|
60
54
|
}
|
|
61
55
|
|
|
62
|
-
const
|
|
56
|
+
const builtTheme = await isBuiltTheme();
|
|
57
|
+
|
|
58
|
+
if (builtTheme && !skipBuild) {
|
|
59
|
+
const result = await build();
|
|
60
|
+
if (!result.ok) {
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const rootDir = builtTheme ? getDistDir() : process.cwd();
|
|
63
66
|
const spinner = p.spinner();
|
|
64
67
|
spinner.start("Packing files...");
|
|
65
68
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
config.storeId,
|
|
75
|
-
config.previewKey,
|
|
76
|
-
zipBuffer,
|
|
77
|
-
);
|
|
69
|
+
const result = await pushPreparedDirectoryToPreview({
|
|
70
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
71
|
+
apiKey: credentials.apiKey,
|
|
72
|
+
storeId: config.storeId,
|
|
73
|
+
previewKey: config.previewKey,
|
|
74
|
+
rootDir,
|
|
75
|
+
spinner,
|
|
76
|
+
});
|
|
78
77
|
|
|
79
78
|
if (!result.ok) {
|
|
80
79
|
spinner.stop("Upload failed.", 1);
|
package/lib/retry.mjs
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @template T
|
|
5
|
+
* @param {(attempt: number) => Promise<T>} operation
|
|
6
|
+
* @param {{
|
|
7
|
+
* attempts?: number,
|
|
8
|
+
* baseDelayMs?: number,
|
|
9
|
+
* maxDelayMs?: number,
|
|
10
|
+
* shouldRetry?: (result: T, attempt: number) => boolean,
|
|
11
|
+
* shouldRetryError?: (error: unknown, attempt: number) => boolean,
|
|
12
|
+
* onRetry?: (result: T, nextAttempt: number, delayMs: number) => void | Promise<void>,
|
|
13
|
+
* onRetryError?: (error: unknown, nextAttempt: number, delayMs: number) => void | Promise<void>,
|
|
14
|
+
* }} [options]
|
|
15
|
+
* @returns {Promise<T>}
|
|
16
|
+
*/
|
|
17
|
+
export const retryAsync = async (operation, options = {}) => {
|
|
18
|
+
const {
|
|
19
|
+
attempts = 3,
|
|
20
|
+
baseDelayMs = 300,
|
|
21
|
+
maxDelayMs = 2000,
|
|
22
|
+
shouldRetry = () => false,
|
|
23
|
+
shouldRetryError = () => true,
|
|
24
|
+
onRetry,
|
|
25
|
+
onRetryError,
|
|
26
|
+
} = options;
|
|
27
|
+
|
|
28
|
+
let lastResult;
|
|
29
|
+
let lastError;
|
|
30
|
+
|
|
31
|
+
const getDelayMs = (attempt) =>
|
|
32
|
+
Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1)) +
|
|
33
|
+
Math.floor(Math.random() * 100);
|
|
34
|
+
|
|
35
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
36
|
+
try {
|
|
37
|
+
const result = await operation(attempt);
|
|
38
|
+
lastResult = result;
|
|
39
|
+
|
|
40
|
+
if (!shouldRetry(result, attempt) || attempt === attempts) {
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const delayMs = getDelayMs(attempt);
|
|
45
|
+
|
|
46
|
+
if (onRetry) {
|
|
47
|
+
await onRetry(result, attempt + 1, delayMs);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await sleep(delayMs);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
lastError = error;
|
|
53
|
+
|
|
54
|
+
if (!shouldRetryError(error, attempt) || attempt === attempts) {
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const delayMs = getDelayMs(attempt);
|
|
59
|
+
if (onRetryError) {
|
|
60
|
+
await onRetryError(error, attempt + 1, delayMs);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await sleep(delayMs);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (lastError) throw lastError;
|
|
68
|
+
return lastResult;
|
|
69
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tiendu",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "CLI para desarrollar y publicar temas en Tiendu",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
|
-
"dev": "node bin/tiendu.
|
|
16
|
+
"dev": "node bin/tiendu.js"
|
|
17
17
|
},
|
|
18
18
|
"engines": {
|
|
19
19
|
"node": ">=20"
|