@wix/astro 1.0.6 → 1.0.8

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/package.json CHANGED
@@ -1,19 +1,16 @@
1
1
  {
2
2
  "name": "@wix/astro",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "devDependencies": {
5
- "@wix/ambassador-devcenter-components-v1-component": "^1.0.459",
6
- "@wix/cli-app-definitions": "0.0.0",
7
- "@wix/cli-app-manifest": "1.0.0",
8
- "@wix/cli-fs": "0.0.0",
9
5
  "@wix/dashboard": "^1.3.35",
10
6
  "@wix/sdk": "^1.15.17",
11
- "@wix/tsup-configs": "0.0.0",
12
7
  "astro": "^5.7.4",
13
8
  "chalk": "^5.4.1",
14
9
  "chokidar": "^3.6.0",
15
10
  "globby": "^14.1.0",
11
+ "is-ci": "^3.0.1",
16
12
  "outdent": "^0.8.0",
13
+ "tsup": "^8.1.0",
17
14
  "vite": "6.2.6",
18
15
  "zod": "^3.24.2"
19
16
  },
@@ -43,5 +40,5 @@
43
40
  ]
44
41
  }
45
42
  },
46
- "falconPackageHash": "7d39aaf53bfa18a8c04125afb217d147fe1fa7f35dfd5965258dce4b"
43
+ "falconPackageHash": "a0b4b875a11bce17302710fe82a519c9bd729f5c60a2b54157afa577"
47
44
  }
@@ -1,17 +1,6 @@
1
1
  import { join } from 'node:path';
2
2
 
3
3
  const SRC_DIR = 'src';
4
- export const GIT_IGNORED_DIR = '.wix';
5
-
6
- const EXTENSIONS_DIR = join(SRC_DIR, 'extensions');
7
- const DASHBOARD_DIR = join(EXTENSIONS_DIR, 'dashboard');
8
-
9
- export const DASHBOARD_PAGES_DIR = join(DASHBOARD_DIR, 'pages');
10
- export const DASHBOARD_PLUGINS_DIR = join(DASHBOARD_DIR, 'plugins');
11
- export const DASHBOARD_MENU_PLUGINS_DIR = join(DASHBOARD_DIR, 'menu-plugins');
12
- export const DASHBOARD_MODALS_DIR = join(DASHBOARD_DIR, 'modals');
13
4
 
14
- const BACKEND_DIR = join(EXTENSIONS_DIR, 'backend');
15
-
16
- export const EVENTS_DIR = join(BACKEND_DIR, 'events');
17
- export const SERVICE_PLUGINS_DIR = join(BACKEND_DIR, 'service-plugins');
5
+ export const GIT_IGNORED_DIR = '.wix';
6
+ export const EXTENSIONS_DIR = join(SRC_DIR, 'extensions');
package/src/index.ts CHANGED
@@ -1,26 +1,18 @@
1
1
  import { join } from 'node:path';
2
- import type { BuildMetadata } from '@wix/cli-app-definitions';
3
2
  import type { AstroConfig, AstroIntegration } from 'astro';
4
- import { outputDir, writeJson } from '@wix/cli-fs';
5
3
  import { envField, passthroughImageService } from 'astro/config';
6
4
  import chokidar from 'chokidar';
7
5
  import { outdent } from 'outdent';
8
- import type { Model } from './utils/createProjectModel.js';
9
- import {
10
- DASHBOARD_MENU_PLUGINS_DIR,
11
- DASHBOARD_MODALS_DIR,
12
- DASHBOARD_PAGES_DIR,
13
- DASHBOARD_PLUGINS_DIR,
14
- EVENTS_DIR,
15
- GIT_IGNORED_DIR,
16
- SERVICE_PLUGINS_DIR,
17
- } from './directories.js';
6
+ import type { Model } from './types.js';
7
+ import { EXTENSIONS_DIR, GIT_IGNORED_DIR } from './directories.js';
18
8
  import { patchGlobal } from './plugins/patchGlobal.js';
