@fluid-app/fluid-cli-theme-dev 0.1.63 → 0.1.64
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 +1 -1
- package/dist/dist-cli-preview/.vite/manifest.json +225 -225
- package/dist/dist-cli-preview/account-screens.js +1 -1
- package/dist/dist-cli-preview/chunks/{Breadcrumb-CZ-skiUy.js → Breadcrumb-BQKo8hPc.js} +1 -1
- package/dist/dist-cli-preview/chunks/{Combobox-BGiCYa56.js → Combobox-cf11L_T-.js} +4 -4
- package/dist/dist-cli-preview/chunks/{ContactsScreen-vsNgy_gn.js → ContactsScreen-gIiy4u7c.js} +1160 -1155
- package/dist/dist-cli-preview/chunks/{MemberManagementProviders-DMwkh3JW.js → MemberManagementProviders-C1bwc3tB.js} +258 -224
- package/dist/dist-cli-preview/chunks/{MessagingScreen-DzJOwPGH.js → MessagingScreen-B8ZxAE0L.js} +3904 -3927
- package/dist/dist-cli-preview/chunks/{MobileActionSheet-DqVPgFKI.js → MobileActionSheet-DMeTsLlK.js} +100 -100
- package/dist/dist-cli-preview/chunks/{MySiteScreen-BSmWlCKA.js → MySiteScreen-Dtw1rsF9.js} +69 -69
- package/dist/dist-cli-preview/chunks/OrdersScreen-C-b6i5jB.js +9 -0
- package/dist/dist-cli-preview/chunks/{OrdersScreen-CVEl9FnN.js → OrdersScreen-D9HfcTaa.js} +247 -249
- package/dist/dist-cli-preview/chunks/{ProfileScreen-BC5gyJDY.js → ProfileScreen-DwXozaLs.js} +1051 -965
- package/dist/dist-cli-preview/chunks/{SubscriptionsScreen-CFM4Pufg.js → SubscriptionsScreen-CIdYNpWT.js} +3 -3
- package/dist/dist-cli-preview/chunks/{account-screen-runtime-DxhJm6Qd.js → account-screen-runtime-DYMunv0A.js} +2482 -2407
- package/dist/dist-cli-preview/chunks/ar-DaN3xEy4.js +351 -0
- package/dist/dist-cli-preview/chunks/{arrow-left-UFKI-PQf.js → arrow-left-Bd7dJtIV.js} +1 -1
- package/dist/dist-cli-preview/chunks/bg-D3Np0de3.js +351 -0
- package/dist/dist-cli-preview/chunks/bn-BDSehnZ3.js +351 -0
- package/dist/dist-cli-preview/chunks/{cli-preview-DunPfnjU.js → cli-preview-BT_ARKjk.js} +13 -13
- package/dist/dist-cli-preview/chunks/{client.gen-CSTBgfxj.js → client.gen-Ck6Pm2jV.js} +246 -236
- package/dist/dist-cli-preview/chunks/contacts-ChSWj70z.js +4 -0
- package/dist/dist-cli-preview/chunks/{countries-api-adapter-B3usO25w.js → countries-api-adapter-lxSE748x.js} +2 -2
- package/dist/dist-cli-preview/chunks/{create-card-entry-container-DHgsbSyr.js → create-card-entry-container-D6kSC2Aw.js} +20726 -20509
- package/dist/dist-cli-preview/chunks/cs-CrmATj4L.js +351 -0
- package/dist/dist-cli-preview/chunks/da-CmJdErjd.js +351 -0
- package/dist/dist-cli-preview/chunks/de-C0KTNBZq.js +351 -0
- package/dist/dist-cli-preview/chunks/el-BOQs49GU.js +351 -0
- package/dist/dist-cli-preview/chunks/es-Dt3VSQPu.js +351 -0
- package/dist/dist-cli-preview/chunks/fi-B8UZ7eJL.js +351 -0
- package/dist/dist-cli-preview/chunks/fr-BomEiNL0.js +351 -0
- package/dist/dist-cli-preview/chunks/he-CfmnXjsr.js +351 -0
- package/dist/dist-cli-preview/chunks/{hey-api-client-DSvKWGPq.js → hey-api-client-DGRBsZ7Y.js} +3 -3
- package/dist/dist-cli-preview/chunks/hi-CTk5zGoJ.js +351 -0
- package/dist/dist-cli-preview/chunks/hr-DaNToqEm.js +351 -0
- package/dist/dist-cli-preview/chunks/hu-lcAuIwez.js +351 -0
- package/dist/dist-cli-preview/chunks/id-CEThcKZG.js +351 -0
- package/dist/dist-cli-preview/chunks/it-BCRCoDOg.js +351 -0
- package/dist/dist-cli-preview/chunks/ja-BIkFdIfq.js +351 -0
- package/dist/dist-cli-preview/chunks/ko-CD3bk6gv.js +351 -0
- package/dist/dist-cli-preview/chunks/{log-out-CBUq5Hd-.js → log-out-CSPZjXTK.js} +1 -1
- package/dist/dist-cli-preview/chunks/{map-pin-DHfjSyoR.js → map-pin-B2ScoijR.js} +1 -1
- package/dist/dist-cli-preview/chunks/messaging-Bq5UPkvG.js +4 -0
- package/dist/dist-cli-preview/chunks/{mount-account-screen-pc1VISwF.js → mount-account-screen-C_CdoGir.js} +2453 -2392
- package/dist/dist-cli-preview/chunks/ms-DI4mGa5X.js +351 -0
- package/dist/dist-cli-preview/chunks/mysite-DcpuKsuW.js +4 -0
- package/dist/dist-cli-preview/chunks/nl-B-XAD3Ra.js +351 -0
- package/dist/dist-cli-preview/chunks/no-C_NmYGbe.js +351 -0
- package/dist/dist-cli-preview/chunks/orders-B_57xKkq.js +4 -0
- package/dist/dist-cli-preview/chunks/pl-mQRzJZEW.js +351 -0
- package/dist/dist-cli-preview/chunks/{plus-C-lo0pFM.js → plus-CocoE5gC.js} +1 -1
- package/dist/dist-cli-preview/chunks/profile-BfM2KxKe.js +4 -0
- package/dist/dist-cli-preview/chunks/pt-BYMWPjog.js +351 -0
- package/dist/dist-cli-preview/chunks/ro-B98EuFQI.js +351 -0
- package/dist/dist-cli-preview/chunks/ru-DBO9IasC.js +351 -0
- package/dist/dist-cli-preview/chunks/schemas-D94nZk5x.js +5374 -0
- package/dist/dist-cli-preview/chunks/{screen-route-slug-utils-B254xI_J.js → screen-route-slug-utils-BDjSpDoe.js} +1 -1
- package/dist/dist-cli-preview/chunks/{sheet-haptics-context-D2WUiXJm.js → sheet-haptics-context-iOjKcNDj.js} +1 -1
- package/dist/dist-cli-preview/chunks/{sidebar-CMSWZCPS.js → sidebar-Ckzom4Xc.js} +12 -12
- package/dist/dist-cli-preview/chunks/sk-hRvNJfiN.js +351 -0
- package/dist/dist-cli-preview/chunks/{store-api-context-BIdILNWL.js → store-api-context-B96Ip2FB.js} +1 -1
- package/dist/dist-cli-preview/chunks/subscriptions-8VtIA4w9.js +4 -0
- package/dist/dist-cli-preview/chunks/sv-noYtTQbO.js +351 -0
- package/dist/dist-cli-preview/chunks/th-CJCAKsR-.js +351 -0
- package/dist/dist-cli-preview/chunks/tl-Bx8pJ7p7.js +351 -0
- package/dist/dist-cli-preview/chunks/tr-C4tCNiV7.js +351 -0
- package/dist/dist-cli-preview/chunks/{trash-2-Bq4IqH0j.js → trash-2-wVweQZMw.js} +4 -4
- package/dist/dist-cli-preview/chunks/uk-xFjXQDP3.js +351 -0
- package/dist/dist-cli-preview/chunks/{use-optional-store-Csor1Z3z.js → use-optional-store-lZUz976C.js} +2 -2
- package/dist/dist-cli-preview/chunks/use-store-DVmgsDXy.js +103 -0
- package/dist/dist-cli-preview/chunks/{useInfiniteQuery-BpFtQ7DH.js → useInfiniteQuery-Dtzov9uh.js} +1 -1
- package/dist/dist-cli-preview/chunks/{user-BGrQJ1GJ.js → user-B81IrSgp.js} +1 -1
- package/dist/dist-cli-preview/chunks/{users-DBL0isYy.js → users-BXTjoCUz.js} +1 -1
- package/dist/dist-cli-preview/chunks/vi-BAvhRTgb.js +351 -0
- package/dist/dist-cli-preview/chunks/zh_CN-Cyq9zeex.js +351 -0
- package/dist/dist-cli-preview/chunks/zh_TW-DFM5MhMA.js +351 -0
- package/dist/index.mjs +346 -32
- package/dist/index.mjs.map +1 -1
- package/dist/skills/themes-cart-feedback/SKILL.md +9 -14
- package/dist/skills/themes-review/SKILL.md +40 -34
- package/dist/skills/themes-review/references/blocks-vs-sections.md +2 -2
- package/dist/skills/themes-review/references/dead-code.md +1 -1
- package/dist/skills/themes-review/references/dynamism.md +9 -3
- package/dist/skills/themes-review/references/editor-attributes.md +25 -26
- package/dist/skills/themes-review/references/examples.md +3 -4
- package/dist/skills/themes-review/references/fairshare-attributes.md +5 -5
- package/dist/skills/themes-review/references/global-settings.md +56 -67
- package/dist/skills/themes-review/references/liquid-correctness.md +10 -15
- package/dist/skills/themes-review/references/media-tag.md +8 -7
- package/dist/skills/themes-review/references/navigation.md +18 -13
- package/dist/skills/themes-review/references/security-accessibility.md +11 -6
- package/dist/skills/themes-review/references/setting-types.md +136 -77
- package/dist/skills/themes-settings-schema/SKILL.md +15 -2
- package/package.json +2 -2
- package/dist/dist-cli-preview/chunks/OrdersScreen-d4fJE_dm.js +0 -9
- package/dist/dist-cli-preview/chunks/ar-B7NQmkZ1.js +0 -341
- package/dist/dist-cli-preview/chunks/bg-CxfL8rZN.js +0 -341
- package/dist/dist-cli-preview/chunks/bn-CdhHCK--.js +0 -341
- package/dist/dist-cli-preview/chunks/contacts-CpYlEgAq.js +0 -4
- package/dist/dist-cli-preview/chunks/cs-DTe-xGeI.js +0 -341
- package/dist/dist-cli-preview/chunks/da-B8Cl_vkl.js +0 -341
- package/dist/dist-cli-preview/chunks/de-D8cYy8G-.js +0 -341
- package/dist/dist-cli-preview/chunks/el-Chjin2JW.js +0 -341
- package/dist/dist-cli-preview/chunks/es-mp8RwFN4.js +0 -341
- package/dist/dist-cli-preview/chunks/fi-91gcpxoy.js +0 -341
- package/dist/dist-cli-preview/chunks/fr-ekvUCKTH.js +0 -341
- package/dist/dist-cli-preview/chunks/he-B7QiuquT.js +0 -341
- package/dist/dist-cli-preview/chunks/hi-DNNhi7ud.js +0 -341
- package/dist/dist-cli-preview/chunks/hr-CAwxMKX_.js +0 -341
- package/dist/dist-cli-preview/chunks/hu-D_oQwH4Q.js +0 -341
- package/dist/dist-cli-preview/chunks/id-oeAszBoo.js +0 -341
- package/dist/dist-cli-preview/chunks/isAfter-BUGgByEf.js +0 -1414
- package/dist/dist-cli-preview/chunks/it-Dme6GXSG.js +0 -341
- package/dist/dist-cli-preview/chunks/ja-CRnvQNCa.js +0 -341
- package/dist/dist-cli-preview/chunks/ko-8mk8A3_I.js +0 -341
- package/dist/dist-cli-preview/chunks/messaging-DkyH4CPP.js +0 -4
- package/dist/dist-cli-preview/chunks/ms-Bs0GvdOw.js +0 -341
- package/dist/dist-cli-preview/chunks/mysite-wvfITV7T.js +0 -4
- package/dist/dist-cli-preview/chunks/nl-tsI6lLxl.js +0 -341
- package/dist/dist-cli-preview/chunks/no-DMot6qX0.js +0 -341
- package/dist/dist-cli-preview/chunks/orders-c13r499A.js +0 -4
- package/dist/dist-cli-preview/chunks/pl-DmTxONNg.js +0 -341
- package/dist/dist-cli-preview/chunks/profile-CoWo9Sxe.js +0 -4
- package/dist/dist-cli-preview/chunks/pt-DgmwnAV-.js +0 -341
- package/dist/dist-cli-preview/chunks/ro-B4M17oRc.js +0 -341
- package/dist/dist-cli-preview/chunks/ru-BS2gZnbs.js +0 -341
- package/dist/dist-cli-preview/chunks/schemas-9_ZwC5nC.js +0 -3977
- package/dist/dist-cli-preview/chunks/sk-BdDjjHfn.js +0 -341
- package/dist/dist-cli-preview/chunks/subscriptions-BHALQrcU.js +0 -4
- package/dist/dist-cli-preview/chunks/sv-CW7FL5HA.js +0 -341
- package/dist/dist-cli-preview/chunks/th-BYBMKmSh.js +0 -341
- package/dist/dist-cli-preview/chunks/tl-CDigaLsK.js +0 -341
- package/dist/dist-cli-preview/chunks/tr-PxiurNCr.js +0 -341
- package/dist/dist-cli-preview/chunks/uk-B4aYnJ8t.js +0 -341
- package/dist/dist-cli-preview/chunks/vi-BoKSdzH3.js +0 -341
- package/dist/dist-cli-preview/chunks/zh_CN-DrCrXsZC.js +0 -341
- package/dist/dist-cli-preview/chunks/zh_TW-JmyC1W5K.js +0 -341
package/dist/index.mjs
CHANGED
|
@@ -134,8 +134,10 @@ function createFetchClient(config) {
|
|
|
134
134
|
const queryString = new URLSearchParams();
|
|
135
135
|
Object.entries(params).forEach(([key, value]) => {
|
|
136
136
|
if (value === void 0 || value === null) return;
|
|
137
|
-
if (Array.isArray(value))
|
|
138
|
-
|
|
137
|
+
if (Array.isArray(value)) {
|
|
138
|
+
const arrayKey = key.endsWith("[]") ? key : `${key}[]`;
|
|
139
|
+
value.forEach((item) => queryString.append(arrayKey, String(item)));
|
|
140
|
+
} else if (typeof value === "object") Object.entries(value).forEach(([subKey, subValue]) => {
|
|
139
141
|
if (subValue === void 0 || subValue === null) return;
|
|
140
142
|
if (Array.isArray(subValue)) subValue.forEach((item) => queryString.append(`${key}[${subKey}][]`, String(item)));
|
|
141
143
|
else queryString.append(`${key}[${subKey}]`, String(subValue));
|
|
@@ -611,7 +613,7 @@ async function listApplicationThemes(client, params) {
|
|
|
611
613
|
}
|
|
612
614
|
/**
|
|
613
615
|
* Create an application theme
|
|
614
|
-
*
|
|
616
|
+
* Creates a new theme for your company. Requires a company Bearer token with permission to update themes. The theme starts in `draft` status unless you pass another `status`. Pass `cover_image_url` to set the cover image. When the company has moved theme stylesheets to theme-level assets, sending `custom_stylesheet` or `global_stylesheet` returns 422. A Git repository is provisioned for the theme in the background when Git sync is available.
|
|
615
617
|
*
|
|
616
618
|
* @param client - Fetch client instance
|
|
617
619
|
* @param body - body
|
|
@@ -621,7 +623,7 @@ async function createApplicationTheme(client, body) {
|
|
|
621
623
|
}
|
|
622
624
|
/**
|
|
623
625
|
* Get an application theme
|
|
624
|
-
*
|
|
626
|
+
* Returns one of your company's themes with its templates (including full Liquid content), versions, images, and Git repositories. Pass `version` to read the theme as it was at a past version. Requires a company Bearer token with permission to view themes. Themes belonging to another company return 404.
|
|
625
627
|
*
|
|
626
628
|
* @param client - Fetch client instance
|
|
627
629
|
* @param id - id
|
|
@@ -632,7 +634,7 @@ async function getApplicationTheme(client, id, params) {
|
|
|
632
634
|
}
|
|
633
635
|
/**
|
|
634
636
|
* Returns available themeables for a given type scoped to the theme's company
|
|
635
|
-
*
|
|
637
|
+
* Lists the company resources of one type (for example `product`, `page`, `collection`, or `enrollment_pack`) that templates in this theme can be assigned to, sorted by title. Pass `themeable` to choose the type; unsupported types return 400. Pass `search` to filter by title. Results are paginated, with page details in `meta.pagination`, 20 per page by default. Requires a company Bearer token with permission to view themes.
|
|
636
638
|
*
|
|
637
639
|
* @param client - Fetch client instance
|
|
638
640
|
* @param id - id
|
|
@@ -654,7 +656,7 @@ async function cloneApplicationThemeForDevelopment(client, id, body) {
|
|
|
654
656
|
}
|
|
655
657
|
/**
|
|
656
658
|
* Publishes the theme
|
|
657
|
-
*
|
|
659
|
+
* Publishes the theme so the storefront serves its latest content, then syncs it to any connected Git repository. Pass the optional `version` query parameter to publish a specific past version instead; an unknown version returns 404. Requires a company Bearer token with permission to update themes. Returns 422 when the theme's member configuration references a template or navigation item that does not exist.
|
|
658
660
|
*
|
|
659
661
|
* @param client - Fetch client instance
|
|
660
662
|
* @param id - id
|
|
@@ -664,7 +666,7 @@ async function publishApplicationTheme(client, id) {
|
|
|
664
666
|
}
|
|
665
667
|
/**
|
|
666
668
|
* Get theme assets
|
|
667
|
-
*
|
|
669
|
+
* Lists the files (images, stylesheets, scripts, and other assets) uploaded to one of your company's themes. Requires a company Bearer token with permission to view themes.
|
|
668
670
|
*
|
|
669
671
|
* @param client - Fetch client instance
|
|
670
672
|
* @param id - id
|
|
@@ -695,7 +697,7 @@ async function updateThemeResource(client, application_theme_id, body) {
|
|
|
695
697
|
}
|
|
696
698
|
/**
|
|
697
699
|
* Deletes a theme resource
|
|
698
|
-
*
|
|
700
|
+
* Deletes one theme file, identified by its `key`, as the theme CLI does when a local file is removed. Send the `base_sha` from your last pull to guard against overwriting newer server changes (409 when stale). The member site manifest `members_sites.json` cannot be deleted this way. Requires a company admin token with the `application_themes` update permission.
|
|
699
701
|
*
|
|
700
702
|
* @param client - Fetch client instance
|
|
701
703
|
* @param application_theme_id - application_theme_id
|
|
@@ -706,7 +708,7 @@ async function deleteThemeResource(client, application_theme_id, body) {
|
|
|
706
708
|
}
|
|
707
709
|
/**
|
|
708
710
|
* Creates a file resource
|
|
709
|
-
*
|
|
711
|
+
* Uploads a file to the company's asset library. Provide exactly one source: `content` for a text file, a multipart `file` upload, or `dam_asset_code` to link an existing digital asset; otherwise supply `url` with `filename`, `content_type`, and `content_size`. To attach the file to a theme or template, send both `relateable_type` (`ApplicationTheme` or `ApplicationThemeTemplate`) and `relateable_id`. Requires the `file_resources` update permission.
|
|
710
712
|
*
|
|
711
713
|
* @param client - Fetch client instance
|
|
712
714
|
* @param body - body
|
|
@@ -716,7 +718,7 @@ async function createFileResource(client, body) {
|
|
|
716
718
|
}
|
|
717
719
|
/**
|
|
718
720
|
* Deletes a file resource
|
|
719
|
-
*
|
|
721
|
+
* Deletes one of the company's uploaded files and returns its ID. Requires the `file_resources` update permission. Returns 404 when the file does not belong to the company.
|
|
720
722
|
*
|
|
721
723
|
* @param client - Fetch client instance
|
|
722
724
|
* @param id - id
|
|
@@ -972,6 +974,23 @@ function readLegacyThemeConfig(themeRoot) {
|
|
|
972
974
|
return null;
|
|
973
975
|
}
|
|
974
976
|
}
|
|
977
|
+
/**
|
|
978
|
+
* Whether a push or pull to `theme` may use this directory's own sync state
|
|
979
|
+
* and rebind it to `theme`.
|
|
980
|
+
*
|
|
981
|
+
* Development themes are scratch copies `fluid theme dev` creates and syncs.
|
|
982
|
+
* Using one here would move the project's sync baseline to the scratch copy
|
|
983
|
+
* and rebind the project, so a later Publish would update the copy instead of
|
|
984
|
+
* the theme the project was pulled from. Push syncs such a theme separately;
|
|
985
|
+
* pull refuses. An explicit `--theme` to any other theme still rebinds.
|
|
986
|
+
*/
|
|
987
|
+
function mayBindTheme(existing, theme) {
|
|
988
|
+
return !existing || existing.themeId === theme.id || theme.status !== "development";
|
|
989
|
+
}
|
|
990
|
+
/** Why `mayBindTheme` refused, with the way forward. */
|
|
991
|
+
function developmentThemeRefusal(existing, theme) {
|
|
992
|
+
return `${theme.name} (#${theme.id}) is a development theme, and this project is bound to ${existing.themeName} (#${existing.themeId}). Pulling it here would replace this project's sync state. Use \`fluid theme dev\` or \`fluid theme push -t\` to update the development theme, or pull it into a separate directory with --root.`;
|
|
993
|
+
}
|
|
975
994
|
/** Write `.fluid-theme.json` to a theme directory. */
|
|
976
995
|
function writeThemeConfig(themeRoot, config) {
|
|
977
996
|
writeFileSync(configPath(themeRoot), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
@@ -1031,6 +1050,21 @@ const THEME_LEVEL_RESOURCE_KEYS = new Set([
|
|
|
1031
1050
|
"variables.json",
|
|
1032
1051
|
"members_sites.json"
|
|
1033
1052
|
]);
|
|
1053
|
+
/**
|
|
1054
|
+
* Template types Fluid has retired. The resources API answers 404 for them, so
|
|
1055
|
+
* a theme that still carries their folders must not try to upload them: member
|
|
1056
|
+
* home pages are `members_page` templates on the `/` route, sign-in is hosted,
|
|
1057
|
+
* and account pages became member pages.
|
|
1058
|
+
*/
|
|
1059
|
+
const RETIRED_TEMPLATE_TYPES = new Set([
|
|
1060
|
+
"account_page",
|
|
1061
|
+
"members_dashboard",
|
|
1062
|
+
"members_login"
|
|
1063
|
+
]);
|
|
1064
|
+
function isRetiredTemplateKey(relativePath) {
|
|
1065
|
+
const [prefix] = normalizeThemeResourceKey(relativePath).split("/");
|
|
1066
|
+
return prefix !== void 0 && RETIRED_TEMPLATE_TYPES.has(prefix);
|
|
1067
|
+
}
|
|
1034
1068
|
const COMPOSITE_RESOURCE_FILE_NAMES = new Set([
|
|
1035
1069
|
"index.liquid",
|
|
1036
1070
|
"styles.css",
|
|
@@ -1051,6 +1085,7 @@ function isThemeResourceKey(relativePath) {
|
|
|
1051
1085
|
const key = normalizeThemeResourceKey(relativePath);
|
|
1052
1086
|
if (!hasSafeSegments(key)) return false;
|
|
1053
1087
|
if (THEME_LEVEL_RESOURCE_KEYS.has(key)) return true;
|
|
1088
|
+
if (isRetiredTemplateKey(key)) return false;
|
|
1054
1089
|
const segments = key.split("/");
|
|
1055
1090
|
const prefix = segments[0];
|
|
1056
1091
|
const fileName = segments.at(-1);
|
|
@@ -1665,7 +1700,7 @@ function injectHotReload(html, mode) {
|
|
|
1665
1700
|
}
|
|
1666
1701
|
//#endregion
|
|
1667
1702
|
//#region src/theme/dev-server/member-preview-html.ts
|
|
1668
|
-
function escapeHtml(value) {
|
|
1703
|
+
function escapeHtml$1(value) {
|
|
1669
1704
|
return value.replace(/[&<>"']/g, (char) => ({
|
|
1670
1705
|
"&": "&",
|
|
1671
1706
|
"<": "<",
|
|
@@ -1723,9 +1758,9 @@ function renderMemberHtml(render, opts) {
|
|
|
1723
1758
|
if (href) href.value = previewLink(href.value, opts);
|
|
1724
1759
|
}
|
|
1725
1760
|
}
|
|
1726
|
-
const runtime = parseFragment(`<script type="module" src="${escapeHtml(opts.localOrigin)}/_members/_runtime/account-screens.js"><\/script>`);
|
|
1761
|
+
const runtime = parseFragment(`<script type="module" src="${escapeHtml$1(opts.localOrigin)}/_members/_runtime/account-screens.js"><\/script>`);
|
|
1727
1762
|
head.childNodes.unshift(...runtime.childNodes);
|
|
1728
|
-
const base = parseFragment(`<base href="${escapeHtml(new URL(opts.path, opts.origin).href)}">`);
|
|
1763
|
+
const base = parseFragment(`<base href="${escapeHtml$1(new URL(opts.path, opts.origin).href)}">`);
|
|
1729
1764
|
head.childNodes.unshift(...base.childNodes);
|
|
1730
1765
|
const styles = parseFragment(render.styles);
|
|
1731
1766
|
head.childNodes.push(...styles.childNodes);
|
|
@@ -1859,7 +1894,7 @@ var MemberPreview = class {
|
|
|
1859
1894
|
}));
|
|
1860
1895
|
return true;
|
|
1861
1896
|
}
|
|
1862
|
-
if (url.pathname === "/_members" || url.pathname === "/_members/") {
|
|
1897
|
+
if ((url.pathname === "/_members" || url.pathname === "/_members/") && !url.searchParams.has("members_site_id")) {
|
|
1863
1898
|
const screens = metadata.system_screens.filter((screen) => this.screenAssets.screens.includes(screen.key));
|
|
1864
1899
|
const links = metadata.members_sites.map((site) => {
|
|
1865
1900
|
const destinations = [...site.pages.map((page) => ({
|
|
@@ -1869,7 +1904,7 @@ var MemberPreview = class {
|
|
|
1869
1904
|
path: `/${screen.key}`,
|
|
1870
1905
|
title: screen.key + " (sample)"
|
|
1871
1906
|
}))];
|
|
1872
|
-
return `<h2>${escapeHtml(site.name)}</h2><ul>${destinations.map((page) => `<li><a href="${escapeHtml(memberPreviewUrl(page.path, { members_site_id: site.id }))}">${escapeHtml(page.title)}</a></li>`).join("")}</ul>`;
|
|
1907
|
+
return `<h2>${escapeHtml$1(site.name)}</h2><ul>${destinations.map((page) => `<li><a href="${escapeHtml$1(memberPreviewUrl(page.path, { members_site_id: site.id }))}">${escapeHtml$1(page.title)}</a></li>`).join("")}</ul>`;
|
|
1873
1908
|
}).join("");
|
|
1874
1909
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
1875
1910
|
res.end(req.method === "HEAD" ? void 0 : `<!doctype html><meta charset="utf-8"><title>Member preview</title><h1>Member preview</h1><p>Sample data. Select a page to preview its site's layout.</p>${links || "<p>No member pages in this theme.</p>"}`);
|
|
@@ -1878,9 +1913,9 @@ var MemberPreview = class {
|
|
|
1878
1913
|
const selection = parseSelection(url.searchParams);
|
|
1879
1914
|
const site = metadata.members_sites.find((s) => s.id === selection.members_site_id);
|
|
1880
1915
|
if (!site) throw new MemberPreviewError("This site no longer belongs to the preview theme. Select a page at /_members.", 404);
|
|
1881
|
-
const path = url.pathname.slice(9);
|
|
1882
|
-
const
|
|
1883
|
-
const
|
|
1916
|
+
const path = url.pathname.slice(9) || "/";
|
|
1917
|
+
const screen = metadata.system_screens.find((s) => path === `/${s.key}` || path.startsWith(`/${s.key}/`));
|
|
1918
|
+
const page = screen ? void 0 : site.pages.find((p) => p.path === path);
|
|
1884
1919
|
if (!page && !screen) throw new MemberPreviewError("This member page is unavailable in the selected site. Select a page at /_members.", 404);
|
|
1885
1920
|
if (screen && !this.screenAssets.screens.includes(screen.key)) throw new MemberPreviewError("This system screen has no packaged sample preview. Rebuild or update the CLI.", 501);
|
|
1886
1921
|
const html = renderMemberHtml(parseMemberRender(await this.api.post(`/api/v202604/themes/${this.theme.id}/member-preview`, {
|
|
@@ -1945,6 +1980,89 @@ var SSEStream = class {
|
|
|
1945
1980
|
}
|
|
1946
1981
|
};
|
|
1947
1982
|
//#endregion
|
|
1983
|
+
//#region src/theme/dev-server/template-overrides.ts
|
|
1984
|
+
const TEMPLATE_OVERRIDE_PARAM = "theme_template_id";
|
|
1985
|
+
/**
|
|
1986
|
+
* Maps an explicit `theme_template_id` onto the dev theme the server renders.
|
|
1987
|
+
*
|
|
1988
|
+
* Mist, `theme navigate` and bookmarked URLs carry template ids from the
|
|
1989
|
+
* theme the project was pulled from. Forwarded unchanged next to
|
|
1990
|
+
* `x-fluid-theme: <dev theme>`, the storefront renders that original
|
|
1991
|
+
* template and the local edits never show. Resolve such an id to the dev
|
|
1992
|
+
* theme's template with the same themeable type and name instead. Ids that
|
|
1993
|
+
* already belong to the dev theme and global and extension templates (no
|
|
1994
|
+
* owning theme) pass through untouched. A failed lookup also passes through,
|
|
1995
|
+
* with a warning, since the page may then show the original template.
|
|
1996
|
+
*/
|
|
1997
|
+
function createTemplateOverrideResolver(api, devThemeId) {
|
|
1998
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1999
|
+
const warnLookupFailed = (requestedId, error) => {
|
|
2000
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2001
|
+
console.warn(`[Templates] Could not map template #${requestedId} to dev theme #${devThemeId} (${message}); rendering it unchanged, which may show the original theme's template.`);
|
|
2002
|
+
};
|
|
2003
|
+
/** `null` means the lookup failed: forward the id as-is, uncached. */
|
|
2004
|
+
const lookup = async (requestedId) => {
|
|
2005
|
+
let requested;
|
|
2006
|
+
try {
|
|
2007
|
+
requested = (await api.get(`/api/application_theme_templates/${encodeURIComponent(requestedId)}`)).application_theme_template;
|
|
2008
|
+
} catch (error) {
|
|
2009
|
+
warnLookupFailed(requestedId, error);
|
|
2010
|
+
return null;
|
|
2011
|
+
}
|
|
2012
|
+
if (!requested) return null;
|
|
2013
|
+
const { name, themeable_type: themeableType } = requested;
|
|
2014
|
+
const sourceThemeId = requested.application_theme_id;
|
|
2015
|
+
if (!sourceThemeId || sourceThemeId === devThemeId) return {
|
|
2016
|
+
kind: "use",
|
|
2017
|
+
id: requestedId
|
|
2018
|
+
};
|
|
2019
|
+
const params = new URLSearchParams({
|
|
2020
|
+
application_theme_id: String(devThemeId),
|
|
2021
|
+
themeable_type: themeableType
|
|
2022
|
+
});
|
|
2023
|
+
let candidates;
|
|
2024
|
+
try {
|
|
2025
|
+
candidates = (await api.get(`/api/application_theme_templates?${params}`)).templates ?? [];
|
|
2026
|
+
} catch (error) {
|
|
2027
|
+
warnLookupFailed(requestedId, error);
|
|
2028
|
+
return null;
|
|
2029
|
+
}
|
|
2030
|
+
const matches = candidates.filter((t) => t.application_theme_id === devThemeId && t.themeable_type === themeableType && t.name === name);
|
|
2031
|
+
const [counterpart] = matches;
|
|
2032
|
+
if (matches.length === 1 && counterpart) return {
|
|
2033
|
+
kind: "use",
|
|
2034
|
+
id: String(counterpart.id)
|
|
2035
|
+
};
|
|
2036
|
+
return {
|
|
2037
|
+
kind: "unresolved",
|
|
2038
|
+
reason: matches.length === 0 ? "missing" : "ambiguous",
|
|
2039
|
+
requestedId,
|
|
2040
|
+
themeableType,
|
|
2041
|
+
name,
|
|
2042
|
+
candidateIds: matches.map((t) => t.id)
|
|
2043
|
+
};
|
|
2044
|
+
};
|
|
2045
|
+
return {
|
|
2046
|
+
async resolve(requestedId) {
|
|
2047
|
+
const cached = cache.get(requestedId);
|
|
2048
|
+
if (cached) return {
|
|
2049
|
+
kind: "use",
|
|
2050
|
+
id: cached
|
|
2051
|
+
};
|
|
2052
|
+
const result = await lookup(requestedId);
|
|
2053
|
+
if (!result) return {
|
|
2054
|
+
kind: "use",
|
|
2055
|
+
id: requestedId
|
|
2056
|
+
};
|
|
2057
|
+
if (result.kind === "use") cache.set(requestedId, result.id);
|
|
2058
|
+
return result;
|
|
2059
|
+
},
|
|
2060
|
+
clear() {
|
|
2061
|
+
cache.clear();
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
//#endregion
|
|
1948
2066
|
//#region src/theme/dev-server/proxy.ts
|
|
1949
2067
|
const HOP_BY_HOP = new Set([
|
|
1950
2068
|
"connection",
|
|
@@ -1968,6 +2086,20 @@ async function proxyRequest(req, res, opts) {
|
|
|
1968
2086
|
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
1969
2087
|
url.searchParams.set("_fd", "0");
|
|
1970
2088
|
url.searchParams.set("pb", "0");
|
|
2089
|
+
const requestedTemplateId = url.searchParams.get(TEMPLATE_OVERRIDE_PARAM);
|
|
2090
|
+
if (requestedTemplateId && opts.templateOverrides) {
|
|
2091
|
+
const override = await opts.templateOverrides.resolve(requestedTemplateId);
|
|
2092
|
+
if (override.kind === "use") url.searchParams.set(TEMPLATE_OVERRIDE_PARAM, override.id);
|
|
2093
|
+
else if (isPageRequest(req)) {
|
|
2094
|
+
const html = injectHotReload(unresolvedTemplatePage(override, opts.themeId), opts.reloadMode);
|
|
2095
|
+
res.writeHead(404, {
|
|
2096
|
+
"content-type": "text/html; charset=utf-8",
|
|
2097
|
+
"content-length": String(Buffer.byteLength(html))
|
|
2098
|
+
});
|
|
2099
|
+
res.end(html);
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
1971
2103
|
const pending = opts.pendingFiles?.() ?? [];
|
|
1972
2104
|
const isGet = req.method === "GET" || req.method === "HEAD";
|
|
1973
2105
|
let method = req.method ?? "GET";
|
|
@@ -2022,6 +2154,26 @@ async function proxyRequest(req, res, opts) {
|
|
|
2022
2154
|
proxyReq.end();
|
|
2023
2155
|
});
|
|
2024
2156
|
}
|
|
2157
|
+
const PAGE_FETCH_DESTS = new Set([
|
|
2158
|
+
"document",
|
|
2159
|
+
"iframe",
|
|
2160
|
+
"frame"
|
|
2161
|
+
]);
|
|
2162
|
+
/** Browser navigations to a storefront page, as opposed to API or asset calls. */
|
|
2163
|
+
function isPageRequest(req) {
|
|
2164
|
+
if (req.method !== "GET" && req.method !== "HEAD") return false;
|
|
2165
|
+
if (((req.url ?? "/").split("?")[0] ?? "/").startsWith("/api/")) return false;
|
|
2166
|
+
const dest = req.headers["sec-fetch-dest"];
|
|
2167
|
+
if (typeof dest === "string") return PAGE_FETCH_DESTS.has(dest);
|
|
2168
|
+
const accept = req.headers["accept"];
|
|
2169
|
+
return typeof accept === "string" && accept.includes("text/html");
|
|
2170
|
+
}
|
|
2171
|
+
function unresolvedTemplatePage(override, devThemeId) {
|
|
2172
|
+
return `<!doctype html><title>Template not in dev theme</title><p>${`Template #${escapeHtml(override.requestedId)} (${escapeHtml(override.themeableType)} / ${escapeHtml(override.name)})`} belongs to another theme, and ${override.reason === "missing" ? `dev theme #${devThemeId} has no template with that type and name. Sync or create it locally, or` : `dev theme #${devThemeId} has several templates with that type and name (#${override.candidateIds.join(", #")}), so the edited copy is ambiguous. Remove the duplicates, or`} remove <code>${TEMPLATE_OVERRIDE_PARAM}</code> to use the default.</p>`;
|
|
2173
|
+
}
|
|
2174
|
+
function escapeHtml(value) {
|
|
2175
|
+
return value.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
2176
|
+
}
|
|
2025
2177
|
function readBody(req) {
|
|
2026
2178
|
return new Promise((resolve, reject) => {
|
|
2027
2179
|
const chunks = [];
|
|
@@ -2346,12 +2498,13 @@ function isStylesheetKey(key) {
|
|
|
2346
2498
|
//#endregion
|
|
2347
2499
|
//#region src/theme/member-dependencies.ts
|
|
2348
2500
|
/** The same file graph is used for uploads and incoming omission protection.
|
|
2349
|
-
* Rails remains responsible for resolving company-specific roles and handles.
|
|
2501
|
+
* Rails remains responsible for resolving company-specific roles and handles.
|
|
2502
|
+
* Retired template types are never synced, so they are never dependencies. */
|
|
2350
2503
|
function memberDependencyPaths(document, availablePaths, deletedPaths = [], changedPaths = availablePaths) {
|
|
2351
2504
|
const dependencies = /* @__PURE__ */ new Set();
|
|
2352
|
-
for (const entry of [...records(document.templates), ...records(document.menu_references)]) if (typeof entry.path === "string") dependencies.add(entry.path);
|
|
2353
|
-
for (const site of records(document.sites)) {
|
|
2354
|
-
for (const page of records(site.pages)) if (typeof page.template_path === "string") dependencies.add(page.template_path);
|
|
2505
|
+
for (const entry of [...records$1(document.templates), ...records$1(document.menu_references)]) if (typeof entry.path === "string") dependencies.add(entry.path);
|
|
2506
|
+
for (const site of records$1(document.sites)) {
|
|
2507
|
+
for (const page of records$1(site.pages)) if (typeof page.template_path === "string") dependencies.add(page.template_path);
|
|
2355
2508
|
const layout = site.layout_name;
|
|
2356
2509
|
if (layout === "none") continue;
|
|
2357
2510
|
if (typeof layout === "string" && layout && !["member", "theme"].includes(layout)) {
|
|
@@ -2372,9 +2525,10 @@ function memberDependencyPaths(document, availablePaths, deletedPaths = [], chan
|
|
|
2372
2525
|
if ((suffix === "styles.css" ? changedPaths : availablePaths).has(sibling) || deletedPaths.includes(sibling)) dependencies.add(sibling);
|
|
2373
2526
|
}
|
|
2374
2527
|
}
|
|
2528
|
+
for (const path of dependencies) if (isRetiredTemplateKey(path)) dependencies.delete(path);
|
|
2375
2529
|
return dependencies;
|
|
2376
2530
|
}
|
|
2377
|
-
function records(entries) {
|
|
2531
|
+
function records$1(entries) {
|
|
2378
2532
|
return (Array.isArray(entries) ? entries : []).filter((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry));
|
|
2379
2533
|
}
|
|
2380
2534
|
//#endregion
|
|
@@ -2385,19 +2539,27 @@ function planThemeUploads(themeRoot, changedFiles, deletedPaths = []) {
|
|
|
2385
2539
|
const localFiles = themeRoot.files();
|
|
2386
2540
|
const manifest = localFiles.find((file) => file.relativePath === MEMBER_CONFIGURATION_KEY);
|
|
2387
2541
|
if (!manifest || themeRoot.ignore.ignore("members_sites.json")) return individualUploads(changedFiles);
|
|
2388
|
-
const
|
|
2389
|
-
const
|
|
2390
|
-
if (Array.isArray(
|
|
2391
|
-
if (!isRecord$3(
|
|
2542
|
+
const manifestContent = manifest.read();
|
|
2543
|
+
const parsed = JSON.parse(manifestContent);
|
|
2544
|
+
if (Array.isArray(parsed)) return individualUploads(changedFiles);
|
|
2545
|
+
if (!isRecord$3(parsed)) throw new Error("members_sites.json must contain a JSON object.");
|
|
2546
|
+
const { document, removed } = withoutRetiredTemplates(parsed);
|
|
2547
|
+
const content = removed.length > 0 ? `${JSON.stringify(document, null, 2)}\n` : manifestContent;
|
|
2392
2548
|
const dependencies = memberDependencyPaths(document, new Set(localFiles.map((file) => file.relativePath)), deletedPaths, new Set(changedFiles.map((file) => file.relativePath)));
|
|
2393
2549
|
for (const path of deletedPaths) if (dependencies.has(path)) throw new Error(`Member dependency is still declared and cannot be deleted: ${path}`);
|
|
2394
|
-
|
|
2550
|
+
const memberChanged = changedFiles.some((file) => file.relativePath === "members_sites.json" || dependencies.has(file.relativePath));
|
|
2551
|
+
if (!memberChanged && removed.length === 0) return changedFiles.map((file) => ({ file }));
|
|
2395
2552
|
const availableFiles = new Map(localFiles.map((file) => [file.relativePath, file]));
|
|
2396
|
-
const
|
|
2553
|
+
const unavailable = [...dependencies].find((path) => {
|
|
2397
2554
|
const file = availableFiles.get(path);
|
|
2398
|
-
|
|
2399
|
-
return file;
|
|
2555
|
+
return !file || !file.isText || themeRoot.ignore.ignore(path);
|
|
2400
2556
|
});
|
|
2557
|
+
if (unavailable) {
|
|
2558
|
+
if (!memberChanged) return changedFiles.map((file) => ({ file }));
|
|
2559
|
+
throw new Error(`Member dependency must be an available text file: ${unavailable}`);
|
|
2560
|
+
}
|
|
2561
|
+
const memberFiles = [...dependencies].map((path) => availableFiles.get(path));
|
|
2562
|
+
if (removed.length > 0) manifest.write(content);
|
|
2401
2563
|
const uploads = changedFiles.filter((file) => file.relativePath !== "members_sites.json" && !dependencies.has(file.relativePath)).map((file) => ({ file }));
|
|
2402
2564
|
uploads.push({
|
|
2403
2565
|
file: manifest,
|
|
@@ -2408,6 +2570,43 @@ function planThemeUploads(themeRoot, changedFiles, deletedPaths = []) {
|
|
|
2408
2570
|
});
|
|
2409
2571
|
return uploads;
|
|
2410
2572
|
}
|
|
2573
|
+
/**
|
|
2574
|
+
* Drop references to retired template types. The server rejects the whole
|
|
2575
|
+
* document for one, which would also hold back every member site and menu it
|
|
2576
|
+
* carries.
|
|
2577
|
+
*
|
|
2578
|
+
* Only `templates` and `menu_references` can name one: a site page's template
|
|
2579
|
+
* must be a `members_page`. A menu left without any reference is dropped with
|
|
2580
|
+
* its reference, because the server rejects unreferenced menus too.
|
|
2581
|
+
*/
|
|
2582
|
+
function withoutRetiredTemplates(document) {
|
|
2583
|
+
const removed = /* @__PURE__ */ new Set();
|
|
2584
|
+
const orphanCandidates = /* @__PURE__ */ new Set();
|
|
2585
|
+
const keep = (entry) => {
|
|
2586
|
+
if (!isRecord$3(entry) || typeof entry.path !== "string") return true;
|
|
2587
|
+
if (!isRetiredTemplateKey(entry.path)) return true;
|
|
2588
|
+
removed.add(entry.path);
|
|
2589
|
+
if ("menu_key" in entry) orphanCandidates.add(entry.menu_key);
|
|
2590
|
+
return false;
|
|
2591
|
+
};
|
|
2592
|
+
const next = { ...document };
|
|
2593
|
+
for (const key of ["templates", "menu_references"]) if (Array.isArray(next[key])) next[key] = next[key].filter(keep);
|
|
2594
|
+
if (removed.size === 0) return {
|
|
2595
|
+
document,
|
|
2596
|
+
removed: []
|
|
2597
|
+
};
|
|
2598
|
+
if (orphanCandidates.size > 0 && Array.isArray(next.menus)) {
|
|
2599
|
+
const used = new Set([...records(next.sites).map((site) => site.menu_key), ...records(next.menu_references).map((entry) => entry.menu_key)]);
|
|
2600
|
+
next.menus = next.menus.filter((menu) => !isRecord$3(menu) || !orphanCandidates.has(menu.key) || used.has(menu.key));
|
|
2601
|
+
}
|
|
2602
|
+
return {
|
|
2603
|
+
document: next,
|
|
2604
|
+
removed: [...removed]
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
function records(entries) {
|
|
2608
|
+
return Array.isArray(entries) ? entries.filter(isRecord$3) : [];
|
|
2609
|
+
}
|
|
2411
2610
|
function individualUploads(files) {
|
|
2412
2611
|
return [...files].sort((left, right) => Number(left.relativePath === MEMBER_CONFIGURATION_KEY) - Number(right.relativePath === MEMBER_CONFIGURATION_KEY)).map((file) => ({ file }));
|
|
2413
2612
|
}
|
|
@@ -3193,7 +3392,7 @@ var Syncer = class {
|
|
|
3193
3392
|
if (opts.remoteState) this.useDevRemoteState(opts.remoteState);
|
|
3194
3393
|
else await this.fetchChecksums();
|
|
3195
3394
|
if (!opts.skipPreflight) await this.preflightPush(opts.baseSha);
|
|
3196
|
-
let baseSha = opts.baseSha ?? null;
|
|
3395
|
+
let baseSha = opts.baseSha ?? (opts.guardFetchedRemote && !opts.remoteState ? this.remoteSha() : null);
|
|
3197
3396
|
const localPaths = new Set(localFiles.map((file) => file.relativePath));
|
|
3198
3397
|
if (opts.delete) for (const key of this.assetManifest.keys()) localPaths.add(key);
|
|
3199
3398
|
const toDelete = opts.delete ? this.remoteKeys().filter((key) => key !== "members_sites.json" && this.canDeleteRemoteResource(key) && !localPaths.has(key) && !this.themeRoot.ignore.ignore(key)) : [];
|
|
@@ -3582,6 +3781,7 @@ async function startDevServer(api, theme, themeRoot, opts, onReady) {
|
|
|
3582
3781
|
const sse = new SSEStream();
|
|
3583
3782
|
const memberPreview = new MemberPreview(api, theme, opts.reloadMode);
|
|
3584
3783
|
const syncer = new Syncer(api, theme.id, themeRoot);
|
|
3784
|
+
const templateOverrides = createTemplateOverrideResolver(api, theme.id);
|
|
3585
3785
|
let remoteStateSafe = true;
|
|
3586
3786
|
const invalidateRemoteState = () => {
|
|
3587
3787
|
if (!remoteStateSafe) return;
|
|
@@ -3767,6 +3967,7 @@ async function startDevServer(api, theme, themeRoot, opts, onReady) {
|
|
|
3767
3967
|
console.error("[Member preview] Could not refresh routes after sync. The next member request will retry.");
|
|
3768
3968
|
}
|
|
3769
3969
|
}
|
|
3970
|
+
if (added.length > 0 || removed.length > 0) templateOverrides.clear();
|
|
3770
3971
|
if (removed.length > 0) sse.broadcast(JSON.stringify({ reload_page: true }));
|
|
3771
3972
|
else if (uploads.length > 0) sse.broadcast(JSON.stringify({ modified: uploads.flatMap(({ file, memberConfiguration }) => [file, ...memberConfiguration?.files ?? []].map((updatedFile) => updatedFile.relativePath)) }));
|
|
3772
3973
|
scheduleSync();
|
|
@@ -3787,6 +3988,7 @@ async function startDevServer(api, theme, themeRoot, opts, onReady) {
|
|
|
3787
3988
|
company: theme.company,
|
|
3788
3989
|
themeId: theme.id,
|
|
3789
3990
|
reloadMode: opts.reloadMode,
|
|
3991
|
+
templateOverrides,
|
|
3790
3992
|
pendingFiles: () => [...pendingUpdates].map((p) => themeRoot.file(p)).filter((f) => f.isText).map((f) => ({
|
|
3791
3993
|
relativePath: f.relativePath,
|
|
3792
3994
|
read: () => f.read()
|
|
@@ -4627,6 +4829,78 @@ function createDevCommand() {
|
|
|
4627
4829
|
});
|
|
4628
4830
|
}
|
|
4629
4831
|
//#endregion
|
|
4832
|
+
//#region src/theme/development-push.ts
|
|
4833
|
+
/**
|
|
4834
|
+
* Push a project to a development theme without touching the project's own
|
|
4835
|
+
* sync state.
|
|
4836
|
+
*
|
|
4837
|
+
* A project directory keeps one shadow repo and one `.fluid-theme.json`, both
|
|
4838
|
+
* for the theme it is bound to. Opening the shadow repo for another theme
|
|
4839
|
+
* wipes it, so an ordinary push here would cost the bound theme its baseline:
|
|
4840
|
+
* the next Publish could no longer detect deletions or stale remotes. This
|
|
4841
|
+
* syncs the way `fluid theme dev` does instead: against the development
|
|
4842
|
+
* theme's own remote index and `.fluid-theme/dev-baseline.json`, which it
|
|
4843
|
+
* keeps current for the next dev session.
|
|
4844
|
+
*/
|
|
4845
|
+
async function pushToDevelopmentTheme(api, themeId, themeRoot, opts) {
|
|
4846
|
+
const syncer = new Syncer(api, themeId, themeRoot);
|
|
4847
|
+
const fingerprint = () => new ThemeAssetManifest(themeRoot.root).fingerprint();
|
|
4848
|
+
const common = {
|
|
4849
|
+
delete: opts.delete,
|
|
4850
|
+
validate: opts.validate,
|
|
4851
|
+
linkManagedAssets: { replace: true },
|
|
4852
|
+
pendingBinaryAssets: true,
|
|
4853
|
+
...opts.onProgress ? { onProgress: opts.onProgress } : {}
|
|
4854
|
+
};
|
|
4855
|
+
const saved = readDevRemoteBaseline(themeRoot.root, themeId, fingerprint());
|
|
4856
|
+
let trusted = false;
|
|
4857
|
+
if (saved) try {
|
|
4858
|
+
syncer.useDevRemoteState(saved);
|
|
4859
|
+
await syncer.preflightPush(saved.remoteSha);
|
|
4860
|
+
trusted = true;
|
|
4861
|
+
} catch (error) {
|
|
4862
|
+
if (!(error instanceof PushConflictError)) throw error;
|
|
4863
|
+
}
|
|
4864
|
+
let result;
|
|
4865
|
+
try {
|
|
4866
|
+
result = trusted && saved ? await syncer.uploadTheme({
|
|
4867
|
+
...common,
|
|
4868
|
+
baseSha: syncer.remoteSha(),
|
|
4869
|
+
remoteState: saved,
|
|
4870
|
+
skipPreflight: true
|
|
4871
|
+
}) : await syncer.uploadTheme({
|
|
4872
|
+
...common,
|
|
4873
|
+
guardFetchedRemote: true
|
|
4874
|
+
});
|
|
4875
|
+
} catch (error) {
|
|
4876
|
+
if (!(error instanceof PushConflictError)) throw error;
|
|
4877
|
+
removeDevRemoteBaseline(themeRoot.root);
|
|
4878
|
+
return {
|
|
4879
|
+
uploaded: 0,
|
|
4880
|
+
deleted: 0,
|
|
4881
|
+
linked: 0,
|
|
4882
|
+
errors: ["The development theme changed during this push. Run the push again to compare against its current state."],
|
|
4883
|
+
validationFailed: false,
|
|
4884
|
+
conflict: true
|
|
4885
|
+
};
|
|
4886
|
+
}
|
|
4887
|
+
const errors = [...result.errors];
|
|
4888
|
+
if (result.validationFailed || errors.length > 0) removeDevRemoteBaseline(themeRoot.root);
|
|
4889
|
+
else if (!await syncer.requestSync()) errors.push("Fluid did not accept sync finalization. Retry the push to commit it.");
|
|
4890
|
+
else {
|
|
4891
|
+
const state = syncer.devRemoteState(fingerprint());
|
|
4892
|
+
if (state) writeDevRemoteBaseline(themeRoot.root, state);
|
|
4893
|
+
}
|
|
4894
|
+
return {
|
|
4895
|
+
uploaded: result.uploaded,
|
|
4896
|
+
deleted: result.deleted,
|
|
4897
|
+
linked: result.linked,
|
|
4898
|
+
errors,
|
|
4899
|
+
validationFailed: result.validationFailed,
|
|
4900
|
+
conflict: false
|
|
4901
|
+
};
|
|
4902
|
+
}
|
|
4903
|
+
//#endregion
|
|
4630
4904
|
//#region src/theme/merge-push.ts
|
|
4631
4905
|
/**
|
|
4632
4906
|
* Compute what changed locally since the last time the shadow repo
|
|
@@ -4969,7 +5243,39 @@ function createPushCommand() {
|
|
|
4969
5243
|
else if (config) {
|
|
4970
5244
|
console.log(` Using theme from .fluid-theme.json: ${chalk.bold(config.themeName)} (#${config.themeId})`);
|
|
4971
5245
|
theme = (await getApplicationTheme(api, config.themeId)).application_theme;
|
|
5246
|
+
if (theme.status === "development") console.warn(chalk.yellow(` ${theme.name} (#${theme.id}) is a development theme: a scratch copy made by \`fluid theme dev\`, not a theme your storefront serves. This push updates only that copy. To update the real theme, run \`fluid theme pull -t <theme-id>\` here to bind the project to it, or pass --theme.`));
|
|
4972
5247
|
} else theme = await selectTheme(api, "Select a theme to push to");
|
|
5248
|
+
if (config && !mayBindTheme(config, theme)) {
|
|
5249
|
+
const own = getDevTheme(devThemeKey(config.company, themeRoot.root));
|
|
5250
|
+
if (own?.id !== theme.id) {
|
|
5251
|
+
console.error(`${theme.name} (#${theme.id}) is a development theme, but not this project's` + (own ? ` (that is #${own.id})` : "") + `. Pushing would overwrite it with this directory. Run \`fluid theme dev\` here to sync this project's own development theme, or \`fluid theme dev --theme ${theme.id}\` to sync this one deliberately.`);
|
|
5252
|
+
process.exit(1);
|
|
5253
|
+
}
|
|
5254
|
+
const unresolvedDev = findUnresolvedConflicts(themeRoot.files());
|
|
5255
|
+
if (unresolvedDev.length > 0) {
|
|
5256
|
+
console.error(`✗ ${unresolvedDev.length} file(s) still contain unresolved conflict markers: ${unresolvedDev.join(", ")}. Resolve them before pushing.`);
|
|
5257
|
+
process.exit(1);
|
|
5258
|
+
}
|
|
5259
|
+
if (opts.publish) {
|
|
5260
|
+
console.error(`${theme.name} (#${theme.id}) is a development theme; --publish would make that scratch copy the live theme. Push without --publish, or publish ${config.themeName} (#${config.themeId}).`);
|
|
5261
|
+
process.exit(1);
|
|
5262
|
+
}
|
|
5263
|
+
const devSpinner = ora(`Pushing to development theme ${theme.name} (#${theme.id})…`).start();
|
|
5264
|
+
const result = await pushToDevelopmentTheme(api, theme.id, themeRoot, {
|
|
5265
|
+
validate: !opts.force,
|
|
5266
|
+
delete: !opts.nodelete && !opts.autoBaseline,
|
|
5267
|
+
onProgress: (done, total) => {
|
|
5268
|
+
devSpinner.text = `Pushing ${done}/${total} files to ${theme.name} (#${theme.id})…`;
|
|
5269
|
+
}
|
|
5270
|
+
});
|
|
5271
|
+
if (result.validationFailed) devSpinner.fail(`Schema validation failed (${result.errors.length} error(s)). Use --force to skip.`);
|
|
5272
|
+
else if (result.errors.length > 0) devSpinner.warn(`Pushed with ${result.errors.length} error(s).`);
|
|
5273
|
+
else devSpinner.succeed(`Pushed ${result.uploaded} file(s), removed ${result.deleted}, to development theme #${theme.id}.`);
|
|
5274
|
+
for (const e of result.errors) console.error(` ${e}`);
|
|
5275
|
+
console.log(` This project stays bound to ${config.themeName} (#${config.themeId}); Publish still pushes there.`);
|
|
5276
|
+
if (result.errors.length > 0) process.exitCode = 1;
|
|
5277
|
+
return;
|
|
5278
|
+
}
|
|
4973
5279
|
const shadow = await ShadowRepo.open(themeRoot.root, theme.id);
|
|
4974
5280
|
await migrateLegacyChecksumsIntoShadow({
|
|
4975
5281
|
shadow,
|
|
@@ -5546,6 +5852,14 @@ function createPullCommand() {
|
|
|
5546
5852
|
else root = `.`;
|
|
5547
5853
|
const absoluteRoot = resolve(root);
|
|
5548
5854
|
const existingConfig = readThemeConfig(absoluteRoot);
|
|
5855
|
+
if (existingConfig && !mayBindTheme(existingConfig, theme)) {
|
|
5856
|
+
if (opts.onlyIfClean) {
|
|
5857
|
+
console.log(`Skipped: this directory is bound to ${existingConfig.themeName} (#${existingConfig.themeId}), not development theme #${theme.id}.`);
|
|
5858
|
+
process.exit(3);
|
|
5859
|
+
}
|
|
5860
|
+
console.error(developmentThemeRefusal(existingConfig, theme));
|
|
5861
|
+
process.exit(1);
|
|
5862
|
+
}
|
|
5549
5863
|
const backgroundGuard = opts.onlyIfClean ? new BackgroundPullGuard(absoluteRoot) : void 0;
|
|
5550
5864
|
console.log();
|
|
5551
5865
|
console.log(` Theme: ${chalk.bold(theme.name)} (#${theme.id})`);
|