tiendu 0.2.2 → 0.3.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
@@ -24,27 +24,33 @@ npm install -g tiendu
24
24
 
25
25
  ## Quick start
26
26
 
27
+ ### Buildless theme (simple)
28
+
27
29
  ```bash
28
- # Create a working directory and enter it
29
30
  mkdir my-theme && cd my-theme
30
-
31
- # Connect to your store
32
31
  tiendu init
33
-
34
- # Download the current live theme
35
32
  tiendu pull
33
+ tiendu dev
34
+ ```
36
35
 
37
- # Start developing with live preview
36
+ ### Built theme (TypeScript, npm packages, bundling)
37
+
38
+ Clone the default theme template, connect to your store, and start developing:
39
+
40
+ ```bash
41
+ git clone <default-theme-repo> my-theme && cd my-theme
42
+ npm install
43
+ tiendu init
38
44
  tiendu dev
39
45
  ```
40
46
 
41
- `tiendu dev` creates a remote preview of your theme, uploads your local files, watches for changes and syncs them automatically, and prints a shareable URL like:
47
+ `tiendu dev` creates a remote preview, builds your source files, uploads the output, and watches for changes. It prints a shareable URL like:
42
48
 
43
49
  ```
44
50
  http://preview-xxxxxxxxxxxx.tiendu.uy/
45
51
  ```
46
52
 
47
- The preview renders with the real Tiendu engine — same output as production. Share the URL with your client or team before publishing.
53
+ The preview renders with the real Tiendu engine — same output as production.
48
54
 
49
55
  ---
50
56
 
@@ -64,7 +70,10 @@ tiendu init
64
70
 
65
71
  ### `tiendu pull`
66
72
 
67
- Downloads the current live theme from your store as local files. Run this once to get started, or to reset your local files to the published version.
73
+ Downloads the current live theme from your store as local files.
74
+
75
+ - **Buildless themes:** extracts to the current directory.
76
+ - **Built themes** (with `tiendu.config.json`): extracts to `dist/`.
68
77
 
69
78
  ```bash
70
79
  tiendu pull
@@ -72,9 +81,34 @@ tiendu pull
72
81
 
73
82
  ---
74
83
 
84
+ ### `tiendu build`
85
+
86
+ Builds a theme into its deployable output directory (`dist/`). Only available for built themes (requires `tiendu.config.json`).
87
+
88
+ ```bash
89
+ tiendu build
90
+ ```
91
+
92
+ The build:
93
+
94
+ 1. Copies theme files (`layout/`, `templates/`, `snippets/`, `assets/`) to `dist/`
95
+ 2. Discovers entry points in `src/layout/` and `src/templates/`
96
+ 3. Bundles JS/TS and CSS via esbuild into `dist/assets/`
97
+
98
+ Entry naming convention:
99
+
100
+ - `src/layout/theme.ts` → `dist/assets/layout-theme.bundle.js`
101
+ - `src/templates/product.ts` → `dist/assets/template-product.bundle.js`
102
+ - `src/layout/theme.css` → `dist/assets/layout-theme.bundle.css`
103
+
104
+ ---
105
+
75
106
  ### `tiendu dev`
76
107
 
77
- The main development command. On first run it creates a remote preview, uploads your local files, and starts watching for changes. Every file you save is automatically synced to the preview.
108
+ The main development command.
109
+
110
+ - **Buildless themes:** watches the current directory and syncs file changes to the preview.
111
+ - **Built themes:** runs `tiendu build` in watch mode first, then watches `dist/` and syncs changes to the preview.
78
112
 
79
113
  ```bash
80
114
  tiendu dev
@@ -89,7 +123,10 @@ tiendu dev
89
123
 
90
124
  ### `tiendu push`
91
125
 
92
- Zips all local files (excluding dotfiles) and uploads them to the active preview, replacing its content entirely. Use this instead of `tiendu dev` if you prefer manual syncs.
126
+ Zips and uploads files to the active preview, replacing its content entirely.
127
+
128
+ - **Buildless themes:** uploads from the current directory.
129
+ - **Built themes:** uploads from `dist/`.
93
130
 
94
131
  ```bash
95
132
  tiendu push
@@ -109,7 +146,7 @@ tiendu publish
109
146
 
110
147
  ### `tiendu preview create [name]`
111
148
 
112
- Creates a new remote preview. Fails with a conflict error if one already exists for your account on this store — delete it first with `tiendu preview delete`.
149
+ Creates a new remote preview.
113
150
 
114
151
  ```bash