19
9
  import { createProjectModel } from './utils/createProjectModel.js';
10
+ import { outputDir, writeJson } from './utils/fs-utils.js';
20
11
  import { generateAppManifest } from './utils/generateAppManifest.js';
21
12
  import { isValidBackofficeComponent } from './utils/isValidBackofficeComponent.js';
22
13
  import { isValidServicePluginComponent } from './utils/isValidServicePluginComponent.js';
23
14
  import { isValidWebhookComponent } from './utils/isValidWebhookComponent.js';
15
+ import { resolveBuildMetadata } from './utils/resolveBuildMetadata.js';
24
16
  import { writeVirtualBackofficeExtensionFiles } from './utils/writeVirtualBackofficeExtensionFiles.js';
25
17
  import { writeVirtualServicePluginExtensionFiles } from './utils/writeVirtualServicePluginExtensionFiles.js';
26
18
  import { writeVirtualWebhookExtensionFiles } from './utils/writeVirtualWebhookExtensionFiles.js';
@@ -40,23 +32,19 @@ const createIntegration = (): AstroIntegration => {
40
32
  '_wix/app-manifest.json'
41
33
  );
42
34
 
43
- await writeJson(appManifestPath, appManifest);
35
+ await writeJson(appManifestPath, appManifest, { spaces: 2 });
44
36
 
45
37
  await writeJson(
46
38
  join(_config.root.pathname, GIT_IGNORED_DIR, 'build-metadata.json'),
47
- {
48
- appManifestPath,
49
- clientDir: _config.build.client.pathname,
50
- outDir: _config.outDir.pathname,
51
- serverDir: _config.build.server.pathname,
52
- } satisfies BuildMetadata
39
+ await resolveBuildMetadata(appManifestPath, _config),
40
+ { spaces: 2 }
53
41
  );
54
42
  },
55
43
  'astro:config:done': async ({ config }) => {
56
44
  _config = config;
57
45
  },
