@fluid-app/fluid-cli-theme-dev 0.1.2 → 0.1.4
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/.turbo/turbo-build.log +7 -9
- package/README.md +100 -0
- package/dist/index.mjs +133 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
- package/src/commands/dev.ts +32 -26
- package/src/commands/navigate.ts +7 -7
- package/src/commands/pull.ts +9 -13
- package/src/commands/push.ts +12 -19
- package/src/theme/root.ts +5 -2
- package/src/theme/syncer.ts +23 -28
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @fluid-app/fluid-cli-theme-dev@0.1.
|
|
2
|
+
> @fluid-app/fluid-cli-theme-dev@0.1.4 build /home/runner/_work/fluid-mono/fluid-mono/packages/cli/theme-dev
|
|
3
3
|
> tsdown
|
|
4
4
|
|
|
5
5
|
[34mℹ[39m tsdown [2mv0.21.0[22m powered by rolldown [2mv1.0.0-rc.7[22m
|
|
@@ -8,11 +8,9 @@
|
|
|
8
8
|
[34mℹ[39m target: [34mnode18[39m
|
|
9
9
|
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
10
10
|
[34mℹ[39m Build start
|
|
11
|
-
[34mℹ[39m [2mdist/[22m[1mindex.mjs[22m [
|
|
12
|
-
[34mℹ[39m [2mdist/[22mindex.mjs.map [
|
|
13
|
-
[34mℹ[39m [2mdist/[22mindex.d.mts.map [2m
|
|
14
|
-
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [2m
|
|
15
|
-
[34mℹ[39m 4 files, total:
|
|
16
|
-
[
|
|
17
|
-
|
|
18
|
-
[32m✔[39m Build complete in [32m4554ms[39m
|
|
11
|
+
[34mℹ[39m [2mdist/[22m[1mindex.mjs[22m [2m 41.47 kB[22m [2m│ gzip: 11.70 kB[22m
|
|
12
|
+
[34mℹ[39m [2mdist/[22mindex.mjs.map [2m110.82 kB[22m [2m│ gzip: 24.52 kB[22m
|
|
13
|
+
[34mℹ[39m [2mdist/[22mindex.d.mts.map [2m 0.11 kB[22m [2m│ gzip: 0.12 kB[22m
|
|
14
|
+
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [2m 0.19 kB[22m [2m│ gzip: 0.16 kB[22m
|
|
15
|
+
[34mℹ[39m 4 files, total: 152.60 kB
|
|
16
|
+
[32m✔[39m Build complete in [32m1219ms[39m
|
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# @fluid-app/fluid-cli-theme-dev
|
|
2
|
+
|
|
3
|
+
Fluid CLI plugin for theme development. Adds `fluid theme` commands for local dev server, push, pull, and scaffolding.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @fluid-app/fluid-cli @fluid-app/fluid-cli-theme-dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires `@fluid-app/fluid-cli` as the core CLI.
|
|
12
|
+
|
|
13
|
+
## Authentication
|
|
14
|
+
|
|
15
|
+
Log in before using any theme commands:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
fluid login
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Commands
|
|
22
|
+
|
|
23
|
+
### `fluid theme dev`
|
|
24
|
+
|
|
25
|
+
Start a local dev server that proxies your storefront with hot reload:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
fluid theme dev
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The dev server will:
|
|
32
|
+
|
|
33
|
+
1. Create (or reuse) a development theme
|
|
34
|
+
2. Upload all local files to the dev theme
|
|
35
|
+
3. Watch for file changes and sync them automatically
|
|
36
|
+
4. Proxy requests to `{company}.fluid.app` with local file overrides
|
|
37
|
+
|
|
38
|
+
| Flag | Default | Description |
|
|
39
|
+
| -------------------------- | ----------- | ----------------------------------------- |
|
|
40
|
+
| `--host <host>` | `127.0.0.1` | Local server host |
|
|
41
|
+
| `--port <port>` | `9292` | Local server port |
|
|
42
|
+
| `-t, --theme <name-or-id>` | auto | Use a specific theme instead of dev theme |
|
|
43
|
+
| `--live-reload <mode>` | `full-page` | Reload mode: `full-page` or `off` |
|
|
44
|
+
| `--navigate` | off | Open browser navigator after start |
|
|
45
|
+
| `--root <path>` | `.` | Theme root directory |
|
|
46
|
+
|
|
47
|
+
### `fluid theme push`
|
|
48
|
+
|
|
49
|
+
Upload local theme files to a remote theme:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
fluid theme push # Interactive theme selection
|
|
53
|
+
fluid theme push --theme "My Theme" # By name
|
|
54
|
+
fluid theme push --theme 42 # By ID
|
|
55
|
+
fluid theme push --publish # Push and publish
|
|
56
|
+
fluid theme push --nodelete # Keep remote files not present locally
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### `fluid theme pull`
|
|
60
|
+
|
|
61
|
+
Download a remote theme to your local directory:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
fluid theme pull # Interactive theme selection
|
|
65
|
+
fluid theme pull --theme "My Theme" # By name or ID
|
|
66
|
+
fluid theme pull --nodelete # Keep local files not present on remote
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `fluid theme init`
|
|
70
|
+
|
|
71
|
+
Scaffold a new theme from the base template:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
fluid theme init my-theme
|
|
75
|
+
cd my-theme
|
|
76
|
+
fluid theme dev
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `fluid theme navigate`
|
|
80
|
+
|
|
81
|
+
Interactively select a route and open it in the browser (requires a running dev server):
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
fluid theme navigate
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Theme Directory Structure
|
|
88
|
+
|
|
89
|
+
A valid theme directory must contain at least one of: `templates/`, `assets/`, or `config/`.
|
|
90
|
+
|
|
91
|
+
Use a `.fluidignore` file (same syntax as `.gitignore`) to exclude files from syncing.
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
For contributors working in `fluid-mono`:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pnpm --filter @fluid-app/fluid-cli-theme-dev build
|
|
99
|
+
node packages/cli/core/dist/bin/fluid.mjs theme --help
|
|
100
|
+
```
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { getAuthToken, readConfig, updateConfig } from "@fluid-app/fluid-cli";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
import http from "node:http";
|
|
7
7
|
import https from "node:https";
|
|
@@ -377,7 +377,7 @@ var ThemeRoot = class {
|
|
|
377
377
|
}
|
|
378
378
|
file(pathOrFile) {
|
|
379
379
|
if (pathOrFile instanceof ThemeFile) return pathOrFile;
|
|
380
|
-
return new ThemeFile(join(this.root, pathOrFile), this.root);
|
|
380
|
+
return new ThemeFile(isAbsolute(pathOrFile) ? pathOrFile : join(this.root, pathOrFile), this.root);
|
|
381
381
|
}
|
|
382
382
|
glob(dir) {
|
|
383
383
|
const results = [];
|
|
@@ -610,6 +610,92 @@ function watchTheme(root, handler) {
|
|
|
610
610
|
return () => watcher.close();
|
|
611
611
|
}
|
|
612
612
|
//#endregion
|
|
613
|
+
//#region ../../api-clients/themes/src/namespaces/v0.ts
|
|
614
|
+
/**
|
|
615
|
+
* List application themes
|
|
616
|
+
* Get all application themes with optional filters
|
|
617
|
+
*
|
|
618
|
+
* @param client - Fetch client instance
|
|
619
|
+
* @param [params] - params
|
|
620
|
+
*/
|
|
621
|
+
async function listApplicationThemes(client, params) {
|
|
622
|
+
return client.get(`/api/application_themes`, params);
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Create an application theme
|
|
626
|
+
*
|
|
627
|
+
*
|
|
628
|
+
* @param client - Fetch client instance
|
|
629
|
+
* @param body - body
|
|
630
|
+
*/
|
|
631
|
+
async function createApplicationTheme(client, body) {
|
|
632
|
+
return client.post(`/api/application_themes`, body);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Get an application theme
|
|
636
|
+
*
|
|
637
|
+
*
|
|
638
|
+
* @param client - Fetch client instance
|
|
639
|
+
* @param id - id
|
|
640
|
+
* @param [params] - params
|
|
641
|
+
*/
|
|
642
|
+
async function getApplicationTheme(client, id, params) {
|
|
643
|
+
return client.get(`/api/application_themes/${id}`, params);
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Returns available themeables for a given type scoped to the theme's company
|
|
647
|
+
*
|
|
648
|
+
*
|
|
649
|
+
* @param client - Fetch client instance
|
|
650
|
+
* @param id - id
|
|
651
|
+
* @param [params] - params
|
|
652
|
+
*/
|
|
653
|
+
async function getApplicationThemeAvailableThemeables(client, id, params) {
|
|
654
|
+
return client.get(`/api/application_themes/${id}/available_themeables`, params);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Publishes the theme
|
|
658
|
+
*
|
|
659
|
+
*
|
|
660
|
+
* @param client - Fetch client instance
|
|
661
|
+
* @param id - id
|
|
662
|
+
*/
|
|
663
|
+
async function publishApplicationTheme(client, id) {
|
|
664
|
+
return client.post(`/api/application_themes/${id}/publish`);
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Lists all theme resources
|
|
668
|
+
*
|
|
669
|
+
*
|
|
670
|
+
* @param client - Fetch client instance
|
|
671
|
+
* @param application_theme_id - application_theme_id
|
|
672
|
+
*/
|
|
673
|
+
async function listThemeResources(client, application_theme_id) {
|
|
674
|
+
return client.get(`/api/application_themes/${application_theme_id}/resources`);
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Updates a theme resource
|
|
678
|
+
*
|
|
679
|
+
*
|
|
680
|
+
* @param client - Fetch client instance
|
|
681
|
+
* @param application_theme_id - application_theme_id
|
|
682
|
+
* @param body - body
|
|
683
|
+
*/
|
|
684
|
+
async function updateThemeResource(client, application_theme_id, body) {
|
|
685
|
+
return client.put(`/api/application_themes/${application_theme_id}/resources`, body);
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Deletes a theme resource
|
|
689
|
+
*
|
|
690
|
+
*
|
|
691
|
+
* @param client - Fetch client instance
|
|
692
|
+
* @param application_theme_id - application_theme_id
|
|
693
|
+
* @param body - body
|
|
694
|
+
*/
|
|
695
|
+
async function deleteThemeResource(client, application_theme_id, body) {
|
|
696
|
+
return client.delete(`/api/application_themes/${application_theme_id}/resources`, { body });
|
|
697
|
+
}
|
|
698
|
+
//#endregion
|
|
613
699
|
//#region src/theme/syncer.ts
|
|
614
700
|
var Syncer = class {
|
|
615
701
|
checksums = /* @__PURE__ */ new Map();
|
|
@@ -619,11 +705,11 @@ var Syncer = class {
|
|
|
619
705
|
this.themeRoot = themeRoot;
|
|
620
706
|
}
|
|
621
707
|
async fetchChecksums() {
|
|
622
|
-
const body = await this.api
|
|
708
|
+
const body = await listThemeResources(this.api, this.themeId);
|
|
623
709
|
this.updateChecksums(body.application_theme_resources ?? []);
|
|
624
710
|
}
|
|
625
711
|
updateChecksums(resources) {
|
|
626
|
-
for (const r of resources) if (r.key) this.checksums.set(r.key, r.checksum);
|
|
712
|
+
for (const r of resources) if (r.key && r.checksum) this.checksums.set(r.key, r.checksum);
|
|
627
713
|
for (const key of this.checksums.keys()) if (this.checksums.has(`${key}.liquid`)) this.checksums.delete(key);
|
|
628
714
|
}
|
|
629
715
|
hasChanged(file) {
|
|
@@ -633,14 +719,13 @@ var Syncer = class {
|
|
|
633
719
|
return [...this.checksums.keys()];
|
|
634
720
|
}
|
|
635
721
|
async uploadFile(file) {
|
|
636
|
-
|
|
637
|
-
if (file.isText) await this.api.put(path, { application_theme_resource: {
|
|
722
|
+
if (file.isText) await updateThemeResource(this.api, this.themeId, { application_theme_resource: {
|
|
638
723
|
key: file.relativePath,
|
|
639
724
|
content: file.read()
|
|
640
725
|
} });
|
|
641
|
-
else await this.uploadBinaryFile(file
|
|
726
|
+
else await this.uploadBinaryFile(file);
|
|
642
727
|
}
|
|
643
|
-
async uploadBinaryFile(file
|
|
728
|
+
async uploadBinaryFile(file) {
|
|
644
729
|
const asset = (await this.api.post("/api/dam/assets", { placeholder_asset: {
|
|
645
730
|
description: `Uploaded via Fluid CLI: ${file.name}`,
|
|
646
731
|
mime_type: file.mime.name,
|
|
@@ -675,7 +760,7 @@ var Syncer = class {
|
|
|
675
760
|
if (ikBody.height) backfillPayload["asset"]["height"] = ikBody.height;
|
|
676
761
|
if (ikBody.width) backfillPayload["asset"]["width"] = ikBody.width;
|
|
677
762
|
const backfillBody = await this.api.post("/api/dam/assets/backfill_imagekit", backfillPayload);
|
|
678
|
-
await this.api.
|
|
763
|
+
await updateThemeResource(this.api, this.themeId, { application_theme_resource: {
|
|
679
764
|
key: file.relativePath,
|
|
680
765
|
dam_asset: {
|
|
681
766
|
dam_asset_code: backfillBody.asset.code,
|
|
@@ -702,13 +787,13 @@ var Syncer = class {
|
|
|
702
787
|
}[category] ?? "files"}/${assetCode}`;
|
|
703
788
|
}
|
|
704
789
|
async deleteRemoteFile(relativePath) {
|
|
705
|
-
await this.api
|
|
790
|
+
await deleteThemeResource(this.api, this.themeId, { application_theme_resource: { key: relativePath } });
|
|
706
791
|
this.checksums.delete(relativePath);
|
|
707
792
|
}
|
|
708
793
|
async downloadAll() {
|
|
709
|
-
const
|
|
710
|
-
this.updateChecksums(
|
|
711
|
-
return
|
|
794
|
+
const resources = (await listThemeResources(this.api, this.themeId)).application_theme_resources ?? [];
|
|
795
|
+
this.updateChecksums(resources);
|
|
796
|
+
return resources;
|
|
712
797
|
}
|
|
713
798
|
async downloadBinaryAsset(url) {
|
|
714
799
|
const resp = await fetch(url);
|
|
@@ -767,7 +852,10 @@ var Syncer = class {
|
|
|
767
852
|
if (resource.resource_type === "FileResource" && resource.url) {
|
|
768
853
|
const buf = await this.downloadBinaryAsset(resource.url);
|
|
769
854
|
file.write(buf);
|
|
770
|
-
} else if (resource.content !== void 0
|
|
855
|
+
} else if (resource.content !== void 0 && resource.content !== null) {
|
|
856
|
+
const content = typeof resource.content === "string" ? resource.content : JSON.stringify(resource.content);
|
|
857
|
+
file.write(content);
|
|
858
|
+
}
|
|
771
859
|
result.downloaded++;
|
|
772
860
|
} catch (e) {
|
|
773
861
|
result.errors.push(`Download ${resource.key}: ${e}`);
|
|
@@ -856,7 +944,7 @@ async function startDevServer(api, theme, themeRoot, opts, onReady) {
|
|
|
856
944
|
//#region src/commands/dev.ts
|
|
857
945
|
async function ensureDevTheme(api, identifier) {
|
|
858
946
|
if (identifier) {
|
|
859
|
-
const body = await
|
|
947
|
+
const body = await listApplicationThemes(api);
|
|
860
948
|
const found = (body.application_themes ?? []).find((t) => String(t.id) === identifier) ?? (body.application_themes ?? []).find((t) => t.name.toLowerCase() === identifier.toLowerCase());
|
|
861
949
|
if (!found) {
|
|
862
950
|
console.error(`Theme not found: ${identifier}`);
|
|
@@ -866,17 +954,16 @@ async function ensureDevTheme(api, identifier) {
|
|
|
866
954
|
}
|
|
867
955
|
const { devThemeId } = getPluginState();
|
|
868
956
|
if (devThemeId) try {
|
|
869
|
-
const body = await
|
|
957
|
+
const body = await getApplicationTheme(api, devThemeId);
|
|
870
958
|
if (body.application_theme) {
|
|
871
959
|
console.log(`Using existing dev theme #${devThemeId}`);
|
|
872
960
|
return body.application_theme;
|
|
873
961
|
}
|
|
874
962
|
} catch {}
|
|
875
963
|
const { hostname } = await import("node:os");
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
role: "development"
|
|
964
|
+
const theme = (await createApplicationTheme(api, { application_theme: {
|
|
965
|
+
name: `Development (${hostname().split(".")[0] ?? "dev"}-${Math.random().toString(36).slice(2, 8)})`.slice(0, 50),
|
|
966
|
+
status: "development"
|
|
880
967
|
} })).application_theme;
|
|
881
968
|
setPluginState({
|
|
882
969
|
devThemeId: theme.id,
|
|
@@ -893,8 +980,18 @@ function createDevCommand() {
|
|
|
893
980
|
console.error(`'${opts.root}' does not look like a theme directory.`);
|
|
894
981
|
process.exit(1);
|
|
895
982
|
}
|
|
983
|
+
const port = Number(opts.port);
|
|
984
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
985
|
+
console.error(`Invalid port: '${opts.port}'. Must be an integer between 1 and 65535.`);
|
|
986
|
+
process.exit(1);
|
|
987
|
+
}
|
|
896
988
|
const reloadMode = opts.liveReload === "off" ? "off" : "full-page";
|
|
897
989
|
const api = createApiClient();
|
|
990
|
+
const company = (await api.get("/api/company/v1/companies/me")).data?.company?.subdomain;
|
|
991
|
+
if (!company) {
|
|
992
|
+
console.error("Could not determine company subdomain. Make sure your token is valid.");
|
|
993
|
+
process.exit(1);
|
|
994
|
+
}
|
|
898
995
|
const theme = await ensureDevTheme(api, opts.theme);
|
|
899
996
|
let stop;
|
|
900
997
|
const cleanup = () => {
|
|
@@ -903,16 +1000,11 @@ function createDevCommand() {
|
|
|
903
1000
|
};
|
|
904
1001
|
process.on("SIGINT", cleanup);
|
|
905
1002
|
process.on("SIGTERM", cleanup);
|
|
906
|
-
const port = Number(opts.port);
|
|
907
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
908
|
-
console.error(`Invalid port: '${opts.port}'. Must be an integer between 1 and 65535.`);
|
|
909
|
-
process.exit(1);
|
|
910
|
-
}
|
|
911
1003
|
stop = await startDevServer(api, {
|
|
912
1004
|
id: theme.id,
|
|
913
1005
|
name: theme.name,
|
|
914
|
-
company
|
|
915
|
-
editorUrl: theme.editor_url
|
|
1006
|
+
company,
|
|
1007
|
+
editorUrl: theme.editor_url ?? void 0
|
|
916
1008
|
}, themeRoot, {
|
|
917
1009
|
host: opts.host,
|
|
918
1010
|
port,
|
|
@@ -929,8 +1021,8 @@ function createDevCommand() {
|
|
|
929
1021
|
//#endregion
|
|
930
1022
|
//#region src/commands/push.ts
|
|
931
1023
|
async function selectTheme(api) {
|
|
932
|
-
const
|
|
933
|
-
if (!
|
|
1024
|
+
const themeList = (await listApplicationThemes(api)).application_themes ?? [];
|
|
1025
|
+
if (!themeList.length) {
|
|
934
1026
|
console.error("No themes found.");
|
|
935
1027
|
process.exit(1);
|
|
936
1028
|
}
|
|
@@ -938,7 +1030,7 @@ async function selectTheme(api) {
|
|
|
938
1030
|
type: "select",
|
|
939
1031
|
name: "id",
|
|
940
1032
|
message: "Select a theme to push to",
|
|
941
|
-
choices:
|
|
1033
|
+
choices: themeList.map((t) => ({
|
|
942
1034
|
title: `${t.name} (#${t.id})`,
|
|
943
1035
|
value: t.id
|
|
944
1036
|
}))
|
|
@@ -947,11 +1039,11 @@ async function selectTheme(api) {
|
|
|
947
1039
|
console.error("No theme selected.");
|
|
948
1040
|
process.exit(1);
|
|
949
1041
|
}
|
|
950
|
-
return
|
|
1042
|
+
return themeList.find((t) => t.id === id);
|
|
951
1043
|
}
|
|
952
1044
|
async function findTheme(api, identifier) {
|
|
953
|
-
const
|
|
954
|
-
const found =
|
|
1045
|
+
const themeList = (await listApplicationThemes(api)).application_themes ?? [];
|
|
1046
|
+
const found = themeList.find((t) => String(t.id) === identifier) ?? themeList.find((t) => t.name.toLowerCase() === identifier.toLowerCase());
|
|
955
1047
|
if (!found) {
|
|
956
1048
|
console.error(`No theme found with identifier: ${identifier}`);
|
|
957
1049
|
process.exit(1);
|
|
@@ -983,7 +1075,7 @@ function createPushCommand() {
|
|
|
983
1075
|
if (opts.publish) {
|
|
984
1076
|
const pubSpinner = ora("Publishing theme…").start();
|
|
985
1077
|
try {
|
|
986
|
-
await
|
|
1078
|
+
await publishApplicationTheme(api, theme.id);
|
|
987
1079
|
pubSpinner.succeed("Theme published.");
|
|
988
1080
|
} catch (e) {
|
|
989
1081
|
pubSpinner.fail(`Publish failed: ${e}`);
|
|
@@ -994,13 +1086,13 @@ function createPushCommand() {
|
|
|
994
1086
|
//#endregion
|
|
995
1087
|
//#region src/commands/pull.ts
|
|
996
1088
|
async function selectOrFindTheme(api, identifier) {
|
|
997
|
-
const
|
|
998
|
-
if (!
|
|
1089
|
+
const themeList = (await listApplicationThemes(api)).application_themes ?? [];
|
|
1090
|
+
if (!themeList.length) {
|
|
999
1091
|
console.error("No themes found.");
|
|
1000
1092
|
process.exit(1);
|
|
1001
1093
|
}
|
|
1002
1094
|
if (identifier) {
|
|
1003
|
-
const found =
|
|
1095
|
+
const found = themeList.find((t) => String(t.id) === identifier) ?? themeList.find((t) => t.name.toLowerCase() === identifier.toLowerCase());
|
|
1004
1096
|
if (!found) {
|
|
1005
1097
|
console.error(`No theme found with identifier: ${identifier}`);
|
|
1006
1098
|
process.exit(1);
|
|
@@ -1011,7 +1103,7 @@ async function selectOrFindTheme(api, identifier) {
|
|
|
1011
1103
|
type: "select",
|
|
1012
1104
|
name: "id",
|
|
1013
1105
|
message: "Select a theme to pull",
|
|
1014
|
-
choices:
|
|
1106
|
+
choices: themeList.map((t) => ({
|
|
1015
1107
|
title: `${t.name} (#${t.id})`,
|
|
1016
1108
|
value: t.id
|
|
1017
1109
|
}))
|
|
@@ -1020,7 +1112,7 @@ async function selectOrFindTheme(api, identifier) {
|
|
|
1020
1112
|
console.error("No theme selected.");
|
|
1021
1113
|
process.exit(1);
|
|
1022
1114
|
}
|
|
1023
|
-
return
|
|
1115
|
+
return themeList.find((t) => t.id === id);
|
|
1024
1116
|
}
|
|
1025
1117
|
function createPullCommand() {
|
|
1026
1118
|
return new Command("pull").description("Pull a remote theme to your local directory").option("-t, --theme <name-or-id>", "Theme name or ID to pull").option("-n, --nodelete", "Do not delete local files missing on remote").option("--root <path>", "Theme root directory", ".").action(async (opts) => {
|
|
@@ -1188,7 +1280,7 @@ function createNavigateCommand() {
|
|
|
1188
1280
|
let path;
|
|
1189
1281
|
if (typeof dest === "string") path = dest;
|
|
1190
1282
|
else {
|
|
1191
|
-
const resources = (await createApiClient()
|
|
1283
|
+
const resources = (await getApplicationThemeAvailableThemeables(createApiClient(), themeId, {
|
|
1192
1284
|
themeable: dest.resourceType,
|
|
1193
1285
|
per_page: 50
|
|
1194
1286
|
})).available_themeables ?? [];
|
|
@@ -1201,7 +1293,7 @@ function createNavigateCommand() {
|
|
|
1201
1293
|
name: "slug",
|
|
1202
1294
|
message: `Select a ${dest.label.toLowerCase()}`,
|
|
1203
1295
|
choices: resources.map((r) => ({
|
|
1204
|
-
title: r.title ?? r.slug,
|
|
1296
|
+
title: r.title ?? r.slug ?? "Untitled",
|
|
1205
1297
|
value: r.slug
|
|
1206
1298
|
}))
|
|
1207
1299
|
}, { onCancel });
|