@akash-chowdhury-24/deployhub 2.0.26 → 2.0.28
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/README.md +38 -20
- package/package.json +1 -1
- package/src/adapters/index.js +1 -0
- package/src/artifact/engine.js +2 -2
- package/src/commands/doctor.js +8 -4
- package/src/commands/init.js +5 -0
- package/src/core/config.js +7 -0
- package/src/deployment/deployment-env.js +115 -41
- package/src/deployment/init-prompts.js +52 -7
- package/src/deployment/providers/azure-vm.js +6 -4
- package/src/deployment/providers/ec2.js +8 -3
- package/src/deployment/providers/gcp-vm.js +4 -4
- package/src/deployment/providers/ssh.js +2 -2
- package/src/detectors/backend.detector.js +107 -2
- package/src/detectors/index.js +3 -2
- package/src/utils/credential-inventory.js +292 -0
- package/src/utils/docker-image.js +6 -3
- package/src/utils/dockerfile-expose.js +1 -0
- package/src/utils/dockerfile.js +181 -28
- package/src/utils/github-actions.js +194 -17
|
@@ -18,12 +18,27 @@ import {
|
|
|
18
18
|
} from '../deployment/deployment-env.js';
|
|
19
19
|
import {
|
|
20
20
|
getEnvMethod,
|
|
21
|
+
getEnvTrigger,
|
|
21
22
|
getEnabledEnvironmentNames,
|
|
22
23
|
isEnvEnabled,
|
|
23
24
|
} from '../core/environments.js';
|
|
24
25
|
|
|
26
|
+
/** Default PHP for CI when config does not set `phpVersion` / `backend.phpVersion`. */
|
|
27
|
+
export const DEFAULT_PHP_VERSION = '8.4';
|
|
28
|
+
|
|
25
29
|
/** @typedef {'aws'|'azure'|'gcp'|'gdrive'|'dropbox'|'local'|'ftp'|'ssh'} ProviderEnvKey */
|
|
26
30
|
|
|
31
|
+
/** Storage-only providers — never treat deployment method ids as storage. */
|
|
32
|
+
const STORAGE_PROVIDER_IDS = new Set([
|
|
33
|
+
'aws',
|
|
34
|
+
'azure',
|
|
35
|
+
'gcp',
|
|
36
|
+
'gdrive',
|
|
37
|
+
'dropbox',
|
|
38
|
+
'local',
|
|
39
|
+
'ftp',
|
|
40
|
+
]);
|
|
41
|
+
|
|
27
42
|
const PROVIDER_ENV_MAP = {
|
|
28
43
|
aws: [
|
|
29
44
|
'AWS_ACCESS_KEY_ID',
|
|
@@ -41,7 +56,7 @@ const PROVIDER_ENV_MAP = {
|
|
|
41
56
|
],
|
|
42
57
|
dropbox: ['DROPBOX_ACCESS_TOKEN'],
|
|
43
58
|
local: [],
|
|
44
|
-
ftp: ['FTP_HOST', 'FTP_USER', 'FTP_PASSWORD'],
|
|
59
|
+
ftp: ['FTP_HOST', 'FTP_USER', 'FTP_PASSWORD', 'FTP_PORT', 'FTP_PATH'],
|
|
45
60
|
ssh: DEPLOYMENT_ENV_KEYS.ssh,
|
|
46
61
|
docker: DEPLOYMENT_ENV_KEYS.docker,
|
|
47
62
|
ec2: DEPLOYMENT_ENV_KEYS.ec2,
|
|
@@ -50,6 +65,27 @@ const PROVIDER_ENV_MAP = {
|
|
|
50
65
|
kubernetes: DEPLOYMENT_ENV_KEYS.kubernetes,
|
|
51
66
|
};
|
|
52
67
|
|
|
68
|
+
/** @type {Record<string, string>} */
|
|
69
|
+
const STORAGE_SECRET_NOTES = {
|
|
70
|
+
AWS_ACCESS_KEY_ID: 'AWS S3 storage credential (not EC2 instance lookup)',
|
|
71
|
+
AWS_SECRET_ACCESS_KEY: 'AWS S3 storage credential (not EC2 instance lookup)',
|
|
72
|
+
AWS_BUCKET: 'AWS S3 storage bucket',
|
|
73
|
+
AWS_REGION: 'AWS S3 storage region (not EC2_LOOKUP_AWS_REGION)',
|
|
74
|
+
AZURE_CONNECTION_STRING: 'Azure Blob storage credential',
|
|
75
|
+
AZURE_CONTAINER: 'Azure Blob storage container',
|
|
76
|
+
GCP_PROJECT_ID: 'GCP Storage credential (not GCP VM instance lookup)',
|
|
77
|
+
GCP_KEY_FILE: 'GCP Storage credential (not GCP_VM_LOOKUP_KEY_FILE)',
|
|
78
|
+
GCP_BUCKET: 'GCP Storage bucket',
|
|
79
|
+
GDRIVE_CLIENT_ID: 'Google Drive storage credential',
|
|
80
|
+
GDRIVE_CLIENT_SECRET: 'Google Drive storage credential',
|
|
81
|
+
GDRIVE_REFRESH_TOKEN: 'Google Drive storage credential',
|
|
82
|
+
GDRIVE_FOLDER_ID: 'Google Drive folder',
|
|
83
|
+
DROPBOX_ACCESS_TOKEN: 'Dropbox storage credential',
|
|
84
|
+
FTP_HOST: 'FTP storage host',
|
|
85
|
+
FTP_USER: 'FTP storage user',
|
|
86
|
+
FTP_PASSWORD: 'FTP storage password',
|
|
87
|
+
};
|
|
88
|
+
|
|
53
89
|
const PROVIDER_LABELS = {
|
|
54
90
|
aws: 'AWS S3',
|
|
55
91
|
azure: 'Azure Blob',
|
|
@@ -67,6 +103,7 @@ const PROVIDER_LABELS = {
|
|
|
67
103
|
|
|
68
104
|
const ENV_VAR_DEFAULTS = {
|
|
69
105
|
AWS_REGION: 'us-east-1',
|
|
106
|
+
EC2_LOOKUP_AWS_REGION: 'us-east-1',
|
|
70
107
|
FTP_PORT: '21',
|
|
71
108
|
FTP_PATH: '/uploads',
|
|
72
109
|
SSH_DEPLOY_PATH: '/var/www/app',
|
|
@@ -234,6 +271,40 @@ function getGithubGitConfigStep() {
|
|
|
234
271
|
fi`;
|
|
235
272
|
}
|
|
236
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Resolve PHP version for CI setup-php.
|
|
276
|
+
* Order: `backend.phpVersion` → top-level `phpVersion` → {@link DEFAULT_PHP_VERSION} (`8.4`).
|
|
277
|
+
* Set either config key in deployhub.config.json to pin a different runtime
|
|
278
|
+
* (e.g. `"phpVersion": "8.3"` or `"backend": { "phpVersion": "8.3" }`).
|
|
279
|
+
*
|
|
280
|
+
* @param {import('../core/config.js').DeployHubConfig} [config]
|
|
281
|
+
* @returns {string}
|
|
282
|
+
*/
|
|
283
|
+
export function resolvePhpVersion(config) {
|
|
284
|
+
const fromBackend = config?.backend?.phpVersion;
|
|
285
|
+
if (typeof fromBackend === 'string' && fromBackend.trim()) {
|
|
286
|
+
return fromBackend.trim();
|
|
287
|
+
}
|
|
288
|
+
const fromRoot = config?.phpVersion;
|
|
289
|
+
if (typeof fromRoot === 'string' && fromRoot.trim()) {
|
|
290
|
+
return fromRoot.trim();
|
|
291
|
+
}
|
|
292
|
+
return DEFAULT_PHP_VERSION;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* @param {import('../core/config.js').DeployHubConfig} [config]
|
|
297
|
+
* @returns {boolean}
|
|
298
|
+
*/
|
|
299
|
+
function isPhpProject(config) {
|
|
300
|
+
if (!config) return false;
|
|
301
|
+
const projectType = config.projectType || 'frontend';
|
|
302
|
+
if (projectType !== 'backend' && projectType !== 'both') return false;
|
|
303
|
+
const framework = config.backend?.framework || config.framework || '';
|
|
304
|
+
const language = config.backend?.language || config.language || '';
|
|
305
|
+
return language === 'php' || ['laravel', 'symfony', 'php'].includes(framework);
|
|
306
|
+
}
|
|
307
|
+
|
|
237
308
|
/**
|
|
238
309
|
* @param {import('../core/config.js').DeployHubConfig} [config]
|
|
239
310
|
* @returns {string[]}
|
|
@@ -249,16 +320,24 @@ function getBackendSetupSteps(config) {
|
|
|
249
320
|
|
|
250
321
|
if (projectType === 'backend' || projectType === 'both') {
|
|
251
322
|
const framework = config.backend?.framework || config.framework || 'express';
|
|
252
|
-
if (['express', 'nestjs', 'fastify', 'koa', 'nextjs'].includes(framework)) {
|
|
323
|
+
if (['express', 'nestjs', 'fastify', 'koa', 'nextjs', 'node'].includes(framework)) {
|
|
253
324
|
steps.push(` - uses: actions/setup-node@v4
|
|
254
325
|
with:
|
|
255
326
|
node-version: '20'`);
|
|
256
327
|
}
|
|
257
|
-
if (['fastapi', 'django', 'flask'].includes(framework)) {
|
|
328
|
+
if (['fastapi', 'django', 'flask', 'python'].includes(framework)) {
|
|
258
329
|
steps.push(` - uses: actions/setup-python@v5
|
|
259
330
|
with:
|
|
260
331
|
python-version: '3.11'`);
|
|
261
332
|
}
|
|
333
|
+
if (isPhpProject(config)) {
|
|
334
|
+
const phpVersion = resolvePhpVersion(config);
|
|
335
|
+
steps.push(` - name: Setup PHP
|
|
336
|
+
uses: shivammathur/setup-php@v2
|
|
337
|
+
with:
|
|
338
|
+
php-version: '${phpVersion}'
|
|
339
|
+
tools: composer`);
|
|
340
|
+
}
|
|
262
341
|
if (['spring', 'java'].includes(framework)) {
|
|
263
342
|
steps.push(` - uses: actions/setup-java@v4
|
|
264
343
|
with:
|
|
@@ -275,7 +354,7 @@ function getBackendSetupSteps(config) {
|
|
|
275
354
|
with:
|
|
276
355
|
dotnet-version: '8.0.x'`);
|
|
277
356
|
}
|
|
278
|
-
if (framework === 'rails') {
|
|
357
|
+
if (framework === 'rails' || framework === 'ruby') {
|
|
279
358
|
steps.push(` - uses: ruby/setup-ruby@v1
|
|
280
359
|
with:
|
|
281
360
|
ruby-version: '3.2'
|
|
@@ -329,18 +408,69 @@ function resolveKubeconfigWorkflowSecretName(deployEnvironments, environments, c
|
|
|
329
408
|
: 'KUBECONFIG';
|
|
330
409
|
}
|
|
331
410
|
|
|
411
|
+
/**
|
|
412
|
+
* GitHub Actions `if:` so kubectl/kubeconfig only run when this job needs k8s.
|
|
413
|
+
* - Push: only when at least one push-triggered env uses kubernetes (skip when push
|
|
414
|
+
* only deploys EC2/SSH/etc. and k8s is manual-only).
|
|
415
|
+
* - workflow_dispatch / rollback: when selected env is k8s, `all`, or blank
|
|
416
|
+
* (blank = deploy/rollback all enabled envs).
|
|
417
|
+
* Returns null when every enabled env is kubernetes (steps always needed).
|
|
418
|
+
*
|
|
419
|
+
* @param {string[]} deployEnvironments
|
|
420
|
+
* @param {Record<string, { type?: string, method?: string, trigger?: string }>} environments
|
|
421
|
+
* @param {'deploy'|'rollback'} [kind]
|
|
422
|
+
* @returns {string|null}
|
|
423
|
+
*/
|
|
424
|
+
function getKubernetesSetupIfExpression(
|
|
425
|
+
deployEnvironments,
|
|
426
|
+
environments,
|
|
427
|
+
kind = 'deploy'
|
|
428
|
+
) {
|
|
429
|
+
const k8sNames = deployEnvironments.filter(
|
|
430
|
+
(n) => getEnvMethod(environments[n]) === 'kubernetes'
|
|
431
|
+
);
|
|
432
|
+
if (k8sNames.length === 0) return null;
|
|
433
|
+
|
|
434
|
+
const nonK8s = deployEnvironments.filter(
|
|
435
|
+
(n) => getEnvMethod(environments[n]) !== 'kubernetes'
|
|
436
|
+
);
|
|
437
|
+
// All enabled envs are kubernetes — setup is always required.
|
|
438
|
+
if (nonK8s.length === 0) return null;
|
|
439
|
+
|
|
440
|
+
const dispatchNeedK8s = [
|
|
441
|
+
...k8sNames.map((n) => `inputs.environment == '${n}'`),
|
|
442
|
+
`inputs.environment == 'all'`,
|
|
443
|
+
`inputs.environment == ''`,
|
|
444
|
+
].join(' || ');
|
|
445
|
+
|
|
446
|
+
if (kind === 'rollback') {
|
|
447
|
+
return `github.event_name == 'workflow_dispatch' && (${dispatchNeedK8s})`;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const pushK8s = k8sNames.filter(
|
|
451
|
+
(n) => getEnvTrigger(environments[n]) === 'push'
|
|
452
|
+
);
|
|
453
|
+
if (pushK8s.length > 0) {
|
|
454
|
+
return `github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && (${dispatchNeedK8s}))`;
|
|
455
|
+
}
|
|
456
|
+
// Manual-only kubernetes: never configure kubeconfig on plain push.
|
|
457
|
+
return `github.event_name == 'workflow_dispatch' && (${dispatchNeedK8s})`;
|
|
458
|
+
}
|
|
459
|
+
|
|
332
460
|
/**
|
|
333
461
|
* @param {string} [kubeconfigSecretName]
|
|
462
|
+
* @param {string|null} [ifExpression]
|
|
334
463
|
* @returns {string}
|
|
335
464
|
*/
|
|
336
|
-
function getKubernetesSetupSteps(kubeconfigSecretName = 'KUBECONFIG') {
|
|
465
|
+
function getKubernetesSetupSteps(kubeconfigSecretName = 'KUBECONFIG', ifExpression = null) {
|
|
337
466
|
// One kubeconfig file per job — see resolveKubeconfigWorkflowSecretName LIMITATION.
|
|
338
|
-
|
|
467
|
+
const ifLine = ifExpression ? `\n if: ${ifExpression}` : '';
|
|
468
|
+
return ` - name: Setup kubectl${ifLine}
|
|
339
469
|
uses: azure/setup-kubectl@v4
|
|
340
470
|
with:
|
|
341
471
|
version: '${KUBECTL_VERSION}'
|
|
342
472
|
|
|
343
|
-
- name: Configure kubeconfig
|
|
473
|
+
- name: Configure kubeconfig${ifLine}
|
|
344
474
|
env:
|
|
345
475
|
KUBECONFIG_SECRET: \${{ secrets.${kubeconfigSecretName} }}
|
|
346
476
|
run: |
|
|
@@ -416,6 +546,7 @@ export function buildWorkflowEnvEntries(
|
|
|
416
546
|
]);
|
|
417
547
|
|
|
418
548
|
for (const provider of storageProviders) {
|
|
549
|
+
if (!STORAGE_PROVIDER_IDS.has(provider)) continue;
|
|
419
550
|
const keys = PROVIDER_ENV_MAP[provider] || [];
|
|
420
551
|
for (const key of keys) {
|
|
421
552
|
upsertWorkflowEnvLine(envVars, key, `\${{ secrets.${key} }}`);
|
|
@@ -546,8 +677,13 @@ export function generateWorkflowYaml(
|
|
|
546
677
|
environments,
|
|
547
678
|
config
|
|
548
679
|
);
|
|
680
|
+
const k8sIf = getKubernetesSetupIfExpression(
|
|
681
|
+
allDeployNames,
|
|
682
|
+
environments,
|
|
683
|
+
'deploy'
|
|
684
|
+
);
|
|
549
685
|
const kubernetesSteps = hasKubernetesDeploy(allDeployNames, environments)
|
|
550
|
-
? `${getKubernetesSetupSteps(kubeconfigSecret)}\n`
|
|
686
|
+
? `${getKubernetesSetupSteps(kubeconfigSecret, k8sIf)}\n`
|
|
551
687
|
: '';
|
|
552
688
|
|
|
553
689
|
const projectType = config?.projectType || 'frontend';
|
|
@@ -654,6 +790,13 @@ export function generateRollbackWorkflowYaml(
|
|
|
654
790
|
const envBlock = formatWorkflowEnvBlock(envVars);
|
|
655
791
|
|
|
656
792
|
const installSpec = getCliInstallSpec(cliSource);
|
|
793
|
+
// Rollback does not generally install project deps; PHP is the exception
|
|
794
|
+
// (composer install) so we only inject setup-php here — not other backends.
|
|
795
|
+
const phpSetupSteps = isPhpProject(config)
|
|
796
|
+
? [...new Set(getBackendSetupSteps(config))]
|
|
797
|
+
.filter((s) => s.includes('setup-php'))
|
|
798
|
+
.join('\n')
|
|
799
|
+
: '';
|
|
657
800
|
const githubGitConfigStep = isGithubCliSource(cliSource)
|
|
658
801
|
? `${getGithubGitConfigStep()}\n`
|
|
659
802
|
: '';
|
|
@@ -662,14 +805,25 @@ export function generateRollbackWorkflowYaml(
|
|
|
662
805
|
environments,
|
|
663
806
|
config
|
|
664
807
|
);
|
|
808
|
+
const k8sIf = getKubernetesSetupIfExpression(
|
|
809
|
+
allDeployNames,
|
|
810
|
+
environments,
|
|
811
|
+
'rollback'
|
|
812
|
+
);
|
|
665
813
|
const kubernetesSteps = hasKubernetesDeploy(allDeployNames, environments)
|
|
666
|
-
? `${getKubernetesSetupSteps(kubeconfigSecret)}\n`
|
|
814
|
+
? `${getKubernetesSetupSteps(kubeconfigSecret, k8sIf)}\n`
|
|
667
815
|
: '';
|
|
668
816
|
|
|
669
817
|
const rollbackCmd = getCliRollbackCommand();
|
|
670
818
|
const envChoiceOptions = formatEnvironmentChoiceOptions(environments);
|
|
671
819
|
const hasEnvs = envNames.length > 0;
|
|
672
820
|
|
|
821
|
+
const phpInstallStep = isPhpProject(config)
|
|
822
|
+
? ` - name: Install project dependencies
|
|
823
|
+
run: ${getInstallDepsCommand(config)}
|
|
824
|
+
`
|
|
825
|
+
: '';
|
|
826
|
+
|
|
673
827
|
const environmentInput = hasEnvs
|
|
674
828
|
? ` environment:
|
|
675
829
|
description: 'Environment to roll back (or "all")'
|
|
@@ -713,10 +867,10 @@ ${environmentInput}jobs:
|
|
|
713
867
|
runs-on: ubuntu-latest
|
|
714
868
|
steps:
|
|
715
869
|
- uses: actions/checkout@v4
|
|
716
|
-
- uses: actions/setup-node@v4
|
|
870
|
+
${phpSetupSteps ? `${phpSetupSteps}\n` : ''} - uses: actions/setup-node@v4
|
|
717
871
|
with:
|
|
718
872
|
node-version: '20'
|
|
719
|
-
${kubernetesSteps}${githubGitConfigStep} - name: Install DeployHub CLI
|
|
873
|
+
${kubernetesSteps}${githubGitConfigStep}${phpInstallStep} - name: Install DeployHub CLI
|
|
720
874
|
run: npm install ${installSpec} --no-save
|
|
721
875
|
- name: Rollback
|
|
722
876
|
env:
|
|
@@ -737,10 +891,10 @@ function getInstallDepsCommand(config) {
|
|
|
737
891
|
config.backend?.framework || config.framework || 'express';
|
|
738
892
|
const language = config.backend?.language || config.language;
|
|
739
893
|
|
|
740
|
-
if (language === 'python' || ['fastapi', 'django', 'flask'].includes(framework)) {
|
|
894
|
+
if (language === 'python' || ['fastapi', 'django', 'flask', 'python'].includes(framework)) {
|
|
741
895
|
return 'pip install -r requirements.txt';
|
|
742
896
|
}
|
|
743
|
-
if (['laravel', 'symfony'].includes(framework)) {
|
|
897
|
+
if (language === 'php' || ['laravel', 'symfony', 'php'].includes(framework)) {
|
|
744
898
|
return 'composer install --no-interaction';
|
|
745
899
|
}
|
|
746
900
|
if (framework === 'spring' || framework === 'java') {
|
|
@@ -752,7 +906,7 @@ function getInstallDepsCommand(config) {
|
|
|
752
906
|
if (framework === 'dotnet') {
|
|
753
907
|
return 'dotnet restore';
|
|
754
908
|
}
|
|
755
|
-
if (framework === 'rails') {
|
|
909
|
+
if (framework === 'rails' || framework === 'ruby') {
|
|
756
910
|
return 'bundle install';
|
|
757
911
|
}
|
|
758
912
|
return 'npm install';
|
|
@@ -1202,9 +1356,14 @@ export function getGithubSecretsChecklist(
|
|
|
1202
1356
|
}
|
|
1203
1357
|
|
|
1204
1358
|
for (const provider of storageProviders) {
|
|
1359
|
+
if (!STORAGE_PROVIDER_IDS.has(provider)) continue;
|
|
1205
1360
|
const keys = PROVIDER_ENV_MAP[provider] || [];
|
|
1206
1361
|
for (const key of keys) {
|
|
1207
|
-
byKey.set(key, {
|
|
1362
|
+
byKey.set(key, {
|
|
1363
|
+
key,
|
|
1364
|
+
required: true,
|
|
1365
|
+
note: STORAGE_SECRET_NOTES[key] || 'storage credential',
|
|
1366
|
+
});
|
|
1208
1367
|
}
|
|
1209
1368
|
}
|
|
1210
1369
|
|
|
@@ -1280,18 +1439,36 @@ export function generateEnvExampleContent(
|
|
|
1280
1439
|
};
|
|
1281
1440
|
|
|
1282
1441
|
for (const provider of storageProviders) {
|
|
1442
|
+
// Defensive: ignore accidental deploy-method ids in storageProviders
|
|
1443
|
+
// (PROVIDER_ENV_MAP also maps ec2/ssh/… for workflow wiring).
|
|
1444
|
+
if (!STORAGE_PROVIDER_IDS.has(provider)) continue;
|
|
1283
1445
|
const keys = PROVIDER_ENV_MAP[provider] || [];
|
|
1284
1446
|
if (keys.length > 0) {
|
|
1285
1447
|
addSection(PROVIDER_LABELS[provider] || provider, keys);
|
|
1286
1448
|
}
|
|
1287
1449
|
}
|
|
1288
1450
|
|
|
1451
|
+
/** @type {Set<string>} */
|
|
1452
|
+
const seenDeployEnvs = new Set();
|
|
1453
|
+
const cfg = {
|
|
1454
|
+
...(config || {}),
|
|
1455
|
+
environments: environments || config?.environments || {},
|
|
1456
|
+
};
|
|
1457
|
+
|
|
1289
1458
|
for (const envName of deployEnvironments) {
|
|
1459
|
+
if (seenDeployEnvs.has(envName)) continue;
|
|
1460
|
+
seenDeployEnvs.add(envName);
|
|
1461
|
+
|
|
1290
1462
|
const env = environments[envName];
|
|
1291
1463
|
const method = getEnvMethod(env);
|
|
1292
1464
|
if (!method) continue;
|
|
1293
1465
|
|
|
1294
|
-
const deploySection = generateDeploymentEnvSection(
|
|
1466
|
+
const deploySection = generateDeploymentEnvSection(
|
|
1467
|
+
method,
|
|
1468
|
+
cfg,
|
|
1469
|
+
environments,
|
|
1470
|
+
{ envName }
|
|
1471
|
+
);
|
|
1295
1472
|
if (deploySection) {
|
|
1296
1473
|
sections.push(`${deploySection}\n`);
|
|
1297
1474
|
}
|
|
@@ -1323,4 +1500,4 @@ export function generateEnvExampleContent(
|
|
|
1323
1500
|
return `${sections.join('\n')}\n`;
|
|
1324
1501
|
}
|
|
1325
1502
|
|
|
1326
|
-
export { PROVIDER_ENV_MAP };
|
|
1503
|
+
export { PROVIDER_ENV_MAP, STORAGE_PROVIDER_IDS };
|