tiendu 0.6.0 → 0.7.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,16 +24,18 @@ npm install -g tiendu
24
24
 
25
25
  ## Quick start
26
26
 
27
- ### Buildless theme (simple)
27
+ ### Simple theme
28
28
 
29
29
  ```bash
30
30
  mkdir my-theme && cd my-theme
31
31
  tiendu init
32
+ tiendu stores list
33
+ tiendu stores set <store-id>
32
34
  tiendu pull
33
35
  tiendu dev
34
36
  ```
35
37
 
36
- ### Built theme (TypeScript, npm packages, bundling)
38
+ ### Pipeline-enabled theme
37
39
 
38
40
  Clone the default theme template, connect to your store, and start developing:
39
41
 
@@ -41,10 +43,22 @@ Clone the default theme template, connect to your store, and start developing:
41
43
  git clone <default-theme-repo> my-theme && cd my-theme
42
44
  npm install
43
45
  tiendu init
46
+ tiendu stores list
47
+ tiendu stores set <store-id>
44
48
  tiendu dev
45
49
  ```
46
50
 
47
- `tiendu dev` creates a remote preview, builds your source files, runs an initial push from the prepared output, and then watches for changes. It prints a local live-preview URL first, plus a sharable preview URL like:
51
+ ### Agent-friendly setup
52
+
53
+ ```bash
54
+ tiendu init <api-key> [base-url] --non-interactive
55
+ tiendu stores list --non-interactive
56
+ tiendu stores set <store-id> --non-interactive
57
+ ```
58
+
59
+ When `--non-interactive` is passed, the CLI avoids prompts and prints plain text output.
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:
48
62
 
49
63
  ```
50
64
  http://preview-xxxxxxxxxxxx.tiendu.uy/
@@ -59,24 +73,54 @@ It also starts a local live-preview URL that proxies the preview and auto-reload
59
73
 
60
74
  ## Commands
61
75
 
62
- ### `tiendu init`
76
+ ### `tiendu init [apiKey] [baseUrl]`
77
+
78
+ Initializes a theme project in the current directory.
63
79
 
64
- Initializes a theme project in the current directory. Prompts for your API key, API base URL (defaults to `https://tiendu.uy`), and store ID. Saves configuration to `.cli/`.
80
+ - With no arguments, it runs the interactive setup wizard.
81
+ - With `apiKey` and optional `baseUrl`, it reinitializes the saved config without prompts.
82
+ - If only one store is available, it is selected automatically.
83
+ - If multiple stores are available, leave the store unset and use `tiendu stores list` plus `tiendu stores set <id>`.
65
84
 
66
85
  ```bash
67
86
  tiendu init
87
+ tiendu init <api-key>
88
+ tiendu init <api-key> https://tiendu.uy --non-interactive
68
89
  ```
69
90
 
70
91
  > Add `.cli/` to your `.gitignore` if you version-control your theme — it contains your API key.
71
92
 
72
93
  ---
73
94
 
95
+ ### `tiendu stores list`
96
+
97
+ Lists all stores available for the configured API key and highlights the active one when present.
98
+
99
+ ```bash
100
+ tiendu stores list
101
+ tiendu stores list --non-interactive
102
+ ```
103
+
104
+ ---
105
+
106
+ ### `tiendu stores set <storeId>`
107
+
108
+ Validates the store against the configured API key and saves it as the active store.
109
+
110
+ ```bash
111
+ tiendu stores set 123
112
+ tiendu stores set 123 --non-interactive
113
+ ```
114
+
115
+ ---
116
+
74
117
  ### `tiendu pull`
75
118
 
76
- Downloads the current live theme from your store as local files.
119
+ Downloads the current live theme from your store into `dist/`.
77
120
 
78
- - **Buildless themes:** extracts to the current directory.
79
- - **Built themes** (with `tiendu.config.json`): extracts to `dist/`.
121
+ - `pull` clears `dist/` first.
122
+ - The downloaded archive is then extracted into `dist/`.
123
+ - Your source files, including `src/`, are left untouched.
80
124
 
