@scandipwa/magento-scripts 2.4.0-alpha.1 → 2.4.0-alpha.3

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 (62) hide show
  1. package/lib/commands/import-db.js +36 -3
  2. package/lib/config/docker.js +164 -56
  3. package/lib/config/port-config.js +46 -10
  4. package/lib/config/services/elasticsearch/default-es-env.js +1 -1
  5. package/lib/config/services/mariadb/versions/mariadb-10.2.js +3 -1
  6. package/lib/config/services/mariadb/versions/mariadb-10.3.js +3 -1
  7. package/lib/config/services/mariadb/versions/mariadb-10.4.js +3 -1
  8. package/lib/config/services/mariadb/versions/mariadb-10.6.js +3 -1
  9. package/lib/config/services/mariadb/versions/mariadb-11.4.js +3 -1
  10. package/lib/config/services/mariadb/versions/mariadb-11.6.js +3 -1
  11. package/lib/config/services/opensearch/default-os-env.js +1 -1
  12. package/lib/config/services/php/extensions/xdebug.js +1 -0
  13. package/lib/config/templates/nginx.template.conf +2 -2
  14. package/lib/config/templates/php-fpm.template.conf +3 -4
  15. package/lib/config/templates/ssl-terminator.template.conf +1 -1
  16. package/lib/tasks/database/create-magento-database.js +2 -1
  17. package/lib/tasks/database/fix-db.js +2 -0
  18. package/lib/tasks/database/import-dump-to-database.js +7 -4
  19. package/lib/tasks/database/import-remote-db/ssh/index.js +1 -1
  20. package/lib/tasks/database/import-remote-db/ssh/readymage.js +1 -1
  21. package/lib/tasks/database/import-remote-db/ssh/regular-server.js +1 -1
  22. package/lib/tasks/docker/containers/container-api.d.ts +5 -0
  23. package/lib/tasks/docker/containers/container-api.js +3 -1
  24. package/lib/tasks/docker/containers/tasks.js +86 -21
  25. package/lib/tasks/docker/project-image-builder.js +57 -44
  26. package/lib/tasks/docker/system/system-api.d.ts +66 -0
  27. package/lib/tasks/docker/system/system-api.js +28 -1
  28. package/lib/tasks/execute.js +1 -1
  29. package/lib/tasks/file-system/create-nginx-config.js +22 -8
  30. package/lib/tasks/file-system/create-php-fpm-config.js +6 -1
  31. package/lib/tasks/file-system/create-php-fpm-debug-config.js +6 -1
  32. package/lib/tasks/file-system/create-ssl-terminator-config.js +20 -7
  33. package/lib/tasks/magento/install-magento-project.js +101 -34
  34. package/lib/tasks/magento/setup-magento/check-file-permissions.php +55 -0
  35. package/lib/tasks/magento/setup-magento/disable-custom-admin-path.js +21 -0
  36. package/lib/tasks/magento/setup-magento/disable-maintenance-mode.js +13 -0
  37. package/lib/tasks/magento/setup-magento/index.js +2 -0
  38. package/lib/tasks/magento/setup-magento/make-magento-binaries-executable.js +44 -0
  39. package/lib/tasks/magento/setup-magento/set-deployment-mode.js +8 -5
  40. package/lib/tasks/magento/setup-magento/set-mail-config.js +16 -2
  41. package/lib/tasks/magento/setup-magento/setup-file-permissions.js +236 -0
  42. package/lib/tasks/php/php-container.js +21 -6
  43. package/lib/tasks/php/update-env-php.js +3 -9
  44. package/lib/tasks/requirements/cgroup-version.js +69 -0
  45. package/lib/tasks/requirements/elasticsearch-version.js +19 -3
  46. package/lib/tasks/requirements/index.js +3 -0
  47. package/lib/tasks/requirements/opensearch-version.js +1 -1
  48. package/lib/tasks/requirements/searchengine-version.js +1 -2
  49. package/lib/tasks/status/index.js +1 -0
  50. package/lib/util/database.js +36 -0
  51. package/lib/util/dockerfile-builder/build-instructions.js +5 -1
  52. package/lib/util/dockerfile-builder/types.d.ts +1 -1
  53. package/lib/util/execute-in-container.js +3 -1
  54. package/lib/util/get-installed-magento-version.js +60 -2
  55. package/lib/util/portscanner.js +3 -3
  56. package/lib/util/run-composer.js +1 -1
  57. package/lib/util/run-magento.js +2 -1
  58. package/lib/util/run-php.js +2 -1
  59. package/lib/util/set-config.js +4 -2
  60. package/package.json +16 -16
  61. package/typings/context.d.ts +4 -2
  62. package/typings/index.d.ts +10 -0
