cloudcommerce 2.4.2 → 2.5.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.
Files changed (77) hide show
  1. package/.github/workflows/test-apps.yml +2 -2
  2. package/CHANGELOG.md +28 -0
  3. package/action.yml +3 -3
  4. package/ecomplus-stores/barradoce/functions/many/package.json +3 -3
  5. package/ecomplus-stores/barradoce/functions/ssr/package.json +10 -8
  6. package/ecomplus-stores/barradoce/functions/ssr/src/components/Pagination.vue +4 -7
  7. package/ecomplus-stores/barradoce/functions/ssr/src/components/ProductDetails.vue +1 -1
  8. package/ecomplus-stores/barradoce/functions/ssr/src/layouts/PageHeader.astro +5 -4
  9. package/ecomplus-stores/barradoce/functions/ssr/src/main/content/Hero.astro +1 -1
  10. package/ecomplus-stores/barradoce/functions/ssr/src/main/content/Sections.astro +10 -7
  11. package/ecomplus-stores/barradoce/functions/ssr/src/pages/admin/index.astro +25 -0
  12. package/ecomplus-stores/barradoce/functions/with-apps/package.json +3 -3
  13. package/ecomplus-stores/barradoce/package.json +2 -2
  14. package/package.json +10 -10
  15. package/packages/api/package.json +1 -1
  16. package/packages/apps/affiliate-program/package.json +1 -1
  17. package/packages/apps/correios/package.json +2 -2
  18. package/packages/apps/custom-payment/package.json +1 -1
  19. package/packages/apps/custom-shipping/package.json +1 -1
  20. package/packages/apps/datafrete/package.json +1 -1
  21. package/packages/apps/discounts/package.json +1 -1
  22. package/packages/apps/emails/package.json +1 -1
  23. package/packages/apps/fb-conversions/package.json +1 -1
  24. package/packages/apps/flash-courier/package.json +1 -1
  25. package/packages/apps/frenet/package.json +1 -1
  26. package/packages/apps/galaxpay/package.json +1 -1
  27. package/packages/apps/google-analytics/package.json +1 -1
  28. package/packages/apps/jadlog/package.json +1 -1
  29. package/packages/apps/loyalty-points/package.json +1 -1
  30. package/packages/apps/mandae/package.json +1 -1
  31. package/packages/apps/melhor-envio/package.json +1 -1
  32. package/packages/apps/mercadopago/package.json +1 -1
  33. package/packages/apps/pagarme/package.json +1 -1
  34. package/packages/apps/pagarme-v5/package.json +1 -1
  35. package/packages/apps/paghiper/package.json +1 -1
  36. package/packages/apps/pix/package.json +1 -1
  37. package/packages/apps/tiny-erp/package.json +1 -1
  38. package/packages/apps/webhooks/package.json +1 -1
  39. package/packages/cli/ci/bunny-config-base.sh +2 -0
  40. package/packages/cli/package.json +2 -2
  41. package/packages/config/package.json +1 -1
  42. package/packages/emails/package.json +1 -1
  43. package/packages/eslint/package.json +3 -3
  44. package/packages/events/package.json +1 -1
  45. package/packages/feeds/package.json +1 -1
  46. package/packages/firebase/package.json +3 -3
  47. package/packages/i18n/package.json +1 -1
  48. package/packages/modules/package.json +1 -1
  49. package/packages/passport/package.json +1 -1
  50. package/packages/ssr/lib/lib/serve-storefront.js +1 -1
  51. package/packages/ssr/lib/lib/serve-storefront.js.map +1 -1
  52. package/packages/ssr/package.json +3 -3
  53. package/packages/ssr/src/lib/serve-storefront.ts +2 -2
  54. package/packages/storefront/astro.config.mjs +5 -1
  55. package/packages/storefront/client.d.ts +3 -0
  56. package/packages/storefront/config/astro/client-sf-directive.mjs +97 -0
  57. package/packages/storefront/config/astro/index.d.ts +9 -2
  58. package/packages/storefront/config/storefront.cms.js +3 -3
  59. package/packages/storefront/package.json +8 -9
  60. package/packages/storefront/src/decap-cms/create-preview-component.ts +135 -0
  61. package/packages/storefront/src/decap-cms/get-cms-config.ts +320 -12
  62. package/packages/storefront/src/lib/assets/decap-cms.css +81 -0
  63. package/packages/storefront/src/lib/components/Drawer.vue +8 -13
  64. package/packages/storefront/src/lib/components/QuantitySelector.vue +4 -9
  65. package/packages/storefront/src/lib/composables/use-pitch-bar.ts +3 -9
  66. package/packages/storefront/src/lib/composables/use-product-shelf.ts +4 -1
  67. package/packages/storefront/src/lib/content.d.ts +19 -4
  68. package/packages/storefront/src/lib/layouts/BaseBody.astro +13 -0
  69. package/packages/storefront/src/lib/layouts/BaseHead.astro +1 -1
  70. package/packages/storefront/src/lib/scripts/decap-cms.ts +37 -29
  71. package/packages/storefront/src/lib/ssr-context.ts +35 -11
  72. package/packages/storefront/src/lib/state/use-cms-preview.ts +88 -27
  73. package/packages/test-base/package.json +1 -1
  74. package/packages/types/package.json +1 -1
  75. package/packages/storefront/config/astro/context-directive.mjs +0 -46
  76. package/packages/storefront/src/decap-cms/gen-preview-container.ts +0 -51
  77. package/packages/storefront/src/decap-cms/preview/webcontainer.ts +0 -104