81
125
  ```bash
82
126
  tiendu pull
@@ -86,7 +130,11 @@ tiendu pull
86
130
 
87
131
  ### `tiendu build`
88
132
 
89
- Builds a theme into its deployable output directory (`dist/`). Only available for built themes (requires `tiendu.config.json`).
133
+ Builds or stages the current theme into its deployable output directory (`dist/`).
134
+
135
+ - Theme files and assets are always prepared into `dist/`.
136
+ - Optional pipeline steps are enabled through `tiendu.config.json`.
137
+ - With no config file, or with no enabled pipeline steps, `build` just stages the theme files into `dist/`.
90
138
 
91
139
  ```bash
92
140
  tiendu build
@@ -94,11 +142,11 @@ tiendu build
94
142
 
95
143
  The build:
96
144
 
97
- 1. Copies theme files from `src/layout/`, `src/templates/`, and `src/snippets/` to `dist/`
145
+ 1. Copies theme files from `src/layout/`, `src/templates/`, `src/sections/`, `src/blocks/`, `src/snippets/`, and `src/config/` to `dist/`
98
146
  2. Flattens static files from `src/assets/` into `dist/assets/`
99
- 3. Discovers entry points in `src/layout/` and `src/templates/`
100
- 4. Bundles JS/TS and CSS into `dist/assets/`
101
- 5. Runs project PostCSS plugins for CSS entries when available (for example Tailwind v4)
147
+ 3. Optionally discovers script and style entry points in `src/layout/` and `src/templates/`
148
+ 4. Optionally compiles JS/TS and CSS into `dist/assets/`
149
+ 5. Optionally runs project PostCSS plugins for compiled CSS entries
102
150
 
103
151
  For TypeScript source, extensionless relative imports such as `import { initHeaderCart } from '../lib/scripts/cart'` are supported and recommended.
104
152
 
@@ -114,8 +162,8 @@ Entry naming convention:
114
162
 
115
163
  The main development command.
116
164
 
117
- - **Buildless themes:** watches the current directory and syncs file changes to the preview.
118
- - **Built themes:** runs `tiendu build` in watch mode first, then watches `dist/` and syncs changes to the preview.
165
+ - Runs `tiendu build` in watch mode first.
166
+ - Watches `dist/` and syncs changes to the preview.
119
167
 
120
168
  ```bash
121
169
  tiendu dev
@@ -133,33 +181,33 @@ tiendu dev
133
181
 
134
182
  ### `tiendu push`
135
183
 
136
- Zips and uploads files to the active preview, replacing its content entirely.
184
+ Zips and uploads `dist/` to the active preview, replacing its content entirely.
137
185
 
138
- - **Buildless themes:** uploads from the current directory.
139
- - **Built themes:** runs `tiendu build` first, then uploads from `dist/`.
186
+ - By default it runs `tiendu build` first.
187
+ - Use `--skip-build` to upload the existing `dist/` artifact without rebuilding.
140
188
 
141
189
  ```bash
142
190
  tiendu push
143
191
  tiendu push --skip-build
192
+ tiendu push --skip-build --non-interactive
144
193
  ```
145
194
 
146
- Use `--skip-build` to upload the existing `dist/` output without rebuilding.
147
-
148
195
  ---
149
196
 
150
197
  ### `tiendu publish`
151
198
 
152
199
  Publishes the active preview to the live storefront. Visitors will see the new theme immediately. All previews for the store are removed after publishing.
153
200
 
154
- - **Buildless themes:** publishes the active preview as-is.
155
- - **Built themes:** builds the theme, uploads the latest `dist/` output to the preview, then publishes it.
201
+ - By default it runs `tiendu build`, uploads `dist/` to the preview, and then publishes it.
202
+ - Use `--skip-build` to publish after syncing the existing `dist/` output.
156
203
 
157
204
  ```bash
158
205
  tiendu publish
159
206
  tiendu publish --skip-build
207
+ tiendu publish --skip-build --non-interactive
160
208
  ```
161
209
 
162
- Use `--skip-build` to publish after uploading the existing `dist/` output without rebuilding.
210
+ In non-interactive mode, the publish confirmation is skipped.
163
211
 
164
212
  ---
165
213
 
@@ -211,6 +259,7 @@ Deletes the active preview (both remotely and from your local config).
211
259
 
212
260
  ```bash
