tiendu 0.8.1 → 0.9.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 +37 -14
- package/bin/tiendu.js +65 -21
- package/lib/build.mjs +7 -7
- package/lib/config.mjs +63 -1
- package/lib/dev.mjs +11 -25
- package/lib/init.mjs +184 -15
- package/lib/preview.mjs +2 -0
- package/lib/publish.mjs +2 -2
- package/lib/pull.mjs +64 -2
- package/lib/push.mjs +9 -6
- package/lib/stores.mjs +7 -2
- package/package.json +1 -1
- package/lib/local-preview.mjs +0 -393
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Official CLI for [Tiendu](https://tiendu.uy) — develop and publish storefront themes from your local machine.
|
|
4
4
|
|
|
5
|
-
Download your store's theme, edit files locally, preview changes
|
|
5
|
+
Download your store's theme, edit files locally, preview changes with a sharable preview URL, and publish when you're ready — all from the terminal.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -58,7 +58,7 @@ tiendu stores set <store-id> --non-interactive
|
|
|
58
58
|
|
|
59
59
|
When `--non-interactive` is passed, the CLI avoids prompts and prints plain text output.
|
|
60
60
|
|
|
61
|
-
`tiendu dev` creates a remote preview, builds or stages your theme into `dist/`, runs an initial push from that prepared output, and then watches for changes. It prints a
|
|
61
|
+
`tiendu dev` creates or attaches a remote preview, builds or stages your theme into `dist/`, runs an initial push from that prepared output, and then watches for changes. It prints a sharable preview URL like:
|
|
62
62
|
|
|
63
63
|
```
|
|
64
64
|
http://preview-xxxxxxxxxxxx.tiendu.uy/
|
|
@@ -67,7 +67,8 @@ http://preview-xxxxxxxxxxxx.tiendu.uy/
|
|
|
67
67
|
The preview renders with the real Tiendu engine — same output as production.
|
|
68
68
|
|
|
69
69
|
When `tiendu dev` starts, it always re-syncs your current local files to the active preview before watching for changes.
|
|
70
|
-
|
|
70
|
+
|
|
71
|
+
By default, the CLI preserves editor-managed theme state so local development does not overwrite changes made in the theme editor. State files are `templates/*.json`, section group files like `sections/header-group.json`, and `config/settings_data.json`. Use `--override-state` when your local state JSON files should override the editor state.
|
|
71
72
|
|
|
72
73
|
---
|
|
73
74
|
|
|
@@ -116,14 +117,17 @@ tiendu stores set 123 --non-interactive
|
|
|
116
117
|
|
|
117
118
|
### `tiendu pull`
|
|
118
119
|
|
|
119
|
-
Downloads the
|
|
120
|
+
Downloads the attached preview theme, or the live theme with `--live`, into `dist/` and syncs theme directories to `src/`.
|
|
120
121
|
|
|
121
122
|
- `pull` clears `dist/` first.
|
|
122
123
|
- The downloaded archive is then extracted into `dist/`.
|
|
123
|
-
-
|
|
124
|
+
- Theme directories from the download are synced into `src/`, overwriting local theme files.
|
|
125
|
+
- In interactive mode, the CLI asks before overwriting `src/`.
|
|
126
|
+
- In non-interactive mode, `src/` is overwritten without prompting.
|
|
124
127
|
|
|
125
128
|
```bash
|
|
126
129
|
tiendu pull
|
|
130
|
+
tiendu pull --live
|
|
127
131
|
```
|
|
128
132
|
|
|
129
133
|
---
|
|
@@ -138,10 +142,13 @@ Builds or stages the current theme into its deployable output directory (`dist/`
|
|
|
138
142
|
|
|
139
143
|
```bash
|
|
140
144
|
tiendu build
|
|
141
|
-
tiendu build --
|
|
145
|
+
tiendu build --override-state
|
|
142
146
|
```
|
|
143
147
|
|
|
144
|
-
-
|
|
148
|
+
- By default, `build` omits editor-managed state files from `dist/`.
|
|
149
|
+
- Use `--override-state` to include template JSON, section group JSON, and `config/settings_data.json` in `dist/`.
|
|
150
|
+
- `--include-instances` is still accepted as a deprecated alias for `--override-state`.
|
|
151
|
+
- `--skip-instances` is still accepted as a deprecated alias for the default preserve behavior.
|
|
145
152
|
|
|
146
153
|
The build:
|
|
147
154
|
|
|
@@ -170,15 +177,15 @@ The main development command.
|
|
|
170
177
|
|
|
171
178
|
```bash
|
|
172
179
|
tiendu dev
|
|
173
|
-
tiendu dev --
|
|
180
|
+
tiendu dev --override-state
|
|
174
181
|
```
|
|
175
182
|
|
|
176
|
-
-
|
|
183
|
+
- By default, `dev` preserves template JSON, section group JSON, and `config/settings_data.json` on the preview so theme editor changes are not overwritten.
|
|
184
|
+
- Use `--override-state` to sync those state files from your local project too.
|
|
177
185
|
- Prints the preview URL on start
|
|
178
186
|
- Re-syncs the full local theme to the preview on startup
|
|
179
187
|
- Syncs file creates, edits and deletes
|
|
180
188
|
- Retries failed file sync operations up to 3 times before giving up
|
|
181
|
-
- Starts a local live-preview URL on `localhost` that refreshes after successful uploads
|
|
182
189
|
- Handles both text and binary files (images, fonts, etc.)
|
|
183
190
|
- Press `Ctrl+C` to stop
|
|
184
191
|
|
|
@@ -195,10 +202,11 @@ Zips and uploads `dist/` to the active preview, replacing its content entirely.
|
|
|
195
202
|
tiendu push
|
|
196
203
|
tiendu push --skip-build
|
|
197
204
|
tiendu push --skip-build --non-interactive
|
|
198
|
-
tiendu push --
|
|
205
|
+
tiendu push --override-state
|
|
199
206
|
```
|
|
200
207
|
|
|
201
|
-
-
|
|
208
|
+
- By default, `push` uploads code/assets while preserving editor-managed state on the preview.
|
|
209
|
+
- Use `--override-state` to upload local template JSON, section group JSON, and `config/settings_data.json`.
|
|
202
210
|
|
|
203
211
|
---
|
|
204
212
|
|
|
@@ -213,13 +221,28 @@ Publishes the active preview to the live storefront. Visitors will see the new t
|
|
|
213
221
|
tiendu publish
|
|
214
222
|
tiendu publish --skip-build
|
|
215
223
|
tiendu publish --skip-build --non-interactive
|
|
216
|
-
tiendu publish --
|
|
224
|
+
tiendu publish --override-state
|
|
217
225
|
```
|
|
218
226
|
|
|
219
|
-
-
|
|
227
|
+
- By default, `publish` syncs code/assets before publishing while preserving editor-managed state.
|
|
228
|
+
- Use `--override-state` to publish local template JSON, section group JSON, and `config/settings_data.json`.
|
|
220
229
|
|
|
221
230
|
In non-interactive mode, the publish confirmation is skipped.
|
|
222
231
|
|
|
232
|
+
### State sync defaults
|
|
233
|
+
|
|
234
|
+
You can set the default for a project in `tiendu.config.json`:
|
|
235
|
+
|
|
236
|
+
```json
|
|
237
|
+
{
|
|
238
|
+
"sync": {
|
|
239
|
+
"state": false
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Use `true` when local state JSON files should override editor state by default.
|
|
245
|
+
|
|
223
246
|
---
|
|
224
247
|
|
|
225
248
|
### `tiendu check-updates`
|
package/bin/tiendu.js
CHANGED
|
@@ -21,22 +21,26 @@ import {
|
|
|
21
21
|
checkForUpdatesNow,
|
|
22
22
|
getCurrentVersion,
|
|
23
23
|
} from "../lib/update-check.mjs";
|
|
24
|
+
import { resolveOverrideState } from "../lib/config.mjs";
|
|
24
25
|
import { configureUi } from "../lib/ui.mjs";
|
|
25
26
|
|
|
26
27
|
const HELP = `
|
|
27
28
|
tiendu — Tiendu theme development CLI
|
|
28
29
|
|
|
29
30
|
Usage:
|
|
30
|
-
tiendu init [apiKey] [baseUrl] [--dir <path>]
|
|
31
|
+
tiendu init [apiKey] [baseUrl] [--api-key <key>] [--base-url <url>] [--preview-key <key>] [--dir <path>]
|
|
31
32
|
Initialize interactively, or reset config with direct credentials
|
|
32
33
|
tiendu stores list List stores available for the configured API key
|
|
33
34
|
tiendu stores set <storeId> Select the active store
|
|
34
|
-
tiendu pull [previewKey]
|
|
35
|
-
|
|
36
|
-
tiendu
|
|
35
|
+
tiendu pull [previewKey] [--live]
|
|
36
|
+
Download the attached preview or a specific preview into dist/ and src/
|
|
37
|
+
tiendu build [--override-state]
|
|
38
|
+
Build or stage the current theme into dist/
|
|
39
|
+
tiendu push [previewKey] [--skip-build] [--override-state]
|
|
37
40
|
Upload dist/ to the attached or specified preview
|
|
38
|
-
tiendu dev
|
|
39
|
-
|
|
41
|
+
tiendu dev [--override-state]
|
|
42
|
+
Start dev mode: auto-sync changes to a live preview URL
|
|
43
|
+
tiendu publish [previewKey] [--skip-build] [--override-state]
|
|
40
44
|
Build/sync dist/ and publish the preview live
|
|
41
45
|
|
|
42
46
|
tiendu preview Show the attached preview details
|
|
@@ -54,8 +58,15 @@ Usage:
|
|
|
54
58
|
Global options:
|
|
55
59
|
--non-interactive Disable prompts, print plain text output, and skip confirmations
|
|
56
60
|
--dir <path> Create the project inside a new directory during init
|
|
61
|
+
--api-key <key> Provide an API key to tiendu init (alternative to positional arg)
|
|
62
|
+
--base-url <url> Provide a base URL to tiendu init (alternative to positional arg)
|
|
63
|
+
--preview-key <key> Attach a preview during tiendu init
|
|
64
|
+
--live Force tiendu pull to download the live theme
|
|
57
65
|
--skip-build Reuse the existing dist/ output for push or publish
|
|
58
|
-
--
|
|
66
|
+
--override-state Sync local theme state JSON and override editor state
|
|
67
|
+
--preserve-state Preserve editor-managed state JSON (default)
|
|
68
|
+
--include-instances Deprecated alias for --override-state
|
|
69
|
+
--skip-instances Deprecated alias for --preserve-state
|
|
59
70
|
--help, -h Show this help message
|
|
60
71
|
--version, -v Show the current CLI version
|
|
61
72
|
|
|
@@ -63,12 +74,16 @@ Init behavior:
|
|
|
63
74
|
tiendu init Interactive setup wizard
|
|
64
75
|
tiendu init <apiKey> Reset saved config and connect using the default base URL
|
|
65
76
|
tiendu init <apiKey> <url> Reset saved config and connect using a custom base URL
|
|
77
|
+
tiendu init --api-key <key> --base-url <url> Using flags instead of positional args
|
|
78
|
+
tiendu init --preview-key <key> Attach a preview directly
|
|
66
79
|
The default base URL points to the Tiendu platform and rarely needs to change.
|
|
67
80
|
If exactly one store is available, it is selected automatically.
|
|
68
|
-
If multiple stores are available,
|
|
81
|
+
If multiple stores are available, the interactive init will let you choose one.
|
|
82
|
+
After selecting a store, you can also create or attach a preview.
|
|
69
83
|
|
|
70
84
|
Agent-friendly setup:
|
|
71
85
|
tiendu init <apiKey> [baseUrl] --non-interactive
|
|
86
|
+
tiendu init --api-key <key> --base-url <url> --non-interactive
|
|
72
87
|
tiendu stores list --non-interactive
|
|
73
88
|
tiendu stores set <id> --non-interactive
|
|
74
89
|
tiendu pull --non-interactive
|
|
@@ -78,8 +93,8 @@ Agent-friendly setup:
|
|
|
78
93
|
Push and pull behavior:
|
|
79
94
|
build always prepares dist/ as the local deploy artifact.
|
|
80
95
|
push sends a zip of dist/ to the target preview.
|
|
81
|
-
pull
|
|
82
|
-
pull
|
|
96
|
+
pull downloads from the attached preview by default, or the live theme with --live.
|
|
97
|
+
pull also syncs downloaded theme directories to src/.
|
|
83
98
|
|
|
84
99
|
Pipeline behavior:
|
|
85
100
|
tiendu.config.json can enable optional pipeline steps.
|
|
@@ -88,6 +103,13 @@ Pipeline behavior:
|
|
|
88
103
|
pipeline.postcss enables PostCSS for compiled style entries.
|
|
89
104
|
With no config file, or with no enabled pipeline steps, build just stages theme files into dist/.
|
|
90
105
|
|
|
106
|
+
Theme state behavior:
|
|
107
|
+
By default, the CLI preserves editor-managed state files: templates/*.json,
|
|
108
|
+
sections/*.json, and config/settings_data.json.
|
|
109
|
+
Use --override-state when your local state JSON should overwrite preview/editor state.
|
|
110
|
+
In tiendu.config.json, set { "sync": { "state": true } } to make local
|
|
111
|
+
state JSON the project default, or false to keep the safe default explicit.
|
|
112
|
+
|
|
91
113
|
Typical workflow:
|
|
92
114
|
tiendu init Connect to Tiendu and save your credentials
|
|
93
115
|
tiendu stores list See available stores
|
|
@@ -110,13 +132,13 @@ const parseArgv = (argv) => {
|
|
|
110
132
|
continue;
|
|
111
133
|
}
|
|
112
134
|
|
|
113
|
-
if (arg === "--dir") {
|
|
135
|
+
if (arg === "--dir" || arg === "--api-key" || arg === "--base-url" || arg === "--preview-key") {
|
|
114
136
|
const value = argv[index + 1];
|
|
115
137
|
if (!value || value.startsWith("--")) {
|
|
116
|
-
console.error(
|
|
138
|
+
console.error(`Missing value for ${arg}.`);
|
|
117
139
|
process.exit(1);
|
|
118
140
|
}
|
|
119
|
-
values.set(
|
|
141
|
+
values.set(arg.slice(2), value);
|
|
120
142
|
index += 1;
|
|
121
143
|
continue;
|
|
122
144
|
}
|
|
@@ -133,7 +155,12 @@ const main = async () => {
|
|
|
133
155
|
const command = positionals[0];
|
|
134
156
|
const subcommand = positionals[1];
|
|
135
157
|
const skipBuild = flags.has("--skip-build");
|
|
136
|
-
const
|
|
158
|
+
const overrideStateFlag =
|
|
159
|
+
flags.has("--override-state") || flags.has("--include-instances");
|
|
160
|
+
const preserveStateFlag =
|
|
161
|
+
flags.has("--preserve-state") ||
|
|
162
|
+
flags.has("--preserve-instances") ||
|
|
163
|
+
flags.has("--skip-instances");
|
|
137
164
|
const nonInteractive =
|
|
138
165
|
flags.has("--non-interactive") || !process.stdin.isTTY || !process.stdout.isTTY;
|
|
139
166
|
|
|
@@ -164,8 +191,9 @@ const main = async () => {
|
|
|
164
191
|
const initArgs = positionals.slice(1);
|
|
165
192
|
await init({
|
|
166
193
|
dirArg: values.get("dir"),
|
|
167
|
-
apiKeyArg: initArgs[0],
|
|
168
|
-
baseUrlArg: initArgs[1],
|
|
194
|
+
apiKeyArg: values.get("api-key") ?? initArgs[0],
|
|
195
|
+
baseUrlArg: values.get("base-url") ?? initArgs[1],
|
|
196
|
+
previewKeyArg: values.get("preview-key"),
|
|
169
197
|
});
|
|
170
198
|
return;
|
|
171
199
|
}
|
|
@@ -187,28 +215,44 @@ const main = async () => {
|
|
|
187
215
|
}
|
|
188
216
|
|
|
189
217
|
if (command === "pull") {
|
|
190
|
-
await pull({ previewKey: positionals[1] });
|
|
218
|
+
await pull({ previewKey: positionals[1], forceLive: flags.has("--live") });
|
|
191
219
|
return;
|
|
192
220
|
}
|
|
193
221
|
|
|
194
222
|
if (command === "build") {
|
|
195
|
-
const
|
|
223
|
+
const overrideState = await resolveOverrideState({
|
|
224
|
+
overrideStateFlag,
|
|
225
|
+
preserveStateFlag,
|
|
226
|
+
});
|
|
227
|
+
const result = await build({ overrideState });
|
|
196
228
|
if (!result.ok) process.exit(1);
|
|
197
229
|
return;
|
|
198
230
|
}
|
|
199
231
|
|
|
200
232
|
if (command === "push") {
|
|
201
|
-
await
|
|
233
|
+
const overrideState = await resolveOverrideState({
|
|
234
|
+
overrideStateFlag,
|
|
235
|
+
preserveStateFlag,
|
|
236
|
+
});
|
|
237
|
+
await push({ skipBuild, previewKey: positionals[1], overrideState });
|
|
202
238
|
return;
|
|
203
239
|
}
|
|
204
240
|
|
|
205
241
|
if (command === "dev") {
|
|
206
|
-
await
|
|
242
|
+
const overrideState = await resolveOverrideState({
|
|
243
|
+
overrideStateFlag,
|
|
244
|
+
preserveStateFlag,
|
|
245
|
+
});
|
|
246
|
+
await dev({ overrideState });
|
|
207
247
|
return;
|
|
208
248
|
}
|
|
209
249
|
|
|
210
250
|
if (command === "publish") {
|
|
211
|
-
await
|
|
251
|
+
const overrideState = await resolveOverrideState({
|
|
252
|
+
overrideStateFlag,
|
|
253
|
+
preserveStateFlag,
|
|
254
|
+
});
|
|
255
|
+
await publish({ skipBuild, previewKey: positionals[1], overrideState });
|
|
212
256
|
return;
|
|
213
257
|
}
|
|
214
258
|
|
package/lib/build.mjs
CHANGED
|
@@ -129,10 +129,10 @@ const rewriteDirectAssetPaths = (source, knownAssetLogicalPaths) =>
|
|
|
129
129
|
return flattened ? `/assets/${flattened}${suffix}` : match;
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
const shouldCopyThemeSourceFile = (sourceRelativePath, outputRelativePath,
|
|
132
|
+
const shouldCopyThemeSourceFile = (sourceRelativePath, outputRelativePath, overrideState = false) => {
|
|
133
133
|
const extension = path.extname(sourceRelativePath).toLowerCase();
|
|
134
134
|
if (ENTRY_SOURCE_EXTENSIONS.has(extension)) return false;
|
|
135
|
-
if (
|
|
135
|
+
if (!overrideState && isInstanceFile(outputRelativePath)) return false;
|
|
136
136
|
return true;
|
|
137
137
|
};
|
|
138
138
|
|
|
@@ -165,7 +165,7 @@ const copyThemeFiles = async (
|
|
|
165
165
|
distDir,
|
|
166
166
|
themeSourceDirs,
|
|
167
167
|
knownAssetLogicalPaths,
|
|
168
|
-
|
|
168
|
+
overrideState = false,
|
|
169
169
|
) => {
|
|
170
170
|
for (const sourceDir of themeSourceDirs) {
|
|
171
171
|
const absoluteSourceDir = path.join(rootDir, sourceDir.sourceRelativeDir);
|
|
@@ -181,7 +181,7 @@ const copyThemeFiles = async (
|
|
|
181
181
|
sourceDir.outputRelativeDir,
|
|
182
182
|
nestedRelativePath,
|
|
183
183
|
);
|
|
184
|
-
if (!shouldCopyThemeSourceFile(sourceRelativePath, outputRelativePath,
|
|
184
|
+
if (!shouldCopyThemeSourceFile(sourceRelativePath, outputRelativePath, overrideState)) continue;
|
|
185
185
|
await copyThemeSourceFile(
|
|
186
186
|
rootDir,
|
|
187
187
|
distDir,
|
|
@@ -261,7 +261,7 @@ const runEntryBuilds = async (jsBuildOptions, cssBuildOptions) => {
|
|
|
261
261
|
* @param {{ watch?: boolean }} options
|
|
262
262
|
* @returns {Promise<{ ok: boolean, cleanup?: () => Promise<void> }>}
|
|
263
263
|
*/
|
|
264
|
-
export const build = async ({ watch: watchMode = false,
|
|
264
|
+
export const build = async ({ watch: watchMode = false, overrideState = false } = {}) => {
|
|
265
265
|
const rootDir = process.cwd();
|
|
266
266
|
const distDir = path.join(rootDir, "dist");
|
|
267
267
|
|
|
@@ -324,7 +324,7 @@ export const build = async ({ watch: watchMode = false, skipInstances = false }
|
|
|
324
324
|
distDir,
|
|
325
325
|
themeSourceDirs,
|
|
326
326
|
knownAssetLogicalPaths,
|
|
327
|
-
|
|
327
|
+
overrideState,
|
|
328
328
|
);
|
|
329
329
|
|
|
330
330
|
if (cssCount > 0 && pipeline.postcss) {
|
|
@@ -512,7 +512,7 @@ export const build = async ({ watch: watchMode = false, skipInstances = false }
|
|
|
512
512
|
const timer = setTimeout(async () => {
|
|
513
513
|
debounceMap.delete(sourceRelativePath);
|
|
514
514
|
try {
|
|
515
|
-
if (!shouldCopyThemeSourceFile(sourceRelativePath, outputRelativePath,
|
|
515
|
+
if (!shouldCopyThemeSourceFile(sourceRelativePath, outputRelativePath, overrideState)) {
|
|
516
516
|
return;
|
|
517
517
|
}
|
|
518
518
|
|
package/lib/config.mjs
CHANGED
|
@@ -10,7 +10,8 @@ const THEME_CONFIG_FILE = "tiendu.config.json";
|
|
|
10
10
|
* @typedef {{ storeId?: number, apiBaseUrl: string, previewKey?: string }} TienduConfig
|
|
11
11
|
* @typedef {{ apiKey: string }} TienduCredentials
|
|
12
12
|
* @typedef {{ compileScripts: boolean, compileStyles: boolean, postcss: boolean }} TienduPipelineConfig
|
|
13
|
-
* @typedef {{
|
|
13
|
+
* @typedef {{ state?: boolean, instances?: "preserve" | "include" }} TienduSyncConfig
|
|
14
|
+
* @typedef {{ pipeline?: Partial<TienduPipelineConfig>, sync?: TienduSyncConfig, preserveInstances?: boolean }} TienduThemeConfig
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
const getConfigDir = () => path.resolve(process.cwd(), CONFIG_DIR);
|
|
@@ -41,6 +42,35 @@ const validateThemeConfig = (themeConfig) => {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
if (themeConfig.sync !== undefined && !isPlainObject(themeConfig.sync)) {
|
|
46
|
+
throw new Error('tiendu.config.json: "sync" must be an object.');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (
|
|
50
|
+
themeConfig.sync?.state !== undefined &&
|
|
51
|
+
typeof themeConfig.sync.state !== "boolean"
|
|
52
|
+
) {
|
|
53
|
+
throw new Error('tiendu.config.json: "sync.state" must be true or false.');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (
|
|
57
|
+
themeConfig.sync?.instances !== undefined &&
|
|
58
|
+
!["preserve", "include"].includes(themeConfig.sync.instances)
|
|
59
|
+
) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
'tiendu.config.json: "sync.instances" must be "preserve" or "include".',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
themeConfig.preserveInstances !== undefined &&
|
|
67
|
+
typeof themeConfig.preserveInstances !== "boolean"
|
|
68
|
+
) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'tiendu.config.json: "preserveInstances" must be true or false.',
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
44
74
|
return themeConfig;
|
|
45
75
|
};
|
|
46
76
|
|
|
@@ -119,6 +149,38 @@ export const getThemePipelineConfig = (themeConfig) => ({
|
|
|
119
149
|
export const readThemePipelineConfig = async () =>
|
|
120
150
|
getThemePipelineConfig(await readThemeConfig());
|
|
121
151
|
|
|
152
|
+
/**
|
|
153
|
+
* @param {TienduThemeConfig | null} themeConfig
|
|
154
|
+
* @returns {boolean}
|
|
155
|
+
*/
|
|
156
|
+
export const getThemeOverrideStateConfig = (themeConfig) => {
|
|
157
|
+
if (themeConfig?.sync?.state !== undefined) return themeConfig.sync.state;
|
|
158
|
+
if (themeConfig?.sync?.instances === "include") return true;
|
|
159
|
+
if (themeConfig?.sync?.instances === "preserve") return false;
|
|
160
|
+
if (themeConfig?.preserveInstances === false) return true;
|
|
161
|
+
return false;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {{ overrideStateFlag?: boolean, preserveStateFlag?: boolean }} [options]
|
|
166
|
+
* @returns {Promise<boolean>}
|
|
167
|
+
*/
|
|
168
|
+
export const resolveOverrideState = async ({
|
|
169
|
+
overrideStateFlag = false,
|
|
170
|
+
preserveStateFlag = false,
|
|
171
|
+
} = {}) => {
|
|
172
|
+
if (overrideStateFlag && preserveStateFlag) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
"Use either --override-state or --preserve-state, not both.",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (overrideStateFlag) return true;
|
|
179
|
+
if (preserveStateFlag) return false;
|
|
180
|
+
|
|
181
|
+
return getThemeOverrideStateConfig(await readThemeConfig());
|
|
182
|
+
};
|
|
183
|
+
|
|
122
184
|
/** @returns {string} */
|
|
123
185
|
export const getDistDir = () => path.resolve(process.cwd(), "dist");
|
|
124
186
|
|
package/lib/dev.mjs
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
} from "./api.mjs";
|
|
13
13
|
import { build, isInstanceFile } from "./build.mjs";
|
|
14
14
|
import { isDotfile } from "./fs-utils.mjs";
|
|
15
|
-
import { startLocalPreviewServer } from "./local-preview.mjs";
|
|
16
15
|
import { pushPreparedDirectoryToPreview } from "./push.mjs";
|
|
17
16
|
import { retryAsync } from "./retry.mjs";
|
|
18
17
|
import * as ui from "./ui.mjs";
|
|
@@ -115,15 +114,14 @@ const deleteFileWithRetries = (
|
|
|
115
114
|
},
|
|
116
115
|
);
|
|
117
116
|
|
|
118
|
-
export const dev = async ({
|
|
117
|
+
export const dev = async ({ overrideState = false } = {}) => {
|
|
119
118
|
const { config, credentials } = await loadConfigOrFail();
|
|
120
119
|
const { apiBaseUrl, storeId } = config;
|
|
121
120
|
const { apiKey } = credentials;
|
|
122
121
|
const rootDir = getDistDir();
|
|
123
122
|
let buildCleanup = null;
|
|
124
|
-
let localPreviewServer = null;
|
|
125
123
|
|
|
126
|
-
const buildResult = await build({ watch: true,
|
|
124
|
+
const buildResult = await build({ watch: true, overrideState });
|
|
127
125
|
if (!buildResult.ok) {
|
|
128
126
|
ui.log.error("Initial build failed. Fix errors and try again.");
|
|
129
127
|
process.exit(1);
|
|
@@ -133,7 +131,7 @@ export const dev = async ({ skipInstances = false } = {}) => {
|
|
|
133
131
|
// Resolve preview via shared interactive picker
|
|
134
132
|
const previewKey = await resolvePreviewKeyInteractively({ config, credentials });
|
|
135
133
|
|
|
136
|
-
// Fetch preview
|
|
134
|
+
// Fetch preview details for user-facing URLs.
|
|
137
135
|
const previewResult = await fetchPreviewDetails(
|
|
138
136
|
apiBaseUrl,
|
|
139
137
|
apiKey,
|
|
@@ -145,7 +143,6 @@ export const dev = async ({ skipInstances = false } = {}) => {
|
|
|
145
143
|
process.exit(1);
|
|
146
144
|
}
|
|
147
145
|
|
|
148
|
-
const previewHostname = previewResult.data.preview.previewHostname;
|
|
149
146
|
const previewUrl = previewResult.data.url;
|
|
150
147
|
|
|
151
148
|
const spinner = ui.spinner();
|
|
@@ -161,7 +158,7 @@ export const dev = async ({ skipInstances = false } = {}) => {
|
|
|
161
158
|
compressMessage: "Compressing files...",
|
|
162
159
|
retryMessage: (result, nextAttempt) =>
|
|
163
160
|
`Initial push failed. Retrying ${nextAttempt}/${RETRY_ATTEMPTS}... ${result.error}`,
|
|
164
|
-
|
|
161
|
+
overrideState,
|
|
165
162
|
});
|
|
166
163
|
|
|
167
164
|
if (!uploadResult.ok) {
|
|
@@ -170,20 +167,14 @@ export const dev = async ({ skipInstances = false } = {}) => {
|
|
|
170
167
|
process.exit(1);
|
|
171
168
|
}
|
|
172
169
|
|
|
173
|
-
try {
|
|
174
|
-
localPreviewServer = await startLocalPreviewServer({
|
|
175
|
-
apiBaseUrl,
|
|
176
|
-
previewHostname,
|
|
177
|
-
});
|
|
178
|
-
} catch (error) {
|
|
179
|
-
ui.log.warn(`Could not start local live preview: ${error.message}`);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
170
|
spinner.stop(`Preview ready (${previewKey}).`);
|
|
183
|
-
if (localPreviewServer) {
|
|
184
|
-
ui.log.message(`Local live preview: ${localPreviewServer.url}`);
|
|
185
|
-
}
|
|
186
171
|
ui.log.message(`Sharable preview: ${previewUrl}`);
|
|
172
|
+
ui.log.message(
|
|
173
|
+
`Theme editor: ${apiBaseUrl}/admin/tiendas/${storeId}/tema/personalizar?preview=${previewKey}`,
|
|
174
|
+
);
|
|
175
|
+
ui.log.message(
|
|
176
|
+
`Theme state: ${overrideState ? "overridden from local files" : "preserved from the theme editor"}`,
|
|
177
|
+
);
|
|
187
178
|
|
|
188
179
|
ui.log.message("Watching for changes - press Ctrl+C to stop.");
|
|
189
180
|
|
|
@@ -236,8 +227,6 @@ export const dev = async ({ skipInstances = false } = {}) => {
|
|
|
236
227
|
|
|
237
228
|
if (!result.ok) {
|
|
238
229
|
ui.log.warn(` Failed to delete after ${RETRY_ATTEMPTS} attempts: ${result.error}`);
|
|
239
|
-
} else {
|
|
240
|
-
localPreviewServer?.notifyReload();
|
|
241
230
|
}
|
|
242
231
|
}
|
|
243
232
|
|
|
@@ -269,8 +258,6 @@ export const dev = async ({ skipInstances = false } = {}) => {
|
|
|
269
258
|
|
|
270
259
|
if (!result.ok) {
|
|
271
260
|
ui.log.warn(` Failed to upload after ${RETRY_ATTEMPTS} attempts: ${result.error}`);
|
|
272
|
-
} else {
|
|
273
|
-
localPreviewServer?.notifyReload();
|
|
274
261
|
}
|
|
275
262
|
} catch (error) {
|
|
276
263
|
ui.log.warn(` Error processing ${relativePath}: ${error.message}`);
|
|
@@ -289,7 +276,7 @@ export const dev = async ({ skipInstances = false } = {}) => {
|
|
|
289
276
|
if (shouldIgnoreWatchedPath(filename, true)) return;
|
|
290
277
|
|
|
291
278
|
const relativePath = filename.split(path.sep).join("/");
|
|
292
|
-
if (
|
|
279
|
+
if (!overrideState && isInstanceFile(relativePath)) return;
|
|
293
280
|
queueSync(relativePath);
|
|
294
281
|
});
|
|
295
282
|
|
|
@@ -301,7 +288,6 @@ export const dev = async ({ skipInstances = false } = {}) => {
|
|
|
301
288
|
watcher.close();
|
|
302
289
|
for (const timer of debounceMap.values()) clearTimeout(timer);
|
|
303
290
|
|
|
304
|
-
await runCleanupStep("Local preview shutdown", () => localPreviewServer?.close());
|
|
305
291
|
await runCleanupStep("Build watcher shutdown", buildCleanup);
|
|
306
292
|
|
|
307
293
|
ui.outro("Dev mode stopped.");
|