@@ -93,7 +93,7 @@ window._emitApiContext = (id = null) => {
93
93
  url: $storefront.url,
94
94
  apiContext: $storefront.apiContext,
95
95
  });
96
- console.log('[ctx] emit ' + id);
96
+ console.debug('[ctx] emit ' + id);
97
97
  window.dispatchEvent(new Event('storefront:apiContext'));
98
98
  window._emitedContextId = id;
99
99
  window.__sfIds = {};
@@ -1,15 +1,30 @@
1
1
  import Deepmerge from '@fastify/deepmerge';
2
2
  import afetch from '../../helpers/afetch';
3
3
  import getCmsConfig from '../../decap-cms/get-cms-config';
4
- import genPreviewContainer from '../../decap-cms/gen-preview-container';
4
+ import createPreviewComponent from '../../decap-cms/create-preview-component';
5
5
 
6
- let cmsConfig: Record<string, any> = getCmsConfig();
7
- let ghToken: string | undefined;
6
+ let cmsConfig: Record<string, any> = {};
8
7
  const initCmsWithPreview = () => {
9
- const { React, CMS } = window as any;
8
+ const { createClass, h, CMS } = window as any;
10
9
  CMS.init({ config: cmsConfig });
11
- const Preview = genPreviewContainer({ React, cmsConfig, ghToken });
12
- CMS.registerPreviewTemplate('general', Preview);
10
+ if (createClass && h) {
11
+ const Preview = createPreviewComponent({
12
+ createClass,
13
+ h,
14
+ cmsConfig,
15
+ });
16
+ cmsConfig.collections.forEach(({ name, files }) => {
17
+ const char = name.charAt(0);
18
+ if (char === '_' || char === '.') return;
19
+ if (files) {
20
+ files.forEach((file: Record<string, any>) => {
21
+ CMS.registerPreviewTemplate(file.name, Preview);
22
+ });
23
+ return;
24
+ }
25
+ CMS.registerPreviewTemplate(name, Preview);
26
+ });
27
+ }
13
28
  };
14
29
 