213
261
  tiendu preview delete
262
+ tiendu preview delete --non-interactive
214
263
  ```
215
264
 
216
265
  ---
@@ -227,23 +276,27 @@ tiendu preview open
227
276
 
228
277
  ## Typical workflow
229
278
 
230
- ### Buildless
279
+ ### Standard
231
280
 
232
281
  ```
233
- tiendu init # one time: connect to your store
234
- tiendu pull # one time: download the live theme
282
+ tiendu init # one time: connect to your Tiendu account
283
+ tiendu stores list # one time: see available stores
284
+ tiendu stores set # one time: select the store to work on
285
+ tiendu pull # one time: refresh dist/ from the live theme
235
286
 
236
- tiendu dev # develop: edit locally, see changes live at the preview URL
287
+ tiendu dev # develop: build/stage into dist/, sync preview updates live
237
288
 
238
289
  tiendu publish # when ready: push to the live storefront
239
290
  ```
240
291
 
241
- ### Built theme
292
+ ### Pipeline-enabled
242
293
 
243
294
  ```
244
295
  git clone <template-repo> my-theme
245
296
  cd my-theme && npm install
246
- tiendu init # one time: connect to your store
297
+ tiendu init # one time: connect to your Tiendu account
298
+ tiendu stores list # one time: see available stores
299
+ tiendu stores set # one time: select the store to work on
247
300
 
248
301
  tiendu dev # develop: builds src/, watches dist/, syncs to preview
249
302
 
@@ -264,9 +317,13 @@ A **theme preview** is a remote copy of your theme hosted by Tiendu. It renders
264
317
 
265
318
  ---
266
319
 
267
- ## Built themes
320
+ ## Pipeline-enabled themes
321
+
322
+ All themes are staged into `dist/` before upload.
268
323
 
269
- A **built theme** is a theme that uses `tiendu.config.json` to enable the build pipeline. It allows:
324
+ `tiendu.config.json` can optionally enable extra pipeline steps such as script compilation, style compilation, and PostCSS processing.
325
+
326
+ When pipeline steps are enabled, a theme can use:
270
327
 
271
328
  - npm packages via a local `package.json`
272
329
  - TypeScript (`.ts`) for browser code
@@ -277,7 +334,7 @@ A **built theme** is a theme that uses `tiendu.config.json` to enable the build
277
334
 
278
335
  ```
279
336
  my-theme/
280
- ├── tiendu.config.json # marks this as a built theme
337
+ ├── tiendu.config.json # optional pipeline flags
281
338
  ├── package.json # npm dependencies
282
339
  ├── .gitignore
283
340
  ├── src/
@@ -293,21 +350,21 @@ my-theme/
293
350
  │ ├── assets/ # source assets → flattened into dist/assets/
294
351
  │ ├── lib/ # shared modules (bundled into entries, not served)
295
352
  │ └── css/ # shared CSS (imported by entry CSS)
296
- └── dist/ # build output (gitignored, uploaded to Tiendu)
353
+ └── dist/ # staged upload artifact (gitignored, uploaded to Tiendu)
297
354
  ```
298
355
 
299
356
  ### How it works
300
357
 
301
- 1. Source assets in `src/assets/` are flattened into `dist/assets/` (`payment-methods/visa.svg` becomes `payment-methods___visa.svg`)
302
- 2. Source JS/TS/CSS in `src/` is bundled by esbuild into `dist/assets/`
303
- 3. CSS entries also run through your local PostCSS pipeline when configured
304
- 4. Liquid files are copied from `src/` to `dist/`
358
+ 1. Theme files and static assets are staged into `dist/`
359
+ 2. Script entries are compiled only when `pipeline.compileScripts` is enabled
360
+ 3. Style entries are compiled only when `pipeline.compileStyles` is enabled
361
+ 4. PostCSS runs only when `pipeline.postcss` is enabled
305
362
  5. `dist/` is what gets uploaded — it looks like a normal Tiendu theme