@@ -9,6 +9,11 @@ const getJsonFileData = require('../../util/get-jsonfile-data')
9
9
  const KnownError = require('../../errors/known-error')
10
10
  const UnknownError = require('../../errors/unknown-error')
11
11
  const { runPHPContainerCommand } = require('../php/php-container')
12
+ const {
13
+ setupMagentoFilePermissions,
14
+ setupComposerCachePermissions
15
+ } = require('./setup-magento/setup-file-permissions')
16
+ const makeBinariesExecutable = require('./setup-magento/make-magento-binaries-executable')
12
17
 
13
18
  const magentoProductEnterpriseEdition = 'magento/product-enterprise-edition'
14
19
  const magentoProductCommunityEdition = 'magento/product-community-edition'
@@ -59,7 +64,8 @@ const adjustComposerJson = async (
59
64
  ? undefined
60
65
  : (t) => {
61
66
  task.output = t
62
- }
67
+ },
68
+ useAutomaticUser: true
63
69
  }
64
70
  )
65
71
  }
@@ -154,6 +160,46 @@ mv ${tempDir}/composer.json ${
154
160
  )
155
161
  }
156
162
 
163
+ /**
164
+ * Will check if the following conditions are met:
165
+ * - composer.lock file exists
166
+ * - vendor directory exists
167
+ * - all packages from composer.lock are installed in vendor directory
168
+ * @param {string} magentoDir
169
+ */
170
+ const getIsVendorFolderCorrupted = async (magentoDir) => {
171
+ const composerLockFile = path.join(magentoDir, 'composer.lock')
172
+ const vendorDir = path.join(magentoDir, 'vendor')
173
+ const [vendorDirStat, composerLockFileStat] = await Promise.all([
174
+ pathExists(vendorDir),
175
+ pathExists(composerLockFile)
176
+ ])
177
+ if (!vendorDirStat || !composerLockFileStat) {
178
+ return true
179
+ }
180
+ /**
181
+ * @type {{ packages: { name: string }[] } | null}
182
+ */
183
+ const composerLockData = await getJsonFileData(composerLockFile)
184
+ if (!composerLockData || !composerLockData.packages) {
185
+ return true
186
+ }
187
+ const { packages } = composerLockData
188
+ const packagesNames = packages.map((pkg) => pkg.name)
189
+
190
+ const hasCorruptPackages = await Promise.all(
191
+ packagesNames.map(async (pkg) => {
192
+ const vendorPackage = path.join(vendorDir, pkg)
193
+ const composerJson = path.join(vendorPackage, 'composer.json')
194
+ if (!(await pathExists(composerJson))) {
195
+ return true
196
+ }
197
+ })
198
+ )
199
+
200
+ return hasCorruptPackages.every((result) => result === true)
201
+ }
202
+
157
203
  /**
158
204
  * @returns {import('listr2').ListrTask<import('../../../typings/context').ListrContext>}
159
205
  */
@@ -191,7 +237,11 @@ const installMagentoProject = () => ({
191
237
  'composer.lock': true
192
238
  })
193
239
 
