@scandipwa/magento-scripts 2.4.12-alpha.0 → 2.4.13

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/.oxlintrc.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "../../node_modules/oxlint/configuration_schema.json",
3
+ "env": {
4
+ "node": true,
5
+ "es2022": true
6
+ },
7
+ "globals": {
8
+ "verbose": "readonly",
9
+ "NodeJS": "readonly",
10
+ "BufferEncoding": "readonly"
11
+ },
12
+ "categories": {
13
+ "correctness": "error"
14
+ },
15
+ "ignorePatterns": [
16
+ "**/node_modules/**",
17
+ "**/lib/config/templates/**",
18
+ "**/typings/**",
19
+ "**/*.d.ts"
20
+ ],
21
+ "rules": {
22
+ "no-unused-vars": [
23
+ "warn",
24
+ {
25
+ "argsIgnorePattern": "^_",
26
+ "caughtErrorsIgnorePattern": "^_",
27
+ "varsIgnorePattern": "^_"
28
+ }
29
+ ],
30
+ "unicorn/no-useless-fallback-in-spread": "off"
31
+ }
32
+ }
package/exec.js CHANGED
@@ -93,7 +93,7 @@ const newVersionIsAPatch = (latestVersion, currentVersion) => {
93
93
  process.isOutOfDateVersion = true
94
94
  process.isOutOfDateVersionMessage = message
95
95
  }
96
- } catch (e) {
96
+ } catch {
97
97
  logger.warn(`Package ${logger.style.misc(name)} is not yet published.`)
98
98
  logger.log() // add empty line
99
99
  }
package/index.js CHANGED
@@ -6,7 +6,6 @@ const logger = require('@scandipwa/scandipwa-dev-utils/logger')
6
6
  const semver = require('semver')
7
7
  const isInstalledGlobally = require('is-installed-globally')
8
8
  const isRunningRoot = require('./lib/util/is-running-root')
9
- const ensureAgentsMd = require('./lib/util/ensure-agents-md')
10
9
 
