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 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 live with a local auto-reloading URL plus a sharable preview URL, and publish when you're ready — all from the terminal.
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 local live-preview URL first, plus a sharable preview URL like:
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
- It also starts a local live-preview URL that proxies the preview and auto-reloads after successful syncs.
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 current live theme from your store into `dist/`.
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
- - Your source files, including `src/`, are left untouched.
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 --skip-instances
145
+ tiendu build --override-state
142
146
  ```
143
147
 
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.
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 --skip-instances
180
+ tiendu dev --override-state
174
181
  ```
175
182
 
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.
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 --skip-instances
205
+ tiendu push --override-state
199
206
  ```
200
207
 
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.
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 --skip-instances
224
+ tiendu publish --override-state
217
225
  ```
218
226
 
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.
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] Download the live theme or a preview into dist/
35
- tiendu build Build or stage the current theme into dist/
36
- tiendu push [previewKey] [--skip-build]
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 Start dev mode: auto-sync changes to a live preview URL
39
- tiendu publish [previewKey] [--skip-build]
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
- --skip-instances Skip template/section group JSON and settings_data.json (preserves existing instances on the preview)
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, run tiendu stores list and tiendu stores set <id>.
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 resets dist/ and extracts the downloaded theme there.
82
- pull does not delete src/ files.
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("Missing value for --dir.");
138
+ console.error(`Missing value for ${arg}.`);
117
139
  process.exit(1);
118
140
  }
119
- values.set("dir", value);
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 skipInstances = flags.has("--skip-instances");
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 result = await build({ skipInstances });
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 push({ skipBuild, previewKey: positionals[1], skipInstances });
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 dev({ skipInstances });
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 publish({ skipBuild, previewKey: positionals[1], skipInstances });
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, skipInstances = false) => {
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 (skipInstances && isInstanceFile(outputRelativePath)) return false;
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
- skipInstances = false,
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, skipInstances)) continue;
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, skipInstances = 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
- skipInstances,
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, skipInstances)) {
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 {{ pipeline?: Partial<TienduPipelineConfig> }} TienduThemeConfig
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 ({ skipInstances = false } = {}) => {
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, skipInstances });
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 to get hostname for local proxy
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
- skipInstances,
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 (skipInstances && isInstanceFile(relativePath)) return;
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.");