194
- if (isFsMatching) {
240
+ const isVendorFolderCorrupted = await getIsVendorFolderCorrupted(
241
+ baseConfig.magentoDir
242
+ )
243
+
244
+ if (isFsMatching && !isVendorFolderCorrupted) {
195
245
  ctx.magentoFirstInstall = false
196
246
  task.skip()
197
247
  return
@@ -200,43 +250,60 @@ const installMagentoProject = () => ({
200
250
  task.title = `Installing Magento ${magentoPackageVersion}`
201
251
  task.output = `Creating Magento ${magentoPackageVersion} project`
202
252
 
203
- if (!(await pathExists(path.join(process.cwd(), 'composer.json')))) {
204
- await createMagentoProject(ctx, task, {
205
- magentoProject,
206
- magentoPackageVersion
207
- })
208
- }
209
-
210
- if (!(await pathExists(path.join(process.cwd(), 'app', 'etc')))) {
211
- await fs.promises.mkdir(path.join(process.cwd(), 'app', 'etc'), {
212
- recursive: true
213
- })
214
- }
215
-
216
- try {
217
- await runComposerCommand(ctx, 'install', {
218
- callback: !ctx.verbose
219
- ? undefined
220
- : (t) => {
221
- task.output = t
222
- }
223
- })
224
- } catch (e) {
253
+ if (!isFsMatching) {
225
254
  if (
226
- e instanceof UnknownError &&
227
- e.message.includes('man-in-the-middle attack')
255
+ !(await pathExists(path.join(process.cwd(), 'composer.json')))
228
256
  ) {
229
- throw new KnownError(`Probably you haven't setup pubkeys in composer.
230
- Please run ${logger.style.command(
231
- 'composer diagnose'
232
- )} in cli to get mode.\n\n${e}`)
257
+ await createMagentoProject(ctx, task, {
258
+ magentoProject,
259
+ magentoPackageVersion
260
+ })
233
261
  }
234
262
 
235
- throw new UnknownError(
236
- `Unexpected error during composer install.\n\n${e}`
237
- )
263
+ if (!(await pathExists(path.join(process.cwd(), 'app', 'etc')))) {
264
+ await fs.promises.mkdir(
265
+ path.join(process.cwd(), 'app', 'etc'),
266
+ {
267
+ recursive: true
268
+ }
269
+ )
270
+ }
238
271
  }
239
- ctx.magentoFirstInstall = true
272
+
273
+ return task.newListr([
274
+ setupMagentoFilePermissions(),
275
+ setupComposerCachePermissions(),
276
+ {
277
+ title: 'Installing Magento dependencies',
278
+ task: async () => {
279
+ try {
280
+ await runComposerCommand(ctx, 'install -vvv', {
281
+ callback: !ctx.verbose
282
+ ? undefined
283
+ : (t) => {
284
+ task.output = t
285
+ }
286
+ })
287
+ } catch (e) {
288
+ if (
289
+ e instanceof UnknownError &&
290
+ e.message.includes('man-in-the-middle attack')
291
+ ) {
292
+ throw new KnownError(`Probably you haven't setup pubkeys in composer.
293
+ Please run ${logger.style.command(
294
+ 'composer diagnose'
295
+ )} in cli to get mode.\n\n${e}`)
296
+ }
297
+
298
+ throw new UnknownError(
299
+ `Unexpected error during composer install.\n\n${e}`
300
+ )
301
+ }
302
+ ctx.magentoFirstInstall = true
303
+ }
304
+ },
305
+ makeBinariesExecutable()
306
+ ])
240
307
  },
241
308
  options: {
242
309
  bottomBar: 10
@@ -0,0 +1,55 @@
1
+ <?php
2
+ // receive directories from command line arguments
3
+ $directories = explode(",", $argv[1]);
4
+
5
+ $results = [];
6
+
7
+ foreach ($directories as $directory) {
8
+ $result = [];
9
+ $result['directory'] = $directory;
10
+ $result['exists'] = file_exists($directory);
11
+ $result['writable'] = is_writable($directory);
12
+ $result['readable'] = is_readable($directory);
13
+
14
+ if ($result['exists']) {
15
+ $result['owner'] = posix_getpwuid(fileowner($directory));
16
+ $result['group'] = posix_getgrgid(filegroup($directory));
17
+ $result['current_user'] = posix_getpwuid(posix_geteuid());
18
+
19
+ // Check if current user is in the group
20
+ if ($result['group']['members'] && is_array($result['group']['members'])) {
21
+ $result['current_user_in_group'] = in_array($result['current_user']['name'], $result['group']['members']);
22
+ } else {
23
+ $result['current_user_in_group'] = false;
24
+ }
25
+
26
+ // Check if the directory has group write permissions (g+w)
27
+ $permissions = fileperms($directory);
28
+ $result['has_group_write_permissions'] = ($permissions & 0x0010) !== 0;
29
+
30
+ $result['permissions'] = substr(sprintf('%o', fileperms($directory)), -4);
31
+ $result['is_current_user_directory_owner'] = $result['current_user'] === $result['owner'];
32
+
33
+ if ($result['writable']) {
34
+ // Check if new files inherit the directory's group
35
+ try {
36
+ $tempFile = $directory . DIRECTORY_SEPARATOR . '.temp_file_' . uniqid();
37
+ touch($tempFile);
38
+
39
+ $tempFileGroup = posix_getgrgid(filegroup($tempFile));
40
+ $result['new_files_inherit_group'] = $tempFileGroup === $result['group'];
41
+ } catch (Exception $e) {
42
+ $result['new_files_inherit_group'] = false;
43
+ } finally {
44
+ // Clean up the temporary file
45
+ if (file_exists($tempFile)) {
46
+ unlink($tempFile);
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ $results[] = $result;
53
+ }
54
+
55
+ echo json_encode($results, JSON_PRETTY_PRINT);
@@ -0,0 +1,21 @@
1
+ const { deleteTableValues } = require('../../../util/database')
2
+
3
+ /**
4
+ * @returns {import('listr2').ListrTask<import('../../../../typings/context').ListrContext>}
5
+ */
6
+ const disableCustomAdminPath = () => ({
7
+ title: 'Disabling custom admin path',
8
+ task: async ({ databaseConnection }, task) => {
9
+ await deleteTableValues(
10
+ 'core_config_data',
11
+ [
12
+ {
13
+ path: 'admin/url/use_custom_path'
14
+ }
15
+ ],
16
+ { databaseConnection, task }
17
+ )
18
+ }
19
+ })
20
+
21
+ module.exports = disableCustomAdminPath
@@ -1,3 +1,5 @@
1
+ const path = require('path')
2
+ const pathExists = require('../../../util/path-exists')
1
3
  const runMagentoCommand = require('../../../util/run-magento')
2
4
 
3
5
  /**
@@ -6,6 +8,17 @@ const runMagentoCommand = require('../../../util/run-magento')
6
8
  const disableMaintenanceMode = () => ({
7
9
  title: 'Disabling maintenance mode',
8
10
  task: async (ctx, task) => {
11
+ const maintenanceModeFile = `var/.maintenance.flag`
12
+
13
+ if (
14
+ !(await pathExists(
15
+ path.join(ctx.config.baseConfig.magentoDir, maintenanceModeFile)
16
+ ))
17
+ ) {
18
+ task.skip()
19
+ return
20
+ }
21
+
9
22
  const { result } = await runMagentoCommand(ctx, 'maintenance:status', {
10
23
  throwNonZeroCode: false
11
24
  })
@@ -13,6 +13,7 @@ const urnHighlighter = require('./urn-highlighter')
13
13
  const adjustFullPageCache = require('./adjust-full-page-cache')
14
14
  const updateEnvPHP = require('../../php/update-env-php')
15
15
  const setMailConfig = require('./set-mail-config')
16
+ const { setupMagentoFilePermissions } = require('./setup-file-permissions')
16
17
 
17
18
  /**
18
19
  * @param {Object} [options]
@@ -34,6 +35,7 @@ const setupMagento = (options = {}) => ({
34
35
  return task.newListr(
35
36
  [
36
37
  waitingForRedis(),
38
+ setupMagentoFilePermissions(),
37
39
  updateEnvPHP(),
38
40
  migrateDatabase(),
39
41
  flushRedisConfig(),
@@ -0,0 +1,44 @@
1
+ // Yes, I know binary files are files in binary format, but this is a common term for executable files
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const pathExists = require('../../../util/path-exists')
6
+
7
+ /**
8
+ * @returns {import('listr2').ListrTask<import('../../../../typings/context').ListrContext>}
9
+ */
10
+ const makeBinariesExecutable = () => ({
11
+ task: async (ctx, task) => {
12
+ const directoriesWithBinFiles = [
13
+ path.join(ctx.config.baseConfig.magentoDir, 'bin'),
14
+ path.join(ctx.config.baseConfig.magentoDir, 'vendor', 'bin')
15
+ ]
16
+
17
+ for (const directory of directoriesWithBinFiles) {
18
+ if (!(await pathExists(directory))) {
19
+ continue
20
+ }
21
+ const files = await fs.promises.readdir(directory, {
22
+ withFileTypes: true
23
+ })
24
+
25
+ for (const file of files) {
26
+ if (!file.isFile()) {
27
+ continue
28
+ }
29
+
30
+ const filePath = path.join(directory, file.name)
31
+ const stats = await fs.promises.stat(filePath)
32
+
33
+ if (!filePath.startsWith(file.name) && stats.mode & 0o111) {
34
+ continue
35
+ }
36
+
37
+ task.output = `Making ${filePath} executable`
38
+ await fs.promises.chmod(filePath, 0o755)
39
+ }
40
+ }
41
+ }
42
+ })
43
+
44
+ module.exports = makeBinariesExecutable
@@ -1,5 +1,5 @@
1
+ const envPhpToJson = require('../../../util/env-php-json')
1
2
  const magentoTask = require('../../../util/magento-task')
2
- const runMagentoCommand = require('../../../util/run-magento')
3
3
 
4
4
  /**
5
5
  * @returns {import('listr2').ListrTask<import('../../../../typings/context').ListrContext>}
@@ -12,11 +12,14 @@ module.exports = () => ({
12
12
  magentoConfiguration: { mode }
13
13
  }
14
14
  } = ctx
15
- const { result } = await runMagentoCommand(ctx, 'deploy:mode:show', {
16
- throwNonZeroCode: false
17
- })
15
+ const envPhpData = await envPhpToJson(ctx)
16
+ if (!envPhpData) {
17
+ task.skip()
18
+ return
19
+ }
20
+ const { MAGE_MODE } = envPhpData
18
21
 
19
- if (result.includes(mode)) {
22
+ if (MAGE_MODE === mode) {
20
23
  task.skip()
21
24
  return
22
25
  }
@@ -10,14 +10,28 @@ const setMailConfig = () => ({
10
10
  'core_config_data',
11
11
  [
12
12
  {
13
- path: 'smtp/configuration_option/port',
13
+ path: 'system/smtp/transport',
14
+ value: 'smtp'
15
+ },
16
+ {
17
+ path: 'system/smtp/host',
18
+ value: isDockerDesktop
19
+ ? 'host.docker.internal'
20
+ : '127.0.0.1'
21
+ },
22
+ {
23
+ path: 'system/smtp/port',
14
24
  value: `${ports.maildevSMTP}`
15
25
  },
16
26
  {
17
27
  path: 'smtp/configuration_option/host',
18
28
  value: isDockerDesktop
19
29
  ? 'host.docker.internal'
20
- : 'localhost'
30
+ : '127.0.0.1'
31
+ },
32
+ {
33
+ path: 'smtp/configuration_option/port',
34
+ value: `${ports.maildevSMTP}`
21
35
  }
22
36
  ],
23
37
  { databaseConnection, task }
@@ -0,0 +1,236 @@
1
+ const os = require('os')
2
+ const fs = require('fs')
3
+ const path = require('path')
4
+ const {
5
+ runPHPContainerCommandTask,
6
+ runPHPContainerCommand
7
+ } = require('../../php/php-container')
8
+
9
+ const directoriesToCheck = [
10
+ 'var',
11
+ 'generated',
12
+ 'vendor',
13
+ 'pub/media',
14
+ 'pub/static',
15
+ 'app/etc'
16
+ ]
17
+
18
+ /**
19
+ * @param {string[]} directories
20
+ * @returns {import('listr2').ListrTask<import('../../../../typings/context').ListrContext>}
21
+ */
22
+ const makeNewFilesCreatedInFolderUseDirectoryGroup = (directories) => ({
23
+ title: 'Make new files created in folder use directory group',
24
+ task: async (ctx, task) => {
25
+ if (directories.length === 0) {
26
+ task.skip()
27
+ return
28
+ }
29
+
30
+ return task.newListr([
31
+ runPHPContainerCommandTask(
32
+ `find ${directories.join(' ')} -type d -exec chmod g+ws {} +`,
33
+ {
34
+ // should prevent command from failing the task
35
+ // if the folder does not exist
36
+ withCode: true,
37
+ useAutomaticUser: false
38
+ }
39
+ )
40
+ ])
41
+ }
42
+ })
43
+
44
+ /**
45
+ * @param {string[]} directories
46
+ * @returns {import('listr2').ListrTask<import('../../../../typings/context').ListrContext>}
47
+ */
48
+ const makeFilesWritableForGroupMembers = (directories) => ({
49
+ title: 'Make files writable for group members',
50
+ task: async (ctx, task) => {
51
+ if (directories.length === 0) {
52
+ task.skip()
53
+ return
54
+ }
55
+
56
+ return task.newListr([
57
+ runPHPContainerCommandTask(
58
+ `find ${directories.join(
59
+ ' '
60
+ )} -type -type f -exec chmod g+w {} +`,
61
+ {
62
+ // should prevent command from failing the task
63
+ // if the folder does not exist
64
+ withCode: true
65
+ }
66
+ )
67
+ ])
68
+ }
69
+ })
70
+
71
+ /**
72
+ * @param {string} folder
73
+ * @param {string | number} user
74
+ * @param {string | number} [group]
75
+ * @returns {import('listr2').ListrTask<import('../../../../typings/context').ListrContext>}
76
+ */
77
+ const makeFolderOwnedByUser = (folder, user, group) => ({
78
+ title: `Make folder ${folder} owned by ${user} user${
79
+ group ? ` and ${group} group` : ''
80
+ }`,
81
+ task: (ctx, task) =>
82
+ task.newListr([
83
+ runPHPContainerCommandTask(
84
+ `chown -R ${user}${group ? `:${group}` : ''} ${folder}`,
85
+ {
86
+ user: 'root:root'
87
+ }
88
+ )
89
+ ])
90
+ })
91
+
92
+ /**
93
+ * @param {import('../../../../typings/context').ListrContext} ctx
94
+ * @param {string[]} directories
95
+ * @return {Promise<{directory: string, exists: boolean, new_files_inherit_group: boolean, has_group_write_permissions: boolean, writable: boolean, permissions: string, directory_owner: {name: string, passwd: string, uid: number, gid: number, gecos: string, dir: string, shell: string} | boolean, directory_group: boolean, current_user: {name: string, passwd: string, uid: number, gid: number, gecos: string, dir: string, shell: string} | boolean, is_current_user_directory_owner: boolean}[]>}
96
+ */
97
+ const checkDirectoriesPermissions = async (ctx, directories) => {
98
+ const checkPHPPermissionsFileName = 'check-file-permissions.php'
99
+ const cacheDirFilePath = path.join(
100
+ ctx.config.baseConfig.cacheDir,
101
+ `${Date.now()}-${checkPHPPermissionsFileName}`
102
+ )
103
+ await fs.promises.copyFile(
104
+ path.join(__dirname, checkPHPPermissionsFileName),
105
+ cacheDirFilePath
106
+ )
107
+ const user =
108
+ ctx.platform === 'linux' ? `${os.userInfo().username}` : 'www-data'
109
+ const group =
110
+ ctx.platform === 'linux' ? `${os.userInfo().username}` : 'www-data'
111
+
112
+ const userWithGroup = `${user}:${group}`
113
+
114
+ const result = await runPHPContainerCommand(
115
+ ctx,
116
+ `php ${path.relative(
117
+ ctx.config.baseConfig.containerMagentoDir,
118
+ cacheDirFilePath
119
+ )} ${directories.join(',')}`,
120
+ {
121
+ user: userWithGroup,
122
+ cwd: ctx.config.baseConfig.containerMagentoDir
123
+ }
124
+ )
125
+
126
+ await fs.promises.unlink(cacheDirFilePath)
127
+
128
+ return JSON.parse(result)
129
+ }
130
+
131
+ /**
132
+ * @returns {import('listr2').ListrTask<import('../../../../typings/context').ListrContext>}
133
+ */
134
+ const setupMagentoFilePermissions = () => ({
135
+ title: 'Setting Magento file permissions',
136
+ task: async (ctx, task) => {
137
+ const parsedResult = await checkDirectoriesPermissions(
138
+ ctx,
139
+ directoriesToCheck.map((directory) =>
140
+ path.join(ctx.config.baseConfig.containerMagentoDir, directory)
141
+ )
142
+ )
143
+ const nonWritableDirectories = parsedResult.filter(
144
+ ({ exists, writable }) => exists && !writable
145
+ )
146
+
147
+ const tasks = []
148
+
149
+ if (nonWritableDirectories.length !== 0) {
150
+ const nonWritableDirectoriesPaths = nonWritableDirectories.map(
151
+ ({ directory }) => directory
152
+ )
153
+ const user =
154
+ ctx.platform === 'linux'
155
+ ? `${os.userInfo().username}`
156
+ : 'www-data'
157
+ const group =
158
+ ctx.platform === 'linux'
159
+ ? `${os.userInfo().username}`
160
+ : 'www-data'
161
+
162
+ tasks.push(
163
+ makeFilesWritableForGroupMembers(nonWritableDirectoriesPaths),
164
+ {
165
+ task: (subCtx, subTask) =>
166
+ subTask.newListr(
167
+ nonWritableDirectoriesPaths.map((directory) =>
168
+ makeFolderOwnedByUser(directory, user, group)
169
+ )
170
+ ),
171
+ options: {
172
+ concurrent: true
173
+ }
174
+ }
175
+ )
176
+ }
177
+
178
+ const directoriesThatNeedNewFilesInheritGroup = parsedResult
179
+ .filter(
180
+ ({ exists, new_files_inherit_group: nfig }) => exists && !nfig
181
+ )
182
+ .map(({ directory }) => directory)
183
+
184
+ if (directoriesThatNeedNewFilesInheritGroup.length > 0) {
185
+ tasks.push(
186
+ makeNewFilesCreatedInFolderUseDirectoryGroup(
187
+ directoriesThatNeedNewFilesInheritGroup
188
+ )
189
+ )
190
+ }
191
+
192
+ if (tasks.length === 0) {
193
+ task.skip()
194
+ return
195
+ }
196
+
197
+ return task.newListr(tasks)
198
+ }
199
+ })
200
+
201
+ /**
202
+ * @returns {import('listr2').ListrTask<import('../../../../typings/context').ListrContext>}
203
+ */
204
+ const setupComposerCachePermissions = () => ({
205
+ title: 'Setting Composer Cache permissions',
206
+ task: async (ctx, task) => {
207
+ const parsedResult = await checkDirectoriesPermissions(ctx, [
208
+ '/composer/home'
209
+ ])
210
+
211
+ if (parsedResult.every((dir) => dir.writable)) {
212
+ task.skip()
213
+ return
214
+ }
215
+
216
+ const userAndGroup =
217
+ ctx.platform === 'linux' ? `${os.userInfo().username}` : 'www-data'
218
+
219
+ return task.newListr([
220
+ makeFolderOwnedByUser('/composer/home', userAndGroup, userAndGroup),
221
+ runPHPContainerCommandTask('chmod g+ws /composer/home/cache', {
222
+ user: ctx.platform === 'linux' ? 'root:root' : ''
223
+ }),
224
+ runPHPContainerCommandTask('chmod g+w /composer/home/cache', {
225
+ user: ctx.platform === 'linux' ? 'root:root' : ''
226
+ })
227
+ ])
228
+ }
229
+ })
230
+
231
+ module.exports = {
232
+ makeFilesWritableForGroupMembers,
233
+ makeNewFilesCreatedInFolderUseDirectoryGroup,
234
+ setupMagentoFilePermissions,
235
+ setupComposerCachePermissions
236
+ }
@@ -1,11 +1,15 @@
1
1
  /* eslint-disable no-use-before-define */
2
+ const os = require('os')
2
3
  const { deepmerge } = require('../../util/deepmerge')
3
4
  const { containerApi } = require('../docker/containers')
4
5
 
5
6
  /**
6
- * @type {typeof import('./php-container')['runPHPContainerCommand']}
7
+ * @param {Parameters<typeof import('./php-container')['runPHPContainerCommand']>[0]} ctx
8
+ * @param {Parameters<typeof import('./php-container')['runPHPContainerCommand']>[1]} command
9
+ * @param {Parameters<typeof import('./php-container')['runPHPContainerCommand']>[2] & { useAutomaticUser?: boolean}} [options]
7
10
  */
8
11
  const runPHPContainerCommand = async (ctx, command, options = {}) => {
12
+ const { useAutomaticUser = true } = options
9
13
  const { php } = ctx.config.docker.getContainers(ctx.ports)
10
14
 
11
15
  const containers = await containerApi.ls({
@@ -23,11 +27,21 @@ const runPHPContainerCommand = async (ctx, command, options = {}) => {
23
27
  php,
24
28
  {
25
29
  detach: false,
26
- rm: true
27
- },
28
- {
30
+ rm: true,
29
31
  command
30
- }
32
+ },
33
+ useAutomaticUser && ctx.platform === 'linux'
34
+ ? {
35
+ user: `${os.userInfo().username}:${
36
+ os.userInfo().username
37
+ }`
38
+ }
39
+ : {},
40
+ options.user
41
+ ? {
42
+ user: options.user
43
+ }
44
+ : {}
31
45
  ),
32
46
  options
33
47
  )
@@ -68,7 +82,8 @@ const execPHPContainerCommand = async (ctx, command, options = {}) => {
68
82
  {
69
83
  container: php.name,
70
84
  ...deepmerge(php, options.env ? { env: options.env } : {}),
71
- command
85
+ command,
86
+ ...(options.user ? { user: options.user } : {})
72
87
  },
73
88
  options
74
89
  )