mercur-cli 0.1.2 → 0.1.5

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 (40) hide show
  1. package/cli/backend-setup.js +9 -13
  2. package/cli/data.js +121 -0
  3. package/cli/pull-and-install.js +38 -1
  4. package/cli/start.js +1 -1
  5. package/index.js +1 -1
  6. package/mercur/backend/.env.template +8 -0
  7. package/mercur/backend/.env.test +0 -0
  8. package/mercur/backend/.github/dependabot.yml +21 -0
  9. package/mercur/backend/.github/scripts/wait-for-server-live.sh +29 -0
  10. package/mercur/backend/.github/workflows/test-cli.yml +222 -0
  11. package/mercur/backend/.github/workflows/update-preview-deps-ci.yml +69 -0
  12. package/mercur/backend/.github/workflows/update-preview-deps.yml +67 -0
  13. package/mercur/backend/.vscode/settings.json +2 -0
  14. package/mercur/backend/.yarnrc.yml +1 -0
  15. package/mercur/backend/README.md +62 -0
  16. package/mercur/backend/instrumentation.ts +24 -0
  17. package/mercur/backend/integration-tests/http/README.md +29 -0
  18. package/mercur/backend/integration-tests/http/health.spec.ts +15 -0
  19. package/mercur/backend/integration-tests/setup.js +3 -0
  20. package/mercur/backend/jest.config.js +27 -0
  21. package/mercur/backend/medusa-config.ts +88 -0
  22. package/mercur/backend/package.json +65 -0
  23. package/mercur/backend/src/admin/README.md +33 -0
  24. package/mercur/backend/src/admin/tsconfig.json +24 -0
  25. package/mercur/backend/src/admin/vite-env.d.ts +1 -0
  26. package/mercur/backend/src/api/README.md +135 -0
  27. package/mercur/backend/src/api/admin/custom/route.ts +8 -0
  28. package/mercur/backend/src/api/store/custom/route.ts +8 -0
  29. package/mercur/backend/src/jobs/README.md +38 -0
  30. package/mercur/backend/src/links/README.md +26 -0
  31. package/mercur/backend/src/modules/README.md +117 -0
  32. package/mercur/backend/src/scripts/README.md +63 -0
  33. package/mercur/backend/src/scripts/seed/seed-functions.ts +519 -0
  34. package/mercur/backend/src/scripts/seed/seed-products.ts +601 -0
  35. package/mercur/backend/src/scripts/seed.ts +75 -0
  36. package/mercur/backend/src/subscribers/README.md +61 -0
  37. package/mercur/backend/src/workflows/README.md +81 -0
  38. package/mercur/backend/tsconfig.json +35 -0
  39. package/mercur/backend/yarn.lock +11469 -0
  40. package/package.json +10 -8
