tiendu 0.2.3 → 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 +107 -20
- package/bin/tiendu.js +9 -0
- package/lib/build.mjs +214 -0
- package/lib/config.mjs +20 -0
- package/lib/dev.mjs +17 -3
- package/lib/pull.mjs +2 -2
- package/lib/push.mjs +2 -2
- package/package.json +2 -1
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
├── .
|
|
183
|
-
├──
|
|
184
|
-
├──
|
|
185
|
-
├──
|
|
186
|
-
├──
|
|
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
|
|
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,
|
|
@@ -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/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.
|
|
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
|
}
|