From fb07bafce61305113436d8a098088c4e5d2458cf Mon Sep 17 00:00:00 2001 From: Charles Shin Date: Mon, 22 Jan 2024 09:18:34 -0800 Subject: [PATCH] chore: remove Logger class & refactor Printer (#903) * chore: remove Logger class & refactor Printer * fix: update API.md * fix: update API.md * fix: update API.md * fix: make constructor private * fix: remove yargs & use process.argv * bump package-lock * fix: update Printer to be singleton instead of static class * update changeset * update API.md * remove unused imports * remove unused imports * fix * removing no-console linter rule override * update API.md * fix test * .eslintrc * update integ test * revert back to stderr * error logs should write to stderr * update API.md * remove printRecord * remove async * spread printRecords, updating to singleton --- .changeset/polite-meals-search.md | 8 + package-lock.json | 242 +++++++++--------- packages/cli-core/API.md | 22 +- packages/cli-core/src/printer/.eslintrc.json | 5 - packages/cli-core/src/printer/printer.test.ts | 96 +++++++ packages/cli-core/src/printer/printer.ts | 170 ++++++++++-- .../configure_profile_command.test.ts | 5 +- .../configure/configure_profile_command.ts | 9 +- .../configure_telemetry_command.test.ts | 15 +- .../telemetry/configure_telemetry_command.ts | 8 +- packages/cli/src/commands/open/open.ts | 6 +- .../sandbox_delete_command.test.ts | 15 +- .../sandbox_secret_command_factory.ts | 6 +- .../sandbox_secret_get_command.test.ts | 13 +- .../sandbox_secret_get_command.ts | 4 +- .../sandbox_secret_list_command.test.ts | 12 +- .../sandbox_secret_list_command.ts | 4 +- .../commands/sandbox/sandbox_command.test.ts | 29 ++- .../sandbox/sandbox_command_factory.ts | 6 +- .../sandbox_event_handler_factory.test.ts | 8 +- .../sandbox/sandbox_event_handler_factory.ts | 11 +- packages/cli/src/error_handler.test.ts | 9 +- packages/cli/src/error_handler.ts | 15 +- packages/cli/src/main_parser_factory.ts | 3 + packages/cli/src/printer.ts | 7 + packages/create-amplify/.eslintrc.json | 6 +- .../src/amplify_project_creator.test.ts | 34 +-- .../src/amplify_project_creator.ts | 18 +- packages/create-amplify/src/create_amplify.ts | 7 +- .../src/execute_with_logger.test.ts | 14 +- .../create-amplify/src/execute_with_logger.ts | 13 +- .../create-amplify/src/get_project_root.ts | 11 +- .../src/gitignore_initializer.test.ts | 16 +- .../src/gitignore_initializer.ts | 8 +- packages/create-amplify/src/logger.test.ts | 120 --------- packages/create-amplify/src/logger.ts | 169 ------------ .../src/npm_project_initializer.test.ts | 9 +- .../src/npm_project_initializer.ts | 8 +- packages/create-amplify/src/printer.ts | 7 + packages/sandbox/.eslintrc.json | 5 - packages/sandbox/API.md | 3 +- .../sandbox/src/file_watching_sandbox.test.ts | 11 +- packages/sandbox/src/file_watching_sandbox.ts | 37 +-- packages/sandbox/src/sandbox_executor.test.ts | 9 +- packages/sandbox/src/sandbox_executor.ts | 8 +- .../sandbox/src/sandbox_singleton_factory.ts | 12 +- 46 files changed, 639 insertions(+), 614 deletions(-) create mode 100644 .changeset/polite-meals-search.md delete mode 100644 packages/cli-core/src/printer/.eslintrc.json create mode 100644 packages/cli-core/src/printer/printer.test.ts create mode 100644 packages/cli/src/printer.ts delete mode 100644 packages/create-amplify/src/logger.test.ts delete mode 100644 packages/create-amplify/src/logger.ts create mode 100644 packages/create-amplify/src/printer.ts delete mode 100644 packages/sandbox/.eslintrc.json diff --git a/.changeset/polite-meals-search.md b/.changeset/polite-meals-search.md new file mode 100644 index 0000000000..52f0378962 --- /dev/null +++ b/.changeset/polite-meals-search.md @@ -0,0 +1,8 @@ +--- +'create-amplify': minor +'@aws-amplify/cli-core': minor +'@aws-amplify/sandbox': minor +'@aws-amplify/backend-cli': minor +--- + +Refactor Printer class & deprecate Logger diff --git a/package-lock.json b/package-lock.json index 49d3678907..5b1208aeb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9667,13 +9667,13 @@ } }, "node_modules/@typescript-eslint/rule-tester": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/rule-tester/-/rule-tester-6.18.1.tgz", - "integrity": "sha512-Ju9k2VbCHA1GQmuprVv7XrLuhS4lUuDj7EaLOBtiERFUCKYze8AxvQCCniToREcPjIiT4ljO2UB7MupLYUKfzg==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/rule-tester/-/rule-tester-6.19.0.tgz", + "integrity": "sha512-4/nUf0k1LYIxdEoNZBIvk3k4iXecV03mzKbHZQcB2TeyFuPUOnJGDQI8rrfbP7jbE2a6K7h5zU0ai0uG1ytO6g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.18.1", - "@typescript-eslint/utils": "6.18.1", + "@typescript-eslint/typescript-estree": "6.19.0", + "@typescript-eslint/utils": "6.19.0", "ajv": "^6.10.0", "lodash.merge": "4.6.2", "semver": "^7.5.4" @@ -9691,13 +9691,13 @@ } }, "node_modules/@typescript-eslint/rule-tester/node_modules/@typescript-eslint/scope-manager": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", - "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz", + "integrity": "sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1" + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/visitor-keys": "6.19.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -9708,9 +9708,9 @@ } }, "node_modules/@typescript-eslint/rule-tester/node_modules/@typescript-eslint/types": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", - "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz", + "integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -9721,13 +9721,13 @@ } }, "node_modules/@typescript-eslint/rule-tester/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", - "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz", + "integrity": "sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/visitor-keys": "6.19.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -9749,17 +9749,17 @@ } }, "node_modules/@typescript-eslint/rule-tester/node_modules/@typescript-eslint/utils": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz", - "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.0.tgz", + "integrity": "sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/scope-manager": "6.19.0", + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/typescript-estree": "6.19.0", "semver": "^7.5.4" }, "engines": { @@ -9774,12 +9774,12 @@ } }, "node_modules/@typescript-eslint/rule-tester/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", - "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz", + "integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/types": "6.19.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -21091,11 +21091,11 @@ }, "packages/auth-construct": { "name": "@aws-amplify/auth-construct-alpha", - "version": "0.5.2", + "version": "0.5.3", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-output-schemas": "^0.5.1", - "@aws-amplify/backend-output-storage": "^0.2.10", + "@aws-amplify/backend-output-schemas": "^0.5.2", + "@aws-amplify/backend-output-storage": "^0.2.11", "@aws-amplify/plugin-types": "^0.7.1", "@aws-sdk/util-arn-parser": "^3.465.0" }, @@ -21106,18 +21106,18 @@ }, "packages/backend": { "name": "@aws-amplify/backend", - "version": "0.10.1", + "version": "0.10.2", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-auth": "^0.4.3", - "@aws-amplify/backend-data": "^0.9.4", - "@aws-amplify/backend-function": "^0.6.2", - "@aws-amplify/backend-output-schemas": "^0.5.1", - "@aws-amplify/backend-output-storage": "^0.2.10", - "@aws-amplify/backend-secret": "^0.4.2", - "@aws-amplify/backend-storage": "^0.4.3", + "@aws-amplify/backend-auth": "^0.4.4", + "@aws-amplify/backend-data": "^0.9.5", + "@aws-amplify/backend-function": "^0.6.3", + "@aws-amplify/backend-output-schemas": "^0.5.2", + "@aws-amplify/backend-output-storage": "^0.2.11", + "@aws-amplify/backend-secret": "^0.4.3", + "@aws-amplify/backend-storage": "^0.4.4", "@aws-amplify/data-schema": "^0.12.9", - "@aws-amplify/platform-core": "^0.4.2", + "@aws-amplify/platform-core": "^0.4.3", "@aws-amplify/plugin-types": "^0.7.1", "@aws-sdk/client-amplify": "^3.465.0" }, @@ -21132,16 +21132,16 @@ }, "packages/backend-auth": { "name": "@aws-amplify/backend-auth", - "version": "0.4.3", + "version": "0.4.4", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/auth-construct-alpha": "^0.5.2", - "@aws-amplify/backend-output-storage": "^0.2.10", + "@aws-amplify/auth-construct-alpha": "^0.5.3", + "@aws-amplify/backend-output-storage": "^0.2.11", "@aws-amplify/plugin-types": "^0.7.1" }, "devDependencies": { "@aws-amplify/backend-platform-test-stubs": "^0.3.2", - "@aws-amplify/platform-core": "^0.4.2" + "@aws-amplify/platform-core": "^0.4.3" }, "peerDependencies": { "aws-cdk-lib": "^2.110.1", @@ -21150,11 +21150,11 @@ }, "packages/backend-data": { "name": "@aws-amplify/backend-data", - "version": "0.9.4", + "version": "0.9.5", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-output-schemas": "^0.5.1", - "@aws-amplify/backend-output-storage": "^0.2.10", + "@aws-amplify/backend-output-schemas": "^0.5.2", + "@aws-amplify/backend-output-storage": "^0.2.11", "@aws-amplify/data-construct": "^1.4.1", "@aws-amplify/data-schema-types": "^0.6.6", "@aws-amplify/plugin-types": "^0.7.1" @@ -21162,7 +21162,7 @@ "devDependencies": { "@aws-amplify/backend-platform-test-stubs": "^0.3.2", "@aws-amplify/data-schema": "^0.12.9", - "@aws-amplify/platform-core": "^0.4.2" + "@aws-amplify/platform-core": "^0.4.3" }, "peerDependencies": { "aws-cdk-lib": "^2.110.1", @@ -21171,10 +21171,10 @@ }, "packages/backend-deployer": { "name": "@aws-amplify/backend-deployer", - "version": "0.4.6", + "version": "0.4.7", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/platform-core": "^0.4.2", + "@aws-amplify/platform-core": "^0.4.3", "@aws-amplify/plugin-types": "^0.7.1", "execa": "^8.0.1", "tsx": "^4.6.1" @@ -21186,16 +21186,16 @@ }, "packages/backend-function": { "name": "@aws-amplify/backend-function", - "version": "0.6.2", + "version": "0.6.3", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-output-storage": "^0.2.10", + "@aws-amplify/backend-output-storage": "^0.2.11", "@aws-amplify/plugin-types": "^0.7.1", "execa": "^8.0.1" }, "devDependencies": { "@aws-amplify/backend-platform-test-stubs": "^0.3.2", - "@aws-amplify/platform-core": "^0.4.2", + "@aws-amplify/platform-core": "^0.4.3", "@aws-sdk/client-ssm": "^3.465.0", "uuid": "^9.0.1" }, @@ -21219,7 +21219,7 @@ }, "packages/backend-output-schemas": { "name": "@aws-amplify/backend-output-schemas", - "version": "0.5.1", + "version": "0.5.2", "license": "Apache-2.0", "devDependencies": { "@aws-amplify/plugin-types": "^0.7.1" @@ -21230,11 +21230,11 @@ }, "packages/backend-output-storage": { "name": "@aws-amplify/backend-output-storage", - "version": "0.2.10", + "version": "0.2.11", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-output-schemas": "^0.5.1", - "@aws-amplify/platform-core": "^0.4.2" + "@aws-amplify/backend-output-schemas": "^0.5.2", + "@aws-amplify/platform-core": "^0.4.3" }, "peerDependencies": { "aws-cdk-lib": "^2.110.1" @@ -21251,10 +21251,10 @@ }, "packages/backend-secret": { "name": "@aws-amplify/backend-secret", - "version": "0.4.2", + "version": "0.4.3", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/platform-core": "^0.4.2", + "@aws-amplify/platform-core": "^0.4.3", "@aws-amplify/plugin-types": "^0.7.1", "@aws-sdk/client-ssm": "^3.465.0" }, @@ -21264,16 +21264,16 @@ }, "packages/backend-storage": { "name": "@aws-amplify/backend-storage", - "version": "0.4.3", + "version": "0.4.4", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-output-schemas": "^0.5.1", - "@aws-amplify/backend-output-storage": "^0.2.10", + "@aws-amplify/backend-output-schemas": "^0.5.2", + "@aws-amplify/backend-output-storage": "^0.2.11", "@aws-amplify/plugin-types": "^0.7.1" }, "devDependencies": { "@aws-amplify/backend-platform-test-stubs": "^0.3.2", - "@aws-amplify/platform-core": "^0.4.2" + "@aws-amplify/platform-core": "^0.4.3" }, "peerDependencies": { "aws-cdk-lib": "^2.110.1", @@ -21282,19 +21282,19 @@ }, "packages/cli": { "name": "@aws-amplify/backend-cli", - "version": "0.9.6", + "version": "0.9.7", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-deployer": "^0.4.6", - "@aws-amplify/backend-output-schemas": "^0.5.1", - "@aws-amplify/backend-secret": "^0.4.2", + "@aws-amplify/backend-deployer": "^0.4.7", + "@aws-amplify/backend-output-schemas": "^0.5.2", + "@aws-amplify/backend-secret": "^0.4.3", "@aws-amplify/cli-core": "^0.2.0", - "@aws-amplify/client-config": "^0.5.2", - "@aws-amplify/deployed-backend-client": "^0.3.8", + "@aws-amplify/client-config": "^0.5.3", + "@aws-amplify/deployed-backend-client": "^0.3.9", "@aws-amplify/form-generator": "^0.6.1", - "@aws-amplify/model-generator": "^0.2.6", - "@aws-amplify/platform-core": "^0.4.2", - "@aws-amplify/sandbox": "^0.3.13", + "@aws-amplify/model-generator": "^0.2.7", + "@aws-amplify/platform-core": "^0.4.3", + "@aws-amplify/sandbox": "^0.3.14", "@aws-sdk/credential-provider-ini": "^3.465.0", "@aws-sdk/credential-providers": "^3.465.0", "@aws-sdk/region-config-resolver": "^3.465.0", @@ -21429,12 +21429,12 @@ }, "packages/client-config": { "name": "@aws-amplify/client-config", - "version": "0.5.2", + "version": "0.5.3", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-output-schemas": "^0.5.1", - "@aws-amplify/deployed-backend-client": "^0.3.8", - "@aws-amplify/model-generator": "^0.2.6", + "@aws-amplify/backend-output-schemas": "^0.5.2", + "@aws-amplify/deployed-backend-client": "^0.3.9", + "@aws-amplify/model-generator": "^0.2.7", "@aws-sdk/client-amplify": "^3.465.0", "@aws-sdk/client-cloudformation": "^3.465.0", "@aws-sdk/client-ssm": "^3.465.0", @@ -21442,16 +21442,16 @@ "zod": "^3.22.2" }, "devDependencies": { - "@aws-amplify/platform-core": "^0.4.2", + "@aws-amplify/platform-core": "^0.4.3", "@aws-sdk/types": "^3.465.0" } }, "packages/create-amplify": { - "version": "0.4.3", + "version": "0.4.4", "license": "Apache-2.0", "dependencies": { "@aws-amplify/cli-core": "^0.2.0", - "@aws-amplify/platform-core": "^0.4.2", + "@aws-amplify/platform-core": "^0.4.3", "execa": "^8.0.1", "yargs": "^17.7.2" }, @@ -21564,11 +21564,11 @@ }, "packages/deployed-backend-client": { "name": "@aws-amplify/deployed-backend-client", - "version": "0.3.8", + "version": "0.3.9", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-output-schemas": "^0.5.1", - "@aws-amplify/platform-core": "^0.4.2", + "@aws-amplify/backend-output-schemas": "^0.5.2", + "@aws-amplify/platform-core": "^0.4.3", "@aws-sdk/client-amplify": "^3.465.0", "@aws-sdk/client-cloudformation": "^3.465.0", "@aws-sdk/client-s3": "^3.465.0", @@ -21588,12 +21588,12 @@ } }, "packages/eslint-rules/node_modules/@typescript-eslint/scope-manager": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", - "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz", + "integrity": "sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==", "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1" + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/visitor-keys": "6.19.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -21604,9 +21604,9 @@ } }, "packages/eslint-rules/node_modules/@typescript-eslint/types": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", - "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz", + "integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==", "engines": { "node": "^16.0.0 || >=18.0.0" }, @@ -21616,12 +21616,12 @@ } }, "packages/eslint-rules/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", - "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz", + "integrity": "sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==", "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/visitor-keys": "6.19.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -21643,16 +21643,16 @@ } }, "packages/eslint-rules/node_modules/@typescript-eslint/utils": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz", - "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.0.tgz", + "integrity": "sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/scope-manager": "6.19.0", + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/typescript-estree": "6.19.0", "semver": "^7.5.4" }, "engines": { @@ -21667,11 +21667,11 @@ } }, "packages/eslint-rules/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", - "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz", + "integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==", "dependencies": { - "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/types": "6.19.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -21749,15 +21749,15 @@ }, "packages/integration-tests": { "name": "@aws-amplify/integration-tests", - "version": "0.4.2", + "version": "0.4.3", "license": "Apache-2.0", "devDependencies": { - "@aws-amplify/auth-construct-alpha": "^0.5.2", - "@aws-amplify/backend": "^0.10.1", - "@aws-amplify/backend-secret": "^0.4.2", - "@aws-amplify/client-config": "^0.5.2", + "@aws-amplify/auth-construct-alpha": "^0.5.3", + "@aws-amplify/backend": "^0.10.2", + "@aws-amplify/backend-secret": "^0.4.3", + "@aws-amplify/client-config": "^0.5.3", "@aws-amplify/data-schema": "^0.12.9", - "@aws-amplify/platform-core": "^0.4.2", + "@aws-amplify/platform-core": "^0.4.3", "@aws-sdk/client-amplify": "^3.465.0", "@aws-sdk/client-cloudformation": "^3.465.0", "@aws-sdk/client-lambda": "^3.465.0", @@ -22162,11 +22162,11 @@ }, "packages/model-generator": { "name": "@aws-amplify/model-generator", - "version": "0.2.6", + "version": "0.2.7", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-output-schemas": "^0.5.1", - "@aws-amplify/deployed-backend-client": "^0.3.8", + "@aws-amplify/backend-output-schemas": "^0.5.2", + "@aws-amplify/deployed-backend-client": "^0.3.9", "@aws-amplify/graphql-generator": "^0.1.3", "@aws-amplify/graphql-types-generator": "^3.4.4", "@aws-sdk/client-appsync": "^3.465.0", @@ -22182,7 +22182,7 @@ }, "packages/platform-core": { "name": "@aws-amplify/platform-core", - "version": "0.4.2", + "version": "0.4.3", "license": "Apache-2.0", "dependencies": { "@aws-amplify/plugin-types": "^0.7.1", @@ -22615,15 +22615,15 @@ }, "packages/sandbox": { "name": "@aws-amplify/sandbox", - "version": "0.3.13", + "version": "0.3.14", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-deployer": "^0.4.6", - "@aws-amplify/backend-secret": "^0.4.2", + "@aws-amplify/backend-deployer": "^0.4.7", + "@aws-amplify/backend-secret": "^0.4.3", "@aws-amplify/cli-core": "^0.2.0", - "@aws-amplify/client-config": "^0.5.2", - "@aws-amplify/deployed-backend-client": "^0.3.8", - "@aws-amplify/platform-core": "^0.4.2", + "@aws-amplify/client-config": "^0.5.3", + "@aws-amplify/deployed-backend-client": "^0.3.9", + "@aws-amplify/platform-core": "^0.4.3", "@aws-sdk/client-cloudformation": "^3.465.0", "@aws-sdk/credential-providers": "^3.465.0", "@aws-sdk/types": "^3.465.0", diff --git a/packages/cli-core/API.md b/packages/cli-core/API.md index 2f2a5d44f1..519f9001f7 100644 --- a/packages/cli-core/API.md +++ b/packages/cli-core/API.md @@ -4,6 +4,8 @@ ```ts +/// + // @public export class AmplifyPrompter { static input: (options: { @@ -23,12 +25,24 @@ export enum COLOR { RED = "31m" } +// @public (undocumented) +export enum LogLevel { + // (undocumented) + DEBUG = 2, + // (undocumented) + ERROR = 0, + // (undocumented) + INFO = 1 +} + // @public export class Printer { - static print: (message: string, colorName?: COLOR) => void; - static printNewLine: () => void; - static printRecord: >(object: T) => void; - static printRecords: >(objects: T[]) => void; + constructor(minimumLogLevel: LogLevel, stdout?: NodeJS.WriteStream, stderr?: NodeJS.WriteStream, refreshRate?: number); + indicateProgress(message: string, callback: () => Promise): Promise; + log(message: string, level?: LogLevel, eol?: boolean): void; + print: (message: string, colorName?: COLOR) => void; + printNewLine: () => void; + printRecords: >(...objects: T[]) => void; } // @public (undocumented) diff --git a/packages/cli-core/src/printer/.eslintrc.json b/packages/cli-core/src/printer/.eslintrc.json deleted file mode 100644 index d5ba8f9d9c..0000000000 --- a/packages/cli-core/src/printer/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rules": { - "no-console": "off" - } -} diff --git a/packages/cli-core/src/printer/printer.test.ts b/packages/cli-core/src/printer/printer.test.ts new file mode 100644 index 0000000000..1aaa2ee708 --- /dev/null +++ b/packages/cli-core/src/printer/printer.test.ts @@ -0,0 +1,96 @@ +import { after, before, beforeEach, describe, it, mock } from 'node:test'; +import assert from 'assert'; +import { LogLevel, Printer } from './printer.js'; + +void describe('Printer', () => { + const mockedWrite = mock.method(process.stdout, 'write'); + let originalWrite: typeof process.stdout.write; + + before(() => { + originalWrite = process.stdout.write; + process.stdout.write = mockedWrite; + }); + + after(() => { + // restore write function after all tests. + process.stdout.write = originalWrite; + }); + + beforeEach(() => { + mockedWrite.mock.resetCalls(); + }); + + void it('log should print message followed by new line', () => { + new Printer(LogLevel.INFO).log('hello world'); + assert.strictEqual(mockedWrite.mock.callCount(), 2); + assert.match( + mockedWrite.mock.calls[0].arguments[0].toString(), + /hello world/ + ); + assert.match(mockedWrite.mock.calls[1].arguments[0].toString(), /\n/); + }); + + void it('log should print message without new line', () => { + new Printer(LogLevel.INFO).log('hello world', LogLevel.INFO, false); + assert.strictEqual(mockedWrite.mock.callCount(), 1); + assert.match( + mockedWrite.mock.calls[0].arguments[0].toString(), + /hello world/ + ); + }); + + void it('log should not print debug logs by default', () => { + new Printer(LogLevel.INFO).log('hello world', LogLevel.DEBUG); + assert.strictEqual(mockedWrite.mock.callCount(), 0); + }); + + void it('log should print debug logs when printer is configured with minimum log level >= DEBUG', () => { + new Printer(LogLevel.DEBUG).log('hello world', LogLevel.DEBUG); + assert.strictEqual(mockedWrite.mock.callCount(), 2); + assert.match( + mockedWrite.mock.calls[0].arguments[0].toString(), + /hello world/ + ); + assert.match(mockedWrite.mock.calls[1].arguments[0].toString(), /\n/); + }); + + void it('log should not print debug logs by default', () => { + new Printer(LogLevel.INFO).log('hello world', LogLevel.DEBUG); + assert.strictEqual(mockedWrite.mock.callCount(), 0); + }); + + void it('indicateProgress logs message & animates ellipsis if on TTY', async () => { + process.stdout.isTTY = true; + await new Printer(LogLevel.INFO).indicateProgress( + 'loading a long list', + () => new Promise((resolve) => setTimeout(resolve, 3000)) + ); + // filter out the escape characters. + const logMessages = mockedWrite.mock.calls + .filter((message) => + message.arguments.toString().match(/loading a long list/) + ) + .map((call) => call.arguments.toString()); + + logMessages.forEach((message) => { + assert.match(message, /loading a long list(.*)/); + }); + }); + + void it('indicateProgress does not animates ellipsis if not TTY & prints log message once', async () => { + process.stdout.isTTY = false; + await new Printer(LogLevel.INFO).indicateProgress( + 'loading a long list', + () => new Promise((resolve) => setTimeout(resolve, 1500)) + ); + // filter out the escape characters. + const logMessages = mockedWrite.mock.calls + .filter((message) => + message.arguments.toString().match(/loading a long list/) + ) + .map((call) => call.arguments.toString()); + + assert.strictEqual(logMessages.length, 1); + assert.match(logMessages[0], /loading a long list/); + }); +}); diff --git a/packages/cli-core/src/printer/printer.ts b/packages/cli-core/src/printer/printer.ts index 1bec5664cd..7fab563e03 100644 --- a/packages/cli-core/src/printer/printer.ts +++ b/packages/cli-core/src/printer/printer.ts @@ -4,28 +4,28 @@ import { EOL } from 'os'; export type RecordValue = string | number | string[] | Date; /** - * The class that pretty prints to the console. + * The class that pretty prints to the output stream. */ export class Printer { + // Properties for ellipsis animation + private timer: ReturnType; + private timerSet: boolean; + /** - * Print an object/record to console. + * Sets default configs */ - static printRecord = >( - object: T - ): void => { - let message = ''; - const entries = Object.entries(object); - entries.forEach(([key, val]) => { - message = message.concat(` ${key}: ${val as string}${EOL}`); - }); - console.log(message); - }; + constructor( + private readonly minimumLogLevel: LogLevel, + private readonly stdout: NodeJS.WriteStream = process.stdout, + private readonly stderr: NodeJS.WriteStream = process.stderr, + private readonly refreshRate: number = 500 + ) {} /** - * Prints an array of objects/records to console. + * Prints an array of objects/records to output stream. */ - static printRecords = >( - objects: T[] + printRecords = >( + ...objects: T[] ): void => { for (const obj of objects) { this.printRecord(obj); @@ -33,20 +33,146 @@ export class Printer { }; /** - * Prints a given message (with optional color) to console. + * Prints a given message (with optional color) to output stream. */ - static print = (message: string, colorName?: COLOR) => { + print = (message: string, colorName?: COLOR) => { if (colorName) { - console.log(color(colorName, message)); + this.stdout.write(color(colorName, message)); } else { - console.log(message); + this.stdout.write(message); } }; /** - * Prints a new line to console + * Logs a message with animated ellipsis */ - static printNewLine = () => { - console.log(EOL); + async indicateProgress(message: string, callback: () => Promise) { + try { + this.startAnimatingEllipsis(message); + await callback(); + } finally { + this.stopAnimatingEllipsis(message); + } + } + + /** + * Prints a new line to output stream + */ + printNewLine = () => { + this.stdout.write(EOL); }; + + /** + * Logs a message to the output stream. + */ + log(message: string, level: LogLevel = LogLevel.INFO, eol = true) { + const doLogMessage = level <= this.minimumLogLevel; + + if (!doLogMessage) { + return; + } + + const logMessage = + this.minimumLogLevel === LogLevel.DEBUG + ? `[${LogLevel[level]}] ${new Date().toISOString()}: ${message}` + : message; + + if (level === LogLevel.ERROR) { + this.stderr.write(logMessage); + } else { + this.stdout.write(logMessage); + } + + if (eol) { + this.printNewLine(); + } + } + + /** + * Print an object/record to output stream. + */ + private printRecord = >( + object: T + ): void => { + let message = ''; + const entries = Object.entries(object); + entries.forEach(([key, val]) => { + message = message.concat(` ${key}: ${val as string}${EOL}`); + }); + this.stdout.write(message); + }; + + /** + * Start animating ellipsis at the end of a log message. + */ + private startAnimatingEllipsis(message: string) { + if (!this.isTTY()) { + this.log(message, LogLevel.INFO); + return; + } + + if (this.timerSet) { + throw new Error( + 'Timer is already set to animate ellipsis, stop the current running timer before starting a new one.' + ); + } + + const frameLength = 4; // number of desired dots - 1 + let frameCount = 0; + this.timerSet = true; + this.writeEscapeSequence(EscapeSequence.HIDE_CURSOR); + this.stdout.write(message); + this.timer = setInterval(() => { + this.writeEscapeSequence(EscapeSequence.CLEAR_LINE); + this.writeEscapeSequence(EscapeSequence.MOVE_CURSOR_TO_START); + this.stdout.write(message + '.'.repeat(++frameCount % frameLength)); + }, this.refreshRate); + } + + /** + * Stops animating ellipsis and replace with a log message. + */ + private stopAnimatingEllipsis(message: string) { + if (!this.isTTY()) { + return; + } + + clearInterval(this.timer); + this.timerSet = false; + this.writeEscapeSequence(EscapeSequence.CLEAR_LINE); + this.writeEscapeSequence(EscapeSequence.MOVE_CURSOR_TO_START); + this.writeEscapeSequence(EscapeSequence.SHOW_CURSOR); + this.stdout.write(`${message}...${EOL}`); + } + + /** + * Writes escape sequence to stdout + */ + private writeEscapeSequence(action: EscapeSequence) { + if (!this.isTTY()) { + return; + } + + this.stdout.write(action); + } + + /** + * Checks if the environment is TTY + */ + private isTTY() { + return this.stdout.isTTY; + } +} + +export enum LogLevel { + ERROR = 0, + INFO = 1, + DEBUG = 2, +} + +enum EscapeSequence { + CLEAR_LINE = '\x1b[2K', + MOVE_CURSOR_TO_START = '\x1b[0G', + SHOW_CURSOR = '\x1b[?25h', + HIDE_CURSOR = '\x1b[?25l', } diff --git a/packages/cli/src/commands/configure/configure_profile_command.test.ts b/packages/cli/src/commands/configure/configure_profile_command.test.ts index b99266b9bf..4931d402bd 100644 --- a/packages/cli/src/commands/configure/configure_profile_command.test.ts +++ b/packages/cli/src/commands/configure/configure_profile_command.test.ts @@ -3,9 +3,10 @@ import yargs, { CommandModule } from 'yargs'; import { TestCommandRunner } from '../../test-utils/command_runner.js'; import assert from 'node:assert'; import { ConfigureProfileCommand } from './configure_profile_command.js'; -import { AmplifyPrompter, Printer } from '@aws-amplify/cli-core'; +import { AmplifyPrompter } from '@aws-amplify/cli-core'; import { Open } from '../open/open.js'; import { ProfileController } from './profile_controller.js'; +import { printer } from '../../printer.js'; const testAccessKeyId = 'testAccessKeyId'; const testSecretAccessKey = 'testSecretAccessKey'; @@ -35,7 +36,7 @@ void describe('configure command', () => { 'profileExists', () => Promise.resolve(true) ); - const mockPrint = contextual.mock.method(Printer, 'print'); + const mockPrint = contextual.mock.method(printer, 'print'); await commandRunner.runCommand(`profile --name ${testProfile}`); diff --git a/packages/cli/src/commands/configure/configure_profile_command.ts b/packages/cli/src/commands/configure/configure_profile_command.ts index c40967db0a..a3e3d862af 100644 --- a/packages/cli/src/commands/configure/configure_profile_command.ts +++ b/packages/cli/src/commands/configure/configure_profile_command.ts @@ -1,10 +1,11 @@ import { Argv, CommandModule } from 'yargs'; -import { AmplifyPrompter, Printer } from '@aws-amplify/cli-core'; +import { AmplifyPrompter } from '@aws-amplify/cli-core'; import { DEFAULT_PROFILE } from '@smithy/shared-ini-file-loader'; import { EOL } from 'os'; import { Open } from '../open/open.js'; import { ArgumentsKebabCase } from '../../kebab_case.js'; import { ProfileController } from './profile_controller.js'; +import { printer } from '../../printer.js'; const configureAccountUrl = 'https://docs.amplify.aws/gen2/start/account-setup/'; @@ -44,7 +45,7 @@ export class ConfigureProfileCommand profileName ); if (profileExists) { - Printer.print( + printer.print( `Profile '${profileName}' already exists!${EOL}${profileSetupInstruction}` ); return; @@ -54,7 +55,7 @@ export class ConfigureProfileCommand }); if (!hasIAMUser) { - Printer.print(profileSetupInstruction); + printer.print(profileSetupInstruction); await Open.open(configureAccountUrl, { wait: false }); await AmplifyPrompter.input({ @@ -80,7 +81,7 @@ export class ConfigureProfileCommand secretAccessKey, }); - Printer.print(`Created profile ${profileName} successfully!`); + printer.print(`Created profile ${profileName} successfully!`); }; /** diff --git a/packages/cli/src/commands/configure/telemetry/configure_telemetry_command.test.ts b/packages/cli/src/commands/configure/telemetry/configure_telemetry_command.test.ts index 0947296ac7..67dfe573a6 100644 --- a/packages/cli/src/commands/configure/telemetry/configure_telemetry_command.test.ts +++ b/packages/cli/src/commands/configure/telemetry/configure_telemetry_command.test.ts @@ -1,4 +1,3 @@ -import { Printer } from '@aws-amplify/cli-core'; import { beforeEach, describe, it, mock } from 'node:test'; import yargs, { CommandModule } from 'yargs'; import assert from 'node:assert'; @@ -8,6 +7,7 @@ import { USAGE_DATA_TRACKING_ENABLED, configControllerFactory, } from '@aws-amplify/platform-core'; +import { printer } from '../../../printer.js'; void describe('configure command', () => { const mockedConfigControllerSet = mock.fn(); @@ -19,18 +19,17 @@ void describe('configure command', () => { ); const parser = yargs().command(telemetryCommand as unknown as CommandModule); const commandRunner = new TestCommandRunner(parser); - - const mockedPrint = mock.method(Printer, 'print'); + const logMock = mock.method(printer, 'log'); beforeEach(() => { - mockedPrint.mock.resetCalls(); + logMock.mock.resetCalls(); mockedConfigControllerSet.mock.resetCalls(); }); void it('enable telemetry & updates local config', async () => { await commandRunner.runCommand(`telemetry enable`); assert.match( - mockedPrint.mock.calls[0].arguments[0], + logMock.mock.calls[0].arguments[0], /You have enabled telemetry data collection/ ); assert.strictEqual( @@ -46,7 +45,7 @@ void describe('configure command', () => { void it('disables telemetry & updates local config', async () => { await commandRunner.runCommand(`telemetry disable`); assert.match( - mockedPrint.mock.calls[0].arguments[0], + logMock.mock.calls[0].arguments[0], /You have disabled telemetry data collection/ ); assert.strictEqual( @@ -60,9 +59,9 @@ void describe('configure command', () => { }); void it('if subcommand is not defined, it should list of subcommands and demandCommand', async () => { - await commandRunner.runCommand(`telemetry`); + const output = await commandRunner.runCommand(`telemetry`); assert.match( - mockedPrint.mock.calls[0].arguments[0], + output, /Not enough non-option arguments: got 0, need at least 1/ ); assert.strictEqual(mockedConfigControllerSet.mock.callCount(), 0); diff --git a/packages/cli/src/commands/configure/telemetry/configure_telemetry_command.ts b/packages/cli/src/commands/configure/telemetry/configure_telemetry_command.ts index 7011d59d58..6c7aada1fd 100644 --- a/packages/cli/src/commands/configure/telemetry/configure_telemetry_command.ts +++ b/packages/cli/src/commands/configure/telemetry/configure_telemetry_command.ts @@ -1,9 +1,9 @@ -import { Printer } from '@aws-amplify/cli-core'; import { ConfigurationController, USAGE_DATA_TRACKING_ENABLED, } from '@aws-amplify/platform-core'; import { Argv, CommandModule } from 'yargs'; +import { printer } from '../../../printer.js'; /** * Command to configure AWS Amplify profile. */ @@ -43,13 +43,11 @@ export class ConfigureTelemetryCommand implements CommandModule { return yargs .command('enable', 'Enable anonymous data collection', {}, async () => { await this.configController.set(USAGE_DATA_TRACKING_ENABLED, true); - - Printer.print('You have enabled telemetry data collection'); + printer.log('You have enabled telemetry data collection'); }) .command('disable', 'Disable anonymous data collection', {}, async () => { await this.configController.set(USAGE_DATA_TRACKING_ENABLED, false); - - Printer.print('You have disabled telemetry data collection'); + printer.log('You have disabled telemetry data collection'); }) .demandCommand() .strictCommands() diff --git a/packages/cli/src/commands/open/open.ts b/packages/cli/src/commands/open/open.ts index 43ac766325..cb78e3408b 100644 --- a/packages/cli/src/commands/open/open.ts +++ b/packages/cli/src/commands/open/open.ts @@ -1,6 +1,6 @@ import opn, { Options } from 'open'; import { ChildProcess } from 'child_process'; -import { Printer } from '@aws-amplify/cli-core'; +import { printer } from '../../printer.js'; /** * Helper class to open apps (URLs, files, executable). Cross-platform. @@ -22,9 +22,9 @@ export class Open { }; static handleOpenError = (err: Error, target: string) => { - Printer.print(`Unable to open ${target}: ${err.message}`); + printer.log(`Unable to open ${target}: ${err.message}`); if ('code' in err && err['code'] === 'ENOENT') { - Printer.print('Have you installed `xdg-utils` on your machine?'); + printer.log('Have you installed `xdg-utils` on your machine?'); } }; } diff --git a/packages/cli/src/commands/sandbox/sandbox-delete/sandbox_delete_command.test.ts b/packages/cli/src/commands/sandbox/sandbox-delete/sandbox_delete_command.test.ts index 4b69fde97b..3beaa74d7e 100644 --- a/packages/cli/src/commands/sandbox/sandbox-delete/sandbox_delete_command.test.ts +++ b/packages/cli/src/commands/sandbox/sandbox-delete/sandbox_delete_command.test.ts @@ -9,6 +9,7 @@ import { SandboxSingletonFactory } from '@aws-amplify/sandbox'; import { createSandboxSecretCommand } from '../sandbox-secret/sandbox_secret_command_factory.js'; import { ClientConfigGeneratorAdapter } from '../../../client-config/client_config_generator_adapter.js'; import { CommandMiddleware } from '../../../command_middleware.js'; +import { printer } from '../../../printer.js'; void describe('sandbox delete command', () => { let commandRunner: TestCommandRunner; @@ -22,12 +23,14 @@ void describe('sandbox delete command', () => { ); beforeEach(async () => { - const sandboxFactory = new SandboxSingletonFactory(() => - Promise.resolve({ - namespace: 'testSandboxId', - name: 'testSandboxName', - type: 'sandbox', - }) + const sandboxFactory = new SandboxSingletonFactory( + () => + Promise.resolve({ + namespace: 'testSandboxId', + name: 'testSandboxName', + type: 'sandbox', + }), + printer ); const sandbox = await sandboxFactory.getInstance(); sandboxDeleteMock = mock.method(sandbox, 'delete', () => diff --git a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_command_factory.ts b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_command_factory.ts index 76e67b5e6c..7fb2fb3f4d 100644 --- a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_command_factory.ts +++ b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_command_factory.ts @@ -1,14 +1,14 @@ import { CommandModule } from 'yargs'; -import { LocalNamespaceResolver } from '../../../backend-identifier/local_namespace_resolver.js'; -import { SandboxBackendIdResolver } from '../sandbox_id_resolver.js'; import { PackageJsonReader } from '@aws-amplify/platform-core'; -import { SandboxSecretCommand } from './sandbox_secret_command.js'; import { getSecretClient } from '@aws-amplify/backend-secret'; +import { SandboxSecretCommand } from './sandbox_secret_command.js'; import { SandboxSecretSetCommand } from './sandbox_secret_set_command.js'; import { SandboxSecretRemoveCommand } from './sandbox_secret_remove_command.js'; import { SandboxSecretGetCommand } from './sandbox_secret_get_command.js'; import { SandboxSecretListCommand } from './sandbox_secret_list_command.js'; +import { SandboxBackendIdResolver } from '../sandbox_id_resolver.js'; +import { LocalNamespaceResolver } from '../../../backend-identifier/local_namespace_resolver.js'; /** * Creates sandbox secret commands. diff --git a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_get_command.test.ts b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_get_command.test.ts index cf447b8510..f8dd518ceb 100644 --- a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_get_command.test.ts +++ b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_get_command.test.ts @@ -9,7 +9,9 @@ import { getSecretClient, } from '@aws-amplify/backend-secret'; import { SandboxSecretGetCommand } from './sandbox_secret_get_command.js'; -import { Printer } from '@aws-amplify/cli-core'; +import { printer } from '../../../printer.js'; + +const printRecordsMock = mock.method(printer, 'printRecords'); const testSecretName = 'testSecretName'; const testBackendId = 'testBackendId'; @@ -53,11 +55,10 @@ void describe('sandbox secret get command', () => { beforeEach(async () => { secretGetMock.mock.resetCalls(); + printRecordsMock.mock.resetCalls(); }); - void it('gets a secret', async (contextual) => { - const mockPrintRecord = contextual.mock.method(Printer, 'printRecord'); - + void it('gets a secret', async () => { await commandRunner.runCommand(`get ${testSecretName}`); assert.equal(secretGetMock.mock.callCount(), 1); @@ -70,9 +71,9 @@ void describe('sandbox secret get command', () => { testSecretIdentifier, ]); - assert.equal(mockPrintRecord.mock.callCount(), 1); + assert.equal(printRecordsMock.mock.callCount(), 1); assert.deepStrictEqual( - mockPrintRecord.mock.calls[0].arguments[0], + printRecordsMock.mock.calls[0].arguments[0], testSecret ); }); diff --git a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_get_command.ts b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_get_command.ts index d1753831cb..3f54bf3248 100644 --- a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_get_command.ts +++ b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_get_command.ts @@ -1,8 +1,8 @@ import { Argv, CommandModule } from 'yargs'; import { SecretClient } from '@aws-amplify/backend-secret'; import { SandboxBackendIdResolver } from '../sandbox_id_resolver.js'; -import { Printer } from '@aws-amplify/cli-core'; import { ArgumentsKebabCase } from '../../../kebab_case.js'; +import { printer } from '../../../printer.js'; /** * Command to get sandbox secret. @@ -39,7 +39,7 @@ export class SandboxSecretGetCommand const secret = await this.secretClient.getSecret(sandboxBackendIdentifier, { name: args['secret-name'], }); - Printer.printRecord(secret); + printer.printRecords(secret); }; /** diff --git a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_list_command.test.ts b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_list_command.test.ts index e9fe46f357..049a1ff6c0 100644 --- a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_list_command.test.ts +++ b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_list_command.test.ts @@ -5,7 +5,7 @@ import assert from 'node:assert'; import { SandboxBackendIdResolver } from '../sandbox_id_resolver.js'; import { Secret, getSecretClient } from '@aws-amplify/backend-secret'; import { SandboxSecretListCommand } from './sandbox_secret_list_command.js'; -import { Printer } from '@aws-amplify/cli-core'; +import { printer } from '../../../printer.js'; const testBackendId = 'testBackendId'; const testSandboxName = 'testSandboxName'; @@ -20,6 +20,7 @@ const testSecrets: Secret[] = [ value: 'val2', }, ]; +const printRecordsMock = mock.method(printer, 'printRecords'); void describe('sandbox secret list command', () => { const secretClient = getSecretClient(); @@ -47,11 +48,10 @@ void describe('sandbox secret list command', () => { beforeEach(async () => { secretListMock.mock.resetCalls(); + printRecordsMock.mock.resetCalls(); }); - void it('list secrets', async (contextual) => { - const mockPrintRecord = contextual.mock.method(Printer, 'printRecord'); - + void it('list secrets', async () => { await commandRunner.runCommand(`list`); assert.equal(secretListMock.mock.callCount(), 1); @@ -61,8 +61,8 @@ void describe('sandbox secret list command', () => { type: 'sandbox', }); - assert.equal(mockPrintRecord.mock.callCount(), 1); - assert.deepStrictEqual(mockPrintRecord.mock.calls[0].arguments[0], { + assert.equal(printRecordsMock.mock.callCount(), 1); + assert.deepStrictEqual(printRecordsMock.mock.calls[0].arguments[0], { names: testSecrets.map((s) => s.name), }); }); diff --git a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_list_command.ts b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_list_command.ts index cb0609ef3d..be4e3cf4d3 100644 --- a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_list_command.ts +++ b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_list_command.ts @@ -1,7 +1,7 @@ import { CommandModule } from 'yargs'; import { SecretClient } from '@aws-amplify/backend-secret'; import { SandboxBackendIdResolver } from '../sandbox_id_resolver.js'; -import { Printer } from '@aws-amplify/cli-core'; +import { printer } from '../../../printer.js'; /** * Command to list sandbox secrets. @@ -37,7 +37,7 @@ export class SandboxSecretListCommand implements CommandModule { sandboxBackendIdentifier ); - Printer.printRecord({ + printer.printRecords({ names: secretIds.map((secretId) => secretId.name), }); }; diff --git a/packages/cli/src/commands/sandbox/sandbox_command.test.ts b/packages/cli/src/commands/sandbox/sandbox_command.test.ts index e24bb996a1..f692ec54cf 100644 --- a/packages/cli/src/commands/sandbox/sandbox_command.test.ts +++ b/packages/cli/src/commands/sandbox/sandbox_command.test.ts @@ -16,6 +16,7 @@ import { createSandboxSecretCommand } from './sandbox-secret/sandbox_secret_comm import { ClientConfigGeneratorAdapter } from '../../client-config/client_config_generator_adapter.js'; import { CommandMiddleware } from '../../command_middleware.js'; import path from 'path'; +import { printer } from '../../printer.js'; void describe('sandbox command factory', () => { void it('instantiate a sandbox command correctly', () => { @@ -44,12 +45,14 @@ void describe('sandbox command', () => { const sandboxProfile = 'test-sandbox'; beforeEach(async () => { - const sandboxFactory = new SandboxSingletonFactory(() => - Promise.resolve({ - namespace: 'testSandboxId', - name: 'testSandboxName', - type: 'sandbox', - }) + const sandboxFactory = new SandboxSingletonFactory( + () => + Promise.resolve({ + namespace: 'testSandboxId', + name: 'testSandboxName', + type: 'sandbox', + }), + printer ); sandbox = await sandboxFactory.getInstance(); @@ -238,12 +241,14 @@ void describe('sandbox command', () => { void it('starts sandbox with user provided valid AWS profile', async () => { mockHandleProfile.mock.mockImplementationOnce(() => null); - const sandboxFactory = new SandboxSingletonFactory(() => - Promise.resolve({ - namespace: 'testSandboxId', - name: 'testSandboxName', - type: 'sandbox', - }) + const sandboxFactory = new SandboxSingletonFactory( + () => + Promise.resolve({ + namespace: 'testSandboxId', + name: 'testSandboxName', + type: 'sandbox', + }), + printer ); sandbox = await sandboxFactory.getInstance(); sandboxStartMock = mock.method(sandbox, 'start', () => Promise.resolve()); diff --git a/packages/cli/src/commands/sandbox/sandbox_command_factory.ts b/packages/cli/src/commands/sandbox/sandbox_command_factory.ts index e1f5797ecf..e8e606ad8c 100644 --- a/packages/cli/src/commands/sandbox/sandbox_command_factory.ts +++ b/packages/cli/src/commands/sandbox/sandbox_command_factory.ts @@ -1,4 +1,5 @@ import { CommandModule } from 'yargs'; +import { fileURLToPath } from 'url'; import { SandboxCommand, SandboxCommandOptions } from './sandbox_command.js'; import { SandboxSingletonFactory } from '@aws-amplify/sandbox'; import { SandboxDeleteCommand } from './sandbox-delete/sandbox_delete_command.js'; @@ -13,7 +14,7 @@ import { } from '@aws-amplify/platform-core'; import { SandboxEventHandlerFactory } from './sandbox_event_handler_factory.js'; import { CommandMiddleware } from '../../command_middleware.js'; -import { fileURLToPath } from 'url'; +import { printer } from '../../printer.js'; /** * Creates wired sandbox command. @@ -44,7 +45,8 @@ export const createSandboxCommand = (): CommandModule< }; const sandboxFactory = new SandboxSingletonFactory( - sandboxBackendIdentifierResolver + sandboxBackendIdentifierResolver, + printer ); const clientConfigGeneratorAdapter = new ClientConfigGeneratorAdapter( credentialProvider diff --git a/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.test.ts b/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.test.ts index d37afff008..b9e4070ba4 100644 --- a/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.test.ts +++ b/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.test.ts @@ -12,7 +12,7 @@ import { ClientConfigLifecycleHandler } from '../../client-config/client_config_ import fs from 'fs'; import fsp from 'fs/promises'; import path from 'node:path'; -import { Printer } from '@aws-amplify/cli-core'; +import { printer } from '../../printer.js'; void describe('sandbox_event_handler_factory', () => { // client config mocks @@ -35,7 +35,7 @@ void describe('sandbox_event_handler_factory', () => { emitFailure: emitFailureMock, } as unknown as UsageDataEmitter; - const printerMock = mock.method(Printer, 'print'); + const printMock = mock.method(printer, 'print'); // Class under test const eventFactory = new SandboxEventHandlerFactory( @@ -169,12 +169,12 @@ void describe('sandbox_event_handler_factory', () => { ); assert.deepStrictEqual( - printerMock.mock.calls[0].arguments[0], + printMock.mock.calls[0].arguments[0], 'Amplify configuration could not be generated.' ); assert.deepStrictEqual( - printerMock.mock.calls[1].arguments[0], + printMock.mock.calls[1].arguments[0], 'test error message' ); diff --git a/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.ts b/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.ts index ad7e6124b9..8e1438409c 100644 --- a/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.ts +++ b/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.ts @@ -2,7 +2,8 @@ import { SandboxEventHandlerCreator } from './sandbox_command.js'; import { BackendIdentifier } from '@aws-amplify/plugin-types'; import { AmplifyError, UsageDataEmitter } from '@aws-amplify/platform-core'; import { DeployResult } from '@aws-amplify/backend-deployer'; -import { COLOR, Printer } from '@aws-amplify/cli-core'; +import { COLOR } from '@aws-amplify/cli-core'; +import { printer } from '../../printer.js'; /** * Coordinates creation of sandbox event handlers @@ -44,19 +45,19 @@ export class SandboxEventHandlerFactory { } } catch (error) { // Don't crash sandbox if config cannot be generated, but print the error message - Printer.print( + printer.print( 'Amplify configuration could not be generated.', COLOR.RED ); if (error instanceof Error) { - Printer.print(error.message, COLOR.RED); + printer.print(error.message, COLOR.RED); } else { try { - Printer.print(JSON.stringify(error, null, 2), COLOR.RED); + printer.print(JSON.stringify(error, null, 2), COLOR.RED); } catch { // fallback in case there's an error stringify the error // like with circular references. - Printer.print('Unknown error', COLOR.RED); + printer.print('Unknown error', COLOR.RED); } } } diff --git a/packages/cli/src/error_handler.test.ts b/packages/cli/src/error_handler.test.ts index 7820beb7a6..682460f0d8 100644 --- a/packages/cli/src/error_handler.test.ts +++ b/packages/cli/src/error_handler.test.ts @@ -4,13 +4,14 @@ import { generateCommandFailureHandler, } from './error_handler.js'; import { Argv } from 'yargs'; -import { COLOR, Printer } from '@aws-amplify/cli-core'; +import { COLOR } from '@aws-amplify/cli-core'; import assert from 'node:assert'; import { InvalidCredentialError } from './error/credential_error.js'; +import { printer } from './printer.js'; -void describe('generateCommandFailureHandler', () => { - const mockPrint = mock.method(Printer, 'print'); +const mockPrint = mock.method(printer, 'print'); +void describe('generateCommandFailureHandler', () => { const mockShowHelp = mock.fn(); const mockExit = mock.fn(); @@ -89,8 +90,6 @@ void describe('generateCommandFailureHandler', () => { }); void describe('attachUnhandledExceptionListeners', { concurrency: 1 }, () => { - const mockPrint = mock.method(Printer, 'print'); - before(() => { attachUnhandledExceptionListeners(); }); diff --git a/packages/cli/src/error_handler.ts b/packages/cli/src/error_handler.ts index 87a6c7b073..ded56ab88f 100644 --- a/packages/cli/src/error_handler.ts +++ b/packages/cli/src/error_handler.ts @@ -1,5 +1,6 @@ -import { COLOR, Printer } from '@aws-amplify/cli-core'; +import { COLOR } from '@aws-amplify/cli-core'; import { InvalidCredentialError } from './error/credential_error.js'; +import { printer } from './printer.js'; import { EOL } from 'os'; import { Argv } from 'yargs'; @@ -52,9 +53,9 @@ export const generateCommandFailureHandler = ( */ const handleCommandFailure = (message: string, error?: Error) => { const printHelp = () => { - Printer.printNewLine(); + printer.printNewLine(); parser.showHelp(); - Printer.printNewLine(); + printer.printNewLine(); }; handleError(error, printHelp, message); @@ -81,16 +82,16 @@ const handleError = ( } if (error instanceof InvalidCredentialError) { - Printer.print(`${error.message}${EOL}`, COLOR.RED); + printer.print(`${error.message}${EOL}`, COLOR.RED); return; } printMessagePreamble?.(); - Printer.print(message || String(error), COLOR.RED); + printer.print(message || String(error), COLOR.RED); if (errorHasCauseMessage(error)) { - Printer.print(error.cause.message, COLOR.RED); + printer.print(error.cause.message, COLOR.RED); } - Printer.printNewLine(); + printer.printNewLine(); }; const isUserForceClosePromptError = (err?: Error): boolean => { diff --git a/packages/cli/src/main_parser_factory.ts b/packages/cli/src/main_parser_factory.ts index 01ca16d682..7b0b99e149 100644 --- a/packages/cli/src/main_parser_factory.ts +++ b/packages/cli/src/main_parser_factory.ts @@ -16,6 +16,9 @@ export const createMainParser = (): Argv => { ); const parser = yargs() .version(packageJson.version ?? '') + // This option is being used indirectly to configure the log level of the Printer instance. + // refer: https://github.com/aws-amplify/amplify-backend/blob/main/packages/cli/src/printer.ts + .options('debug', { type: 'boolean', default: false }) .command(createGenerateCommand()) .command(createSandboxCommand()) .command(createPipelineDeployCommand()) diff --git a/packages/cli/src/printer.ts b/packages/cli/src/printer.ts new file mode 100644 index 0000000000..cc70ed21ed --- /dev/null +++ b/packages/cli/src/printer.ts @@ -0,0 +1,7 @@ +import { LogLevel, Printer } from '@aws-amplify/cli-core'; + +const minimumLogLevel = process.argv.includes('--debug') + ? LogLevel.DEBUG + : LogLevel.INFO; + +export const printer = new Printer(minimumLogLevel); diff --git a/packages/create-amplify/.eslintrc.json b/packages/create-amplify/.eslintrc.json index d5ba8f9d9c..0967ef424b 100644 --- a/packages/create-amplify/.eslintrc.json +++ b/packages/create-amplify/.eslintrc.json @@ -1,5 +1 @@ -{ - "rules": { - "no-console": "off" - } -} +{} diff --git a/packages/create-amplify/src/amplify_project_creator.test.ts b/packages/create-amplify/src/amplify_project_creator.test.ts index 64d23e6d6c..2a9f62d666 100644 --- a/packages/create-amplify/src/amplify_project_creator.test.ts +++ b/packages/create-amplify/src/amplify_project_creator.test.ts @@ -1,16 +1,18 @@ -import { describe, it, mock } from 'node:test'; +import { beforeEach, describe, it, mock } from 'node:test'; import assert from 'assert'; import { AmplifyProjectCreator } from './amplify_project_creator.js'; -import { logger } from './logger.js'; +import { printer } from './printer.js'; + +const logSpy = mock.method(printer, 'log'); +const indicateProgressSpy = mock.method(printer, 'indicateProgress'); void describe('AmplifyProjectCreator', () => { + beforeEach(() => { + logSpy.mock.resetCalls(); + indicateProgressSpy.mock.resetCalls(); + }); + void it('create project if passing `--yes` or `-y` to `npm create`', async () => { - const logMock = { - log: mock.fn(), - debug: mock.fn(), - startAnimatingEllipsis: mock.fn(), - stopAnimatingEllipsis: mock.fn(), - }; const packageManagerControllerMock = { installDependencies: mock.fn() }; const projectRootValidatorMock = { validate: mock.fn() }; const initialProjectFileGeneratorMock = { @@ -26,7 +28,6 @@ void describe('AmplifyProjectCreator', () => { gitIgnoreInitializerMock as never, process.cwd() ); - mock.method(logger, 'log', logMock.log); await amplifyProjectCreator.create(); assert.equal( packageManagerControllerMock.installDependencies.mock.callCount(), @@ -42,22 +43,16 @@ void describe('AmplifyProjectCreator', () => { 1 ); assert.equal( - logMock.log.mock.calls[4].arguments[0], + logSpy.mock.calls[4].arguments[0], 'Welcome to AWS Amplify! \nRun `npx amplify help` for a list of available commands. \nGet started by running `npx amplify sandbox`.' ); assert.equal( - logMock.log.mock.calls[5].arguments[0], + logSpy.mock.calls[5].arguments[0], `Amplify (Gen 2) collects anonymous telemetry data about general usage of the CLI.\n\nParticipation is optional, and you may opt-out by using \`amplify configure telemetry disable\`.\n\nTo learn more about telemetry, visit https://docs.amplify.aws/gen2/reference/telemetry` ); }); void it('should instruct users to use the custom project root', async () => { - const logMock = { - log: mock.fn(), - debug: mock.fn(), - startAnimatingEllipsis: mock.fn(), - stopAnimatingEllipsis: mock.fn(), - }; const packageManagerControllerMock = { installDependencies: mock.fn() }; const projectRootValidatorMock = { validate: mock.fn() }; const initialProjectFileGeneratorMock = { @@ -73,15 +68,14 @@ void describe('AmplifyProjectCreator', () => { gitIgnoreInitializerMock as never, '/project/root' ); - mock.method(logger, 'log', logMock.log); await amplifyProjectCreator.create(); assert.equal( - logMock.log.mock.calls[4].arguments[0], + logSpy.mock.calls[4].arguments[0], 'Welcome to AWS Amplify! \nRun `npx amplify help` for a list of available commands. \nGet started by running `cd ./project/root; npx amplify sandbox`.' ); assert.equal( - logMock.log.mock.calls[5].arguments[0], + logSpy.mock.calls[5].arguments[0], `Amplify (Gen 2) collects anonymous telemetry data about general usage of the CLI.\n\nParticipation is optional, and you may opt-out by using \`amplify configure telemetry disable\`.\n\nTo learn more about telemetry, visit https://docs.amplify.aws/gen2/reference/telemetry` ); }); diff --git a/packages/create-amplify/src/amplify_project_creator.ts b/packages/create-amplify/src/amplify_project_creator.ts index 6c40ec25e3..0780e76d5b 100644 --- a/packages/create-amplify/src/amplify_project_creator.ts +++ b/packages/create-amplify/src/amplify_project_creator.ts @@ -1,9 +1,10 @@ +import { LogLevel } from '@aws-amplify/cli-core'; import { PackageManagerController } from './package_manager_controller.js'; import { ProjectRootValidator } from './project_root_validator.js'; import { InitialProjectFileGenerator } from './initial_project_file_generator.js'; import { NpmProjectInitializer } from './npm_project_initializer.js'; import { GitIgnoreInitializer } from './gitignore_initializer.js'; -import { logger } from './logger.js'; +import { printer } from './printer.js'; const LEARN_MORE_USAGE_DATA_TRACKING_LINK = `https://docs.amplify.aws/gen2/reference/telemetry`; @@ -39,12 +40,15 @@ export class AmplifyProjectCreator { * Executes the create-amplify workflow */ create = async (): Promise => { - logger.debug(`Validating current state of target directory...`); + printer.log( + `Validating current state of target directory...`, + LogLevel.DEBUG + ); await this.projectRootValidator.validate(); await this.npmInitializedEnsurer.ensureInitialized(); - await logger.indicateProgress( + await printer.indicateProgress( `Installing required dependencies`, async () => { await this.packageManagerController.installDependencies( @@ -59,26 +63,26 @@ export class AmplifyProjectCreator { } ); - await logger.indicateProgress(`Creating template files`, async () => { + await printer.indicateProgress(`Creating template files`, async () => { await this.gitIgnoreInitializer.ensureInitialized(); await this.initialProjectFileGenerator.generateInitialProjectFiles(); }); - logger.log('Successfully created a new project!'); + printer.log('Successfully created a new project!'); const cdCommand = process.cwd() === this.projectRoot ? '' : `cd .${this.projectRoot.replace(process.cwd(), '')}; `; - logger.log( + printer.log( `Welcome to AWS Amplify! Run \`npx amplify help\` for a list of available commands. Get started by running \`${cdCommand}npx amplify sandbox\`.` ); - logger.log( + printer.log( `Amplify (Gen 2) collects anonymous telemetry data about general usage of the CLI. Participation is optional, and you may opt-out by using \`amplify configure telemetry disable\`. diff --git a/packages/create-amplify/src/create_amplify.ts b/packages/create-amplify/src/create_amplify.ts index 26c28651c8..2d885634f8 100644 --- a/packages/create-amplify/src/create_amplify.ts +++ b/packages/create-amplify/src/create_amplify.ts @@ -14,6 +14,8 @@ import { InitialProjectFileGenerator } from './initial_project_file_generator.js import { NpmProjectInitializer } from './npm_project_initializer.js'; import { getProjectRoot } from './get_project_root.js'; import { GitIgnoreInitializer } from './gitignore_initializer.js'; +import { LogLevel } from '@aws-amplify/cli-core'; +import { printer } from './printer.js'; const projectRoot = await getProjectRoot(); @@ -29,6 +31,9 @@ const amplifyProjectCreator = new AmplifyProjectCreator( try { await amplifyProjectCreator.create(); } catch (err) { - console.error(err instanceof Error ? err.message : err); + printer.log( + (err instanceof Error ? err.message : err) as string, + LogLevel.ERROR + ); process.exitCode = 1; } diff --git a/packages/create-amplify/src/execute_with_logger.test.ts b/packages/create-amplify/src/execute_with_logger.test.ts index cca744fe06..11607b0614 100644 --- a/packages/create-amplify/src/execute_with_logger.test.ts +++ b/packages/create-amplify/src/execute_with_logger.test.ts @@ -1,11 +1,15 @@ -import { describe, it, mock } from 'node:test'; -import { executeWithDebugLogger } from './execute_with_logger.js'; import assert from 'assert'; +import { beforeEach, describe, it, mock } from 'node:test'; +import { executeWithDebugLogger } from './execute_with_logger.js'; + +const execaMock = mock.fn(); void describe(() => { - void it('executes a command with no args', async () => { - const execaMock = mock.fn(); + beforeEach(() => { + execaMock.mock.resetCalls(); + }); + void it('executes a command with no args', async () => { await executeWithDebugLogger( '/testProjectRoot', 'testCommand', @@ -20,8 +24,6 @@ void describe(() => { }); void it('executes a command with args', async () => { - const execaMock = mock.fn(); - await executeWithDebugLogger( '/testProjectRoot', 'testCommand', diff --git a/packages/create-amplify/src/execute_with_logger.ts b/packages/create-amplify/src/execute_with_logger.ts index 46c4641b22..0f042a85ee 100644 --- a/packages/create-amplify/src/execute_with_logger.ts +++ b/packages/create-amplify/src/execute_with_logger.ts @@ -1,8 +1,9 @@ +import { LogLevel } from '@aws-amplify/cli-core'; import { execa as _execa } from 'execa'; -import { logger } from './logger.js'; +import { printer } from './printer.js'; /** - * Abstracts the execution of a command and pipes outputs/errors to `logger.debug` + * Abstracts the execution of a command and pipes outputs/errors to `Printer.debug` */ export const executeWithDebugLogger = async ( cwd: string, @@ -16,8 +17,12 @@ export const executeWithDebugLogger = async ( cwd, }); - childProcess?.stdout?.on('data', (data) => logger.debug(data)); - childProcess?.stderr?.on('data', (data) => logger.debug(data)); + childProcess?.stdout?.on('data', (data: string) => + printer.log(data, LogLevel.DEBUG) + ); + childProcess?.stderr?.on('data', (data: string) => + printer.log(data, LogLevel.DEBUG) + ); await childProcess; } catch { diff --git a/packages/create-amplify/src/get_project_root.ts b/packages/create-amplify/src/get_project_root.ts index 72892bd058..d132e59740 100644 --- a/packages/create-amplify/src/get_project_root.ts +++ b/packages/create-amplify/src/get_project_root.ts @@ -1,7 +1,7 @@ import fsp from 'fs/promises'; import path from 'path'; -import { AmplifyPrompter } from '@aws-amplify/cli-core'; -import { logger } from './logger.js'; +import { AmplifyPrompter, LogLevel } from '@aws-amplify/cli-core'; +import { printer } from './printer.js'; /** * Returns the project root directory. @@ -25,8 +25,11 @@ export const getProjectRoot = async () => { .then(() => true) .catch(() => false); // There's no `fsp.exists` method, so we use `stat` instead. See https://github.com/nodejs/node/issues/39960#issuecomment-909444667 if (!isExistProjectRoot) { - logger.debug(`The provided directory (${projectRoot}) does not exist.`); - logger.debug(`Creating directory ${projectRoot}`); + printer.log( + `The provided directory (${projectRoot}) does not exist.`, + LogLevel.DEBUG + ); + printer.log(`Creating directory ${projectRoot}`, LogLevel.DEBUG); await fsp.mkdir(projectRoot, { recursive: true }); } return projectRoot; diff --git a/packages/create-amplify/src/gitignore_initializer.test.ts b/packages/create-amplify/src/gitignore_initializer.test.ts index 72d21c6137..a7e7e4eb8b 100644 --- a/packages/create-amplify/src/gitignore_initializer.test.ts +++ b/packages/create-amplify/src/gitignore_initializer.test.ts @@ -1,15 +1,18 @@ -import { describe, it, mock } from 'node:test'; +import { beforeEach, describe, it, mock } from 'node:test'; import { GitIgnoreInitializer } from './gitignore_initializer.js'; import assert from 'assert'; import * as path from 'path'; import * as os from 'os'; -import { logger } from './logger.js'; +import { printer } from './printer.js'; void describe('GitIgnoreInitializer', () => { + const logMock = mock.method(printer, 'log'); + + beforeEach(() => { + logMock.mock.resetCalls(); + }); + void it('creates .gitignore and adds all contents if no .gitignore file exists', async () => { - const logMock = { - debug: mock.fn(), - }; const existsSyncMock = mock.fn(() => false); const fsMock = { appendFile: mock.fn(), @@ -25,10 +28,9 @@ void describe('GitIgnoreInitializer', () => { `.amplify${os.EOL}`, `amplifyconfiguration*${os.EOL}`, ]; - mock.method(logger, 'debug', logMock.debug); await gitIgnoreInitializer.ensureInitialized(); assert.equal( - logMock.debug.mock.calls[0].arguments[0], + logMock.mock.calls[0].arguments[0], 'No .gitignore file found in the working directory. Creating .gitignore...' ); assert.deepStrictEqual(fsMock.appendFile.mock.calls[0].arguments, [ diff --git a/packages/create-amplify/src/gitignore_initializer.ts b/packages/create-amplify/src/gitignore_initializer.ts index a8490312ce..43e13fc347 100644 --- a/packages/create-amplify/src/gitignore_initializer.ts +++ b/packages/create-amplify/src/gitignore_initializer.ts @@ -2,7 +2,8 @@ import { existsSync as _existsSync } from 'fs'; import _fs from 'fs/promises'; import * as path from 'path'; import * as os from 'os'; -import { logger } from './logger.js'; +import { LogLevel } from '@aws-amplify/cli-core'; +import { printer } from './printer.js'; /** * Ensure that the .gitignore file exists with the correct contents in the current working directory @@ -50,8 +51,9 @@ export class GitIgnoreInitializer { return; } - logger.debug( - 'No .gitignore file found in the working directory. Creating .gitignore...' + printer.log( + 'No .gitignore file found in the working directory. Creating .gitignore...', + LogLevel.DEBUG ); await this.addIgnorePatterns(ignorePatterns); diff --git a/packages/create-amplify/src/logger.test.ts b/packages/create-amplify/src/logger.test.ts deleted file mode 100644 index f0be7d329d..0000000000 --- a/packages/create-amplify/src/logger.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { describe, it, mock } from 'node:test'; -import assert from 'assert'; -import { LogLevel, Logger } from './logger.js'; - -void describe('Logger', () => { - void it('logs a message at INFO level', () => { - const mockStdout = { - write: mock.fn(), - }; - const logger = new Logger(LogLevel.INFO, mockStdout as never); - logger.info('Test log message'); - assert.equal(mockStdout.write.mock.callCount(), 1); - assert.match( - [...mockStdout.write.mock.calls[0].arguments][0] ?? '', - new RegExp(`Test log message`) - ); - }); - - void it('do NOT logs a debug message at INFO level', () => { - const mockStdout = { - write: mock.fn(), - }; - const logger = new Logger(LogLevel.INFO, mockStdout as never); - logger.debug('Test log message'); - assert.equal(mockStdout.write.mock.callCount(), 0); - }); - - void it('logs a debug message at DEBUG level', () => { - const mockStdout = { - write: mock.fn(), - }; - - const logger = new Logger(LogLevel.DEBUG, mockStdout as never); - logger.debug('Test log message'); - - assert.match( - [...mockStdout.write.mock.calls[0].arguments][0] ?? '', - new RegExp(`\\[DEBUG\\].*: Test log message`) - ); - }); - - void it('animating ellipsis is a noop in non-TTY terminal and instead logs a message at INFO level', async () => { - const mockStdout = { - write: mock.fn(), - }; - const mockLog = mock.fn(); - - const logger = new Logger(LogLevel.INFO, mockStdout as never); - - mock.method(logger, 'isTTY', () => false); - mock.method(logger, 'log', mockLog); - await logger.indicateProgress('Test log message', () => Promise.resolve()); - - assert.equal(mockStdout.write.mock.callCount(), 0); - assert.equal(mockLog.mock.callCount(), 1); - assert.match( - [...mockLog.mock.calls[0].arguments][0], - new RegExp('Test log message') - ); - assert.equal(mockLog.mock.calls[0].arguments[1], LogLevel.INFO); - }); - - void it('start animating ellipsis with message and stops animation in TTY terminal', async () => { - const mockStdout = { - write: mock.fn(), - }; - const mockWriteEscapeSequence = mock.fn(); - - const logger = new Logger(LogLevel.INFO, mockStdout as never); - - mock.method(logger, 'isTTY', () => true); - mock.method(logger, 'writeEscapeSequence', mockWriteEscapeSequence); - await logger.indicateProgress('Test log message', async () => { - // Wait for default refresh rate plus small delta - await new Promise((resolve) => setTimeout(resolve, 500 + 10)); - }); - - assert.equal(mockStdout.write.mock.callCount(), 3); - assert.equal(mockWriteEscapeSequence.mock.callCount(), 6); - - assert.match( - [...mockStdout.write.mock.calls[0].arguments][0], - new RegExp(`Test log message`) - ); - assert.match( - [...mockStdout.write.mock.calls[1].arguments][0], - new RegExp(`Test log message.`) - ); - assert.match( - [...mockStdout.write.mock.calls[2].arguments][0], - new RegExp('Test log message...') - ); - }); - - void it('throws if animating another message with ellipsis before old one is done', async () => { - const mockStdout = { - write: mock.fn(), - }; - const mockWriteEscapeSequence = mock.fn(); - - const logger = new Logger(LogLevel.INFO, mockStdout as never); - - mock.method(logger, 'isTTY', () => true); - mock.method(logger, 'writeEscapeSequence', mockWriteEscapeSequence); - await logger.indicateProgress('Test log message', async () => { - // call indicateProgress again during first one - await assert.rejects( - () => - logger.indicateProgress('Another log message', async () => { - await new Promise((resolve) => setTimeout(resolve, 500 + 10)); - }), - { - message: - 'Timer is already set to animate ellipsis, stop the current running timer before starting a new one.', - } - ); - await new Promise((resolve) => setTimeout(resolve, 500 + 10)); - }); - }); -}); diff --git a/packages/create-amplify/src/logger.ts b/packages/create-amplify/src/logger.ts deleted file mode 100644 index 4e95293151..0000000000 --- a/packages/create-amplify/src/logger.ts +++ /dev/null @@ -1,169 +0,0 @@ -import yargs from 'yargs'; -import * as os from 'os'; - -/** - * A logger that logs messages to the console. - */ -export class Logger { - // Properties for ellipsis animation - private timer: ReturnType; - private refreshRate: number; - private timerSet: boolean; - - /** - * Creates a new Logger instance. Injecting stdout for testing. - * @param minimumLogLevel The minimum log level to log. - * @param stdout The stream to write logs to. Injected for testing. - */ - constructor( - private readonly minimumLogLevel: LogLevel = LogLevel.INFO, - private readonly stdout = process.stdout - ) { - this.refreshRate = 500; // every 0.5 seconds - } - - /** - * Logs a message to the console. - */ - log(message: string, level: LogLevel = LogLevel.INFO) { - const toLogMessage = level <= this.minimumLogLevel; - - if (!toLogMessage) { - return; - } - - const logMessage = - this.minimumLogLevel === LogLevel.DEBUG - ? `[${LogLevel[level]}] ${new Date().toISOString()}: ${message}` - : message; - this.stdout.write(logMessage + os.EOL); - } - - /** - * Logs a message with animated ellipsis - */ - async indicateProgress(message: string, callback: () => Promise) { - try { - this.startAnimatingEllipsis(message); - await callback(); - } finally { - this.stopAnimatingEllipsis(message); - } - } - - /** - * Writes escape sequence to stdout - */ - writeEscapeSequence(action: EscapeSequence) { - if (!this.isTTY()) { - return; - } - - this.stdout.write(action); - } - - /** - * Checks if the environment is TTY - */ - isTTY() { - return this.stdout.isTTY; - } - - /** - * Logs an error to the console. - */ - error(message: string) { - this.log(message, LogLevel.ERROR); - } - - /** - * Logs a warning to the console. - */ - warn(message: string) { - this.log(message, LogLevel.WARNING); - } - - /** - * Logs an info message to the console. - */ - info(message: string) { - this.log(message, LogLevel.INFO); - } - - /** - * Logs a debug message to the console. - */ - debug(message: string) { - this.log(message, LogLevel.DEBUG); - } - - /** - * Start animating ellipsis at the end of a log message. - */ - private startAnimatingEllipsis(message: string) { - if (!this.isTTY()) { - this.log(message, LogLevel.INFO); - return; - } - - if (this.timerSet) { - throw new Error( - 'Timer is already set to animate ellipsis, stop the current running timer before starting a new one.' - ); - } - - const frameLength = 4; // number of desired dots - 1 - let frameCount = 0; - this.timerSet = true; - this.writeEscapeSequence(EscapeSequence.HIDE_CURSOR); - this.stdout.write(message); - this.timer = setInterval(() => { - this.writeEscapeSequence(EscapeSequence.CLEAR_LINE); - this.writeEscapeSequence(EscapeSequence.MOVE_CURSOR_TO_START); - this.stdout.write(message + '.'.repeat(++frameCount % frameLength)); - }, this.refreshRate); - } - - /** - * Stops animating ellipsis and replace with a log message. - */ - private stopAnimatingEllipsis(message: string) { - if (!this.isTTY()) { - return; - } - - clearInterval(this.timer); - this.timerSet = false; - this.writeEscapeSequence(EscapeSequence.CLEAR_LINE); - this.writeEscapeSequence(EscapeSequence.MOVE_CURSOR_TO_START); - this.writeEscapeSequence(EscapeSequence.SHOW_CURSOR); - this.stdout.write(`${message}...${os.EOL}`); - } -} - -enum EscapeSequence { - CLEAR_LINE = '\x1b[2K', - MOVE_CURSOR_TO_START = '\x1b[0G', - SHOW_CURSOR = '\x1b[?25h', - HIDE_CURSOR = '\x1b[?25l', -} - -export enum LogLevel { - ERROR, - WARNING, - INFO, - DEBUG, -} - -export const argv = await yargs(process.argv.slice(2)).options({ - debug: { - type: 'boolean', - default: false, - }, -}).argv; - -const minimumLogLevel = argv.debug ? LogLevel.DEBUG : LogLevel.INFO; - -const logger = new Logger(minimumLogLevel, process.stdout); - -export { logger }; diff --git a/packages/create-amplify/src/npm_project_initializer.test.ts b/packages/create-amplify/src/npm_project_initializer.test.ts index 8b2ad3a772..0562d266e5 100644 --- a/packages/create-amplify/src/npm_project_initializer.test.ts +++ b/packages/create-amplify/src/npm_project_initializer.test.ts @@ -1,8 +1,15 @@ -import { describe, it, mock } from 'node:test'; +import { beforeEach, describe, it, mock } from 'node:test'; import { NpmProjectInitializer } from './npm_project_initializer.js'; import assert from 'assert'; +import { printer } from './printer.js'; void describe('NpmInitializedEnsurer', () => { + const logMock = mock.method(printer, 'log'); + + beforeEach(() => { + logMock.mock.resetCalls(); + }); + void it('does nothing if package.json already exists', async () => { const existsSyncMock = mock.fn(() => true); const execaMock = mock.fn(); diff --git a/packages/create-amplify/src/npm_project_initializer.ts b/packages/create-amplify/src/npm_project_initializer.ts index b6679fa8e5..8b0a565029 100644 --- a/packages/create-amplify/src/npm_project_initializer.ts +++ b/packages/create-amplify/src/npm_project_initializer.ts @@ -1,8 +1,9 @@ import { existsSync as _existsSync } from 'fs'; import * as path from 'path'; import { execa as _execa } from 'execa'; -import { logger } from './logger.js'; import { executeWithDebugLogger } from './execute_with_logger.js'; +import { LogLevel } from '@aws-amplify/cli-core'; +import { printer } from './printer.js'; /** * Ensure that the current working directory is a valid JavaScript project @@ -25,8 +26,9 @@ export class NpmProjectInitializer { // if package.json already exists, no need to do anything return; } - logger.debug( - 'No package.json file found in the current directory. Running `npm init`...' + printer.log( + 'No package.json file found in the current directory. Running `npm init`...', + LogLevel.DEBUG ); try { diff --git a/packages/create-amplify/src/printer.ts b/packages/create-amplify/src/printer.ts new file mode 100644 index 0000000000..cc70ed21ed --- /dev/null +++ b/packages/create-amplify/src/printer.ts @@ -0,0 +1,7 @@ +import { LogLevel, Printer } from '@aws-amplify/cli-core'; + +const minimumLogLevel = process.argv.includes('--debug') + ? LogLevel.DEBUG + : LogLevel.INFO; + +export const printer = new Printer(minimumLogLevel); diff --git a/packages/sandbox/.eslintrc.json b/packages/sandbox/.eslintrc.json deleted file mode 100644 index d5ba8f9d9c..0000000000 --- a/packages/sandbox/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rules": { - "no-console": "off" - } -} diff --git a/packages/sandbox/API.md b/packages/sandbox/API.md index cc58a15b94..f327beb929 100644 --- a/packages/sandbox/API.md +++ b/packages/sandbox/API.md @@ -9,6 +9,7 @@ import { BackendIdentifier } from '@aws-amplify/plugin-types'; import { ClientConfigFormat } from '@aws-amplify/client-config'; import EventEmitter from 'events'; +import { Printer } from '@aws-amplify/cli-core'; // @public (undocumented) export type BackendIdSandboxResolver = (sandboxName?: string) => Promise; @@ -39,7 +40,7 @@ export type SandboxOptions = { // @public export class SandboxSingletonFactory { - constructor(sandboxIdResolver: BackendIdSandboxResolver); + constructor(sandboxIdResolver: BackendIdSandboxResolver, printer: Printer); getInstance: () => Promise; } diff --git a/packages/sandbox/src/file_watching_sandbox.test.ts b/packages/sandbox/src/file_watching_sandbox.test.ts index 7ddb250efe..ee96995cb9 100644 --- a/packages/sandbox/src/file_watching_sandbox.test.ts +++ b/packages/sandbox/src/file_watching_sandbox.test.ts @@ -17,7 +17,7 @@ import _open from 'open'; import { SecretListItem, getSecretClient } from '@aws-amplify/backend-secret'; import { ClientConfigFormat } from '@aws-amplify/client-config'; import { Sandbox } from './sandbox.js'; -import { AmplifyPrompter } from '@aws-amplify/cli-core'; +import { AmplifyPrompter, Printer } from '@aws-amplify/cli-core'; import { fileURLToPath } from 'url'; import { BackendIdentifier } from '@aws-amplify/plugin-types'; import { AmplifyUserError } from '@aws-amplify/platform-core'; @@ -48,10 +48,15 @@ const listSecretMock = mock.method(secretClient, 'listSecrets', () => newlyUpdatedSecretItem, ]) ); +const printer = { + log: mock.fn(), + print: mock.fn(), +}; const sandboxExecutor = new AmplifySandboxExecutor( backendDeployer, - secretClient + secretClient, + printer as unknown as Printer ); const backendDeployerDeployMock = mock.method(backendDeployer, 'deploy', () => @@ -113,6 +118,7 @@ void describe('Sandbox to check if region is bootstrapped', () => { async () => testSandboxBackendId, sandboxExecutor, cfnClientMock, + printer as unknown as Printer, openMock as never ); @@ -792,6 +798,7 @@ const setupAndStartSandbox = async ( }), testData.executor, testData.cfnClient, + printer as unknown as Printer, testData.open ?? _open ); diff --git a/packages/sandbox/src/file_watching_sandbox.ts b/packages/sandbox/src/file_watching_sandbox.ts index e6ebae7694..af9f774132 100644 --- a/packages/sandbox/src/file_watching_sandbox.ts +++ b/packages/sandbox/src/file_watching_sandbox.ts @@ -17,7 +17,12 @@ import { CloudFormationClient, DescribeStacksCommand, } from '@aws-sdk/client-cloudformation'; -import { AmplifyPrompter, COLOR, Printer } from '@aws-amplify/cli-core'; +import { + AmplifyPrompter, + COLOR, + LogLevel, + Printer, +} from '@aws-amplify/cli-core'; import { FilesChangesTracker, createFilesChangesTracker, @@ -52,6 +57,7 @@ export class FileWatchingSandbox extends EventEmitter implements Sandbox { private readonly backendIdSandboxResolver: BackendIdSandboxResolver, private readonly executor: AmplifySandboxExecutor, private readonly cfnClient: CloudFormationClient, + private readonly printer: Printer, private readonly open = _open ) { process.once('SIGINT', () => void this.stop()); @@ -85,7 +91,7 @@ export class FileWatchingSandbox extends EventEmitter implements Sandbox { ); const bootstrapped = await this.isBootstrapped(); if (!bootstrapped) { - console.log( + this.printer.log( 'The given region has not been bootstrapped. Sign in to console as a Root user or Admin to complete the bootstrap process and re-run the amplify sandbox command.' ); // get region from an available sdk client; @@ -98,7 +104,7 @@ export class FileWatchingSandbox extends EventEmitter implements Sandbox { this.outputFilesExcludedFromWatch = this.outputFilesExcludedFromWatch.concat(...ignoredPaths); - console.debug(`[Sandbox] Initializing...`); + this.printer.log(`[Sandbox] Initializing...`, LogLevel.DEBUG); // Since 'cdk deploy' is a relatively slow operation for a 'watch' process, // introduce a concurrency latch that tracks the state. // This way, if file change events arrive when a 'cdk deploy' is still executing, @@ -124,7 +130,7 @@ export class FileWatchingSandbox extends EventEmitter implements Sandbox { // TypeScript doesn't realize latch can change between 'awaits' ¯\_(ツ)_/¯, // and thinks the above 'while' condition is always 'false' without the cast latch = 'deploying'; - console.log( + this.printer.log( "[Sandbox] Detected file changes while previous deployment was in progress. Invoking 'sandbox' again" ); await this.deploy(options); @@ -140,7 +146,7 @@ export class FileWatchingSandbox extends EventEmitter implements Sandbox { await Promise.all( events.map(({ type: eventName, path }) => { this.filesChangesTracker.trackFileChange(path); - console.log( + this.printer.log( `[Sandbox] Triggered due to a file ${eventName} event: ${path}` ); }) @@ -150,7 +156,7 @@ export class FileWatchingSandbox extends EventEmitter implements Sandbox { } else { // this means latch is either 'deploying' or 'queued' latch = 'queued'; - console.log( + this.printer.log( '[Sandbox] Previous deployment is still in progress. ' + 'Will queue for another deployment after this one finishes' ); @@ -171,7 +177,7 @@ export class FileWatchingSandbox extends EventEmitter implements Sandbox { * @inheritdoc */ stop = async () => { - console.debug(`[Sandbox] Shutting down`); + this.printer.log(`[Sandbox] Shutting down`, LogLevel.DEBUG); // can be undefined if command exits before subscription await this.watcherSubscription?.unsubscribe(); }; @@ -180,14 +186,14 @@ export class FileWatchingSandbox extends EventEmitter implements Sandbox { * @inheritdoc */ delete = async (options: SandboxDeleteOptions) => { - console.log( + this.printer.log( '[Sandbox] Deleting all the resources in the sandbox environment...' ); await this.executor.destroy( await this.backendIdSandboxResolver(options.name) ); this.emit('successfulDeletion'); - console.log('[Sandbox] Finished deleting.'); + this.printer.log('[Sandbox] Finished deleting.'); }; private shouldValidateAppSources = (): boolean => { @@ -210,11 +216,11 @@ export class FileWatchingSandbox extends EventEmitter implements Sandbox { // not reset tracker prematurely this.shouldValidateAppSources ); - console.debug('[Sandbox] Running successfulDeployment event handlers'); + this.printer.log('[Sandbox] Deployment successful', LogLevel.DEBUG); this.emit('successfulDeployment', deployResult); } catch (error) { // Print a meaningful message - Printer.print(this.getErrorMessage(error), COLOR.RED); + this.printer.print(this.getErrorMessage(error), COLOR.RED); this.emit('failedDeployment', error); // If the error is because of a non-allowed destructive change such as @@ -239,7 +245,7 @@ export class FileWatchingSandbox extends EventEmitter implements Sandbox { * Just a shorthand console log to indicate whenever watcher is going idle */ private emitWatching = () => { - console.log(`[Sandbox] Watching for file changes...`); + this.printer.log(`[Sandbox] Watching for file changes...`); }; /** @@ -255,7 +261,7 @@ export class FileWatchingSandbox extends EventEmitter implements Sandbox { ) .filter((pattern: string) => { if (pattern.startsWith('!')) { - console.log( + this.printer.log( `[Sandbox] Pattern ${pattern} found in .gitignore. "${pattern.substring( 1 )}" will not be watched if other patterns in .gitignore are excluding it.` @@ -327,8 +333,9 @@ export class FileWatchingSandbox extends EventEmitter implements Sandbox { private handleUnsupportedDestructiveChanges = async ( options: SandboxOptions ) => { - console.error( - '[Sandbox] We cannot deploy your new changes. You can either revert them or recreate your sandbox with the new changes (deleting all user data)' + this.printer.print( + '[Sandbox] We cannot deploy your new changes. You can either revert them or recreate your sandbox with the new changes (deleting all user data)', + COLOR.RED ); // offer to recreate the sandbox with new properties const answer = await AmplifyPrompter.yesOrNo({ diff --git a/packages/sandbox/src/sandbox_executor.test.ts b/packages/sandbox/src/sandbox_executor.test.ts index 214c40c86d..41d0df6107 100644 --- a/packages/sandbox/src/sandbox_executor.test.ts +++ b/packages/sandbox/src/sandbox_executor.test.ts @@ -4,11 +4,17 @@ import { AmplifySandboxExecutor } from './sandbox_executor.js'; import { BackendDeployerFactory } from '@aws-amplify/backend-deployer'; import { SecretListItem, getSecretClient } from '@aws-amplify/backend-secret'; +const logMock = mock.fn(); +const mockedPrinter = { + log: mock.fn(), +}; + const backendDeployer = BackendDeployerFactory.getInstance(); const secretClient = getSecretClient(); const sandboxExecutor = new AmplifySandboxExecutor( backendDeployer, - secretClient + secretClient, + mockedPrinter as never ); const newlyUpdatedSecretItem: SecretListItem = { @@ -40,6 +46,7 @@ void describe('Sandbox executor', () => { backendDeployerDeployMock.mock.resetCalls(); validateAppSourcesProvider.mock.resetCalls(); listSecretMock.mock.resetCalls(); + logMock.mock.resetCalls(); }); void it('retrieves file change summary once (debounce)', async () => { diff --git a/packages/sandbox/src/sandbox_executor.ts b/packages/sandbox/src/sandbox_executor.ts index d6b5d7575c..cc4473902a 100644 --- a/packages/sandbox/src/sandbox_executor.ts +++ b/packages/sandbox/src/sandbox_executor.ts @@ -6,6 +6,7 @@ import { DestroyResult, } from '@aws-amplify/backend-deployer'; import { SecretClient } from '@aws-amplify/backend-secret'; +import { LogLevel, Printer } from '@aws-amplify/cli-core'; /** * Execute CDK commands. @@ -27,7 +28,8 @@ export class AmplifySandboxExecutor { */ constructor( private readonly backendDeployer: BackendDeployer, - private readonly secretClient: SecretClient + private readonly secretClient: SecretClient, + private readonly printer: Printer ) {} /** @@ -37,7 +39,7 @@ export class AmplifySandboxExecutor { backendId: BackendIdentifier, validateAppSourcesProvider: () => boolean ): Promise => { - console.debug('[Sandbox] Executing command `deploy`'); + this.printer.log('[Sandbox] Executing command `deploy`', LogLevel.DEBUG); const secretLastUpdated = await this.getSecretLastUpdated(backendId); return this.invoke(() => { @@ -55,7 +57,7 @@ export class AmplifySandboxExecutor { * Destroy sandbox. Do not swallow errors */ destroy = (backendId: BackendIdentifier): Promise => { - console.debug('[Sandbox] Executing command `destroy`'); + this.printer.log('[Sandbox] Executing command `destroy`', LogLevel.DEBUG); return this.invoke(() => this.backendDeployer.destroy(backendId)); }; diff --git a/packages/sandbox/src/sandbox_singleton_factory.ts b/packages/sandbox/src/sandbox_singleton_factory.ts index adc28738fe..158e9e84b7 100644 --- a/packages/sandbox/src/sandbox_singleton_factory.ts +++ b/packages/sandbox/src/sandbox_singleton_factory.ts @@ -4,6 +4,7 @@ import { BackendDeployerFactory } from '@aws-amplify/backend-deployer'; import { AmplifySandboxExecutor } from './sandbox_executor.js'; import { CloudFormationClient } from '@aws-sdk/client-cloudformation'; import { getSecretClient } from '@aws-amplify/backend-secret'; +import { Printer } from '@aws-amplify/cli-core'; /** * Factory to create a new sandbox @@ -13,7 +14,10 @@ export class SandboxSingletonFactory { /** * sandboxIdResolver allows sandbox to lazily load the sandbox backend id on demand */ - constructor(private readonly sandboxIdResolver: BackendIdSandboxResolver) {} + constructor( + private readonly sandboxIdResolver: BackendIdSandboxResolver, + private readonly printer: Printer + ) {} /** * Returns a singleton instance of a Sandbox @@ -24,9 +28,11 @@ export class SandboxSingletonFactory { this.sandboxIdResolver, new AmplifySandboxExecutor( BackendDeployerFactory.getInstance(), - getSecretClient() + getSecretClient(), + this.printer ), - new CloudFormationClient() + new CloudFormationClient(), + this.printer ); } return this.instance;