115
152
  tiendu preview create
@@ -150,6 +187,8 @@ tiendu preview open
150
187
 
151
188
  ## Typical workflow
152
189
 
190
+ ### Buildless
191
+
153
192
  ```
154
193
  tiendu init # one time: connect to your store
155
194
  tiendu pull # one time: download the live theme
@@ -159,6 +198,18 @@ tiendu dev # develop: edit locally, see changes live at the preview URL
159
198
  tiendu publish # when ready: push to the live storefront
160
199
  ```
161
200
 
201
+ ### Built theme
202
+
203
+ ```
204
+ git clone <template-repo> my-theme
205
+ cd my-theme && npm install
206
+ tiendu init # one time: connect to your store
207
+
208
+ tiendu dev # develop: builds src/, watches dist/, syncs to preview
209
+
210
+ tiendu publish # when ready: push to the live storefront
211
+ ```
212
+
162
213
  ---
163
214
 
164
215
  ## How previews work
@@ -173,18 +224,54 @@ A **theme preview** is a remote copy of your theme hosted by Tiendu. It renders
173
224
 
174
225
  ---
175
226
 
176
- ## Local project structure
227
+ ## Built themes
228
+
229
+ A **built theme** is a theme that uses `tiendu.config.json` to enable the build pipeline. It allows:
177
230
 
178
- After `tiendu pull` your directory will look like your store's theme. The `.cli/` folder holds local CLI configuration and is never uploaded to Tiendu.
231
+ - npm packages via a local `package.json`
232
+ - TypeScript (`.ts`) for browser code
233
+ - JS bundling (multiple modules → single versioned bundle)
234
+ - CSS bundling (`@import` support)
235
+
236
+ ### Project structure
179
237
 
180
238
  ```
181
239
  my-theme/
182
- ├── .cli/ # local config (API key, store ID, active preview key)
183
- ├── layout/
184
- ├── templates/
185
- ├── snippets/
186
- ├── assets/
187
- └── ...
240
+ ├── tiendu.config.json # marks this as a built theme
241
+ ├── package.json # npm dependencies
242
+ ├── .gitignore
243
+ ├── src/
244
+ ├── layout/
245
+ │ │ ├── theme.ts # layout TS entry → layout-theme.bundle.js
246
+ │ │ └── theme.css # layout CSS entry → layout-theme.bundle.css
247
+ │ ├── templates/
248
+ │ │ ├── product.ts # template TS entry → template-product.bundle.js
249
+ │ │ └── product.css # template CSS entry → template-product.bundle.css
250
+ │ ├── lib/ # shared modules (bundled into entries, not served)
251
+ │ └── css/ # shared CSS (imported by entry CSS)
252
+ ├── layout/ # Liquid layouts
253
+ ├── templates/ # Liquid templates
254
+ ├── snippets/ # Liquid snippets
255
+ ├── assets/ # static assets (SVGs, images, fonts)
256
+ └── dist/ # build output (gitignored, uploaded to Tiendu)
257
+ ```
258
+
259
+ ### How it works
260
+
261
+ 1. Source JS/TS/CSS in `src/` is bundled by esbuild into `dist/assets/`
262
+ 2. Liquid files and static assets are copied from root to `dist/`
263
+ 3. `dist/` is what gets uploaded — it looks like a normal Tiendu theme
264
+ 4. Liquid templates reference bundles via `asset_url` (e.g. `{{ 'layout-theme.bundle.js' | asset_url | script_tag }}`)
265
+
266
+ ### tiendu.config.json
267
+
268
+ Minimal config — the build conventions are hardcoded:
269
+
270
+ ```json
271
+ {
272
+ "name": "my-theme",
273
+ "version": "1.0.0"
274
+ }
188
275
  ```
189
276
 
190
277
  ---
package/bin/tiendu.js CHANGED
@@ -5,6 +5,7 @@ import { pull } from "../lib/pull.mjs";
5
5
  import { push } from "../lib/push.mjs";
6
6
  import { dev } from "../lib/dev.mjs";
7
7
  import { publish } from "../lib/publish.mjs";