@@ -0,0 +1,117 @@
1
+ # Custom Module
2
+
3
+ A module is a package of reusable functionalities. It can be integrated into your Medusa application without affecting the overall system. You can create a module as part of a plugin.
4
+
5
+ > Learn more about modules in [this documentation](https://docs.medusajs.com/learn/fundamentals/modules).
6
+
7
+ To create a module:
8
+
9
+ ## 1. Create a Data Model
10
+
11
+ A data model represents a table in the database. You create a data model in a TypeScript or JavaScript file under the `models` directory of a module.
12
+
13
+ For example, create the file `src/modules/blog/models/post.ts` with the following content:
14
+
15
+ ```ts
16
+ import { model } from "@medusajs/framework/utils"
17
+
18
+ const Post = model.define("post", {
19
+ id: model.id().primaryKey(),
20
+ title: model.text(),
21
+ })
22
+
23
+ export default Post
24
+ ```
25
+
26
+ ## 2. Create a Service
27
+
28
+ A module must define a service. A service is a TypeScript or JavaScript class holding methods related to a business logic or commerce functionality.
29
+
30
+ For example, create the file `src/modules/blog/service.ts` with the following content:
31
+
32
+ ```ts
33
+ import { MedusaService } from "@medusajs/framework/utils"
34
+ import Post from "./models/post"
35
+
36
+ class BlogModuleService extends MedusaService({
37
+ Post,
38
+ }){
39
+ }
40
+
41
+ export default BlogModuleService
42
+ ```
43
+
44
+ ## 3. Export Module Definition
45
+
46
+ A module must have an `index.ts` file in its root directory that exports its definition. The definition specifies the main service of the module.
47
+
48
+ For example, create the file `src/modules/blog/index.ts` with the following content:
49
+
50
+ ```ts
51
+ import BlogModuleService from "./service"
52
+ import { Module } from "@medusajs/framework/utils"
53
+
54
+ export const BLOG_MODULE = "blog"
55
+
56
+ export default Module(BLOG_MODULE, {
57
+ service: BlogModuleService,
58
+ })
59
+ ```
60
+
61
+ ## 4. Add Module to Medusa's Configurations
62
+
63
+ To start using the module, add it to `medusa-config.ts`:
64
+
65
+ ```ts
66
+ module.exports = defineConfig({
67
+ projectConfig: {
68
+ // ...
69
+ },
70
+ modules: [
71
+ {
72
+ resolve: "./src/modules/blog",
73
+ },
74
+ ],
75
+ })
76
+ ```
77
+
78
+ ## 5. Generate and Run Migrations
79
+
80
+ To generate migrations for your module, run the following command:
81
+
82
+ ```bash
83
+ npx medusa db:generate blog
84
+ ```
85
+
86
+ Then, to run migrations, run the following command:
87
+
88
+ ```bash
89
+ npx medusa db:migrate
90
+ ```
91
+
92
+ ## Use Module
93
+
94
+ You can use the module in customizations within the Medusa application, such as workflows and API routes.
95
+
96
+ For example, to use the module in an API route:
97
+
98
+ ```ts
99
+ import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
100
+ import BlogModuleService from "../../../modules/blog/service"
101
+ import { BLOG_MODULE } from "../../../modules/blog"
102
+
103
+ export async function GET(
104
+ req: MedusaRequest,
105
+ res: MedusaResponse
106
+ ): Promise<void> {
107
+ const blogModuleService: BlogModuleService = req.scope.resolve(
108
+ BLOG_MODULE
109
+ )
110
+
111
+ const posts = await blogModuleService.listPosts()
112
+
113
+ res.json({
114
+ posts
115
+ })
116
+ }
117
+ ```
@@ -0,0 +1,63 @@
1
+ # Custom CLI Script
2
+
3
+ A custom CLI script is a function to execute through Medusa's CLI tool. This is useful when creating custom Medusa tooling to run as a CLI tool.
4
+
5
+ > Learn more about custom CLI scripts in [this documentation](https://docs.medusajs.com/learn/fundamentals/custom-cli-scripts).
6
+
7
+ ## How to Create a Custom CLI Script?
8
+
9
+ To create a custom CLI script, create a TypeScript or JavaScript file under the `src/scripts` directory. The file must default export a function.
10
+
11
+ For example, create the file `src/scripts/my-script.ts` with the following content:
12
+
13
+ ```ts title="src/scripts/my-script.ts"
14
+ import {
15
+ ExecArgs,
16
+ } from "@medusajs/framework/types"
17
+
18
+ export default async function myScript ({
19
+ container
20
+ }: ExecArgs) {
21
+ const productModuleService = container.resolve("product")
22
+
23
+ const [, count] = await productModuleService.listAndCountProducts()
24
+
25
+ console.log(`You have ${count} product(s)`)
26
+ }
27
+ ```
28
+
29
+ The function receives as a parameter an object having a `container` property, which is an instance of the Medusa Container. Use it to resolve resources in your Medusa application.
30
+
31
+ ---
32
+
33
+ ## How to Run Custom CLI Script?
34
+
35
+ To run the custom CLI script, run the `exec` command:
36
+
37
+ ```bash
38
+ npx medusa exec ./src/scripts/my-script.ts
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Custom CLI Script Arguments
44
+
45
+ Your script can accept arguments from the command line. Arguments are passed to the function's object parameter in the `args` property.
46
+
47
+ For example:
48
+
49
+ ```ts
50
+ import { ExecArgs } from "@medusajs/framework/types"
51
+
52
+ export default async function myScript ({
53
+ args
54
+ }: ExecArgs) {
55
+ console.log(`The arguments you passed: ${args}`)
56
+ }
57
+ ```
58
+
59
+ Then, pass the arguments in the `exec` command after the file path:
60
+
61
+ ```bash
62
+ npx medusa exec ./src/scripts/my-script.ts arg1 arg2
63
+ ```
@@ -0,0 +1,519 @@
1
+ import { MedusaContainer } from '@medusajs/framework'
2
+ import { ContainerRegistrationKeys, Modules } from '@medusajs/framework/utils'
3
+ import {
4
+ createApiKeysWorkflow,
5
+ createCollectionsWorkflow,
6
+ createInventoryLevelsWorkflow,
7
+ createProductCategoriesWorkflow,
8
+ createProductsWorkflow,
9
+ createRegionsWorkflow,
10
+ createSalesChannelsWorkflow,
11
+ createServiceZonesWorkflow,
12
+ createShippingOptionsWorkflow,
13
+ createStockLocationsWorkflow,
14
+ createTaxRegionsWorkflow,
15
+ linkSalesChannelsToApiKeyWorkflow,
16
+ updateStoresWorkflow
17
+ } from '@medusajs/medusa/core-flows'
18
+
19
+ import { SELLER_MODULE } from '@mercurjs/b2c-core/modules/seller'
20
+ import {
21
+ createConfigurationRuleWorkflow,
22
+ createLocationFulfillmentSetAndAssociateWithSellerWorkflow,
23
+ createSellerWorkflow
24
+ } from '@mercurjs/b2c-core/workflows'
25
+ import { createCommissionRuleWorkflow } from '@mercurjs/commission/workflows'
26
+ import {
27
+ ConfigurationRuleDefaults,
28
+ SELLER_SHIPPING_PROFILE_LINK
29
+ } from '@mercurjs/framework'
30
+
31
+ import { productsToInsert } from './seed-products'
32
+
33
+ const countries = ['be', 'de', 'dk', 'se', 'fr', 'es', 'it', 'pl', 'cz', 'nl']
34
+
35
+ export async function createSalesChannel(container: MedusaContainer) {
36
+ const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL)
37
+ let [defaultSalesChannel] = await salesChannelModuleService.listSalesChannels(
38
+ {
39
+ name: 'Default Sales Channel'
40
+ }
41
+ )
42
+
43
+ if (!defaultSalesChannel) {
44
+ const {
45
+ result: [salesChannelResult]
46
+ } = await createSalesChannelsWorkflow(container).run({
47
+ input: {
48
+ salesChannelsData: [
49
+ {
50
+ name: 'Default Sales Channel'
51
+ }
52
+ ]
53
+ }
54
+ })
55
+ defaultSalesChannel = salesChannelResult
56
+ }
57
+
58
+ return defaultSalesChannel
59
+ }
60
+
61
+ export async function createStore(
62
+ container: MedusaContainer,
63
+ salesChannelId: string,
64
+ regionId: string
65
+ ) {
66
+ const storeModuleService = container.resolve(Modules.STORE)
67
+ const [store] = await storeModuleService.listStores()
68
+
69
+ if (!store) {
70
+ return
71
+ }
72
+
73
+ await updateStoresWorkflow(container).run({
74
+ input: {
75
+ selector: { id: store.id },
76
+ update: {
77
+ supported_currencies: [
78
+ {
79
+ currency_code: 'eur',
80
+ is_default: true
81
+ }
82
+ ],
83
+ default_sales_channel_id: salesChannelId,
84
+ default_region_id: regionId
85
+ }
86
+ }
87
+ })
88
+ }
89
+ export async function createRegions(container: MedusaContainer) {
90
+ const {
91
+ result: [region]
92
+ } = await createRegionsWorkflow(container).run({
93
+ input: {
94
+ regions: [
95
+ {
96
+ name: 'Europe',
97
+ currency_code: 'eur',
98
+ countries,
99
+ payment_providers: ['pp_system_default']
100
+ }
101
+ ]
102
+ }
103
+ })
104
+
105
+ await createTaxRegionsWorkflow(container).run({
106
+ input: countries.map((country_code) => ({
107
+ country_code
108
+ }))
109
+ })
110
+
111
+ return region
112
+ }
113
+
114
+ export async function createPublishableKey(
115
+ container: MedusaContainer,
116
+ salesChannelId: string
117
+ ) {
118
+ const apiKeyService = container.resolve(Modules.API_KEY)
119
+
120
+ let [key] = await apiKeyService.listApiKeys({ type: 'publishable' })
121
+
122
+ if (!key) {
123
+ const {
124
+ result: [publishableApiKeyResult]
125
+ } = await createApiKeysWorkflow(container).run({
126
+ input: {
127
+ api_keys: [
128
+ {
129
+ title: 'Default publishable key',
130
+ type: 'publishable',
131
+ created_by: ''
132
+ }
133
+ ]
134
+ }
135
+ })
136
+ key = publishableApiKeyResult
137
+ }
138
+
139
+ await linkSalesChannelsToApiKeyWorkflow(container).run({
140
+ input: {
141
+ id: key.id,
142
+ add: [salesChannelId]
143
+ }
144
+ })
145
+
146
+ return key
147
+ }
148
+
149
+ export async function createProductCategories(container: MedusaContainer) {
150
+ const { result } = await createProductCategoriesWorkflow(container).run({
151
+ input: {
152
+ product_categories: [
153
+ {
154
+ name: 'Sneakers',
155
+ is_active: true
156
+ },
157
+ {
158
+ name: 'Sandals',
159
+ is_active: true
160
+ },
161
+ {
162
+ name: 'Boots',
163
+ is_active: true
164
+ },
165
+ {
166
+ name: 'Sport',
167
+ is_active: true
168
+ },
169
+ {
170
+ name: 'Accessories',
171
+ is_active: true
172
+ },
173
+ {
174
+ name: 'Tops',
175
+ is_active: true
176
+ }
177
+ ]
178
+ }
179
+ })
180
+
181
+ return result
182
+ }
183
+
184
+ export async function createProductCollections(container: MedusaContainer) {
185
+ const { result } = await createCollectionsWorkflow(container).run({
186
+ input: {
187
+ collections: [
188
+ {
189
+ title: 'Luxury'
190
+ },
191
+ {
192
+ title: 'Vintage'
193
+ },
194
+ {
195
+ title: 'Casual'
196
+ },
197
+ {
198
+ title: 'Soho'
199
+ },
200
+ {
201
+ title: 'Streetwear'
202
+ },
203
+ {
204
+ title: 'Y2K'
205
+ }
206
+ ]
207
+ }
208
+ })
209
+
210
+ return result
211
+ }
212
+
213
+ export async function createSeller(container: MedusaContainer) {
214
+ const authService = container.resolve(Modules.AUTH)
215
+
216
+ const { authIdentity } = await authService.register('emailpass', {
217
+ body: {
218
+ email: 'seller@mercurjs.com',
219
+ password: 'secret'
220
+ }
221
+ })
222
+
223
+ const { result: seller } = await createSellerWorkflow.run({
224
+ container,
225
+ input: {
226
+ auth_identity_id: authIdentity?.id,
227
+ member: {
228
+ name: 'John Doe',
229
+ email: 'seller@mercurjs.com'
230
+ },
231
+ seller: {
232
+ name: 'MercurJS Store'
233
+ }
234
+ }
235
+ })
236
+
237
+ return seller
238
+ }
239
+
240
+ export async function createSellerStockLocation(
241
+ container: MedusaContainer,
242
+ sellerId: string,
243
+ salesChannelId: string
244
+ ) {
245
+ const link = container.resolve(ContainerRegistrationKeys.LINK)
246
+ const {
247
+ result: [stock]
248
+ } = await createStockLocationsWorkflow(container).run({
249
+ input: {
250
+ locations: [
251
+ {
252
+ name: `Stock Location for seller ${sellerId}`,
253
+ address: {
254
+ address_1: 'Random Strasse',
255
+ city: 'Berlin',
256
+ country_code: 'de'
257
+ }
258
+ }
259
+ ]
260
+ }
261
+ })
262
+
263
+ await link.create([
264
+ {
265
+ [SELLER_MODULE]: {
266
+ seller_id: sellerId
267
+ },
268
+ [Modules.STOCK_LOCATION]: {
269
+ stock_location_id: stock.id
270
+ }
271
+ },
272
+ {
273
+ [Modules.STOCK_LOCATION]: {
274
+ stock_location_id: stock.id
275
+ },
276
+ [Modules.FULFILLMENT]: {
277
+ fulfillment_provider_id: 'manual_manual'
278
+ }
279
+ },
280
+ {
281
+ [Modules.SALES_CHANNEL]: {
282
+ sales_channel_id: salesChannelId
283
+ },
284
+ [Modules.STOCK_LOCATION]: {
285
+ stock_location_id: stock.id
286
+ }
287
+ }
288
+ ])
289
+
290
+ await createLocationFulfillmentSetAndAssociateWithSellerWorkflow.run({
291
+ container,
292
+ input: {
293
+ fulfillment_set_data: {
294
+ name: `${sellerId} fulfillment set`,
295
+ type: 'shipping'
296
+ },
297
+ location_id: stock.id,
298
+ seller_id: sellerId
299
+ }
300
+ })
301
+
302
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
303
+
304
+ const {
305
+ data: [stockLocation]
306
+ } = await query.graph({
307
+ entity: 'stock_location',
308
+ fields: ['*', 'fulfillment_sets.*'],
309
+ filters: {
310
+ id: stock.id
311
+ }
312
+ })
313
+
314
+ return stockLocation
315
+ }
316
+
317
+ export async function createServiceZoneForFulfillmentSet(
318
+ container: MedusaContainer,
319
+ sellerId: string,
320
+ fulfillmentSetId: string
321
+ ) {
322
+ await createServiceZonesWorkflow.run({
323
+ container,
324
+ input: {
325
+ data: [
326
+ {
327
+ fulfillment_set_id: fulfillmentSetId,
328
+ name: `Europe`,
329
+ geo_zones: countries.map((c) => ({
330
+ type: 'country',
331
+ country_code: c
332
+ }))
333
+ }
334
+ ]
335
+ }
336
+ })
337
+
338
+ const fulfillmentService = container.resolve(Modules.FULFILLMENT)
339
+
340
+ const [zone] = await fulfillmentService.listServiceZones({
341
+ fulfillment_set: {
342
+ id: fulfillmentSetId
343
+ }
344
+ })
345
+
346
+ const link = container.resolve(ContainerRegistrationKeys.LINK)
347
+ await link.create({
348
+ [SELLER_MODULE]: {
349
+ seller_id: sellerId
350
+ },
351
+ [Modules.FULFILLMENT]: {
352
+ service_zone_id: zone.id
353
+ }
354
+ })
355
+
356
+ return zone
357
+ }
358
+
359
+ export async function createSellerShippingOption(
360
+ container: MedusaContainer,
361
+ sellerId: string,
362
+ sellerName: string,
363
+ regionId: string,
364
+ serviceZoneId: string
365
+ ) {
366
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
367
+ const {
368
+ data: [shippingProfile]
369
+ } = await query.graph({
370
+ entity: SELLER_SHIPPING_PROFILE_LINK,
371
+ fields: ['shipping_profile_id'],
372
+ filters: {
373
+ seller_id: sellerId
374
+ }
375
+ })
376
+
377
+ const {
378
+ result: [shippingOption]
379
+ } = await createShippingOptionsWorkflow.run({
380
+ container,
381
+ input: [
382
+ {
383
+ name: `${sellerName} shipping`,
384
+ shipping_profile_id: shippingProfile.shipping_profile_id,
385
+ service_zone_id: serviceZoneId,
386
+ provider_id: 'manual_manual',
387
+ type: {
388
+ label: `${sellerName} shipping`,
389
+ code: sellerName,
390
+ description: 'Europe shipping'
391
+ },
392
+ rules: [
393
+ { value: 'true', attribute: 'enabled_in_store', operator: 'eq' },
394
+ { attribute: 'is_return', value: 'false', operator: 'eq' }
395
+ ],
396
+ prices: [
397
+ { currency_code: 'eur', amount: 10 },
398
+ { amount: 10, region_id: regionId }
399
+ ],
400
+ price_type: 'flat',
401
+ data: { id: 'manual-fulfillment' }
402
+ }
403
+ ]
404
+ })
405
+
406
+ const link = container.resolve(ContainerRegistrationKeys.LINK)
407
+ await link.create({
408
+ [SELLER_MODULE]: {
409
+ seller_id: sellerId
410
+ },
411
+ [Modules.FULFILLMENT]: {
412
+ shipping_option_id: shippingOption.id
413
+ }
414
+ })
415
+
416
+ return shippingOption
417
+ }
418
+
419
+ export async function createSellerProducts(
420
+ container: MedusaContainer,
421
+ sellerId: string,
422
+ salesChannelId: string
423
+ ) {
424
+ const productService = container.resolve(Modules.PRODUCT)
425
+ const collections = await productService.listProductCollections(
426
+ {},
427
+ { select: ['id', 'title'] }
428
+ )
429
+ const categories = await productService.listProductCategories(
430
+ {},
431
+ { select: ['id', 'name'] }
432
+ )
433
+
434
+ const randomCategory = () =>
435
+ categories[Math.floor(Math.random() * categories.length)]
436
+ const randomCollection = () =>
437
+ collections[Math.floor(Math.random() * collections.length)]
438
+
439
+ const toInsert = productsToInsert.map((p) => ({
440
+ ...p,
441
+ categories: [
442
+ {
443
+ id: randomCategory().id
444
+ }
445
+ ],
446
+ collection_id: randomCollection().id,
447
+ sales_channels: [
448
+ {
449
+ id: salesChannelId
450
+ }
451
+ ]
452
+ }))
453
+
454
+ const { result } = await createProductsWorkflow.run({
455
+ container,
456
+ input: {
457
+ products: toInsert,
458
+ additional_data: {
459
+ seller_id: sellerId
460
+ }
461
+ }
462
+ })
463
+
464
+ return result
465
+ }
466
+
467
+ export async function createInventoryItemStockLevels(
468
+ container: MedusaContainer,
469
+ stockLocationId: string
470
+ ) {
471
+ const inventoryService = container.resolve(Modules.INVENTORY)
472
+ const items = await inventoryService.listInventoryItems(
473
+ {},
474
+ { select: ['id'] }
475
+ )
476
+
477
+ const toCreate = items.map((i) => ({
478
+ inventory_item_id: i.id,
479
+ location_id: stockLocationId,
480
+ stocked_quantity: Math.floor(Math.random() * 50) + 1
481
+ }))
482
+
483
+ const { result } = await createInventoryLevelsWorkflow.run({
484
+ container,
485
+ input: {
486
+ inventory_levels: toCreate
487
+ }
488
+ })
489
+ return result
490
+ }
491
+
492
+ export async function createDefaultCommissionLevel(container: MedusaContainer) {
493
+ await createCommissionRuleWorkflow.run({
494
+ container,
495
+ input: {
496
+ name: 'default',
497
+ is_active: true,
498
+ reference: 'site',
499
+ reference_id: '',
500
+ rate: {
501
+ include_tax: true,
502
+ type: 'percentage',
503
+ percentage_rate: 2
504
+ }
505
+ }
506
+ })
507
+ }
508
+
509
+ export async function createConfigurationRules(container: MedusaContainer) {
510
+ for (const [ruleType, isEnabled] of ConfigurationRuleDefaults) {
511
+ await createConfigurationRuleWorkflow.run({
512
+ container,
513
+ input: {
514
+ rule_type: ruleType,
515
+ is_enabled: isEnabled
516
+ }
517
+ })
518
+ }
519
+ }