58
46
  async 'astro:config:setup'({
59
- // addMiddleware,
47
+ addMiddleware,
60
48
  command,
61
49
  config,
62
50
  createCodegenDir,
@@ -126,15 +114,14 @@ const createIntegration = (): AstroIntegration => {
126
114
  `
127
115
  );
128
116
 
129
- // TODO This is commented out until BAAS will add support to env var
130
117
  // support server side context calls
131
- // addMiddleware({
132
- // entrypoint: new URL(
133
- // '../build-runtime/middleware/auth.js',
134
- // import.meta.url
135
- // ),
136
- // order: 'pre',
137
- // });
118
+ addMiddleware({
119
+ entrypoint: new URL(
120
+ '../build-runtime/middleware/auth.js',
121
+ import.meta.url
122
+ ),
123
+ order: 'pre',
124
+ });
138
125
 
139
126
  updateConfig({
140
127
  env: {
@@ -181,7 +168,7 @@ const createIntegration = (): AstroIntegration => {
181
168
  });
182
169
 
183
170
  chokidar
184
- .watch([EVENTS_DIR], {
171
+ .watch([EXTENSIONS_DIR], {
185
172
  cwd: config.root.pathname,
186
173
  ignoreInitial: true,
187
174
  useFsEvents: false,
@@ -192,17 +179,17 @@ const createIntegration = (): AstroIntegration => {
192
179
  });
193
180
  } else {
194
181
  const webhookExtensions = model.extensions.filter((extension) =>
195
- isValidWebhookComponent(extension.config)
182
+ isValidWebhookComponent(extension.manifest)
196
183
  );
197
184
  for (const extension of webhookExtensions) {
198
185
  const virtualEntrypoint = join(
199
186
  webhookCodegenDir,
200
- `${extension.config.compId}.ts`
187
+ `${extension.manifest.compId}.ts`
201
188
  );
202
189
 
203
190
  injectRoute({
204
191
  entrypoint: virtualEntrypoint,
205
- pattern: `/_wix/extensions/webhooks/${extension.config.compId}`,
192
+ pattern: `/_wix/extensions/webhooks/${extension.manifest.compId}`,
206
193
  });
207
194
  }
208
195
  }
@@ -227,7 +214,7 @@ const createIntegration = (): AstroIntegration => {
227
214
  });
228
215
 
229
216
  chokidar
230
- .watch([SERVICE_PLUGINS_DIR], {
217
+ .watch([EXTENSIONS_DIR], {
231
218
  cwd: config.root.pathname,
232
219
  ignoreInitial: true,
233
220
  useFsEvents: false,
@@ -241,16 +228,16 @@ const createIntegration = (): AstroIntegration => {
241
228
  });
242
229
  } else {
243
230
  const servicePluginExtensions = model.extensions.filter((extension) =>
244
- isValidServicePluginComponent(extension.config)
231
+ isValidServicePluginComponent(extension.manifest)
245
232
  );
246
233
  for (const extension of servicePluginExtensions) {
247
234
  const virtualEntrypoint = join(
248
235
  servicePluginCodegenDir,
249
- `${extension.config.compId}.ts`
236
+ `${extension.manifest.compId}.ts`
250
237
  );
251
238
  injectRoute({
252
239
  entrypoint: virtualEntrypoint,
253
- pattern: `/_wix/extensions/service-plugins/${extension.config.compId}`,
240
+ pattern: `/_wix/extensions/service-plugins/${extension.manifest.compId}`,
254
241
  });
255
242
  }
256
243
  }
@@ -272,19 +259,11 @@ const createIntegration = (): AstroIntegration => {
272
259
  });
273
260
 
274
261
  chokidar
275
- .watch(
276
- [
277
- DASHBOARD_PAGES_DIR,
278
- DASHBOARD_PLUGINS_DIR,
279
- DASHBOARD_MENU_PLUGINS_DIR,
280
- DASHBOARD_MODALS_DIR,
281
- ],
282
- {
283
- cwd: config.root.pathname,
284
- ignoreInitial: true,
285
- useFsEvents: false,
286
- }
287
- )
262
+ .watch([EXTENSIONS_DIR], {
263
+ cwd: config.root.pathname,
264
+ ignoreInitial: true,
265
+ useFsEvents: false,
266
+ })
288
267
  .on('all', async () => {
289
268
  model = await createProjectModel(logger);
290
269
  await writeVirtualBackofficeExtensionFiles(
@@ -294,18 +273,18 @@ const createIntegration = (): AstroIntegration => {
294
273
  });
295
274
  } else {
296
275
  const backofficeExtensions = model.extensions.filter((extension) =>
297
- isValidBackofficeComponent(extension.config)
276
+ isValidBackofficeComponent(extension.manifest)
298
277
  );
299
278
 
300
279
  for (const extension of backofficeExtensions) {
301
280
  const virtualEntrypoint = join(
302
281
  backofficeCodegenDir,
303
- `${extension.config.compId}.astro`
282
+ `${extension.manifest.compId}.astro`
304
283
  );
305
284
 
306
285
  injectRoute({
307
286
  entrypoint: virtualEntrypoint,
308
- pattern: `/_wix/extensions/backoffice/${extension.config.compId}`,
287
+ pattern: `/_wix/extensions/backoffice/${extension.manifest.compId}`,
309
288
  });
310
289
  }
311
290
  }
package/src/schemas.ts ADDED
@@ -0,0 +1,204 @@
1
+ import { z } from 'zod';
2
+
3
+ export const baseComponentSchema = z
4
+ .object({
5
+ compData: z.unknown(),
6
+ compId: z.string(),
7
+ compName: z.string().optional(),
8
+ compType: z.string(),
9
+ })
10
+ .passthrough();
11
+
12
+ export type BaseComponent = z.TypeOf<typeof baseComponentSchema>;
13
+
14
+ const webhookSchema = baseComponentSchema.merge(
15
+ z.object({
16
+ compData: z.object({
17
+ webhook: z
18
+ .object({
19
+ callbackUrl: z.string().optional(),
20
+ })
21
+ .passthrough(),
22
+ }),
23
+ compType: z.literal('WEBHOOK'),
24
+ })
25
+ );
26
+
27
+ export type Webhook = z.TypeOf<typeof webhookSchema>;
28
+
29
+ const backofficeExtensionWidgetSchema = baseComponentSchema.merge(
30
+ z.object({
31
+ compData: z.object({
32
+ backOfficeExtensionWidget: z
33
+ .object({
34
+ iframeUrl: z.string().optional(),
35
+ })
36
+ .passthrough(),
37
+ }),
38
+ compType: z.literal('BACK_OFFICE_EXTENSION_WIDGET'),
39
+ })
40
+ );
41
+
42
+ export type BackofficeExtensionWidget = z.TypeOf<
43
+ typeof backofficeExtensionWidgetSchema
44
+ >;
45
+
46
+ const backofficeExtensionMenuPluginSchema = baseComponentSchema.merge(
47
+ z.object({
48
+ compData: z.object({
49
+ backOfficeExtensionMenuItem: z
50
+ .object({
51
+ iframeUrl: z.string().optional(),
52
+ })
53
+ .passthrough(),
54
+ }),
55
+ compType: z.literal('BACK_OFFICE_EXTENSION_MENU_ITEM'),
56
+ })
57
+ );
58
+
59
+ export type BackofficeExtensionMenuItem = z.TypeOf<
60
+ typeof backofficeExtensionMenuPluginSchema
61
+ >;
62
+
63
+ const backofficeModalSchema = baseComponentSchema.merge(
64
+ z.object({
65
+ compData: z.object({
66
+ backOfficeModal: z
67
+ .object({
68
+ iframeUrl: z.string().optional(),
69
+ })
70
+ .passthrough(),
71
+ }),
72
+ compType: z.literal('BACK_OFFICE_MODAL'),
73
+ })
74
+ );
75
+
76
+ export type BackofficeModal = z.TypeOf<typeof backofficeModalSchema>;
77
+
78
+ const backofficePageSchema = baseComponentSchema.merge(
79
+ z.object({
80
+ compData: z.object({
81
+ backOfficePage: z
82
+ .object({
83
+ iframeUrl: z.string().optional(),
84
+ })
85
+ .passthrough(),
86
+ }),
87
+ compType: z.literal('BACK_OFFICE_PAGE'),
88
+ })
89
+ );
90
+
91
+ export type BackofficePage = z.TypeOf<typeof backofficePageSchema>;
92
+
93
+ const ecomAdditionalFeesSchema = baseComponentSchema.merge(
94
+ z.object({
95
+ compData: z.object({
96
+ ecomAdditionalFees: z
97
+ .object({
98
+ deploymentUri: z.string().optional(),
99
+ })
100
+ .passthrough(),
101
+ }),
102
+ compType: z.literal('ECOM_ADDITIONAL_FEES'),
103
+ })
104
+ );
105
+
106
+ export type EcomAdditionalFees = z.TypeOf<typeof ecomAdditionalFeesSchema>;
107
+
108
+ const ecomDiscountsTriggerSchema = baseComponentSchema.merge(
109
+ z.object({
110
+ compData: z.object({
111
+ ecomDiscountsTrigger: z
112
+ .object({
113
+ deploymentUri: z.string().optional(),
114
+ })
115
+ .passthrough(),
116
+ }),
117
+ compType: z.literal('ECOM_DISCOUNTS_TRIGGER'),
118
+ })
119
+ );
120
+
121
+ export type EcomDiscountsTrigger = z.TypeOf<typeof ecomDiscountsTriggerSchema>;
122
+
123
+ const ecomPaymentSettingsSchema = baseComponentSchema.merge(
124
+ z.object({
125
+ compData: z.object({
126
+ ecomPaymentSettings: z
127
+ .object({
128
+ deploymentUri: z.string().optional(),
129
+ })
130
+ .passthrough(),
131
+ }),
132
+ compType: z.literal('ECOM_PAYMENT_SETTINGS'),
133
+ })
134
+ );
135
+
136
+ export type EcomPaymentSettings = z.TypeOf<typeof ecomPaymentSettingsSchema>;
137
+
138
+ const ecomShippingRatesSchema = baseComponentSchema.merge(
139
+ z.object({
140
+ compData: z.object({
141
+ ecomShippingRates: z
142
+ .object({
143
+ deploymentUri: z.string().optional(),
144
+ })
145
+ .passthrough(),
146
+ }),
147
+ compType: z.literal('ECOM_SHIPPING_RATES'),
148
+ })
149
+ );
150
+
151
+ export type EcomShippingRates = z.TypeOf<typeof ecomShippingRatesSchema>;
152
+
153
+ const ecomValidationsSchema = baseComponentSchema.merge(
154
+ z.object({
155
+ compData: z.object({
156
+ ecomValidations: z
157
+ .object({
158
+ deploymentUri: z.string().optional(),
159
+ })
160
+ .passthrough(),
161
+ }),
162
+ compType: z.literal('ECOM_VALIDATIONS'),
163
+ })
164
+ );
165
+
166
+ export type EcomValidations = z.TypeOf<typeof ecomValidationsSchema>;
167
+
168
+ const ecomGiftCardsProviderSchema = baseComponentSchema.merge(
169
+ z.object({
170
+ compData: z.object({
171
+ giftCardsProvider: z
172
+ .object({
173
+ deploymentUri: z.string().optional(),
174
+ })
175
+ .passthrough(),
176
+ }),
177
+ compType: z.literal('GIFT_CARDS_PROVIDER'),
178
+ })
179
+ );
180
+
181
+ export type EcomGiftCardsProvider = z.TypeOf<
182
+ typeof ecomGiftCardsProviderSchema
183
+ >;
184
+
185
+ export const componentSchema = z.discriminatedUnion('compType', [
186
+ backofficePageSchema,
187
+ backofficeExtensionWidgetSchema,
188
+ backofficeExtensionMenuPluginSchema,
189
+ backofficeModalSchema,
190
+ webhookSchema,
191
+ ecomAdditionalFeesSchema,
192
+ ecomShippingRatesSchema,
193
+ ecomDiscountsTriggerSchema,
194
+ ecomValidationsSchema,
195
+ ecomPaymentSettingsSchema,
196
+ ecomGiftCardsProviderSchema,
197
+ ]);
198
+
199
+ export type Component = z.infer<typeof componentSchema>;
200
+
201
+ export interface AppManifest {
202
+ appId: string;
203
+ components: BaseComponent[];
204
+ }
package/src/types.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { BaseComponent, Component } from './schemas.js';
2
+
3
+ export interface Extension {
4
+ cwd: string;
5
+ manifest: Component;
6
+ }
7
+
8
+ export interface UnknownExtension {
9
+ cwd: string;
10
+ manifest: BaseComponent;
11
+ }
12
+
13
+ export interface Model {
14
+ appId: string;
15
+ env: Record<string, string>;
16
+ extensions: Extension[];
17
+ rootDir: string;
18
+ unknownExtensions: UnknownExtension[];
19
+ }
@@ -1,110 +1,90 @@
1
+ import { join } from 'node:path';
1
2
  import { cwd } from 'node:process';
2
3
  import type { AstroIntegrationLogger } from 'astro';
3
- import {
4
- backofficeExtensionMenuPluginSchema,
5
- backofficeExtensionWidgetSchema,
6
- backofficeModalSchema,
7
- backofficePageSchema,
8
- servicePluginSchema,
9
- webhookSchema,
10
- } from '@wix/cli-app-manifest';
11
- import chalk from 'chalk';
4
+ import { globby } from 'globby';
12
5
  import { outdent } from 'outdent';
13
- import { loadEnv } from 'vite';
14
- import {
15
- DASHBOARD_MENU_PLUGINS_DIR,
16
- DASHBOARD_MODALS_DIR,
17
- DASHBOARD_PAGES_DIR,
18
- DASHBOARD_PLUGINS_DIR,
19
- EVENTS_DIR,
20
- SERVICE_PLUGINS_DIR,
21
- } from '../directories.js';
22
- import { loadExtension } from './loadExtension.js';
23
-
24
- export type Model = Awaited<ReturnType<typeof createProjectModel>>;
6
+ import type { Extension, Model, UnknownExtension } from '../types.js';
7
+ import { EXTENSIONS_DIR } from '../directories.js';
8
+ import { baseComponentSchema, componentSchema } from '../schemas.js';
9
+ import { pathExists, readJson } from './fs-utils.js';
10
+ import { loadEnvVars } from './loadEnvVars.js';
25
11
 
26
12
  export async function createProjectModel(logger: AstroIntegrationLogger) {
27
13
  const rootDir = cwd();
14
+ const { appId, env } = loadEnvVars(rootDir, logger);
28
15
 
29
- const env = loadEnv(process.env.NODE_ENV ?? 'development', process.cwd(), '');
30
-
31
- const appId = env.WIX_CLIENT_ID;
32
-
33
- if (!appId) {
34
- logger.error(
35
- outdent`
36
- Missing environment variable ${chalk.blueBright(`WIX_CLIENT_ID`)}
37
- To use the Wix SDK, you must provide the ${chalk.blueBright(
38
- 'WIX_CLIENT_ID'
39
- )} environment variable.
40
- 💡 To pull the required environment variables from Wix, run:
41
- ${chalk.magenta('npx wix edge env --env local pull')}
42
- 🔍 Need Help?
43
- - Visit our docs: https://dev.wix.com/docs/go-headless
44
- - Join the community: https://discord.com/channels/1114269395317968906/1288424190969511987
45
- `
46
- );
47
-
48
- throw new Error(
49
- `${chalk.magenta(
50
- `WIX_CLIENT_ID`
51
- )} not found in loaded environment variables`
52
- );
53
- }
54
-
55
- const backofficePages = await loadExtension({
56
- directory: DASHBOARD_PAGES_DIR,
57
- fileName: 'page',
58
- rootDir,
59
- schema: backofficePageSchema,
16
+ const extensionsPaths = await globby(join(EXTENSIONS_DIR, '*'), {
17
+ cwd: rootDir,
18
+ onlyDirectories: true,
60
19
  });
61
20
 
62
- const backofficePlugins = await loadExtension({
63
- directory: DASHBOARD_PLUGINS_DIR,
64
- fileName: 'plugin',
65
- rootDir,
66
- schema: backofficeExtensionWidgetSchema,
67
- });
21
+ const extensions: Extension[] = [];
22
+ const unknownExtensions: UnknownExtension[] = [];
68
23
 
69
- const backofficeMenuItems = await loadExtension({
70
- directory: DASHBOARD_MENU_PLUGINS_DIR,
71
- fileName: 'plugin',
72
- rootDir,
73
- schema: backofficeExtensionMenuPluginSchema,
74
- });
24
+ for (const extensionPath of extensionsPaths) {
25
+ const extensionManifest = await readExtensionManifest(extensionPath);
26
+ const knownAppManifest =
27
+ await parseKnownExtensionManifest(extensionManifest);
75
28
 
76
- const backofficeModals = await loadExtension({
77
- directory: DASHBOARD_MODALS_DIR,
78
- fileName: 'modal',
79
- rootDir,
80
- schema: backofficeModalSchema,
81
- });
29
+ if (knownAppManifest != null) {
30
+ extensions.push({
31
+ cwd: extensionPath,
32
+ manifest: knownAppManifest,
33
+ });
82
34
 
83
- const events = await loadExtension({
84
- directory: EVENTS_DIR,
85
- fileName: 'event',
86
- rootDir,
87
- schema: webhookSchema,
88
- });
35
+ continue;
36
+ }
89
37
 
90
- const servicePlugins = await loadExtension({
91
- directory: SERVICE_PLUGINS_DIR,
92
- fileName: 'plugin',
93
- rootDir,
94
- schema: servicePluginSchema,
95
- });
38
+ const unknownAppManifest =
39
+ await parseBaseExtensionManifest(extensionManifest);
40
+
41
+ unknownExtensions.push({
42
+ cwd: extensionPath,
43
+ manifest: unknownAppManifest,
44
+ });
45
+ }
96
46
 
97
47
  return {
98
48
  appId,
99
49
  env,
100
- extensions: [
101
- ...backofficeMenuItems,
102
- ...backofficeModals,
103
- ...backofficePages,
104
- ...backofficePlugins,
105
- ...events,
106
- ...servicePlugins,
107
- ],
50
+ extensions,
108
51
  rootDir,
109
- };
52
+ unknownExtensions,
53
+ } satisfies Model;
54
+ }
55
+
56
+ async function parseKnownExtensionManifest(extensionManifest: unknown) {
57
+ const appManifestResult = componentSchema.safeParse(extensionManifest);
58
+
59
+ if (appManifestResult.error) {
60
+ return null;
61
+ }
62
+
63
+ return appManifestResult.data;
64
+ }
65
+
66
+ async function parseBaseExtensionManifest(extensionManifest: unknown) {
67
+ const appManifestResult = baseComponentSchema.safeParse(extensionManifest);
68
+
69
+ if (!appManifestResult.success) {
70
+ throw new Error(outdent`
71
+ Invalid extension configuration:
72
+
73
+ ${appManifestResult.error.errors
74
+ .map((e) => `${e.path.join('.')}: ${e.message}`)
75
+ .join('\n')}
76
+ `);
77
+ }
78
+
79
+ return appManifestResult.data;
80
+ }
81
+
82
+ async function readExtensionManifest(directoryPath: string) {
83
+ const manifestFilePath = join(directoryPath, 'extension.json');
84
+
85
+ if (!(await pathExists(manifestFilePath))) {
86
+ throw new Error('Missing extension manifest');
87
+ }
88
+
89
+ return await readJson(manifestFilePath);
110
90
  }
@@ -0,0 +1,37 @@
1
+ import {
2
+ access,
3
+ readFile as fsReadFile,
4
+ mkdir,
5
+ writeFile,
6
+ } from 'node:fs/promises';
7
+ import { EOL } from 'node:os';
8
+ import { dirname } from 'node:path';
9
+
10
+ function toJsonString(object: unknown, opts?: { spaces: number }) {
11
+ return JSON.stringify(object, null, opts?.spaces).concat(EOL);
12
+ }
13
+
14
+ export async function writeJson(
15
+ filePath: string,
16
+ object: unknown,
17
+ opts?: { spaces: number }
18
+ ) {
19
+ const str = toJsonString(object, opts);
20
+ await outputDir(dirname(filePath));
21
+
22
+ await writeFile(filePath, str, 'utf-8');
23
+ }
24
+
25
+ export async function readJson(file: string): Promise<unknown> {
26
+ return JSON.parse(await fsReadFile(file, 'utf-8'));
27
+ }
28
+
29
+ export function pathExists(path: string) {
30
+ return access(path)
31
+ .then(() => true)
32
+ .catch(() => false);
33
+ }
34
+
35
+ export async function outputDir(dir: string) {
36
+ await mkdir(dir, { recursive: true });
37
+ }