tiendu 0.7.0 → 0.8.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/README.md +11 -0
- package/bin/tiendu.js +6 -4
- package/lib/api.mjs +18 -16
- package/lib/archive.mjs +2 -1
- package/lib/build.mjs +20 -5
- package/lib/dev.mjs +5 -3
- package/lib/publish.mjs +2 -2
- package/lib/push.mjs +11 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,8 +138,11 @@ Builds or stages the current theme into its deployable output directory (`dist/`
|
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
140
|
tiendu build
|
|
141
|
+
tiendu build --skip-instances
|
|
141
142
|
```
|
|
142
143
|
|
|
144
|
+
- Use `--skip-instances` to omit template JSON, section group JSON, and `config/settings_data.json` from `dist/`. This is useful when you want to preserve the existing page/section instances on the preview.
|
|
145
|
+
|
|
143
146
|
The build:
|
|
144
147
|
|
|
145
148
|
1. Copies theme files from `src/layout/`, `src/templates/`, `src/sections/`, `src/blocks/`, `src/snippets/`, and `src/config/` to `dist/`
|
|
@@ -167,8 +170,10 @@ The main development command.
|
|
|
167
170
|
|
|
168
171
|
```bash
|
|
169
172
|
tiendu dev
|
|
173
|
+
tiendu dev --skip-instances
|
|
170
174
|
```
|
|
171
175
|
|
|
176
|
+
- Use `--skip-instances` to sync everything except template JSON, section group JSON, and `config/settings_data.json`. Existing instances on the preview are preserved.
|
|
172
177
|
- Prints the preview URL on start
|
|
173
178
|
- Re-syncs the full local theme to the preview on startup
|
|
174
179
|
- Syncs file creates, edits and deletes
|
|
@@ -190,8 +195,11 @@ Zips and uploads `dist/` to the active preview, replacing its content entirely.
|
|
|
190
195
|
tiendu push
|
|
191
196
|
tiendu push --skip-build
|
|
192
197
|
tiendu push --skip-build --non-interactive
|
|
198
|
+
tiendu push --skip-instances
|
|
193
199
|
```
|
|
194
200
|
|
|
201
|
+
- Use `--skip-instances` to upload everything except template JSON, section group JSON, and `config/settings_data.json`. Existing instances on the preview are preserved.
|
|
202
|
+
|
|
195
203
|
---
|
|
196
204
|
|
|
197
205
|
### `tiendu publish`
|
|
@@ -205,8 +213,11 @@ Publishes the active preview to the live storefront. Visitors will see the new t
|
|
|
205
213
|
tiendu publish
|
|
206
214
|
tiendu publish --skip-build
|
|
207
215
|
tiendu publish --skip-build --non-interactive
|
|
216
|
+
tiendu publish --skip-instances
|
|
208
217
|
```
|
|
209
218
|
|
|
219
|
+
- Use `--skip-instances` to publish everything except template JSON, section group JSON, and `config/settings_data.json`. Existing instances on the preview are preserved.
|
|
220
|
+
|
|
210
221
|
In non-interactive mode, the publish confirmation is skipped.
|
|
211
222
|
|
|
212
223
|
---
|
package/bin/tiendu.js
CHANGED
|
@@ -55,6 +55,7 @@ Global options:
|
|
|
55
55
|
--non-interactive Disable prompts, print plain text output, and skip confirmations
|
|
56
56
|
--dir <path> Create the project inside a new directory during init
|
|
57
57
|
--skip-build Reuse the existing dist/ output for push or publish
|
|
58
|
+
--skip-instances Skip template/section group JSON and settings_data.json (preserves existing instances on the preview)
|
|
58
59
|
--help, -h Show this help message
|
|
59
60
|
--version, -v Show the current CLI version
|
|
60
61
|
|
|
@@ -132,6 +133,7 @@ const main = async () => {
|
|
|
132
133
|
const command = positionals[0];
|
|
133
134
|
const subcommand = positionals[1];
|
|
134
135
|
const skipBuild = flags.has("--skip-build");
|
|
136
|
+
const skipInstances = flags.has("--skip-instances");
|
|
135
137
|
const nonInteractive =
|
|
136
138
|
flags.has("--non-interactive") || !process.stdin.isTTY || !process.stdout.isTTY;
|
|
137
139
|
|
|
@@ -190,23 +192,23 @@ const main = async () => {
|
|
|
190
192
|
}
|
|
191
193
|
|
|
192
194
|
if (command === "build") {
|
|
193
|
-
const result = await build();
|
|
195
|
+
const result = await build({ skipInstances });
|
|
194
196
|
if (!result.ok) process.exit(1);
|
|
195
197
|
return;
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
if (command === "push") {
|
|
199
|
-
await push({ skipBuild, previewKey: positionals[1] });
|
|
201
|
+
await push({ skipBuild, previewKey: positionals[1], skipInstances });
|
|
200
202
|
return;
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
if (command === "dev") {
|
|
204
|
-
await dev();
|
|
206
|
+
await dev({ skipInstances });
|
|
205
207
|
return;
|
|
206
208
|
}
|
|
207
209
|
|
|
208
210
|
if (command === "publish") {
|
|
209
|
-
await publish({ skipBuild, previewKey: positionals[1] });
|
|
211
|
+
await publish({ skipBuild, previewKey: positionals[1], skipInstances });
|
|
210
212
|
return;
|
|
211
213
|
}
|
|
212
214
|
|
package/lib/api.mjs
CHANGED
|
@@ -219,23 +219,25 @@ export const downloadPreviewArchive = async (
|
|
|
219
219
|
* @returns {Promise<{ ok: true } | { ok: false, error: string, retriable?: boolean }>}
|
|
220
220
|
*/
|
|
221
221
|
export const uploadPreviewZip = async (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
222
|
+
apiBaseUrl,
|
|
223
|
+
apiKey,
|
|
224
|
+
storeId,
|
|
225
|
+
previewKey,
|
|
226
|
+
zipBuffer,
|
|
227
|
+
preserveInstances = false,
|
|
227
228
|
) => {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
229
|
+
try {
|
|
230
|
+
const query = preserveInstances ? "?preserveInstances=true" : "";
|
|
231
|
+
const response = await apiFetch(
|
|
232
|
+
apiBaseUrl,
|
|
233
|
+
apiKey,
|
|
234
|
+
`/api/admin/stores/${storeId}/theme-previews/${previewKey}/upload${query}`,
|
|
235
|
+
{
|
|
236
|
+
method: "POST",
|
|
237
|
+
body: zipBuffer,
|
|
238
|
+
contentType: "application/zip",
|
|
239
|
+
},
|
|
240
|
+
);
|
|
239
241
|
|
|
240
242
|
if (!response.ok) {
|
|
241
243
|
const body = await response.text().catch(() => "");
|
package/lib/archive.mjs
CHANGED
|
@@ -13,7 +13,7 @@ export const listAllFiles = async (rootDir) => listFilesRecursive(rootDir);
|
|
|
13
13
|
* @param {string} rootDir
|
|
14
14
|
* @returns {Promise<Buffer>}
|
|
15
15
|
*/
|
|
16
|
-
export const createZipFromDirectory = async (rootDir) => {
|
|
16
|
+
export const createZipFromDirectory = async (rootDir, shouldInclude) => {
|
|
17
17
|
const absoluteFiles = await listAllFiles(rootDir);
|
|
18
18
|
/** @type {Record<string, Uint8Array>} */
|
|
19
19
|
const entries = {};
|
|
@@ -23,6 +23,7 @@ export const createZipFromDirectory = async (rootDir) => {
|
|
|
23
23
|
.relative(rootDir, absolutePath)
|
|
24
24
|
.split(path.sep)
|
|
25
25
|
.join("/");
|
|
26
|
+
if (shouldInclude && !shouldInclude(relativePath)) continue;
|
|
26
27
|
entries[relativePath] = new Uint8Array(await readFile(absolutePath));
|
|
27
28
|
}
|
|
28
29
|
|
package/lib/build.mjs
CHANGED
|
@@ -36,6 +36,17 @@ const ENTRY_SOURCE_EXTENSIONS = new Set([".js", ".ts", ".css"]);
|
|
|
36
36
|
const NESTED_ASSET_PATH_PATTERN =
|
|
37
37
|
/\/assets\/([A-Za-z0-9._-]+(?:\/[A-Za-z0-9._/-]+)+)([?#][A-Za-z0-9=&._-]+)?/g;
|
|
38
38
|
|
|
39
|
+
const INSTANCE_FILE_PATTERNS = [
|
|
40
|
+
/^templates\/[^/]+\.json$/,
|
|
41
|
+
/^sections\/[^/]+\.json$/,
|
|
42
|
+
/^config\/settings_data\.json$/,
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export const isInstanceFile = (relativePath) => {
|
|
46
|
+
const normalized = relativePath.split(path.sep).join("/");
|
|
47
|
+
return INSTANCE_FILE_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
48
|
+
};
|
|
49
|
+
|
|
39
50
|
/**
|
|
40
51
|
* Discover optional JS/TS and CSS entry points from src/layout/templates or layout/templates.
|
|
41
52
|
* Returns separate maps for JS and CSS to avoid key collisions.
|
|
@@ -118,9 +129,11 @@ const rewriteDirectAssetPaths = (source, knownAssetLogicalPaths) =>
|
|
|
118
129
|
return flattened ? `/assets/${flattened}${suffix}` : match;
|
|
119
130
|
});
|
|
120
131
|
|
|
121
|
-
const shouldCopyThemeSourceFile = (sourceRelativePath) => {
|
|
132
|
+
const shouldCopyThemeSourceFile = (sourceRelativePath, outputRelativePath, skipInstances = false) => {
|
|
122
133
|
const extension = path.extname(sourceRelativePath).toLowerCase();
|
|
123
|
-
|
|
134
|
+
if (ENTRY_SOURCE_EXTENSIONS.has(extension)) return false;
|
|
135
|
+
if (skipInstances && isInstanceFile(outputRelativePath)) return false;
|
|
136
|
+
return true;
|
|
124
137
|
};
|
|
125
138
|
|
|
126
139
|
const copyThemeSourceFile = async (
|
|
@@ -152,6 +165,7 @@ const copyThemeFiles = async (
|
|
|
152
165
|
distDir,
|
|
153
166
|
themeSourceDirs,
|
|
154
167
|
knownAssetLogicalPaths,
|
|
168
|
+
skipInstances = false,
|
|
155
169
|
) => {
|
|
156
170
|
for (const sourceDir of themeSourceDirs) {
|
|
157
171
|
const absoluteSourceDir = path.join(rootDir, sourceDir.sourceRelativeDir);
|
|
@@ -167,7 +181,7 @@ const copyThemeFiles = async (
|
|
|
167
181
|
sourceDir.outputRelativeDir,
|
|
168
182
|
nestedRelativePath,
|
|
169
183
|
);
|
|
170
|
-
if (!shouldCopyThemeSourceFile(sourceRelativePath)) continue;
|
|
184
|
+
if (!shouldCopyThemeSourceFile(sourceRelativePath, outputRelativePath, skipInstances)) continue;
|
|
171
185
|
await copyThemeSourceFile(
|
|
172
186
|
rootDir,
|
|
173
187
|
distDir,
|
|
@@ -247,7 +261,7 @@ const runEntryBuilds = async (jsBuildOptions, cssBuildOptions) => {
|
|
|
247
261
|
* @param {{ watch?: boolean }} options
|
|
248
262
|
* @returns {Promise<{ ok: boolean, cleanup?: () => Promise<void> }>}
|
|
249
263
|
*/
|
|
250
|
-
export const build = async ({ watch: watchMode = false } = {}) => {
|
|
264
|
+
export const build = async ({ watch: watchMode = false, skipInstances = false } = {}) => {
|
|
251
265
|
const rootDir = process.cwd();
|
|
252
266
|
const distDir = path.join(rootDir, "dist");
|
|
253
267
|
|
|
@@ -310,6 +324,7 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
310
324
|
distDir,
|
|
311
325
|
themeSourceDirs,
|
|
312
326
|
knownAssetLogicalPaths,
|
|
327
|
+
skipInstances,
|
|
313
328
|
);
|
|
314
329
|
|
|
315
330
|
if (cssCount > 0 && pipeline.postcss) {
|
|
@@ -497,7 +512,7 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
497
512
|
const timer = setTimeout(async () => {
|
|
498
513
|
debounceMap.delete(sourceRelativePath);
|
|
499
514
|
try {
|
|
500
|
-
if (!shouldCopyThemeSourceFile(sourceRelativePath)) {
|
|
515
|
+
if (!shouldCopyThemeSourceFile(sourceRelativePath, outputRelativePath, skipInstances)) {
|
|
501
516
|
return;
|
|
502
517
|
}
|
|
503
518
|
|
package/lib/dev.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
deletePreviewFile,
|
|
11
11
|
uploadPreviewFileMultipart,
|
|
12
12
|
} from "./api.mjs";
|
|
13
|
-
import { build } from "./build.mjs";
|
|
13
|
+
import { build, isInstanceFile } from "./build.mjs";
|
|
14
14
|
import { isDotfile } from "./fs-utils.mjs";
|
|
15
15
|
import { startLocalPreviewServer } from "./local-preview.mjs";
|
|
16
16
|
import { pushPreparedDirectoryToPreview } from "./push.mjs";
|
|
@@ -115,7 +115,7 @@ const deleteFileWithRetries = (
|
|
|
115
115
|
},
|
|
116
116
|
);
|
|
117
117
|
|
|
118
|
-
export const dev = async () => {
|
|
118
|
+
export const dev = async ({ skipInstances = false } = {}) => {
|
|
119
119
|
const { config, credentials } = await loadConfigOrFail();
|
|
120
120
|
const { apiBaseUrl, storeId } = config;
|
|
121
121
|
const { apiKey } = credentials;
|
|
@@ -123,7 +123,7 @@ export const dev = async () => {
|
|
|
123
123
|
let buildCleanup = null;
|
|
124
124
|
let localPreviewServer = null;
|
|
125
125
|
|
|
126
|
-
const buildResult = await build({ watch: true });
|
|
126
|
+
const buildResult = await build({ watch: true, skipInstances });
|
|
127
127
|
if (!buildResult.ok) {
|
|
128
128
|
ui.log.error("Initial build failed. Fix errors and try again.");
|
|
129
129
|
process.exit(1);
|
|
@@ -161,6 +161,7 @@ export const dev = async () => {
|
|
|
161
161
|
compressMessage: "Compressing files...",
|
|
162
162
|
retryMessage: (result, nextAttempt) =>
|
|
163
163
|
`Initial push failed. Retrying ${nextAttempt}/${RETRY_ATTEMPTS}... ${result.error}`,
|
|
164
|
+
skipInstances,
|
|
164
165
|
});
|
|
165
166
|
|
|
166
167
|
if (!uploadResult.ok) {
|
|
@@ -288,6 +289,7 @@ export const dev = async () => {
|
|
|
288
289
|
if (shouldIgnoreWatchedPath(filename, true)) return;
|
|
289
290
|
|
|
290
291
|
const relativePath = filename.split(path.sep).join("/");
|
|
292
|
+
if (skipInstances && isInstanceFile(relativePath)) return;
|
|
291
293
|
queueSync(relativePath);
|
|
292
294
|
});
|
|
293
295
|
|
package/lib/publish.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { push } from "./push.mjs";
|
|
8
8
|
import * as ui from "./ui.mjs";
|
|
9
9
|
|
|
10
|
-
export const publish = async ({ skipBuild = false, previewKey: previewKeyArg } = {}) => {
|
|
10
|
+
export const publish = async ({ skipBuild = false, previewKey: previewKeyArg, skipInstances = false } = {}) => {
|
|
11
11
|
const { config, credentials } = await loadConfigOrFail();
|
|
12
12
|
|
|
13
13
|
// Resolve preview key: explicit arg > interactive picker
|
|
@@ -42,7 +42,7 @@ export const publish = async ({ skipBuild = false, previewKey: previewKeyArg } =
|
|
|
42
42
|
? "Syncing existing dist/ output to the preview before publishing..."
|
|
43
43
|
: "Building and syncing the latest dist/ output before publishing...",
|
|
44
44
|
);
|
|
45
|
-
await push({ skipBuild, previewKey });
|
|
45
|
+
await push({ skipBuild, previewKey, skipInstances });
|
|
46
46
|
|
|
47
47
|
const spinner = ui.spinner();
|
|
48
48
|
spinner.start("Publishing preview to live storefront...");
|
package/lib/push.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getDistDir, loadConfigOrFail } from "./config.mjs";
|
|
2
2
|
import { uploadPreviewZip } from "./api.mjs";
|
|
3
3
|
import { createZipFromDirectory } from "./archive.mjs";
|
|
4
|
-
import { build } from "./build.mjs";
|
|
4
|
+
import { build, isInstanceFile } from "./build.mjs";
|
|
5
5
|
import {
|
|
6
6
|
fetchPreviewDetails,
|
|
7
7
|
resolvePreviewKeyInteractively,
|
|
@@ -26,16 +26,21 @@ export const pushPreparedDirectoryToPreview = async ({
|
|
|
26
26
|
compressMessage = "Compressing files...",
|
|
27
27
|
uploadMessage,
|
|
28
28
|
retryMessage,
|
|
29
|
+
skipInstances = false,
|
|
29
30
|
}) => {
|
|
30
31
|
spinner.message(compressMessage);
|
|
31
32
|
|
|
32
|
-
const
|
|
33
|
+
const shouldInclude = skipInstances
|
|
34
|
+
? (relativePath) => !isInstanceFile(relativePath)
|
|
35
|
+
: undefined;
|
|
36
|
+
|
|
37
|
+
const zipBuffer = await createZipFromDirectory(rootDir, shouldInclude);
|
|
33
38
|
spinner.message(
|
|
34
39
|
uploadMessage ?? `Uploading to preview ${previewKey} (${formatBytes(zipBuffer.length)})...`,
|
|
35
40
|
);
|
|
36
41
|
|
|
37
42
|
return retryAsync(
|
|
38
|
-
() => uploadPreviewZip(apiBaseUrl, apiKey, storeId, previewKey, zipBuffer),
|
|
43
|
+
() => uploadPreviewZip(apiBaseUrl, apiKey, storeId, previewKey, zipBuffer, skipInstances),
|
|
39
44
|
{
|
|
40
45
|
attempts: 3,
|
|
41
46
|
shouldRetry: (uploadResult) => !uploadResult.ok && Boolean(uploadResult.retriable),
|
|
@@ -49,11 +54,11 @@ export const pushPreparedDirectoryToPreview = async ({
|
|
|
49
54
|
);
|
|
50
55
|
};
|
|
51
56
|
|
|
52
|
-
export const push = async ({ skipBuild = false, previewKey: previewKeyArg } = {}) => {
|
|
57
|
+
export const push = async ({ skipBuild = false, previewKey: previewKeyArg, skipInstances = false } = {}) => {
|
|
53
58
|
const { config, credentials } = await loadConfigOrFail();
|
|
54
59
|
|
|
55
60
|
if (!skipBuild) {
|
|
56
|
-
const result = await build();
|
|
61
|
+
const result = await build({ skipInstances });
|
|
57
62
|
if (!result.ok) {
|
|
58
63
|
process.exit(1);
|
|
59
64
|
}
|
|
@@ -84,6 +89,7 @@ export const push = async ({ skipBuild = false, previewKey: previewKeyArg } = {}
|
|
|
84
89
|
previewKey,
|
|
85
90
|
rootDir,
|
|
86
91
|
spinner,
|
|
92
|
+
skipInstances,
|
|
87
93
|
});
|
|
88
94
|
|
|
89
95
|
if (!result.ok) {
|