11
10
  if (isRunningRoot()) {
12
11
  logger.error('Root privileges detected!')
@@ -28,8 +27,6 @@ If you are experiencing problems with ${logger.style.misc(
28
27
  process.exit(1)
29
28
  }
30
29
 
31
- ensureAgentsMd()
32
-
33
30
  const commands = [
34
31
  require('./lib/commands/link'),
35
32
  require('./lib/commands/logs'),
@@ -118,7 +115,7 @@ const newVersionIsAPatch = (latestVersion, currentVersion) => {
118
115
  process.isOutOfDateVersion = true
119
116
  process.isOutOfDateVersionMessage = message
120
117
  }
121
- } catch (e) {
118
+ } catch {
122
119
  logger.warn(`Package ${logger.style.misc(name)} is not yet published.`)
123
120
  logger.log() // add empty line
124
121
  }
@@ -3,7 +3,7 @@ const { Listr } = require('listr2')
3
3
  const getMagentoVersionConfig = require('../config/get-magento-version-config')
4
4
  const { getCachedPorts } = require('../config/get-port-config')
5
5
 
6
- const { prettyStatus } = require('../tasks/status')
6
+ const { prettyStatus, simpleStatus } = require('../tasks/status')
7
7
  const { checkRequirements } = require('../tasks/requirements')
8
8
  const { statusContainers } = require('../tasks/docker/containers')
9
9
  const getProjectConfiguration = require('../config/get-project-configuration')
@@ -20,10 +20,35 @@ module.exports = (yargs) => {
20
20
  yargs.command(
21
21
  'status',
22
22
  'Show application status',
23
- // eslint-disable-next-line @typescript-eslint/no-empty-function
24
- () => {},
23
+ (yargs) => {
24
+ yargs
25
+ .option('non-interactive', {
26
+ alias: 'n',
27
+ type: 'boolean',
28
+ default: false,
29
+ description:
30
+ 'Print a plain-text status summary (for AI terminals and scripts)'
31
+ })
32
+ .option('verbose', {
33
+ alias: 'v',
34
+ type: 'boolean',
35
+ default: false,
36
+ description:
37
+ 'Retrieve Docker image and volume sizes (slower, off by default)'
38
+ })
39
+ },
25
40
  async (args) => {
26
41
  const silent = /** @type {boolean} */ (args.silent)
42
+ // A non-TTY stdout (pipe, CI, AI terminal) is inherently
43
+ // non-interactive; the -n flag forces it even inside a TTY.
44
+ const nonInteractive =
45
+ !!(args.nonInteractive || args.n) || !process.stdout.isTTY
46
+ // Enumerating per-image/-volume sizes shells out to
47
+ // `docker system df --verbose`, which is slow; skip it by default.
48
+ // The non-interactive report already prints the full status; the
49
+ // --verbose flag only adds the Docker image/volume sizes (and is
50
+ // what the pretty renderer needs to render them at all).
51
+ const verbose = !!(args.verbose || args.v)
27
52
  const tasks = new Listr(
28
53
  [
29
54
  checkRequirements(),
@@ -40,6 +65,9 @@ module.exports = (yargs) => {
40
65
  checkSearchEngineVersion(),
41
66
  {
42
67
  title: 'Retrieving Docker System data',
68
+ skip: () =>
69
+ !verbose &&
70
+ 'Docker image and volume sizes omitted (pass --verbose to include them)',
43
71
  task: async (ctx) => {
44
72
  ctx.systemDFData =
45
73
  await systemApi.df({
@@ -59,15 +87,25 @@ module.exports = (yargs) => {
59
87
  {
60
88
  concurrent: false,
61
89
  exitOnError: false,
62
- ctx: { throwMagentoVersionMissing: true, ...args, silent },
63
- renderer:
64
- silent || !process.stdout.isTTY ? 'silent' : 'default',
90
+ ctx: {
91
+ throwMagentoVersionMissing: true,
92
+ ...args,
93
+ silent,
94
+ nonInteractive,
95
+ verbose
96
+ },
97
+ renderer: silent || nonInteractive ? 'silent' : 'default',
65
98
  rendererOptions: { collapse: false, clearOutput: false }
66
99
  }
67
100
  )
68
101
 
69
102
  try {
70
- await prettyStatus(await tasks.run())
103
+ const ctx = await tasks.run()
104
+ if (nonInteractive) {
105
+ simpleStatus(ctx)
106
+ } else {
107
+ await prettyStatus(ctx)
108
+ }
71
109
  } catch (e) {
72
110
  logger.error(e.message || e)
73
111
  process.exit(1)
@@ -23,7 +23,7 @@ const rosettaTranslatedContainers =
23
23
  */
24
24
  const volumeDirectives = (directives) => {
25
25
  const directivesResult = Object.entries(directives)
26
- .filter(([name, value]) => value === true)
26
+ .filter(([, value]) => value === true)
27
27
  .map(([name]) => name)
28
28
  .join(',')
29
29
 
@@ -25,7 +25,7 @@ const getSystemConfig = async ({ validate = true } = {}) => {
25
25
  let userSystemConfigParsed
26
26
  try {
27
27
  userSystemConfigParsed = JSON.parse(userSystemConfig)
28
- } catch (e) {
28
+ } catch {
29
29
  throw new KnownError(
30
30
  `System configuration file is not a valid JSON!\n\nFile location: ${systemConfigPath}`
31
31
  )
@@ -61,7 +61,7 @@ const getSystemConfigSync = ({ validate = true } = {}) => {
61
61
  let userSystemConfigParsed
62
62
  try {
63
63
  userSystemConfigParsed = JSON.parse(userSystemConfig)
64
- } catch (e) {
64
+ } catch {
65
65
  throw new KnownError(
66
66
  `System configuration file is not a valid JSON!\n\nFile location: ${systemConfigPath}`
67
67
  )
@@ -38,7 +38,8 @@ const readymageSSH = () => ({
38
38
  ...databaseDumpCommandWithOptions,
39
39
  '--no-data',
40
40
  '--result-file=dump-1.sql',
41
- ...[...orderTables, ...customerTables]
41
+ ...orderTables,
42
+ ...customerTables
42
43
  ].join(' ')
43
44
 
44
45
  await ssh.execCommand(secondCommand)
@@ -172,7 +172,7 @@ const startContainers = () => ({
172
172
 
173
173
  const missingContainers = Object.entries(containers)
174
174
  .filter(
175
- ([nameWithoutPrefix, { name }]) =>
175
+ ([, { name }]) =>
176
176
  !containerList.some((c) => c.Names === name)
177
177
  )
178
178
  .map(([nameWithoutPrefix, containerOptions]) => ({
@@ -11,7 +11,7 @@ function isExec(p) {
11
11
  try {
12
12
  fs.accessSync(p, fs.constants.X_OK)
13
13
  return true
14
- } catch (e) {
14
+ } catch {
15
15
  return false
16
16
  }
17
17
  }
@@ -1,6 +1,9 @@
1
1
  const path = require('path')
2
2
  const setConfigFile = require('../../util/set-config')
3
3
  const UnknownError = require('../../errors/unknown-error')
4
+ const {
5
+ shouldUseStoreDomainMapping
6
+ } = require('../../util/store-domains')
4
7
 
5
8
  /**
6
9
  * @returns {import('listr2').ListrTask<import('../../../typings/context').ListrContext>}
@@ -35,7 +38,7 @@ const createNginxConfig = () => ({
35
38
  }
36
39
 
37
40
  const useStoreDomainMapping =
38
- storeDomains && Object.keys(storeDomains).length > 1
41
+ shouldUseStoreDomainMapping(storeDomains)
39
42
 
40
43
  try {
41
44
  await setConfigFile({
@@ -18,14 +18,12 @@ const setupPhpDockerSettingsConfig = () => ({
18
18
  )
19
19
  const phpDockerSettingsConfigs =
20
20
  phpDockerSettingsConfigContent.project.component
21
- const hasChanges = await Promise.all([
22
- setupPHPDockerContainerSettingsConfig(
23
- phpDockerSettingsConfigs,
24
- ctx
25
- )
26
- ])
21
+ const hasChanges = await setupPHPDockerContainerSettingsConfig(
22
+ phpDockerSettingsConfigs,
23
+ ctx
24
+ )
27
25
 
28
- if (hasChanges.includes(true)) {
26
+ if (hasChanges === true) {
29
27
  await buildXmlFile(
30
28
  phpDockerSettingsConfig.path,
31
29
  phpDockerSettingsConfigContent
@@ -41,9 +39,7 @@ const setupPhpDockerSettingsConfig = () => ({
41
39
  const phpDockerSettingsConfigs =
42
40
  phpDockerSettingsConfigContent.project.component
43
41
 
44
- await Promise.all([
45
- setupPHPDockerContainerSettingsConfig(phpDockerSettingsConfigs, ctx)
46
- ])
42
+ await setupPHPDockerContainerSettingsConfig(phpDockerSettingsConfigs, ctx)
47
43
 
48
44
  await buildXmlFile(
49
45
  phpDockerSettingsConfig.path,
@@ -283,7 +283,7 @@ const installMagentoProject = () => ({
283
283
  isVendorFolderCorrupted = await getIsVendorFolderCorrupted(
284
284
  baseConfig.magentoDir
285
285
  )
286
- } catch (e) {
286
+ } catch {
287
287
  // ignore error just in case
288
288
  }
289
289
 
@@ -1,5 +1,8 @@
1
1
  const { updateTableValues, databaseQuery } = require('../../../util/database')
2
2
  const KnownError = require('../../../errors/known-error')
3
+ const {
4
+ resolveStoreDomainsForScopes
5
+ } = require('../../../util/store-domains')
3
6
 
4
7
  /**
5
8
  * @param {number} scopeId
@@ -129,25 +132,10 @@ const setBaseUrl = () => ({
129
132
  )
130
133
  }
131
134
 
132
- const storeDomainsWithMapping = Object.entries(storeDomains).reduce(
133
- (acc, [key, val]) => {
134
- const entity = entities.find(
135
- /** @param {{ code: string }} entity */
136
- (entity) => entity.code === key
137
- )
138
- if (entity) {
139
- return {
140
- ...acc,
141
- [entity.code]: {
142
- scopeId: entity[idField],
143
- domain: val
144
- }
145
- }
146
- }
147
-
148
- return acc
149
- },
150
- {}
135
+ const storeDomainsWithMapping = resolveStoreDomainsForScopes(
136
+ storeDomains,
137
+ entities,
138
+ idField
151
139
  )
152
140
 
153
141
  // Check for missing store codes when runType is 'store'
@@ -80,7 +80,7 @@ const waitingForVarnish = () => ({
80
80
  } else {
81
81
  break
82
82
  }
83
- } catch (e) {
83
+ } catch {
84
84
  tries++
85
85
  await sleep(200)
86
86
  }
@@ -155,7 +155,7 @@ Do you want to try resolving this issue by replacing ${logger.style.file(
155
155
  connectionFixed = true
156
156
  break
157
157
  }
158
- } catch (e) {
158
+ } catch {
159
159
  await sleep(200)
160
160
  }
161
161
  }
@@ -94,7 +94,7 @@ const checkDockerStatusMacOS = () => ({
94
94
  } else {
95
95
  ready = true
96
96
  }
97
- } catch (e) {
97
+ } catch {
98
98
  //
99
99
  }
100
100
  }
@@ -79,7 +79,7 @@ const checkSearchEngineVersion = () => ({
79
79
  : 'OpenSearch'
80
80
  } version ${searchEngineVersion} in container`
81
81
  }
82
- } catch (e) {
82
+ } catch {
83
83
  subTask.skip()
84
84
  }
85
85
  }
@@ -8,6 +8,16 @@ const { getArchSync } = require('../../util/arch')
8
8
  const ConsoleBlock = require('../../util/console-block')
9
9
  const { getInstanceMetadata } = require('../../util/instance-metadata')
10
10
 
11
+ // eslint-disable-next-line no-control-regex
12
+ const consoleStyleReplacer = /[]\[\S+?m/g
13
+
14
+ /**
15
+ * chalk already drops styling when stdout is not a TTY, but strip defensively
16
+ * so the plain output stays clean even under FORCE_COLOR.
17
+ * @param {string} str
18
+ */
19
+ const stripStyle = (str) => String(str).replace(consoleStyleReplacer, '')
20
+
11
21
  /**
12
22
  * @param {any} str
13
23
  * @returns {str is string}
@@ -18,7 +28,7 @@ const isJSON = (str) => {
18
28
  if (typeof result === 'object') {
19
29
  return true
20
30
  }
21
- } catch (e) {
31
+ } catch {
22
32
  //
23
33
  }
24
34
 
@@ -230,9 +240,10 @@ const prettyStatus = async (ctx) => {
230
240
 
231
241
  Object.values(volumes)
232
242
  .map((volume) => {
233
- volume.volumeData = systemDFData.Volumes.find(
234
- (v) => v.Name === volume.name
235
- )
243
+ volume.volumeData =
244
+ systemDFData &&
245
+ systemDFData.Volumes &&
246
+ systemDFData.Volumes.find((v) => v.Name === volume.name)
236
247
 
237
248
  return volume
238
249
  })
@@ -273,4 +284,174 @@ const prettyStatus = async (ctx) => {
273
284
  block.log()
274
285
  }
275
286
 
276
- module.exports = { prettyStatus }
287
+ /**
288
+ * Plain-text status summary for non-interactive environments (AI agents, CI,
289
+ * pipes). No box drawing or ANSI styling — just the facts an agent needs to
290
+ * act, plus the commands to run next.
291
+ * @param {import('../../../typings/context').ListrContext & { containers: ReturnType<Awaited<ReturnType<import('../../config/docker')>>['getContainers']> }} ctx
292
+ */
293
+ const simpleStatus = (ctx) => {
294
+ const {
295
+ config: { baseConfig },
296
+ magentoVersion,
297
+ composerVersion,
298
+ dockerVersion,
299
+ containers,
300
+ systemDFData,
301
+ verbose
302
+ } = ctx
303
+
304
+ const lines = []
305
+
306
+ lines.push(
307
+ `magento-scripts ${packageVersion} — status (non-interactive)`,
308
+ '',
309
+ `Project: ${baseConfig.prefix}`,
310
+ `Location: ${process.cwd()}`,
311
+ `Magento: ${magentoVersion || 'unknown'}`,
312
+ `PHP: ${ctx.phpVersion || 'unknown'}`,
313
+ `Composer: ${composerVersion || 'unknown'}`,
314
+ `Docker: ${dockerVersion || 'unknown'}`,
315
+ `Platform: ${ctx.platform} (${getArchSync()})`
316
+ )
317
+
318
+ lines.push(
319
+ `Platform version: ${ctx.platformVersion || 'unknown'}`,
320
+ `CGroup version: ${ctx.cgroupVersion || 'unknown'}`
321
+ )
322
+
323
+ const projectCreatedAt = getProjectCreatedAt()
324
+ if (projectCreatedAt) {
325
+ lines.push(
326
+ `Project created: ${projectCreatedAt.toDateString()} ${projectCreatedAt.toTimeString()}`
327
+ )
328
+ }
329
+
330
+ lines.push('', 'Containers:')
331
+
332
+ let anyRunning = false
333
+
334
+ Object.values(containers).forEach((container) => {
335
+ const state = container.status && container.status.State
336
+ let status
337
+
338
+ if (state && state.Health && state.Status === 'running') {
339
+ status = `running (${state.Health.Status})`
340
+ anyRunning = true
341
+ } else if (state && state.Status && state.Status !== 'exited') {
342
+ status = state.Status
343
+ if (state.Status === 'running') {
344
+ anyRunning = true
345
+ }
346
+ } else {
347
+ status = 'not running'
348
+ }
349
+
350
+ lines.push(` ${container._ || container.name}: ${status}`)
351
+
352
+ lines.push(` Name: ${container.name}`)
353
+
354
+ const image =
355
+ container.status &&
356
+ container.status.Config &&
357
+ container.status.Config.Image
358
+ ? container.status.Config.Image
359
+ : container.image
360
+ lines.push(` Image: ${image}`, ` Network: ${container.network}`)
361
+
362
+ if (
363
+ status !== 'not running' &&
364
+ container.forwardedPorts &&
365
+ container.forwardedPorts.length > 0
366
+ ) {
367
+ lines.push(' Port forwarding:')
368
+ container.forwardedPorts.forEach((port) => {
369
+ const { host, hostPort, containerPort } = parsePort(port)
370
+ if (container.network !== 'host') {
371
+ lines.push(
372
+ ` ${host}:${hostPort} -> ${containerPort} (host -> container)`
373
+ )
374
+ } else {
375
+ lines.push(
376
+ ` Running on host network - ${host}:${hostPort}`
377
+ )
378
+ }
379
+ })
380
+ }
381
+
382
+ if (container.env && Object.keys(container.env).length > 0) {
383
+ lines.push(' Environment variables:')
384
+ for (const [envName, envValue] of Object.entries(container.env)) {
385
+ lines.push(` ${envName}=${envValue}`)
386
+ }
387
+ }
388
+
389
+ if (container.description) {
390
+ lines.push(' Description:')
391
+ container.description.split('\n').forEach((line) => {
392
+ lines.push(` ${stripStyle(line)}`)
393
+ })
394
+ }
395
+ })
396
+
397
+ lines.push('', 'Volumes:')
398
+
399
+ const { volumes } = ctx.config.docker
400
+
401
+ Object.values(volumes).forEach((volume) => {
402
+ const volumeData =
403
+ systemDFData &&
404
+ systemDFData.Volumes &&
405
+ systemDFData.Volumes.find((v) => v.Name === volume.name)
406
+
407
+ lines.push(` ${volume.name}`)
408
+
409
+ if (volumeData) {
410
+ lines.push(` Size: ${volumeData.Size}`)
411
+ }
412
+
413
+ if (ctx.isDockerDesktop && volume.opt && volume.opt.device) {
414
+ lines.push(
415
+ ` Mountpoint: ${volume.opt.device.replace(
416
+ process.cwd(),
417
+ '<project location>'
418
+ )}`
419
+ )
420
+ }
421
+ })
422
+
423
+ if (!verbose) {
424
+ lines.push(' (volume sizes omitted — pass --verbose to include them)')
425
+ }
426
+
427
+ const instanceMetadata = getInstanceMetadata(ctx)
428
+
429
+ lines.push('', 'Frontend:')
430
+ instanceMetadata.frontend.forEach(({ title, text }) => {
431
+ lines.push(` ${stripStyle(title)}: ${stripStyle(text)}`)
432
+ })
433
+
434
+ lines.push('', 'Admin:')
435
+ instanceMetadata.admin.forEach(({ title, text }) => {
436
+ lines.push(` ${stripStyle(title)}: ${stripStyle(text)}`)
437
+ })
438
+
439
+ lines.push('', 'MailDev:')
440
+ instanceMetadata.maildev.forEach(({ title, text }) => {
441
+ lines.push(` ${stripStyle(title)}: ${stripStyle(text)}`)
442
+ })
443
+
444
+ lines.push('')
445
+ if (!anyRunning) {
446
+ lines.push(
447
+ 'Environment is not running. Start it with: magento-scripts start'
448
+ )
449
+ }
450
+ lines.push(
451
+ 'Run Magento CLI: magento-scripts exec php bin/magento <command>'
452
+ )
453
+
454
+ logger.log(lines.join('\n'))
455
+ }
456
+
457
+ module.exports = { prettyStatus, simpleStatus }
@@ -30,7 +30,7 @@ const buildTheme = ({ themePath }) => ({
30
30
  task.output = t
31
31
  }
32
32
  })
33
- } catch (e) {
33
+ } catch {
34
34
  throw new UnknownError(`We were unable to install theme dependencies in ${themePath} using ${logger.style.code(
35
35
  commandToInstallDependencies
36
36
  )} command!
@@ -168,7 +168,7 @@ class Analytics {
168
168
 
169
169
  try {
170
170
  this.setClientIdentifier(generateUUID())
171
- } catch (e) {
171
+ } catch {
172
172
  this.setClientIdentifier(`${Date.now()}`)
173
173
  }
174
174
  }
@@ -233,7 +233,7 @@ class Analytics {
233
233
  analyticsParameters.session = {
234
234
  ipOverride: await getExternalIpAddress()
235
235
  }
236
- } catch (e) {
236
+ } catch {
237
237
  // Do nothing
238
238
  }
239
239
 
@@ -67,7 +67,7 @@ const execAsyncSpawn = (
67
67
  .split('\n')
68
68
  .map((str) => str.trim())
69
69
  .forEach((str) => {
70
- callback && callback(str)
70
+ callback?.(str)
71
71
  })
72
72
  if (logOutput) {
73
73
  newData
package/lib/util/ip.js CHANGED
@@ -39,7 +39,7 @@ const getExternalIpAddress = async () => {
39
39
  ip = response.data
40
40
  break
41
41
  }
42
- } catch (e) {
42
+ } catch {
43
43
  //
44
44
  }
45
45
  }
@@ -22,7 +22,7 @@ const getIsWsl = async () => {
22
22
  const procVersion = await fs.promises.readFile('/proc/version', 'utf8')
23
23
 
24
24
  return procVersion.toLowerCase().includes('microsoft')
25
- } catch (e) {
25
+ } catch {
26
26
  return false
27
27
  }
28
28
  }
@@ -7,7 +7,7 @@ const fs = require('fs')
7
7
  const pathExistsSync = (path) => {
8
8
  try {
9
9
  fs.accessSync(path, fs.constants.F_OK)
10
- } catch (e) {
10
+ } catch {
11
11
  return false
12
12
  }
13
13
 
@@ -8,7 +8,7 @@ const fs = require('fs')
8
8
  const pathExists = async (path) => {
9
9
  try {
10
10
  await fs.promises.access(path, fs.constants.F_OK)
11
- } catch (e) {
11
+ } catch {
12
12
  return false
13
13
  }
14
14
 
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Enable nginx host → store mapping only when multiple stores use distinct domains.
3
+ * Duplicate hostnames produce invalid nginx map directives.
4
+ *
5
+ * @param {Record<string, string> | undefined} storeDomains
6
+ * @returns {boolean}
7
+ */
8
+ const shouldUseStoreDomainMapping = (storeDomains) => {
9
+ if (!storeDomains) {
10
+ return false
11
+ }
12
+
13
+ const entries = Object.entries(storeDomains)
14
+
15
+ if (entries.length <= 1) {
16
+ return false
17
+ }
18
+
19
+ const domains = entries.map(([, domain]) => domain)
20
+
21
+ return new Set(domains).size === domains.length
22
+ }
23
+
24
+ /**
25
+ * Map store/website entities to domains from storeDomains, falling back to admin
26
+ * for scopes not explicitly configured (e.g. website "base" on single-domain setups).
27
+ *
28
+ * @param {Record<string, string>} storeDomains
29
+ * @param {{ code: string }[]} entities
30
+ * @param {'website_id' | 'store_id'} idField
31
+ * @returns {Record<string, { scopeId: number, domain: string }>}
32
+ */
33
+ const resolveStoreDomainsForScopes = (storeDomains, entities, idField) => {
34
+ const defaultDomain = storeDomains.admin
35
+
36
+ /** @type {Record<string, { scopeId: number, domain: string }>} */
37
+ const mapped = Object.entries(storeDomains).reduce(
38
+ (acc, [key, val]) => {
39
+ const entity = entities.find((entity) => entity.code === key)
40
+
41
+ if (entity) {
42
+ return {
43
+ ...acc,
44
+ [entity.code]: {
45
+ scopeId: entity[idField],
46
+ domain: val
47
+ }
48
+ }
49
+ }
50
+
51
+ return acc
52
+ },
53
+ {}
54
+ )
55
+
56
+ for (const entity of entities) {
57
+ if (!mapped[entity.code]) {
58
+ mapped[entity.code] = {
59
+ scopeId: entity[idField],
60
+ domain: defaultDomain
61
+ }
62
+ }
63
+ }
64
+
65
+ return mapped
66
+ }
67
+
68
+ module.exports = {
69
+ shouldUseStoreDomainMapping,
70
+ resolveStoreDomainsForScopes
71
+ }
@@ -0,0 +1,52 @@
1
+ const assert = require('assert')
2
+ const {
3
+ shouldUseStoreDomainMapping,
4
+ resolveStoreDomainsForScopes
5
+ } = require('./store-domains')
6
+
7
+ assert.strictEqual(shouldUseStoreDomainMapping(undefined), false)
8
+ assert.strictEqual(shouldUseStoreDomainMapping({ admin: 'localhost' }), false)
9
+ assert.strictEqual(
10
+ shouldUseStoreDomainMapping({
11
+ admin: 'shop.local',
12
+ base: 'shop.local'
13
+ }),
14
+ false
15
+ )
16
+ assert.strictEqual(
17
+ shouldUseStoreDomainMapping({
18
+ admin: 'admin.local',
19
+ base: 'shop.local'
20
+ }),
21
+ true
22
+ )
23
+
24
+ const websites = [
25
+ { website_id: 0, code: 'admin' },
26
+ { website_id: 1, code: 'base' }
27
+ ]
28
+
29
+ const resolved = resolveStoreDomainsForScopes(
30
+ { admin: 'jollyes.local' },
31
+ websites,
32
+ 'website_id'
33
+ )
34
+
35
+ assert.deepStrictEqual(resolved, {
36
+ admin: { scopeId: 0, domain: 'jollyes.local' },
37
+ base: { scopeId: 1, domain: 'jollyes.local' }
38
+ })
39
+
40
+ assert.deepStrictEqual(
41
+ resolveStoreDomainsForScopes(
42
+ { admin: 'jollyes.local', base: 'other.local' },
43
+ websites,
44
+ 'website_id'
45
+ ),
46
+ {
47
+ admin: { scopeId: 0, domain: 'jollyes.local' },
48
+ base: { scopeId: 1, domain: 'other.local' }
49
+ }
50
+ )
51
+
52
+ console.log('store-domains.test.js: ok')
@@ -66,7 +66,7 @@ const systemctlControl = (serviceName, defaultOptions = {}) => ({
66
66
  ...options
67
67
  })
68
68
  return result.includes('enabled')
69
- } catch (e) {
69
+ } catch {
70
70
  return false
71
71
  }
72
72
  },
@@ -81,7 +81,7 @@ const systemctlControl = (serviceName, defaultOptions = {}) => ({
81
81
  })
82
82
 
83
83
  return result.includes('active (running)')
84
- } catch (e) {
84
+ } catch {
85
85
  return false
86
86
  }
87
87
  },
@@ -101,7 +101,7 @@ const systemctlControl = (serviceName, defaultOptions = {}) => ({
101
101
  const result = await execAsyncSpawn(command)
102
102
 
103
103
  return result === '1'
104
- } catch (e) {
104
+ } catch {
105
105
  return false
106
106
  }
107
107
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Scripts and configuration used by CMA.",
4
4
  "homepage": "https://docs.create-magento-app.com/",
5
5
  "repository": "github:scandipwa/create-magento-app",
6
- "version": "2.4.12-alpha.0",
6
+ "version": "2.4.13",
7
7
  "main": "./index.js",
8
8
  "types": "./typings/index.d.ts",
9
9
  "license": "OSL-3.0",
@@ -59,5 +59,9 @@
59
59
  "@types/node": "^20.14.11",
60
60
  "@types/yargs": "^17.0.32"
61
61
  },
62
- "gitHead": "0fe2409c4e619ff985cf90a7f3d6cc4d45ca2552"
62
+ "scripts": {
63
+ "lint": "oxlint",
64
+ "test:store-domains": "node lib/util/store-domains.test.js"
65
+ },
66
+ "gitHead": "a6d5ac78bf24f1ebd5b725248bb775e3e7c3d3ef"
63
67
  }
package/.eslintrc DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "globals": {
3
- "verbose":true,
4
- "NodeJS": true,
5
- "BufferEncoding": true
6
- },
7
- "env": {
8
- "node": true
9
- }
10
- }
@@ -1,79 +0,0 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
-
4
- const AGENTS_MD_CONTENT = `# CMA (magento-scripts) — AI/CI Reference
5
-
6
- > Auto-generated by magento-scripts. Do not delete.
7
-
8
- ## Critical
9
-
10
- - **Cannot run as root** — exits immediately with code 1, no override.
11
- - **Non-TTY safe** — silent renderer activates automatically in CI/pipes; no \`-q\` needed.
12
- - **Do NOT use \`--\` with exec** — it gets passed as the command and fails. Correct: \`magento-scripts exec php bin/magento cache:flush\`
13
- - **\`cli\` is TTY-only** — use \`exec php bin/magento <cmd>\` in automation instead.
14
- - **\`import-db\` is self-contained** — it stops running containers, assigns ports, starts services, waits for MariaDB, and imports. Do NOT run \`start\` before or between \`import-db\` attempts — that creates port conflicts. Just run \`import-db\` directly.
15
- - **Do NOT run \`start\` then \`import-db\`** — each command manages its own container lifecycle. Running both creates split-brain port assignments. Use one or the other.
16
- - **Long-running commands** — \`start\` and \`import-db\` can take 10+ minutes (container setup, large dumps). Set timeouts to at least 600000ms (10 min) or run without a timeout.
17
- - **Shell escaping** — Avoid \`!\` in SQL strings passed via \`exec\` (e.g., \`!=\`), as the shell may interpret it. Use SQL alternatives like \`<>\` instead of \`!=\`.
18
-
19
- ## Commands
20
-
21
- | Command | What it does | Key flags |
22
- |---------|-------------|-----------|
23
- | \`start\` | Start Docker environment | \`--no-open\`, \`--skip-setup\`, \`--port\` |
24
- | \`stop\` | Stop all containers | — |
25
- | \`status\` | Show container/DB status | — |
26
- | \`exec <container> [cmd...]\` | Run command in container | use \`--\` before flags |
27
- | \`import-db [file]\` | Import SQL dump into MariaDB | \`-y\` (non-interactive), \`--remote-db=ssh://user@host\` |
28
- | \`logs <container>\` | Stream container logs | \`--tail N\`, \`--follow\` |
29
- | \`cleanup\` | Remove cached/generated files | \`--force\` |
30
- | \`cli\` | Interactive shell (TTY only) | — |
31
- | \`link <path>\` | Link ScandiPWA theme | — |
32
-
33
- ## Containers
34
-
35
- \`php\`, \`phpWithXdebug\`, \`nginx\`, \`sslTerminator\`, \`redis\`, \`mariadb\`, \`elasticsearch\`, \`maildev\`, \`varnish\` (if enabled)
36
-
37
- ## Examples
38
-
39
- \`\`\`bash
40
- magento-scripts start --no-open --skip-setup
41
- magento-scripts import-db dump.sql -y
42
- magento-scripts import-db -y --remote-db=ssh://user@host
43
- magento-scripts exec php bin/magento cache:flush
44
- magento-scripts exec php bin/magento indexer:reindex
45
-
46
- # Query MariaDB (use mariadb binary, not mysql)
47
- magento-scripts exec mariadb mariadb -u magento -pmagento magento -e "SELECT COUNT(*) FROM store"
48
- magento-scripts logs magento --tail 100
49
- magento-scripts stop
50
- \`\`\`
51
- `
52
-
53
- /**
54
- * Write AGENTS.md and CLAUDE.md to the current working directory if they do not already exist.
55
- * This gives AI agents and CI pipelines a command reference for the project.
56
- */
57
- function ensureAgentsMd() {
58
- const cwd = process.cwd()
59
-
60
- const agentsDest = path.join(cwd, 'AGENTS.md')
61
- if (!fs.existsSync(agentsDest)) {
62
- try {
63
- fs.writeFileSync(agentsDest, AGENTS_MD_CONTENT, 'utf8')
64
- } catch (e) {
65
- // Non-fatal — silently skip if the directory is not writable
66
- }
67
- }
68
-
69
- const claudeDest = path.join(cwd, 'CLAUDE.md')
70
- if (!fs.existsSync(claudeDest)) {
71
- try {
72
- fs.writeFileSync(claudeDest, '@AGENTS.md\n', 'utf8')
73
- } catch (e) {
74
- // Non-fatal
75
- }
76
- }
77
- }
78
-
79
- module.exports = ensureAgentsMd