15
30
  const authAndInitCms = async () => {
@@ -18,8 +33,14 @@ const authAndInitCms = async () => {
18
33
  sessionStorage,
19
34
  ECOM_STORE_ID,
20
35
  GIT_REPO,
36
+ CMS_CUSTOM_CONFIG,
21
37
  CMS_SSO_URL = 'https://app.e-com.plus/pages/login?api_version=2',
22
38
  } = window;
39
+ cmsConfig = getCmsConfig();
40
+ if (CMS_CUSTOM_CONFIG) {
41
+ const deepmerge = Deepmerge();
42
+ cmsConfig = deepmerge(cmsConfig, CMS_CUSTOM_CONFIG);
43
+ }
23
44
  if (import.meta.env.DEV) {
24
45
  cmsConfig.local_backend = true;
25
46
  cmsConfig.backend = {
@@ -60,7 +81,7 @@ const authAndInitCms = async () => {
60
81
  ...cmsConfig.backend,
61
82
  };
62
83
  if (cmsConfig.backend.api_root?.startsWith('https://ecomplus.app/')) {
63
- const res = await afetch('https://ecomplus.app/api/github-installation', {
84
+ const res = await afetch('https://ecomplus.app/api/github-installations', {
64
85
  headers: {
65
86
  'X-Store-ID': `${ECOM_STORE_ID}`,
66
87
  Authorization: `Bearer ${ssoToken}`,
@@ -76,7 +97,7 @@ const authAndInitCms = async () => {
76
97
  if (installation?.gh_token && installation.gh_token.charAt(0) !== '*') {
77
98
  // Consume GitHub REST API directly
78
99
  token = installation.gh_token as string;
79
- ghToken = token;
100
+ // ghToken = token;
80
101
  delete cmsConfig.backend.api_root;
81
102
  cmsConfig.backend.repo = installation.repository;
82
103
  cmsConfig.backend.name = 'github';
@@ -118,26 +139,13 @@ const authAndInitCms = async () => {
118
139
  };
119
140
 
120
141
  if (!import.meta.env.SSR) {
121
- if (window.CMS_CUSTOM_CONFIG) {
122
- const deepmerge = Deepmerge();
123
- cmsConfig = deepmerge(cmsConfig, window.CMS_CUSTOM_CONFIG);
124
- }
125
142
  (window as any).CMS_MANUAL_INIT = true;
126
- /* eslint-disable import/no-unresolved */
127
- Promise.all([
128
- // @ts-ignore
129
- import(/* @vite-ignore */ 'https://esm.sh/react@^18'),
130
- // @ts-ignore
131
- import(/* @vite-ignore */ 'https://esm.sh/decap-cms-app@^3'),
132
- ])
133
- .then(([React, { default: CMS }]) => {
134
- (window as any).React = React;
135
- (window as any).CMS = CMS;
136
- authAndInitCms();
137
- })
138
- .catch((err) => {
139
- console.error(err);
140
- // eslint-disable-next-line
141
- window.alert('Failed importing Decap CMS app or preview dependencies');
142
- });
143
+ if (window.CMS) {
144
+ authAndInitCms();
145
+ } else {
146
+ const cmsScript = document.createElement('script');
147
+ cmsScript.src = 'https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js';
148
+ cmsScript.onload = authAndInitCms;
149
+ document.body.appendChild(cmsScript);
150
+ }
143
151
  }
@@ -118,7 +118,27 @@ const loadRouteContext = async (
118
118
  let urlPath = Astro.url.pathname;
119
119
  const isPreview = urlPath.startsWith('/~preview');
120
120
  if (isPreview) {
121
- urlPath = urlPath.replace('/~preview', '');
121
+ urlPath = urlPath.replace(/^\/~[^/]+\/?/, '/');
122
+ const getQueryContent = (filename: string) => {
123
+ const contentJson = Astro.url.searchParams.get(`content:${filename}`);
124
+ if (typeof contentJson === 'string') {
125
+ try {
126
+ const content = JSON.parse(contentJson);
127
+ return content;
128
+ } catch {
129
+ //
130
+ }
131
+ }
132
+ return null;
133
+ };
134
+ global.$storefrontCmsHandler = ({ filename, loadLocal }) => {
135
+ const content = getQueryContent(filename);
136
+ if (filename === 'settings') return content || loadLocal();
137
+ return new Promise((resolve) => {
138
+ if (content) resolve(content);
139
+ loadLocal().then(resolve);
140
+ });
141
+ };
122
142
  }
123
143
  const isHomepage = urlPath === '/';
124
144
  const isSearchPage = !isHomepage && (urlPath.startsWith('/s/') || urlPath === '/s');
@@ -131,7 +151,9 @@ const loadRouteContext = async (
131
151
  }
132
152
  const config = getConfig();
133
153
  globalThis.$storefront.settings = config.settings;
134
- let cmsContent: PageContent | null = { sections: [] };
154
+ let cmsContent: PageContent & { $filename?: `${string}/${string}` } | null = {
155
+ sections: [],
156
+ };
135
157
  const apiState: {
136
158
  categories?: CategoriesList,
137
159
  brands?: BrandsList,
@@ -157,13 +179,14 @@ const loadRouteContext = async (
157
179
  error: null,
158
180
  };
159
181
  const { slug } = Astro.params;
182
+ let contentFilename: `${string}/${string}` | undefined;
160
183
  if (isHomepage) {
161
- cmsContent = await config.getContent('pages/home');
184
+ contentFilename = 'pages/home';
162
185
  } else if (isSearchPage) {
163
- cmsContent = await config.getContent('pages/search');
186
+ contentFilename = 'pages/search';
164
187
  } else if (slug && typeof slug === 'string') {
165
188
  if (contentCollection) {
166
- cmsContent = await config.getContent(`${contentCollection}/${slug}`);
189
+ contentFilename = `${contentCollection}/${slug}`;
167
190
  } else if (slug.startsWith('_api/') || slug === '_analytics') {
168
191
  const err: any = new Error(`/${slug} route not implemented on SSR directly`);
169
192
  Astro.response.status = 501;
@@ -177,10 +200,12 @@ const loadRouteContext = async (
177
200
  Object.assign(apiContext, response.data);
178
201
  const apiResource = apiContext.resource as
179
202
  Exclude<typeof apiContext.resource, undefined>;
180
- config.getContent(`pages/${apiResource}`)
203
+ contentFilename = `pages/${apiResource}`;
204
+ config.getContent(contentFilename)
181
205
  .then((_cmsContent) => {
182
206
  if (cmsContent && _cmsContent) {
183
207
  Object.assign(cmsContent, _cmsContent);
208
+ cmsContent.$filename = contentFilename;
184
209
  }
185
210
  })
186
211
  .catch(console.warn);
@@ -212,6 +237,10 @@ const loadRouteContext = async (
212
237
  }
213
238
  }
214
239
  }
240
+ if (contentFilename) {
241
+ cmsContent = await config.getContent(contentFilename);
242
+ if (cmsContent) cmsContent.$filename = contentFilename;
243
+ }
215
244
  try {
216
245
  (await Promise.all(apiPrefetchings)).forEach((response) => {
217
246
  if (response) {
@@ -267,11 +296,6 @@ const loadRouteContext = async (
267
296
  } else {
268
297
  setResponseCache(Astro, 120, 180);
269
298
  }
270
- if (isPreview || urlPath.startsWith('/admin/')) {
271
- // https://webcontainers.io/guides/configuring-headers#configuring-headers
272
- Astro.response.headers.set('Cross-Origin-Embedder-Policy', 'require-corp');
273
- Astro.response.headers.set('Cross-Origin-Opener-Policy', 'same-origin');
274
- }
275
299
  const routeContext = {
276
300
  ...config,
277
301
  isHomepage,
@@ -1,38 +1,99 @@
1
- import type { ContentFilename, ContentData } from '@@sf/content';
2
- import { shallowRef } from 'vue';
1
+ import type { Ref, ShallowRef, ShallowReactive } from 'vue';
2
+ import type { ContentFilename, ContentData as _ContentData } from '@@sf/content';
3
+ import { watch, shallowRef } from 'vue';
3
4
 
4
- function getCmsUpdates<T extends ContentFilename>(
5
- filename: T,
6
- cb: (newData: NonNullable<ContentData<T>>) => void,
7
- ) {
8
- if (!import.meta.env.SSR) {
5
+ type ContentData<T extends ContentFilename> = NonNullable<_ContentData<T>>;
6
+
7
+ type SubfieldContentKeys<
8
+ T extends ContentFilename,
9
+ F extends undefined | keyof ContentData<T>,
10
+ > = F extends keyof ContentData<T> ? keyof ContentData<T>[F] : undefined;
11
+
12
+ type NestedContentData<
13
+ T extends ContentFilename,
14
+ F extends undefined | keyof ContentData<T>,
15
+ S extends SubfieldContentKeys<T, F>,
16
+ > = F extends keyof ContentData<T>
17
+ ? S extends keyof ContentData<T>[F]
18
+ ? ContentData<T>[F][S] : ContentData<T>[F]
19
+ : ContentData<T>;
20
+
21
+ export function useCmsPreview<
22
+ T extends ContentFilename,
23
+ F extends keyof ContentData<T>,
24
+ S extends SubfieldContentKeys<T, F>,
25
+ >(filename: T | [T] | [T, F] | [T, F, S]) {
26
+ let field: F | undefined;
27
+ let subfield: S | undefined;
28
+ if (Array.isArray(filename)) {
29
+ field = filename[1];
30
+ subfield = filename[2];
31
+ filename = filename[0];
32
+ }
33
+ const liveContent = shallowRef<NestedContentData<T, F, S> | undefined>();
34
+ if (!import.meta.env.SSR && window.$isCmsPreview) {
9
35
  const id = btoa(JSON.stringify({ filename }));
10
- window.parent.postMessage(
11
- JSON.parse(JSON.stringify({
12
- type: 'open',
13
- filename,
14
- id,
15
- })),
16
- window.location.origin,
17
- );
36
+ const openMessage = {
37
+ type: 'open',
38
+ filename,
39
+ field,
40
+ subfield,
41
+ id,
42
+ };
43
+ window.parent.postMessage(openMessage, window.location.origin);
18
44
  window.addEventListener('message', (event) => {
19
45
  if (event.data.id === id) {
20
- cb(event.data.data);
46
+ if (field) {
47
+ liveContent.value = subfield
48
+ ? event.data.data[field][subfield]
49
+ : event.data.data[field];
50
+ } else {
51
+ liveContent.value = event.data.data;
52
+ }
21
53
  }
22
54
  });
23
55
  }
24
- }
25
-
26
- const useCmsPreview = <T extends ContentFilename>(filename: T) => {
27
- const liveContent = shallowRef<NonNullable<ContentData<T>> | null>(null);
28
- if (!import.meta.env.SSR && window.$isCmsPreview) {
29
- getCmsUpdates(filename, (newData) => {
30
- liveContent.value = newData;
31
- });
32
- }
33
56
  return { liveContent };
34
- };
57
+ }
35
58
 
36
59
  export default useCmsPreview;
37
60
 
38
- export { useCmsPreview, getCmsUpdates };
61
+ export interface SectionPreviewProps {
62
+ cmsPreview?: {
63
+ contentFilename: `${string}/${string}`;
64
+ sectionIndex: number;
65
+ };
66
+ }
67
+
68
+ export const useSectionPreview = (
69
+ { cmsPreview }: SectionPreviewProps,
70
+ refs?: Record<string, Ref<any> | ShallowRef<any> | ShallowReactive<any>>,
71
+ ) => {
72
+ if (cmsPreview) {
73
+ const { contentFilename, sectionIndex } = cmsPreview;
74
+ const {
75
+ liveContent,
76
+ } = useCmsPreview([contentFilename, 'sections', sectionIndex]);
77
+ if (refs) {
78
+ watch(liveContent, (data) => {
79
+ if (!data) return;
80
+ Object.keys(refs).forEach((key) => {
81
+ if (refs[key].value !== undefined) {
82
+ if (data[key] === undefined) return;
83
+ refs[key].value = data[key];
84
+ return;
85
+ }
86
+ if (!data[key]) return;
87
+ if (Array.isArray(refs[key])) {
88
+ refs[key].splice(0);
89
+ data[key].forEach((v: any) => refs[key].push(v));
90
+ return;
91
+ }
92
+ Object.assign(refs[key], data[key]);
93
+ });
94
+ });
95
+ }
96
+ return { liveContent };
97
+ }
98
+ return null;
99
+ };
@@ -2,7 +2,7 @@
2
2
  "name": "@cloudcommerce/test-base",
3
3
  "private": true,
4
4
  "type": "module",
5
- "version": "2.4.2",
5
+ "version": "2.5.0",
6
6
  "description": "E-Com Plus Cloud Commerce basic setup for testing",
7
7
  "main": "lib/index.js",
8
8
  "repository": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudcommerce/types",
3
3
  "type": "module",
4
- "version": "2.4.2",
4
+ "version": "2.5.0",
5
5
  "description": "E-Com Plus Cloud Commerce reusable type definitions",
6
6
  "main": "index.ts",
7
7
  "repository": {
@@ -1,46 +0,0 @@
1
- /**
2
- * Hydrate on context script executed (`$storefront.apiContext` ready)
3
- * Check event emits at BaseHead.astro and use-shared-data.ts
4
- * @type {import('astro').ClientDirective}
5
- */
6
- export default (load, opts) => {
7
- const hy = async () => {
8
- const hydrate = await load();
9
- await hydrate();
10
- };
11
- const next = () => {
12
- const arrOpts = Array.isArray(opts.value) ? opts.value : [opts.value];
13
- for (let i = 0; i < arrOpts.length; i++) {
14
- if (typeof arrOpts[i] === 'string' && arrOpts[i].startsWith('data:')) {
15
- const field = arrOpts[i].substring(5);
16
- if (!window.$storefront?.data?.[field]) {
17
- window.addEventListener(
18
- `storefront:data:${field}`,
19
- next,
20
- { once: true },
21
- );
22
- return;
23
- }
24
- }
25
- }
26
- if (arrOpts.includes('idle')) {
27
- if (typeof window.requestIdleCallback === 'function') {
28
- setTimeout(() => window.requestIdleCallback(hy), 9);
29
- return;
30
- }
31
- setTimeout(hy, 200);
32
- return;
33
- }
34
- hy();
35
- };
36
- const id = window.$storefront?.apiContext?.doc._id || null;
37
- if (window._firstLoadContextId === id && window._emitedContextId === id) {
38
- console.log('[ctx] first load');
39
- next();
40
- document.addEventListener('astro:beforeload', () => {
41
- delete window._firstLoadContextId;
42
- }, { once: true });
43
- return;
44
- }
45
- window.addEventListener('storefront:apiContext', next, { once: true });
46
- };
@@ -1,51 +0,0 @@
1
- import type NReact from 'react';
2
- import { initWebcontainer } from './preview/webcontainer';
3
-
4
- export const genPreviewContainer = ({ React, cmsConfig, ghToken }: {
5
- React: typeof NReact,
6
- cmsConfig: Record<string, any>,
7
- ghToken?: string,
8
- }) => {
9
- const { repo } = cmsConfig.backend;
10
- const cliTextareaId = 'webcontainerCli';
11
- const serverIframeId = 'serverPreview';
12
- // https://github.com/decaporg/decap-cms/issues/2183#issuecomment-997373169
13
- return class Prevew extends React.Component {
14
- render() {
15
- const { entry } = (this as any).props;
16
- console.log({ entry });
17
- const slug = '';
18
- const urlPath = `/~preview/${slug}`;
19
- const html = `
20
- <textarea id="${cliTextareaId}"></textarea>
21
- <iframe
22
- if="${serverIframeId}"
23
- data-url="${urlPath}"
24
- border="0"
25
- width="100%"
26
- height="100%"
27
- style="border: 1px solid #EEE; height: calc(100vh - 30px)"
28
- ></iframe>`;
29
- return React.createElement('div', {
30
- dangerouslySetInnerHTML: { __html: html },
31
- });
32
- }
33
- componentDidMount() {
34
- const iframe = document.getElementById('preview-pane') as HTMLIFrameElement;
35
- const cliTextarea = iframe.contentWindow!.document
36
- .getElementById(cliTextareaId) as HTMLTextAreaElement;
37
- const previewIframe = iframe.contentWindow!.document
38
- .getElementById(serverIframeId) as HTMLIFrameElement;
39
- initWebcontainer({ repo, ghToken, cliTextarea })
40
- .then(({ webcontainerInstance, startDevServer }) => {
41
- webcontainerInstance.on('server-ready', (port, url) => {
42
- console.log({ port, url });
43
- previewIframe.src = `${url}${previewIframe.dataset.url}`;
44
- });
45
- startDevServer();
46
- });
47
- }
48
- };
49
- };
50
-
51
- export default genPreviewContainer;
@@ -1,104 +0,0 @@
1
- import { WebContainer } from '@webcontainer/api';
2
-
3
- export const genContainerFiles = ({ repo, ghToken, repoDir }: {
4
- repo: string,
5
- ghToken?: string,
6
- repoDir: string,
7
- }) => ({
8
- 'git.js': {
9
- file: {
10
- contents: `
11
- import fs from 'node:fs';
12
- import { join as joinPath } from 'node:path';
13
- import { clone, pull } from 'isomorphic-git';
14
- import * as http from 'isomorphic-git/http/node/index.cjs';
15
- const dir = joinPath(process.cwd(), '${repoDir}');
16
- const isClone = process.argv.find((val) => val === '--clone');
17
- const options = {
18
- fs,
19
- http,
20
- dir,
21
- singleBranch: true,
22
- };
23
- ${(ghToken
24
- ? `
25
- options.oauth2format = 'github';
26
- options.token = '${ghToken}';`
27
- : '')}
28
- if (isClone) {
29
- options.url = 'https://github.com/${repo}.git';
30
- options.depth = 1;
31
- clone(options);
32
- } else {
33
- pull(options);
34
- }
35
- `,
36
- },
37
- },
38
- 'package.json': {
39
- file: {
40
- contents: `
41
- {
42
- "name": "git-app",
43
- "version": "1.0.0",
44
- "type": "module",
45
- "private": true,
46
- "description": "",
47
- "author": "",
48
- "license": "ISC",
49
- "scripts": {
50
- "git:clone": "node git.js --clone",
51
- "git:pull": "node git.js --pull"
52
- },
53
- "dependencies": {
54
- "isomorphic-git": "^1.25.3"
55
- }
56
- }`,
57
- },
58
- },
59
- });
60
-
61
- export const initWebcontainer = async ({ repo, ghToken, cliTextarea }: {
62
- repo: string,
63
- ghToken?: string,
64
- cliTextarea: HTMLTextAreaElement,
65
- }) => {
66
- const webcontainerInstance = await WebContainer.boot();
67
- const repoDir = 'store';
68
- const files = genContainerFiles({ repo, ghToken, repoDir });
69
- await webcontainerInstance.mount(files);
70
- const exec = async (command: string, args: string[]) => {
71
- const cliArgs = args.reduce((acc, opt) => `${acc} ${opt}`, '');
72
- const cli = `$ ${command}${cliArgs}\n`;
73
- cliTextarea.value += cli;
74
- const proc = await webcontainerInstance.spawn(command, args);
75
- if (import.meta.env.DEV || (window as any).DEBUG) {
76
- proc.output.pipeTo(new WritableStream({
77
- write(stdout) {
78
- console.debug?.('webcontainer', { stdout });
79
- },
80
- }));
81
- }
82
- if (await proc.exit !== 0) {
83
- throw new Error(`${command} failed`);
84
- }
85
- };
86
- await exec('npm', ['install']);
87
- await exec('npm', ['run', 'git:clone']);
88
- const ssrDir = `${repoDir}/functions/ssr`;
89
- await exec('npm', ['--prefix', ssrDir, 'i']);
90
- await webcontainerInstance.fs.writeFile(
91
- `${ssrDir}/.env`,
92
- `ECOM_STORE_ID=${window.ECOM_STORE_ID}\n`,
93
- );
94
- const startDevServer = async () => {
95
- await exec('npm', ['--prefix', ssrDir, 'run', 'dev']);
96
- // Keep restarting dev server (can crash)
97
- startDevServer();
98
- };
99
- return {
100
- webcontainerInstance,
101
- webcontainerExec: exec,
102
- startDevServer,
103
- };
104
- };