306
- 6. Liquid templates reference bundles and assets via `asset_url` (e.g. `{{ 'layout-theme.bundle.js' | asset_url | script_tag }}` or `{{ 'payment-methods/visa.svg' | asset_url }}`)
363
+ 6. Liquid templates reference bundles and assets via `asset_url` when compiled entries are used
307
364
 
308
365
  ### Tailwind v4
309
366
 
310
- Built themes can use Tailwind v4 in CSS entry files.
367
+ Pipeline-enabled themes can use Tailwind v4 in CSS entry files when `pipeline.compileStyles` and `pipeline.postcss` are enabled.
311
368
 
312
369
  Install it in your theme project:
313
370
 
@@ -338,15 +395,22 @@ export default {
338
395
 
339
396
  ### tiendu.config.json
340
397
 
341
- Minimal config the build conventions are hardcoded:
398
+ Config is optional. When present, you can enable pipeline steps explicitly:
342
399
 
343
400
  ```json
344
401
  {
345
- "name": "my-theme",
346
- "version": "1.0.0"
402
+ "pipeline": {
403
+ "compileScripts": true,
404
+ "compileStyles": true,
405
+ "postcss": true
406
+ }
347
407
  }
348
408
  ```
349
409
 
410
+ Without enabled pipeline steps, the CLI still stages the theme into `dist/`, but it skips compilation and PostCSS.
411
+
412
+ With no `tiendu.config.json`, the behavior is the same as having all pipeline steps disabled.
413
+
350
414
  ---
351
415
 
352
416
  ## License
package/bin/tiendu.js CHANGED
@@ -15,80 +15,138 @@ import {
15
15
  previewAttach,
16
16
  previewDetach,
17
17
  } from "../lib/preview.mjs";
18
+ import { storesList, storesSet } from "../lib/stores.mjs";
18
19
  import {
19
20
  checkForUpdates,
20
21
  checkForUpdatesNow,
21
22
  getCurrentVersion,
22
23
  } from "../lib/update-check.mjs";
24
+ import { configureUi } from "../lib/ui.mjs";
23
25
 
24
26
  const HELP = `
25
27
  tiendu — Tiendu theme development CLI
26
28
 
27
29
  Usage:
28
- tiendu init [dir] Set up a theme project (optionally in a new directory)
29
- tiendu pull [previewKey] Download the live theme, or a specific preview's files
30
- tiendu build Build a theme (requires tiendu.config.json)
30
+ tiendu init [apiKey] [baseUrl] [--dir <path>]
31
+ Initialize interactively, or reset config with direct credentials
32
+ tiendu stores list List stores available for the configured API key
33
+ 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/
31
36
  tiendu push [previewKey] [--skip-build]
32
- Upload files to the attached or specified preview
33
- tiendu dev Start dev mode: auto-sync changes to a live preview URL
37
+ Upload dist/ to the attached or specified preview
38
+ tiendu dev Start dev mode: auto-sync changes to a live preview URL
34
39
  tiendu publish [previewKey] [--skip-build]
35
- Publish the attached or specified preview to the live storefront
40
+ Build/sync dist/ and publish the preview live
36
41
 
37
- tiendu preview Show the attached preview details
42
+ tiendu preview Show the attached preview details
38
43
  tiendu preview create [name]
39
- Create a new preview (and attach to it)
40
- tiendu preview list List all previews for your store
41
- tiendu preview attach [key]
42
- Attach to an existing preview by its key
43
- tiendu preview detach Detach from the current preview (without deleting it)
44
- tiendu preview delete [key]
45
- Delete a preview (defaults to the attached one)
46
- tiendu preview open Open the attached preview URL in your browser
44
+ Create a new preview (and attach to it)
45
+ tiendu preview list List all previews for your store
46
+ tiendu preview attach [key] Attach to an existing preview by its key
47
+ tiendu preview detach Detach from the current preview (without deleting it)
48
+ tiendu preview delete [key] Delete a preview (defaults to the attached one)
49
+ tiendu preview open Open the attached preview URL in your browser
47
50
 
48
- tiendu check-updates Check npm for a newer CLI version
49
- tiendu version Show the current CLI version
51
+ tiendu check-updates Check npm for a newer CLI version
52
+ tiendu version Show the current CLI version
50
53
 
51
- tiendu --help, -h Show this help message
52
- tiendu --version, -v Show the current CLI version
54
+ Global options:
55
+ --non-interactive Disable prompts, print plain text output, and skip confirmations
56
+ --dir <path> Create the project inside a new directory during init
57
+ --skip-build Reuse the existing dist/ output for push or publish
58
+ --help, -h Show this help message
59
+ --version, -v Show the current CLI version
60
+
61
+ Init behavior:
62
+ tiendu init Interactive setup wizard
63
+ tiendu init <apiKey> Reset saved config and connect using the default base URL
64
+ tiendu init <apiKey> <url> Reset saved config and connect using a custom base URL
65
+ The default base URL points to the Tiendu platform and rarely needs to change.
66
+ If exactly one store is available, it is selected automatically.
67
+ If multiple stores are available, run tiendu stores list and tiendu stores set <id>.
68
+
69
+ Agent-friendly setup:
70
+ tiendu init <apiKey> [baseUrl] --non-interactive
71
+ tiendu stores list --non-interactive
72
+ tiendu stores set <id> --non-interactive
73
+ tiendu pull --non-interactive
74
+ tiendu push --non-interactive
75
+ tiendu publish --non-interactive
76
+
77
+ Push and pull behavior:
78
+ build always prepares dist/ as the local deploy artifact.
79
+ push sends a zip of dist/ to the target preview.
80
+ pull resets dist/ and extracts the downloaded theme there.
81
+ pull does not delete src/ files.
82
+
83
+ Pipeline behavior:
84
+ tiendu.config.json can enable optional pipeline steps.
85
+ pipeline.compileScripts enables JS/TS entry compilation.
86
+ pipeline.compileStyles enables CSS entry compilation.
87
+ pipeline.postcss enables PostCSS for compiled style entries.
88
+ With no config file, or with no enabled pipeline steps, build just stages theme files into dist/.
53
89
 
54
90
  Typical workflow:
55
- tiendu init my-store Set up a new project in ./my-store
56
- cd my-store
57
- tiendu pull Download the current live theme
58
- tiendu build Build the theme (for themes with tiendu.config.json)
59
- tiendu dev Edit locally — preview updates in real time
60
- tiendu publish Ship to the live storefront when ready
91
+ tiendu init Connect to Tiendu and save your credentials
92
+ tiendu stores list See available stores
93
+ tiendu stores set <id> Select the store you want to work on
94
+ tiendu pull Refresh dist/ from the current live theme
95
+ tiendu dev Edit locally — preview updates in real time
96
+ tiendu publish Ship to the live storefront when ready
61
97
  `;
62
98
 
63
- /**
64
- * Extract the first positional argument that is not a flag (--skip-build, etc.).
65
- * @param {string[]} args - CLI args after the command name
66
- * @returns {string | undefined}
67
- */
68
- const extractPositionalArg = (args) =>
69
- args.find((arg) => !arg.startsWith("--"));
99
+ const parseArgv = (argv) => {
100
+ const flags = new Set();
101
+ const values = new Map();
102
+ const positionals = [];
103
+
104
+ for (let index = 0; index < argv.length; index += 1) {
105
+ const arg = argv[index];
106
+
107
+ if (!arg.startsWith("--")) {
108
+ positionals.push(arg);
109
+ continue;
110
+ }
111
+
112
+ if (arg === "--dir") {
113
+ const value = argv[index + 1];
114
+ if (!value || value.startsWith("--")) {
115
+ console.error("Missing value for --dir.");
116
+ process.exit(1);
117
+ }
118
+ values.set("dir", value);
119
+ index += 1;
120
+ continue;
121
+ }
122
+
123
+ flags.add(arg);
124
+ }
125
+
126
+ return { flags, values, positionals };
127
+ };
70
128
 
71
129
  const main = async () => {
72
- const args = process.argv.slice(2);
73
- const command = args[0];
74
- const subcommand = args[1];
75
- const restArgs = args.slice(1);
76
- const skipBuild = args.includes("--skip-build");
130
+ const argv = process.argv.slice(2);
131
+ const { flags, values, positionals } = parseArgv(argv);
132
+ const command = positionals[0];
133
+ const subcommand = positionals[1];
134
+ const skipBuild = flags.has("--skip-build");
135
+ const nonInteractive =
136
+ flags.has("--non-interactive") || !process.stdin.isTTY || !process.stdout.isTTY;
137
+
138
+ configureUi({ nonInteractive });
77
139
 
78
140
  if (
79
141
  command === "version" ||
80
- command === "--version" ||
81
- command === "-v"
142
+ argv.includes("--version") ||
143
+ argv.includes("-v")
82
144
  ) {
83
145
  console.log(getCurrentVersion());
84
146
  process.exit(0);
85
147
  }
86
148
 
87
- if (
88
- !command ||
89
- command === "--help" ||
90
- command === "-h"
91
- ) {
149
+ if (!command || argv.includes("--help") || argv.includes("-h")) {
92
150
  console.log(HELP.trim());
93
151
  process.exit(0);
94
152
  }
@@ -98,17 +156,36 @@ const main = async () => {
98
156
  return;
99
157
  }
100
158
 
101
- // Check for updates at most once per day (non-blocking)
102
159
  await checkForUpdates();
103
160
 
104
161
  if (command === "init") {
105
- await init(args[1]); // optional directory name
162
+ const initArgs = positionals.slice(1);
163
+ await init({
164
+ dirArg: values.get("dir"),
165
+ apiKeyArg: initArgs[0],
166
+ baseUrlArg: initArgs[1],
167
+ });
106
168
  return;
107
169
  }
108
170
 
171
+ if (command === "stores") {
172
+ if (subcommand === "list") {
173
+ await storesList();
174
+ return;
175
+ }
176
+
177
+ if (subcommand === "set") {
178
+ await storesSet(positionals[2]);
179
+ return;
180
+ }
181
+
182
+ console.error(`Unknown subcommand: stores ${subcommand ?? "(none)"}`);
183
+ console.log(HELP.trim());
184
+ process.exit(1);
185
+ }
186
+
109
187
  if (command === "pull") {
110
- const previewKey = extractPositionalArg(restArgs);
111
- await pull({ previewKey });
188
+ await pull({ previewKey: positionals[1] });
112
189
  return;
113
190
  }
114
191
 
@@ -119,8 +196,7 @@ const main = async () => {
119
196
  }
120
197
 
121
198
  if (command === "push") {
122
- const previewKey = extractPositionalArg(restArgs);
123
- await push({ skipBuild, previewKey });
199
+ await push({ skipBuild, previewKey: positionals[1] });
124
200
  return;
125
201
  }
126
202
 
@@ -130,8 +206,7 @@ const main = async () => {
130
206
  }
131
207
 
132
208
  if (command === "publish") {
133
- const previewKey = extractPositionalArg(restArgs);
134
- await publish({ skipBuild, previewKey });
209
+ await publish({ skipBuild, previewKey: positionals[1] });
135
210
  return;
136
211
  }
137
212
 
@@ -141,7 +216,7 @@ const main = async () => {
141
216
  return;
142
217
  }
143
218
  if (subcommand === "create") {
144
- await previewCreate(args[2]);
219
+ await previewCreate(positionals[2]);
145
220
  return;
146
221
  }
147
222
  if (subcommand === "list") {
@@ -149,7 +224,7 @@ const main = async () => {
149
224
  return;
150
225
  }
151
226
  if (subcommand === "attach") {
152
- await previewAttach(args[2]);
227
+ await previewAttach(positionals[2]);
153
228
  return;
154
229
  }
155
230
  if (subcommand === "detach") {
@@ -157,7 +232,7 @@ const main = async () => {
157
232
  return;
158
233
  }
159
234
  if (subcommand === "delete") {
160
- await previewDelete(args[2]);
235
+ await previewDelete(positionals[2]);
161
236
  return;
162
237
  }
163
238
  if (subcommand === "open") {