diff --git a/.circleci/config.yml b/.circleci/config.yml index 311dcfe306a..a26ff94d19b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -817,6 +817,14 @@ jobs: environment: TEST_SUITE: src/__tests__/env.test.ts CLI_REGION: ap-southeast-1 + feature-flags-amplify_e2e_tests: + working_directory: ~/repo + docker: *ref_0 + resource_class: large + steps: *ref_1 + environment: + TEST_SUITE: src/__tests__/feature-flags.test.ts + CLI_REGION: ap-southeast-2 function_1-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -824,7 +832,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/function_1.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: us-east-2 function_2-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -832,7 +840,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/function_2.test.ts - CLI_REGION: us-east-2 + CLI_REGION: us-west-2 init-special-case-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -840,7 +848,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/init-special-case.test.ts - CLI_REGION: us-west-2 + CLI_REGION: eu-west-2 layer-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -848,7 +856,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/layer.test.ts - CLI_REGION: eu-west-2 + CLI_REGION: eu-central-1 schema-auth-1-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -856,7 +864,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-auth-1.test.ts - CLI_REGION: eu-central-1 + CLI_REGION: ap-northeast-1 schema-auth-2-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -864,7 +872,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-auth-2.test.ts - CLI_REGION: ap-northeast-1 + CLI_REGION: ap-southeast-1 schema-auth-3-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -872,7 +880,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-auth-3.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 schema-auth-4-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -880,7 +888,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-auth-4.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: us-east-2 schema-auth-5-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -888,7 +896,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-auth-5.test.ts - CLI_REGION: us-east-2 + CLI_REGION: us-west-2 schema-auth-6-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -896,7 +904,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-auth-6.test.ts - CLI_REGION: us-west-2 + CLI_REGION: eu-west-2 schema-auth-7-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -904,7 +912,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-auth-7.test.ts - CLI_REGION: eu-west-2 + CLI_REGION: eu-central-1 schema-auth-8-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -912,7 +920,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-auth-8.test.ts - CLI_REGION: eu-central-1 + CLI_REGION: ap-northeast-1 schema-connection-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -920,7 +928,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-connection.test.ts - CLI_REGION: ap-northeast-1 + CLI_REGION: ap-southeast-1 schema-data-access-patterns-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -928,7 +936,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-data-access-patterns.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 schema-function-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -936,7 +944,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-function.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: us-east-2 schema-key-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -944,7 +952,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-key.test.ts - CLI_REGION: us-east-2 + CLI_REGION: us-west-2 schema-model-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -952,7 +960,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-model.test.ts - CLI_REGION: us-west-2 + CLI_REGION: eu-west-2 schema-predictions-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -960,7 +968,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-predictions.test.ts - CLI_REGION: eu-west-2 + CLI_REGION: eu-central-1 schema-searchable-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -968,7 +976,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-searchable.test.ts - CLI_REGION: eu-central-1 + CLI_REGION: ap-northeast-1 schema-versioned-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -976,7 +984,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/schema-versioned.test.ts - CLI_REGION: ap-northeast-1 + CLI_REGION: ap-southeast-1 tags-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -984,7 +992,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/tags.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 plugin-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -992,7 +1000,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/plugin.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: us-east-2 datastore-modegen-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -1000,7 +1008,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/datastore-modegen.test.ts - CLI_REGION: us-east-2 + CLI_REGION: us-west-2 interactions-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -1008,7 +1016,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/interactions.test.ts - CLI_REGION: us-west-2 + CLI_REGION: eu-west-2 hosting-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -1016,7 +1024,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/hosting.test.ts - CLI_REGION: eu-west-2 + CLI_REGION: eu-central-1 init-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -1024,7 +1032,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/init.test.ts - CLI_REGION: eu-central-1 + CLI_REGION: ap-northeast-1 amplify-app-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -1032,7 +1040,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/amplify-app.test.ts - CLI_REGION: ap-northeast-1 + CLI_REGION: ap-southeast-1 analytics-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -1040,7 +1048,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/analytics.test.ts - CLI_REGION: ap-southeast-1 + CLI_REGION: ap-southeast-2 hostingPROD-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -1048,7 +1056,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/hostingPROD.test.ts - CLI_REGION: ap-southeast-2 + CLI_REGION: us-east-2 predictions-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -1056,7 +1064,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/predictions.test.ts - CLI_REGION: us-east-2 + CLI_REGION: us-west-2 delete-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -1064,7 +1072,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/delete.test.ts - CLI_REGION: us-west-2 + CLI_REGION: eu-west-2 storage-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -1072,7 +1080,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/storage.test.ts - CLI_REGION: eu-west-2 + CLI_REGION: eu-central-1 migration-api-key-migration-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -1080,7 +1088,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/migration/api.key.migration.test.ts - CLI_REGION: eu-central-1 + CLI_REGION: ap-northeast-1 migration-api-connection-migration-amplify_e2e_tests: working_directory: ~/repo docker: *ref_0 @@ -1088,7 +1096,7 @@ jobs: steps: *ref_1 environment: TEST_SUITE: src/__tests__/migration/api.connection.migration.test.ts - CLI_REGION: ap-northeast-1 + CLI_REGION: ap-southeast-1 workflows: version: 2 nightly_console_integration_tests: @@ -1181,6 +1189,9 @@ workflows: - amplify_console_integration_tests - amplify_migration_tests_latest - amplify_migration_tests_v4 + - schema-function-amplify_e2e_tests + - plugin-amplify_e2e_tests + - hostingPROD-amplify_e2e_tests - schema-key-amplify_e2e_tests - datastore-modegen-amplify_e2e_tests - predictions-amplify_e2e_tests @@ -1199,9 +1210,6 @@ workflows: - schema-data-access-patterns-amplify_e2e_tests - tags-amplify_e2e_tests - analytics-amplify_e2e_tests - - schema-function-amplify_e2e_tests - - plugin-amplify_e2e_tests - - hostingPROD-amplify_e2e_tests filters: branches: only: @@ -1216,6 +1224,33 @@ workflows: - graphqlschemae2e requires: - publish_to_local_registry + - function_1-amplify_e2e_tests: + filters: *ref_2 + requires: + - publish_to_local_registry + - schema-auth-4-amplify_e2e_tests: + filters: *ref_2 + requires: + - publish_to_local_registry + - schema-function-amplify_e2e_tests: + filters: *ref_2 + requires: + - publish_to_local_registry + - amplify-configure-amplify_e2e_tests + - plugin-amplify_e2e_tests: + filters: *ref_2 + requires: + - publish_to_local_registry + - function_1-amplify_e2e_tests + - hostingPROD-amplify_e2e_tests: + filters: *ref_2 + requires: + - publish_to_local_registry + - schema-auth-4-amplify_e2e_tests + - api_1-amplify_e2e_tests: + filters: *ref_2 + requires: + - publish_to_local_registry - function_2-amplify_e2e_tests: filters: *ref_2 requires: @@ -1228,7 +1263,7 @@ workflows: filters: *ref_2 requires: - publish_to_local_registry - - amplify-configure-amplify_e2e_tests + - api_1-amplify_e2e_tests - datastore-modegen-amplify_e2e_tests: filters: *ref_2 requires: @@ -1239,7 +1274,7 @@ workflows: requires: - publish_to_local_registry - schema-auth-5-amplify_e2e_tests - - api_1-amplify_e2e_tests: + - api_2-amplify_e2e_tests: filters: *ref_2 requires: - publish_to_local_registry @@ -1255,7 +1290,7 @@ workflows: filters: *ref_2 requires: - publish_to_local_registry - - api_1-amplify_e2e_tests + - api_2-amplify_e2e_tests - interactions-amplify_e2e_tests: filters: *ref_2 requires: @@ -1266,7 +1301,7 @@ workflows: requires: - publish_to_local_registry - schema-auth-6-amplify_e2e_tests - - api_2-amplify_e2e_tests: + - auth_1-amplify_e2e_tests: filters: *ref_2 requires: - publish_to_local_registry @@ -1282,7 +1317,7 @@ workflows: filters: *ref_2 requires: - publish_to_local_registry - - api_2-amplify_e2e_tests + - auth_1-amplify_e2e_tests - hosting-amplify_e2e_tests: filters: *ref_2 requires: @@ -1293,7 +1328,7 @@ workflows: requires: - publish_to_local_registry - schema-auth-7-amplify_e2e_tests - - auth_1-amplify_e2e_tests: + - auth_2-amplify_e2e_tests: filters: *ref_2 requires: - publish_to_local_registry @@ -1309,7 +1344,7 @@ workflows: filters: *ref_2 requires: - publish_to_local_registry - - auth_1-amplify_e2e_tests + - auth_2-amplify_e2e_tests - init-amplify_e2e_tests: filters: *ref_2 requires: @@ -1320,7 +1355,7 @@ workflows: requires: - publish_to_local_registry - schema-auth-8-amplify_e2e_tests - - auth_2-amplify_e2e_tests: + - env-amplify_e2e_tests: filters: *ref_2 requires: - publish_to_local_registry @@ -1336,7 +1371,7 @@ workflows: filters: *ref_2 requires: - publish_to_local_registry - - auth_2-amplify_e2e_tests + - env-amplify_e2e_tests - amplify-app-amplify_e2e_tests: filters: *ref_2 requires: @@ -1347,7 +1382,7 @@ workflows: requires: - publish_to_local_registry - schema-connection-amplify_e2e_tests - - env-amplify_e2e_tests: + - feature-flags-amplify_e2e_tests: filters: *ref_2 requires: - publish_to_local_registry @@ -1363,31 +1398,9 @@ workflows: filters: *ref_2 requires: - publish_to_local_registry - - env-amplify_e2e_tests + - feature-flags-amplify_e2e_tests - analytics-amplify_e2e_tests: filters: *ref_2 requires: - publish_to_local_registry - schema-auth-3-amplify_e2e_tests - - function_1-amplify_e2e_tests: - filters: *ref_2 - requires: - - publish_to_local_registry - - schema-auth-4-amplify_e2e_tests: - filters: *ref_2 - requires: - - publish_to_local_registry - - schema-function-amplify_e2e_tests: - filters: *ref_2 - requires: - - publish_to_local_registry - - plugin-amplify_e2e_tests: - filters: *ref_2 - requires: - - publish_to_local_registry - - function_1-amplify_e2e_tests - - hostingPROD-amplify_e2e_tests: - filters: *ref_2 - requires: - - publish_to_local_registry - - schema-auth-4-amplify_e2e_tests diff --git a/packages/amplify-cli-core/src/__tests__/featureFlags.test.ts b/packages/amplify-cli-core/src/__tests__/featureFlags.test.ts index 7ebd55917bb..72a0446c321 100644 --- a/packages/amplify-cli-core/src/__tests__/featureFlags.test.ts +++ b/packages/amplify-cli-core/src/__tests__/featureFlags.test.ts @@ -3,11 +3,23 @@ import * as os from 'os'; import * as path from 'path'; import * as rimraf from 'rimraf'; import { v4 as uuid } from 'uuid'; -import { EnvVarFormatError, FeatureFlags, CLIEnvironmentProvider, CLIContextEnvironmentProvider, JSONUtilities } from '..'; -import { amplifyConfigFileName } from '../constants'; +import { + EnvVarFormatError, + FeatureFlags, + CLIEnvironmentProvider, + CLIContextEnvironmentProvider, + JSONUtilities, + pathManager, + pathManager as realPathManager, + FeatureFlagRegistration, +} from '..'; import { FeatureFlagEnvironmentProvider } from '../feature-flags/featureFlagEnvironmentProvider'; import { FeatureFlagFileProvider } from '../feature-flags/featureFlagFileProvider'; +// These constants are not exported, hence the redefinition for tests +const amplifyDirName = 'amplify'; +const amplifyConfigFileName = 'cli.json'; + describe('feature flags', () => { describe('featureflag provider tests', () => { const realProcessEnv: NodeJS.ProcessEnv = { ...process.env }; @@ -29,11 +41,16 @@ describe('feature flags', () => { try { await fs.mkdirs(tempProjectDir); - const projectConfigFileName = path.join(tempProjectDir, amplifyConfigFileName); + process.chdir(tempProjectDir); + + const projectConfigFileName = pathManager.getCLIJSONFilePath(tempProjectDir); + + await fs.mkdirs(path.dirname(projectConfigFileName)); await fs.copyFile(templateConfigFileName, projectConfigFileName); - await FeatureFlags.initialize(({ getCurrentEnvName: () => 'dev' } as unknown) as CLIEnvironmentProvider, tempProjectDir); + await FeatureFlags.initialize(({ getCurrentEnvName: () => 'dev' } as unknown) as CLIEnvironmentProvider, undefined, getTestFlags()); + await FeatureFlags.ensureDefaultFeatureFlags(true); const updatedConfig = JSONUtilities.readJson(projectConfigFileName); @@ -54,11 +71,15 @@ describe('feature flags', () => { try { await fs.mkdirs(tempProjectDir); + process.chdir(tempProjectDir); + const projectConfigFileName = path.join(tempProjectDir, amplifyConfigFileName); + await fs.mkdirs(path.dirname(projectConfigFileName)); + await fs.copyFile(templateConfigFileName, projectConfigFileName); - await FeatureFlags.initialize(({ getCurrentEnvName: () => 'dev' } as unknown) as CLIEnvironmentProvider, tempProjectDir); + await FeatureFlags.initialize(({ getCurrentEnvName: () => 'dev' } as unknown) as CLIEnvironmentProvider, undefined, getTestFlags()); await FeatureFlags.ensureDefaultFeatureFlags(true); const originalConfig = (await fs.readFile(templateConfigFileName)).toString(); @@ -77,9 +98,13 @@ describe('feature flags', () => { try { await fs.mkdirs(tempProjectDir); - const projectConfigFileName = path.join(tempProjectDir, amplifyConfigFileName); + process.chdir(tempProjectDir); + + const projectConfigFileName = path.join(tempProjectDir, amplifyDirName, amplifyConfigFileName); - await FeatureFlags.initialize(({ getCurrentEnvName: () => 'dev' } as unknown) as CLIEnvironmentProvider, tempProjectDir); + await fs.mkdirs(path.dirname(projectConfigFileName)); + + await FeatureFlags.initialize(({ getCurrentEnvName: () => 'dev' } as unknown) as CLIEnvironmentProvider, undefined, getTestFlags()); await FeatureFlags.ensureDefaultFeatureFlags(true); const createdConfig = (await fs.readFile(projectConfigFileName)).toString(); @@ -92,42 +117,10 @@ describe('feature flags', () => { test('missing environmentProvider argument', async () => { await expect(async () => { - await FeatureFlags.initialize((undefined as unknown) as CLIContextEnvironmentProvider, (undefined as unknown) as string); + await FeatureFlags.initialize((undefined as unknown) as CLIContextEnvironmentProvider, undefined, getTestFlags()); }).rejects.toThrowError(`'environmentProvider' argument is required`); }); - test('missing projectPath argument for initialize', async () => { - const context: any = { - getEnvInfo: (_: boolean): any => { - return { - envName: 'dev', - }; - }, - }; - - const envProvider: CLIEnvironmentProvider = new CLIContextEnvironmentProvider(context); - - await expect(async () => { - await FeatureFlags.initialize(envProvider, (undefined as unknown) as string); - }).rejects.toThrowError(`'projectPath' argument is required`); - }); - - test('projectPath does not exist for initialize', async () => { - const context: any = { - getEnvInfo: (_: boolean): any => { - return { - envName: 'dev', - }; - }, - }; - - const envProvider: CLIEnvironmentProvider = new CLIContextEnvironmentProvider(context); - - await expect(async () => { - await FeatureFlags.initialize(envProvider, '/foo/bar'); - }).rejects.toThrowError(`Project path: '/foo/bar' does not exist.`); - }); - test('initialize feature flag provider successfully', async () => { const context: any = { getEnvInfo: (_: boolean): any => { @@ -143,7 +136,7 @@ describe('feature flags', () => { // Set current cwd to projectPath for .env to work correctly process.chdir(projectPath); - await FeatureFlags.initialize(envProvider, projectPath); + await FeatureFlags.initialize(envProvider, undefined, getTestFlags()); const transformerVersion = FeatureFlags.getNumber('graphQLTransformer.transformerVersion'); const isDefaultQueryEnabled = FeatureFlags.getBoolean('keyTransformer.defaultQuery'); @@ -152,6 +145,27 @@ describe('feature flags', () => { expect(isDefaultQueryEnabled).toBe(true); }); + test('initialize feature flag provider successfully with no files and return new project defaults', async () => { + const osTempDir = await fs.realpath(os.tmpdir()); + const tempProjectDir = path.join(osTempDir, `amp-${uuid()}`); + + try { + await fs.mkdirs(tempProjectDir); + + process.chdir(tempProjectDir); + + await FeatureFlags.initialize(({ getCurrentEnvName: () => 'dev' } as unknown) as CLIEnvironmentProvider, true, getTestFlags()); + + const transformerVersion = FeatureFlags.getNumber('graphQLTransformer.transformerVersion'); + const isDefaultQueryEnabled = FeatureFlags.getBoolean('keyTransformer.defaultQuery'); + + expect(transformerVersion).toBe(5); + expect(isDefaultQueryEnabled).toBe(true); + } finally { + rimraf.sync(tempProjectDir); + } + }); + test('initialize feature flag provider fail with json error', async () => { const context: any = { getEnvInfo: (_: boolean): any => { @@ -168,7 +182,7 @@ describe('feature flags', () => { process.chdir(projectPath); await expect(async () => { - await FeatureFlags.initialize(envProvider, projectPath); + await FeatureFlags.initialize(envProvider, undefined, getTestFlags()); }).rejects.toThrowError( `Found '}' where a key name was expected (check your syntax or use quotes if the key name includes {}[],: or whitespace) at line 1,0 >>> Not a json {\n ...`, ); @@ -189,7 +203,7 @@ describe('feature flags', () => { // Set current cwd to projectPath for .env to work correctly process.chdir(projectPath); - await FeatureFlags.initialize(envProvider, projectPath); + await FeatureFlags.initialize(envProvider, undefined, getTestFlags()); const effectiveFlags = FeatureFlags.getEffectiveFlags(); expect(effectiveFlags).toMatchObject({ @@ -217,7 +231,7 @@ describe('feature flags', () => { // Set current cwd to projectPath for .env to work correctly process.chdir(projectPath); - await FeatureFlags.initialize(envProvider, projectPath); + await FeatureFlags.initialize(envProvider, undefined, getTestFlags()); const effectiveFlags = FeatureFlags.getEffectiveFlags(); expect(effectiveFlags).toMatchObject({ @@ -245,7 +259,7 @@ describe('feature flags', () => { // Set current cwd to projectPath for .env to work correctly process.chdir(projectPath); - await FeatureFlags.initialize(envProvider, projectPath); + await FeatureFlags.initialize(envProvider, undefined, getTestFlags()); const effectiveFlags = FeatureFlags.getEffectiveFlags(); expect(effectiveFlags).toMatchObject({ @@ -273,7 +287,7 @@ describe('feature flags', () => { // Set current cwd to projectPath for .env to work correctly process.chdir(projectPath); - await FeatureFlags.initialize(envProvider, projectPath); + await FeatureFlags.initialize(envProvider, undefined, getTestFlags()); const effectiveFlags = FeatureFlags.getEffectiveFlags(); expect(effectiveFlags).toMatchObject({ @@ -302,7 +316,7 @@ describe('feature flags', () => { process.chdir(projectPath); await expect(async () => { - await FeatureFlags.initialize(envProvider, projectPath); + await FeatureFlags.initialize(envProvider, undefined, getTestFlags()); }).rejects.toThrowError(`Section 'foo' is not registered in feature provider`); }); @@ -322,7 +336,7 @@ describe('feature flags', () => { process.chdir(projectPath); await expect(async () => { - await FeatureFlags.initialize(envProvider, projectPath); + await FeatureFlags.initialize(envProvider, undefined, getTestFlags()); }).rejects.toThrowError(`Flag 'bar' within 'graphqltransformer' is not registered in feature provider`); }); @@ -342,7 +356,7 @@ describe('feature flags', () => { process.chdir(projectPath); await expect(async () => { - await FeatureFlags.initialize(envProvider, projectPath); + await FeatureFlags.initialize(envProvider, undefined, getTestFlags()); }).rejects.toThrowError(`Invalid boolean value: 'invalid' for 'defaultquery' in section 'keytransformer'`); }); @@ -362,9 +376,30 @@ describe('feature flags', () => { process.chdir(projectPath); await expect(async () => { - await FeatureFlags.initialize(envProvider, projectPath); + await FeatureFlags.initialize(envProvider, undefined, getTestFlags()); }).rejects.toThrowError(`Invalid number value: 'invalid' for 'transformerversion' in section 'graphqltransformer'`); }); + + const getTestFlags = (): Record => { + return { + graphQLTransformer: [ + { + name: 'transformerVersion', + type: 'number', + defaultValueForExistingProjects: 4, + defaultValueForNewProjects: 5, + }, + ], + keyTransformer: [ + { + name: 'defaultQuery', + type: 'boolean', + defaultValueForExistingProjects: false, + defaultValueForNewProjects: true, + }, + ], + }; + }; }); describe('environment provider tests', () => { @@ -491,26 +526,6 @@ describe('feature flags', () => { }).rejects.toThrowError(`'projectPath' option is missing`); }); - test('projectPath does not exist', async () => { - const context: any = { - getEnvInfo: (_: boolean): any => { - return { - envName: 'dev', - }; - }, - }; - - const envProvider: CLIEnvironmentProvider = new CLIContextEnvironmentProvider(context); - - await expect(async () => { - const provider = new FeatureFlagFileProvider(envProvider, { - projectPath: '/foo/bar', - }); - - await provider.load(); - }).rejects.toThrowError(`Project path: '/foo/bar' does not exist.`); - }); - test('reads features when both files exists', async () => { const context: any = { getEnvInfo: (_: boolean): any => { diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/project-with-features/amplify.json b/packages/amplify-cli-core/src/__tests__/testFiles/project-with-features/cli.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/project-with-features/amplify.json rename to packages/amplify-cli-core/src/__tests__/testFiles/project-with-features/cli.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/project-with-no-features/amplify.json b/packages/amplify-cli-core/src/__tests__/testFiles/project-with-no-features/cli.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/project-with-no-features/amplify.json rename to packages/amplify-cli-core/src/__tests__/testFiles/project-with-no-features/cli.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify.dev.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify/cli.dev.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify.dev.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify/cli.dev.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify/cli.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-both-files/amplify/cli.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify/cli.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify/cli.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify.prod.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify/cli.prod.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify.prod.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-1/amplify/cli.prod.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify.dev.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.dev.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify.dev.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.dev.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify.prod.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.prod.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify.prod.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-2/amplify/cli.prod.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify.dev.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.dev.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify.dev.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.dev.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify.prod.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.prod.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify.prod.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-3/amplify/cli.prod.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify.dev.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.dev.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify.dev.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.dev.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify.prod.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.prod.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify.prod.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-4/amplify/cli.prod.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-json-error/amplify.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-json-error/amplify/cli.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-json-error/amplify.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize-json-error/amplify/cli.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify.dev.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.dev.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify.dev.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.dev.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify.prod.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.prod.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify.prod.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-initialize/amplify/cli.prod.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-env/amplify.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-env/amplify/cli.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-env/amplify.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-env/amplify/cli.json diff --git a/packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-project/amplify.dev.json b/packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-project/amplify/cli.dev.json similarity index 100% rename from packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-project/amplify.dev.json rename to packages/amplify-cli-core/src/__tests__/testFiles/testProject-no-project/amplify/cli.dev.json diff --git a/packages/amplify-cli-core/src/constants.ts b/packages/amplify-cli-core/src/constants.ts deleted file mode 100644 index e21068ed154..00000000000 --- a/packages/amplify-cli-core/src/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const amplifyConfigFileName = 'amplify.json'; -export const amplifyConfigEnvFileNameTemplate = (env: string) => `amplify.${env}.json`; diff --git a/packages/amplify-cli-core/src/feature-flags/featureFlagFileProvider.ts b/packages/amplify-cli-core/src/feature-flags/featureFlagFileProvider.ts index 7593d99848c..9b21983a98a 100644 --- a/packages/amplify-cli-core/src/feature-flags/featureFlagFileProvider.ts +++ b/packages/amplify-cli-core/src/feature-flags/featureFlagFileProvider.ts @@ -1,10 +1,8 @@ -import * as fs from 'fs-extra'; -import * as _ from 'lodash'; -import * as path from 'path'; +import _ from 'lodash'; import { FeatureFlagConfiguration, FeatureFlagsEntry } from '.'; -import { CLIEnvironmentProvider, JSONUtilities } from '..'; +import { CLIEnvironmentProvider } from '..'; import { FeatureFlagValueProvider } from './featureFlagValueProvider'; -import { amplifyConfigFileName, amplifyConfigEnvFileNameTemplate } from '../constants'; +import { stateManager } from '../state-manager'; export type FeatureFlagFileProviderOptions = { projectPath?: string; @@ -18,28 +16,23 @@ export class FeatureFlagFileProvider implements FeatureFlagValueProvider { throw new Error(`'projectPath' option is missing`); } - if (!(await fs.pathExists(this.options.projectPath))) { - throw new Error(`Project path: '${this.options.projectPath}' does not exist.`); - } - const result: FeatureFlagConfiguration = { project: {}, environments: {}, }; - // Read project level file exists - const projectConfigFileName = path.join(this.options.projectPath, amplifyConfigFileName); - const projectFeatures = await this.loadConfig(projectConfigFileName); + // Read project level file if exists + const projectFeatures = await this.loadConfig(this.options.projectPath); if (projectFeatures) { result.project = projectFeatures; } - // Read environment level file if we've a valid environment and file exists + // Read environment level file if we have a valid environment and the file exists const envName = this.environmentProvider.getCurrentEnvName(); + if (envName !== '') { - const envConfigFileName = path.join(this.options.projectPath, amplifyConfigEnvFileNameTemplate(envName)); - const envFeatures = await this.loadConfig(envConfigFileName); + const envFeatures = await this.loadConfig(this.options.projectPath, envName); if (envFeatures) { result.environments[envName] = envFeatures; @@ -49,8 +42,8 @@ export class FeatureFlagFileProvider implements FeatureFlagValueProvider { return result; }; - private loadConfig = async (fileName: string): Promise => { - const configFileData = JSONUtilities.readJson<{ features: FeatureFlagsEntry }>(fileName, { + private loadConfig = async (projectPath: string, env?: string): Promise => { + const configFileData = <{ features: FeatureFlagsEntry }>stateManager.getCLIJSON(projectPath, env, { throwIfNotExist: false, }); diff --git a/packages/amplify-cli-core/src/feature-flags/featureFlags.ts b/packages/amplify-cli-core/src/feature-flags/featureFlags.ts index 01506693985..419daf754f3 100644 --- a/packages/amplify-cli-core/src/feature-flags/featureFlags.ts +++ b/packages/amplify-cli-core/src/feature-flags/featureFlags.ts @@ -1,25 +1,33 @@ import Ajv, { AdditionalPropertiesParams } from 'ajv'; import * as fs from 'fs-extra'; -import _ from 'lodash'; import * as path from 'path'; +import _ from 'lodash'; import { JSONSchema7, JSONSchema7Definition } from 'json-schema'; -import { CLIEnvironmentProvider, JSONUtilities, JSONValidationError } from '..'; +import { CLIEnvironmentProvider, JSONValidationError } from '..'; import { FeatureFlagConfiguration, FeatureFlagRegistration, FeatureFlagsEntry, FeatureFlagType } from '.'; -import { amplifyConfigFileName, amplifyConfigEnvFileNameTemplate } from '../constants'; import { FeatureFlagFileProvider } from './featureFlagFileProvider'; import { FeatureFlagEnvironmentProvider } from './featureFlagEnvironmentProvider'; +import { pathManager } from '../state-manager/pathManager'; +import { stateManager } from '../state-manager'; +import { JSONUtilities } from '../jsonUtilities'; export class FeatureFlags { private static instance: FeatureFlags; private readonly registrations: Map = new Map(); + private fileValueProvider!: FeatureFlagFileProvider; + private envValueProvider!: FeatureFlagEnvironmentProvider; private effectiveFlags: Readonly = {}; private newProjectDefaults: Readonly = {}; private existingProjectDefaults: Readonly = {}; - private constructor(private environmentProvider: CLIEnvironmentProvider, private projectPath: string) {} + private constructor(private environmentProvider: CLIEnvironmentProvider, private projectPath: string, private useNewDefaults: boolean) {} - public static initialize = async (environmentProvider: CLIEnvironmentProvider, projectPath: string): Promise => { + public static initialize = async ( + environmentProvider: CLIEnvironmentProvider, + useNewDefaults: boolean = false, + additionalFlags?: Record, + ): Promise => { // If we are not running by tests, guard against multiple calls to initialize invocations if (typeof jest === 'undefined' && FeatureFlags.instance) { throw new Error('FeatureFlags can only be initialzied once'); @@ -29,66 +37,33 @@ export class FeatureFlags { throw new Error(`'environmentProvider' argument is required`); } - if (!projectPath) { - throw new Error(`'projectPath' argument is required`); - } + // fallback to process.cwd() if no projectPath cannot be determined + const projectPath = pathManager.findProjectRoot() ?? process.cwd(); - if (!(await fs.pathExists(projectPath))) { - throw new Error(`Project path: '${projectPath}' does not exist.`); - } + await FeatureFlags.removeOriginalConfigFile(projectPath); - const instance = new FeatureFlags(environmentProvider, projectPath); + const instance = new FeatureFlags(environmentProvider, projectPath, useNewDefaults); // Populate registrations here, later this can be coming from plugins' manifests instance.registerFlags(); + if (additionalFlags) { + for (const sectionName of Object.keys(additionalFlags)) { + const flags = additionalFlags[sectionName]; + + instance.registerFlag(sectionName, flags); + } + } + // Create the providers - const fileValueProvider = new FeatureFlagFileProvider(environmentProvider, { + instance.fileValueProvider = new FeatureFlagFileProvider(environmentProvider, { projectPath, }); - const envValueProvider = new FeatureFlagEnvironmentProvider({ + instance.envValueProvider = new FeatureFlagEnvironmentProvider({ projectPath, }); - // Load the flags from all providers - const fileFlags = await fileValueProvider.load(); - const envFlags = instance.transformEnvFlags(await envValueProvider.load()); - - // Validate the loaded flags from all providers - instance.validateFlags([ - { - name: 'File', - flags: fileFlags, - }, - { - name: 'Environment', - flags: envFlags, - }, - ]); - - // Build default value objects from registrations - instance.buildDefaultValues(); - - // - // To make access easy, we are unfolding the values from the providers, dot notation based key value pairs. - // Example: 'graphqltransformer.transformerversion': 5 - // - // top to bottom the following order is used: - // - file project level - // - file env level - // - environment project level - // - environment env level - // - // The sections, properties and values are verified against the dynamically built up json schema - // - - instance.effectiveFlags = _.merge( - {}, - fileFlags.project, - fileFlags.environments[environmentProvider.getCurrentEnvName()] ?? {}, - envFlags.project, - envFlags.environments[environmentProvider.getCurrentEnvName()] ?? {}, - ); + await instance.loadValues(); FeatureFlags.instance = instance; }; @@ -103,9 +78,7 @@ export class FeatureFlags { public static ensureDefaultFeatureFlags = async (newProject: boolean): Promise => { FeatureFlags.ensureInitialized(); - const configFileName = path.join(FeatureFlags.instance.projectPath, amplifyConfigFileName); - - let config = JSONUtilities.readJson<{ [key: string]: any }>(configFileName, { + let config = stateManager.getCLIJSON(FeatureFlags.instance.projectPath, undefined, { throwIfNotExist: false, preserveComments: true, }); @@ -117,9 +90,7 @@ export class FeatureFlags { features: newProject ? FeatureFlags.getNewProjectDefaults() : FeatureFlags.getExistingProjectDefaults(), }; - JSONUtilities.writeJson(configFileName, config, { - keepComments: true, - }); + stateManager.setCLIJSON(FeatureFlags.instance.projectPath, config); } }; @@ -167,13 +138,13 @@ export class FeatureFlags { } if (removeProjectConfiguration) { - const configFileName = path.join(FeatureFlags.instance.projectPath, amplifyConfigFileName); + const configFileName = pathManager.getCLIJSONFilePath(FeatureFlags.instance.projectPath); await fs.remove(configFileName); } for (let envName of envNames) { - const configFileName = path.join(FeatureFlags.instance.projectPath, amplifyConfigEnvFileNameTemplate(envName)); + const configFileName = pathManager.getCLIJSONFilePath(FeatureFlags.instance.projectPath, envName); await fs.remove(configFileName); } @@ -183,6 +154,36 @@ export class FeatureFlags { return FeatureFlags.instance !== undefined; }; + public static reloadValues = async (): Promise => { + FeatureFlags.ensureInitialized(); + + await FeatureFlags.instance.loadValues(); + }; + + private static removeOriginalConfigFile = async (projectPath: string): Promise => { + // Try to read in original `amplify.json` in project root and if it is a valid JSON + // and contains a top level `features` property, remove the file. + const originalConfigFileName = 'amplify.json'; + + try { + if (!projectPath) { + return; + } + + const originalConfigFilePath = path.join(projectPath, originalConfigFileName); + + const configFileData = JSONUtilities.readJson<{ features: FeatureFlagsEntry }>(originalConfigFilePath, { + throwIfNotExist: false, + }); + + if (configFileData?.features !== undefined) { + fs.removeSync(originalConfigFilePath); + } + } catch { + // Intentionally left blank + } + }; + private static ensureInitialized = (): void => { if (!FeatureFlags.instance) { throw new Error('FeatureFlags is not initialized'); @@ -225,7 +226,11 @@ export class FeatureFlags { // If there is no value, return the registered defaults for existing projects if (!value) { - value = (flagRegistrationEntry.defaultValueForExistingProjects as unknown); + if (this.useNewDefaults) { + value = (flagRegistrationEntry.defaultValueForNewProjects as unknown); + } else { + value = (flagRegistrationEntry.defaultValueForExistingProjects as unknown); + } } return value; @@ -400,6 +405,48 @@ export class FeatureFlags { return features; }; + private loadValues = async () => { + // Load the flags from all providers + const fileFlags = await this.fileValueProvider.load(); + const envFlags = this.transformEnvFlags(await this.envValueProvider.load()); + + // Validate the loaded flags from all providers + this.validateFlags([ + { + name: 'File', + flags: fileFlags, + }, + { + name: 'Environment', + flags: envFlags, + }, + ]); + + // Build default value objects from registrations + this.buildDefaultValues(); + + // + // To make access easy, we are unfolding the values from the providers, dot notation based key value pairs. + // Example: 'graphqltransformer.transformerversion': 5 + // + // top to bottom the following order is used: + // - file project level + // - file env level + // - environment project level + // - environment env level + // + // The sections, properties and values are verified against the dynamically built up json schema + // + + this.effectiveFlags = _.merge( + this.useNewDefaults ? this.newProjectDefaults : this.existingProjectDefaults, + fileFlags.project, + fileFlags.environments[this.environmentProvider.getCurrentEnvName()] ?? {}, + envFlags.project, + envFlags.environments[this.environmentProvider.getCurrentEnvName()] ?? {}, + ); + }; + private registerFlag = (section: string, flags: FeatureFlagRegistration[]): void => { if (!section) { throw new Error(`'section' argument is required`); @@ -431,22 +478,22 @@ export class FeatureFlags { // DEVS: Register feature flags here private registerFlags = (): void => { - this.registerFlag('graphQLTransformer', [ - { - name: 'transformerVersion', - type: 'number', - defaultValueForExistingProjects: 4, - defaultValueForNewProjects: 5, - }, - ]); - - this.registerFlag('keyTransformer', [ - { - name: 'defaultQuery', - type: 'boolean', - defaultValueForExistingProjects: false, - defaultValueForNewProjects: true, - }, - ]); + // Examples: + // this.registerFlag('graphQLTransformer', [ + // { + // name: 'transformerVersion', + // type: 'number', + // defaultValueForExistingProjects: 4, + // defaultValueForNewProjects: 5, + // }, + // ]); + // this.registerFlag('keyTransformer', [ + // { + // name: 'defaultQuery', + // type: 'boolean', + // defaultValueForExistingProjects: false, + // defaultValueForNewProjects: true, + // }, + // ]); }; } diff --git a/packages/amplify-cli-core/src/state-manager/pathManager.ts b/packages/amplify-cli-core/src/state-manager/pathManager.ts index 4647b906f94..013ce50dc65 100644 --- a/packages/amplify-cli-core/src/state-manager/pathManager.ts +++ b/packages/amplify-cli-core/src/state-manager/pathManager.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import * as fs from 'fs-extra'; import { homedir } from 'os'; -const PathConstants = { +export const PathConstants = { // in home directory DotAWSDir: '.aws', AWSCredentials: 'credentials', @@ -28,6 +28,10 @@ const PathConstants = { LocalAWSInfoFileName: 'local-aws-info.json', TeamProviderInfoFileName: 'team-provider-info.json', BackendConfigFileName: 'backend-config.json', + + CLIJSONFileName: 'cli.json', + CLIJSONFileNameGlob: 'cli*.json', + CLIJsonWithEnvironmentFileName: (env: string) => `cli.${env}.json`, }; export class PathManager { @@ -100,6 +104,12 @@ export class PathManager { getAWSConfigFilePath = (): string => path.normalize(path.join(this.getDotAWSDirPath(), PathConstants.AWSConfig)); + getCLIJSONFilePath = (projectPath: string, env?: string): string => { + const fileName = env === undefined ? PathConstants.CLIJSONFileName : PathConstants.CLIJsonWithEnvironmentFileName(env); + + return this.constructPath(projectPath, [PathConstants.AmplifyDirName, fileName]); + }; + private constructPath = (projectPath?: string, segments: string[] = []): string => { if (!projectPath) { projectPath = this.findProjectRoot(); diff --git a/packages/amplify-cli-core/src/state-manager/stateManager.ts b/packages/amplify-cli-core/src/state-manager/stateManager.ts index 64a813665bd..976182f453c 100644 --- a/packages/amplify-cli-core/src/state-manager/stateManager.ts +++ b/packages/amplify-cli-core/src/state-manager/stateManager.ts @@ -6,6 +6,7 @@ import { Tag, ReadValidateTags } from '../tags'; export type GetOptions = { throwIfNotExist?: boolean; + preserveComments?: boolean; default?: T; }; @@ -76,6 +77,8 @@ export class StateManager { return this.getData<$TSAny>(filePath, mergedOptions); }; + projectConfigExists = (projectPath?: string): boolean => fs.existsSync(pathManager.getProjectConfigFilePath(projectPath)); + getProjectConfig = (projectPath?: string, options?: GetOptions<$TSAny>): $TSAny => { const filePath = pathManager.getProjectConfigFilePath(projectPath); const mergedOptions = { @@ -145,6 +148,26 @@ export class StateManager { JSONUtilities.writeJson(filePath, meta); }; + cliJSONFileExists = (projectPath: string, env?: string): boolean => fs.existsSync(pathManager.getCLIJSONFilePath(projectPath, env)); + + getCLIJSON = (projectPath: string, env?: string, options?: GetOptions<$TSAny>): $TSAny => { + const filePath = pathManager.getCLIJSONFilePath(projectPath, env); + const mergedOptions = { + throwIfNotExist: true, + ...options, + }; + + return this.getData<$TSAny>(filePath, mergedOptions); + }; + + setCLIJSON = (projectPath: string, cliJSON: any, env?: string): void => { + const filePath = pathManager.getCLIJSONFilePath(projectPath, env); + + JSONUtilities.writeJson(filePath, cliJSON, { + keepComments: true, + }); + }; + private getData = (filePath: string, options?: GetOptions): T | undefined => { const data = JSONUtilities.readJson(filePath, { throwIfNotExist: options?.throwIfNotExist ?? true, diff --git a/packages/amplify-cli/src/attach-backend.ts b/packages/amplify-cli/src/attach-backend.ts index b42838e6404..07b53800365 100644 --- a/packages/amplify-cli/src/attach-backend.ts +++ b/packages/amplify-cli/src/attach-backend.ts @@ -1,6 +1,6 @@ import * as fs from 'fs-extra'; import * as path from 'path'; -import { pathManager, stateManager, $TSContext } from 'amplify-cli-core'; +import { pathManager, stateManager, $TSContext, FeatureFlags } from 'amplify-cli-core'; import { queryProvider } from './attach-backend-steps/a10-queryProvider'; import { analyzeProject } from './attach-backend-steps/a20-analyzeProject'; import { initFrontend } from './attach-backend-steps/a30-initFrontend'; @@ -18,6 +18,13 @@ export async function attachBackend(context: $TSContext, inputParams) { try { await queryProvider(context); + + // After pulling down backend reload feature flag values as new values can affect the remaining + // operations of the pull command. + if (FeatureFlags.isInitialized()) { + await FeatureFlags.reloadValues(); + } + await analyzeProject(context); await initFrontend(context); await generateFiles(context); diff --git a/packages/amplify-cli/src/index.ts b/packages/amplify-cli/src/index.ts index 325c48791d8..2218db1400f 100644 --- a/packages/amplify-cli/src/index.ts +++ b/packages/amplify-cli/src/index.ts @@ -1,6 +1,6 @@ import * as fs from 'fs-extra'; import * as path from 'path'; -import { CLIContextEnvironmentProvider, FeatureFlags, JSONUtilities, pathManager, $TSAny } from 'amplify-cli-core'; +import { CLIContextEnvironmentProvider, FeatureFlags, JSONUtilities, $TSAny, pathManager, stateManager } from 'amplify-cli-core'; import { Input } from './domain/input'; import { getPluginPlatform, scan } from './plugin-manager'; import { getCommandLineInput, verifyInput } from './input-manager'; @@ -65,28 +65,9 @@ export async function run() { getEnvInfo: context.amplify.getEnvInfo, }); - const getProjectPath = (): string => { - try { - let { projectPath } = context.amplify.getEnvInfo(); - - // Check if the returned path exists, because it is possible that - // local-env-info.json is checked in and contains an invalid path - // https://github.com/aws-amplify/amplify-cli/issues/4950 - if (projectPath && !fs.pathExistsSync(projectPath)) { - projectPath = ''; - } - - return projectPath; - } catch { - return ''; - } - }; - - const projectPath = getProjectPath(); - - if (projectPath) { - await FeatureFlags.initialize(contextEnvironmentProvider, projectPath); - } + const projectPath = pathManager.findProjectRoot() ?? process.cwd(); + const useNewDefaults = !stateManager.projectConfigExists(projectPath); + await FeatureFlags.initialize(contextEnvironmentProvider, useNewDefaults); await attachUsageData(context); errorHandler = boundErrorHandler.bind(context); diff --git a/packages/amplify-cli/src/init-steps/s9-onSuccess.ts b/packages/amplify-cli/src/init-steps/s9-onSuccess.ts index e76602da68a..10ad819d469 100644 --- a/packages/amplify-cli/src/init-steps/s9-onSuccess.ts +++ b/packages/amplify-cli/src/init-steps/s9-onSuccess.ts @@ -37,7 +37,7 @@ export async function onSuccess(context: $TSContext) { }); if (!FeatureFlags.isInitialized()) { - await FeatureFlags.initialize(contextEnvironmentProvider, projectPath); + await FeatureFlags.initialize(contextEnvironmentProvider, true); } await FeatureFlags.ensureDefaultFeatureFlags(true); diff --git a/packages/amplify-console-hosting/package.json b/packages/amplify-console-hosting/package.json index 63c58785a1e..f25a08b315e 100644 --- a/packages/amplify-console-hosting/package.json +++ b/packages/amplify-console-hosting/package.json @@ -6,12 +6,14 @@ "author": "Amazon Web Services", "license": "Apache-2.0", "dependencies": { + "amplify-cli-core": "1.3.1", "archiver": "^3.1.1", "aws-sdk": "^2.608.0", "chalk": "^3.0.0", "cli-table3": "^0.5.1", "execa": "^4.0.0", "fs-extra": "^8.1.0", + "glob": "^7.1.6", "inquirer": "^7.0.3", "open": "^7.0.0", "ora": "^4.0.3", diff --git a/packages/amplify-console-hosting/utils/build-utils.js b/packages/amplify-console-hosting/utils/build-utils.js index 651bdfefe25..c9019058a68 100644 --- a/packages/amplify-console-hosting/utils/build-utils.js +++ b/packages/amplify-console-hosting/utils/build-utils.js @@ -1,11 +1,12 @@ const chalk = require('chalk'); const { command: executeCommand } = require('execa'); const fs = require('fs-extra'); +const path = require('path'); const archiver = require('archiver'); const DIR_NOT_FOUND_ERROR_MESSAGE = 'Please ensure your build artifacts path exists.'; -function zipFile(sourceDir, destFilePath) { +function zipFile(sourceDir, destFilePath, extraFiles) { return new Promise((resolve, reject) => { if (!fs.pathExistsSync(sourceDir)) { reject(DIR_NOT_FOUND_ERROR_MESSAGE); @@ -23,6 +24,15 @@ function zipFile(sourceDir, destFilePath) { }); archive.pipe(output); archive.directory(sourceDir, false); + + if (extraFiles && extraFiles.length && extraFiles.length > 0) { + for (const filePath of extraFiles) { + const fileName = path.basename(filePath); + + archive.file(filePath, { name: fileName }); + } + } + archive.finalize(); }); } diff --git a/packages/amplify-console-hosting/utils/config-utils.js b/packages/amplify-console-hosting/utils/config-utils.js index 1c81bf3ca98..cfa565df77b 100644 --- a/packages/amplify-console-hosting/utils/config-utils.js +++ b/packages/amplify-console-hosting/utils/config-utils.js @@ -1,20 +1,22 @@ -const constants = require('../constants/plugin-constants'); -const pathManager = require('../utils/path-manager'); const fs = require('fs-extra'); -const utils = require('../utils/amplify-context-utils'); const path = require('path'); +const { pathManager, PathConstants } = require('amplify-cli-core'); +const glob = require('glob'); +const constants = require('../constants/plugin-constants'); +const utils = require('../utils/amplify-context-utils'); const clientFactory = require('../utils/client-factory'); +const consolePathManager = require('../utils/path-manager'); const buildUtils = require('./build-utils'); function initCFNTemplate(context, templateFilePath) { const templateContent = context.amplify.readJsonFile(templateFilePath); - const serviceDirPath = pathManager.getAmplifyHostingDirPath(context); + const serviceDirPath = consolePathManager.getAmplifyHostingDirPath(context); - fs.ensureDirSync(pathManager.getHostingDirPath(context)); + fs.ensureDirSync(consolePathManager.getHostingDirPath(context)); fs.ensureDirSync(serviceDirPath); const jsonString = JSON.stringify(templateContent, null, 4); - fs.writeFileSync(pathManager.getTemplatePath(context), jsonString, 'utf8'); + fs.writeFileSync(consolePathManager.getTemplatePath(context), jsonString, 'utf8'); } async function initMetaFile(context, category, resourceName, type) { @@ -26,11 +28,7 @@ async function initMetaFile(context, category, resourceName, type) { lastPushTimeStamp: timeStamp, }; - context.amplify.updateamplifyMetaAfterResourceAdd( - category, - resourceName, - metaData, - ); + context.amplify.updateamplifyMetaAfterResourceAdd(category, resourceName, metaData); if (timeStamp) { // init #current-cloud-backend config file for CICD @@ -46,7 +44,7 @@ async function initCurrBackendMeta(context, category, resourceName, type, timeSt lastPushTimeStamp: timeStamp, }; // init backend meta - const currMetaFilePath = pathManager.getCurrentAmplifyMetaFilePath(context); + const currMetaFilePath = consolePathManager.getCurrentAmplifyMetaFilePath(context); const currMetaContent = context.amplify.readJsonFile(currMetaFilePath); if (!currMetaContent[category]) { currMetaContent[category] = {}; @@ -60,7 +58,7 @@ async function initCurrBackendMeta(context, category, resourceName, type, timeSt fs.writeFileSync(currMetaFilePath, JSON.stringify(currMetaContent, null, 4)); // init backend config - const curBackendConfigFilePath = pathManager.getCurrBackendConfigFilePath(context); + const curBackendConfigFilePath = consolePathManager.getCurrBackendConfigFilePath(context); if (!fs.existsSync(curBackendConfigFilePath)) { fs.ensureFileSync(curBackendConfigFilePath); fs.writeFileSync(curBackendConfigFilePath, JSON.stringify({}, null, 4)); @@ -83,8 +81,8 @@ async function initCurrBackendMeta(context, category, resourceName, type, timeSt fs.writeFileSync(curBackendConfigFilePath, JSON.stringify(backendConfig, null, 4)); - const currHostingDir = pathManager.getCurrCloudBackendHostingDirPath(context); - const currAmplifyHostingDir = pathManager.getCurrCloudBackendAmplifyHostingDirPath(context); + const currHostingDir = consolePathManager.getCurrCloudBackendHostingDirPath(context); + const currAmplifyHostingDir = consolePathManager.getCurrCloudBackendAmplifyHostingDirPath(context); fs.ensureDirSync(currHostingDir); fs.ensureDirSync(currAmplifyHostingDir); await storeCurrentCloudBackend(context); @@ -95,7 +93,7 @@ function initTeamProviderInfo(context, category, resourceName, type) { const { amplify } = context; const currEnv = amplify.getEnvInfo().envName; - const teamProviderInfoFilePath = pathManager.getProviderInfoFilePath(context); + const teamProviderInfoFilePath = consolePathManager.getProviderInfoFilePath(context); const teamProviderInfo = amplify.readJsonFile(teamProviderInfoFilePath); if (!teamProviderInfo[currEnv][categories]) { teamProviderInfo[currEnv][categories] = {}; @@ -114,16 +112,13 @@ function initTeamProviderInfo(context, category, resourceName, type) { appId, type, }; - fs.writeFileSync( - teamProviderInfoFilePath, - JSON.stringify(teamProviderInfo, null, 4), - ); + fs.writeFileSync(teamProviderInfoFilePath, JSON.stringify(teamProviderInfo, null, 4)); } async function deleteConsoleConfigFromCurrMeta(context) { const category = constants.CATEGORY; const resourceName = constants.CONSOLE_RESOURCE_NAME; - const currMetaFilePath = pathManager.getCurrentAmplifyMetaFilePath(context); + const currMetaFilePath = consolePathManager.getCurrentAmplifyMetaFilePath(context); const currMetaContent = context.amplify.readJsonFile(currMetaFilePath); if (!currMetaContent[category]) { return; @@ -145,7 +140,7 @@ function deleteConsoleConfigFromTeamProviderInfo(context) { const { amplify } = context; const currEnv = amplify.getEnvInfo().envName; - const teamProviderInfoFilePath = pathManager.getProviderInfoFilePath(context); + const teamProviderInfoFilePath = consolePathManager.getProviderInfoFilePath(context); const teamProviderInfo = amplify.readJsonFile(teamProviderInfoFilePath); if (!teamProviderInfo[currEnv][categories]) { return; @@ -159,14 +154,11 @@ function deleteConsoleConfigFromTeamProviderInfo(context) { return; } teamProviderInfo[currEnv][categories][category][resourceName] = undefined; - fs.writeFileSync( - teamProviderInfoFilePath, - JSON.stringify(teamProviderInfo, null, 4), - ); + fs.writeFileSync(teamProviderInfoFilePath, JSON.stringify(teamProviderInfo, null, 4)); } function initBackendConfig(context, category, resourceName, type) { - const backendConfigFilePath = pathManager.getBackendConfigPath(context); + const backendConfigFilePath = consolePathManager.getBackendConfigPath(context); const backendConfig = context.amplify.readJsonFile(backendConfigFilePath); if (!backendConfig[category]) { @@ -182,10 +174,7 @@ function initBackendConfig(context, category, resourceName, type) { providerPlugin: type === constants.TYPE_CICD ? undefined : constants.PROVIDER, type, }; - fs.writeFileSync( - backendConfigFilePath, - JSON.stringify(backendConfig, null, 4), - ); + fs.writeFileSync(backendConfigFilePath, JSON.stringify(backendConfig, null, 4)); } function loadConsoleConfigFromTeamProviderinfo(context) { @@ -219,7 +208,12 @@ async function storeCurrentCloudBackend(context) { try { const zipFilePath = path.normalize(path.join(tempDir, zipFilename)); - await buildUtils.zipFile(currentCloudBackendDir, zipFilePath); + const cliJSONFiles = glob.sync(PathConstants.CLIJSONFileNameGlob, { + cwd: pathManager.getAmplifyDirPath(), + absolute: true, + }); + + await buildUtils.zipFile(currentCloudBackendDir, zipFilePath, cliJSONFiles); await uploadFile(s3, zipFilePath, zipFilename, context); await uploadFile(s3, amplifyMetaFilePath, 'amplify-meta.json', context); await uploadFile(s3, backendConfigFilePath, 'backend-config.json', context); diff --git a/packages/amplify-console-integration-tests/package.json b/packages/amplify-console-integration-tests/package.json index 0ad8217f4ca..9a43c8e3d92 100644 --- a/packages/amplify-console-integration-tests/package.json +++ b/packages/amplify-console-integration-tests/package.json @@ -29,7 +29,6 @@ "ini": "^1.3.5", "moment": "^2.24.0", "nexpect": "^0.6.0", - "os": "^0.1.1", "promise-sequential": "^1.1.1" }, "jest": { diff --git a/packages/amplify-e2e-core/package.json b/packages/amplify-e2e-core/package.json index 535eaf9a1ba..6e5121f70d5 100644 --- a/packages/amplify-e2e-core/package.json +++ b/packages/amplify-e2e-core/package.json @@ -21,6 +21,7 @@ "clean": "rimraf ./lib" }, "dependencies": { + "amplify-cli-core": "1.3.1", "amplify-headless-interface": "1.3.0", "chalk": "^3.0.0", "execa": "^4.0.3", diff --git a/packages/amplify-e2e-core/src/index.ts b/packages/amplify-e2e-core/src/index.ts index d79e6fba962..cee83b380c1 100644 --- a/packages/amplify-e2e-core/src/index.ts +++ b/packages/amplify-e2e-core/src/index.ts @@ -1,6 +1,8 @@ -import { join } from 'path'; +import * as os from 'os'; +import * as path from 'path'; import * as fs from 'fs-extra'; import { spawnSync, execSync } from 'child_process'; +import { v4 as uuid } from 'uuid'; export * from './configure/'; export * from './init/'; @@ -16,11 +18,13 @@ declare global { } } +const amplifyTestsDir = 'amplify-e2e-tests'; + export function getCLIPath(testingWithLatestCodebase = false) { if (isCI() && !testingWithLatestCodebase) { return 'amplify'; } - return join(__dirname, '..', '..', 'amplify-cli', 'bin', 'amplify'); + return path.join(__dirname, '..', '..', 'amplify-cli', 'bin', 'amplify'); } export function isCI(): boolean { @@ -39,16 +43,28 @@ export async function installAmplifyCLI(version: string = 'latest') { }); } -export async function createNewProjectDir(projectName: string, prefix = join('/tmp', 'amplify-e2e-tests')): Promise { +export async function createNewProjectDir( + projectName: string, + prefix = path.join(fs.realpathSync(os.tmpdir()), amplifyTestsDir), +): Promise { const currentHash = execSync('git rev-parse --short HEAD', { cwd: __dirname }) .toString() .trim(); let projectDir; do { const randomId = await global.getRandomId(); - projectDir = join(prefix, `${projectName}_${currentHash}_${randomId}`); + projectDir = path.join(prefix, `${projectName}_${currentHash}_${randomId}`); } while (fs.existsSync(projectDir)); fs.ensureDirSync(projectDir); return projectDir; } + +export const createTempDir = () => { + const osTempDir = fs.realpathSync(os.tmpdir()); + const tempProjectDir = path.join(osTempDir, amplifyTestsDir, uuid()); + + fs.mkdirsSync(tempProjectDir); + + return tempProjectDir; +}; diff --git a/packages/amplify-e2e-core/src/init/amplifyPull.ts b/packages/amplify-e2e-core/src/init/amplifyPull.ts index 4b3e12559d4..8cca2999631 100644 --- a/packages/amplify-e2e-core/src/init/amplifyPull.ts +++ b/packages/amplify-e2e-core/src/init/amplifyPull.ts @@ -1,11 +1,21 @@ import { getCLIPath, nspawn as spawn } from '../../src'; -export function amplifyPull(cwd: string, settings: { override?: boolean; emptyDir?: boolean; appId?: string }) { +export function amplifyPull(cwd: string, settings: { override?: boolean; emptyDir?: boolean; appId?: string; withRestore?: boolean }) { return new Promise((resolve, reject) => { const tableHeaderRegex = /\|\sCategory\s+\|\sResource\sname\s+\|\sOperation\s+\|\sProvider\splugin\s+\|/; const tableSeperator = /\|(\s-+\s\|){4}/; - const chain = spawn(getCLIPath(), settings.appId ? ['pull', '--appId', settings.appId] : ['pull'], { cwd, stripColors: true }); + const args = ['pull']; + + if (settings.appId) { + args.push('--appId', settings.appId); + } + + if (settings.withRestore) { + args.push('--restore'); + } + + const chain = spawn(getCLIPath(), args, { cwd, stripColors: true }); if (settings.emptyDir) { chain diff --git a/packages/amplify-e2e-tests/package.json b/packages/amplify-e2e-tests/package.json index c225a58bdfa..2a06cfff867 100644 --- a/packages/amplify-e2e-tests/package.json +++ b/packages/amplify-e2e-tests/package.json @@ -21,6 +21,7 @@ "setup-profile": "ts-node ./src/configure_tests.ts" }, "dependencies": { + "amplify-cli-core": "1.3.1", "amplify-e2e-core": "1.6.1", "aws-amplify": "^3.0.8", "aws-appsync": "^2.0.2", @@ -32,7 +33,6 @@ "graphql-tag": "^2.10.1", "graphql-transformer-core": "6.21.3", "lodash": "^4.17.19", - "os": "^0.1.1", "promise-sequential": "^1.1.1", "rimraf": "^3.0.0", "uuid": "^3.4.0" diff --git a/packages/amplify-e2e-tests/src/__tests__/feature-flags.test.ts b/packages/amplify-e2e-tests/src/__tests__/feature-flags.test.ts new file mode 100644 index 00000000000..12b923cd123 --- /dev/null +++ b/packages/amplify-e2e-tests/src/__tests__/feature-flags.test.ts @@ -0,0 +1,68 @@ +import * as fs from 'fs-extra'; +import { initJSProjectWithProfile, deleteProject, addApiWithSchema, amplifyPush, amplifyPull, getAppId } from 'amplify-e2e-core'; +import { createNewProjectDir, deleteProjectDir } from 'amplify-e2e-core'; +import { pathManager } from 'amplify-cli-core'; +import { addEnvironment } from '../environment/env'; + +describe('feature flags', () => { + let projRoot: string; + + beforeEach(async () => { + projRoot = await createNewProjectDir('feature-flags'); + }); + + afterEach(async () => { + await deleteProject(projRoot); + deleteProjectDir(projRoot); + }); + + it('init the project and cli.json should be created', async () => { + await initJSProjectWithProfile(projRoot, {}); + + expect(fs.existsSync(pathManager.getCLIJSONFilePath(projRoot))).toBeTruthy(); + }); + + it('push and pull with multiple config files for environments', async () => { + await initJSProjectWithProfile(projRoot, {}); + await addApiWithSchema(projRoot, 'simple_model.graphql'); + + const envName = 'test'; + const cliJSONPath = pathManager.getCLIJSONFilePath(projRoot); + const testCLIJSONPath = pathManager.getCLIJSONFilePath(projRoot, envName); + + fs.copyFileSync(cliJSONPath, testCLIJSONPath); + + await amplifyPush(projRoot); + + const appId = getAppId(projRoot); + expect(appId).toBeDefined(); + + let projRoot2; + + try { + projRoot2 = await createNewProjectDir('feature-flags-pull'); + + await amplifyPull(projRoot2, { override: false, emptyDir: true, appId }); + + expect(fs.existsSync(cliJSONPath)).toBeTruthy(); + expect(fs.existsSync(testCLIJSONPath)).toBeTruthy(); + } finally { + deleteProjectDir(projRoot2); + } + }); + + it('config is cloned when new environment is created', async () => { + await initJSProjectWithProfile(projRoot, {}); + + const envName = 'test'; + const cliJSONPath = pathManager.getCLIJSONFilePath(projRoot); + const cliDevJSONPath = pathManager.getCLIJSONFilePath(projRoot, 'integtest'); + + fs.copyFileSync(cliJSONPath, cliDevJSONPath); + + await addEnvironment(projRoot, { envName }); + + const testCLIJSONPath = pathManager.getCLIJSONFilePath(projRoot, envName); + expect(fs.existsSync(testCLIJSONPath)).toBeTruthy(); + }); +}); diff --git a/packages/amplify-e2e-tests/src/environment/env.ts b/packages/amplify-e2e-tests/src/environment/env.ts index 1bd039e3cba..587d26e299f 100644 --- a/packages/amplify-e2e-tests/src/environment/env.ts +++ b/packages/amplify-e2e-tests/src/environment/env.ts @@ -1,6 +1,6 @@ import { nspawn as spawn, getCLIPath, getSocialProviders } from 'amplify-e2e-core'; -export function addEnvironment(cwd: string, settings: { envName: string; numLayers: number }) { +export function addEnvironment(cwd: string, settings: { envName: string; numLayers?: number }) { return new Promise((resolve, reject) => { const chain = spawn(getCLIPath(), ['env', 'add'], { cwd, stripColors: true }) .wait('Do you want to use an existing environment?') @@ -41,7 +41,7 @@ export function checkoutEnvironment(cwd: string, settings: { envName: string }) } // Test multiple Environments by passing settings.numEnv -export function listEnvironment(cwd: string, settings: { numEnv: number }) { +export function listEnvironment(cwd: string, settings: { numEnv?: number }) { return new Promise((resolve, reject) => { let numEnv = settings.numEnv || 1; let regex = /\|\s\*?[a-z]{2,10}\s+\|/; diff --git a/packages/amplify-frontend-javascript/package.json b/packages/amplify-frontend-javascript/package.json index 852a2553e0b..1ef89d9b91a 100644 --- a/packages/amplify-frontend-javascript/package.json +++ b/packages/amplify-frontend-javascript/package.json @@ -16,7 +16,6 @@ "aws" ], "dependencies": { - "amplify-cli-core": "1.3.1", "chalk": "^3.0.0", "execa": "^4.0.0", "fs-extra": "^8.1.0", diff --git a/packages/amplify-provider-awscloudformation/lib/attach-backend.js b/packages/amplify-provider-awscloudformation/lib/attach-backend.js index 1675fa32cd6..5351dd3f687 100644 --- a/packages/amplify-provider-awscloudformation/lib/attach-backend.js +++ b/packages/amplify-provider-awscloudformation/lib/attach-backend.js @@ -1,8 +1,10 @@ const aws = require('aws-sdk'); const fs = require('fs-extra'); const path = require('path'); +const glob = require('glob'); const extract = require('extract-zip'); const inquirer = require('inquirer'); +const { pathManager, PathConstants } = require('amplify-cli-core'); const configurationManager = require('./configuration-manager'); const { getConfiguredAmplifyClient } = require('../src/aws-utils/aws-amplify'); const { checkAmplifyServiceIAMPermission } = require('./amplify-service-permission-check'); @@ -45,12 +47,13 @@ async function ensureAmplifyMeta(context, amplifyApp, awsConfig) { // if not, it's a migration case and we need to // 1. insert the appId // 2. upload the metadata file and the backend config file into the deployment bucket - const currentAmplifyMetaFilePath = context.amplify.pathManager.getCurrentAmplifyMetaFilePath(process.cwd()); + const projectPath = process.cwd(); + const currentAmplifyMetaFilePath = context.amplify.pathManager.getCurrentAmplifyMetaFilePath(projectPath); const currentAmplifyMeta = context.amplify.readJsonFile(currentAmplifyMetaFilePath); if (!currentAmplifyMeta.providers[constants.ProviderName][constants.AmplifyAppIdLabel]) { currentAmplifyMeta.providers[constants.ProviderName][constants.AmplifyAppIdLabel] = amplifyApp.appId; - const amplifyMetaFilePath = context.amplify.pathManager.getAmplifyMetaFilePath(process.cwd()); + const amplifyMetaFilePath = context.amplify.pathManager.getAmplifyMetaFilePath(projectPath); const jsonString = JSON.stringify(currentAmplifyMeta, null, 4); fs.writeFileSync(currentAmplifyMetaFilePath, jsonString, 'utf8'); fs.writeFileSync(amplifyMetaFilePath, jsonString, 'utf8'); @@ -63,9 +66,10 @@ async function ensureAmplifyMeta(context, amplifyApp, awsConfig) { } async function storeArtifactsForAmplifyService(context, awsConfig, deploymentBucketName) { + const projectPath = process.cwd(); const s3Client = new aws.S3(awsConfig); - const amplifyMetaFilePath = context.amplify.pathManager.getCurrentAmplifyMetaFilePath(process.cwd()); - const backendConfigFilePath = context.amplify.pathManager.getCurrentBackendConfigFilePath(process.cwd()); + const amplifyMetaFilePath = context.amplify.pathManager.getCurrentAmplifyMetaFilePath(projectPath); + const backendConfigFilePath = context.amplify.pathManager.getCurrentBackendConfigFilePath(projectPath); await uploadFile(s3Client, deploymentBucketName, amplifyMetaFilePath); await uploadFile(s3Client, deploymentBucketName, backendConfigFilePath); } @@ -213,10 +217,11 @@ async function downloadBackend(context, backendEnv, awsConfig) { if (!backendEnv) { return; } - const amplifyDirPath = context.amplify.pathManager.getAmplifyDirPath(process.cwd()); - const tempDirPath = path.join(amplifyDirPath, 'temp'); - const currentCloudBackendDir = context.amplify.pathManager.getCurrentCloudBackendDirPath(process.cwd()); - const backendDir = context.amplify.pathManager.getBackendDirPath(process.cwd()); + const projectPath = process.cwd(); + const amplifyDirPath = context.amplify.pathManager.getAmplifyDirPath(projectPath); + const tempDirPath = path.join(amplifyDirPath, '.temp'); + const currentCloudBackendDir = context.amplify.pathManager.getCurrentCloudBackendDirPath(projectPath); + const backendDir = context.amplify.pathManager.getBackendDirPath(projectPath); const zipFileName = constants.S3BackendZipFileName; const s3Client = new aws.S3(awsConfig); @@ -231,23 +236,42 @@ async function downloadBackend(context, backendEnv, awsConfig) { const buff = Buffer.from(zipObject.Body); fs.ensureDirSync(tempDirPath); - const tempFilePath = path.join(tempDirPath, zipFileName); - fs.writeFileSync(tempFilePath, buff); - const unzippedDirPath = path.join(tempDirPath, path.basename(zipFileName, '.zip')); + try { + const tempFilePath = path.join(tempDirPath, zipFileName); + fs.writeFileSync(tempFilePath, buff); - await new Promise((res, rej) => { - extract(tempFilePath, { dir: unzippedDirPath }, err => { - if (err) { - rej(err); - } - res(unzippedDirPath); + const unzippedDirPath = path.join(tempDirPath, path.basename(zipFileName, '.zip')); + + await extract(tempFilePath, { dir: unzippedDirPath }); + + // Move out cli.*json if exists in the temp directory into the amplify directory before copying backand and + // current cloud backend directories. + const cliJSONFiles = glob.sync(PathConstants.CLIJSONFileNameGlob, { + cwd: unzippedDirPath, + absolute: true, }); - }); + const amplifyDir = pathManager.getAmplifyDirPath(); - fs.copySync(unzippedDirPath, currentCloudBackendDir); - fs.copySync(unzippedDirPath, backendDir); - fs.removeSync(tempDirPath); + if (context.exeInfo && context.exeInfo.restoreBackend) { + // If backend must be restored then copy out the config files and overwrite existing ones. + for (const cliJSONFilePath of cliJSONFiles) { + const targetPath = path.join(amplifyDir, path.basename(cliJSONFilePath)); + + fs.moveSync(cliJSONFilePath, targetPath, { overwrite: true }); + } + } else { + // If backend is not being restored, just delete the config files in the current cloud backend if present + for (const cliJSONFilePath of cliJSONFiles) { + fs.removeSync(cliJSONFilePath); + } + } + + fs.copySync(unzippedDirPath, currentCloudBackendDir); + fs.copySync(unzippedDirPath, backendDir); + } finally { + fs.removeSync(tempDirPath); + } } module.exports = { diff --git a/packages/amplify-provider-awscloudformation/lib/initialize-env.js b/packages/amplify-provider-awscloudformation/lib/initialize-env.js index 4ee53973e1e..a51decaf19c 100644 --- a/packages/amplify-provider-awscloudformation/lib/initialize-env.js +++ b/packages/amplify-provider-awscloudformation/lib/initialize-env.js @@ -1,4 +1,7 @@ const fs = require('fs-extra'); +const path = require('path'); +const glob = require('glob'); +const { PathConstants, stateManager } = require('amplify-cli-core'); const Cloudformation = require('../src/aws-utils/aws-cfn'); const S3 = require('../src/aws-utils/aws-s3'); const { downloadZip, extractZip } = require('./zip-util'); @@ -10,18 +13,42 @@ function run(context, providerMetadata) { } const amplifyDir = context.amplify.pathManager.getAmplifyDirPath(); - const tempDir = `${amplifyDir}/.temp`; + const tempDir = path.join(amplifyDir, '.temp'); const currentCloudBackendDir = context.amplify.pathManager.getCurrentCloudBackendDirPath(); const backendDir = context.amplify.pathManager.getBackendDirPath(); + return new S3(context) .then(s3 => downloadZip(s3, tempDir, S3BackendZipFileName).then(file => extractZip(tempDir, file).then(unzippeddir => { fs.removeSync(currentCloudBackendDir); + + // Move out cli.*json if exists in the temp directory into the amplify directory before copying backand and + // current cloud backend directories. + const cliJSONFiles = glob.sync(PathConstants.CLIJSONFileNameGlob, { + cwd: unzippeddir, + absolute: true, + }); + + if (context.exeInfo.restoreBackend) { + // If backend must be restored then copy out the config files and overwrite existing ones. + for (const cliJSONFilePath of cliJSONFiles) { + const targetPath = path.join(amplifyDir, path.basename(cliJSONFilePath)); + + fs.moveSync(cliJSONFilePath, targetPath, { overwrite: true }); + } + } else { + // If backend is not being restored, just delete the config files in the current cloud backend if present + for (const cliJSONFilePath of cliJSONFiles) { + fs.removeSync(cliJSONFilePath); + } + } + fs.copySync(unzippeddir, currentCloudBackendDir); + if (context.exeInfo.restoreBackend) { fs.removeSync(backendDir); - fs.copySync(`${tempDir}/#current-cloud-backend`, backendDir); + fs.copySync(unzippeddir, backendDir); } fs.removeSync(tempDir); }), @@ -31,11 +58,8 @@ function run(context, providerMetadata) { .then(cfnItem => cfnItem.updateamplifyMetaFileWithStackOutputs(providerMetadata.StackName)) .then(() => { // Copy provider metadata from current-cloud-backend/amplify-meta to backend/ampliy-meta - const currentAmplifyMetafilePath = context.amplify.pathManager.getCurrentAmplifyMetaFilePath(); - const currentAmplifyMeta = context.amplify.readJsonFile(currentAmplifyMetafilePath); - - const amplifyMetafilePath = context.amplify.pathManager.getAmplifyMetaFilePath(); - const amplifyMeta = context.amplify.readJsonFile(amplifyMetafilePath); + const currentAmplifyMeta = stateManager.getCurrentMeta(); + const amplifyMeta = stateManager.getMeta(); // Copy providerMetadata for each resource - from what is there in the cloud @@ -47,7 +71,7 @@ function run(context, providerMetadata) { }); }); - context.amplify.writeObjectAsJson(amplifyMetafilePath, amplifyMeta, true); + stateManager.setMeta(undefined, amplifyMeta); }); } diff --git a/packages/amplify-provider-awscloudformation/lib/initializer.js b/packages/amplify-provider-awscloudformation/lib/initializer.js index f0f854d6bcb..efe10e0fab5 100644 --- a/packages/amplify-provider-awscloudformation/lib/initializer.js +++ b/packages/amplify-provider-awscloudformation/lib/initializer.js @@ -1,5 +1,7 @@ const moment = require('moment'); const path = require('path'); +const { pathManager, PathConstants, stateManager } = require('amplify-cli-core'); +const glob = require('glob'); const archiver = require('../src/utils/archiver'); const fs = require('fs-extra'); const ora = require('ora'); @@ -62,6 +64,8 @@ async function run(context) { const stackDescriptionData = await cfnItem.createResourceStack(params); processStackCreationData(context, amplifyAppId, stackDescriptionData); + cloneCLIJSONForNewEnvironment(context); + spinner.succeed('Successfully created initial AWS cloud resources for deployments.'); return context; @@ -108,6 +112,26 @@ function processStackCreationData(context, amplifyAppId, stackDescriptiondata) { } } +function cloneCLIJSONForNewEnvironment(context) { + if (context.exeInfo.isNewEnv && !context.exeInfo.isNewProject) { + const { projectPath } = context.exeInfo.localEnvInfo; + const { envName } = stateManager.getLocalEnvInfo(undefined, { + throwIfNotExist: false, + default: {}, + }); + + if (envName) { + const currentEnvCLIJSONPath = pathManager.getCLIJSONFilePath(projectPath, envName); + + if (fs.existsSync(currentEnvCLIJSONPath)) { + const newEnvCLIJSONPath = pathManager.getCLIJSONFilePath(projectPath, context.exeInfo.localEnvInfo.envName); + + fs.copyFileSync(currentEnvCLIJSONPath, newEnvCLIJSONPath); + } + } + } +} + async function onInitSuccessful(context) { configurationManager.onInitSuccessful(context); if (context.exeInfo.isNewEnv) { @@ -120,18 +144,23 @@ async function onInitSuccessful(context) { function storeCurrentCloudBackend(context) { const zipFilename = '#current-cloud-backend.zip'; const backendDir = context.amplify.pathManager.getBackendDirPath(); - const tempDir = `${backendDir}/.temp`; + const tempDir = path.join(backendDir, '.temp'); const currentCloudBackendDir = context.exeInfo - ? `${context.exeInfo.localEnvInfo.projectPath}/amplify/#current-cloud-backend` + ? path.join(context.exeInfo.localEnvInfo.projectPath, PathConstants.AmplifyDirName, PathConstants.CurrentCloudBackendDirName) : context.amplify.pathManager.getCurrentCloudBackendDirPath(); if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir); } + const cliJSONFiles = glob.sync(PathConstants.CLIJSONFileNameGlob, { + cwd: pathManager.getAmplifyDirPath(), + absolute: true, + }); + const zipFilePath = path.normalize(path.join(tempDir, zipFilename)); return archiver - .run(currentCloudBackendDir, zipFilePath) + .run(currentCloudBackendDir, zipFilePath, undefined, cliJSONFiles) .then(result => { const s3Key = `${result.zipFilename}`; return new S3(context).then(s3 => { diff --git a/packages/amplify-provider-awscloudformation/lib/push-resources.js b/packages/amplify-provider-awscloudformation/lib/push-resources.js index 6c8bb848730..cc9c3905b90 100644 --- a/packages/amplify-provider-awscloudformation/lib/push-resources.js +++ b/packages/amplify-provider-awscloudformation/lib/push-resources.js @@ -3,6 +3,7 @@ const fs = require('fs-extra'); const path = require('path'); const cfnLint = require('cfn-lint'); const glob = require('glob'); +const { pathManager, PathConstants } = require('amplify-cli-core'); const ora = require('ora'); const S3 = require('../src/aws-utils/aws-s3'); const Cloudformation = require('../src/aws-utils/aws-cfn'); @@ -175,16 +176,21 @@ async function updateStackForAPIMigration(context, category, resourceName, optio function storeCurrentCloudBackend(context) { const zipFilename = '#current-cloud-backend.zip'; const backendDir = context.amplify.pathManager.getBackendDirPath(); - const tempDir = `${backendDir}/.temp`; + const tempDir = path.join(backendDir, '.temp'); const currentCloudBackendDir = context.amplify.pathManager.getCurrentCloudBackendDirPath(); if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir); } + const cliJSONFiles = glob.sync(PathConstants.CLIJSONFileNameGlob, { + cwd: pathManager.getAmplifyDirPath(), + absolute: true, + }); + const zipFilePath = path.normalize(path.join(tempDir, zipFilename)); return archiver - .run(currentCloudBackendDir, zipFilePath) + .run(currentCloudBackendDir, zipFilePath, undefined, cliJSONFiles) .then(result => { const s3Key = `${result.zipFilename}`; return new S3(context).then(s3 => { diff --git a/packages/amplify-provider-awscloudformation/lib/zip-util.js b/packages/amplify-provider-awscloudformation/lib/zip-util.js index 69f8ef8871a..7c30a80cb6a 100644 --- a/packages/amplify-provider-awscloudformation/lib/zip-util.js +++ b/packages/amplify-provider-awscloudformation/lib/zip-util.js @@ -8,7 +8,7 @@ function downloadZip(s3, tempDir, zipFileName, envName) { { Key: zipFileName, }, - envName + envName, ).then((objectResult, objectError) => { if (objectError) { reject(objectError); @@ -16,8 +16,10 @@ function downloadZip(s3, tempDir, zipFileName, envName) { } fs.ensureDirSync(tempDir); + const buff = Buffer.from(objectResult); const tempfile = `${tempDir}/${zipFileName}`; + fs.writeFile(tempfile, buff, err => { if (err) { reject(err); @@ -29,18 +31,14 @@ function downloadZip(s3, tempDir, zipFileName, envName) { }); } -function extractZip(tempdir, zipFile) { - return new Promise((resolve, reject) => { - const filenameext = path.basename(zipFile); - const filename = filenameext.split('.')[0]; - const unzippedDir = `${tempdir}/${filename}`; - extract(zipFile, { dir: unzippedDir }, err => { - if (err) { - reject(err); - } - resolve(unzippedDir); - }); - }); +async function extractZip(tempdir, zipFile) { + const filenameext = path.basename(zipFile); + const filename = filenameext.split('.')[0]; + const unzippedDir = path.join(tempdir, filename); + + await extract(zipFile, { dir: unzippedDir }); + + return unzippedDir; } module.exports = { diff --git a/packages/amplify-provider-awscloudformation/package.json b/packages/amplify-provider-awscloudformation/package.json index 753ee2379c3..1ccaa32b71d 100644 --- a/packages/amplify-provider-awscloudformation/package.json +++ b/packages/amplify-provider-awscloudformation/package.json @@ -26,7 +26,7 @@ "cfn-lint": "^1.9.7", "chalk": "^3.0.0", "columnify": "^1.5.4", - "extract-zip": "^1.6.7", + "extract-zip": "^2.0.1", "folder-hash": "^3.3.0", "fs-extra": "^8.1.0", "glob": "^7.1.6", @@ -50,7 +50,6 @@ "moment": "^2.24.0", "open": "^7.0.0", "ora": "^4.0.3", - "os": "^0.1.1", "promise-sequential": "^1.1.1", "proxy-agent": "^3.1.1" } diff --git a/packages/amplify-provider-awscloudformation/src/utils/archiver.js b/packages/amplify-provider-awscloudformation/src/utils/archiver.js index 2d664496472..2fa8cc43817 100644 --- a/packages/amplify-provider-awscloudformation/src/utils/archiver.js +++ b/packages/amplify-provider-awscloudformation/src/utils/archiver.js @@ -4,7 +4,7 @@ const fs = require('fs-extra'); const DEFAULT_IGNORE_PATTERN = ['*/*/build/**', '*/*/dist/**', 'function/*/src/node_modules/**']; -function run(folder, zipFilePath, ignorePattern = DEFAULT_IGNORE_PATTERN) { +function run(folder, zipFilePath, ignorePattern = DEFAULT_IGNORE_PATTERN, extraFiles) { const zipFileFolder = path.dirname(zipFilePath); const zipFilename = path.basename(zipFilePath); @@ -30,6 +30,15 @@ function run(folder, zipFilePath, ignorePattern = DEFAULT_IGNORE_PATTERN) { ignore: ignorePattern, dot: true, }); + + if (extraFiles && extraFiles.length && extraFiles.length > 0) { + for (const filePath of extraFiles) { + const fileName = path.basename(filePath); + + zip.file(filePath, { name: fileName }); + } + } + zip.finalize(); }); } diff --git a/yarn.lock b/yarn.lock index 4ff5813beb2..e433d30bb60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5030,6 +5030,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yauzl@^2.9.1": + version "2.9.1" + resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" + integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== + dependencies: + "@types/node" "*" + "@types/zen-observable@0.8.0", "@types/zen-observable@^0.8.0": version "0.8.0" resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" @@ -7924,7 +7931,7 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0, concat-stream@^1.6.2: +concat-stream@^1.5.0: version "1.6.2" resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -10384,15 +10391,16 @@ extract-files@^8.0.0: resolved "https://registry.npmjs.org/extract-files/-/extract-files-8.1.0.tgz#46a0690d0fe77411a2e3804852adeaa65cd59288" integrity sha512-PTGtfthZK79WUMk+avLmwx3NGdU8+iVFXC2NMGxKsn0MnihOG2lvumj+AZo8CTwTrwjXDgZ5tztbRlEdRjBonQ== -extract-zip@^1.6.7: - version "1.7.0" - resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" - integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== +extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== dependencies: - concat-stream "^1.6.2" - debug "^2.6.9" - mkdirp "^0.5.4" + debug "^4.1.1" + get-stream "^5.1.0" yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" extsprintf@1.3.0: version "1.3.0" @@ -15501,7 +15509,7 @@ mkdirp@0.3.0: resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4= -mkdirp@0.5.x, mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.5.x, mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -16417,11 +16425,6 @@ os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -os@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/os/-/os-0.1.1.tgz#208845e89e193ad4d971474b93947736a56d13f3" - integrity sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M= - osenv@^0.1.4, osenv@^0.1.5: version "0.1.5" resolved "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"