8
+ import { build } from "../lib/build.mjs";
8
9
  import {
9
10
  previewCreate,
10
11
  previewShow,
@@ -20,6 +21,7 @@ tiendu — Tiendu theme development CLI
20
21
  Usage:
21
22
  tiendu init [dir] Set up a theme project (optionally in a new directory)
22
23
  tiendu pull Download the live theme from your store
24
+ tiendu build Build a theme (requires tiendu.config.json)
23
25
  tiendu push Upload local files to the active preview (full replace)
24
26
  tiendu dev Start dev mode: auto-sync changes to a live preview URL
25
27
  tiendu publish Publish the active preview to the live storefront
@@ -36,6 +38,7 @@ Typical workflow:
36
38
  tiendu init my-store Set up a new project in ./my-store
37
39
  cd my-store
38
40
  tiendu pull Download the current live theme
41
+ tiendu build Build the theme (for themes with tiendu.config.json)
39
42
  tiendu dev Edit locally — preview updates in real time
40
43
  tiendu publish Ship to the live storefront when ready
41
44
  `;
@@ -68,6 +71,12 @@ const main = async () => {
68
71
  return;
69
72
  }
70
73
 
74
+ if (command === "build") {
75
+ const result = await build();
76
+ if (!result.ok) process.exit(1);
77
+ return;
78
+ }
79
+
71
80
  if (command === "push") {
72
81
  await push();
73
82
  return;
package/lib/build.mjs ADDED
@@ -0,0 +1,214 @@
1
+ import { watch } from "node:fs";
2
+ import {
3
+ cp,
4
+ mkdir,
5
+ readdir,
6
+ rm,
7
+ stat,
8
+ copyFile,
9
+ } from "node:fs/promises";
10
+ import path from "node:path";
11
+ import * as esbuild from "esbuild";
12
+ import * as p from "@clack/prompts";
13
+ import { readThemeConfig } from "./config.mjs";
14
+
15
+ const THEME_DIRS = ["layout", "templates", "snippets", "assets"];
16
+
17
+ /**
18
+ * Discover JS/TS and CSS entry points from src/layout and src/templates.
19
+ * Returns separate maps for JS and CSS to avoid key collisions.
20
+ */
21
+ const discoverEntryPoints = async (srcDir) => {
22
+ const jsEntries = {};
23
+ const cssEntries = {};
24
+
25
+ for (const [dir, prefix] of [
26
+ ["layout", "layout"],
27
+ ["templates", "template"],
28
+ ]) {
29
+ const dirPath = path.join(srcDir, dir);
30
+ let files;
31
+ try {
32
+ files = await readdir(dirPath);
33
+ } catch {
34
+ continue;
35
+ }
36
+ for (const file of files) {
37
+ const ext = path.extname(file);
38
+ if (![".js", ".ts", ".css"].includes(ext)) continue;
39
+ const name = path.basename(file, ext);
40
+ const key = `${prefix}-${name}.bundle`;
41
+ const fullPath = path.join(dirPath, file);
42
+ if (ext === ".css") {
43
+ cssEntries[key] = fullPath;
44
+ } else {
45
+ jsEntries[key] = fullPath;
46
+ }
47
+ }
48
+ }
49
+
50
+ return { jsEntries, cssEntries };
51
+ };
52
+
53
+ /**
54
+ * Copy theme directories (layout/, templates/, snippets/, assets/) to dist/.
55
+ */
56
+ const copyThemeFiles = async (rootDir, distDir) => {
57
+ for (const dir of THEME_DIRS) {
58
+ const src = path.join(rootDir, dir);
59
+ const dest = path.join(distDir, dir);
60
+ try {
61
+ await stat(src);
62
+ } catch {
63
+ continue;
64
+ }
65
+ await cp(src, dest, { recursive: true });
66
+ }
67
+ };
68
+
69
+ /**
70
+ * Copy a single file from root to dist, preserving relative path.
71
+ */
72
+ const copySingleFile = async (rootDir, distDir, relativePath) => {
73
+ const src = path.join(rootDir, relativePath);
74
+ const dest = path.join(distDir, relativePath);
75
+ await mkdir(path.dirname(dest), { recursive: true });
76
+ await copyFile(src, dest);
77
+ };
78
+
79
+ /**
80
+ * Run a one-shot build or start watch mode.
81
+ * @param {{ watch?: boolean }} options
82
+ * @returns {Promise<{ ok: boolean, cleanup?: () => Promise<void> }>}
83
+ */
84
+ export const build = async ({ watch: watchMode = false } = {}) => {
85
+ const rootDir = process.cwd();
86
+ const srcDir = path.join(rootDir, "src");
87
+ const distDir = path.join(rootDir, "dist");
88
+
89
+ const themeConfig = await readThemeConfig();
90
+ if (!themeConfig) {
91
+ p.log.error("No tiendu.config.json found. This is not a built theme.");
92
+ return { ok: false };
93
+ }
94
+
95
+ // Clean and recreate dist
96
+ await rm(distDir, { recursive: true, force: true });
97
+ await mkdir(distDir, { recursive: true });
98
+
99
+ // Copy theme files first
100
+ await copyThemeFiles(rootDir, distDir);
101
+
102
+ // Discover entry points (JS and CSS separately to avoid key collisions)
103
+ const { jsEntries, cssEntries } = await discoverEntryPoints(srcDir);
104
+ const jsCount = Object.keys(jsEntries).length;
105
+ const cssCount = Object.keys(cssEntries).length;
106
+ const entryCount = jsCount + cssCount;
107
+
108
+ if (entryCount === 0) {
109
+ p.log.warn("No entry points found in src/layout or src/templates.");
110
+ return { ok: true };
111
+ }
112
+
113
+ const outdir = path.join(distDir, "assets");
114
+ const jsBuildOptions = jsCount > 0
115
+ ? { entryPoints: jsEntries, bundle: true, format: "esm", target: "es2020", outdir, logLevel: "warning", write: true }
116
+ : null;
117
+ const cssBuildOptions = cssCount > 0
118
+ ? { entryPoints: cssEntries, bundle: true, outdir, logLevel: "warning", write: true }
119
+ : null;
120
+
121
+ if (!watchMode) {
122
+ // One-shot build
123
+ try {
124
+ const builds = [];
125
+ if (jsBuildOptions) builds.push(esbuild.build(jsBuildOptions));
126
+ if (cssBuildOptions) builds.push(esbuild.build(cssBuildOptions));
127
+ await Promise.all(builds);
128
+ p.log.success(
129
+ `Built ${entryCount} entry point${entryCount === 1 ? "" : "s"} to dist/`,
130
+ );
131
+ return { ok: true };
132
+ } catch (error) {
133
+ p.log.error(`Build failed: ${error.message}`);
134
+ return { ok: false };
135
+ }
136
+ }
137
+
138
+ // Watch mode — create contexts for both JS and CSS
139
+ const contexts = [];
140
+ try {
141
+ if (jsBuildOptions) {
142
+ const jsCtx = await esbuild.context(jsBuildOptions);
143
+ await jsCtx.watch();
144
+ contexts.push(jsCtx);
145
+ }
146
+ if (cssBuildOptions) {
147
+ const cssCtx = await esbuild.context(cssBuildOptions);
148
+ await cssCtx.watch();
149
+ contexts.push(cssCtx);
150
+ }
151
+ } catch (error) {
152
+ p.log.error(`Build failed: ${error.message}`);
153
+ for (const ctx of contexts) await ctx.dispose();
154
+ return { ok: false };
155
+ }
156
+
157
+ p.log.success(
158
+ `Built ${entryCount} entry point${entryCount === 1 ? "" : "s"}. Watching for changes...`,
159
+ );
160
+
161
+ // Watch theme directories for Liquid/static asset changes and copy to dist
162
+ const themeWatchers = [];
163
+ const debounceMap = new Map();
164
+ const DEBOUNCE_MS = 200;
165
+
166
+ for (const dir of THEME_DIRS) {
167
+ const dirPath = path.join(rootDir, dir);
168
+ try {
169
+ await stat(dirPath);
170
+ } catch {
171
+ continue;
172
+ }
173
+
174
+ const watcher = watch(dirPath, { recursive: true }, (eventType, filename) => {
175
+ if (!filename) return;
176
+ const relativePath = path.join(dir, filename);
177
+
178
+ const existing = debounceMap.get(relativePath);
179
+ if (existing) clearTimeout(existing);
180
+
181
+ const timer = setTimeout(async () => {
182
+ debounceMap.delete(relativePath);
183
+ try {
184
+ const fileStat = await stat(path.join(rootDir, relativePath)).catch(
185
+ () => null,
186
+ );
187
+ if (fileStat && fileStat.isFile()) {
188
+ await copySingleFile(rootDir, distDir, relativePath);
189
+ console.log(`⟳ ${relativePath}`);
190
+ } else if (!fileStat) {
191
+ // File deleted — remove from dist
192
+ const dest = path.join(distDir, relativePath);
193
+ await rm(dest, { force: true });
194
+ console.log(`✕ ${relativePath}`);
195
+ }
196
+ } catch (error) {
197
+ p.log.warn(`Error copying ${relativePath}: ${error.message}`);
198
+ }
199
+ }, DEBOUNCE_MS);
200
+
201
+ debounceMap.set(relativePath, timer);
202
+ });
203
+
204
+ themeWatchers.push(watcher);
205
+ }
206
+
207
+ const cleanup = async () => {
208
+ for (const w of themeWatchers) w.close();
209
+ for (const timer of debounceMap.values()) clearTimeout(timer);
210
+ for (const ctx of contexts) await ctx.dispose();
211
+ };
212
+
213
+ return { ok: true, cleanup };
214
+ };
package/lib/config.mjs CHANGED
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  const CONFIG_DIR = ".cli";
5
5
  const CONFIG_FILE = "config.json";
6
6
  const CREDENTIALS_FILE = "credentials.json";
7
+ const THEME_CONFIG_FILE = "tiendu.config.json";
7
8
 
8
9
  /**
9
10
  * @typedef {{ storeId: number, apiBaseUrl: string, previewKey?: string }} TienduConfig
@@ -54,6 +55,25 @@ export const writeCredentials = async (credentials) => {
54
55
  );
55
56
  };
56
57
 
58
+ /** @returns {Promise<object | null>} */
59
+ export const readThemeConfig = async () => {
60
+ try {
61
+ const raw = await readFile(
62
+ path.resolve(process.cwd(), THEME_CONFIG_FILE),
63
+ "utf-8",
64
+ );
65
+ return JSON.parse(raw);
66
+ } catch {
67
+ return null;
68
+ }
69
+ };
70
+
71
+ /** @returns {Promise<boolean>} */
72
+ export const isBuiltTheme = async () => (await readThemeConfig()) !== null;
73
+
74
+ /** @returns {string} */
75
+ export const getDistDir = () => path.resolve(process.cwd(), "dist");
76
+
57
77
  /**
58
78
  * @returns {Promise<{ config: TienduConfig, credentials: TienduCredentials }>}
59
79
  */
package/lib/dev.mjs CHANGED
@@ -3,7 +3,7 @@ import { readFile, readdir, stat } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import * as p from "@clack/prompts";
5
5
  import { zipSync } from "fflate";
6
- import { loadConfigOrFail, writeConfig } from "./config.mjs";
6
+ import { loadConfigOrFail, writeConfig, isBuiltTheme, getDistDir } from "./config.mjs";
7
7
  import {
8
8
  createPreview,
9
9
  listPreviews,
@@ -14,6 +14,7 @@ import {
14
14
  uploadPreviewFileMultipart,
15
15
  uploadPreviewZip,
16
16
  } from "./api.mjs";
17
+ import { build } from "./build.mjs";
17
18
 
18
19
  const isDotfile = (name) => name.startsWith(".");
19
20
 
@@ -56,7 +57,19 @@ export const dev = async () => {
56
57
  const { config, credentials } = await loadConfigOrFail();
57
58
  const { apiBaseUrl, storeId } = config;
58
59
  const { apiKey } = credentials;
59
- const rootDir = process.cwd();
60
+ const builtTheme = await isBuiltTheme();
61
+ const rootDir = builtTheme ? getDistDir() : process.cwd();
62
+ let buildCleanup = null;
63
+
64
+ // For built themes, run the build first (with watch mode)
65
+ if (builtTheme) {
66
+ const buildResult = await build({ watch: true });
67
+ if (!buildResult.ok) {
68
+ p.log.error("Initial build failed. Fix errors and try again.");
69
+ process.exit(1);
70
+ }
71
+ buildCleanup = buildResult.cleanup;
72
+ }
60
73
 
61
74
  const existingPreviewsResult = await listPreviews(
62
75
  apiBaseUrl,
@@ -138,7 +151,7 @@ export const dev = async () => {
138
151
  spinner.stop(`Preview: ${previewUrl}`);
139
152
  }
140
153
 
141
- p.log.message("Watching for changes — press Ctrl+C to stop.\n");
154
+ p.log.message("Watching for changes — press Ctrl+C to stop.");
142
155
 
143
156
  // ── File watcher ──────────────────────────────────────────────────────────
144
157
  /** @type {Map<string, NodeJS.Timeout>} */
@@ -162,7 +175,7 @@ export const dev = async () => {
162
175
 
163
176
  if (!fileStat || !fileStat.isFile()) {
164
177
  if (!fileStat) {
165
- p.log.message(` ✕ ${relativePath}`);
178
+ console.log(`✕ ${relativePath}`);
166
179
  const result = await deletePreviewFile(
167
180
  apiBaseUrl,
168
181
  apiKey,
@@ -177,7 +190,7 @@ export const dev = async () => {
177
190
  return;
178
191
  }
179
192
 
180
- p.log.message(` ↑ ${relativePath}`);
193
+ console.log(`↑ ${relativePath}`);
181
194
  const content = await readFile(absolutePath);
182
195
  const result = await uploadPreviewFileMultipart(
183
196
  apiBaseUrl,
@@ -199,9 +212,10 @@ export const dev = async () => {
199
212
  debounceMap.set(relativePath, timer);
200
213
  });
201
214
 
202
- const cleanup = () => {
215
+ const cleanup = async () => {
203
216
  watcher.close();
204
217
  for (const timer of debounceMap.values()) clearTimeout(timer);
218
+ if (buildCleanup) await buildCleanup();
205
219
  p.outro("Dev mode stopped.");
206
220
  process.exit(0);
207
221
  };
package/lib/preview.mjs CHANGED
@@ -245,9 +245,6 @@ const formatRelativeDate = (value) => {
245
245
  export const previewShow = async () => {
246
246
  const { config, credentials } = await loadConfigOrFail();
247
247
 
248
- const spinner = p.spinner();
249
- spinner.start("Fetching preview details...");
250
-
251
248
  const result = await listPreviews(
252
249
  config.apiBaseUrl,
253
250
  credentials.apiKey,
@@ -255,14 +252,12 @@ export const previewShow = async () => {
255
252
  );
256
253
 
257
254
  if (!result.ok) {
258
- spinner.stop("Failed to fetch previews.", 1);
259
255
  p.log.error(result.error);
260
256
  process.exit(1);
261
257
  }
262
258
 
263
259
  const preview = resolveActivePreview(result.data, config.previewKey);
264
260
  if (!preview) {
265
- spinner.stop("Could not determine the active preview.", 1);
266
261
  p.log.error(
267
262
  result.data.length === 0
268
263
  ? "No previews found for this store."
@@ -272,7 +267,6 @@ export const previewShow = async () => {
272
267
  }
273
268
 
274
269
  const url = buildPreviewUrl(config.apiBaseUrl, preview.previewHostname);
275
- spinner.stop("Preview details loaded.");
276
270
 
277
271
  p.note(
278
272
  [
package/lib/pull.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as p from "@clack/prompts";
2
- import { loadConfigOrFail } from "./config.mjs";
2
+ import { loadConfigOrFail, isBuiltTheme, getDistDir } from "./config.mjs";
3
3
  import { downloadStorefrontArchive } from "./api.mjs";
4
4
  import { extractZip } from "./zip.mjs";
5
5
 
@@ -32,7 +32,7 @@ export const pull = async () => {
32
32
  `Archive received (${formatBytes(result.data.length)}). Extracting...`,
33
33
  );
34
34
 
35
- const outputDir = process.cwd();
35
+ const outputDir = (await isBuiltTheme()) ? getDistDir() : process.cwd();
36
36
  const extractedFiles = await extractZip(result.data, outputDir);
37
37
 
38
38
  p.log.success(
package/lib/push.mjs CHANGED
@@ -2,7 +2,7 @@ import { readdir, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import * as p from "@clack/prompts";
4
4
  import { zipSync } from "fflate";
5
- import { loadConfigOrFail } from "./config.mjs";
5
+ import { loadConfigOrFail, isBuiltTheme, getDistDir } from "./config.mjs";
6
6
  import { uploadPreviewZip } from "./api.mjs";
7
7
 
8
8
  /** @param {number} bytes */
@@ -59,7 +59,7 @@ export const push = async () => {
59
59
  process.exit(1);
60
60
  }
61
61
 
62
- const rootDir = process.cwd();
62
+ const rootDir = (await isBuiltTheme()) ? getDistDir() : process.cwd();
63
63
  const spinner = p.spinner();
64
64
  spinner.start("Packing files...");
65
65
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiendu",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "CLI para desarrollar y publicar temas en Tiendu",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,6 +37,7 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@clack/prompts": "^1.1.0",
40
+ "esbuild": "^0.24.0",
40
41
  "fflate": "^0.8.2"
41
42
  }
42
43
  }